UX: Show suggestion buttons only if sufficient content is present (#204)
This commit is contained in:
parent
8d674c451a
commit
0733ff7e67
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue