Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit b767150

Browse files
committed
FEATURE: Use different personas to power AI helper features.
You can now edit each AI helper prompt individually through personas, limit access to specific groups, set different LLMs, etc.
1 parent 59f4b66 commit b767150

File tree

32 files changed

+816
-572
lines changed

32 files changed

+816
-572
lines changed

app/controllers/discourse_ai/ai_helper/assistant_controller.rb

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,24 @@ def suggest
2525
input = get_text_param!
2626
force_default_locale = params[:force_default_locale] || false
2727

28-
prompt = CompletionPrompt.find_by(id: params[:mode])
28+
raise Discourse::InvalidParameters.new(:mode) if params[:mode].blank?
2929

30-
raise Discourse::InvalidParameters.new(:mode) if !prompt || !prompt.enabled?
31-
32-
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
30+
if params[:mode] == DiscourseAi::AiHelper::Assistant::CUSTOM_PROMPT
3331
raise Discourse::InvalidParameters.new(:custom_prompt) if params[:custom_prompt].blank?
34-
35-
prompt.custom_instruction = params[:custom_prompt]
3632
end
3733

38-
return suggest_thumbnails(input) if prompt.id == CompletionPrompt::ILLUSTRATE_POST
34+
if params[:mode] == DiscourseAi::AiHelper::Assistant::ILLUSTRATE_POST
35+
return suggest_thumbnails(input)
36+
end
3937

4038
hijack do
4139
render json:
4240
DiscourseAi::AiHelper::Assistant.new.generate_and_send_prompt(
43-
prompt,
41+
params[:mode],
4442
input,
4543
current_user,
4644
force_default_locale: force_default_locale,
45+
custom_prompt: params[:custom_prompt],
4746
),
4847
status: 200
4948
end
@@ -60,13 +59,10 @@ def suggest_title
6059
input = get_text_param!
6160
end
6261

63-
prompt = CompletionPrompt.enabled_by_name("generate_titles")
64-
raise Discourse::InvalidParameters.new(:mode) if !prompt
65-
6662
hijack do
6763
render json:
6864
DiscourseAi::AiHelper::Assistant.new.generate_and_send_prompt(
69-
prompt,
65+
DiscourseAi::AiHelper::Assistant::GENERATE_TITLES,
7066
input,
7167
current_user,
7268
),
@@ -115,12 +111,12 @@ def stream_suggestion
115111
location = params[:location]
116112
raise Discourse::InvalidParameters.new(:location) if !location
117113

118-
prompt = CompletionPrompt.find_by(id: params[:mode])
119-
120-
raise Discourse::InvalidParameters.new(:mode) if !prompt || !prompt.enabled?
121-
return suggest_thumbnails(input) if prompt.id == CompletionPrompt::ILLUSTRATE_POST
114+
raise Discourse::InvalidParameters.new(:mode) if params[:mode].blank?
115+
if params[:mode] == DiscourseAi::AiHelper::Assistant::ILLUSTRATE_POST
116+
return suggest_thumbnails(input)
117+
end
122118

123-
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
119+
if params[:mode] == DiscourseAi::AiHelper::Assistant::CUSTOM_PROMPT
124120
raise Discourse::InvalidParameters.new(:custom_prompt) if params[:custom_prompt].blank?
125121
end
126122

@@ -133,7 +129,7 @@ def stream_suggestion
133129
:stream_composer_helper,
134130
user_id: current_user.id,
135131
text: text,
136-
prompt: prompt.name,
132+
prompt: params[:mode],
137133
custom_prompt: params[:custom_prompt],
138134
force_default_locale: params[:force_default_locale] || false,
139135
client_id: params[:client_id],
@@ -149,7 +145,7 @@ def stream_suggestion
149145
post_id: post.id,
150146
user_id: current_user.id,
151147
text: text,
152-
prompt: prompt.name,
148+
prompt: params[:mode],
153149
custom_prompt: params[:custom_prompt],
154150
client_id: params[:client_id],
155151
)

app/jobs/regular/stream_composer_helper.rb

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,16 @@ def execute(args)
1010
return unless args[:text]
1111
return unless args[:client_id]
1212

13-
prompt = CompletionPrompt.enabled_by_name(args[:prompt])
14-
15-
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
16-
prompt.custom_instruction = args[:custom_prompt]
17-
end
13+
helper_mode = args[:prompt]
1814

1915
DiscourseAi::AiHelper::Assistant.new.stream_prompt(
20-
prompt,
16+
helper_mode,
2117
args[:text],
2218
user,
2319
"/discourse-ai/ai-helper/stream_composer_suggestion",
2420
force_default_locale: args[:force_default_locale],
2521
client_id: args[:client_id],
22+
custom_prompt: args[:custom_prompt],
2623
)
2724
end
2825
end

app/jobs/regular/stream_post_helper.rb

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,12 @@ def execute(args)
1414

1515
return unless user.guardian.can_see?(post)
1616

17-
prompt = CompletionPrompt.enabled_by_name(args[:prompt])
17+
helper_mode = args[:prompt]
1818

19-
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
20-
prompt.custom_instruction = args[:custom_prompt]
21-
end
22-
23-
if prompt.name == "explain"
24-
input = <<~TEXT
25-
<term>#{args[:text]}</term>
26-
<context>#{post.raw}</context>
19+
if helper_mode == DiscourseAi::AiHelper::Assistant::EXPLAIN
20+
input = <<~TEXT.strip
21+
<term>#{args[:text]}</term>
22+
<context>#{post.raw}</context>
2723
<topic>#{topic.title}</topic>
2824
#{reply_to ? "<replyTo>#{reply_to.raw}</replyTo>" : nil}
2925
TEXT
@@ -32,10 +28,11 @@ def execute(args)
3228
end
3329

3430
DiscourseAi::AiHelper::Assistant.new.stream_prompt(
35-
prompt,
31+
helper_mode,
3632
input,
3733
user,
3834
"/discourse-ai/ai-helper/stream_suggestion/#{post.id}",
35+
custom_prompt: args[:custom_prompt],
3936
)
4037
end
4138
end

assets/javascripts/discourse/components/ai-composer-helper-menu.gjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export default class AiComposerHelperMenu extends Component {
7373
}
7474

7575
prompts.forEach((p) => {
76-
this.prompts[p.id] = p;
76+
this.prompts[p.name] = p;
7777
});
7878

7979
this.promptTypes = prompts.reduce((memo, p) => {
@@ -116,7 +116,7 @@ export default class AiComposerHelperMenu extends Component {
116116
if (option.name === "illustrate_post") {
117117
return this.modal.show(ThumbnailSuggestion, {
118118
model: {
119-
mode: option.id,
119+
mode: option.name,
120120
selectedText: this.args.data.selectedText,
121121
thumbnails: this.thumbnailSuggestions,
122122
},
@@ -128,7 +128,7 @@ export default class AiComposerHelperMenu extends Component {
128128

129129
return this.modal.show(ModalDiffModal, {
130130
model: {
131-
mode: option.id,
131+
mode: option.name,
132132
selectedText: this.args.data.selectedText,
133133
revert: this.undoAiAction,
134134
toolbarEvent: this.args.data.toolbarEvent,

assets/javascripts/discourse/components/ai-helper-options-list.gjs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@ export default class AiHelperOptionsList extends Component {
2929
@submit={{@performAction}}
3030
/>
3131
{{else}}
32-
<li data-name={{option.translated_name}} data-value={{option.id}}>
32+
<li data-name={{option.translated_name}} data-value={{option.name}}>
3333
<DButton
3434
@icon={{option.icon}}
3535
@translatedLabel={{option.translated_name}}
3636
@action={{fn @performAction option}}
3737
data-name={{option.name}}
38-
data-value={{option.id}}
3938
class="ai-helper-options__button"
4039
>
4140
{{#if (and (eq option.name "proofread") this.showShortcut)}}

assets/javascripts/discourse/components/ai-post-helper-menu.gjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export default class AiPostHelperMenu extends Component {
202202
this._activeAiRequest = ajax("/discourse-ai/ai-helper/suggest", {
203203
method: "POST",
204204
data: {
205-
mode: option.id,
205+
mode: option.name,
206206
text: this.args.data.quoteState.buffer,
207207
custom_prompt: this.customPromptValue,
208208
},
@@ -238,7 +238,7 @@ export default class AiPostHelperMenu extends Component {
238238
method: "POST",
239239
data: {
240240
location: "post",
241-
mode: option.id,
241+
mode: option.name,
242242
text: this.args.data.selectedText,
243243
post_id: this.args.data.quoteState.postId,
244244
custom_prompt: this.customPromptValue,

assets/javascripts/discourse/connectors/fast-edit-footer-after/ai-edit-suggestion-button.gjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default class AiEditSuggestionButton extends Component {
3434
this._activeAIRequest = ajax("/discourse-ai/ai-helper/suggest", {
3535
method: "POST",
3636
data: {
37-
mode: this.mode.id,
37+
mode: this.mode.name,
3838
text: this.args.outletArgs.initialValue,
3939
custom_prompt: "",
4040
},

assets/javascripts/initializers/ai-helper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function initializeAiHelperTrigger(api) {
4444

4545
const mode = currentUser?.ai_helper_prompts.find(
4646
(p) => p.name === "proofread"
47-
).id;
47+
).name;
4848

4949
modal.show(ModalDiffModal, {
5050
model: {

config/locales/server.en.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,33 @@ en:
339339
concept_deduplicator:
340340
name: "Concept Deduplicator"
341341
description: "AI Bot specialized in deduplicating concepts"
342+
custom_prompt:
343+
name: "Custom Prompt"
344+
description: "Default persona powering the AI helper's custom prompt feature"
345+
smart_dates:
346+
name: "Smart Dates"
347+
description: "Default persona powering the AI helper's smart dates feature"
348+
markdown_table_generator:
349+
name: "Markdown Table Generator"
350+
description: "Default persona powering the AI helper's generate Markdown table feature"
351+
post_illustrator:
352+
name: "Post Illustrator"
353+
description: "Generates StableDiffusion prompts to power the AI helper's illustrate post feature"
354+
proofreader:
355+
name: "Proofreader"
356+
description: "Default persona powering the AI helper's proofread text feature"
357+
titles_generator:
358+
name: "Titles Generator"
359+
description: "Default persona powering the AI helper's suggest topic titles feature"
360+
tutor:
361+
name: "Tutor"
362+
description: "Default persona powering the AI helper's explain feature"
363+
translator:
364+
name: "Translator"
365+
description: "Default persona powering the AI helper's translator feature"
366+
image_captioner:
367+
name: "Image Captions"
368+
description: "Default persona powering the AI helper's image caption feature"
342369
topic_not_found: "Summary unavailable, topic not found!"
343370
summarizing: "Summarizing topic"
344371
searching: "Searching for: '%{query}'"

config/settings.yml

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,14 @@ discourse_ai:
104104
allow_any: false
105105
type: enum
106106
enum: "DiscourseAi::Configuration::LlmEnumerator"
107-
validator: "DiscourseAi::Configuration::LlmValidator"
108-
ai_helper_custom_prompts_allowed_groups:
107+
hidden: true
108+
ai_helper_custom_prompts_allowed_groups: # Deprecated. TODO(roman): Remove 2025-09-01
109109
type: group_list
110110
list_type: compact
111111
default: "3" # 3: @staff
112112
allow_any: false
113113
refresh: true
114+
hidden: true
114115
post_ai_helper_allowed_groups:
115116
type: group_list
116117
list_type: compact
@@ -143,6 +144,7 @@ discourse_ai:
143144
default: ""
144145
type: enum
145146
enum: "DiscourseAi::Configuration::LlmVisionEnumerator"
147+
hidden: true
146148
ai_auto_image_caption_allowed_groups:
147149
client: true
148150
type: group_list
@@ -160,6 +162,42 @@ discourse_ai:
160162
hidden: true
161163
type: list
162164
list_type: compact
165+
ai_helper_proofreader_persona:
166+
default: "-22"
167+
type: enum
168+
enum: "DiscourseAi::Configuration::PersonaEnumerator"
169+
ai_helper_tittle_suggestions_persona:
170+
default: "-23"
171+
type: enum
172+
enum: "DiscourseAi::Configuration::PersonaEnumerator"
173+
ai_helper_explain_persona:
174+
default: "-24"
175+
type: enum
176+
enum: "DiscourseAi::Configuration::PersonaEnumerator"
177+
ai_helper_post_illustrator_persona:
178+
default: "-21"
179+
type: enum
180+
enum: "DiscourseAi::Configuration::PersonaEnumerator"
181+
ai_helper_smart_dates_persona:
182+
default: "-19"
183+
type: enum
184+
enum: "DiscourseAi::Configuration::PersonaEnumerator"
185+
ai_helper_translator_persona:
186+
default: "-25"
187+
type: enum
188+
enum: "DiscourseAi::Configuration::PersonaEnumerator"
189+
ai_helper_markdown_tables_persona:
190+
default: "-20"
191+
type: enum
192+
enum: "DiscourseAi::Configuration::PersonaEnumerator"
193+
ai_helper_custom_prompt_persona:
194+
default: "-18"
195+
type: enum
196+
enum: "DiscourseAi::Configuration::PersonaEnumerator"
197+
ai_helper_image_caption_persona:
198+
default: "-26"
199+
type: enum
200+
enum: "DiscourseAi::Configuration::PersonaEnumerator"
163201

164202
ai_embeddings_enabled:
165203
default: false

0 commit comments

Comments
 (0)