FEATURE: Display bot in feature list (#1466)

- 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
This commit is contained in:
Sam 2025-06-27 12:35:41 +10:00 committed by GitHub
parent a40e2d3156
commit 73768ce920
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 420 additions and 135 deletions

View File

@ -1,3 +1,4 @@
import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
import SiteSetting from "admin/models/site-setting"; import SiteSetting from "admin/models/site-setting";
@ -24,4 +25,11 @@ export default class AdminPluginsShowDiscourseAiFeaturesEdit extends DiscourseRo
return currentFeature; 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");
}
} }

View File

@ -37,11 +37,11 @@ module DiscourseAi
def serialize_feature(feature) def serialize_feature(feature)
{ {
name: feature.name, name: feature.name,
persona: serialize_persona(persona_id_obj_hash[feature.persona_id]), personas: feature.persona_ids.map { |id| serialize_persona(persona_id_obj_hash[id]) },
llm_model: { llm_models:
id: feature.llm_model&.id, feature.llm_models.map do |llm_model|
name: feature.llm_model&.name, { id: llm_model.id, name: llm_model.display_name }
}, end,
enabled: feature.enabled?, enabled: feature.enabled?,
} }
end end
@ -57,9 +57,7 @@ module DiscourseAi
def persona_id_obj_hash def persona_id_obj_hash
@persona_id_obj_hash ||= @persona_id_obj_hash ||=
begin begin
setting_names = DiscourseAi::Configuration::Feature.all_persona_setting_names ids = DiscourseAi::Configuration::Feature.all.map(&:persona_ids).flatten.uniq
ids = setting_names.map { |sn| SiteSetting.public_send(sn) }
AiPersona.where(id: ids).index_by(&:id) AiPersona.where(id: ids).index_by(&:id)
end end
end end

View File

@ -1,98 +1,206 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { concat } from "@ember/helper"; import { concat } from "@ember/helper";
import { gt } from "truth-helpers"; import { action } from "@ember/object";
import DButton from "discourse/components/d-button"; import DButton from "discourse/components/d-button";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
const AiFeaturesList = <template> class ExpandableList extends Component {
<div class="ai-features-list"> @tracked isExpanded = false;
{{#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}}
/>
</div>
<div>{{i18n
(concat
"discourse_ai.features." module.module_name ".description"
)
}}</div>
</div>
<div class="admin-section-landing-wrapper ai-feature-cards"> get maxItemsToShow() {
{{#each module.features as |feature|}} return this.args.maxItemsToShow ?? 5;
<div }
class="admin-section-landing-item ai-feature-card"
data-feature-name={{feature.name}} get hasMore() {
> return this.args.items?.length > this.maxItemsToShow;
<div class="admin-section-landing-item__content"> }
<div class="ai-feature-card__feature-name">
{{i18n get visibleItems() {
(concat if (!this.args.items) {
"discourse_ai.features." return [];
module.module_name }
"." return this.isExpanded
feature.name ? this.args.items
) : this.args.items.slice(0, this.maxItemsToShow);
}} }
{{#unless feature.enabled}}
<span>{{i18n "discourse_ai.features.disabled"}}</span> get remainingCount() {
{{/unless}} return this.args.items?.length - this.maxItemsToShow;
</div> }
<div class="ai-feature-card__persona">
<span>{{i18n "discourse_ai.features.persona"}}</span> get expandToggleLabel() {
{{#if feature.persona}} if (this.isExpanded) {
<DButton return i18n("discourse_ai.features.collapse_list");
class="btn-flat btn-small ai-feature-card__persona-button" } else {
@translatedLabel={{feature.persona.name}} return i18n("discourse_ai.features.expand_list", {
@route="adminPlugins.show.discourse-ai-personas.edit" count: this.remainingCount,
@routeModels={{feature.persona.id}} });
/> }
{{else}} }
{{i18n "discourse_ai.features.no_persona"}}
{{/if}} @action
</div> toggleExpanded() {
<div class="ai-feature-card__llm"> this.isExpanded = !this.isExpanded;
<span>{{i18n "discourse_ai.features.llm"}}</span> }
{{#if feature.llm_model.name}}
<DButton <template>
class="btn-flat btn-small ai-feature-card__llm-button" {{#each this.visibleItems as |item index|}}
@translatedLabel={{feature.llm_model.name}} {{yield item index}}
@route="adminPlugins.show.discourse-ai-llms.edit" {{/each}}
@routeModels={{feature.llm_model.id}}
/> {{#if this.hasMore}}
{{else}} <DButton
{{i18n "discourse_ai.features.no_llm"}} class="btn-flat btn-small ai-expanded-list__toggle-button"
{{/if}} @translatedLabel={{this.expandToggleLabel}}
</div> @action={{this.toggleExpanded}}
{{#if feature.persona}} />
<div class="ai-feature-card__groups"> {{/if}}
<span>{{i18n "discourse_ai.features.groups"}}</span> </template>
{{#if (gt feature.persona.allowed_groups.length 0)}} }
<ul class="ai-feature-card__item-groups">
{{#each feature.persona.allowed_groups as |group|}} export default class AiFeaturesList extends Component {
<li>{{group.name}}</li> get sortedModules() {
{{/each}} return this.args.modules.sort((a, b) => {
</ul> 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 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}}
<ExpandableList
@items={{feature.personas}}
@maxItemsToShow={{5}}
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}}
/>
</ExpandableList>
{{else}} {{else}}
{{i18n "discourse_ai.features.no_groups"}} {{i18n "discourse_ai.features.no_persona"}}
{{/if}} {{/if}}
</div> </div>
{{/if}} <div class="ai-feature-card__llm">
{{#if feature.llm_models}}
<span>{{i18n
"discourse_ai.features.llm"
count=feature.llm_models.length
}}</span>
{{/if}}
{{#if feature.llm_models}}
<ExpandableList
@items={{feature.llm_models}}
@maxItemsToShow={{5}}
as |llm|
>
<DButton
class="btn-flat btn-small ai-feature-card__llm-button"
@translatedLabel={{llm.name}}
@route="adminPlugins.show.discourse-ai-llms.edit"
@routeModels={{llm.id}}
/>
</ExpandableList>
{{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>
</div> {{/each}}
{{/each}} </div>
</div> </div>
</div> {{/each}}
{{/each}} </div>
</div> </template>
</template>; }
export default AiFeaturesList;

View File

@ -22,12 +22,18 @@
background: var(--primary-very-low); background: var(--primary-very-low);
border: 1px solid var(--primary-low); border: 1px solid var(--primary-low);
padding: 0.5rem; padding: 0.5rem;
display: block; display: flex;
flex-direction: column;
&__llm, &__llm,
&__persona, &__persona,
&__groups { &__groups {
font-size: var(--font-down-1-rem); font-size: var(--font-down-1-rem);
display: flex;
flex-flow: row wrap;
gap: 0.1em;
margin-top: 0.5rem;
align-items: center;
} }
&__persona { &__persona {
@ -36,7 +42,7 @@
&__persona-button, &__persona-button,
&__llm-button { &__llm-button {
padding-left: 0; padding-left: 0.2em;
} }
&__groups { &__groups {

View File

@ -186,13 +186,25 @@ 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." 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" back: "Back"
disabled: "(disabled)" disabled: "(disabled)"
persona: "Persona:" persona:
one: "Persona:"
other: "Personas:"
groups: "Groups:" groups: "Groups:"
llm: "LLM:" llm:
one: "LLM:"
other: "LLMs:"
no_llm: "No LLM selected" no_llm: "No LLM selected"
no_persona: "Not set" no_persona: "Not set"
no_groups: "None" no_groups: "None"
edit: "Edit" edit: "Edit"
expand_list:
one: "(%{count} more)"
other: "(%{count} more)"
collapse_list: "(show less)"
bot:
bot: "Chatbot"
name: "Bot"
description: "A chat bot that can answer questions and assist users in private messagges, forum and in chat"
nav: nav:
configured: "Configured" configured: "Configured"
unconfigured: "Unconfigured" unconfigured: "Unconfigured"

View File

@ -350,19 +350,22 @@ discourse_ai:
ai_bot_enabled: ai_bot_enabled:
default: false default: false
client: true client: true
area: "ai-features/search" area: "ai-features/bot"
ai_bot_enable_chat_warning: ai_bot_enable_chat_warning:
default: false default: false
client: true client: true
area: "ai-features/bot"
ai_bot_debugging_allowed_groups: ai_bot_debugging_allowed_groups:
type: group_list type: group_list
list_type: compact list_type: compact
default: "" default: ""
allow_any: false allow_any: false
area: "ai-features/bot"
ai_bot_allowed_groups: ai_bot_allowed_groups:
type: group_list type: group_list
list_type: compact list_type: compact
default: "3|14" # 3: @staff, 14: @trust_level_4 default: "3|14" # 3: @staff, 14: @trust_level_4
area: "ai-features/bot"
ai_bot_public_sharing_allowed_groups: ai_bot_public_sharing_allowed_groups:
client: false client: false
type: group_list type: group_list
@ -370,17 +373,21 @@ discourse_ai:
default: "1|2" # 1: admins, 2: moderators default: "1|2" # 1: admins, 2: moderators
allow_any: false allow_any: false
refresh: true refresh: true
area: "ai-features/bot"
ai_bot_add_to_header: ai_bot_add_to_header:
default: true default: true
client: true client: true
area: "ai-features/bot"
ai_bot_github_access_token: ai_bot_github_access_token:
default: "" default: ""
secret: true secret: true
area: "ai-features/bot"
ai_bot_allowed_seeded_models: ai_bot_allowed_seeded_models:
default: "" default: ""
hidden: true hidden: true
type: list type: list
list_type: compact list_type: compact
area: "ai-features/bot"
ai_bot_discover_persona: ai_bot_discover_persona:
default: "" default: ""
type: enum type: enum

View File

@ -118,6 +118,32 @@ module DiscourseAi
] ]
end end
def bot_features
feature_cache[:bot] ||= [
new(
"bot",
nil,
DiscourseAi::Configuration::Module::BOT_ID,
DiscourseAi::Configuration::Module::BOT,
persona_ids_lookup: -> { lookup_bot_persona_ids },
llm_models_lookup: -> { lookup_bot_llms },
),
]
end
def lookup_bot_persona_ids
AiPersona
.where(enabled: true)
.where(
"allow_chat_channel_mentions OR allow_chat_direct_messages OR allow_topic_mentions OR allow_personal_messages",
)
.pluck(:id)
end
def lookup_bot_llms
LlmModel.where(enabled_chat_bot: true).to_a
end
def translation_features def translation_features
feature_cache[:translation] ||= [ feature_cache[:translation] ||= [
new( new(
@ -155,46 +181,62 @@ module DiscourseAi
inference_features, inference_features,
ai_helper_features, ai_helper_features,
translation_features, translation_features,
bot_features,
].flatten ].flatten
end end
def all_persona_setting_names
all.map(&:persona_setting)
end
def find_features_using(persona_id:) def find_features_using(persona_id:)
all.select { |feature| feature.persona_id == persona_id } all.select { |feature| feature.persona_ids.include?(persona_id) }
end end
end end
def initialize(name, persona_setting, module_id, module_name, enabled_by_setting: "") def initialize(
name,
persona_setting,
module_id,
module_name,
enabled_by_setting: "",
persona_ids_lookup: nil,
llm_models_lookup: nil
)
@name = name @name = name
@persona_setting = persona_setting @persona_setting = persona_setting
@module_id = module_id @module_id = module_id
@module_name = module_name @module_name = module_name
@enabled_by_setting = enabled_by_setting @enabled_by_setting = enabled_by_setting
@persona_ids_lookup = persona_ids_lookup
@llm_models_lookup = llm_models_lookup
end end
def llm_model def llm_models
persona = AiPersona.find_by(id: persona_id) return @llm_models_lookup.call if @llm_models_lookup
return if persona.blank? return if !persona_ids
persona_klass = persona.class_instance llm_models = []
personas = AiPersona.where(id: persona_ids)
personas.each do |persona|
next if persona.blank?
llm_model = persona_klass = persona.class_instance
case module_name
when DiscourseAi::Configuration::Module::SUMMARIZATION llm_model =
DiscourseAi::Summarization.find_summarization_model(persona_klass) case module_name
when DiscourseAi::Configuration::Module::AI_HELPER when DiscourseAi::Configuration::Module::SUMMARIZATION
DiscourseAi::AiHelper::Assistant.find_ai_helper_model(name, persona_klass) DiscourseAi::Summarization.find_summarization_model(persona_klass)
when DiscourseAi::Configuration::Module::TRANSLATION when DiscourseAi::Configuration::Module::AI_HELPER
DiscourseAi::Translation::BaseTranslator.preferred_llm_model(persona_klass) DiscourseAi::AiHelper::Assistant.find_ai_helper_model(name, persona_klass)
when DiscourseAi::Configuration::Module::TRANSLATION
DiscourseAi::Translation::BaseTranslator.preferred_llm_model(persona_klass)
end
if llm_model.blank? && persona.default_llm_id
llm_model = LlmModel.find_by(id: persona.default_llm_id)
end end
if llm_model.blank? && persona.default_llm_id llm_models << llm_model if llm_model
llm_model = LlmModel.find_by(id: persona.default_llm_id)
end end
llm_model
llm_models.compact.uniq
end end
attr_reader :name, :persona_setting, :module_id, :module_name attr_reader :name, :persona_setting, :module_id, :module_name
@ -203,8 +245,17 @@ module DiscourseAi
@enabled_by_setting.blank? || SiteSetting.get(@enabled_by_setting) @enabled_by_setting.blank? || SiteSetting.get(@enabled_by_setting)
end end
def persona_id def persona_ids
SiteSetting.get(persona_setting).to_i if @persona_ids_lookup
@persona_ids_lookup.call
else
id = SiteSetting.get(persona_setting).to_i
if id != 0
[id]
else
[]
end
end
end end
end end
end end

View File

@ -9,8 +9,9 @@ module DiscourseAi
INFERENCE = "inference" INFERENCE = "inference"
AI_HELPER = "ai_helper" AI_HELPER = "ai_helper"
TRANSLATION = "translation" TRANSLATION = "translation"
BOT = "bot"
NAMES = [SUMMARIZATION, SEARCH, DISCORD, INFERENCE, AI_HELPER, TRANSLATION] NAMES = [SUMMARIZATION, SEARCH, DISCORD, INFERENCE, AI_HELPER, TRANSLATION, BOT].freeze
SUMMARIZATION_ID = 1 SUMMARIZATION_ID = 1
SEARCH_ID = 2 SEARCH_ID = 2
@ -18,6 +19,7 @@ module DiscourseAi
INFERENCE_ID = 4 INFERENCE_ID = 4
AI_HELPER_ID = 5 AI_HELPER_ID = 5
TRANSLATION_ID = 6 TRANSLATION_ID = 6
BOT_ID = 7
class << self class << self
def all def all
@ -33,6 +35,7 @@ module DiscourseAi
SEARCH, SEARCH,
"ai_bot_enabled", "ai_bot_enabled",
features: DiscourseAi::Configuration::Feature.search_features, features: DiscourseAi::Configuration::Feature.search_features,
extra_check: -> { SiteSetting.ai_bot_discover_persona.present? },
), ),
new( new(
DISCORD_ID, DISCORD_ID,
@ -58,6 +61,12 @@ module DiscourseAi
"ai_translation_enabled", "ai_translation_enabled",
features: DiscourseAi::Configuration::Feature.translation_features, features: DiscourseAi::Configuration::Feature.translation_features,
), ),
new(
BOT_ID,
BOT,
"ai_bot_enabled",
features: DiscourseAi::Configuration::Feature.bot_features,
),
] ]
end end
@ -66,17 +75,24 @@ module DiscourseAi
end end
end end
def initialize(id, name, enabled_by_setting, features: []) def initialize(id, name, enabled_by_setting, features: [], extra_check: nil)
@id = id @id = id
@name = name @name = name
@enabled_by_setting = enabled_by_setting @enabled_by_setting = enabled_by_setting
@features = features @features = features
@extra_check = extra_check
end end
attr_reader :id, :name, :enabled_by_setting, :features attr_reader :id, :name, :enabled_by_setting, :features
def enabled? def enabled?
SiteSetting.get(enabled_by_setting) enabled_setting = SiteSetting.get(enabled_by_setting)
if @extra_check
enabled_setting && @extra_check.call
else
enabled_setting
end
end end
end end
end end

View File

@ -22,7 +22,7 @@ RSpec.describe DiscourseAi::Configuration::Feature do
) )
SiteSetting.ai_summarization_persona = 999_999 SiteSetting.ai_summarization_persona = 999_999
expect(ai_feature.llm_model).to be_nil expect(ai_feature.llm_models).to eq([])
end end
end end
@ -39,7 +39,7 @@ RSpec.describe DiscourseAi::Configuration::Feature do
it "returns the configured llm model" do it "returns the configured llm model" do
SiteSetting.ai_summarization_persona = ai_persona.id SiteSetting.ai_summarization_persona = ai_persona.id
allow_configuring_setting { SiteSetting.ai_summarization_model = "custom:#{llm_model.id}" } allow_configuring_setting { SiteSetting.ai_summarization_model = "custom:#{llm_model.id}" }
expect(ai_feature.llm_model).to eq(llm_model) expect(ai_feature.llm_models).to eq([llm_model])
end end
end end
@ -57,7 +57,7 @@ RSpec.describe DiscourseAi::Configuration::Feature do
SiteSetting.ai_helper_proofreader_persona = ai_persona.id SiteSetting.ai_helper_proofreader_persona = ai_persona.id
SiteSetting.ai_helper_model = "" SiteSetting.ai_helper_model = ""
expect(ai_feature.llm_model).to eq(llm_model) expect(ai_feature.llm_models).to eq([llm_model])
end end
end end
@ -80,7 +80,7 @@ RSpec.describe DiscourseAi::Configuration::Feature do
SiteSetting.ai_translation_model = "custom:#{translation_model.id}" SiteSetting.ai_translation_model = "custom:#{translation_model.id}"
end end
expect(ai_feature.llm_model).to eq(translation_model) expect(ai_feature.llm_models).to eq([translation_model])
end end
end end
end end
@ -116,7 +116,85 @@ RSpec.describe DiscourseAi::Configuration::Feature do
end end
end end
describe "#persona_id" do describe ".bot_features" do
fab!(:bot_llm) { Fabricate(:llm_model, enabled_chat_bot: true) }
fab!(:non_bot_llm) { Fabricate(:llm_model, enabled_chat_bot: false) }
fab!(:chat_persona) do
Fabricate(
:ai_persona,
default_llm_id: bot_llm.id,
allow_chat_channel_mentions: true,
allow_chat_direct_messages: false,
)
end
fab!(:dm_persona) do
Fabricate(
:ai_persona,
default_llm_id: bot_llm.id,
allow_chat_channel_mentions: false,
allow_chat_direct_messages: true,
)
end
fab!(:topic_persona) do
Fabricate(
:ai_persona,
default_llm_id: bot_llm.id,
allow_topic_mentions: true,
allow_personal_messages: false,
)
end
fab!(:pm_persona) do
Fabricate(:ai_persona, allow_topic_mentions: false, allow_personal_messages: true)
end
fab!(:inactive_persona) do
Fabricate(
:ai_persona,
enabled: false,
allow_chat_channel_mentions: false,
allow_chat_direct_messages: false,
allow_topic_mentions: false,
allow_personal_messages: true,
)
end
let(:bot_feature) { described_class.bot_features.first }
it "returns bot features with correct configuration" do
expect(bot_feature.name).to eq("bot")
expect(bot_feature.persona_setting).to be_nil
expect(bot_feature.module_id).to eq(DiscourseAi::Configuration::Module::BOT_ID)
expect(bot_feature.module_name).to eq(DiscourseAi::Configuration::Module::BOT)
end
it "returns only LLMs with enabled_chat_bot" do
expect(bot_feature.llm_models).to contain_exactly(bot_llm)
expect(bot_feature.llm_models).not_to include(non_bot_llm)
end
it "returns only personas with at least one bot permission enabled" do
expected_ids = [chat_persona.id, dm_persona.id, topic_persona.id, pm_persona.id]
AiPersona.where("id not in (:ids)", ids: expected_ids).update_all(enabled: false)
expect(bot_feature.persona_ids).to match_array(expected_ids)
expect(bot_feature.persona_ids).not_to include(inactive_persona.id)
end
it "includes personas with multiple permissions enabled" do
multi_permission_persona =
Fabricate(
:ai_persona,
enabled: true,
default_llm_id: bot_llm.id,
allow_chat_channel_mentions: true,
allow_chat_direct_messages: true,
allow_topic_mentions: true,
allow_personal_messages: true,
)
expect(bot_feature.persona_ids).to include(multi_permission_persona.id)
end
end
describe "#persona_ids" do
it "returns the persona id from site settings" do it "returns the persona id from site settings" do
ai_feature = ai_feature =
described_class.new( described_class.new(
@ -127,7 +205,7 @@ RSpec.describe DiscourseAi::Configuration::Feature do
) )
SiteSetting.ai_summarization_persona = ai_persona.id SiteSetting.ai_summarization_persona = ai_persona.id
expect(ai_feature.persona_id).to eq(ai_persona.id) expect(ai_feature.persona_ids).to eq([ai_persona.id])
end end
end end

View File

@ -19,7 +19,7 @@ RSpec.describe DiscourseAi::Admin::AiFeaturesController do
get "/admin/plugins/discourse-ai/ai-features.json" get "/admin/plugins/discourse-ai/ai-features.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.parsed_body["ai_features"].count).to eq(6) expect(response.parsed_body["ai_features"].count).to eq(7)
end end
end end

View File

@ -27,7 +27,8 @@ RSpec.describe "Admin AI features configuration", type: :system, js: true do
ai_features_page.toggle_unconfigured ai_features_page.toggle_unconfigured
expect(ai_features_page).to have_listed_modules(5) # this changes as we add more AI features
expect(ai_features_page).to have_listed_modules(6)
end end
it "lists the persona used for the corresponding AI feature" do it "lists the persona used for the corresponding AI feature" do