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

Commit 28bcfc3

Browse files
committed
WIP: Add streaming to composer helper
1 parent 0024f2d commit 28bcfc3

File tree

4 files changed

+109
-27
lines changed

4 files changed

+109
-27
lines changed

app/controllers/discourse_ai/ai_helper/assistant_controller.rb

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -110,26 +110,43 @@ def suggest_thumbnails(input)
110110
end
111111

112112
def stream_suggestion
113-
post_id = get_post_param!
114113
text = get_text_param!
115-
post = Post.includes(:topic).find_by(id: post_id)
114+
115+
location = params[:location]
116+
raise Discourse::InvalidParameters.new(:location) if !location
117+
116118
prompt = CompletionPrompt.find_by(id: params[:mode])
117119

118120
raise Discourse::InvalidParameters.new(:mode) if !prompt || !prompt.enabled?
119-
raise Discourse::InvalidParameters.new(:post_id) unless post
121+
return suggest_thumbnails(input) if prompt.id == CompletionPrompt::ILLUSTRATE_POST
120122

121123
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
122124
raise Discourse::InvalidParameters.new(:custom_prompt) if params[:custom_prompt].blank?
123125
end
124126

125-
Jobs.enqueue(
126-
:stream_post_helper,
127-
post_id: post.id,
128-
user_id: current_user.id,
129-
text: text,
130-
prompt: prompt.name,
131-
custom_prompt: params[:custom_prompt],
132-
)
127+
if location == "composer"
128+
Jobs.enqueue(
129+
:stream_composer_helper,
130+
user_id: current_user.id,
131+
text: text,
132+
prompt: prompt.name,
133+
custom_prompt: params[:custom_prompt],
134+
)
135+
else
136+
post_id = get_post_param!
137+
post = Post.includes(:topic).find_by(id: post_id)
138+
139+
raise Discourse::InvalidParameters.new(:post_id) unless post
140+
141+
Jobs.enqueue(
142+
:stream_post_helper,
143+
post_id: post.id,
144+
user_id: current_user.id,
145+
text: text,
146+
prompt: prompt.name,
147+
custom_prompt: params[:custom_prompt],
148+
)
149+
end
133150

134151
render json: { success: true }, status: 200
135152
rescue DiscourseAi::Completions::Endpoints::Base::CompletionFailed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# frozen_string_literal: true
2+
3+
module Jobs
4+
class StreamComposerHelper < ::Jobs::Base
5+
sidekiq_options retry: false
6+
# TODO handle force_default_locale stuff
7+
def execute(args)
8+
return unless args[:prompt]
9+
return unless user = User.find_by(id: args[:user_id])
10+
return unless args[:text]
11+
12+
prompt = CompletionPrompt.enabled_by_name(args[:prompt])
13+
14+
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
15+
prompt.custom_instruction = args[:custom_prompt]
16+
end
17+
18+
DiscourseAi::AiHelper::Assistant.new.stream_prompt(
19+
prompt,
20+
args[:text],
21+
user,
22+
"/discourse-ai/ai-helper/stream_suggestion",
23+
)
24+
end
25+
end
26+
end

assets/javascripts/discourse/components/modal/diff-modal.gjs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import Component from "@glimmer/component";
22
import { tracked } from "@glimmer/tracking";
33
import { action } from "@ember/object";
4+
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
5+
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
46
import { service } from "@ember/service";
57
import { htmlSafe } from "@ember/template";
68
import CookText from "discourse/components/cook-text";
79
import DButton from "discourse/components/d-button";
810
import DModal from "discourse/components/d-modal";
911
import { ajax } from "discourse/lib/ajax";
1012
import { popupAjaxError } from "discourse/lib/ajax-error";
13+
import { bind } from "discourse/lib/decorators";
1114
import { i18n } from "discourse-i18n";
1215
import AiIndicatorWave from "../ai-indicator-wave";
1316

1417
export default class ModalDiffModal extends Component {
1518
@service currentUser;
19+
@service messageBus;
1620

1721
@tracked loading = false;
1822
@tracked diff;
@@ -23,23 +27,47 @@ export default class ModalDiffModal extends Component {
2327
this.suggestChanges();
2428
}
2529

30+
@bind
31+
subscribe() {
32+
console.log("subscribe called");
33+
const channel = "/discourse-ai/ai-helper/stream_suggestion";
34+
this.messageBus.subscribe(channel, this.updateResult);
35+
}
36+
37+
@bind
38+
unsubscribe() {
39+
console.log("unsubscribe called");
40+
const channel = "/discourse-ai/ai-helper/stream_suggestion";
41+
this.messageBus.subscribe(channel, this.updateResult);
42+
}
43+
44+
@action
45+
async updateResult(result) {
46+
console.log("updateResult called");
47+
console.log("result", result);
48+
49+
this.diff = result.diff;
50+
this.suggestion = result.result;
51+
}
52+
2653
@action
2754
async suggestChanges() {
2855
this.loading = true;
2956

3057
try {
31-
const suggestion = await ajax("/discourse-ai/ai-helper/suggest", {
58+
return await ajax("/discourse-ai/ai-helper/stream_suggestion", {
3259
method: "POST",
3360
data: {
61+
location: "composer",
3462
mode: this.args.model.mode,
3563
text: this.args.model.selectedText,
3664
custom_prompt: this.args.model.customPromptValue,
3765
force_default_locale: true,
3866
},
3967
});
4068

41-
this.diff = suggestion.diff;
42-
this.suggestion = suggestion.suggestions[0];
69+
// this.diff = suggestion.diff;
70+
// this.suggestion = suggestion.suggestions[0];
4371
} catch (e) {
4472
popupAjaxError(e);
4573
} finally {
@@ -71,17 +99,23 @@ export default class ModalDiffModal extends Component {
7199
<CookText @rawText={{@model.selectedText}} />
72100
</div>
73101
{{else}}
74-
{{#if this.diff}}
75-
{{htmlSafe this.diff}}
76-
{{else}}
77-
<div class="composer-ai-helper-modal__old-value">
78-
{{@model.selectedText}}
79-
</div>
80-
81-
<div class="composer-ai-helper-modal__new-value">
82-
{{this.suggestion}}
83-
</div>
84-
{{/if}}
102+
<div
103+
class="composer-ai-helper-modal__suggestion"
104+
{{didInsert this.subscribe}}
105+
{{willDestroy this.unsubscribe}}
106+
>
107+
{{#if this.diff}}
108+
{{htmlSafe this.diff}}
109+
{{else}}
110+
<div class="composer-ai-helper-modal__old-value">
111+
{{@model.selectedText}}
112+
</div>
113+
114+
<div class="composer-ai-helper-modal__new-value">
115+
{{this.suggestion}}
116+
</div>
117+
{{/if}}
118+
</div>
85119
{{/if}}
86120

87121
</:body>

lib/ai_helper/assistant.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,23 +161,28 @@ def generate_and_send_prompt(completion_prompt, input, user, force_default_local
161161
end
162162

163163
def stream_prompt(completion_prompt, input, user, channel)
164+
streamed_diff = +""
164165
streamed_result = +""
165166
start = Time.now
166167

167168
generate_prompt(completion_prompt, input, user) do |partial_response, cancel_function|
168169
streamed_result << partial_response
169170

171+
streamed_diff = parse_diff(input, partial_response) if completion_prompt.diff?
172+
170173
# Throttle the updates
171174
if (Time.now - start > 0.5) || Rails.env.test?
172-
payload = { result: sanitize_result(streamed_result), done: false }
175+
payload = { result: sanitize_result(streamed_result), diff: streamed_diff, done: false }
173176
publish_update(channel, payload, user)
174177
start = Time.now
175178
end
176179
end
177180

181+
final_diff = parse_diff(input, streamed_result) if completion_prompt.diff?
182+
178183
sanitized_result = sanitize_result(streamed_result)
179184
if sanitized_result.present?
180-
publish_update(channel, { result: sanitized_result, done: true }, user)
185+
publish_update(channel, { result: sanitized_result, diff: final_diff, done: true }, user)
181186
end
182187
end
183188

0 commit comments

Comments
 (0)