mirror of
				https://github.com/discourse/discourse-ai.git
				synced 2025-10-31 06:28:48 +00:00 
			
		
		
		
	FEATURE: Add custom prompts to post helper options (#355)
* FEATURE: Add custom prompts to post helper options * 💄Make pretty * 💄Make pretty!
This commit is contained in:
		
							parent
							
								
									6de9b9c274
								
							
						
					
					
						commit
						74a7ac4a3d
					
				| @ -0,0 +1,46 @@ | |||||||
|  | import Component from "@glimmer/component"; | ||||||
|  | import { tracked } from "@glimmer/tracking"; | ||||||
|  | import { Input } from "@ember/component"; | ||||||
|  | import { action } from "@ember/object"; | ||||||
|  | import didInsert from "@ember/render-modifiers/modifiers/did-insert"; | ||||||
|  | import DButton from "discourse/components/d-button"; | ||||||
|  | import i18n from "discourse-common/helpers/i18n"; | ||||||
|  | import not from "truth-helpers/helpers/not"; | ||||||
|  | 
 | ||||||
|  | export default class AiHelperCustomPrompt extends Component { | ||||||
|  |   <template> | ||||||
|  |     <div class="ai-custom-prompt" {{didInsert this.setupCustomPrompt}}> | ||||||
|  |       <Input | ||||||
|  |         @value={{@value}} | ||||||
|  |         placeholder={{i18n | ||||||
|  |           "discourse_ai.ai_helper.context_menu.custom_prompt.placeholder" | ||||||
|  |         }} | ||||||
|  |         class="ai-custom-prompt__input" | ||||||
|  |         @enter={{this.sendInput}} | ||||||
|  |       /> | ||||||
|  | 
 | ||||||
|  |       <DButton | ||||||
|  |         @class="ai-custom-prompt__submit btn-primary" | ||||||
|  |         @icon="discourse-sparkles" | ||||||
|  |         @action={{@submit}} | ||||||
|  |         @actionParam={{@promptArgs}} | ||||||
|  |         @disabled={{not @value.length}} | ||||||
|  |       /> | ||||||
|  |     </div> | ||||||
|  |   </template> | ||||||
|  | 
 | ||||||
|  |   @tracked _customPromptInput; | ||||||
|  | 
 | ||||||
|  |   @action | ||||||
|  |   setupCustomPrompt() { | ||||||
|  |     this._customPromptInput = document.querySelector( | ||||||
|  |       ".ai-custom-prompt__input" | ||||||
|  |     ); | ||||||
|  |     this._customPromptInput.focus(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @action | ||||||
|  |   sendInput() { | ||||||
|  |     return this.args.submit(this.args.promptArgs); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -17,27 +17,11 @@ | |||||||
|         <ul class="ai-helper-context-menu__options"> |         <ul class="ai-helper-context-menu__options"> | ||||||
|           {{#each this.helperOptions as |option|}} |           {{#each this.helperOptions as |option|}} | ||||||
|             {{#if (eq option.name "custom_prompt")}} |             {{#if (eq option.name "custom_prompt")}} | ||||||
|               <div |               <AiHelperCustomPrompt | ||||||
|                 class="ai-custom-prompt" |  | ||||||
|                 {{did-insert this.setupCustomPrompt}} |  | ||||||
|               > |  | ||||||
|                 <Input |  | ||||||
|                 @value={{this.customPromptValue}} |                 @value={{this.customPromptValue}} | ||||||
|                   placeholder={{i18n |                 @promptArgs={{option}} | ||||||
|                     "discourse_ai.ai_helper.context_menu.custom_prompt.placeholder" |                 @submit={{this.updateSelected}} | ||||||
|                   }} |  | ||||||
|                   class="ai-custom-prompt__input" |  | ||||||
|                   @enter={{action (fn this.updateSelected option)}} |  | ||||||
|               /> |               /> | ||||||
| 
 |  | ||||||
|                 <DButton |  | ||||||
|                   @class="ai-custom-prompt__submit btn-primary" |  | ||||||
|                   @icon="discourse-sparkles" |  | ||||||
|                   @action={{this.updateSelected}} |  | ||||||
|                   @actionParam={{option}} |  | ||||||
|                   @disabled={{not this.customPromptValue.length}} |  | ||||||
|                 /> |  | ||||||
|               </div> |  | ||||||
|             {{else}} |             {{else}} | ||||||
|               <li data-name={{option.translated_name}} data-value={{option.id}}> |               <li data-name={{option.translated_name}} data-value={{option.id}}> | ||||||
|                 <DButton |                 <DButton | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ import { cook } from "discourse/lib/text"; | |||||||
| import { bind } from "discourse-common/utils/decorators"; | import { bind } from "discourse-common/utils/decorators"; | ||||||
| import eq from "truth-helpers/helpers/eq"; | import eq from "truth-helpers/helpers/eq"; | ||||||
| import not from "truth-helpers/helpers/not"; | import not from "truth-helpers/helpers/not"; | ||||||
|  | import AiHelperCustomPrompt from "../../components/ai-helper-custom-prompt"; | ||||||
| import AiHelperLoading from "../../components/ai-helper-loading"; | import AiHelperLoading from "../../components/ai-helper-loading"; | ||||||
| import { showPostAIHelper } from "../../lib/show-ai-helper"; | import { showPostAIHelper } from "../../lib/show-ai-helper"; | ||||||
| 
 | 
 | ||||||
| @ -19,12 +20,14 @@ export default class AIHelperOptionsMenu extends Component { | |||||||
|     return showPostAIHelper(outletArgs, helper); |     return showPostAIHelper(outletArgs, helper); | ||||||
|   } |   } | ||||||
|   @service messageBus; |   @service messageBus; | ||||||
|  |   @service siteSettings; | ||||||
|  |   @service currentUser; | ||||||
|   @tracked helperOptions = []; |   @tracked helperOptions = []; | ||||||
|   @tracked menuState = this.MENU_STATES.triggers; |   @tracked menuState = this.MENU_STATES.triggers; | ||||||
|   @tracked loading = false; |   @tracked loading = false; | ||||||
|   @tracked suggestion = ""; |   @tracked suggestion = ""; | ||||||
|   @tracked showMainButtons = true; |   @tracked showMainButtons = true; | ||||||
| 
 |   @tracked customPromptValue = ""; | ||||||
|   @tracked copyButtonIcon = "copy"; |   @tracked copyButtonIcon = "copy"; | ||||||
|   @tracked copyButtonLabel = "discourse_ai.ai_helper.post_options_menu.copy"; |   @tracked copyButtonLabel = "discourse_ai.ai_helper.post_options_menu.copy"; | ||||||
| 
 | 
 | ||||||
| @ -96,15 +99,20 @@ export default class AIHelperOptionsMenu extends Component { | |||||||
|       this._activeAIRequest = ajax("/discourse-ai/ai-helper/suggest", { |       this._activeAIRequest = ajax("/discourse-ai/ai-helper/suggest", { | ||||||
|         method: "POST", |         method: "POST", | ||||||
|         data: { |         data: { | ||||||
|           mode: option.value, |           mode: option.id, | ||||||
|           text: this.args.outletArgs.data.quoteState.buffer, |           text: this.args.outletArgs.data.quoteState.buffer, | ||||||
|           custom_prompt: "", |           custom_prompt: this.customPromptValue, | ||||||
|         }, |         }, | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (option.name !== "Explain") { |     if (option.name !== "Explain") { | ||||||
|       this._activeAIRequest.catch(popupAjaxError).finally(() => { |       this._activeAIRequest | ||||||
|  |         .then(({ suggestions }) => { | ||||||
|  |           this.suggestion = suggestions[0]; | ||||||
|  |         }) | ||||||
|  |         .catch(popupAjaxError) | ||||||
|  |         .finally(() => { | ||||||
|           this.loading = false; |           this.loading = false; | ||||||
|           this.menuState = this.MENU_STATES.result; |           this.menuState = this.MENU_STATES.result; | ||||||
|         }); |         }); | ||||||
| @ -142,15 +150,32 @@ export default class AIHelperOptionsMenu extends Component { | |||||||
|   async loadPrompts() { |   async loadPrompts() { | ||||||
|     let prompts = await ajax("/discourse-ai/ai-helper/prompts"); |     let prompts = await ajax("/discourse-ai/ai-helper/prompts"); | ||||||
| 
 | 
 | ||||||
|     this.helperOptions = prompts |     prompts = prompts.filter((item) => item.location.includes("post")); | ||||||
|       .filter((item) => item.location.includes("post")) | 
 | ||||||
|       .map((p) => { |     // Find the custom_prompt object and move it to the beginning of the array | ||||||
|         return { |     const customPromptIndex = prompts.findIndex( | ||||||
|           name: p.translated_name, |       (p) => p.name === "custom_prompt" | ||||||
|           value: p.id, |     ); | ||||||
|           icon: p.icon, | 
 | ||||||
|         }; |     if (customPromptIndex !== -1) { | ||||||
|       }); |       const customPrompt = prompts.splice(customPromptIndex, 1)[0]; | ||||||
|  |       prompts.unshift(customPrompt); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!this._showUserCustomPrompts()) { | ||||||
|  |       prompts = prompts.filter((p) => p.name !== "custom_prompt"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     this.helperOptions = prompts; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _showUserCustomPrompts() { | ||||||
|  |     const allowedGroups = | ||||||
|  |       this.siteSettings?.ai_helper_custom_prompts_allowed_groups | ||||||
|  |         .split("|") | ||||||
|  |         .map((id) => parseInt(id, 10)); | ||||||
|  | 
 | ||||||
|  |     return this.currentUser?.groups.some((g) => allowedGroups.includes(g.id)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   <template> |   <template> | ||||||
| @ -170,15 +195,23 @@ export default class AIHelperOptionsMenu extends Component { | |||||||
|       {{else if (eq this.menuState this.MENU_STATES.options)}} |       {{else if (eq this.menuState this.MENU_STATES.options)}} | ||||||
|         <div class="ai-post-helper__options"> |         <div class="ai-post-helper__options"> | ||||||
|           {{#each this.helperOptions as |option|}} |           {{#each this.helperOptions as |option|}} | ||||||
|  |             {{#if (eq option.name "custom_prompt")}} | ||||||
|  |               <AiHelperCustomPrompt | ||||||
|  |                 @value={{this.customPromptValue}} | ||||||
|  |                 @promptArgs={{option}} | ||||||
|  |                 @submit={{this.performAISuggestion}} | ||||||
|  |               /> | ||||||
|  |             {{else}} | ||||||
|               <DButton |               <DButton | ||||||
|                 @class="btn-flat" |                 @class="btn-flat" | ||||||
|                 @icon={{option.icon}} |                 @icon={{option.icon}} | ||||||
|               @translatedLabel={{option.name}} |                 @translatedLabel={{option.translated_name}} | ||||||
|                 @action={{this.performAISuggestion}} |                 @action={{this.performAISuggestion}} | ||||||
|                 @actionParam={{option}} |                 @actionParam={{option}} | ||||||
|                 data-name={{option.name}} |                 data-name={{option.name}} | ||||||
|                 data-value={{option.value}} |                 data-value={{option.value}} | ||||||
|               /> |               /> | ||||||
|  |             {{/if}} | ||||||
|           {{/each}} |           {{/each}} | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -108,41 +108,21 @@ | |||||||
|     align-items: center; |     align-items: center; | ||||||
|     flex-flow: row wrap; |     flex-flow: row wrap; | ||||||
|   } |   } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|   &__custom-prompt { | .ai-custom-prompt { | ||||||
|     display: flex; |  | ||||||
|     flex-flow: row wrap; |  | ||||||
|     padding: 0.5rem; |  | ||||||
| 
 |  | ||||||
|     &-header { |  | ||||||
|       margin-bottom: 0.5rem; |  | ||||||
| 
 |  | ||||||
|       .btn { |  | ||||||
|         padding: 0; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     .ai-custom-prompt-input { |  | ||||||
|       min-height: 90px; |  | ||||||
|       width: 100%; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   .ai-custom-prompt { |  | ||||||
|   display: flex; |   display: flex; | ||||||
|   gap: 0.25rem; |   gap: 0.25rem; | ||||||
|   margin-bottom: 0.5rem; |   margin-bottom: 0.5rem; | ||||||
| 
 | 
 | ||||||
|     &__input { |   &__input[type="text"] { | ||||||
|       background: var(--primary-low); |     border-color: var(--primary-400); | ||||||
|       border-color: var(--primary-low); |  | ||||||
|     margin-bottom: 0; |     margin-bottom: 0; | ||||||
| 
 | 
 | ||||||
|     &::placeholder { |     &::placeholder { | ||||||
|       color: var(--primary-medium); |       color: var(--primary-medium); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .d-editor-input.loading { | .d-editor-input.loading { | ||||||
| @ -326,6 +306,15 @@ | |||||||
|     align-items: flex-start; |     align-items: flex-start; | ||||||
|     gap: 0.25rem; |     gap: 0.25rem; | ||||||
|     justify-content: flex-start; |     justify-content: flex-start; | ||||||
|  | 
 | ||||||
|  |     > button:last-child { | ||||||
|  |       margin-bottom: 0.5rem; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .ai-custom-prompt { | ||||||
|  |       padding: 0.5rem; | ||||||
|  |       margin-bottom: 0; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &__suggestion { |   &__suggestion { | ||||||
|  | |||||||
| @ -85,6 +85,8 @@ module DiscourseAi | |||||||
|           </input> |           </input> | ||||||
|           <output> |           <output> | ||||||
|           </output> |           </output> | ||||||
|  |           <result> | ||||||
|  |           </result> | ||||||
|         ] |         ] | ||||||
| 
 | 
 | ||||||
|         result.dup.tap { |dup_result| tags_to_remove.each { |tag| dup_result.gsub!(tag, "") } } |         result.dup.tap { |dup_result| tags_to_remove.each { |tag| dup_result.gsub!(tag, "") } } | ||||||
| @ -130,7 +132,7 @@ module DiscourseAi | |||||||
|         when "tone" |         when "tone" | ||||||
|           %w[composer] |           %w[composer] | ||||||
|         when "custom_prompt" |         when "custom_prompt" | ||||||
|           %w[composer] |           %w[composer post] | ||||||
|         when "rewrite" |         when "rewrite" | ||||||
|           %w[composer] |           %w[composer] | ||||||
|         when "explain" |         when "explain" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user