UX: Show suggestion buttons only if sufficient content is present (#204)

This commit is contained in:
Keegan George 2023-09-06 12:20:08 -07:00 committed by GitHub
parent 8d674c451a
commit 0733ff7e67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 59 deletions

View File

@ -1,5 +1,4 @@
import Component from '@glimmer/component'; import Component from '@glimmer/component';
import DButton from "discourse/components/d-button";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
@ -8,17 +7,21 @@ import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import I18n from "I18n"; import I18n from "I18n";
import DButton from "discourse/components/d-button";
export default class AISuggestionDropdown extends Component { export default class AISuggestionDropdown extends Component {
<template> <template>
<DButton {{#if this.showAIButton}}
@class="suggestion-button {{if this.loading 'is-loading'}}" <DButton
@icon={{this.suggestIcon}} @class="suggestion-button {{if this.loading 'is-loading'}}"
@title="discourse_ai.ai_helper.suggest" @icon={{this.suggestIcon}}
@action={{this.performSuggestion}} @title="discourse_ai.ai_helper.suggest"
@disabled={{this.disableSuggestionButton}} @action={{this.performSuggestion}}
...attributes @disabled={{this.disableSuggestionButton}}
/> ...attributes
/>
{{/if}}
{{#if this.showMenu}} {{#if this.showMenu}}
{{! template-lint-disable modifier-name-case }} {{! template-lint-disable modifier-name-case }}
<ul class="popup-menu ai-suggestions-menu" {{didInsert this.handleClickOutside}}> <ul class="popup-menu ai-suggestions-menu" {{didInsert this.handleClickOutside}}>
@ -42,6 +45,7 @@ export default class AISuggestionDropdown extends Component {
@service dialog; @service dialog;
@service site; @service site;
@service siteSettings; @service siteSettings;
@service composer;
@tracked loading = false; @tracked loading = false;
@tracked showMenu = false; @tracked showMenu = false;
@tracked generatedSuggestions = []; @tracked generatedSuggestions = [];
@ -59,21 +63,15 @@ export default class AISuggestionDropdown extends Component {
document.removeEventListener("click", this.onClickOutside); document.removeEventListener("click", this.onClickOutside);
} }
get composerInput() { get showAIButton() {
return document.querySelector(".d-editor-input")?.value || this.args.composer.reply; const minCharacterCount = 40;
return this.composer.model.replyLength > minCharacterCount;
} }
get disableSuggestionButton() { get disableSuggestionButton() {
return this.loading; return this.loading;
} }
closeMenu() {
this.suggestIcon = "discourse-sparkles";
this.showMenu = false;
this.showErrors = false;
this.errors = "";
}
@bind @bind
onClickOutside(event) { onClickOutside(event) {
const menu = document.querySelector(".ai-title-suggestions-menu"); const menu = document.querySelector(".ai-title-suggestions-menu");
@ -82,7 +80,7 @@ export default class AISuggestionDropdown extends Component {
return; return;
} }
return this.closeMenu(); return this.#closeMenu();
} }
@action @action
@ -104,21 +102,53 @@ export default class AISuggestionDropdown extends Component {
if (this.args.mode === this.SUGGESTION_TYPES.title) { if (this.args.mode === this.SUGGESTION_TYPES.title) {
composer.set("title", suggestion); composer.set("title", suggestion);
return this.closeMenu(); return this.#closeMenu();
} }
if (this.args.mode === this.SUGGESTION_TYPES.category) { if (this.args.mode === this.SUGGESTION_TYPES.category) {
const selectedCategoryId = this.site.categories.find((c) => c.slug === suggestion).id; const selectedCategoryId = this.site.categories.find((c) => c.slug === suggestion).id;
composer.set("categoryId", selectedCategoryId); composer.set("categoryId", selectedCategoryId);
return this.closeMenu(); return this.#closeMenu();
} }
if (this.args.mode === this.SUGGESTION_TYPES.tag) { if (this.args.mode === this.SUGGESTION_TYPES.tag) {
this.updateTags(suggestion, composer); this.#updateTags(suggestion, composer);
} }
} }
updateTags(suggestion, composer) { @action
async performSuggestion() {
if (!this.args.mode) {
return;
}
if (this.composer.model.replyLength === 0) {
return this.dialog.alert(I18n.t("discourse_ai.ai_helper.missing_content"));
}
this.loading = true;
this.suggestIcon = "spinner";
return ajax(`/discourse-ai/ai-helper/${this.args.mode}`, {
method: "POST",
data: { text: this.composer.model.reply },
}).then((data) => {
this.#assignGeneratedSuggestions(data, this.args.mode);
}).catch(popupAjaxError).finally(() => {
this.loading = false;
this.suggestIcon = "sync-alt";
this.showMenu = true;
});
}
#closeMenu() {
this.suggestIcon = "discourse-sparkles";
this.showMenu = false;
this.showErrors = false;
this.errors = "";
}
#updateTags(suggestion, composer) {
const maxTags = this.siteSettings.max_tags_per_topic; const maxTags = this.siteSettings.max_tags_per_topic;
if (!composer.tags) { if (!composer.tags) {
@ -142,43 +172,26 @@ export default class AISuggestionDropdown extends Component {
return this.generatedSuggestions = this.generatedSuggestions.filter((s) => s !== suggestion); return this.generatedSuggestions = this.generatedSuggestions.filter((s) => s !== suggestion);
} }
@action #tagSelectorHasValues() {
async performSuggestion() { return this.args.composer?.tags && this.args.composer?.tags.length > 0;
if (!this.args.mode) { }
return;
#assignGeneratedSuggestions(data, mode) {
if (mode === this.SUGGESTION_TYPES.title) {
return this.generatedSuggestions = data.suggestions;
} }
if (this.composerInput?.length === 0) { const suggestions = data.assistant.map((s) => s.name);
return this.dialog.alert(I18n.t("discourse_ai.ai_helper.missing_content"));
}
this.loading = true; if (mode === this.SUGGESTION_TYPES.tag) {
this.suggestIcon = "spinner"; if (this.#tagSelectorHasValues()) {
// Filter out tags if they are already selected in the tag input
return ajax(`/discourse-ai/ai-helper/${this.args.mode}`, { return this.generatedSuggestions = suggestions.filter((t) => !this.args.composer.tags.includes(t));
method: "POST",
data: { text: this.composerInput },
}).then((data) => {
if (this.args.mode === this.SUGGESTION_TYPES.title) {
this.generatedSuggestions = data.suggestions;
} else { } else {
const suggestions = data.assistant.map((s) => s.name); return this.generatedSuggestions = suggestions;
if (this.SUGGESTION_TYPES.tag) {
if (this.args.composer?.tags && this.args.composer?.tags.length > 0) {
// Filter out tags if they are already selected in the tag input
this.generatedSuggestions = suggestions.filter((t) => !this.args.composer.tags.includes(t));
} else {
this.generatedSuggestions = suggestions;
}
} else {
this.generatedSuggestions = suggestions;
}
} }
}).catch(popupAjaxError).finally(() => { }
this.loading = false;
this.suggestIcon = "sync-alt";
this.showMenu = true;
});
return this.generatedSuggestions = suggestions;
} }
} }

View File

@ -339,6 +339,17 @@ RSpec.describe "AI Composer helper", type: :system, js: true do
expect(ai_suggestion_dropdown).to have_no_dropdown expect(ai_suggestion_dropdown).to have_no_dropdown
end end
it "only shows trigger button if there is sufficient content in the composer" do
visit("/latest")
page.find("#create-topic").click
composer.fill_content("abc")
expect(ai_suggestion_dropdown).to have_no_suggestion_button
composer.fill_content(OpenAiCompletionsInferenceStubs.translated_response)
expect(ai_suggestion_dropdown).to have_suggestion_button
end
end end
context "when suggesting the category with AI category suggester" do context "when suggesting the category with AI category suggester" do

View File

@ -3,13 +3,14 @@
module PageObjects module PageObjects
module Components module Components
class AISuggestionDropdown < PageObjects::Components::Base class AISuggestionDropdown < PageObjects::Components::Base
TITLE_BUTTON_SELECTOR = ".suggestion-button.suggest-titles-button" SUGGESTION_BUTTON_SELECTOR = ".suggestion-button"
CATEGORY_BUTTON_SELECTOR = ".suggestion-button.suggest-category-button" TITLE_BUTTON_SELECTOR = "#{SUGGESTION_BUTTON_SELECTOR}.suggest-titles-button"
TAG_BUTTON_SELECTOR = ".suggestion-button.suggest-tags-button" CATEGORY_BUTTON_SELECTOR = "#{SUGGESTION_BUTTON_SELECTOR}.suggest-category-button"
TAG_BUTTON_SELECTOR = "#{SUGGESTION_BUTTON_SELECTOR}.suggest-tags-button"
MENU_SELECTOR = ".ai-suggestions-menu" MENU_SELECTOR = ".ai-suggestions-menu"
def click_suggest_titles_button def click_suggest_titles_button
find(TITLE_BUTTON_SELECTOR, visible: :all).click page.find(TITLE_BUTTON_SELECTOR, visible: :all).click
end end
def click_suggest_category_button def click_suggest_category_button
@ -40,6 +41,14 @@ module PageObjects
def has_no_dropdown? def has_no_dropdown?
has_no_css?(MENU_SELECTOR) has_no_css?(MENU_SELECTOR)
end end
def has_suggestion_button?
has_css?(SUGGESTION_BUTTON_SELECTOR)
end
def has_no_suggestion_button?
has_no_css?(SUGGESTION_BUTTON_SELECTOR)
end
end end
end end
end end