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:
Keegan George 2023-12-14 08:47:20 -08:00 committed by GitHub
parent 6de9b9c274
commit 74a7ac4a3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 131 additions and 77 deletions

View File

@ -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);
}
}

View File

@ -17,27 +17,11 @@
<ul class="ai-helper-context-menu__options">
{{#each this.helperOptions as |option|}}
{{#if (eq option.name "custom_prompt")}}
<div
class="ai-custom-prompt"
{{did-insert this.setupCustomPrompt}}
>
<Input
@value={{this.customPromptValue}}
placeholder={{i18n
"discourse_ai.ai_helper.context_menu.custom_prompt.placeholder"
}}
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>
<AiHelperCustomPrompt
@value={{this.customPromptValue}}
@promptArgs={{option}}
@submit={{this.updateSelected}}
/>
{{else}}
<li data-name={{option.translated_name}} data-value={{option.id}}>
<DButton

View File

@ -11,6 +11,7 @@ import { cook } from "discourse/lib/text";
import { bind } from "discourse-common/utils/decorators";
import eq from "truth-helpers/helpers/eq";
import not from "truth-helpers/helpers/not";
import AiHelperCustomPrompt from "../../components/ai-helper-custom-prompt";
import AiHelperLoading from "../../components/ai-helper-loading";
import { showPostAIHelper } from "../../lib/show-ai-helper";
@ -19,12 +20,14 @@ export default class AIHelperOptionsMenu extends Component {
return showPostAIHelper(outletArgs, helper);
}
@service messageBus;
@service siteSettings;
@service currentUser;
@tracked helperOptions = [];
@tracked menuState = this.MENU_STATES.triggers;
@tracked loading = false;
@tracked suggestion = "";
@tracked showMainButtons = true;
@tracked customPromptValue = "";
@tracked copyButtonIcon = "copy";
@tracked copyButtonLabel = "discourse_ai.ai_helper.post_options_menu.copy";
@ -96,18 +99,23 @@ export default class AIHelperOptionsMenu extends Component {
this._activeAIRequest = ajax("/discourse-ai/ai-helper/suggest", {
method: "POST",
data: {
mode: option.value,
mode: option.id,
text: this.args.outletArgs.data.quoteState.buffer,
custom_prompt: "",
custom_prompt: this.customPromptValue,
},
});
}
if (option.name !== "Explain") {
this._activeAIRequest.catch(popupAjaxError).finally(() => {
this.loading = false;
this.menuState = this.MENU_STATES.result;
});
this._activeAIRequest
.then(({ suggestions }) => {
this.suggestion = suggestions[0];
})
.catch(popupAjaxError)
.finally(() => {
this.loading = false;
this.menuState = this.MENU_STATES.result;
});
}
return this._activeAIRequest;
@ -142,15 +150,32 @@ export default class AIHelperOptionsMenu extends Component {
async loadPrompts() {
let prompts = await ajax("/discourse-ai/ai-helper/prompts");
this.helperOptions = prompts
.filter((item) => item.location.includes("post"))
.map((p) => {
return {
name: p.translated_name,
value: p.id,
icon: p.icon,
};
});
prompts = prompts.filter((item) => item.location.includes("post"));
// Find the custom_prompt object and move it to the beginning of the array
const customPromptIndex = prompts.findIndex(
(p) => p.name === "custom_prompt"
);
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>
@ -170,15 +195,23 @@ export default class AIHelperOptionsMenu extends Component {
{{else if (eq this.menuState this.MENU_STATES.options)}}
<div class="ai-post-helper__options">
{{#each this.helperOptions as |option|}}
<DButton
@class="btn-flat"
@icon={{option.icon}}
@translatedLabel={{option.name}}
@action={{this.performAISuggestion}}
@actionParam={{option}}
data-name={{option.name}}
data-value={{option.value}}
/>
{{#if (eq option.name "custom_prompt")}}
<AiHelperCustomPrompt
@value={{this.customPromptValue}}
@promptArgs={{option}}
@submit={{this.performAISuggestion}}
/>
{{else}}
<DButton
@class="btn-flat"
@icon={{option.icon}}
@translatedLabel={{option.translated_name}}
@action={{this.performAISuggestion}}
@actionParam={{option}}
data-name={{option.name}}
data-value={{option.value}}
/>
{{/if}}
{{/each}}
</div>

View File

@ -108,39 +108,19 @@
align-items: center;
flex-flow: row wrap;
}
}
&__custom-prompt {
display: flex;
flex-flow: row wrap;
padding: 0.5rem;
.ai-custom-prompt {
display: flex;
gap: 0.25rem;
margin-bottom: 0.5rem;
&-header {
margin-bottom: 0.5rem;
&__input[type="text"] {
border-color: var(--primary-400);
margin-bottom: 0;
.btn {
padding: 0;
}
}
.ai-custom-prompt-input {
min-height: 90px;
width: 100%;
}
}
.ai-custom-prompt {
display: flex;
gap: 0.25rem;
margin-bottom: 0.5rem;
&__input {
background: var(--primary-low);
border-color: var(--primary-low);
margin-bottom: 0;
&::placeholder {
color: var(--primary-medium);
}
&::placeholder {
color: var(--primary-medium);
}
}
}
@ -326,6 +306,15 @@
align-items: flex-start;
gap: 0.25rem;
justify-content: flex-start;
> button:last-child {
margin-bottom: 0.5rem;
}
.ai-custom-prompt {
padding: 0.5rem;
margin-bottom: 0;
}
}
&__suggestion {

View File

@ -85,6 +85,8 @@ module DiscourseAi
</input>
<output>
</output>
<result>
</result>
]
result.dup.tap { |dup_result| tags_to_remove.each { |tag| dup_result.gsub!(tag, "") } }
@ -130,7 +132,7 @@ module DiscourseAi
when "tone"
%w[composer]
when "custom_prompt"
%w[composer]
%w[composer post]
when "rewrite"
%w[composer]
when "explain"