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

Commit b7b9179

Browse files
authored
FEATURE: Allow for persona & llm selection in bot conversations page (#1276)
1 parent 18dda31 commit b7b9179

File tree

13 files changed

+437
-296
lines changed

13 files changed

+437
-296
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import Component from "@glimmer/component";
2+
import { tracked } from "@glimmer/tracking";
3+
import { hash } from "@ember/helper";
4+
import { next } from "@ember/runloop";
5+
import { service } from "@ember/service";
6+
import { i18n } from "discourse-i18n";
7+
import DropdownSelectBox from "select-kit/components/dropdown-select-box";
8+
9+
const PERSONA_SELECTOR_KEY = "ai_persona_selector_id";
10+
const LLM_SELECTOR_KEY = "ai_llm_selector_id";
11+
12+
export default class AiPersonaLlmSelector extends Component {
13+
@service currentUser;
14+
@service keyValueStore;
15+
16+
@tracked llm;
17+
@tracked allowLLMSelector = true;
18+
19+
constructor() {
20+
super(...arguments);
21+
22+
if (this.botOptions?.length) {
23+
this.#loadStoredPersona();
24+
this.#loadStoredLlm();
25+
26+
next(() => {
27+
this.resetTargetRecipients();
28+
});
29+
}
30+
}
31+
32+
get composer() {
33+
return this.args?.outletArgs?.model;
34+
}
35+
36+
get hasLlmSelector() {
37+
return this.currentUser.ai_enabled_chat_bots.any((bot) => !bot.is_persona);
38+
}
39+
40+
get botOptions() {
41+
if (!this.currentUser.ai_enabled_personas) {
42+
return;
43+
}
44+
45+
let enabledPersonas = this.currentUser.ai_enabled_personas;
46+
47+
if (!this.hasLlmSelector) {
48+
enabledPersonas = enabledPersonas.filter((persona) => persona.username);
49+
}
50+
51+
return enabledPersonas.map((persona) => {
52+
return {
53+
id: persona.id,
54+
name: persona.name,
55+
description: persona.description,
56+
};
57+
});
58+
}
59+
60+
get filterable() {
61+
return this.botOptions.length > 8;
62+
}
63+
64+
get value() {
65+
return this._value;
66+
}
67+
68+
set value(newValue) {
69+
this._value = newValue;
70+
this.keyValueStore.setItem(PERSONA_SELECTOR_KEY, newValue);
71+
this.args.setPersonaId(newValue);
72+
this.setAllowLLMSelector();
73+
this.resetTargetRecipients();
74+
}
75+
76+
setAllowLLMSelector() {
77+
if (!this.hasLlmSelector) {
78+
this.allowLLMSelector = false;
79+
return;
80+
}
81+
82+
const persona = this.currentUser.ai_enabled_personas.find(
83+
(innerPersona) => innerPersona.id === this._value
84+
);
85+
86+
this.allowLLMSelector = !persona?.force_default_llm;
87+
}
88+
89+
get currentLlm() {
90+
return this.llm;
91+
}
92+
93+
set currentLlm(newValue) {
94+
this.llm = newValue;
95+
this.keyValueStore.setItem(LLM_SELECTOR_KEY, newValue);
96+
97+
this.resetTargetRecipients();
98+
}
99+
100+
resetTargetRecipients() {
101+
if (this.allowLLMSelector) {
102+
const botUsername = this.currentUser.ai_enabled_chat_bots.find(
103+
(bot) => bot.id === this.llm
104+
).username;
105+
this.args.setTargetRecipient(botUsername);
106+
} else {
107+
const persona = this.currentUser.ai_enabled_personas.find(
108+
(innerPersona) => innerPersona.id === this._value
109+
);
110+
this.args.setTargetRecipient(persona.username || "");
111+
}
112+
}
113+
114+
get llmOptions() {
115+
const availableBots = this.currentUser.ai_enabled_chat_bots
116+
.filter((bot) => !bot.is_persona)
117+
.filter(Boolean);
118+
119+
return availableBots
120+
.map((bot) => {
121+
return {
122+
id: bot.id,
123+
name: bot.display_name,
124+
};
125+
})
126+
.sort((a, b) => a.name.localeCompare(b.name));
127+
}
128+
129+
get showLLMSelector() {
130+
return this.allowLLMSelector && this.llmOptions.length > 1;
131+
}
132+
133+
#loadStoredPersona() {
134+
let personaId = this.keyValueStore.getItem(PERSONA_SELECTOR_KEY);
135+
136+
this._value = this.botOptions[0].id;
137+
if (personaId) {
138+
personaId = parseInt(personaId, 10);
139+
if (this.botOptions.any((bot) => bot.id === personaId)) {
140+
this._value = personaId;
141+
}
142+
}
143+
144+
this.args.setPersonaId(this._value);
145+
}
146+
147+
#loadStoredLlm() {
148+
this.setAllowLLMSelector();
149+
150+
if (this.hasLlmSelector) {
151+
let llm = this.keyValueStore.getItem(LLM_SELECTOR_KEY);
152+
153+
const llmOption =
154+
this.llmOptions.find((innerLlmOption) => innerLlmOption.id === llm) ||
155+
this.llmOptions[0];
156+
157+
if (llmOption) {
158+
llm = llmOption.id;
159+
} else {
160+
llm = "";
161+
}
162+
163+
if (llm) {
164+
next(() => {
165+
this.currentLlm = llm;
166+
});
167+
}
168+
}
169+
}
170+
171+
<template>
172+
<div class="persona-llm-selector">
173+
<div class="persona-llm-selector__selection-wrapper gpt-persona">
174+
{{#if @showLabels}}
175+
<label>{{i18n "discourse_ai.ai_bot.persona"}}</label>
176+
{{/if}}
177+
<DropdownSelectBox
178+
class="persona-llm-selector__persona-dropdown"
179+
@value={{this.value}}
180+
@content={{this.botOptions}}
181+
@options={{hash
182+
icon=(if @showLabels "angle-down" "robot")
183+
filterable=this.filterable
184+
}}
185+
/>
186+
</div>
187+
{{#if this.showLLMSelector}}
188+
<div class="persona-llm-selector__selection-wrapper llm-selector">
189+
{{#if @showLabels}}
190+
<label>{{i18n "discourse_ai.ai_bot.llm"}}</label>
191+
{{/if}}
192+
<DropdownSelectBox
193+
class="persona-llm-selector__llm-dropdown"
194+
@value={{this.currentLlm}}
195+
@content={{this.llmOptions}}
196+
@options={{hash icon=(if @showLabels "angle-down" "globe")}}
197+
/>
198+
</div>
199+
{{/if}}
200+
</div>
201+
</template>
202+
}

0 commit comments

Comments
 (0)