mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-06-29 11:02:17 +00:00
FEATURE: Display features that rely on multiple personas. (#1411)
* FEATURE: Display features that rely on multiple personas. This change makes the previously hidden feature page visible while displaying features, like the AI helper, which relies on multiple personas. * Fix system specs
This commit is contained in:
parent
33fd6801e5
commit
98afd7f8c3
@ -12,7 +12,7 @@ export default class AdminPluginsShowDiscourseAiFeaturesEdit extends DiscourseRo
|
|||||||
|
|
||||||
const { site_settings } = await ajax("/admin/config/site_settings.json", {
|
const { site_settings } = await ajax("/admin/config/site_settings.json", {
|
||||||
data: {
|
data: {
|
||||||
filter_area: `ai-features/${currentFeature.ref}`,
|
filter_area: `ai-features/${currentFeature.module_name}`,
|
||||||
plugin: "discourse-ai",
|
plugin: "discourse-ai",
|
||||||
category: "discourse_ai",
|
category: "discourse_ai",
|
||||||
},
|
},
|
||||||
|
@ -1,156 +0,0 @@
|
|||||||
import Component from "@glimmer/component";
|
|
||||||
import { service } from "@ember/service";
|
|
||||||
import RouteTemplate from "ember-route-template";
|
|
||||||
import { gt } from "truth-helpers";
|
|
||||||
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
|
||||||
import DButton from "discourse/components/d-button";
|
|
||||||
import DPageSubheader from "discourse/components/d-page-subheader";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
|
|
||||||
export default RouteTemplate(
|
|
||||||
class extends Component {
|
|
||||||
@service adminPluginNavManager;
|
|
||||||
|
|
||||||
get tableHeaders() {
|
|
||||||
const prefix = "discourse_ai.features.list.header";
|
|
||||||
return [
|
|
||||||
i18n(`${prefix}.name`),
|
|
||||||
i18n(`${prefix}.persona`),
|
|
||||||
i18n(`${prefix}.groups`),
|
|
||||||
"",
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
get configuredFeatures() {
|
|
||||||
return this.args.model.filter(
|
|
||||||
(feature) => feature.enable_setting.value === true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get unconfiguredFeatures() {
|
|
||||||
return this.args.model.filter(
|
|
||||||
(feature) => feature.enable_setting.value === false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<DBreadcrumbsItem
|
|
||||||
@path="/admin/plugins/{{this.adminPluginNavManager.currentPlugin.name}}/ai-features"
|
|
||||||
@label={{i18n "discourse_ai.features.short_title"}}
|
|
||||||
/>
|
|
||||||
<section class="ai-feature-list admin-detail">
|
|
||||||
<DPageSubheader
|
|
||||||
@titleLabel={{i18n "discourse_ai.features.short_title"}}
|
|
||||||
@descriptionLabel={{i18n "discourse_ai.features.description"}}
|
|
||||||
@learnMoreUrl="todo"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{{#if (gt this.configuredFeatures.length 0)}}
|
|
||||||
<div class="ai-feature-list__configured-features">
|
|
||||||
<h3>{{i18n "discourse_ai.features.list.configured_features"}}</h3>
|
|
||||||
|
|
||||||
<table class="d-admin-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
{{#each this.tableHeaders as |header|}}
|
|
||||||
<th>{{header}}</th>
|
|
||||||
{{/each}}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{{#each this.configuredFeatures as |feature|}}
|
|
||||||
<tr
|
|
||||||
class="ai-feature-list__row d-admin-row__content"
|
|
||||||
data-feature-name={{feature.name}}
|
|
||||||
>
|
|
||||||
<td class="d-admin-row__overview ai-feature-list__row-item">
|
|
||||||
<span class="ai-feature-list__row-item-name">
|
|
||||||
<strong>
|
|
||||||
{{feature.name}}
|
|
||||||
</strong>
|
|
||||||
</span>
|
|
||||||
<span class="ai-feature-list__row-item-description">
|
|
||||||
{{feature.description}}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
class="d-admin-row__detail ai-feature-list__row-item ai-feature-list__persona"
|
|
||||||
>
|
|
||||||
<DButton
|
|
||||||
class="btn-flat btn-small ai-feature-list__row-item-persona"
|
|
||||||
@translatedLabel={{feature.persona.name}}
|
|
||||||
@route="adminPlugins.show.discourse-ai-personas.edit"
|
|
||||||
@routeModels={{feature.persona.id}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
class="d-admin-row__detail ai-feature-list__row-item ai-feature-list__groups"
|
|
||||||
>
|
|
||||||
{{#if (gt feature.persona.allowed_groups.length 0)}}
|
|
||||||
<ul class="ai-feature-list__row-item-groups">
|
|
||||||
{{#each feature.persona.allowed_groups as |group|}}
|
|
||||||
<li>{{group.name}}</li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
<td class="d-admin-row_controls">
|
|
||||||
<DButton
|
|
||||||
class="btn-small edit"
|
|
||||||
@label="discourse_ai.features.list.edit"
|
|
||||||
@route="adminPlugins.show.discourse-ai-features.edit"
|
|
||||||
@routeModels={{feature.id}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{/each}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if (gt this.unconfiguredFeatures.length 0)}}
|
|
||||||
<div class="ai-feature-list__unconfigured-features">
|
|
||||||
<h3>{{i18n "discourse_ai.features.list.unconfigured_features"}}</h3>
|
|
||||||
|
|
||||||
<table class="d-admin-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{i18n "discourse_ai.features.list.header.name"}}</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{{#each this.unconfiguredFeatures as |feature|}}
|
|
||||||
<tr class="ai-feature-list__row d-admin-row__content">
|
|
||||||
<td class="d-admin-row__overview ai-feature-list__row-item">
|
|
||||||
<span class="ai-feature-list__row-item-name">
|
|
||||||
<strong>
|
|
||||||
{{feature.name}}
|
|
||||||
</strong>
|
|
||||||
</span>
|
|
||||||
<span class="ai-feature-list__row-item-description">
|
|
||||||
{{feature.description}}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="d-admin-row_controls">
|
|
||||||
<DButton
|
|
||||||
class="btn-small"
|
|
||||||
@label="discourse_ai.features.list.set_up"
|
|
||||||
@route="adminPlugins.show.discourse-ai-features.edit"
|
|
||||||
@routeModels={{feature.id}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{/each}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
}
|
|
||||||
);
|
|
@ -0,0 +1 @@
|
|||||||
|
<AiFeatures @features={{this.model}} />
|
@ -11,19 +11,22 @@ module DiscourseAi
|
|||||||
|
|
||||||
def edit
|
def edit
|
||||||
raise Discourse::InvalidParameters.new(:id) if params[:id].blank?
|
raise Discourse::InvalidParameters.new(:id) if params[:id].blank?
|
||||||
render json: serialize_feature(DiscourseAi::Features.find_feature_by_id(params[:id].to_i))
|
render json: serialize_module(DiscourseAi::Features.find_module_by_id(params[:id].to_i))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def serialize_features(features)
|
def serialize_features(modules)
|
||||||
features.map { |feature| feature.merge(persona: serialize_persona(feature[:persona])) }
|
modules.map { |a_module| serialize_module(a_module) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def serialize_feature(feature)
|
def serialize_module(a_module)
|
||||||
return nil if feature.blank?
|
return nil if a_module.blank?
|
||||||
|
|
||||||
feature.merge(persona: serialize_persona(feature[:persona]))
|
a_module.merge(
|
||||||
|
features:
|
||||||
|
a_module[:features].map { |f| f.merge(persona: serialize_persona(f[:persona])) },
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def serialize_persona(persona)
|
def serialize_persona(persona)
|
||||||
|
@ -2,14 +2,6 @@ import RestModel from "discourse/models/rest";
|
|||||||
|
|
||||||
export default class AiFeature extends RestModel {
|
export default class AiFeature extends RestModel {
|
||||||
createProperties() {
|
createProperties() {
|
||||||
return this.getProperties(
|
return this.getProperties("id", "module", "global_enabled", "features");
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"ref",
|
|
||||||
"description",
|
|
||||||
"enable_setting",
|
|
||||||
"persona",
|
|
||||||
"persona_setting"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
85
assets/javascripts/discourse/components/ai-features-list.gjs
Normal file
85
assets/javascripts/discourse/components/ai-features-list.gjs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { concat } from "@ember/helper";
|
||||||
|
import { gt } from "truth-helpers";
|
||||||
|
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}}
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
{{#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>
|
||||||
|
{{else}}
|
||||||
|
{{i18n "discourse_ai.features.no_groups"}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
export default AiFeaturesList;
|
90
assets/javascripts/discourse/components/ai-features.gjs
Normal file
90
assets/javascripts/discourse/components/ai-features.gjs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { fn } from "@ember/helper";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { service } from "@ember/service";
|
||||||
|
import { eq } from "truth-helpers";
|
||||||
|
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
||||||
|
import DButton from "discourse/components/d-button";
|
||||||
|
import DPageSubheader from "discourse/components/d-page-subheader";
|
||||||
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
|
import { i18n } from "discourse-i18n";
|
||||||
|
import AiFeaturesList from "./ai-features-list";
|
||||||
|
|
||||||
|
const CONFIGURED = "configured";
|
||||||
|
const UNCONFIGURED = "unconfigured";
|
||||||
|
|
||||||
|
export default class AiFeatures extends Component {
|
||||||
|
@service adminPluginNavManager;
|
||||||
|
|
||||||
|
@tracked selectedFeatureGroup = CONFIGURED;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
|
||||||
|
if (this.configuredFeatures.length === 0) {
|
||||||
|
this.selectedFeatureGroup = UNCONFIGURED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get featureGroups() {
|
||||||
|
return [
|
||||||
|
{ id: CONFIGURED, label: "discourse_ai.features.nav.configured" },
|
||||||
|
{ id: UNCONFIGURED, label: "discourse_ai.features.nav.unconfigured" },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
get configuredFeatures() {
|
||||||
|
return this.args.features.filter(
|
||||||
|
(feature) => feature.module_enabled === true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get unconfiguredFeatures() {
|
||||||
|
return this.args.features.filter(
|
||||||
|
(feature) => feature.module_enabled === false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
selectFeatureGroup(groupId) {
|
||||||
|
this.selectedFeatureGroup = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DBreadcrumbsItem
|
||||||
|
@path="/admin/plugins/{{this.adminPluginNavManager.currentPlugin.name}}/ai-features"
|
||||||
|
@label={{i18n "discourse_ai.features.short_title"}}
|
||||||
|
/>
|
||||||
|
<section class="ai-features admin-detail">
|
||||||
|
<DPageSubheader
|
||||||
|
@titleLabel={{i18n "discourse_ai.features.short_title"}}
|
||||||
|
@descriptionLabel={{i18n "discourse_ai.features.description"}}
|
||||||
|
@learnMoreUrl="todo"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="ai-feature-groups">
|
||||||
|
{{#each this.featureGroups as |groupData|}}
|
||||||
|
<DButton
|
||||||
|
class={{concatClass
|
||||||
|
groupData.id
|
||||||
|
(if
|
||||||
|
(eq this.selectedFeatureGroup groupData.id)
|
||||||
|
"btn-primary"
|
||||||
|
"btn-default"
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
@action={{fn this.selectFeatureGroup groupData.id}}
|
||||||
|
@label={{groupData.label}}
|
||||||
|
/>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if (eq this.selectedFeatureGroup "configured")}}
|
||||||
|
<AiFeaturesList @modules={{this.configuredFeatures}} />
|
||||||
|
{{else}}
|
||||||
|
<AiFeaturesList @modules={{this.unconfiguredFeatures}} />
|
||||||
|
{{/if}}
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
}
|
@ -41,12 +41,11 @@ export default {
|
|||||||
route: "adminPlugins.show.discourse-ai-spam",
|
route: "adminPlugins.show.discourse-ai-spam",
|
||||||
description: "discourse_ai.spam.spam_description",
|
description: "discourse_ai.spam.spam_description",
|
||||||
},
|
},
|
||||||
// TODO(@keegan / @roman): Uncomment this when structured output is merged
|
{
|
||||||
// {
|
label: "discourse_ai.features.short_title",
|
||||||
// label: "discourse_ai.features.short_title",
|
route: "adminPlugins.show.discourse-ai-features",
|
||||||
// route: "adminPlugins.show.discourse-ai-features",
|
description: "discourse_ai.features.description",
|
||||||
// description: "discourse_ai.features.description",
|
},
|
||||||
// },
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1,28 +1,51 @@
|
|||||||
.ai-feature-list {
|
.ai-features-list {
|
||||||
&__configured-features {
|
|
||||||
margin-block: 2rem;
|
margin-block: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-module {
|
||||||
|
&__header {
|
||||||
|
border-bottom: 1px solid var(--primary-low);
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__row-item-name,
|
&__module-title {
|
||||||
&__row-item-description {
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-feature-cards {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-feature-card {
|
||||||
|
background: var(--primary-very-low);
|
||||||
|
border: 1px solid var(--primary-low);
|
||||||
|
padding: 0.5rem;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
&__persona,
|
||||||
|
&__groups {
|
||||||
|
font-size: var(--font-down-1-rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__row-item-persona {
|
&__persona {
|
||||||
padding: 0;
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
@include ellipsis;
|
@include ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__row-item-groups {
|
&__persona-button {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item-groups {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
font-size: var(--font-down-2);
|
font-size: var(--font-down-1);
|
||||||
border-radius: var(--d-border-radius);
|
border-radius: var(--d-border-radius);
|
||||||
background: var(--primary-very-low);
|
background: var(--primary-very-low);
|
||||||
border: 1px solid var(--primary-low);
|
border: 1px solid var(--primary-low);
|
||||||
|
@ -169,15 +169,48 @@ en:
|
|||||||
short_title: "Features"
|
short_title: "Features"
|
||||||
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"
|
||||||
list:
|
disabled: "(disabled)"
|
||||||
header:
|
persona: "Persona:"
|
||||||
name: "Name"
|
groups: "Groups:"
|
||||||
persona: "Persona"
|
no_persona: "Not set"
|
||||||
groups: "Groups"
|
no_groups: "None"
|
||||||
edit: "Edit"
|
edit: "Edit"
|
||||||
set_up: "Set up"
|
nav:
|
||||||
configured_features: "Configured features"
|
configured: "Configured"
|
||||||
unconfigured_features: "Unconfigured features"
|
unconfigured: "Unconfigured"
|
||||||
|
summarization:
|
||||||
|
name: "Summaries"
|
||||||
|
description: "Makes a summarization button available that allows visitors to summarize topics"
|
||||||
|
topic_summaries: "Topic summaries"
|
||||||
|
gists: "Topic list's short summaries"
|
||||||
|
search:
|
||||||
|
name: "Search"
|
||||||
|
description: "Enhances search experience by providing AI-generated answers to queries"
|
||||||
|
discoveries: "Discoveries"
|
||||||
|
discord:
|
||||||
|
name: "Discord integration"
|
||||||
|
description: "Adds the ability to search Discord channels"
|
||||||
|
search: "Discord search"
|
||||||
|
inference:
|
||||||
|
name: "Inferred concepts"
|
||||||
|
description: "Classifies topics and posts into areas of interest / labels."
|
||||||
|
generate_concepts: "Concepts inference"
|
||||||
|
match_concepts: "Concepts matching"
|
||||||
|
deduplicate_concepts: "Concepts deduplication"
|
||||||
|
|
||||||
|
ai_helper:
|
||||||
|
name: "Helper"
|
||||||
|
description: "Assists users in community interaction, such as creating topics, writing posts, and reading content."
|
||||||
|
proofread: Proofread text
|
||||||
|
title_suggestions: "Suggest titles"
|
||||||
|
explain: "Explain"
|
||||||
|
illustrate_post: "Illustrate post"
|
||||||
|
smart_dates: "Smart dates"
|
||||||
|
translate: "Translate"
|
||||||
|
markdown_tables: "Generate Markdown table"
|
||||||
|
custom_prompt: "Custom prompt"
|
||||||
|
image_caption: "Caption images"
|
||||||
|
|
||||||
|
|
||||||
modals:
|
modals:
|
||||||
select_option: "Select an option..."
|
select_option: "Select an option..."
|
||||||
|
@ -579,23 +579,6 @@ en:
|
|||||||
missing_provider_param: "%{param} can't be blank"
|
missing_provider_param: "%{param} can't be blank"
|
||||||
bedrock_invalid_url: "Please complete all the fields to use this model."
|
bedrock_invalid_url: "Please complete all the fields to use this model."
|
||||||
|
|
||||||
features:
|
|
||||||
summarization:
|
|
||||||
name: "Summaries"
|
|
||||||
description: "Makes a summarization button available that allows visitors to summarize topics"
|
|
||||||
gists:
|
|
||||||
name: "Short Summaries"
|
|
||||||
description: "Adds the ability to view short summaries of topics on the topic list"
|
|
||||||
discoveries:
|
|
||||||
name: "Discobot Discoveries"
|
|
||||||
description: "Enhances search experience by providing AI-generated answers to queries"
|
|
||||||
discord_search:
|
|
||||||
name: "Discord Search"
|
|
||||||
description: "Adds the ability to search Discord channels"
|
|
||||||
inferred_concepts:
|
|
||||||
name: "Inferred Concepts"
|
|
||||||
description: "Classifies topics and posts into areas of interest / labels."
|
|
||||||
|
|
||||||
errors:
|
errors:
|
||||||
quota_exceeded: "You have exceeded the quota for this model. Please try again in %{relative_time}."
|
quota_exceeded: "You have exceeded the quota for this model. Please try again in %{relative_time}."
|
||||||
quota_required: "You must specify maximum tokens or usages for this model"
|
quota_required: "You must specify maximum tokens or usages for this model"
|
||||||
|
@ -90,15 +90,18 @@ discourse_ai:
|
|||||||
default: false
|
default: false
|
||||||
client: true
|
client: true
|
||||||
validator: "DiscourseAi::Configuration::LlmDependencyValidator"
|
validator: "DiscourseAi::Configuration::LlmDependencyValidator"
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
composer_ai_helper_allowed_groups:
|
composer_ai_helper_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
|
||||||
allow_any: false
|
allow_any: false
|
||||||
refresh: true
|
refresh: true
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_allowed_in_pm:
|
ai_helper_allowed_in_pm:
|
||||||
default: false
|
default: false
|
||||||
client: true
|
client: true
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_model:
|
ai_helper_model:
|
||||||
default: ""
|
default: ""
|
||||||
allow_any: false
|
allow_any: false
|
||||||
@ -118,10 +121,13 @@ discourse_ai:
|
|||||||
default: "3|14" # 3: @staff, 14: @trust_level_4
|
default: "3|14" # 3: @staff, 14: @trust_level_4
|
||||||
allow_any: false
|
allow_any: false
|
||||||
refresh: true
|
refresh: true
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_automatic_chat_thread_title:
|
ai_helper_automatic_chat_thread_title:
|
||||||
default: false
|
default: false
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_automatic_chat_thread_title_delay:
|
ai_helper_automatic_chat_thread_title_delay:
|
||||||
default: 5
|
default: 5
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_illustrate_post_model:
|
ai_helper_illustrate_post_model:
|
||||||
default: disabled
|
default: disabled
|
||||||
type: enum
|
type: enum
|
||||||
@ -129,6 +135,7 @@ discourse_ai:
|
|||||||
- stable_diffusion_xl
|
- stable_diffusion_xl
|
||||||
- dall_e_3
|
- dall_e_3
|
||||||
- disabled
|
- disabled
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_enabled_features:
|
ai_helper_enabled_features:
|
||||||
client: true
|
client: true
|
||||||
default: "suggestions|context_menu"
|
default: "suggestions|context_menu"
|
||||||
@ -140,6 +147,7 @@ discourse_ai:
|
|||||||
- "suggestions"
|
- "suggestions"
|
||||||
- "context_menu"
|
- "context_menu"
|
||||||
- "image_caption"
|
- "image_caption"
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_image_caption_model:
|
ai_helper_image_caption_model:
|
||||||
default: ""
|
default: ""
|
||||||
type: enum
|
type: enum
|
||||||
@ -152,6 +160,7 @@ discourse_ai:
|
|||||||
default: "10" # 10: @trust_level_0
|
default: "10" # 10: @trust_level_0
|
||||||
allow_any: false
|
allow_any: false
|
||||||
refresh: true
|
refresh: true
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_model_allowed_seeded_models:
|
ai_helper_model_allowed_seeded_models:
|
||||||
default: ""
|
default: ""
|
||||||
hidden: true
|
hidden: true
|
||||||
@ -166,38 +175,47 @@ discourse_ai:
|
|||||||
default: "-22"
|
default: "-22"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_title_suggestions_persona:
|
ai_helper_title_suggestions_persona:
|
||||||
default: "-23"
|
default: "-23"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_explain_persona:
|
ai_helper_explain_persona:
|
||||||
default: "-24"
|
default: "-24"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_post_illustrator_persona:
|
ai_helper_post_illustrator_persona:
|
||||||
default: "-21"
|
default: "-21"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_smart_dates_persona:
|
ai_helper_smart_dates_persona:
|
||||||
default: "-19"
|
default: "-19"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_translator_persona:
|
ai_helper_translator_persona:
|
||||||
default: "-25"
|
default: "-25"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_markdown_tables_persona:
|
ai_helper_markdown_tables_persona:
|
||||||
default: "-20"
|
default: "-20"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_custom_prompt_persona:
|
ai_helper_custom_prompt_persona:
|
||||||
default: "-18"
|
default: "-18"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
ai_helper_image_caption_persona:
|
ai_helper_image_caption_persona:
|
||||||
default: "-26"
|
default: "-26"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
|
area: "ai-features/ai_helper"
|
||||||
|
|
||||||
ai_embeddings_enabled:
|
ai_embeddings_enabled:
|
||||||
default: false
|
default: false
|
||||||
@ -298,12 +316,12 @@ discourse_ai:
|
|||||||
hidden: true
|
hidden: true
|
||||||
ai_summary_gists_enabled:
|
ai_summary_gists_enabled:
|
||||||
default: false
|
default: false
|
||||||
area: "ai-features/gists"
|
area: "ai-features/summarization"
|
||||||
ai_summary_gists_persona:
|
ai_summary_gists_persona:
|
||||||
default: "-12"
|
default: "-12"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
area: "ai-features/gists"
|
area: "ai-features/summarization"
|
||||||
ai_summary_gists_allowed_groups: # Deprecated. TODO(roman): Remove 2025-09-01
|
ai_summary_gists_allowed_groups: # Deprecated. TODO(roman): Remove 2025-09-01
|
||||||
type: group_list
|
type: group_list
|
||||||
list_type: compact
|
list_type: compact
|
||||||
@ -331,7 +349,7 @@ discourse_ai:
|
|||||||
ai_bot_enabled:
|
ai_bot_enabled:
|
||||||
default: false
|
default: false
|
||||||
client: true
|
client: true
|
||||||
area: "ai-features/discoveries"
|
area: "ai-features/search"
|
||||||
ai_bot_enable_chat_warning:
|
ai_bot_enable_chat_warning:
|
||||||
default: false
|
default: false
|
||||||
client: true
|
client: true
|
||||||
@ -367,7 +385,7 @@ discourse_ai:
|
|||||||
type: enum
|
type: enum
|
||||||
client: true
|
client: true
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
area: "ai-features/discoveries"
|
area: "ai-features/search"
|
||||||
ai_automation_max_triage_per_minute:
|
ai_automation_max_triage_per_minute:
|
||||||
default: 60
|
default: 60
|
||||||
hidden: true
|
hidden: true
|
||||||
@ -383,32 +401,32 @@ discourse_ai:
|
|||||||
ai_discord_search_enabled:
|
ai_discord_search_enabled:
|
||||||
default: false
|
default: false
|
||||||
client: true
|
client: true
|
||||||
area: "ai-features/discord_search"
|
area: "ai-features/discord"
|
||||||
ai_discord_app_id:
|
ai_discord_app_id:
|
||||||
default: ""
|
default: ""
|
||||||
client: false
|
client: false
|
||||||
area: "ai-features/discord_search"
|
area: "ai-features/discord"
|
||||||
ai_discord_app_public_key:
|
ai_discord_app_public_key:
|
||||||
default: ""
|
default: ""
|
||||||
client: false
|
client: false
|
||||||
area: "ai-features/discord_search"
|
area: "ai-features/discord"
|
||||||
ai_discord_search_mode:
|
ai_discord_search_mode:
|
||||||
default: "search"
|
default: "search"
|
||||||
type: enum
|
type: enum
|
||||||
choices:
|
choices:
|
||||||
- search
|
- search
|
||||||
- persona
|
- persona
|
||||||
area: "ai-features/discord_search"
|
area: "ai-features/discord"
|
||||||
ai_discord_search_persona:
|
ai_discord_search_persona:
|
||||||
default: ""
|
default: ""
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
area: "ai-features/discord_search"
|
area: "ai-features/discord"
|
||||||
ai_discord_allowed_guilds:
|
ai_discord_allowed_guilds:
|
||||||
type: list
|
type: list
|
||||||
list_type: compact
|
list_type: compact
|
||||||
default: ""
|
default: ""
|
||||||
area: "ai-features/discord_search"
|
area: "ai-features/discord"
|
||||||
|
|
||||||
ai_spam_detection_enabled:
|
ai_spam_detection_enabled:
|
||||||
default: false
|
default: false
|
||||||
@ -459,51 +477,51 @@ discourse_ai:
|
|||||||
inferred_concepts_enabled:
|
inferred_concepts_enabled:
|
||||||
default: false
|
default: false
|
||||||
client: true
|
client: true
|
||||||
area: "ai-features/inferred_concepts"
|
area: "ai-features/inference"
|
||||||
inferred_concepts_background_match:
|
inferred_concepts_background_match:
|
||||||
default: false
|
default: false
|
||||||
client: false
|
client: false
|
||||||
area: "ai-features/inferred_concepts"
|
area: "ai-features/inference"
|
||||||
inferred_concepts_daily_topics_limit:
|
inferred_concepts_daily_topics_limit:
|
||||||
default: 20
|
default: 20
|
||||||
client: false
|
client: false
|
||||||
area: "ai-features/inferred_concepts"
|
area: "ai-features/inference"
|
||||||
inferred_concepts_min_posts:
|
inferred_concepts_min_posts:
|
||||||
default: 5
|
default: 5
|
||||||
client: false
|
client: false
|
||||||
area: "ai-features/inferred_concepts"
|
area: "ai-features/inference"
|
||||||
inferred_concepts_min_likes:
|
inferred_concepts_min_likes:
|
||||||
default: 10
|
default: 10
|
||||||
client: false
|
client: false
|
||||||
area: "ai-features/inferred_concepts"
|
area: "ai-features/inference"
|
||||||
inferred_concepts_min_views:
|
inferred_concepts_min_views:
|
||||||
default: 100
|
default: 100
|
||||||
client: false
|
client: false
|
||||||
area: "ai-features/inferred_concepts"
|
area: "ai-features/inference"
|
||||||
inferred_concepts_lookback_days:
|
inferred_concepts_lookback_days:
|
||||||
default: 30
|
default: 30
|
||||||
client: false
|
client: false
|
||||||
area: "ai-features/inferred_concepts"
|
area: "ai-features/inference"
|
||||||
inferred_concepts_daily_posts_limit:
|
inferred_concepts_daily_posts_limit:
|
||||||
default: 30
|
default: 30
|
||||||
client: false
|
client: false
|
||||||
area: "ai-features/inferred_concepts"
|
area: "ai-features/inference"
|
||||||
inferred_concepts_post_min_likes:
|
inferred_concepts_post_min_likes:
|
||||||
default: 5
|
default: 5
|
||||||
client: false
|
client: false
|
||||||
area: "ai-features/inferred_concepts"
|
area: "ai-features/inference"
|
||||||
inferred_concepts_generate_persona:
|
inferred_concepts_generate_persona:
|
||||||
default: "-15"
|
default: "-15"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
area: "ai-features/inferred_concepts"
|
area: "ai-features/inference"
|
||||||
inferred_concepts_match_persona:
|
inferred_concepts_match_persona:
|
||||||
default: "-16"
|
default: "-16"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
area: "ai-features/inferred_concepts"
|
area: "ai-features/inference"
|
||||||
inferred_concepts_deduplicate_persona:
|
inferred_concepts_deduplicate_persona:
|
||||||
default: "-17"
|
default: "-17"
|
||||||
type: enum
|
type: enum
|
||||||
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
enum: "DiscourseAi::Configuration::PersonaEnumerator"
|
||||||
area: "ai-features/inferred_concepts"
|
area: "ai-features/inference"
|
||||||
|
118
lib/features.rb
118
lib/features.rb
@ -2,91 +2,107 @@
|
|||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Features
|
module Features
|
||||||
def self.feature_config
|
def self.features_config
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name_ref: "summarization",
|
module_name: "summarization",
|
||||||
name_key: "discourse_ai.features.summarization.name",
|
module_enabled: "ai_summarization_enabled",
|
||||||
description_key: "discourse_ai.features.summarization.description",
|
features: [
|
||||||
persona_setting_name: "ai_summarization_persona",
|
{ name: "topic_summaries", persona_setting_name: "ai_summarization_persona" },
|
||||||
enable_setting_name: "ai_summarization_enabled",
|
{
|
||||||
|
name: "gists",
|
||||||
|
persona_setting_name: "ai_summary_gists_persona",
|
||||||
|
enabled: "ai_summary_gists_enabled",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name_ref: "gists",
|
module_name: "search",
|
||||||
name_key: "discourse_ai.features.gists.name",
|
module_enabled: "ai_bot_enabled",
|
||||||
description_key: "discourse_ai.features.gists.description",
|
features: [{ name: "discoveries", persona_setting_name: "ai_bot_discover_persona" }],
|
||||||
persona_setting_name: "ai_summary_gists_persona",
|
|
||||||
enable_setting_name: "ai_summary_gists_enabled",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
name_ref: "discoveries",
|
module_name: "discord",
|
||||||
name_key: "discourse_ai.features.discoveries.name",
|
module_enabled: "ai_discord_search_enabled",
|
||||||
description_key: "discourse_ai.features.discoveries.description",
|
features: [{ name: "search", persona_setting_name: "ai_discord_search_persona" }],
|
||||||
persona_setting_name: "ai_bot_discover_persona",
|
|
||||||
enable_setting_name: "ai_bot_enabled",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
name_ref: "discord_search",
|
module_name: "inference",
|
||||||
name_key: "discourse_ai.features.discord_search.name",
|
module_enabled: "inferred_concepts_enabled",
|
||||||
description_key: "discourse_ai.features.discord_search.description",
|
features: [
|
||||||
persona_setting_name: "ai_discord_search_persona",
|
{
|
||||||
enable_setting_name: "ai_discord_search_enabled",
|
name: "generate_concepts",
|
||||||
|
persona_setting_name: "inferred_concepts_generate_persona",
|
||||||
|
},
|
||||||
|
{ name: "match_concepts", persona_setting_name: "inferred_concepts_match_persona" },
|
||||||
|
{
|
||||||
|
name: "deduplicate_concepts",
|
||||||
|
persona_setting_name: "inferred_concepts_deduplicate_persona",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
name_ref: "inferred_concepts",
|
module_name: "ai_helper",
|
||||||
name_key: "discourse_ai.features.inferred_concepts.name",
|
module_enabled: "ai_helper_enabled",
|
||||||
description_key: "discourse_ai.features.inferred_concepts.description",
|
features: [
|
||||||
persona_setting_name: "inferred_concepts_generate_persona",
|
{ name: "proofread", persona_setting_name: "ai_helper_proofreader_persona" },
|
||||||
enable_setting_name: "inferred_concepts_enabled",
|
{
|
||||||
|
name: "title_suggestions",
|
||||||
|
persona_setting_name: "ai_helper_title_suggestions_persona",
|
||||||
|
},
|
||||||
|
{ name: "explain", persona_setting_name: "ai_helper_explain_persona" },
|
||||||
|
{ name: "illustrate_post", persona_setting_name: "ai_helper_post_illustrator_persona" },
|
||||||
|
{ name: "smart_dates", persona_setting_name: "ai_helper_smart_dates_persona" },
|
||||||
|
{ name: "translate", persona_setting_name: "ai_helper_translator_persona" },
|
||||||
|
{ name: "markdown_tables", persona_setting_name: "ai_helper_markdown_tables_persona" },
|
||||||
|
{ name: "custom_prompt", persona_setting_name: "ai_helper_custom_prompt_persona" },
|
||||||
|
{ name: "image_caption", persona_setting_name: "ai_helper_image_caption_persona" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.features
|
def self.features
|
||||||
feature_config.map do |feature|
|
features_config.map do |a_module|
|
||||||
{
|
{
|
||||||
id: feature[:id],
|
id: a_module[:id],
|
||||||
ref: feature[:name_ref],
|
module_name: a_module[:module_name],
|
||||||
name: I18n.t(feature[:name_key]),
|
module_enabled: SiteSetting.get(a_module[:module_enabled]),
|
||||||
description: I18n.t(feature[:description_key]),
|
features:
|
||||||
|
a_module[:features].map do |feature|
|
||||||
|
{
|
||||||
|
name: feature[:name],
|
||||||
persona: AiPersona.find_by(id: SiteSetting.get(feature[:persona_setting_name])),
|
persona: AiPersona.find_by(id: SiteSetting.get(feature[:persona_setting_name])),
|
||||||
persona_setting: {
|
enabled: feature[:enabled].present? ? SiteSetting.get(feature[:enabled]) : true,
|
||||||
name: feature[:persona_setting_name],
|
}
|
||||||
value: SiteSetting.get(feature[:persona_setting_name]),
|
end,
|
||||||
type: SiteSetting.type_supervisor.get_type(feature[:persona_setting_name]),
|
|
||||||
},
|
|
||||||
enable_setting: {
|
|
||||||
name: feature[:enable_setting_name],
|
|
||||||
value: SiteSetting.get(feature[:enable_setting_name]),
|
|
||||||
type: SiteSetting.type_supervisor.get_type(feature[:enable_setting_name]),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.find_feature_by_id(id)
|
def self.find_module_by_id(id)
|
||||||
lookup = features.index_by { |f| f[:id] }
|
lookup = features.index_by { |f| f[:id] }
|
||||||
lookup[id]
|
lookup[id]
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.find_feature_by_ref(name_ref)
|
def self.find_module_by_name(module_name)
|
||||||
lookup = features.index_by { |f| f[:ref] }
|
lookup = features.index_by { |f| f[:module] }
|
||||||
lookup[name_ref]
|
lookup[module_name]
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.find_feature_id_by_ref(name_ref)
|
def self.find_module_id_by_name(module_name)
|
||||||
find_feature_by_ref(name_ref)&.dig(:id)
|
find_module_by_name(module_name)&.dig(:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.feature_area(name_ref)
|
def self.feature_area(module_name)
|
||||||
name_ref = name_ref.to_s if name_ref.is_a?(Symbol)
|
name_s = module_name.to_s
|
||||||
find_feature_by_ref(name_ref) || raise(ArgumentError, "Feature not found: #{name_ref}")
|
find_module_by_name(name_s) || raise(ArgumentError, "Feature not found: #{name_s}")
|
||||||
"ai-features/#{name_ref}"
|
"ai-features/#{name_s}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -74,8 +74,8 @@ Rails.autoloaders.main.push_dir(File.join(__dir__, "lib"), namespace: ::Discours
|
|||||||
require_relative "lib/engine"
|
require_relative "lib/engine"
|
||||||
require_relative "lib/features"
|
require_relative "lib/features"
|
||||||
|
|
||||||
DiscourseAi::Features.feature_config.each do |feature|
|
DiscourseAi::Features.features_config.each do |feature|
|
||||||
register_site_setting_area("ai-features/#{feature[:name_ref]}")
|
register_site_setting_area("ai-features/#{feature[:module_name]}")
|
||||||
end
|
end
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
|
@ -26,7 +26,7 @@ RSpec.describe DiscourseAi::Admin::AiFeaturesController do
|
|||||||
describe "#edit" do
|
describe "#edit" do
|
||||||
it "returns a success response" do
|
it "returns a success response" do
|
||||||
get "/admin/plugins/discourse-ai/ai-features/1/edit.json"
|
get "/admin/plugins/discourse-ai/ai-features/1/edit.json"
|
||||||
expect(response.parsed_body["name"]).to eq(I18n.t "discourse_ai.features.summarization.name")
|
expect(response.parsed_body["module_name"]).to eq("summarization")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -21,43 +21,37 @@ RSpec.describe "Admin AI features configuration", type: :system, js: true do
|
|||||||
|
|
||||||
it "lists all persona backed AI features separated by configured/unconfigured" do
|
it "lists all persona backed AI features separated by configured/unconfigured" do
|
||||||
ai_features_page.visit
|
ai_features_page.visit
|
||||||
expect(
|
ai_features_page.toggle_configured
|
||||||
ai_features_page
|
|
||||||
.configured_features_table
|
|
||||||
.find(".ai-feature-list__row-item .ai-feature-list__row-item-name")
|
|
||||||
.text,
|
|
||||||
).to eq(I18n.t("discourse_ai.features.summarization.name"))
|
|
||||||
|
|
||||||
expect(ai_features_page).to have_configured_feature_items(1)
|
expect(ai_features_page).to have_listed_modules(1)
|
||||||
expect(ai_features_page).to have_unconfigured_feature_items(4)
|
|
||||||
|
ai_features_page.toggle_unconfigured
|
||||||
|
|
||||||
|
expect(ai_features_page).to have_listed_modules(4)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "lists the persona used for the corresponding AI feature" do
|
it "lists the persona used for the corresponding AI feature" do
|
||||||
ai_features_page.visit
|
ai_features_page.visit
|
||||||
expect(ai_features_page).to have_feature_persona(summarization_persona.name)
|
|
||||||
|
ai_features_page.toggle_configured
|
||||||
|
|
||||||
|
expect(ai_features_page).to have_feature_persona("topic_summaries", summarization_persona.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "lists the groups allowed to use the AI feature" do
|
it "lists the groups allowed to use the AI feature" do
|
||||||
ai_features_page.visit
|
ai_features_page.visit
|
||||||
expect(ai_features_page).to have_feature_groups([group_1.name, group_2.name])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "can navigate the AI plugin with breadcrumbs" do
|
ai_features_page.toggle_configured
|
||||||
visit "/admin/plugins/discourse-ai/ai-features"
|
|
||||||
expect(page).to have_css(".d-breadcrumbs")
|
expect(ai_features_page).to have_feature_groups("topic_summaries", [group_1.name, group_2.name])
|
||||||
expect(page).to have_css(".d-breadcrumbs__item", count: 4)
|
|
||||||
find(".d-breadcrumbs__item", text: I18n.t("admin_js.admin.plugins.title")).click
|
|
||||||
expect(page).to have_current_path("/admin/plugins")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "shows edit page with settings" do
|
it "shows edit page with settings" do
|
||||||
ai_features_page.visit
|
ai_features_page.visit
|
||||||
ai_features_page.click_edit_feature(I18n.t("discourse_ai.features.summarization.name"))
|
|
||||||
|
ai_features_page.click_edit_module("summarization")
|
||||||
|
|
||||||
expect(page).to have_current_path("/admin/plugins/discourse-ai/ai-features/1/edit")
|
expect(page).to have_current_path("/admin/plugins/discourse-ai/ai-features/1/edit")
|
||||||
expect(page).to have_css(
|
|
||||||
".ai-feature-editor__header h2",
|
|
||||||
text: I18n.t("discourse_ai.features.summarization.name"),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(page).to have_css(".setting")
|
expect(page).to have_css(".setting")
|
||||||
end
|
end
|
||||||
|
@ -3,48 +3,44 @@
|
|||||||
module PageObjects
|
module PageObjects
|
||||||
module Pages
|
module Pages
|
||||||
class AdminAiFeatures < PageObjects::Pages::Base
|
class AdminAiFeatures < PageObjects::Pages::Base
|
||||||
CONFIGURED_FEATURES_TABLE = ".ai-feature-list__configured-features .d-admin-table"
|
FEATURES_PAGE = ".ai-features"
|
||||||
UNCONFIGURED_FEATURES_TABLE = ".ai-feature-list__unconfigured-features .d-admin-table"
|
|
||||||
|
|
||||||
def visit
|
def visit
|
||||||
page.visit("/admin/plugins/discourse-ai/ai-features")
|
page.visit("/admin/plugins/discourse-ai/ai-features")
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def configured_features_table
|
def toggle_configured
|
||||||
page.find(CONFIGURED_FEATURES_TABLE)
|
page.find("#{FEATURES_PAGE} .ai-feature-groups .configured").click
|
||||||
end
|
end
|
||||||
|
|
||||||
def unconfigured_features_table
|
def toggle_unconfigured
|
||||||
page.find(UNCONFIGURED_FEATURES_TABLE)
|
page.find("#{FEATURES_PAGE} .ai-feature-groups .unconfigured").click
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_configured_feature_items?(count)
|
def has_listed_modules?(count)
|
||||||
page.has_css?("#{CONFIGURED_FEATURES_TABLE} .ai-feature-list__row", count: count)
|
page.has_css?("#{FEATURES_PAGE} .ai-module", count: count)
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_unconfigured_feature_items?(count)
|
def has_feature_persona?(feature_name, name)
|
||||||
page.has_css?("#{UNCONFIGURED_FEATURES_TABLE} .ai-feature-list__row", count: count)
|
|
||||||
end
|
|
||||||
|
|
||||||
def has_feature_persona?(name)
|
|
||||||
page.has_css?(
|
page.has_css?(
|
||||||
"#{CONFIGURED_FEATURES_TABLE} .ai-feature-list__persona .d-button-label ",
|
"#{FEATURES_PAGE} .ai-feature-card[data-feature-name='#{feature_name}'] .ai-feature-card__persona-button .d-button-label",
|
||||||
text: name,
|
text: name,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_feature_groups?(groups)
|
def has_feature_groups?(feature_name, groups)
|
||||||
listed_groups = page.find("#{CONFIGURED_FEATURES_TABLE} .ai-feature-list__groups")
|
listed_groups =
|
||||||
|
page.find(
|
||||||
|
"#{FEATURES_PAGE} .ai-feature-card[data-feature-name='#{feature_name}'] .ai-feature-card__item-groups",
|
||||||
|
)
|
||||||
list_items = listed_groups.all("li", visible: true).map(&:text)
|
list_items = listed_groups.all("li", visible: true).map(&:text)
|
||||||
|
|
||||||
list_items.sort == groups.sort
|
list_items.sort == groups.sort
|
||||||
end
|
end
|
||||||
|
|
||||||
def click_edit_feature(feature_name)
|
def click_edit_module(module_name)
|
||||||
page.find(
|
page.find("#{FEATURES_PAGE} .ai-module[data-module-name='#{module_name}'] .edit").click
|
||||||
"#{CONFIGURED_FEATURES_TABLE} .ai-feature-list__row[data-feature-name='#{feature_name}'] .edit",
|
|
||||||
).click
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user