mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-07-08 23:32:45 +00:00
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:
parent
a40e2d3156
commit
73768ce920
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user