DEV: Strip out old modal based AI helper (#209)
This commit is contained in:
parent
0733ff7e67
commit
abe96d5533
|
@ -1,69 +0,0 @@
|
||||||
<DModalBody @title="discourse_ai.ai_helper.title">
|
|
||||||
<span>{{i18n "discourse_ai.ai_helper.description"}}</span>
|
|
||||||
|
|
||||||
<ComboBox
|
|
||||||
@value={{this.selected}}
|
|
||||||
@content={{this.helperOptions}}
|
|
||||||
@onChange={{action this.updateSelected}}
|
|
||||||
@valueProperty="value"
|
|
||||||
@class="ai-helper-mode"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="text-preview">
|
|
||||||
<Textarea
|
|
||||||
@value={{this.composedMessage}}
|
|
||||||
disabled="true"
|
|
||||||
class="preview-area"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="selection-hint">{{i18n
|
|
||||||
"discourse_ai.ai_helper.selection_hint"
|
|
||||||
}}</div>
|
|
||||||
|
|
||||||
<div class="text-preview">
|
|
||||||
<ConditionalLoadingSpinner @condition={{this.loading}} />
|
|
||||||
|
|
||||||
{{#unless this.loading}}
|
|
||||||
{{#if this.selectingTopicTitle}}
|
|
||||||
<div class="radios">
|
|
||||||
{{#each this.generatedTitlesSuggestions as |title index|}}
|
|
||||||
<label class="radio-label" for="title-suggestion-{{index}}">
|
|
||||||
<RadioButton
|
|
||||||
@id="title-suggestion-{{index}}"
|
|
||||||
@name="title-suggestion"
|
|
||||||
@value={{title}}
|
|
||||||
@selection={{this.selectedTitle}}
|
|
||||||
/>
|
|
||||||
<b>{{title}}</b>
|
|
||||||
</label>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
{{else if this.proofreadingText}}
|
|
||||||
{{html-safe this.proofreadDiff}}
|
|
||||||
{{else if this.translatingText}}
|
|
||||||
<Textarea
|
|
||||||
@value={{this.translatedSuggestion}}
|
|
||||||
disabled="true"
|
|
||||||
class="preview-area"
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
{{/unless}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</DModalBody>
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
|
||||||
{{#if this.canSave}}
|
|
||||||
<DButton
|
|
||||||
@class="btn-primary create"
|
|
||||||
@action={{this.applySuggestion}}
|
|
||||||
@label="save"
|
|
||||||
/>
|
|
||||||
<DModalCancel @close={{route-action "closeModal"}} />
|
|
||||||
{{else}}
|
|
||||||
<div class="ai-helper-waiting-selection">{{i18n
|
|
||||||
"discourse_ai.modals.select_option"
|
|
||||||
}}</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
|
@ -1,152 +0,0 @@
|
||||||
import Component from "@glimmer/component";
|
|
||||||
import { tracked } from "@glimmer/tracking";
|
|
||||||
import { action, computed } from "@ember/object";
|
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
||||||
|
|
||||||
const LIST = "list";
|
|
||||||
const TEXT = "text";
|
|
||||||
const DIFF = "diff";
|
|
||||||
|
|
||||||
export default class AiHelper extends Component {
|
|
||||||
@tracked selected = null;
|
|
||||||
@tracked loading = false;
|
|
||||||
|
|
||||||
@tracked generatedTitlesSuggestions = [];
|
|
||||||
|
|
||||||
@tracked proofReadSuggestion = null;
|
|
||||||
@tracked translatedSuggestion = null;
|
|
||||||
@tracked selectedTitle = null;
|
|
||||||
|
|
||||||
@tracked proofreadDiff = null;
|
|
||||||
|
|
||||||
@tracked helperOptions = [];
|
|
||||||
prompts = [];
|
|
||||||
promptTypes = {};
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super(...arguments);
|
|
||||||
this.loadPrompts();
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadPrompts() {
|
|
||||||
let prompts = await ajax("/discourse-ai/ai-helper/prompts");
|
|
||||||
|
|
||||||
prompts.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,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get composedMessage() {
|
|
||||||
const editor = this.args.editor;
|
|
||||||
|
|
||||||
return editor.getSelected().value || editor.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed("selected", "selectedTitle", "translatingText", "proofreadingText")
|
|
||||||
get canSave() {
|
|
||||||
return (
|
|
||||||
(this.selected &&
|
|
||||||
this.prompts[this.selected].prompt_type === LIST &&
|
|
||||||
this.selectedTitle) ||
|
|
||||||
this.translatingText ||
|
|
||||||
this.proofreadingText
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed("selected", "translatedSuggestion")
|
|
||||||
get translatingText() {
|
|
||||||
return (
|
|
||||||
this.selected &&
|
|
||||||
this.prompts[this.selected].prompt_type === TEXT &&
|
|
||||||
this.translatedSuggestion
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed("selected", "proofReadSuggestion")
|
|
||||||
get proofreadingText() {
|
|
||||||
return (
|
|
||||||
this.selected &&
|
|
||||||
this.prompts[this.selected].prompt_type === DIFF &&
|
|
||||||
this.proofReadSuggestion
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed("selected", "generatedTitlesSuggestions")
|
|
||||||
get selectingTopicTitle() {
|
|
||||||
return (
|
|
||||||
this.selected &&
|
|
||||||
this.prompts[this.selected].prompt_type === LIST &&
|
|
||||||
this.generatedTitlesSuggestions.length > 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateSuggestedByAI(data) {
|
|
||||||
switch (data.type) {
|
|
||||||
case LIST:
|
|
||||||
this.generatedTitlesSuggestions = data.suggestions;
|
|
||||||
break;
|
|
||||||
case TEXT:
|
|
||||||
this.translatedSuggestion = data.suggestions[0];
|
|
||||||
break;
|
|
||||||
case DIFF:
|
|
||||||
this.proofReadSuggestion = data.suggestions[0];
|
|
||||||
this.proofreadDiff = data.diff;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
async updateSelected(value) {
|
|
||||||
this.loading = true;
|
|
||||||
this.selected = value;
|
|
||||||
|
|
||||||
if (value === LIST) {
|
|
||||||
this.selectedTitle = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasSuggestion) {
|
|
||||||
this.loading = false;
|
|
||||||
} else {
|
|
||||||
return ajax("/discourse-ai/ai-helper/suggest", {
|
|
||||||
method: "POST",
|
|
||||||
data: { mode: this.selected, text: this.composedMessage },
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
this._updateSuggestedByAI(data);
|
|
||||||
})
|
|
||||||
.catch(popupAjaxError)
|
|
||||||
.finally(() => (this.loading = false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
applySuggestion() {
|
|
||||||
if (this.selectingTopicTitle) {
|
|
||||||
const composer = this.args.editor.outletArgs?.composer;
|
|
||||||
|
|
||||||
if (composer) {
|
|
||||||
composer.set("title", this.selectedTitle);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const newText = this.proofreadingText
|
|
||||||
? this.proofReadSuggestion
|
|
||||||
: this.translatedSuggestion;
|
|
||||||
this.args.editor.replaceText(this.composedMessage, newText);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.args.closeModal();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
<AiHelper @editor={{this.editor}} @closeModal={{route-action "closeModal"}} />
|
|
|
@ -1,72 +0,0 @@
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
|
||||||
import showModal from "discourse/lib/show-modal";
|
|
||||||
|
|
||||||
function initializeComposerAIHelper(api) {
|
|
||||||
api.modifyClass("component:composer-editor", {
|
|
||||||
pluginId: "discourse-ai",
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
extraButtons(toolbar) {
|
|
||||||
this._super(toolbar);
|
|
||||||
|
|
||||||
const removeAiHelperFromPM =
|
|
||||||
this.composerModel.privateMessage &&
|
|
||||||
!this.siteSettings.ai_helper_allowed_in_pm;
|
|
||||||
|
|
||||||
if (removeAiHelperFromPM) {
|
|
||||||
const extrasGroup = toolbar.groups.find((g) => g.group === "extras");
|
|
||||||
const newButtons = extrasGroup.buttons.filter(
|
|
||||||
(b) => b.id !== "ai-helper"
|
|
||||||
);
|
|
||||||
|
|
||||||
extrasGroup.buttons = newButtons;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
api.modifyClass("component:d-editor", {
|
|
||||||
pluginId: "discourse-ai",
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
openAIHelper() {
|
|
||||||
if (this.value) {
|
|
||||||
showModal("composer-ai-helper").setProperties({ editor: this });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
api.onToolbarCreate((toolbar) => {
|
|
||||||
toolbar.addButton({
|
|
||||||
id: "ai-helper",
|
|
||||||
title: "discourse_ai.ai_helper.title",
|
|
||||||
group: "extras",
|
|
||||||
icon: "discourse-sparkles",
|
|
||||||
className: "composer-ai-helper",
|
|
||||||
sendAction: () => toolbar.context.send("openAIHelper"),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "discourse-ai-composer-helper",
|
|
||||||
|
|
||||||
initialize(container) {
|
|
||||||
const settings = container.lookup("service:site-settings");
|
|
||||||
const user = container.lookup("service:current-user");
|
|
||||||
const helperEnabled =
|
|
||||||
settings.discourse_ai_enabled && settings.composer_ai_helper_enabled;
|
|
||||||
|
|
||||||
const allowedGroups = settings.ai_helper_allowed_groups
|
|
||||||
.split("|")
|
|
||||||
.map((id) => parseInt(id, 10));
|
|
||||||
const canUseAssistant = user?.groups.some((g) =>
|
|
||||||
allowedGroups.includes(g.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (helperEnabled && canUseAssistant) {
|
|
||||||
withPluginApi("1.6.0", initializeComposerAIHelper);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,10 +1,4 @@
|
||||||
.composer-ai-helper-modal {
|
.composer-ai-helper-modal {
|
||||||
.combobox,
|
|
||||||
.text-preview,
|
|
||||||
.ai-helper-waiting-selection {
|
|
||||||
margin: 10px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-preview,
|
.text-preview,
|
||||||
.inline-diff {
|
.inline-diff {
|
||||||
ins {
|
ins {
|
||||||
|
@ -31,11 +25,6 @@
|
||||||
background-color: var(--success-low);
|
background-color: var(--success-low);
|
||||||
color: var(--success);
|
color: var(--success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selection-hint {
|
|
||||||
font-size: var(--font-down-2);
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-above-suggested-outlet.related-topics {
|
.topic-above-suggested-outlet.related-topics {
|
||||||
|
|
|
@ -13,7 +13,6 @@ RSpec.describe "AI Composer helper", type: :system, js: true do
|
||||||
|
|
||||||
let(:composer) { PageObjects::Components::Composer.new }
|
let(:composer) { PageObjects::Components::Composer.new }
|
||||||
let(:ai_helper_context_menu) { PageObjects::Components::AIHelperContextMenu.new }
|
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(:diff_modal) { PageObjects::Modals::DiffModal.new }
|
||||||
let(:ai_suggestion_dropdown) { PageObjects::Components::AISuggestionDropdown.new }
|
let(:ai_suggestion_dropdown) { PageObjects::Components::AISuggestionDropdown.new }
|
||||||
fab!(:category) { Fabricate(:category) }
|
fab!(:category) { Fabricate(:category) }
|
||||||
|
@ -24,79 +23,6 @@ RSpec.describe "AI Composer helper", type: :system, js: true do
|
||||||
fab!(:feedback) { Fabricate(:tag) }
|
fab!(:feedback) { Fabricate(:tag) }
|
||||||
fab!(:review) { Fabricate(:tag) }
|
fab!(:review) { Fabricate(:tag) }
|
||||||
|
|
||||||
context "when using the translation mode" do
|
|
||||||
let(:mode) { OpenAiCompletionsInferenceStubs::TRANSLATE }
|
|
||||||
|
|
||||||
before { OpenAiCompletionsInferenceStubs.stub_prompt(mode) }
|
|
||||||
|
|
||||||
it "replaces the composed message with AI generated content" do
|
|
||||||
visit("/latest")
|
|
||||||
page.find("#create-topic").click
|
|
||||||
|
|
||||||
composer.fill_content(OpenAiCompletionsInferenceStubs.spanish_text)
|
|
||||||
page.find(".composer-ai-helper").click
|
|
||||||
|
|
||||||
expect(ai_helper_modal).to be_visible
|
|
||||||
|
|
||||||
ai_helper_modal.select_helper_model(OpenAiCompletionsInferenceStubs.text_mode_to_id(mode))
|
|
||||||
ai_helper_modal.save_changes
|
|
||||||
|
|
||||||
expect(composer.composer_input.value).to eq(
|
|
||||||
OpenAiCompletionsInferenceStubs.translated_response.strip,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when using the proofreading mode" do
|
|
||||||
let(:mode) { OpenAiCompletionsInferenceStubs::PROOFREAD }
|
|
||||||
|
|
||||||
before { OpenAiCompletionsInferenceStubs.stub_prompt(mode) }
|
|
||||||
|
|
||||||
it "replaces the composed message with AI generated content" do
|
|
||||||
visit("/latest")
|
|
||||||
page.find("#create-topic").click
|
|
||||||
|
|
||||||
composer.fill_content(OpenAiCompletionsInferenceStubs.translated_response)
|
|
||||||
page.find(".composer-ai-helper").click
|
|
||||||
|
|
||||||
expect(ai_helper_modal).to be_visible
|
|
||||||
|
|
||||||
ai_helper_modal.select_helper_model(OpenAiCompletionsInferenceStubs.text_mode_to_id(mode))
|
|
||||||
|
|
||||||
wait_for { ai_helper_modal.has_diff? == true }
|
|
||||||
|
|
||||||
ai_helper_modal.save_changes
|
|
||||||
|
|
||||||
expect(composer.composer_input.value).to eq(
|
|
||||||
OpenAiCompletionsInferenceStubs.proofread_response.strip,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when selecting an AI generated title" do
|
|
||||||
let(:mode) { OpenAiCompletionsInferenceStubs::GENERATE_TITLES }
|
|
||||||
|
|
||||||
before { OpenAiCompletionsInferenceStubs.stub_prompt(mode) }
|
|
||||||
|
|
||||||
it "replaces the topic title" do
|
|
||||||
visit("/latest")
|
|
||||||
page.find("#create-topic").click
|
|
||||||
|
|
||||||
composer.fill_content(OpenAiCompletionsInferenceStubs.translated_response)
|
|
||||||
page.find(".composer-ai-helper").click
|
|
||||||
|
|
||||||
expect(ai_helper_modal).to be_visible
|
|
||||||
|
|
||||||
ai_helper_modal.select_helper_model(OpenAiCompletionsInferenceStubs.text_mode_to_id(mode))
|
|
||||||
ai_helper_modal.select_title_suggestion(2)
|
|
||||||
ai_helper_modal.save_changes
|
|
||||||
|
|
||||||
expected_title = "The Quiet Piece that Moves Literature: A Gaucho's Story"
|
|
||||||
|
|
||||||
expect(find("#reply-title").value).to eq(expected_title)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def trigger_context_menu(content)
|
def trigger_context_menu(content)
|
||||||
visit("/latest")
|
visit("/latest")
|
||||||
page.find("#create-topic").click
|
page.find("#create-topic").click
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module PageObjects
|
|
||||||
module Modals
|
|
||||||
class AiHelper < PageObjects::Modals::Base
|
|
||||||
def visible?
|
|
||||||
page.has_css?(".composer-ai-helper-modal", wait: 5)
|
|
||||||
end
|
|
||||||
|
|
||||||
def select_helper_model(mode)
|
|
||||||
find(".ai-helper-mode").click
|
|
||||||
find(".select-kit-row[data-value=\"#{mode}\"]").click
|
|
||||||
end
|
|
||||||
|
|
||||||
def save_changes
|
|
||||||
find(".modal-footer button.create", wait: 5).click
|
|
||||||
end
|
|
||||||
|
|
||||||
def select_title_suggestion(option_number)
|
|
||||||
find("input#title-suggestion-#{option_number}").click
|
|
||||||
end
|
|
||||||
|
|
||||||
def has_diff?
|
|
||||||
has_css?(".text-preview .inline-diff")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in New Issue