Skip to content

Commit 7dc3c30

Browse files
authored
FEATURE: correctly decorate AI bots (#1300)
AI bots come in 2 flavors 1. An LLM and LLM user, in this case we should decorate posts with persona name 2. A Persona user, in this case, in PMs we decorate with LLM name (2) is a significant improvement, cause previously when creating a conversation you could not tell which LLM you were talking to by simply looking at the post, you would have to scroll to the top of the page. * lint * translation missing
1 parent 8b90ce7 commit 7dc3c30

File tree

8 files changed

+97
-14
lines changed

8 files changed

+97
-14
lines changed

assets/javascripts/discourse/lib/ai-bot-helper.js

+26-7
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,40 @@ import ShareFullTopicModal from "../components/modal/share-full-topic-modal";
77

88
const MAX_PERSONA_USER_ID = -1200;
99

10-
let enabledChatBotIds;
10+
let enabledChatBotMap = null;
11+
12+
function ensureBotMap() {
13+
if (!enabledChatBotMap) {
14+
const currentUser = getOwnerWithFallback(this).lookup(
15+
"service:current-user"
16+
);
17+
enabledChatBotMap = {};
18+
currentUser.ai_enabled_chat_bots.forEach((bot) => {
19+
enabledChatBotMap[bot.id] = bot;
20+
});
21+
}
22+
}
1123

1224
export function isGPTBot(user) {
1325
if (!user) {
1426
return;
1527
}
1628

17-
if (!enabledChatBotIds) {
18-
const currentUser = getOwnerWithFallback(this).lookup(
19-
"service:current-user"
20-
);
21-
enabledChatBotIds = currentUser.ai_enabled_chat_bots.map((bot) => bot.id);
29+
ensureBotMap();
30+
return !!enabledChatBotMap[user.id];
31+
}
32+
33+
export function getBotType(user) {
34+
if (!user) {
35+
return;
2236
}
2337

24-
return enabledChatBotIds.includes(user.id);
38+
ensureBotMap();
39+
const bot = enabledChatBotMap[user.id];
40+
if (!bot) {
41+
return;
42+
}
43+
return bot.is_persona ? "persona" : "llm";
2544
}
2645

2746
export function isPostFromAiBot(post, currentUser) {

assets/javascripts/initializers/ai-bot-replies.js

+13-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import AiCancelStreamingButton from "../discourse/components/post-menu/ai-cancel
88
import AiDebugButton from "../discourse/components/post-menu/ai-debug-button";
99
import AiShareButton from "../discourse/components/post-menu/ai-share-button";
1010
import {
11+
getBotType,
1112
isGPTBot,
1213
showShareConversationModal,
1314
} from "../discourse/lib/ai-bot-helper";
@@ -62,13 +63,19 @@ function initializePersonaDecorator(api) {
6263

6364
function initializeWidgetPersonaDecorator(api) {
6465
api.decorateWidget(`poster-name:after`, (dec) => {
65-
if (!isGPTBot(dec.attrs.user)) {
66-
return;
66+
const botType = getBotType(dec.attrs.user);
67+
// we have 2 ways of decorating
68+
// 1. if a bot is a LLM we decorate with persona name
69+
// 2. if bot is a persona we decorate with LLM name
70+
if (botType === "llm") {
71+
return dec.widget.attach("persona-flair", {
72+
personaName: dec.model?.topic?.ai_persona_name,
73+
});
74+
} else if (botType === "persona") {
75+
return dec.widget.attach("persona-flair", {
76+
personaName: dec.model?.llm_name,
77+
});
6778
}
68-
69-
return dec.widget.attach("persona-flair", {
70-
personaName: dec.model?.topic?.ai_persona_name,
71-
});
7279
});
7380

7481
registerWidgetShim(

assets/stylesheets/modules/ai-bot/common/bot-replies.scss

-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ article.streaming nav.post-controls .actions button.cancel-streaming {
7878
.topic-body .persona-flair {
7979
order: 2;
8080
font-size: var(--font-down-1);
81-
padding-top: 3px;
8281
}
8382

8483
details.ai-quote {

config/locales/server.en.yml

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ en:
113113
ai_discord_search_mode: "Select the search mode to use for Discord search"
114114
ai_discord_search_persona: "The persona to use for Discord search."
115115
ai_discord_allowed_guilds: "Discord guilds (servers) where the bot is allowed to search"
116+
ai_enable_experimental_bot_ux: "Enable experimental bot UI that allows for a more dedicated experience"
116117

117118
reviewables:
118119
reasons:

lib/ai_bot/entry_point.rb

+13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module DiscourseAi
44
module AiBot
55
USER_AGENT = "Discourse AI Bot 1.0 (https://www.discourse.org)"
66
TOPIC_AI_BOT_PM_FIELD = "is_ai_bot_pm"
7+
POST_AI_LLM_NAME_FIELD = "ai_llm_name"
78

89
class EntryPoint
910
Bot = Struct.new(:id, :name, :llm)
@@ -65,6 +66,10 @@ def self.ai_share_error(topic, guardian)
6566
end
6667

6768
def inject_into(plugin)
69+
# Long term we need a better API here
70+
# we only want to load this custom field for bots
71+
TopicView.default_post_custom_fields << POST_AI_LLM_NAME_FIELD
72+
6873
plugin.register_topic_custom_field_type(TOPIC_AI_BOT_PM_FIELD, :string)
6974

7075
plugin.on(:topic_created) do |topic|
@@ -139,6 +144,14 @@ def inject_into(plugin)
139144
end,
140145
) { true }
141146

147+
plugin.add_to_serializer(
148+
:post,
149+
:llm_name,
150+
include_condition: -> do
151+
object.topic.private_message? && object.custom_fields[POST_AI_LLM_NAME_FIELD]
152+
end,
153+
) { object.custom_fields[POST_AI_LLM_NAME_FIELD] }
154+
142155
plugin.add_to_serializer(
143156
:current_user,
144157
:ai_enabled_personas,

lib/ai_bot/playground.rb

+3
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,9 @@ def reply_to(
458458
skip_jobs: true,
459459
post_type: post_type,
460460
skip_guardian: true,
461+
custom_fields: {
462+
DiscourseAi::AiBot::POST_AI_LLM_NAME_FIELD => bot.llm.llm_model.name,
463+
},
461464
)
462465

463466
publish_update(reply_post, { raw: reply_post.cooked })

spec/lib/modules/ai_bot/playground_spec.rb

+4
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,10 @@
783783
last_post = post.topic.posts.order(:post_number).last
784784
expect(last_post.raw).to eq("Yes I can")
785785
expect(last_post.user_id).to eq(persona.user_id)
786+
787+
expect(last_post.custom_fields[DiscourseAi::AiBot::POST_AI_LLM_NAME_FIELD]).to eq(
788+
gpt_35_turbo.name,
789+
)
786790
end
787791

788792
it "picks the correct llm for persona in PMs" do
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe "AI Bot Post Serializer" do
4+
fab!(:current_user) { Fabricate(:user) }
5+
fab!(:bot_user) { Fabricate(:user) }
6+
7+
before do
8+
SiteSetting.ai_bot_enabled = true
9+
sign_in(current_user)
10+
end
11+
12+
describe "llm_name in post serializer" do
13+
it "includes llm_name when custom field is set in a PM" do
14+
pm_topic = Fabricate(:private_message_topic, user: current_user)
15+
16+
# Create a bot post with the custom field set
17+
bot_post =
18+
Fabricate(
19+
:post,
20+
topic: pm_topic,
21+
user: bot_user,
22+
custom_fields: {
23+
DiscourseAi::AiBot::POST_AI_LLM_NAME_FIELD => "bob",
24+
},
25+
)
26+
27+
get "/t/#{pm_topic.id}.json"
28+
expect(response.status).to eq(200)
29+
30+
json = response.parsed_body
31+
bot_post_data = json["post_stream"]["posts"].find { |p| p["id"] == bot_post.id }
32+
33+
expect(bot_post_data).to have_key("llm_name")
34+
expect(bot_post_data["llm_name"]).to eq("bob")
35+
end
36+
end
37+
end

0 commit comments

Comments
 (0)