Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions app/controllers/discourse_ai/ai_bot/bot_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,40 @@ def discover

render json: {}, status: 200
end

def discover_continue_convo
raise Discourse::InvalidParameters.new("user_id") if !params[:user_id]
raise Discourse::InvalidParameters.new("query") if !params[:query]
raise Discourse::InvalidParameters.new("context") if !params[:context]

user = User.find(params[:user_id])

bot_user_id = AiPersona.find_by(id: SiteSetting.ai_bot_discover_persona).user_id
bot_username = User.find_by(id: bot_user_id).username

query = params[:query]
context = "[quote]\n#{params[:context]}\n[/quote]"

post =
PostCreator.create!(
user,
title:
I18n.t("discourse_ai.ai_bot.discoveries.continue_conversation.title", query: query),
raw:
I18n.t(
"discourse_ai.ai_bot.discoveries.continue_conversation.raw",
query: query,
context: context,
),
archetype: Archetype.private_message,
target_usernames: bot_username,
skip_validations: true,
)

render json: success_json.merge(topic_id: post.topic_id)
rescue StandardError => e
render json: failed_json.merge(errors: [e.message]), status: 422
end
end
end
end
71 changes: 71 additions & 0 deletions assets/javascripts/discourse/components/ai-search-discoveries.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ import CookText from "discourse/components/cook-text";
import DButton from "discourse/components/d-button";
import concatClass from "discourse/helpers/concat-class";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { bind } from "discourse/lib/decorators";
import { withPluginApi } from "discourse/lib/plugin-api";
import DiscourseURL from "discourse/lib/url";
import Topic from "discourse/models/topic";
import { i18n } from "discourse-i18n";
import SmoothStreamer from "../lib/smooth-streamer";
import AiBlinkingAnimation from "./ai-blinking-animation";
import AiIndicatorWave from "./ai-indicator-wave";

const DISCOVERY_TIMEOUT_MS = 10000;

Expand All @@ -23,7 +27,10 @@ export default class AiSearchDiscoveries extends Component {
@service messageBus;
@service discobotDiscoveries;
@service appEvents;
@service currentUser;
@service composer;

@tracked loadingConversationTopic = false;
@tracked hideDiscoveries = false;
@tracked fullDiscoveryToggled = false;
@tracked discoveryPreviewLength = this.args.discoveryPreviewLength || 150;
Expand Down Expand Up @@ -145,6 +152,21 @@ export default class AiSearchDiscoveries extends Component {
return !this.fullDiscoveryToggled && this.canShowExpandtoggle;
}

get canContinueConversation() {
return (
this.discobotDiscoveries.discovery?.length > 0 &&
!this.smoothStreamer.isStreaming
);
}

get continueConvoBtnLabel() {
if (this.loadingConversationTopic) {
return "discourse_ai.discobot_discoveries.loading_convo";
}

return "discourse_ai.discobot_discoveries.continue_convo";
}

@action
async triggerDiscovery() {
if (this.discobotDiscoveries.lastQuery === this.query) {
Expand Down Expand Up @@ -180,6 +202,43 @@ export default class AiSearchDiscoveries extends Component {
this.fullDiscoveryToggled = !this.fullDiscoveryToggled;
}

@action
async continueConversation() {
const data = {
user_id: this.currentUser.id,
query: this.query,
context: this.discobotDiscoveries.discovery,
};
try {
this.loadingConversationTopic = true;
const continueRequest = await ajax(
`/discourse-ai/ai-bot/discover/continue-convo`,
{
type: "POST",
data,
}
);
const topicJSON = await Topic.find(continueRequest.topic_id, {});
const topic = Topic.create(topicJSON);

DiscourseURL.routeTo(`/t/${continueRequest.topic_id}`, {
afterRouteComplete: () => {
if (this.args.closeSearchMenu) {
this.args.closeSearchMenu();
}

this.composer.focusComposer({
topic,
});
},
});
} catch (e) {
popupAjaxError(e);
} finally {
this.loadingConversationTopic = false;
}
}

timeoutDiscovery() {
this.discobotDiscoveries.loadingDiscoveries = false;
this.discobotDiscoveries.discovery = "";
Expand Down Expand Up @@ -226,6 +285,18 @@ export default class AiSearchDiscoveries extends Component {
{{/if}}
{{/if}}
</div>

{{#if this.canContinueConversation}}
<div class="ai-search-discoveries__continue-conversation">
<DButton
@action={{this.continueConversation}}
@label={{this.continueConvoBtnLabel}}
class="btn-small"
>
<AiIndicatorWave @loading={{this.loadingConversationTopic}} />
</DButton>
</div>
{{/if}}
</div>
</template>
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default class AiDiscobotDiscoveries extends Component {
<AiSearchDiscoveries
@searchTerm={{@outletArgs.searchTerm}}
@discoveryPreviewLength={{50}}
@closeSearchMenu={{@outletArgs.closeSearchMenu}}
/>

{{#if this.search.results.topics.length}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@
.cooked p:first-child {
margin-top: 0;
}

&__continue-conversation {
margin-block: 1rem;
}
}

.ai-search-discoveries-tooltip {
Expand Down
2 changes: 2 additions & 0 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,8 @@ en:
main_title: "Discobot discoveries"
regular_results: "Topics"
tell_me_more: "Tell me more..."
continue_convo: "Continue conversation..."
loading_convo: "Loading conversation"
collapse: "Collapse"
timed_out: "Discobot couldn't find any discoveries. Something went wrong."
user_setting: "Enable search discoveries"
Expand Down
4 changes: 4 additions & 0 deletions config/locales/server.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,10 @@ en:
search_settings:
one: "Found %{count} result for '%{query}'"
other: "Found %{count} results for '%{query}'"
discoveries:
continue_conversation:
title: "Discovery conversation: Search for %{query}"
raw: "In my search for %{query}, you showed me the following information:\n\n%{context}\n\nLet's continue the conversation."

summarization:
configuration_hint:
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
post "post/:post_id/stop-streaming" => "bot#stop_streaming_response"

get "discover" => "bot#discover"
post "discover/continue-convo" => "bot#discover_continue_convo"
end

scope module: :ai_bot, path: "/ai-bot/shared-ai-conversations" do
Expand Down
67 changes: 67 additions & 0 deletions spec/requests/ai_bot/bot_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,71 @@
end
end
end

describe "#discover_continue_convo" do
before { SiteSetting.ai_bot_enabled = true }
fab!(:group)
fab!(:llm_model)
fab!(:ai_persona) do
persona = Fabricate(:ai_persona, allowed_group_ids: [group.id], default_llm_id: llm_model.id)
persona.create_user!
persona
end
let(:query) { "What is Discourse?" }
let(:context) { "Discourse is an open-source discussion platform." }

context "when the user is allowed to discover" do
before do
SiteSetting.ai_bot_discover_persona = ai_persona.id
group.add(user)
end

it "returns a 200 and creates a private message topic" do
expect {
post "/discourse-ai/ai-bot/discover/continue-convo",
params: {
user_id: user.id,
query: query,
context: context,
}
}.to change(Topic, :count).by(1)

expect(response.status).to eq(200)
expect(response.parsed_body["topic_id"]).to be_present
end

it "returns invalid parameters if the user_id is missing" do
post "/discourse-ai/ai-bot/discover/continue-convo",
params: {
query: query,
context: context,
}

expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to include("user_id")
end

it "returns invalid parameters if the query is missing" do
post "/discourse-ai/ai-bot/discover/continue-convo",
params: {
user_id: user.id,
context: context,
}

expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to include("query")
end

it "returns invalid parameters if the context is missing" do
post "/discourse-ai/ai-bot/discover/continue-convo",
params: {
user_id: user.id,
query: query,
}

expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to include("context")
end
end
end
end