FEATURE: Stream other post helper options (#745)
This commit is contained in:
parent
1686a8a683
commit
1d6a6c9f8f
|
@ -89,18 +89,26 @@ module DiscourseAi
|
|||
end
|
||||
end
|
||||
|
||||
def explain
|
||||
def stream_suggestion
|
||||
post_id = get_post_param!
|
||||
term_to_explain = get_text_param!
|
||||
text = get_text_param!
|
||||
post = Post.includes(:topic).find_by(id: post_id)
|
||||
prompt = CompletionPrompt.find_by(id: params[:mode])
|
||||
|
||||
raise Discourse::InvalidParameters.new(:mode) if !prompt || !prompt.enabled?
|
||||
raise Discourse::InvalidParameters.new(:post_id) unless post
|
||||
|
||||
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
|
||||
raise Discourse::InvalidParameters.new(:custom_prompt) if params[:custom_prompt].blank?
|
||||
end
|
||||
|
||||
Jobs.enqueue(
|
||||
:stream_post_helper,
|
||||
post_id: post.id,
|
||||
user_id: current_user.id,
|
||||
term_to_explain: term_to_explain,
|
||||
text: text,
|
||||
prompt: prompt.name,
|
||||
custom_prompt: params[:custom_prompt],
|
||||
)
|
||||
|
||||
render json: { success: true }, status: 200
|
||||
|
|
|
@ -7,27 +7,35 @@ module Jobs
|
|||
def execute(args)
|
||||
return unless post = Post.includes(:topic).find_by(id: args[:post_id])
|
||||
return unless user = User.find_by(id: args[:user_id])
|
||||
return unless args[:term_to_explain]
|
||||
return unless args[:text]
|
||||
|
||||
topic = post.topic
|
||||
reply_to = post.reply_to_post
|
||||
|
||||
return unless user.guardian.can_see?(post)
|
||||
|
||||
prompt = CompletionPrompt.enabled_by_name("explain")
|
||||
prompt = CompletionPrompt.enabled_by_name(args[:prompt])
|
||||
|
||||
input = <<~TEXT
|
||||
<term>#{args[:term_to_explain]}</term>
|
||||
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
|
||||
prompt.custom_instruction = args[:custom_prompt]
|
||||
end
|
||||
|
||||
if prompt.name == "explain"
|
||||
input = <<~TEXT
|
||||
<term>#{args[:text]}</term>
|
||||
<context>#{post.raw}</context>
|
||||
<topic>#{topic.title}</topic>
|
||||
#{reply_to ? "<replyTo>#{reply_to.raw}</replyTo>" : nil}
|
||||
TEXT
|
||||
else
|
||||
input = args[:text]
|
||||
end
|
||||
|
||||
DiscourseAi::AiHelper::Assistant.new.stream_prompt(
|
||||
prompt,
|
||||
input,
|
||||
user,
|
||||
"/discourse-ai/ai-helper/explain/#{post.id}",
|
||||
"/discourse-ai/ai-helper/stream_suggestion/#{post.id}",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -116,14 +116,14 @@ export default class AiPostHelperMenu extends Component {
|
|||
|
||||
@bind
|
||||
subscribe() {
|
||||
const channel = `/discourse-ai/ai-helper/explain/${this.args.data.quoteState.postId}`;
|
||||
const channel = `/discourse-ai/ai-helper/stream_suggestion/${this.args.data.quoteState.postId}`;
|
||||
this.messageBus.subscribe(channel, this._updateResult);
|
||||
}
|
||||
|
||||
@bind
|
||||
unsubscribe() {
|
||||
this.messageBus.unsubscribe(
|
||||
"/discourse-ai/ai-helper/explain/*",
|
||||
"/discourse-ai/ai-helper/stream_suggestion/*",
|
||||
this._updateResult
|
||||
);
|
||||
}
|
||||
|
@ -143,9 +143,10 @@ export default class AiPostHelperMenu extends Component {
|
|||
async performAiSuggestion(option) {
|
||||
this.menuState = this.MENU_STATES.loading;
|
||||
this.lastSelectedOption = option;
|
||||
const streamableOptions = ["explain", "translate", "custom_prompt"];
|
||||
|
||||
if (option.name === "explain") {
|
||||
return this._handleExplainOption(option);
|
||||
if (streamableOptions.includes(option.name)) {
|
||||
return this._handleStreamedResult(option);
|
||||
} else {
|
||||
this._activeAiRequest = ajax("/discourse-ai/ai-helper/suggest", {
|
||||
method: "POST",
|
||||
|
@ -174,20 +175,21 @@ export default class AiPostHelperMenu extends Component {
|
|||
return this._activeAiRequest;
|
||||
}
|
||||
|
||||
_handleExplainOption(option) {
|
||||
_handleStreamedResult(option) {
|
||||
this.menuState = this.MENU_STATES.result;
|
||||
const menu = this.menu.getByIdentifier("post-text-selection-toolbar");
|
||||
if (menu) {
|
||||
menu.options.placement = "bottom";
|
||||
}
|
||||
const fetchUrl = `/discourse-ai/ai-helper/explain`;
|
||||
const fetchUrl = `/discourse-ai/ai-helper/stream_suggestion`;
|
||||
|
||||
this._activeAiRequest = ajax(fetchUrl, {
|
||||
method: "POST",
|
||||
data: {
|
||||
mode: option.value,
|
||||
mode: option.id,
|
||||
text: this.args.data.selectedText,
|
||||
post_id: this.args.data.quoteState.postId,
|
||||
custom_prompt: this.customPromptValue,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ DiscourseAi::Engine.routes.draw do
|
|||
post "suggest_title" => "assistant#suggest_title"
|
||||
post "suggest_category" => "assistant#suggest_category"
|
||||
post "suggest_tags" => "assistant#suggest_tags"
|
||||
post "explain" => "assistant#explain"
|
||||
post "stream_suggestion" => "assistant#stream_suggestion"
|
||||
post "caption_image" => "assistant#caption_image"
|
||||
end
|
||||
|
||||
|
|
|
@ -23,10 +23,13 @@ RSpec.describe Jobs::StreamPostHelper do
|
|||
end
|
||||
|
||||
describe "validates params" do
|
||||
let(:mode) { CompletionPrompt::EXPLAIN }
|
||||
let(:prompt) { CompletionPrompt.find_by(id: mode) }
|
||||
|
||||
it "does nothing if there is no post" do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/explain/#{post.id}") do
|
||||
job.execute(post_id: nil, user_id: user.id, term_to_explain: "pie")
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/streamed_suggestion/#{post.id}") do
|
||||
job.execute(post_id: nil, user_id: user.id, text: "pie", prompt: mode)
|
||||
end
|
||||
|
||||
expect(messages).to be_empty
|
||||
|
@ -35,53 +38,96 @@ RSpec.describe Jobs::StreamPostHelper do
|
|||
it "does nothing if there is no user" do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/explain/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: nil, term_to_explain: "pie")
|
||||
job.execute(post_id: post.id, user_id: nil, term_to_explain: "pie", prompt: mode)
|
||||
end
|
||||
|
||||
expect(messages).to be_empty
|
||||
end
|
||||
|
||||
it "does nothing if there is no term to explain" do
|
||||
it "does nothing if there is no text" do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/explain/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: user.id, term_to_explain: nil)
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/streamed_suggestion/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: user.id, text: nil, prompt: mode)
|
||||
end
|
||||
|
||||
expect(messages).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
it "publishes updates with a partial result" do
|
||||
explanation =
|
||||
"In this context, \"pie\" refers to a baked dessert typically consisting of a pastry crust and filling."
|
||||
context "when the prompt is explain" do
|
||||
let(:mode) { CompletionPrompt::EXPLAIN }
|
||||
let(:prompt) { CompletionPrompt.find_by(id: mode) }
|
||||
|
||||
partial_explanation = "I"
|
||||
it "publishes updates with a partial result" do
|
||||
explanation =
|
||||
"In this context, \"pie\" refers to a baked dessert typically consisting of a pastry crust and filling."
|
||||
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([explanation]) do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/explain/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: user.id, term_to_explain: "pie")
|
||||
end
|
||||
partial_explanation = "I"
|
||||
|
||||
partial_result_update = messages.first.data
|
||||
expect(partial_result_update[:done]).to eq(false)
|
||||
expect(partial_result_update[:result]).to eq(partial_explanation)
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([explanation]) do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/stream_suggestion/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: user.id, text: "pie", prompt: prompt.name)
|
||||
end
|
||||
|
||||
partial_result_update = messages.first.data
|
||||
expect(partial_result_update[:done]).to eq(false)
|
||||
expect(partial_result_update[:result]).to eq(partial_explanation)
|
||||
end
|
||||
end
|
||||
|
||||
it "publishes a final update to signal we're done" do
|
||||
explanation =
|
||||
"In this context, \"pie\" refers to a baked dessert typically consisting of a pastry crust and filling."
|
||||
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([explanation]) do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/stream_suggestion/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: user.id, text: "pie", prompt: prompt.name)
|
||||
end
|
||||
|
||||
final_update = messages.last.data
|
||||
expect(final_update[:result]).to eq(explanation)
|
||||
expect(final_update[:done]).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "publishes a final update to signal we're done" do
|
||||
explanation =
|
||||
"In this context, \"pie\" refers to a baked dessert typically consisting of a pastry crust and filling."
|
||||
context "when the prompt is translate" do
|
||||
let(:mode) { CompletionPrompt::TRANSLATE }
|
||||
let(:prompt) { CompletionPrompt.find_by(id: mode) }
|
||||
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([explanation]) do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/explain/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: user.id, term_to_explain: "pie")
|
||||
end
|
||||
it "publishes updates with a partial result" do
|
||||
sentence = "I like to eat pie."
|
||||
translation = "Me gusta comer pastel."
|
||||
partial_translation = "M"
|
||||
|
||||
final_update = messages.last.data
|
||||
expect(final_update[:result]).to eq(explanation)
|
||||
expect(final_update[:done]).to eq(true)
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([translation]) do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/stream_suggestion/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: user.id, text: sentence, prompt: prompt.name)
|
||||
end
|
||||
|
||||
partial_result_update = messages.first.data
|
||||
expect(partial_result_update[:done]).to eq(false)
|
||||
expect(partial_result_update[:result]).to eq(partial_translation)
|
||||
end
|
||||
end
|
||||
|
||||
it "publishes a final update to signal we're done" do
|
||||
sentence = "I like to eat pie."
|
||||
translation = "Me gusta comer pastel."
|
||||
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([translation]) do
|
||||
messages =
|
||||
MessageBus.track_publish("/discourse-ai/ai-helper/stream_suggestion/#{post.id}") do
|
||||
job.execute(post_id: post.id, user_id: user.id, text: sentence, prompt: prompt.name)
|
||||
end
|
||||
|
||||
final_update = messages.last.data
|
||||
expect(final_update[:result]).to eq(translation)
|
||||
expect(final_update[:done]).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -134,16 +134,18 @@ RSpec.describe "AI Post helper", type: :system, js: true do
|
|||
|
||||
let(:translated_input) { "The rain in Spain, stays mainly in the Plane." }
|
||||
|
||||
it "shows a translation of the selected text" do
|
||||
select_post_text(post_2)
|
||||
post_ai_helper.click_ai_button
|
||||
skip "TODO: Streaming causing timing issue in test" do
|
||||
it "shows a translation of the selected text" do
|
||||
select_post_text(post_2)
|
||||
post_ai_helper.click_ai_button
|
||||
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([translated_input]) do
|
||||
post_ai_helper.select_helper_model(mode)
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses([translated_input]) do
|
||||
post_ai_helper.select_helper_model(mode)
|
||||
|
||||
wait_for { post_ai_helper.suggestion_value == translated_input }
|
||||
wait_for { post_ai_helper.suggestion_value == translated_input }
|
||||
|
||||
expect(post_ai_helper.suggestion_value).to eq(translated_input)
|
||||
expect(post_ai_helper.suggestion_value).to eq(translated_input)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue