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.
This commit is contained in:
parent
5331b6dd8e
commit
bf5ccb452c
|
@ -69,6 +69,40 @@ module DiscourseAi
|
||||||
|
|
||||||
render json: {}, status: 200
|
render json: {}, status: 200
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,11 +10,15 @@ import CookText from "discourse/components/cook-text";
|
||||||
import DButton from "discourse/components/d-button";
|
import DButton from "discourse/components/d-button";
|
||||||
import concatClass from "discourse/helpers/concat-class";
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import { bind } from "discourse/lib/decorators";
|
import { bind } from "discourse/lib/decorators";
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
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 { i18n } from "discourse-i18n";
|
||||||
import SmoothStreamer from "../lib/smooth-streamer";
|
import SmoothStreamer from "../lib/smooth-streamer";
|
||||||
import AiBlinkingAnimation from "./ai-blinking-animation";
|
import AiBlinkingAnimation from "./ai-blinking-animation";
|
||||||
|
import AiIndicatorWave from "./ai-indicator-wave";
|
||||||
|
|
||||||
const DISCOVERY_TIMEOUT_MS = 10000;
|
const DISCOVERY_TIMEOUT_MS = 10000;
|
||||||
|
|
||||||
|
@ -23,7 +27,11 @@ export default class AiSearchDiscoveries extends Component {
|
||||||
@service messageBus;
|
@service messageBus;
|
||||||
@service discobotDiscoveries;
|
@service discobotDiscoveries;
|
||||||
@service appEvents;
|
@service appEvents;
|
||||||
|
@service currentUser;
|
||||||
|
@service siteSettings;
|
||||||
|
@service composer;
|
||||||
|
|
||||||
|
@tracked loadingConversationTopic = false;
|
||||||
@tracked hideDiscoveries = false;
|
@tracked hideDiscoveries = false;
|
||||||
@tracked fullDiscoveryToggled = false;
|
@tracked fullDiscoveryToggled = false;
|
||||||
@tracked discoveryPreviewLength = this.args.discoveryPreviewLength || 150;
|
@tracked discoveryPreviewLength = this.args.discoveryPreviewLength || 150;
|
||||||
|
@ -145,6 +153,28 @@ export default class AiSearchDiscoveries extends Component {
|
||||||
return !this.fullDiscoveryToggled && this.canShowExpandtoggle;
|
return !this.fullDiscoveryToggled && this.canShowExpandtoggle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get canContinueConversation() {
|
||||||
|
const personas = this.currentUser?.ai_enabled_personas;
|
||||||
|
const discoverPersona = personas.find(
|
||||||
|
(persona) => persona.id === this.siteSettings?.ai_bot_discover_persona
|
||||||
|
);
|
||||||
|
const discoverPersonaHasBot = discoverPersona?.username;
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.discobotDiscoveries.discovery?.length > 0 &&
|
||||||
|
!this.smoothStreamer.isStreaming &&
|
||||||
|
discoverPersonaHasBot
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get continueConvoBtnLabel() {
|
||||||
|
if (this.loadingConversationTopic) {
|
||||||
|
return "discourse_ai.discobot_discoveries.loading_convo";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "discourse_ai.discobot_discoveries.continue_convo";
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async triggerDiscovery() {
|
async triggerDiscovery() {
|
||||||
if (this.discobotDiscoveries.lastQuery === this.query) {
|
if (this.discobotDiscoveries.lastQuery === this.query) {
|
||||||
|
@ -180,6 +210,43 @@ export default class AiSearchDiscoveries extends Component {
|
||||||
this.fullDiscoveryToggled = !this.fullDiscoveryToggled;
|
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() {
|
timeoutDiscovery() {
|
||||||
this.discobotDiscoveries.loadingDiscoveries = false;
|
this.discobotDiscoveries.loadingDiscoveries = false;
|
||||||
this.discobotDiscoveries.discovery = "";
|
this.discobotDiscoveries.discovery = "";
|
||||||
|
@ -226,6 +293,18 @@ export default class AiSearchDiscoveries extends Component {
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ export default class AiDiscobotDiscoveries extends Component {
|
||||||
<AiSearchDiscoveries
|
<AiSearchDiscoveries
|
||||||
@searchTerm={{@outletArgs.searchTerm}}
|
@searchTerm={{@outletArgs.searchTerm}}
|
||||||
@discoveryPreviewLength={{50}}
|
@discoveryPreviewLength={{50}}
|
||||||
|
@closeSearchMenu={{@outletArgs.closeSearchMenu}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{{#if this.search.results.topics.length}}
|
{{#if this.search.results.topics.length}}
|
||||||
|
|
|
@ -62,6 +62,10 @@
|
||||||
.cooked p:first-child {
|
.cooked p:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__continue-conversation {
|
||||||
|
margin-block: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-search-discoveries-tooltip {
|
.ai-search-discoveries-tooltip {
|
||||||
|
|
|
@ -731,6 +731,8 @@ en:
|
||||||
main_title: "Discobot discoveries"
|
main_title: "Discobot discoveries"
|
||||||
regular_results: "Topics"
|
regular_results: "Topics"
|
||||||
tell_me_more: "Tell me more..."
|
tell_me_more: "Tell me more..."
|
||||||
|
continue_convo: "Continue conversation..."
|
||||||
|
loading_convo: "Loading conversation"
|
||||||
collapse: "Collapse"
|
collapse: "Collapse"
|
||||||
timed_out: "Discobot couldn't find any discoveries. Something went wrong."
|
timed_out: "Discobot couldn't find any discoveries. Something went wrong."
|
||||||
user_setting: "Enable search discoveries"
|
user_setting: "Enable search discoveries"
|
||||||
|
|
|
@ -422,6 +422,10 @@ en:
|
||||||
search_settings:
|
search_settings:
|
||||||
one: "Found %{count} result for '%{query}'"
|
one: "Found %{count} result for '%{query}'"
|
||||||
other: "Found %{count} results 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:
|
summarization:
|
||||||
configuration_hint:
|
configuration_hint:
|
||||||
|
|
|
@ -26,6 +26,7 @@ DiscourseAi::Engine.routes.draw do
|
||||||
post "post/:post_id/stop-streaming" => "bot#stop_streaming_response"
|
post "post/:post_id/stop-streaming" => "bot#stop_streaming_response"
|
||||||
|
|
||||||
get "discover" => "bot#discover"
|
get "discover" => "bot#discover"
|
||||||
|
post "discover/continue-convo" => "bot#discover_continue_convo"
|
||||||
end
|
end
|
||||||
|
|
||||||
scope module: :ai_bot, path: "/ai-bot/shared-ai-conversations" do
|
scope module: :ai_bot, path: "/ai-bot/shared-ai-conversations" do
|
||||||
|
|
|
@ -168,4 +168,71 @@ RSpec.describe DiscourseAi::AiBot::BotController do
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
Loading…
Reference in New Issue