Skip to content

Commit

Permalink
FEATURE: Show suggested title prompt in new location (#171)
Browse files Browse the repository at this point in the history
  • Loading branch information
keegangeorge committed Aug 29, 2023
1 parent 345bfed commit 7457fec
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 67 deletions.
@@ -0,0 +1,108 @@
import Component from '@glimmer/component';
import DButton from "discourse/components/d-button";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { bind } from "discourse-common/utils/decorators";

export default class AITitleSuggester extends Component {
<template>
<DButton
@class="suggest-titles-button {{if this.loading 'is-loading'}}"
@icon={{this.suggestTitleIcon}}
@title="discourse_ai.ai_helper.suggest_titles"
@action={{this.suggestTitles}}
@disabled={{this.disableSuggestionButton}}
/>
{{#if this.showMenu}}
{{! template-lint-disable modifier-name-case }}
<ul class="popup-menu ai-title-suggestions-menu" {{didInsert this.handleClickOutside}}>
{{#each this.generatedTitleSuggestions as |suggestion index|}}
<li data-name={{suggestion}} data-value={{index}}>
<DButton
@class="popup-menu-btn"
@translatedLabel={{suggestion}}
@action={{this.updateTopicTitle}}
@actionParam={{suggestion}}
/>
</li>
{{/each}}
</ul>
{{/if}}
</template>

@tracked loading = false;
@tracked showMenu = false;
@tracked generatedTitleSuggestions = [];
@tracked suggestTitleIcon = "discourse-sparkles";
mode = {
id: -2,
name: "generate_titles",
translated_name: "Suggest topic titles",
prompt_type: "list"
};

willDestroy() {
super.willDestroy(...arguments);
document.removeEventListener("click", this.onClickOutside);
}

get composerInput() {
return document.querySelector(".d-editor-input").value || this.args.outletArgs.composer.reply;
}

get disableSuggestionButton() {
return this.loading;
}

closeMenu() {
this.suggestTitleIcon = "discourse-sparkles";
this.showMenu = false;
}

@bind
onClickOutside(event) {
const menu = document.querySelector(".ai-title-suggestions-menu");

if (event.target === menu) {
return;
}

return this.closeMenu();
}

@action
handleClickOutside() {
document.addEventListener("click", this.onClickOutside);
}

@action
updateTopicTitle(title) {
const composer = this.args.outletArgs?.composer;

if (composer) {
composer.set("title", title);
this.closeMenu();
}
}

@action
async suggestTitles() {
this.loading = true;
this.suggestTitleIcon = "spinner";

return ajax("/discourse-ai/ai-helper/suggest", {
method: "POST",
data: { mode: this.mode.id, text: this.composerInput },
}).then((data) => {
this.generatedTitleSuggestions = data.suggestions;
}).catch(popupAjaxError).finally(() => {
this.loading = false;
this.suggestTitleIcon = "sync-alt";
this.showMenu = true;
});

}
}
Expand Up @@ -27,20 +27,6 @@
{{/each}}
</ul>

{{else if (eq this.menuState this.CONTEXT_MENU_STATES.suggestions)}}
<ul class="ai-helper-context-menu__suggestions">
{{#each this.generatedTitleSuggestions as |suggestion index|}}
<li data-name={{suggestion}} data-value={{index}}>
<DButton
@class="btn-flat"
@translatedLabel={{suggestion}}
@action={{this.updateTopicTitle}}
@actionParam={{suggestion}}
/>
</li>
{{/each}}
</ul>

{{else if (eq this.menuState this.CONTEXT_MENU_STATES.loading)}}
<ul class="ai-helper-context-menu__loading">
<li>
Expand Down
Expand Up @@ -42,7 +42,6 @@ export default class AiHelperContextMenu extends Component {
@tracked loading = false;
@tracked oldEditorValue;
@tracked newEditorValue;
@tracked generatedTitleSuggestions = [];
@tracked lastUsedOption = null;
@tracked showDiffModal = false;
@tracked diff;
Expand All @@ -52,7 +51,6 @@ export default class AiHelperContextMenu extends Component {
options: "OPTIONS",
resets: "RESETS",
loading: "LOADING",
suggesions: "SUGGESTIONS",
review: "REVIEW",
};
prompts = [];
Expand Down Expand Up @@ -81,21 +79,24 @@ export default class AiHelperContextMenu extends Component {
async loadPrompts() {
let prompts = await ajax("/discourse-ai/ai-helper/prompts");
prompts.map((p) => {
this.prompts[p.id] = p;
});
prompts
.filter((p) => p.name !== "generate_titles")
.map((p) => {
this.prompts[p.id] = p;
});
this.promptTypes = prompts.reduce((memo, p) => {
memo[p.name] = p.prompt_type;
return memo;
}, {});
this.helperOptions = prompts.map((p) => {
return {
name: p.translated_name,
value: p.id,
};
});
this.helperOptions = prompts
.filter((p) => p.name !== "generate_titles")
.map((p) => {
return {
name: p.translated_name,
value: p.id,
};
});
}
@bind
Expand Down Expand Up @@ -297,13 +298,7 @@ export default class AiHelperContextMenu extends Component {
// resets the values if new suggestion is started:
this.diff = null;
this.newSelectedText = null;

if (this.prompts[option].name === "generate_titles") {
this.menuState = this.CONTEXT_MENU_STATES.suggestions;
this.generatedTitleSuggestions = data.suggestions;
} else {
this._updateSuggestedByAI(data);
}
this._updateSuggestedByAI(data);
})
.catch(popupAjaxError)
.finally(() => {
Expand All @@ -312,16 +307,6 @@ export default class AiHelperContextMenu extends Component {
});
}

@action
updateTopicTitle(title) {
const composer = this.args.outletArgs?.composer;

if (composer) {
composer.set("title", title);
this.closeContextMenu();
}
}

@action
viewChanges() {
this.showDiffModal = true;
Expand Down
33 changes: 33 additions & 0 deletions assets/stylesheets/modules/ai-helper/common/ai-helper.scss
Expand Up @@ -209,3 +209,36 @@
box-shadow: 10014px 15px 0 0 rgba(152, 128, 255, 0);
}
}

// Suggest Titles Related
.suggest-titles-button {
position: absolute;
top: 1px;
right: 1px;
background: none;
border: none;

.d-icon-spinner {
animation: spin 1s linear infinite;
}
}

.ai-title-suggestions-menu {
list-style: none;
margin-left: 0;
position: absolute;
right: 0;
top: 1.5rem;
max-width: 25rem;
width: unset;
z-index: 999;
}

@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
1 change: 1 addition & 0 deletions config/locales/client.en.yml
Expand Up @@ -16,6 +16,7 @@ en:
title: "Suggest changes using AI"
description: "Choose one of the options below, and the AI will suggest you a new version of the text."
selection_hint: "Hint: You can also select a portion of the text before opening the helper to rewrite only that."
suggest_titles: "Suggest titles with AI"
context_menu:
trigger: "AI"
undo: "Undo"
Expand Down
54 changes: 39 additions & 15 deletions spec/system/ai_helper/ai_composer_helper_spec.rb
Expand Up @@ -15,6 +15,7 @@
let(:ai_helper_context_menu) { PageObjects::Components::AIHelperContextMenu.new }
let(:ai_helper_modal) { PageObjects::Modals::AiHelper.new }
let(:diff_modal) { PageObjects::Modals::DiffModal.new }
let(:ai_title_suggester) { PageObjects::Components::AITitleSuggester.new }

context "when using the translation mode" do
let(:mode) { OpenAiCompletionsInferenceStubs::TRANSLATE }
Expand Down Expand Up @@ -285,25 +286,48 @@ def trigger_context_menu(content)
)
end
end
end

context "when selecting an AI generated title" do
let(:mode) { OpenAiCompletionsInferenceStubs::GENERATE_TITLES }
before { OpenAiCompletionsInferenceStubs.stub_prompt(mode) }
context "when suggesting titles with AI title suggester" do
let(:mode) { OpenAiCompletionsInferenceStubs::GENERATE_TITLES }
before { OpenAiCompletionsInferenceStubs.stub_prompt(mode) }

it "replaces the topic title" do
trigger_context_menu(OpenAiCompletionsInferenceStubs.translated_response)
ai_helper_context_menu.click_ai_button
ai_helper_context_menu.select_helper_model(
OpenAiCompletionsInferenceStubs.text_mode_to_id(mode),
)
expect(ai_helper_context_menu).to be_showing_suggestions
it "opens a menu with title suggestions" do
visit("/latest")
page.find("#create-topic").click
composer.fill_content(OpenAiCompletionsInferenceStubs.translated_response)
ai_title_suggester.click_suggest_titles_button

ai_helper_context_menu.select_title_suggestion(2)
expected_title = "The Quiet Piece that Moves Literature: A Gaucho's Story"
wait_for { ai_title_suggester.has_dropdown? }

wait_for { find("#reply-title").value == expected_title }
expect(find("#reply-title").value).to eq(expected_title)
end
expect(ai_title_suggester).to have_dropdown
end

it "replaces the topic title with the selected title" do
visit("/latest")
page.find("#create-topic").click
composer.fill_content(OpenAiCompletionsInferenceStubs.translated_response)
ai_title_suggester.click_suggest_titles_button

wait_for { ai_title_suggester.has_dropdown? }

ai_title_suggester.select_title_suggestion(2)
expected_title = "The Quiet Piece that Moves Literature: A Gaucho's Story"

expect(find("#reply-title").value).to eq(expected_title)
end

it "closes the menu when clicking outside" do
visit("/latest")
page.find("#create-topic").click
composer.fill_content(OpenAiCompletionsInferenceStubs.translated_response)
ai_title_suggester.click_suggest_titles_button

wait_for { ai_title_suggester.has_dropdown? }

find(".d-editor-preview").click

expect(ai_title_suggester).to have_no_dropdown
end
end
end
9 changes: 0 additions & 9 deletions spec/system/page_objects/components/ai_helper_context_menu.rb
Expand Up @@ -7,7 +7,6 @@ class AIHelperContextMenu < PageObjects::Components::Base
CONTEXT_MENU_SELECTOR = ".ai-helper-context-menu"
TRIGGER_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__trigger"
OPTIONS_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__options"
SUGGESTIONS_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__suggestions"
LOADING_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__loading"
RESETS_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__resets"
REVIEW_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__review"
Expand All @@ -20,10 +19,6 @@ def select_helper_model(mode)
find("#{OPTIONS_STATE_SELECTOR} li[data-value=\"#{mode}\"] .btn").click
end

def select_title_suggestion(option_number)
find("#{SUGGESTIONS_STATE_SELECTOR} li[data-value=\"#{option_number}\"] .btn").click
end

def click_undo_button
find("#{RESETS_STATE_SELECTOR} .undo").click
end
Expand Down Expand Up @@ -64,10 +59,6 @@ def showing_options?
page.has_css?(OPTIONS_STATE_SELECTOR)
end

def showing_suggestions?
page.has_css?(SUGGESTIONS_STATE_SELECTOR)
end

def showing_loading?
page.has_css?(LOADING_STATE_SELECTOR)
end
Expand Down
26 changes: 26 additions & 0 deletions spec/system/page_objects/components/ai_title_suggester.rb
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module PageObjects
module Components
class AITitleSuggester < PageObjects::Components::Base
BUTTON_SELECTOR = ".suggest-titles-button"
MENU_SELECTOR = ".ai-title-suggestions-menu"

def click_suggest_titles_button
find(BUTTON_SELECTOR, visible: :all).click
end

def select_title_suggestion(index)
find("#{MENU_SELECTOR} li[data-value=\"#{index}\"]").click
end

def has_dropdown?
has_css?(MENU_SELECTOR)
end

def has_no_dropdown?
has_no_css?(MENU_SELECTOR)
end
end
end
end

0 comments on commit 7457fec

Please sign in to comment.