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
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import DiscourseRoute from "discourse/routes/discourse";
import SiteSetting from "admin/models/site-setting";
Expand All @@ -24,4 +25,11 @@ export default class AdminPluginsShowDiscourseAiFeaturesEdit extends DiscourseRo

return currentFeature;
}

@action
willTransition() {
// site settings may amend if a feature is enabled or disabled, so refresh the model
// even on back button
this.router.refresh("adminPlugins.show.discourse-ai-features");
}
}
12 changes: 4 additions & 8 deletions app/controllers/discourse_ai/admin/ai_features_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,9 @@ def serialize_module(a_module)
def serialize_feature(feature)
{
name: feature.name,
persona: serialize_persona(persona_id_obj_hash[feature.persona_id]),
llm_model: {
id: feature.llm_model&.id,
name: feature.llm_model&.name,
},
personas: feature.persona_ids.map { |id| serialize_persona(persona_id_obj_hash[id]) },
llm_models:
feature.llm_models.map { |llm_model| { id: llm_model.id, name: llm_model.name } },
enabled: feature.enabled?,
}
end
Expand All @@ -57,9 +55,7 @@ def serialize_persona(persona)
def persona_id_obj_hash
@persona_id_obj_hash ||=
begin
setting_names = DiscourseAi::Configuration::Feature.all_persona_setting_names
ids = setting_names.map { |sn| SiteSetting.public_send(sn) }

ids = DiscourseAi::Configuration::Feature.all.map(&:persona_ids).flatten.uniq
AiPersona.where(id: ids).index_by(&:id)
end
end
Expand Down
217 changes: 130 additions & 87 deletions assets/javascripts/discourse/components/ai-features-list.gjs
Original file line number Diff line number Diff line change
@@ -1,98 +1,141 @@
import Component from "@glimmer/component";
import { concat } from "@ember/helper";
import { gt } from "truth-helpers";
import { action } from "@ember/object";
import DButton from "discourse/components/d-button";
import { i18n } from "discourse-i18n";

const AiFeaturesList = <template>
<div class="ai-features-list">
{{#each @modules as |module|}}
<div class="ai-module" data-module-name={{module.module_name}}>
<div class="ai-module__header">
<div class="ai-module__module-title">
<h3>{{i18n
(concat "discourse_ai.features." module.module_name ".name")
}}</h3>
<DButton
class="edit"
@label="discourse_ai.features.edit"
@route="adminPlugins.show.discourse-ai-features.edit"
@routeModels={{module.id}}
/>
export default class AiFeaturesList extends Component {
get sortedModules() {
return this.args.modules.sort((a, b) => {
const nameA = i18n(`discourse_ai.features.${a.module_name}.name`);
const nameB = i18n(`discourse_ai.features.${b.module_name}.name`);
return nameA.localeCompare(nameB);
});
}

@action
hasGroups(feature) {
return this.groupList(feature).length > 0;
}

@action
groupList(feature) {
const groups = [];
const groupIds = new Set();
if (feature.personas) {
feature.personas.forEach((persona) => {
if (persona.allowed_groups) {
persona.allowed_groups.forEach((group) => {
if (!groupIds.has(group.id)) {
groupIds.add(group.id);
groups.push(group);
}
});
}
});
}
return groups;
}

<template>
<div class="ai-features-list">
{{#each this.sortedModules as |module|}}
<div class="ai-module" data-module-name={{module.module_name}}>
<div class="ai-module__header">
<div class="ai-module__module-title">
<h3>{{i18n
(concat "discourse_ai.features." module.module_name ".name")
}}</h3>
<DButton
class="edit"
@label="discourse_ai.features.edit"
@route="adminPlugins.show.discourse-ai-features.edit"
@routeModels={{module.id}}
/>
</div>
<div>{{i18n
(concat
"discourse_ai.features." module.module_name ".description"
)
}}</div>
</div>
<div>{{i18n
(concat
"discourse_ai.features." module.module_name ".description"
)
}}</div>
</div>

<div class="admin-section-landing-wrapper ai-feature-cards">
{{#each module.features as |feature|}}
<div
class="admin-section-landing-item ai-feature-card"
data-feature-name={{feature.name}}
>
<div class="admin-section-landing-item__content">
<div class="ai-feature-card__feature-name">
{{i18n
(concat
"discourse_ai.features."
module.module_name
"."
feature.name
)
}}
{{#unless feature.enabled}}
<span>{{i18n "discourse_ai.features.disabled"}}</span>
{{/unless}}
</div>
<div class="ai-feature-card__persona">
<span>{{i18n "discourse_ai.features.persona"}}</span>
{{#if feature.persona}}
<DButton
class="btn-flat btn-small ai-feature-card__persona-button"
@translatedLabel={{feature.persona.name}}
@route="adminPlugins.show.discourse-ai-personas.edit"
@routeModels={{feature.persona.id}}
/>
{{else}}
{{i18n "discourse_ai.features.no_persona"}}
{{/if}}
</div>
<div class="ai-feature-card__llm">
<span>{{i18n "discourse_ai.features.llm"}}</span>
{{#if feature.llm_model.name}}
<DButton
class="btn-flat btn-small ai-feature-card__llm-button"
@translatedLabel={{feature.llm_model.name}}
@route="adminPlugins.show.discourse-ai-llms.edit"
@routeModels={{feature.llm_model.id}}
/>
{{else}}
{{i18n "discourse_ai.features.no_llm"}}
{{/if}}
</div>
{{#if feature.persona}}
<div class="ai-feature-card__groups">
<span>{{i18n "discourse_ai.features.groups"}}</span>
{{#if (gt feature.persona.allowed_groups.length 0)}}
<ul class="ai-feature-card__item-groups">
{{#each feature.persona.allowed_groups as |group|}}
<li>{{group.name}}</li>
{{/each}}
</ul>
<div class="admin-section-landing-wrapper ai-feature-cards">
{{#each module.features as |feature|}}
<div
class="admin-section-landing-item ai-feature-card"
data-feature-name={{feature.name}}
>
<div class="admin-section-landing-item__content">
<div class="ai-feature-card__feature-name">
{{i18n
(concat
"discourse_ai.features."
module.module_name
"."
feature.name
)
}}
{{#unless feature.enabled}}
<span>{{i18n "discourse_ai.features.disabled"}}</span>
{{/unless}}
</div>
<div class="ai-feature-card__persona">
<span>{{i18n
"discourse_ai.features.persona"
count=feature.personas.length
}}</span>
{{#if feature.personas}}
{{#each feature.personas as |persona|}}
<DButton
class="btn-flat btn-small ai-feature-card__persona-button"
@translatedLabel={{persona.name}}
@route="adminPlugins.show.discourse-ai-personas.edit"
@routeModels={{persona.id}}
/>
{{/each}}
{{else}}
{{i18n "discourse_ai.features.no_groups"}}
{{i18n "discourse_ai.features.no_persona"}}
{{/if}}
</div>
{{/if}}
<div class="ai-feature-card__llm">
<span>{{i18n
"discourse_ai.features.llm"
count=feature.llm_models.length
}}</span>
{{#if feature.llm_models}}
{{#each feature.llm_models as |llm_model|}}
<DButton
class="btn-flat btn-small ai-feature-card__llm-button"
@translatedLabel={{llm_model.name}}
@route="adminPlugins.show.discourse-ai-llms.edit"
@routeModels={{llm_model.id}}
/>
{{/each}}
{{else}}
{{i18n "discourse_ai.features.no_llm"}}
{{/if}}
</div>
{{#if feature.personas}}
<div class="ai-feature-card__groups">
<span>{{i18n "discourse_ai.features.groups"}}</span>
{{#if (this.hasGroups feature)}}
<ul class="ai-feature-card__item-groups">
{{#each (this.groupList feature) as |group|}}
<li>{{group.name}}</li>
{{/each}}
</ul>
{{else}}
{{i18n "discourse_ai.features.no_groups"}}
{{/if}}
</div>
{{/if}}
</div>
</div>
</div>
{{/each}}
{{/each}}
</div>
</div>
</div>
{{/each}}
</div>
</template>;

export default AiFeaturesList;
{{/each}}
</div>
</template>
}
10 changes: 8 additions & 2 deletions assets/stylesheets/common/ai-features.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@
background: var(--primary-very-low);
border: 1px solid var(--primary-low);
padding: 0.5rem;
display: block;
display: flex;
flex-direction: column;

&__llm,
&__persona,
&__groups {
font-size: var(--font-down-1-rem);
display: flex;
flex-flow: row wrap;
gap: 0.1em;
margin-top: 0.5rem;
align-items: center;
}

&__persona {
Expand All @@ -36,7 +42,7 @@

&__persona-button,
&__llm-button {
padding-left: 0;
padding-left: 0.2em;
}

&__groups {
Expand Down
12 changes: 10 additions & 2 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,21 @@ en:
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."
back: "Back"
disabled: "(disabled)"
persona: "Persona:"
persona:
one: "Persona:"
other: "Personas:"
groups: "Groups:"
llm: "LLM:"
llm:
one: "LLM:"
other: "LLMs:"
no_llm: "No LLM selected"
no_persona: "Not set"
no_groups: "None"
edit: "Edit"
bot:
bot: "Chatbot"
name: "Bot"
description: "A chat bot that can answer questions and assist users in private messagges, forum and in chat"
nav:
configured: "Configured"
unconfigured: "Unconfigured"
Expand Down
9 changes: 8 additions & 1 deletion config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -350,37 +350,44 @@ discourse_ai:
ai_bot_enabled:
default: false
client: true
area: "ai-features/search"
area: "ai-features/bot"
ai_bot_enable_chat_warning:
default: false
client: true
area: "ai-features/bot"
ai_bot_debugging_allowed_groups:
type: group_list
list_type: compact
default: ""
allow_any: false
area: "ai-features/bot"
ai_bot_allowed_groups:
type: group_list
list_type: compact
default: "3|14" # 3: @staff, 14: @trust_level_4
area: "ai-features/bot"
ai_bot_public_sharing_allowed_groups:
client: false
type: group_list
list_type: compact
default: "1|2" # 1: admins, 2: moderators
allow_any: false
refresh: true
area: "ai-features/bot"
ai_bot_add_to_header:
default: true
client: true
area: "ai-features/bot"
ai_bot_github_access_token:
default: ""
secret: true
area: "ai-features/bot"
ai_bot_allowed_seeded_models:
default: ""
hidden: true
type: list
list_type: compact
area: "ai-features/bot"
ai_bot_discover_persona:
default: ""
type: enum
Expand Down
Loading
Loading