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

Commit bf5ccb4

Browse files
authored
FEATURE: Continue conversation from Discobot discovery (#1234)
This feature update allows for continuing the conversation with Discobot Discoveries in an AI bot chat. After discoveries gives you a response to your search you can continue with the existing context.
1 parent 5331b6d commit bf5ccb4

File tree

8 files changed

+192
-0
lines changed

8 files changed

+192
-0
lines changed

app/controllers/discourse_ai/ai_bot/bot_controller.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,40 @@ def discover
6969

7070
render json: {}, status: 200
7171
end
72+
73+
def discover_continue_convo
74+
raise Discourse::InvalidParameters.new("user_id") if !params[:user_id]
75+
raise Discourse::InvalidParameters.new("query") if !params[:query]
76+
raise Discourse::InvalidParameters.new("context") if !params[:context]
77+
78+
user = User.find(params[:user_id])
79+
80+
bot_user_id = AiPersona.find_by(id: SiteSetting.ai_bot_discover_persona).user_id
81+
bot_username = User.find_by(id: bot_user_id).username
82+
83+
query = params[:query]
84+
context = "[quote]\n#{params[:context]}\n[/quote]"
85+
86+
post =
87+
PostCreator.create!(
88+
user,
89+
title:
90+
I18n.t("discourse_ai.ai_bot.discoveries.continue_conversation.title", query: query),
91+
raw:
92+
I18n.t(
93+
"discourse_ai.ai_bot.discoveries.continue_conversation.raw",
94+
query: query,
95+
context: context,
96+
),
97+
archetype: Archetype.private_message,
98+
target_usernames: bot_username,
99+
skip_validations: true,
100+
)
101+
102+
render json: success_json.merge(topic_id: post.topic_id)
103+
rescue StandardError => e
104+
render json: failed_json.merge(errors: [e.message]), status: 422
105+
end
72106
end
73107
end
74108
end

assets/javascripts/discourse/components/ai-search-discoveries.gjs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ import CookText from "discourse/components/cook-text";
1010
import DButton from "discourse/components/d-button";
1111
import concatClass from "discourse/helpers/concat-class";
1212
import { ajax } from "discourse/lib/ajax";
13+
import { popupAjaxError } from "discourse/lib/ajax-error";
1314
import { bind } from "discourse/lib/decorators";
1415
import { withPluginApi } from "discourse/lib/plugin-api";
16+
import DiscourseURL from "discourse/lib/url";
17+
import Topic from "discourse/models/topic";
1518
import { i18n } from "discourse-i18n";
1619
import SmoothStreamer from "../lib/smooth-streamer";
1720
import AiBlinkingAnimation from "./ai-blinking-animation";
21+
import AiIndicatorWave from "./ai-indicator-wave";
1822

1923
const DISCOVERY_TIMEOUT_MS = 10000;
2024

@@ -23,7 +27,11 @@ export default class AiSearchDiscoveries extends Component {
2327
@service messageBus;
2428
@service discobotDiscoveries;
2529
@service appEvents;
30+
@service currentUser;
31+
@service siteSettings;
32+
@service composer;
2633

34+
@tracked loadingConversationTopic = false;
2735
@tracked hideDiscoveries = false;
2836
@tracked fullDiscoveryToggled = false;
2937
@tracked discoveryPreviewLength = this.args.discoveryPreviewLength || 150;
@@ -145,6 +153,28 @@ export default class AiSearchDiscoveries extends Component {
145153
return !this.fullDiscoveryToggled && this.canShowExpandtoggle;
146154
}
147155

156+
get canContinueConversation() {
157+
const personas = this.currentUser?.ai_enabled_personas;
158+
const discoverPersona = personas.find(
159+
(persona) => persona.id === this.siteSettings?.ai_bot_discover_persona
160+
);
161+
const discoverPersonaHasBot = discoverPersona?.username;
162+
163+
return (
164+
this.discobotDiscoveries.discovery?.length > 0 &&
165+
!this.smoothStreamer.isStreaming &&
166+
discoverPersonaHasBot
167+
);
168+
}
169+
170+
get continueConvoBtnLabel() {
171+
if (this.loadingConversationTopic) {
172+
return "discourse_ai.discobot_discoveries.loading_convo";
173+
}
174+
175+
return "discourse_ai.discobot_discoveries.continue_convo";
176+
}
177+
148178
@action
149179
async triggerDiscovery() {
150180
if (this.discobotDiscoveries.lastQuery === this.query) {
@@ -180,6 +210,43 @@ export default class AiSearchDiscoveries extends Component {
180210
this.fullDiscoveryToggled = !this.fullDiscoveryToggled;
181211
}
182212

213+
@action
214+
async continueConversation() {
215+
const data = {
216+
user_id: this.currentUser.id,
217+
query: this.query,
218+
context: this.discobotDiscoveries.discovery,
219+
};
220+
try {
221+
this.loadingConversationTopic = true;
222+
const continueRequest = await ajax(
223+
`/discourse-ai/ai-bot/discover/continue-convo`,
224+
{
225+
type: "POST",
226+
data,
227+
}
228+
);
229+
const topicJSON = await Topic.find(continueRequest.topic_id, {});
230+
const topic = Topic.create(topicJSON);
231+
232+
DiscourseURL.routeTo(`/t/${continueRequest.topic_id}`, {
233+
afterRouteComplete: () => {
234+
if (this.args.closeSearchMenu) {
235+
this.args.closeSearchMenu();
236+
}
237+
238+
this.composer.focusComposer({
239+
topic,
240+
});
241+
},
242+
});
243+
} catch (e) {
244+
popupAjaxError(e);
245+
} finally {
246+
this.loadingConversationTopic = false;
247+
}
248+
}
249+
183250
timeoutDiscovery() {
184251
this.discobotDiscoveries.loadingDiscoveries = false;
185252
this.discobotDiscoveries.discovery = "";
@@ -226,6 +293,18 @@ export default class AiSearchDiscoveries extends Component {
226293
{{/if}}
227294
{{/if}}
228295
</div>
296+
297+
{{#if this.canContinueConversation}}
298+
<div class="ai-search-discoveries__continue-conversation">
299+
<DButton
300+
@action={{this.continueConversation}}
301+
@label={{this.continueConvoBtnLabel}}
302+
class="btn-small"
303+
>
304+
<AiIndicatorWave @loading={{this.loadingConversationTopic}} />
305+
</DButton>
306+
</div>
307+
{{/if}}
229308
</div>
230309
</template>
231310
}

assets/javascripts/discourse/connectors/search-menu-results-top/ai-discobot-discoveries.gjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export default class AiDiscobotDiscoveries extends Component {
3333
<AiSearchDiscoveries
3434
@searchTerm={{@outletArgs.searchTerm}}
3535
@discoveryPreviewLength={{50}}
36+
@closeSearchMenu={{@outletArgs.closeSearchMenu}}
3637
/>
3738

3839
{{#if this.search.results.topics.length}}

assets/stylesheets/modules/ai-bot/common/ai-discobot-discoveries.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@
6262
.cooked p:first-child {
6363
margin-top: 0;
6464
}
65+
66+
&__continue-conversation {
67+
margin-block: 1rem;
68+
}
6569
}
6670

6771
.ai-search-discoveries-tooltip {

config/locales/client.en.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,8 @@ en:
731731
main_title: "Discobot discoveries"
732732
regular_results: "Topics"
733733
tell_me_more: "Tell me more..."
734+
continue_convo: "Continue conversation..."
735+
loading_convo: "Loading conversation"
734736
collapse: "Collapse"
735737
timed_out: "Discobot couldn't find any discoveries. Something went wrong."
736738
user_setting: "Enable search discoveries"

config/locales/server.en.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,10 @@ en:
422422
search_settings:
423423
one: "Found %{count} result for '%{query}'"
424424
other: "Found %{count} results for '%{query}'"
425+
discoveries:
426+
continue_conversation:
427+
title: "Discovery conversation: Search for %{query}"
428+
raw: "In my search for %{query}, you showed me the following information:\n\n%{context}\n\nLet's continue the conversation."
425429

426430
summarization:
427431
configuration_hint:

config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
post "post/:post_id/stop-streaming" => "bot#stop_streaming_response"
2727

2828
get "discover" => "bot#discover"
29+
post "discover/continue-convo" => "bot#discover_continue_convo"
2930
end
3031

3132
scope module: :ai_bot, path: "/ai-bot/shared-ai-conversations" do

spec/requests/ai_bot/bot_controller_spec.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,71 @@
168168
end
169169
end
170170
end
171+
172+
describe "#discover_continue_convo" do
173+
before { SiteSetting.ai_bot_enabled = true }
174+
fab!(:group)
175+
fab!(:llm_model)
176+
fab!(:ai_persona) do
177+
persona = Fabricate(:ai_persona, allowed_group_ids: [group.id], default_llm_id: llm_model.id)
178+
persona.create_user!
179+
persona
180+
end
181+
let(:query) { "What is Discourse?" }
182+
let(:context) { "Discourse is an open-source discussion platform." }
183+
184+
context "when the user is allowed to discover" do
185+
before do
186+
SiteSetting.ai_bot_discover_persona = ai_persona.id
187+
group.add(user)
188+
end
189+
190+
it "returns a 200 and creates a private message topic" do
191+
expect {
192+
post "/discourse-ai/ai-bot/discover/continue-convo",
193+
params: {
194+
user_id: user.id,
195+
query: query,
196+
context: context,
197+
}
198+
}.to change(Topic, :count).by(1)
199+
200+
expect(response.status).to eq(200)
201+
expect(response.parsed_body["topic_id"]).to be_present
202+
end
203+
204+
it "returns invalid parameters if the user_id is missing" do
205+
post "/discourse-ai/ai-bot/discover/continue-convo",
206+
params: {
207+
query: query,
208+
context: context,
209+
}
210+
211+
expect(response.status).to eq(422)
212+
expect(response.parsed_body["errors"]).to include("user_id")
213+
end
214+
215+
it "returns invalid parameters if the query is missing" do
216+
post "/discourse-ai/ai-bot/discover/continue-convo",
217+
params: {
218+
user_id: user.id,
219+
context: context,
220+
}
221+
222+
expect(response.status).to eq(422)
223+
expect(response.parsed_body["errors"]).to include("query")
224+
end
225+
226+
it "returns invalid parameters if the context is missing" do
227+
post "/discourse-ai/ai-bot/discover/continue-convo",
228+
params: {
229+
user_id: user.id,
230+
query: query,
231+
}
232+
233+
expect(response.status).to eq(422)
234+
expect(response.parsed_body["errors"]).to include("context")
235+
end
236+
end
237+
end
171238
end

0 commit comments

Comments
 (0)