DEV: Add review menu state (#159)
This commit is contained in:
parent
65c6b5e16c
commit
7790313b1b
|
@ -0,0 +1,48 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import DButton from "discourse/components/d-button";
|
||||||
|
import DModal from "discourse/components/d-modal";
|
||||||
|
import DModalCancel from "discourse/components/d-modal-cancel";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
|
||||||
|
const t = I18n.t.bind(I18n);
|
||||||
|
|
||||||
|
export default class ModalDiffModal extends Component {
|
||||||
|
<template>
|
||||||
|
<DModal
|
||||||
|
class="composer-ai-helper-modal"
|
||||||
|
@title={{t "discourse_ai.ai_helper.context_menu.changes"}}
|
||||||
|
@closeModal={{@closeModal}}
|
||||||
|
>
|
||||||
|
<:body>
|
||||||
|
{{#if @diff}}
|
||||||
|
{{htmlSafe @diff}}
|
||||||
|
{{else}}
|
||||||
|
<div class="composer-ai-helper-modal__old-value">
|
||||||
|
{{@oldValue}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="composer-ai-helper-modal__new-value">
|
||||||
|
{{@newValue}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</:body>
|
||||||
|
|
||||||
|
<:footer>
|
||||||
|
<DButton
|
||||||
|
class="btn-primary confirm"
|
||||||
|
@action={{this.triggerConfirmChanges}}
|
||||||
|
@label="discourse_ai.ai_helper.context_menu.confirm"
|
||||||
|
/>
|
||||||
|
<DModalCancel @close={{@closeModal}} />
|
||||||
|
</:footer>
|
||||||
|
</DModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
@action
|
||||||
|
triggerConfirmChanges() {
|
||||||
|
this.args.closeModal();
|
||||||
|
this.args.confirm();
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,6 +51,34 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
{{else if (eq this.menuState this.CONTEXT_MENU_STATES.review)}}
|
||||||
|
<ul class="ai-helper-context-menu__review">
|
||||||
|
<li>
|
||||||
|
<DButton
|
||||||
|
@icon="exchange-alt"
|
||||||
|
@label="discourse_ai.ai_helper.context_menu.view_changes"
|
||||||
|
@action={{this.viewChanges}}
|
||||||
|
class="btn-flat view-changes"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<DButton
|
||||||
|
@icon="undo"
|
||||||
|
@label="discourse_ai.ai_helper.context_menu.revert"
|
||||||
|
@action={{this.undoAIAction}}
|
||||||
|
class="btn-flat revert"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<DButton
|
||||||
|
@icon="check"
|
||||||
|
@label="discourse_ai.ai_helper.context_menu.confirm"
|
||||||
|
@action={{this.confirmChanges}}
|
||||||
|
class="btn-flat confirm"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
{{else if (eq this.menuState this.CONTEXT_MENU_STATES.resets)}}
|
{{else if (eq this.menuState this.CONTEXT_MENU_STATES.resets)}}
|
||||||
<ul class="ai-helper-context-menu__resets">
|
<ul class="ai-helper-context-menu__resets">
|
||||||
<li>
|
<li>
|
||||||
|
@ -76,3 +104,13 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#if this.showDiffModal}}
|
||||||
|
<Modal::DiffModal
|
||||||
|
@confirm={{this.confirmChanges}}
|
||||||
|
@diff={{this.diff}}
|
||||||
|
@oldValue={{this.selectedText}}
|
||||||
|
@newValue={{this.newSelectedText}}
|
||||||
|
@closeModal={{fn (mut this.showDiffModal) false}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
|
@ -38,10 +38,14 @@ export default class AiHelperContextMenu extends Component {
|
||||||
@tracked caretCoords;
|
@tracked caretCoords;
|
||||||
@tracked virtualElement;
|
@tracked virtualElement;
|
||||||
@tracked selectedText = "";
|
@tracked selectedText = "";
|
||||||
|
@tracked newSelectedText;
|
||||||
@tracked loading = false;
|
@tracked loading = false;
|
||||||
@tracked oldEditorValue;
|
@tracked oldEditorValue;
|
||||||
|
@tracked newEditorValue;
|
||||||
@tracked generatedTitleSuggestions = [];
|
@tracked generatedTitleSuggestions = [];
|
||||||
@tracked lastUsedOption = null;
|
@tracked lastUsedOption = null;
|
||||||
|
@tracked showDiffModal = false;
|
||||||
|
@tracked diff;
|
||||||
|
|
||||||
CONTEXT_MENU_STATES = {
|
CONTEXT_MENU_STATES = {
|
||||||
triggers: "TRIGGERS",
|
triggers: "TRIGGERS",
|
||||||
|
@ -49,6 +53,7 @@ export default class AiHelperContextMenu extends Component {
|
||||||
resets: "RESETS",
|
resets: "RESETS",
|
||||||
loading: "LOADING",
|
loading: "LOADING",
|
||||||
suggesions: "SUGGESTIONS",
|
suggesions: "SUGGESTIONS",
|
||||||
|
review: "REVIEW",
|
||||||
};
|
};
|
||||||
prompts = [];
|
prompts = [];
|
||||||
promptTypes = {};
|
promptTypes = {};
|
||||||
|
@ -104,8 +109,9 @@ export default class AiHelperContextMenu extends Component {
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
if (this.selectedText.length === 0) {
|
if (this.selectedText.length === 0) {
|
||||||
if (this.loading) {
|
if (this.loading || this.menuState === this.CONTEXT_MENU_STATES.review) {
|
||||||
// prevent accidentally closing context menu while results loading
|
// prevent accidentally closing context menu
|
||||||
|
// while results loading or in review state
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,12 +169,18 @@ export default class AiHelperContextMenu extends Component {
|
||||||
_updateSuggestedByAI(data) {
|
_updateSuggestedByAI(data) {
|
||||||
const composer = this.args.outletArgs.composer;
|
const composer = this.args.outletArgs.composer;
|
||||||
this.oldEditorValue = this._dEditorInput.value;
|
this.oldEditorValue = this._dEditorInput.value;
|
||||||
const newValue = this.oldEditorValue.replace(
|
this.newSelectedText = data.suggestions[0];
|
||||||
|
|
||||||
|
this.newEditorValue = this.oldEditorValue.replace(
|
||||||
this.selectedText,
|
this.selectedText,
|
||||||
data.suggestions[0]
|
this.newSelectedText
|
||||||
);
|
);
|
||||||
composer.set("reply", newValue);
|
|
||||||
this.menuState = this.CONTEXT_MENU_STATES.resets;
|
if (data.diff) {
|
||||||
|
this.diff = data.diff;
|
||||||
|
}
|
||||||
|
composer.set("reply", this.newEditorValue);
|
||||||
|
this.menuState = this.CONTEXT_MENU_STATES.review;
|
||||||
}
|
}
|
||||||
|
|
||||||
@afterRender
|
@afterRender
|
||||||
|
@ -240,6 +252,10 @@ export default class AiHelperContextMenu extends Component {
|
||||||
data: { mode: option, text: this.selectedText },
|
data: { mode: option, text: this.selectedText },
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
// resets the values if new suggestion is started:
|
||||||
|
this.diff = null;
|
||||||
|
this.newSelectedText = null;
|
||||||
|
|
||||||
if (this.prompts[option].name === "generate_titles") {
|
if (this.prompts[option].name === "generate_titles") {
|
||||||
this.menuState = this.CONTEXT_MENU_STATES.suggestions;
|
this.menuState = this.CONTEXT_MENU_STATES.suggestions;
|
||||||
this.generatedTitleSuggestions = data.suggestions;
|
this.generatedTitleSuggestions = data.suggestions;
|
||||||
|
@ -263,4 +279,14 @@ export default class AiHelperContextMenu extends Component {
|
||||||
this.closeContextMenu();
|
this.closeContextMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
viewChanges() {
|
||||||
|
this.showDiffModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
confirmChanges() {
|
||||||
|
this.menuState = this.CONTEXT_MENU_STATES.resets;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
margin: 10px 0 10px 0;
|
margin: 10px 0 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-preview {
|
.text-preview,
|
||||||
|
.inline-diff {
|
||||||
ins {
|
ins {
|
||||||
background-color: var(--success-low);
|
background-color: var(--success-low);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
@ -20,6 +21,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__old-value {
|
||||||
|
background-color: var(--danger-low);
|
||||||
|
color: var(--danger);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__new-value {
|
||||||
|
background-color: var(--success-low);
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
.selection-hint {
|
.selection-hint {
|
||||||
font-size: var(--font-down-2);
|
font-size: var(--font-down-2);
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
@ -34,7 +46,7 @@
|
||||||
background: var(--secondary);
|
background: var(--secondary);
|
||||||
box-shadow: var(--shadow-dropdown);
|
box-shadow: var(--shadow-dropdown);
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
max-width: 15rem;
|
max-width: 25rem;
|
||||||
border: 1px solid var(--primary-low);
|
border: 1px solid var(--primary-low);
|
||||||
list-style: none;
|
list-style: none;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
@ -79,6 +91,12 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__review {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.d-editor-input.loading {
|
.d-editor-input.loading {
|
||||||
|
|
|
@ -22,6 +22,10 @@ en:
|
||||||
loading: "AI is generating"
|
loading: "AI is generating"
|
||||||
cancel: "Cancel"
|
cancel: "Cancel"
|
||||||
regen: "Try Again"
|
regen: "Try Again"
|
||||||
|
view_changes: "View Changes"
|
||||||
|
confirm: "Confirm"
|
||||||
|
revert: "Revert"
|
||||||
|
changes: "Changes"
|
||||||
reviewables:
|
reviewables:
|
||||||
model_used: "Model used:"
|
model_used: "Model used:"
|
||||||
accuracy: "Accuracy:"
|
accuracy: "Accuracy:"
|
||||||
|
|
|
@ -14,6 +14,7 @@ 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(:ai_helper_modal) { PageObjects::Modals::AiHelper.new }
|
||||||
|
let(:diff_modal) { PageObjects::Modals::DiffModal.new }
|
||||||
|
|
||||||
context "when using the translation mode" do
|
context "when using the translation mode" do
|
||||||
let(:mode) { OpenAiCompletionsInferenceStubs::TRANSLATE }
|
let(:mode) { OpenAiCompletionsInferenceStubs::TRANSLATE }
|
||||||
|
@ -140,6 +141,7 @@ RSpec.describe "AI Composer helper", type: :system, js: true do
|
||||||
composer.composer_input.value == OpenAiCompletionsInferenceStubs.translated_response.strip
|
composer.composer_input.value == OpenAiCompletionsInferenceStubs.translated_response.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ai_helper_context_menu.click_confirm_button
|
||||||
expect(ai_helper_context_menu).to be_showing_resets
|
expect(ai_helper_context_menu).to be_showing_resets
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -154,10 +156,26 @@ RSpec.describe "AI Composer helper", type: :system, js: true do
|
||||||
composer.composer_input.value == OpenAiCompletionsInferenceStubs.translated_response.strip
|
composer.composer_input.value == OpenAiCompletionsInferenceStubs.translated_response.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ai_helper_context_menu.click_confirm_button
|
||||||
ai_helper_context_menu.click_undo_button
|
ai_helper_context_menu.click_undo_button
|
||||||
expect(composer.composer_input.value).to eq(OpenAiCompletionsInferenceStubs.spanish_text)
|
expect(composer.composer_input.value).to eq(OpenAiCompletionsInferenceStubs.spanish_text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "reverts results when revert button is clicked" do
|
||||||
|
trigger_context_menu(OpenAiCompletionsInferenceStubs.spanish_text)
|
||||||
|
ai_helper_context_menu.click_ai_button
|
||||||
|
ai_helper_context_menu.select_helper_model(
|
||||||
|
OpenAiCompletionsInferenceStubs.text_mode_to_id(mode),
|
||||||
|
)
|
||||||
|
|
||||||
|
wait_for do
|
||||||
|
composer.composer_input.value == OpenAiCompletionsInferenceStubs.translated_response.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
ai_helper_context_menu.click_revert_button
|
||||||
|
expect(composer.composer_input.value).to eq(OpenAiCompletionsInferenceStubs.spanish_text)
|
||||||
|
end
|
||||||
|
|
||||||
it "reverts results when Ctrl/Cmd + Z is pressed on the keyboard" do
|
it "reverts results when Ctrl/Cmd + Z is pressed on the keyboard" do
|
||||||
trigger_context_menu(OpenAiCompletionsInferenceStubs.spanish_text)
|
trigger_context_menu(OpenAiCompletionsInferenceStubs.spanish_text)
|
||||||
ai_helper_context_menu.click_ai_button
|
ai_helper_context_menu.click_ai_button
|
||||||
|
@ -173,12 +191,57 @@ RSpec.describe "AI Composer helper", type: :system, js: true do
|
||||||
expect(composer.composer_input.value).to eq(OpenAiCompletionsInferenceStubs.spanish_text)
|
expect(composer.composer_input.value).to eq(OpenAiCompletionsInferenceStubs.spanish_text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "confirms the results when confirm button is pressed" do
|
||||||
|
trigger_context_menu(OpenAiCompletionsInferenceStubs.spanish_text)
|
||||||
|
ai_helper_context_menu.click_ai_button
|
||||||
|
ai_helper_context_menu.select_helper_model(
|
||||||
|
OpenAiCompletionsInferenceStubs.text_mode_to_id(mode),
|
||||||
|
)
|
||||||
|
|
||||||
|
wait_for do
|
||||||
|
composer.composer_input.value == OpenAiCompletionsInferenceStubs.translated_response.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
ai_helper_context_menu.click_confirm_button
|
||||||
|
expect(composer.composer_input.value).to eq(
|
||||||
|
OpenAiCompletionsInferenceStubs.translated_response.strip,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
it "hides the context menu when pressing Escape on the keyboard" do
|
it "hides the context menu when pressing Escape on the keyboard" do
|
||||||
trigger_context_menu(OpenAiCompletionsInferenceStubs.spanish_text)
|
trigger_context_menu(OpenAiCompletionsInferenceStubs.spanish_text)
|
||||||
ai_helper_context_menu.click_ai_button
|
ai_helper_context_menu.click_ai_button
|
||||||
ai_helper_context_menu.press_escape_key
|
ai_helper_context_menu.press_escape_key
|
||||||
expect(ai_helper_context_menu).to have_no_context_menu
|
expect(ai_helper_context_menu).to have_no_context_menu
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "shows the changes in a modal when view changes button is pressed" do
|
||||||
|
trigger_context_menu(OpenAiCompletionsInferenceStubs.spanish_text)
|
||||||
|
ai_helper_context_menu.click_ai_button
|
||||||
|
ai_helper_context_menu.select_helper_model(
|
||||||
|
OpenAiCompletionsInferenceStubs.text_mode_to_id(mode),
|
||||||
|
)
|
||||||
|
|
||||||
|
wait_for do
|
||||||
|
composer.composer_input.value == OpenAiCompletionsInferenceStubs.translated_response.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
ai_helper_context_menu.click_view_changes_button
|
||||||
|
expect(diff_modal).to be_visible
|
||||||
|
expect(diff_modal.old_value).to eq(
|
||||||
|
OpenAiCompletionsInferenceStubs.spanish_text.gsub(/[[:space:]]+/, " ").strip,
|
||||||
|
)
|
||||||
|
expect(diff_modal.new_value).to eq(
|
||||||
|
OpenAiCompletionsInferenceStubs
|
||||||
|
.translated_response
|
||||||
|
.gsub(/[[:space:]]+/, " ")
|
||||||
|
.gsub(/[‘’]/, "'")
|
||||||
|
.gsub(/[“”]/, '"')
|
||||||
|
.strip,
|
||||||
|
)
|
||||||
|
diff_modal.confirm_changes
|
||||||
|
expect(ai_helper_context_menu).to be_showing_resets
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when using the proofreading mode" do
|
context "when using the proofreading mode" do
|
||||||
|
|
|
@ -10,6 +10,7 @@ module PageObjects
|
||||||
SUGGESTIONS_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__suggestions"
|
SUGGESTIONS_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__suggestions"
|
||||||
LOADING_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__loading"
|
LOADING_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__loading"
|
||||||
RESETS_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__resets"
|
RESETS_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__resets"
|
||||||
|
REVIEW_STATE_SELECTOR = "#{CONTEXT_MENU_SELECTOR}__review"
|
||||||
|
|
||||||
def click_ai_button
|
def click_ai_button
|
||||||
find("#{TRIGGER_STATE_SELECTOR} .btn").click
|
find("#{TRIGGER_STATE_SELECTOR} .btn").click
|
||||||
|
@ -27,6 +28,18 @@ module PageObjects
|
||||||
find("#{RESETS_STATE_SELECTOR} .undo").click
|
find("#{RESETS_STATE_SELECTOR} .undo").click
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def click_revert_button
|
||||||
|
find("#{REVIEW_STATE_SELECTOR} .revert").click
|
||||||
|
end
|
||||||
|
|
||||||
|
def click_view_changes_button
|
||||||
|
find("#{REVIEW_STATE_SELECTOR} .view-changes").click
|
||||||
|
end
|
||||||
|
|
||||||
|
def click_confirm_button
|
||||||
|
find("#{REVIEW_STATE_SELECTOR} .confirm").click
|
||||||
|
end
|
||||||
|
|
||||||
def press_undo_keys
|
def press_undo_keys
|
||||||
find(COMPOSER_EDITOR_SELECTOR).send_keys([:control, "z"])
|
find(COMPOSER_EDITOR_SELECTOR).send_keys([:control, "z"])
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module PageObjects
|
||||||
|
module Modals
|
||||||
|
class DiffModal < PageObjects::Modals::Base
|
||||||
|
def visible?
|
||||||
|
page.has_css?(".composer-ai-helper-modal", wait: 5)
|
||||||
|
end
|
||||||
|
|
||||||
|
def confirm_changes
|
||||||
|
find(".modal-footer button.confirm", wait: 5).click
|
||||||
|
end
|
||||||
|
|
||||||
|
def old_value
|
||||||
|
find(".composer-ai-helper-modal__old-value").text
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_value
|
||||||
|
find(".composer-ai-helper-modal__new-value").text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue