@@ -2,46 +2,22 @@ import Component from "@glimmer/component";
22import { tracked } from " @glimmer/tracking" ;
33import { action } from " @ember/object" ;
44import { service } from " @ember/service" ;
5- import { modifier } from " ember-modifier" ;
6- import { eq } from " truth-helpers" ;
7- import DButton from " discourse/components/d-button" ;
8- import { ajax } from " discourse/lib/ajax" ;
9- import { popupAjaxError } from " discourse/lib/ajax-error" ;
10- import { bind } from " discourse-common/utils/decorators" ;
115import I18n from " discourse-i18n" ;
12- import AiHelperButtonGroup from " ../components/ai-helper-button-group" ;
13- import AiHelperLoading from " ../components/ai-helper-loading" ;
146import AiHelperOptionsList from " ../components/ai-helper-options-list" ;
157import ModalDiffModal from " ../components/modal/diff-modal" ;
168import ThumbnailSuggestion from " ../components/modal/thumbnail-suggestions" ;
179
1810export default class AiComposerHelperMenu extends Component {
1911 @service modal;
2012 @service siteSettings;
21- @service aiComposerHelper;
2213 @service currentUser;
23- @service capabilities ;
14+ @service site ;
2415 @tracked newSelectedText;
2516 @tracked diff;
26- @tracked initialValue = " " ;
2717 @tracked customPromptValue = " " ;
28- @tracked loading = false ;
29- @tracked lastUsedOption = null ;
30- @tracked thumbnailSuggestions = null ;
31- @tracked showThumbnailModal = false ;
32- @tracked lastSelectionRange = null ;
33- MENU_STATES = this .aiComposerHelper .MENU_STATES ;
3418 prompts = [];
3519 promptTypes = {};
3620
37- documentListeners = modifier (() => {
38- document .addEventListener (" keydown" , this .onKeyDown , { passive: true });
39-
40- return () => {
41- document .removeEventListener (" keydown" , this .onKeyDown );
42- };
43- });
44-
4521 get helperOptions () {
4622 let prompts = this .currentUser ? .ai_helper_prompts ;
4723
@@ -94,260 +70,51 @@ export default class AiComposerHelperMenu extends Component {
9470 return prompts;
9571 }
9672
97- get reviewButtons () {
98- return [
99- {
100- icon: " exchange-alt" ,
101- label: " discourse_ai.ai_helper.context_menu.view_changes" ,
102- action : () =>
103- this .modal .show (ModalDiffModal, {
104- model: {
105- diff: this .diff ,
106- oldValue: this .initialValue ,
107- newValue: this .newSelectedText ,
108- revert: this .undoAiAction ,
109- confirm : () => this .updateMenuState (this .MENU_STATES .resets ),
110- },
111- }),
112- classes: " view-changes" ,
113- },
114- {
115- icon: " undo" ,
116- label: " discourse_ai.ai_helper.context_menu.revert" ,
117- action: this .undoAiAction ,
118- classes: " revert" ,
119- },
120- {
121- icon: " check" ,
122- label: " discourse_ai.ai_helper.context_menu.confirm" ,
123- action : () => this .updateMenuState (this .MENU_STATES .resets ),
124- classes: " confirm" ,
125- },
126- ];
127- }
128-
129- get resetButtons () {
130- return [
131- {
132- icon: " undo" ,
133- label: " discourse_ai.ai_helper.context_menu.undo" ,
134- action: this .undoAiAction ,
135- classes: " undo" ,
136- },
137- {
138- icon: " discourse-sparkles" ,
139- label: " discourse_ai.ai_helper.context_menu.regen" ,
140- action : () => this .updateSelected (this .lastUsedOption ),
141- classes: " regenerate" ,
142- },
143- ];
144- }
145-
146- get canCloseMenu () {
147- if (
148- document .activeElement ===
149- document .querySelector (" .ai-custom-prompt__input" )
150- ) {
151- return false ;
152- }
153-
154- if (this .loading && this ._activeAiRequest !== null ) {
155- return false ;
156- }
157-
158- if (this .aiComposerHelper .menuState === this .MENU_STATES .review ) {
159- return false ;
160- }
161-
162- return true ;
163- }
164-
165- get isExpanded () {
166- if (this .aiComposerHelper .menuState === this .MENU_STATES .triggers ) {
167- return " " ;
168- }
169-
170- return " is-expanded" ;
171- }
172-
173- @bind
174- onKeyDown (event ) {
175- if (event .key === " Escape" ) {
176- return this .closeMenu ();
177- }
178- if (
179- event .key === " Backspace" &&
180- this .args .data .selectedText &&
181- this .aiComposerHelper .menuState === this .MENU_STATES .triggers
182- ) {
183- return this .closeMenu ();
184- }
185- }
186-
187- @action
188- toggleAiHelperOptions () {
189- this .updateMenuState (this .MENU_STATES .options );
190- }
191-
19273 @action
193- async updateSelected (option ) {
194- this ._toggleLoadingState (true );
195- this .lastUsedOption = option;
196- this .updateMenuState (this .MENU_STATES .loading );
197- this .initialValue = this .args .data .selectedText ;
198- this .lastSelectionRange = this .args .data .selectionRange ;
199-
200- try {
201- this ._activeAiRequest = await ajax (" /discourse-ai/ai-helper/suggest" , {
202- method: " POST" ,
203- data: {
74+ suggestChanges (option ) {
75+ if (option .name === " illustrate_post" ) {
76+ this .modal .show (ThumbnailSuggestion, {
77+ model: {
20478 mode: option .id ,
205- text: this .args .data .selectedText ,
206- custom_prompt: this .customPromptValue ,
207- force_default_locale: true ,
79+ selectedText: this .args .data .selectedText ,
80+ thumbnails: this .thumbnailSuggestions ,
20881 },
20982 });
210-
211- const data = await this ._activeAiRequest ;
212-
213- // resets the values if new suggestion is started:
214- this .diff = null ;
215- this .newSelectedText = null ;
216- this .thumbnailSuggestions = null ;
217-
218- if (option .name === " illustrate_post" ) {
219- this ._toggleLoadingState (false );
220- this .closeMenu ();
221- this .thumbnailSuggestions = data .thumbnails ;
222- this .modal .show (ThumbnailSuggestion, {
223- model: {
224- thumbnails: this .thumbnailSuggestions ,
225- },
226- });
227- } else {
228- this ._updateSuggestedByAi (data);
229- }
230- } catch (error) {
231- popupAjaxError (error);
232- } finally {
233- this ._toggleLoadingState (false );
234- }
235-
236- return this ._activeAiRequest ;
237- }
238-
239- @action
240- cancelAiAction () {
241- if (this ._activeAiRequest ) {
242- this ._activeAiRequest .abort ();
243- this ._activeAiRequest = null ;
244- this ._toggleLoadingState (false );
245- this .closeMenu ();
83+ return this .args .close ();
24684 }
247- }
24885
249- @action
250- updateMenuState (newState ) {
251- this .aiComposerHelper .menuState = newState;
86+ this .modal .show (ModalDiffModal, {
87+ model: {
88+ mode: option .id ,
89+ selectedText: this .args .data .selectedText ,
90+ revert: this .undoAiAction ,
91+ toolbarEvent: this .args .data .toolbarEvent ,
92+ customPromptValue: this .customPromptValue ,
93+ },
94+ });
95+ return this .args .close ();
25296 }
25397
25498 @action
25599 closeMenu () {
256- if (! this .canCloseMenu ) {
257- return ;
258- }
259-
260100 this .customPromptValue = " " ;
261- this .updateMenuState (this .MENU_STATES .triggers );
262101 this .args .close ();
263102 }
264103
265- @action
266- undoAiAction () {
267- if (this .capabilities .isFirefox ) {
268- // execCommand("undo") is no not supported in Firefox so we insert old text at range
269- // we also need to calculate the length diffrence between the old and new text
270- const lengthDifference =
271- this .args .data .selectedText .length - this .initialValue .length ;
272- const end = this .lastSelectionRange .y - lengthDifference;
273- this ._insertAt (this .lastSelectionRange .x , end, this .initialValue );
274- } else {
275- document .execCommand (" undo" , false , null );
276- }
277-
278- // context menu is prevented from closing when in review state
279- // so we change to reset state quickly before closing
280- this .updateMenuState (this .MENU_STATES .resets );
281- this .closeMenu ();
282- }
283-
284- _toggleLoadingState (loading ) {
285- if (loading) {
286- this .args .data .dEditorInput .classList .add (" loading" );
287- return (this .loading = true );
288- }
289-
290- this .args .data .dEditorInput .classList .remove (" loading" );
291- this .loading = false ;
292- }
293-
294- _updateSuggestedByAi (data ) {
295- this .newSelectedText = data .suggestions [0 ];
296-
297- if (data .diff ) {
298- this .diff = data .diff ;
299- }
300-
301- this ._insertAt (
302- this .args .data .selectionRange .x ,
303- this .args .data .selectionRange .y ,
304- this .newSelectedText
305- );
306-
307- this .updateMenuState (this .MENU_STATES .review );
308- }
309-
310- _insertAt (start , end , text ) {
311- this .args .data .dEditorInput .setSelectionRange (start, end);
312- this .args .data .dEditorInput .focus ();
313- document .execCommand (" insertText" , false , text);
314- }
315-
316104 <template >
317- <div
318- class =" ai-composer-helper-menu {{this .isExpanded }} "
319- {{this .documentListeners }}
320- >
321- {{#if ( eq this . aiComposerHelper.menuState this . MENU_STATES.triggers) }}
322- <ul class =" ai-composer-helper-menu__triggers" >
323- <li >
324- <DButton
325- @ icon =" discourse-sparkles"
326- @ label =" discourse_ai.ai_helper.context_menu.trigger"
327- @ action ={{this .toggleAiHelperOptions }}
328- class =" btn-flat"
329- />
330- </li >
331- </ul >
332- {{else if ( eq this . aiComposerHelper.menuState this . MENU_STATES.options) }}
333- <AiHelperOptionsList
334- @ options ={{this .helperOptions }}
335- @ customPromptValue ={{this .customPromptValue }}
336- @ performAction ={{this .updateSelected }}
337- />
338- {{else if ( eq this . aiComposerHelper.menuState this . MENU_STATES.loading) }}
339- <AiHelperLoading @ cancel ={{this .cancelAiAction }} />
340- {{else if ( eq this . aiComposerHelper.menuState this . MENU_STATES.review) }}
341- <AiHelperButtonGroup
342- @ buttons ={{this .reviewButtons }}
343- class =" ai-composer-helper-menu__review"
344- />
345- {{else if ( eq this . aiComposerHelper.menuState this . MENU_STATES.resets) }}
346- <AiHelperButtonGroup
347- @ buttons ={{this .resetButtons }}
348- class =" ai-composer-helper-menu__resets"
349- />
105+ <div class =" ai-composer-helper-menu" >
106+ {{#if this . site.mobileView }}
107+ <div class =" ai-composer-helper-menu__selected-text" >
108+ {{@ data.selectedText }}
109+ </div >
350110 {{/if }}
111+
112+ <AiHelperOptionsList
113+ @ options ={{this .helperOptions }}
114+ @ customPromptValue ={{this .customPromptValue }}
115+ @ performAction ={{this .suggestChanges }}
116+ @ shortcutVisible ={{ true }}
117+ />
351118 </div >
352119 </template >
353120}
0 commit comments