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

Commit 8a86c46

Browse files
committed
FEATURE: Display bot in feature list
- allows features to have multiple llms and multiple personas - sorts module list - adds Bot as a first class module - fixes issue where search module was always configured - some tests
1 parent 2fe99a0 commit 8a86c46

File tree

10 files changed

+321
-132
lines changed

10 files changed

+321
-132
lines changed

admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { action } from "@ember/object";
12
import { ajax } from "discourse/lib/ajax";
23
import DiscourseRoute from "discourse/routes/discourse";
34
import SiteSetting from "admin/models/site-setting";
@@ -24,4 +25,11 @@ export default class AdminPluginsShowDiscourseAiFeaturesEdit extends DiscourseRo
2425

2526
return currentFeature;
2627
}
28+
29+
@action
30+
willTransition() {
31+
// site settings may amend if a feature is enabled or disabled, so refresh the model
32+
// even on back button
33+
this.router.refresh("adminPlugins.show.discourse-ai-features");
34+
}
2735
}

app/controllers/discourse_ai/admin/ai_features_controller.rb

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,9 @@ def serialize_module(a_module)
3737
def serialize_feature(feature)
3838
{
3939
name: feature.name,
40-
persona: serialize_persona(persona_id_obj_hash[feature.persona_id]),
41-
llm_model: {
42-
id: feature.llm_model&.id,
43-
name: feature.llm_model&.name,
44-
},
40+
personas: feature.persona_ids.map { |id| serialize_persona(persona_id_obj_hash[id]) },
41+
llm_models:
42+
feature.llm_models.map { |llm_model| { id: llm_model.id, name: llm_model.name } },
4543
enabled: feature.enabled?,
4644
}
4745
end
@@ -57,9 +55,7 @@ def serialize_persona(persona)
5755
def persona_id_obj_hash
5856
@persona_id_obj_hash ||=
5957
begin
60-
setting_names = DiscourseAi::Configuration::Feature.all_persona_setting_names
61-
ids = setting_names.map { |sn| SiteSetting.public_send(sn) }
62-
58+
ids = DiscourseAi::Configuration::Feature.all.map(&:persona_ids).flatten.uniq
6359
AiPersona.where(id: ids).index_by(&:id)
6460
end
6561
end
Lines changed: 105 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,117 @@
1+
import Component from "@glimmer/component";
12
import { concat } from "@ember/helper";
23
import { gt } from "truth-helpers";
34
import DButton from "discourse/components/d-button";
45
import { i18n } from "discourse-i18n";
56

6-
const AiFeaturesList = <template>
7-
<div class="ai-features-list">
8-
{{#each @modules as |module|}}
9-
<div class="ai-module" data-module-name={{module.module_name}}>
10-
<div class="ai-module__header">
11-
<div class="ai-module__module-title">
12-
<h3>{{i18n
13-
(concat "discourse_ai.features." module.module_name ".name")
14-
}}</h3>
15-
<DButton
16-
class="edit"
17-
@label="discourse_ai.features.edit"
18-
@route="adminPlugins.show.discourse-ai-features.edit"
19-
@routeModels={{module.id}}
20-
/>
7+
export default class AiFeaturesList extends Component {
8+
get sortedModules() {
9+
return this.args.modules.sort((a, b) => {
10+
const nameA = i18n(`discourse_ai.features.${a.module_name}.name`);
11+
const nameB = i18n(`discourse_ai.features.${b.module_name}.name`);
12+
return nameA.localeCompare(nameB);
13+
});
14+
}
15+
16+
<template>
17+
<div class="ai-features-list">
18+
{{#each this.sortedModules as |module|}}
19+
<div class="ai-module" data-module-name={{module.module_name}}>
20+
<div class="ai-module__header">
21+
<div class="ai-module__module-title">
22+
<h3>{{i18n
23+
(concat "discourse_ai.features." module.module_name ".name")
24+
}}</h3>
25+
<DButton
26+
class="edit"
27+
@label="discourse_ai.features.edit"
28+
@route="adminPlugins.show.discourse-ai-features.edit"
29+
@routeModels={{module.id}}
30+
/>
31+
</div>
32+
<div>{{i18n
33+
(concat
34+
"discourse_ai.features." module.module_name ".description"
35+
)
36+
}}</div>
2137
</div>
22-
<div>{{i18n
23-
(concat
24-
"discourse_ai.features." module.module_name ".description"
25-
)
26-
}}</div>
27-
</div>
2838

29-
<div class="admin-section-landing-wrapper ai-feature-cards">
30-
{{#each module.features as |feature|}}
31-
<div
32-
class="admin-section-landing-item ai-feature-card"
33-
data-feature-name={{feature.name}}
34-
>
35-
<div class="admin-section-landing-item__content">
36-
<div class="ai-feature-card__feature-name">
37-
{{i18n
38-
(concat
39-
"discourse_ai.features."
40-
module.module_name
41-
"."
42-
feature.name
43-
)
44-
}}
45-
{{#unless feature.enabled}}
46-
<span>{{i18n "discourse_ai.features.disabled"}}</span>
47-
{{/unless}}
48-
</div>
49-
<div class="ai-feature-card__persona">
50-
<span>{{i18n "discourse_ai.features.persona"}}</span>
51-
{{#if feature.persona}}
52-
<DButton
53-
class="btn-flat btn-small ai-feature-card__persona-button"
54-
@translatedLabel={{feature.persona.name}}
55-
@route="adminPlugins.show.discourse-ai-personas.edit"
56-
@routeModels={{feature.persona.id}}
57-
/>
58-
{{else}}
59-
{{i18n "discourse_ai.features.no_persona"}}
60-
{{/if}}
61-
</div>
62-
<div class="ai-feature-card__llm">
63-
<span>{{i18n "discourse_ai.features.llm"}}</span>
64-
{{#if feature.llm_model.name}}
65-
<DButton
66-
class="btn-flat btn-small ai-feature-card__llm-button"
67-
@translatedLabel={{feature.llm_model.name}}
68-
@route="adminPlugins.show.discourse-ai-llms.edit"
69-
@routeModels={{feature.llm_model.id}}
70-
/>
71-
{{else}}
72-
{{i18n "discourse_ai.features.no_llm"}}
73-
{{/if}}
74-
</div>
75-
{{#if feature.persona}}
76-
<div class="ai-feature-card__groups">
77-
<span>{{i18n "discourse_ai.features.groups"}}</span>
78-
{{#if (gt feature.persona.allowed_groups.length 0)}}
79-
<ul class="ai-feature-card__item-groups">
80-
{{#each feature.persona.allowed_groups as |group|}}
81-
<li>{{group.name}}</li>
82-
{{/each}}
83-
</ul>
39+
<div class="admin-section-landing-wrapper ai-feature-cards">
40+
{{#each module.features as |feature|}}
41+
<div
42+
class="admin-section-landing-item ai-feature-card"
43+
data-feature-name={{feature.name}}
44+
>
45+
<div class="admin-section-landing-item__content">
46+
<div class="ai-feature-card__feature-name">
47+
{{i18n
48+
(concat
49+
"discourse_ai.features."
50+
module.module_name
51+
"."
52+
feature.name
53+
)
54+
}}
55+
{{#unless feature.enabled}}
56+
<span>{{i18n "discourse_ai.features.disabled"}}</span>
57+
{{/unless}}
58+
</div>
59+
<div class="ai-feature-card__persona">
60+
<span>{{i18n
61+
"discourse_ai.features.persona"
62+
count=feature.personas.length
63+
}}</span>
64+
{{#if feature.personas}}
65+
{{#each feature.personas as |persona|}}
66+
<DButton
67+
class="btn-flat btn-small ai-feature-card__persona-button"
68+
@translatedLabel={{persona.name}}
69+
@route="adminPlugins.show.discourse-ai-personas.edit"
70+
@routeModels={{persona.id}}
71+
/>
72+
{{/each}}
8473
{{else}}
85-
{{i18n "discourse_ai.features.no_groups"}}
74+
{{i18n "discourse_ai.features.no_persona"}}
8675
{{/if}}
8776
</div>
88-
{{/if}}
77+
<div class="ai-feature-card__llm">
78+
<span>{{i18n
79+
"discourse_ai.features.llm"
80+
count=feature.llm_models.length
81+
}}</span>
82+
{{#if feature.llm_models}}
83+
{{#each feature.llm_models as |llm_model|}}
84+
<DButton
85+
class="btn-flat btn-small ai-feature-card__llm-button"
86+
@translatedLabel={{llm_model.name}}
87+
@route="adminPlugins.show.discourse-ai-llms.edit"
88+
@routeModels={{llm_model.id}}
89+
/>
90+
{{/each}}
91+
{{else}}
92+
{{i18n "discourse_ai.features.no_llm"}}
93+
{{/if}}
94+
</div>
95+
{{#if feature.persona}}
96+
<div class="ai-feature-card__groups">
97+
<span>{{i18n "discourse_ai.features.groups"}}</span>
98+
{{#if (gt feature.persona.allowed_groups.length 0)}}
99+
<ul class="ai-feature-card__item-groups">
100+
{{#each feature.persona.allowed_groups as |group|}}
101+
<li>{{group.name}}</li>
102+
{{/each}}
103+
</ul>
104+
{{else}}
105+
{{i18n "discourse_ai.features.no_groups"}}
106+
{{/if}}
107+
</div>
108+
{{/if}}
109+
</div>
89110
</div>
90-
</div>
91-
{{/each}}
111+
{{/each}}
112+
</div>
92113
</div>
93-
</div>
94-
{{/each}}
95-
</div>
96-
</template>;
97-
98-
export default AiFeaturesList;
114+
{{/each}}
115+
</div>
116+
</template>
117+
}

assets/stylesheets/common/ai-features.scss

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,18 @@
2222
background: var(--primary-very-low);
2323
border: 1px solid var(--primary-low);
2424
padding: 0.5rem;
25-
display: block;
25+
display: flex;
26+
flex-direction: column;
2627

2728
&__llm,
2829
&__persona,
2930
&__groups {
3031
font-size: var(--font-down-1-rem);
32+
display: flex;
33+
flex-flow: row wrap;
34+
gap: 0.1em;
35+
margin-top: 0.5rem;
36+
align-items: center;
3137
}
3238

3339
&__persona {
@@ -36,7 +42,7 @@
3642

3743
&__persona-button,
3844
&__llm-button {
39-
padding-left: 0;
45+
padding-left: 0.2em;
4046
}
4147

4248
&__groups {

config/locales/client.en.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,21 @@ en:
186186
description: "These are the AI features available to visitors on your site. These can be configured to use specific personas and LLMs, and can be access controlled by groups."
187187
back: "Back"
188188
disabled: "(disabled)"
189-
persona: "Persona:"
189+
persona:
190+
one: "Persona:"
191+
other: "Personas:"
190192
groups: "Groups:"
191-
llm: "LLM:"
193+
llm:
194+
one: "LLM:"
195+
other: "LLMs:"
192196
no_llm: "No LLM selected"
193197
no_persona: "Not set"
194198
no_groups: "None"
195199
edit: "Edit"
200+
bot:
201+
bot: "Chatbot"
202+
name: "Bot"
203+
description: "A chat bot that can answer questions and assist users in private messagges, forum and in chat"
196204
nav:
197205
configured: "Configured"
198206
unconfigured: "Unconfigured"

config/settings.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,37 +350,44 @@ discourse_ai:
350350
ai_bot_enabled:
351351
default: false
352352
client: true
353-
area: "ai-features/search"
353+
area: "ai-features/bot"
354354
ai_bot_enable_chat_warning:
355355
default: false
356356
client: true
357+
area: "ai-features/bot"
357358
ai_bot_debugging_allowed_groups:
358359
type: group_list
359360
list_type: compact
360361
default: ""
361362
allow_any: false
363+
area: "ai-features/bot"
362364
ai_bot_allowed_groups:
363365
type: group_list
364366
list_type: compact
365367
default: "3|14" # 3: @staff, 14: @trust_level_4
368+
area: "ai-features/bot"
366369
ai_bot_public_sharing_allowed_groups:
367370
client: false
368371
type: group_list
369372
list_type: compact
370373
default: "1|2" # 1: admins, 2: moderators
371374
allow_any: false
372375
refresh: true
376+
area: "ai-features/bot"
373377
ai_bot_add_to_header:
374378
default: true
375379
client: true
380+
area: "ai-features/bot"
376381
ai_bot_github_access_token:
377382
default: ""
378383
secret: true
384+
area: "ai-features/bot"
379385
ai_bot_allowed_seeded_models:
380386
default: ""
381387
hidden: true
382388
type: list
383389
list_type: compact
390+
area: "ai-features/bot"
384391
ai_bot_discover_persona:
385392
default: ""
386393
type: enum

0 commit comments

Comments
 (0)