From 6dc76327989a9dd31632b87438502df2146c73a7 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Wed, 2 Apr 2025 16:18:49 -0700 Subject: [PATCH 01/12] FEATURE: Configure persona backed features in admin panel --- ...plugins-show-discourse-ai-features-edit.js | 7 ++ ...dmin-plugins-show-discourse-ai-features.js | 11 ++++ .../show/discourse-ai-features/edit.gjs | 7 ++ .../show/discourse-ai-features/index.gjs | 64 +++++++++++++++++++ .../admin/ai_features_controller.rb | 60 +++++++++++++++++ .../admin-discourse-ai-plugin-route-map.js | 4 ++ .../admin-plugin-configuration-nav.js | 5 ++ config/locales/client.en.yml | 4 ++ config/routes.rb | 5 ++ 9 files changed, 167 insertions(+) create mode 100644 admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js create mode 100644 admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features.js create mode 100644 admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs create mode 100644 admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs create mode 100644 app/controllers/discourse_ai/admin/ai_features_controller.rb diff --git a/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js b/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js new file mode 100644 index 000000000..324b9cc87 --- /dev/null +++ b/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js @@ -0,0 +1,7 @@ +import DiscourseRoute from "discourse/routes/discourse"; + +export default class AdminPluginsShowDiscourseAiFeaturesEdit extends DiscourseRoute { + model() { + // todo + } +} diff --git a/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features.js b/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features.js new file mode 100644 index 000000000..07476affa --- /dev/null +++ b/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features.js @@ -0,0 +1,11 @@ +import { ajax } from "discourse/lib/ajax"; +import DiscourseRoute from "discourse/routes/discourse"; + +export default class AdminPluginsShowDiscourseAiFeatures extends DiscourseRoute { + async model() { + const { ai_features } = await ajax( + `/admin/plugins/discourse-ai/ai-features.json` + ); + return ai_features; + } +} diff --git a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs new file mode 100644 index 000000000..675c04973 --- /dev/null +++ b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs @@ -0,0 +1,7 @@ +import RouteTemplate from "ember-route-template"; + +export default RouteTemplate( + +); diff --git a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs new file mode 100644 index 000000000..d2583a6f4 --- /dev/null +++ b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs @@ -0,0 +1,64 @@ +import Component from "@glimmer/component"; +import { service } from "@ember/service"; +import RouteTemplate from "ember-route-template"; +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; + + + } +); diff --git a/app/controllers/discourse_ai/admin/ai_features_controller.rb b/app/controllers/discourse_ai/admin/ai_features_controller.rb new file mode 100644 index 000000000..342b1f1e4 --- /dev/null +++ b/app/controllers/discourse_ai/admin/ai_features_controller.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module DiscourseAi + module Admin + class AiFeaturesController < ::Admin::AdminController + requires_plugin ::DiscourseAi::PLUGIN_NAME + + def index + render json: persona_backed_features + end + + def edit + end + + def update + end + + def destroy + end + + private + + # Eventually we may move this to an active record model + def persona_backed_features + # TODO: WIP just getting data rn, will cleanup AiPersona call later... + [ + { + name: "Summaries", + description: + "Makes a summarization button available that allows visitors to summarize topics.", + # persona: AiPersona.find_by(id: SiteSetting.ai_summarization_persona), + persona: "Foo", + enabled: SiteSetting.ai_summarization_enabled, + }, + { + name: "Short Summaries", + description: "Adds the ability to view short summaries of topics on the topic list.", + # persona: AiPersona.find_by(id: SiteSetting.ai_summary_gists_persona), + persona: "Bar", + enabled: SiteSetting.ai_summary_gists_enabled, + }, + { + name: "Discobot Discoveries", + description: "", + # persona: AiPersona.find_by(id: SiteSetting.ai_bot_discover_persona), + persona: "Baz", + enabled: SiteSetting.ai_bot_enabled, + }, + { + name: "Discord Search", + description: "Adds the ability to search Discord channels.", + # persona: AiPersona.find_by(id: SiteSetting.ai_discord_search_persona), + persona: "Qux", + enabled: "", + }, + ] + end + end + end +end diff --git a/assets/javascripts/discourse/admin-discourse-ai-plugin-route-map.js b/assets/javascripts/discourse/admin-discourse-ai-plugin-route-map.js index 3e798a18d..aa2bed60e 100644 --- a/assets/javascripts/discourse/admin-discourse-ai-plugin-route-map.js +++ b/assets/javascripts/discourse/admin-discourse-ai-plugin-route-map.js @@ -29,5 +29,9 @@ export default { this.route("edit", { path: "/:id/edit" }); } ); + + this.route("discourse-ai-features", { path: "ai-features" }, function () { + this.route("edit", { path: "/:id/edit" }); + }); }, }; diff --git a/assets/javascripts/initializers/admin-plugin-configuration-nav.js b/assets/javascripts/initializers/admin-plugin-configuration-nav.js index 1410086c6..d9ca17a03 100644 --- a/assets/javascripts/initializers/admin-plugin-configuration-nav.js +++ b/assets/javascripts/initializers/admin-plugin-configuration-nav.js @@ -41,6 +41,11 @@ export default { route: "adminPlugins.show.discourse-ai-spam", description: "discourse_ai.spam.spam_description", }, + { + label: "discourse_ai.features.short_title", + route: "adminPlugins.show.discourse-ai-features", + description: "discourse_ai.features.description", + }, ]); }); }, diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 5f4eb92c6..0c84cf88b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -164,6 +164,10 @@ en: discourse_ai: title: "AI" + 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." + modals: select_option: "Select an option..." diff --git a/config/routes.rb b/config/routes.rb index 29fa26587..85eb85189 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -110,6 +110,11 @@ controller: "discourse_ai/admin/ai_embeddings" do collection { get :test } end + + resources :ai_features, + only: %i[index edit update destroy], + path: "ai-features", + controller: "discourse_ai/admin/ai_features" end end From 1764660f4a949479a0ad088c61b86df1593d5cd8 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Thu, 3 Apr 2025 15:02:23 -0700 Subject: [PATCH 02/12] WIP --- ...plugins-show-discourse-ai-features-edit.js | 9 +- ...dmin-plugins-show-discourse-ai-features.js | 9 +- .../show/discourse-ai-features/edit.gjs | 7 +- .../show/discourse-ai-features/index.gjs | 118 ++++++++++++--- .../admin/ai_features_controller.rb | 64 ++++++-- .../ai_features_persona_serializer.rb | 12 ++ .../discourse/admin/adapters/ai-feature.js | 21 +++ .../discourse/admin/models/ai-feature.js | 24 +++ .../components/ai-feature-editor.gjs | 138 ++++++++++++++++++ assets/stylesheets/common/ai-features.scss | 25 ++++ config/locales/client.en.yml | 17 +++ plugin.rb | 1 + 12 files changed, 400 insertions(+), 45 deletions(-) create mode 100644 app/serializers/ai_features_persona_serializer.rb create mode 100644 assets/javascripts/discourse/admin/adapters/ai-feature.js create mode 100644 assets/javascripts/discourse/admin/models/ai-feature.js create mode 100644 assets/javascripts/discourse/components/ai-feature-editor.gjs create mode 100644 assets/stylesheets/common/ai-features.scss diff --git a/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js b/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js index 324b9cc87..ef2245f11 100644 --- a/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js +++ b/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js @@ -1,7 +1,12 @@ import DiscourseRoute from "discourse/routes/discourse"; export default class AdminPluginsShowDiscourseAiFeaturesEdit extends DiscourseRoute { - model() { - // todo + async model(params) { + const allFeatures = this.modelFor( + "adminPlugins.show.discourse-ai-features" + ); + const id = parseInt(params.id, 10); + + return allFeatures.find((feature) => feature.id === id); } } diff --git a/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features.js b/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features.js index 07476affa..72b178946 100644 --- a/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features.js +++ b/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features.js @@ -1,11 +1,10 @@ -import { ajax } from "discourse/lib/ajax"; +import { service } from "@ember/service"; import DiscourseRoute from "discourse/routes/discourse"; export default class AdminPluginsShowDiscourseAiFeatures extends DiscourseRoute { + @service store; + async model() { - const { ai_features } = await ajax( - `/admin/plugins/discourse-ai/ai-features.json` - ); - return ai_features; + return this.store.findAll("ai-feature"); } } diff --git a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs index 675c04973..da320a9a2 100644 --- a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs +++ b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs @@ -1,7 +1,8 @@ import RouteTemplate from "ember-route-template"; +import AiFeatureEditor from "discourse/plugins/discourse-ai/discourse/components/ai-feature-editor"; export default RouteTemplate( - + ); diff --git a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs index d2583a6f4..3a60d240e 100644 --- a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs +++ b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs @@ -1,6 +1,8 @@ import Component from "@glimmer/component"; +import { get } from "@ember/helper"; 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"; @@ -9,55 +11,123 @@ import { i18n } from "discourse-i18n"; export default RouteTemplate( class extends Component { @service adminPluginNavManager; + @service currentUser; + + get tableHeaders() { + const prefix = "discourse_ai.features.list.header"; + return [ + i18n(`${prefix}.name`), + i18n(`${prefix}.persona`), + i18n(`${prefix}.groups`), + "", + ]; + } } diff --git a/app/controllers/discourse_ai/admin/ai_features_controller.rb b/app/controllers/discourse_ai/admin/ai_features_controller.rb index 342b1f1e4..1bd4c8e41 100644 --- a/app/controllers/discourse_ai/admin/ai_features_controller.rb +++ b/app/controllers/discourse_ai/admin/ai_features_controller.rb @@ -10,6 +10,8 @@ def index end def edit + raise Discourse::InvalidParameters.new(:id) if params[:id].blank? + render json: find_feature_by_id(params[:id].to_i) end def update @@ -22,39 +24,79 @@ def destroy # Eventually we may move this to an active record model def persona_backed_features - # TODO: WIP just getting data rn, will cleanup AiPersona call later... [ { + id: 1, name: "Summaries", description: "Makes a summarization button available that allows visitors to summarize topics.", - # persona: AiPersona.find_by(id: SiteSetting.ai_summarization_persona), - persona: "Foo", + persona: + serialize_data( + AiPersona.find_by(id: SiteSetting.ai_summarization_persona), + AiFeaturesPersonaSerializer, + root: false, + ), enabled: SiteSetting.ai_summarization_enabled, + enable_setting: { + type: SiteSetting.ai_summarization_enabled.class, + value: "ai_summarization_enabled", + }, }, { + id: 2, name: "Short Summaries", description: "Adds the ability to view short summaries of topics on the topic list.", - # persona: AiPersona.find_by(id: SiteSetting.ai_summary_gists_persona), - persona: "Bar", + persona: + serialize_data( + AiPersona.find_by(id: SiteSetting.ai_summary_gists_persona), + AiFeaturesPersonaSerializer, + root: false, + ), enabled: SiteSetting.ai_summary_gists_enabled, + enable_setting: { + type: SiteSetting.ai_summary_gists_enabled.class, + value: "ai_summary_gists_enabled", + }, }, { + id: 3, name: "Discobot Discoveries", - description: "", - # persona: AiPersona.find_by(id: SiteSetting.ai_bot_discover_persona), - persona: "Baz", + description: "Enhances search experience by providing AI-generated answers to queries.", + persona: + serialize_data( + AiPersona.find_by(id: SiteSetting.ai_bot_discover_persona), + AiFeaturesPersonaSerializer, + root: false, + ), enabled: SiteSetting.ai_bot_enabled, + enable_setting: { + type: SiteSetting.ai_bot_enabled.class, + value: "ai_bot_enabled", + }, }, { + id: 4, name: "Discord Search", description: "Adds the ability to search Discord channels.", - # persona: AiPersona.find_by(id: SiteSetting.ai_discord_search_persona), - persona: "Qux", - enabled: "", + persona: + serialize_data( + AiPersona.find_by(id: SiteSetting.ai_discord_search_persona), + AiFeaturesPersonaSerializer, + root: false, + ), + enabled: SiteSetting.ai_discord_app_id.present?, + enable_setting: { + type: SiteSetting.ai_discord_app_id.class, + value: "ai_discord_app_id", + }, }, ] end + + def find_feature_by_id(id) + lookup = persona_backed_features.index_by { |feature| feature[:id] } + lookup[id] + end end end end diff --git a/app/serializers/ai_features_persona_serializer.rb b/app/serializers/ai_features_persona_serializer.rb new file mode 100644 index 000000000..782b56042 --- /dev/null +++ b/app/serializers/ai_features_persona_serializer.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class AiFeaturesPersonaSerializer < ApplicationSerializer + attributes :id, :name, :system_prompt, :allowed_groups, :enabled + + def allowed_groups + Group + .where(id: object.allowed_group_ids) + .pluck(:id, :name) + .map { |id, name| { id: id, name: name } } + end +end diff --git a/assets/javascripts/discourse/admin/adapters/ai-feature.js b/assets/javascripts/discourse/admin/adapters/ai-feature.js new file mode 100644 index 000000000..994523d12 --- /dev/null +++ b/assets/javascripts/discourse/admin/adapters/ai-feature.js @@ -0,0 +1,21 @@ +import RestAdapter from "discourse/adapters/rest"; + +export default class AiFeatureAdapter extends RestAdapter { + jsonMode = true; + + basePath() { + return "/admin/plugins/discourse-ai/"; + } + + pathFor(store, type, findArgs) { + // removes underscores which are implemented in base + let path = + this.basePath(store, type, findArgs) + + store.pluralize(this.apiNameFor(type)); + return this.appendQueryParams(path, findArgs); + } + + apiNameFor() { + return "ai-feature"; + } +} diff --git a/assets/javascripts/discourse/admin/models/ai-feature.js b/assets/javascripts/discourse/admin/models/ai-feature.js new file mode 100644 index 000000000..12b5720e2 --- /dev/null +++ b/assets/javascripts/discourse/admin/models/ai-feature.js @@ -0,0 +1,24 @@ +import { TrackedArray, TrackedObject } from "@ember-compat/tracked-built-ins"; +import RestModel from "discourse/models/rest"; + +export default class AiFeature extends RestModel { + createProperties() { + return this.getProperties( + "id", + "name", + "description", + "enabled", + "enable_setting", + "persona" + ); + } + + updateProperties() { + const attrs = this.createProperties(); + + // TODO: add the ones to update + // i.e. attrs.id = this.id; + + return attrs; + } +} diff --git a/assets/javascripts/discourse/components/ai-feature-editor.gjs b/assets/javascripts/discourse/components/ai-feature-editor.gjs new file mode 100644 index 000000000..733c9533a --- /dev/null +++ b/assets/javascripts/discourse/components/ai-feature-editor.gjs @@ -0,0 +1,138 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { service } from "@ember/service"; +import { htmlSafe } from "@ember/template"; +import { eq } from "truth-helpers"; +import BackButton from "discourse/components/back-button"; +import Form from "discourse/components/form"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import getURL from "discourse/lib/get-url"; +import { i18n } from "discourse-i18n"; +import { tracked } from "@glimmer/tracking"; + +export default class AiFeatureEditor extends Component { + @service toasts; + @service currentUser; + + @tracked isSaving = false; + + get formData() { + return { + enabled: this.args.model.enabled, + enable_setting: { + type: this.args.model.enable_setting?.type, + value: this.args.model.enable_setting?.value, + }, + persona: this.args.model.persona.id, + }; + } + + @action + async save(formData) { + this.isSaving = true; + + try { + console.log("Saving feature data", formData); + + // TODO(@keegan): add save logic (updates setting/personas) + + this.toasts.success({ + data: { + message: i18n("discourse_ai.features.editor.saved", { + feature_name: this.args.model.name, + }), + }, + duration: 2000, + }); + } catch (error) { + popupAjaxError(error); + } finally { + this.isSaving = false; + } + } + + get enableSettingType() { + if (this.args.model.enable_setting?.type === "String") { + return "text"; + } + + return "boolean"; + } + + get personasHint() { + return i18n("discourse_ai.features.editor.persona_help", { + config_url: getURL("/admin/plugins/discourse-ai/ai-personas"), + }); + } + + +} diff --git a/assets/stylesheets/common/ai-features.scss b/assets/stylesheets/common/ai-features.scss new file mode 100644 index 000000000..7925322b0 --- /dev/null +++ b/assets/stylesheets/common/ai-features.scss @@ -0,0 +1,25 @@ +.ai-feature-list { + &__configured-features { + margin-block: 2rem; + } + + &__row-item-name, + &__row-item-description { + display: block; + } + + &__row-item-groups { + list-style: none; + margin: 0.5em 0 0 0; + display: flex; + + li { + font-size: var(--font-down-2); + border-radius: 0.25em; + background: var(--primary-very-low); + border: 1px solid var(--primary-low); + padding: 1px 3px; + margin-right: 0.5em; + } + } +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 0c84cf88b..8227595a1 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -167,6 +167,23 @@ en: 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." + back: "Back" + list: + header: + name: "Name" + persona: "Persona" + groups: "Groups" + edit: "Edit" + set_up: "Set up" + configured_features: "Configured features" + unconfigured_features: "Unconfigured features" + editor: + enable_setting: "Enable" + enable_setting_help: "Toggles '%{setting}' setting" + persona: "Persona" + persona_help: "To create/edit personas go to the persona configuration page" + save: "Save" + saved: "%{feature_name} feature saved" modals: select_option: "Select an option..." diff --git a/plugin.rb b/plugin.rb index 77bdde7b3..552295717 100644 --- a/plugin.rb +++ b/plugin.rb @@ -27,6 +27,7 @@ register_asset "stylesheets/common/streaming.scss" register_asset "stylesheets/common/ai-blinking-animation.scss" register_asset "stylesheets/common/ai-user-settings.scss" +register_asset "stylesheets/common/ai-features.scss" register_asset "stylesheets/modules/ai-helper/common/ai-helper.scss" register_asset "stylesheets/modules/ai-helper/desktop/ai-helper-fk-modals.scss", :desktop From 43a56d239a19094cecab66278b68ff822cc7ddeb Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 4 Apr 2025 14:55:24 -0700 Subject: [PATCH 03/12] DEV: Various updates and save method --- .../show/discourse-ai-features/index.gjs | 85 ++++++++------ .../admin/ai_features_controller.rb | 107 +++++++++--------- app/jobs/regular/stream_discord_reply.rb | 2 + .../discourse/admin/models/ai-feature.js | 14 +-- .../components/ai-feature-editor.gjs | 57 ++++------ config/locales/server.en.yml | 14 +++ config/routes.rb | 2 +- config/settings.yml | 4 + lib/ai_bot/entry_point.rb | 5 +- 9 files changed, 148 insertions(+), 142 deletions(-) diff --git a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs index 3a60d240e..272d08ac7 100644 --- a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs +++ b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs @@ -1,5 +1,4 @@ import Component from "@glimmer/component"; -import { get } from "@ember/helper"; import { service } from "@ember/service"; import RouteTemplate from "ember-route-template"; import { gt } from "truth-helpers"; @@ -23,6 +22,18 @@ export default RouteTemplate( ]; } + 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 + ); + } + } diff --git a/app/controllers/discourse_ai/admin/ai_features_controller.rb b/app/controllers/discourse_ai/admin/ai_features_controller.rb index 1bd4c8e41..154db086a 100644 --- a/app/controllers/discourse_ai/admin/ai_features_controller.rb +++ b/app/controllers/discourse_ai/admin/ai_features_controller.rb @@ -15,82 +15,85 @@ def edit end def update - end + raise Discourse::InvalidParameters.new(:id) if params[:id].blank? + raise Discourse::InvalidParameters.new(:ai_feature) if params[:ai_feature].blank? + if params[:ai_feature][:persona_id].blank? + raise Discourse::InvalidParameters.new(:persona_id) + end + raise Discourse::InvalidParameters.new(:enabled) if params[:ai_feature][:enabled].nil? + + feature = find_feature_by_id(params[:id].to_i) + enable_value = params[:ai_feature][:enabled] + persona_id = params[:ai_feature][:persona_id] - def destroy + SiteSetting.set_and_log(feature[:enable_setting][:name], enable_value, guardian.user) + SiteSetting.set_and_log(feature[:persona_setting][:name], persona_id, guardian.user) + + render json: find_feature_by_id(params[:id].to_i) end private - # Eventually we may move this to an active record model - def persona_backed_features + # Eventually we may move this all to an active record model + # but for now we are just using a hash + # to store the features and their corresponding settings + def feature_config [ { id: 1, - name: "Summaries", - description: - "Makes a summarization button available that allows visitors to summarize topics.", - persona: - serialize_data( - AiPersona.find_by(id: SiteSetting.ai_summarization_persona), - AiFeaturesPersonaSerializer, - root: false, - ), - enabled: SiteSetting.ai_summarization_enabled, - enable_setting: { - type: SiteSetting.ai_summarization_enabled.class, - value: "ai_summarization_enabled", - }, + name_key: "discourse_ai.features.summarization.name", + description_key: "discourse_ai.features.summarization.description", + persona_setting_name: "ai_summarization_persona", + enable_setting_name: "ai_summarization_enabled", }, { id: 2, - name: "Short Summaries", - description: "Adds the ability to view short summaries of topics on the topic list.", - persona: - serialize_data( - AiPersona.find_by(id: SiteSetting.ai_summary_gists_persona), - AiFeaturesPersonaSerializer, - root: false, - ), - enabled: SiteSetting.ai_summary_gists_enabled, - enable_setting: { - type: SiteSetting.ai_summary_gists_enabled.class, - value: "ai_summary_gists_enabled", - }, + name_key: "discourse_ai.features.gists.name", + description_key: "discourse_ai.features.gists.description", + persona_setting_name: "ai_summary_gists_persona", + enable_setting_name: "ai_summary_gists_enabled", }, { id: 3, - name: "Discobot Discoveries", - description: "Enhances search experience by providing AI-generated answers to queries.", - persona: - serialize_data( - AiPersona.find_by(id: SiteSetting.ai_bot_discover_persona), - AiFeaturesPersonaSerializer, - root: false, - ), - enabled: SiteSetting.ai_bot_enabled, - enable_setting: { - type: SiteSetting.ai_bot_enabled.class, - value: "ai_bot_enabled", - }, + name_key: "discourse_ai.features.discoveries.name", + description_key: "discourse_ai.features.discoveries.description", + persona_setting_name: "ai_bot_discover_persona", + enable_setting_name: "ai_bot_enabled", }, { id: 4, - name: "Discord Search", - description: "Adds the ability to search Discord channels.", + name_key: "discourse_ai.features.discord_search.name", + description_key: "discourse_ai.features.discord_search.description", + persona_setting_name: "ai_discord_search_persona", + enable_setting_name: "ai_discord_search_enabled", + }, + ] + end + + def persona_backed_features + feature_config.map do |feature| + { + id: feature[:id], + name: I18n.t(feature[:name_key]), + description: I18n.t(feature[:description_key]), persona: serialize_data( - AiPersona.find_by(id: SiteSetting.ai_discord_search_persona), + AiPersona.find_by(id: SiteSetting.get(feature[:persona_setting_name])), AiFeaturesPersonaSerializer, root: false, ), - enabled: SiteSetting.ai_discord_app_id.present?, + persona_setting: { + name: feature[:persona_setting_name], + value: SiteSetting.get(feature[:persona_setting_name]), + type: SiteSetting.type_supervisor.get_type(feature[:persona_setting_name]), + }, enable_setting: { - type: SiteSetting.ai_discord_app_id.class, - value: "ai_discord_app_id", + name: feature[:enable_setting_name], + value: SiteSetting.get(feature[:enable_setting_name]), + type: SiteSetting.type_supervisor.get_type(feature[:enable_setting_name]), }, - }, - ] + } + end end def find_feature_by_id(id) diff --git a/app/jobs/regular/stream_discord_reply.rb b/app/jobs/regular/stream_discord_reply.rb index 9aca1ef58..7029a0808 100644 --- a/app/jobs/regular/stream_discord_reply.rb +++ b/app/jobs/regular/stream_discord_reply.rb @@ -7,6 +7,8 @@ class StreamDiscordReply < ::Jobs::Base def execute(args) interaction = args[:interaction] + return unless SiteSetting.ai_discord_search_enabled + if SiteSetting.ai_discord_search_mode == "persona" DiscourseAi::Discord::Bot::PersonaReplier.new(interaction).handle_interaction! else diff --git a/assets/javascripts/discourse/admin/models/ai-feature.js b/assets/javascripts/discourse/admin/models/ai-feature.js index 12b5720e2..67d9decad 100644 --- a/assets/javascripts/discourse/admin/models/ai-feature.js +++ b/assets/javascripts/discourse/admin/models/ai-feature.js @@ -1,4 +1,3 @@ -import { TrackedArray, TrackedObject } from "@ember-compat/tracked-built-ins"; import RestModel from "discourse/models/rest"; export default class AiFeature extends RestModel { @@ -7,18 +6,9 @@ export default class AiFeature extends RestModel { "id", "name", "description", - "enabled", "enable_setting", - "persona" + "persona", + "persona_setting" ); } - - updateProperties() { - const attrs = this.createProperties(); - - // TODO: add the ones to update - // i.e. attrs.id = this.id; - - return attrs; - } } diff --git a/assets/javascripts/discourse/components/ai-feature-editor.gjs b/assets/javascripts/discourse/components/ai-feature-editor.gjs index 733c9533a..4d51fff37 100644 --- a/assets/javascripts/discourse/components/ai-feature-editor.gjs +++ b/assets/javascripts/discourse/components/ai-feature-editor.gjs @@ -1,4 +1,5 @@ import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; import { action } from "@ember/object"; import { service } from "@ember/service"; import { htmlSafe } from "@ember/template"; @@ -7,23 +8,20 @@ import BackButton from "discourse/components/back-button"; import Form from "discourse/components/form"; import { popupAjaxError } from "discourse/lib/ajax-error"; import getURL from "discourse/lib/get-url"; +import discourseLater from "discourse/lib/later"; import { i18n } from "discourse-i18n"; -import { tracked } from "@glimmer/tracking"; export default class AiFeatureEditor extends Component { @service toasts; @service currentUser; + @service router; @tracked isSaving = false; get formData() { return { - enabled: this.args.model.enabled, - enable_setting: { - type: this.args.model.enable_setting?.type, - value: this.args.model.enable_setting?.value, - }, - persona: this.args.model.persona.id, + enabled: this.args.model.enable_setting?.value, + persona_id: this.args.model.persona?.id, }; } @@ -32,9 +30,10 @@ export default class AiFeatureEditor extends Component { this.isSaving = true; try { - console.log("Saving feature data", formData); - - // TODO(@keegan): add save logic (updates setting/personas) + this.args.model.save({ + enabled: formData.enabled, + persona_id: parseInt(formData.persona_id, 10), + }); this.toasts.success({ data: { @@ -44,6 +43,12 @@ export default class AiFeatureEditor extends Component { }, duration: 2000, }); + + discourseLater(() => { + this.router.transitionTo( + "adminPlugins.show.discourse-ai-features.index" + ); + }, 500); } catch (error) { popupAjaxError(error); } finally { @@ -51,14 +56,6 @@ export default class AiFeatureEditor extends Component { } } - get enableSettingType() { - if (this.args.model.enable_setting?.type === "String") { - return "text"; - } - - return "boolean"; - } - get personasHint() { return i18n("discourse_ai.features.editor.persona_help", { config_url: getURL("/admin/plugins/discourse-ai/ai-personas"), @@ -70,10 +67,6 @@ export default class AiFeatureEditor extends Component { @route="adminPlugins.show.discourse-ai-features" @label="discourse_ai.features.back" /> - - {{log @model}} - {{log this.currentUser}} -

{{@model.name}}

{{@model.description}}

@@ -83,26 +76,17 @@ export default class AiFeatureEditor extends Component { @onSubmit={{this.save}} @data={{this.formData}} class="form-horizontal ai-feature-editor" - as |form data| + as |form| > - {{log data}} - {{#if (eq this.enableSettingType "text")}} - - - - {{else if (eq this.enableSettingType "boolean")}} - {{log data.enable_setting.value}} + {{#if (eq @model.enable_setting.type "bool")}} @@ -110,8 +94,9 @@ export default class AiFeatureEditor extends Component { {{/if}} + {{log this.currentUser}} do - SiteSetting.ai_bot_enabled && scope.authenticated? && - scope.user.in_any_groups?(SiteSetting.ai_bot_allowed_groups_map) - end, + include_condition: -> { scope.authenticated? }, ) do DiscourseAi::Personas::Persona .all(user: scope.user) From 117918a60dd399236afe7d3dedb3f9994760a6b4 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 4 Apr 2025 14:56:03 -0700 Subject: [PATCH 04/12] DEV: Remove log --- assets/javascripts/discourse/components/ai-feature-editor.gjs | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/javascripts/discourse/components/ai-feature-editor.gjs b/assets/javascripts/discourse/components/ai-feature-editor.gjs index 4d51fff37..dbcb89fd4 100644 --- a/assets/javascripts/discourse/components/ai-feature-editor.gjs +++ b/assets/javascripts/discourse/components/ai-feature-editor.gjs @@ -94,7 +94,6 @@ export default class AiFeatureEditor extends Component { {{/if}} - {{log this.currentUser}} Date: Fri, 4 Apr 2025 15:08:16 -0700 Subject: [PATCH 05/12] DEV: Fix, lint, and make persona a link --- .../admin-plugins/show/discourse-ai-features/edit.gjs | 6 +++--- .../admin-plugins/show/discourse-ai-features/index.gjs | 7 ++++++- assets/stylesheets/common/ai-features.scss | 6 +++++- spec/jobs/regular/stream_discord_reply_spec.rb | 1 + 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs index da320a9a2..74d1362c7 100644 --- a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs +++ b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs @@ -2,7 +2,7 @@ import RouteTemplate from "ember-route-template"; import AiFeatureEditor from "discourse/plugins/discourse-ai/discourse/components/ai-feature-editor"; export default RouteTemplate( - + ); diff --git a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs index 272d08ac7..624f4f0a8 100644 --- a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs +++ b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs @@ -73,7 +73,12 @@ export default RouteTemplate( - {{feature.persona.name}} + {{#if (gt feature.persona.allowed_groups.length 0)}} diff --git a/assets/stylesheets/common/ai-features.scss b/assets/stylesheets/common/ai-features.scss index 7925322b0..3cbdb3f61 100644 --- a/assets/stylesheets/common/ai-features.scss +++ b/assets/stylesheets/common/ai-features.scss @@ -8,6 +8,10 @@ display: block; } + &__row-item-persona { + padding: 0; + } + &__row-item-groups { list-style: none; margin: 0.5em 0 0 0; @@ -15,7 +19,7 @@ li { font-size: var(--font-down-2); - border-radius: 0.25em; + border-radius: var(--d-border-radius); background: var(--primary-very-low); border: 1px solid var(--primary-low); padding: 1px 3px; diff --git a/spec/jobs/regular/stream_discord_reply_spec.rb b/spec/jobs/regular/stream_discord_reply_spec.rb index c91f1e38a..1543bb3f7 100644 --- a/spec/jobs/regular/stream_discord_reply_spec.rb +++ b/spec/jobs/regular/stream_discord_reply_spec.rb @@ -17,6 +17,7 @@ fab!(:persona) { Fabricate(:ai_persona, default_llm_id: llm_model.id) } before do + SiteSetting.ai_discord_search_enabled = true SiteSetting.ai_discord_search_mode = "persona" SiteSetting.ai_discord_search_persona = persona.id end From 8ff0d935750c8f8ea80b7fd2dc7425914207d0d8 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 4 Apr 2025 15:17:11 -0700 Subject: [PATCH 06/12] =?UTF-8?q?=F0=9F=92=84=20Make=20it=20pretty?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin-plugins/show/discourse-ai-features/edit.gjs | 4 +--- .../admin-plugins/show/discourse-ai-features/index.gjs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs index 74d1362c7..78f026558 100644 --- a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs +++ b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs @@ -2,7 +2,5 @@ import RouteTemplate from "ember-route-template"; import AiFeatureEditor from "discourse/plugins/discourse-ai/discourse/components/ai-feature-editor"; export default RouteTemplate( - + ); diff --git a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs index 624f4f0a8..7e1db4f11 100644 --- a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs +++ b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs @@ -92,7 +92,7 @@ export default RouteTemplate( From df4bff00ea142b97b9281b5b1c19590c48d3d387 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 4 Apr 2025 15:25:06 -0700 Subject: [PATCH 07/12] DEV: Improve --- assets/stylesheets/common/ai-features.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/stylesheets/common/ai-features.scss b/assets/stylesheets/common/ai-features.scss index 3cbdb3f61..5e1b01602 100644 --- a/assets/stylesheets/common/ai-features.scss +++ b/assets/stylesheets/common/ai-features.scss @@ -10,6 +10,9 @@ &__row-item-persona { padding: 0; + text-align: left; + + @include ellipsis; } &__row-item-groups { From 556fe2fd9e182d0acee3b23f0d63c06a90785d3a Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 4 Apr 2025 15:31:09 -0700 Subject: [PATCH 08/12] DEV: Hide settings to enable features in regular settings --- config/settings.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/settings.yml b/config/settings.yml index d4d14210b..fab3b641d 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -234,6 +234,7 @@ discourse_ai: default: false client: true validator: "DiscourseAi::Configuration::LlmDependencyValidator" + hidden: true ai_summarization_model: default: "" allow_any: false @@ -245,7 +246,7 @@ discourse_ai: default: "-11" type: enum enum: "DiscourseAi::Configuration::PersonaEnumerator" - + hidden: true ai_pm_summarization_allowed_groups: type: group_list list_type: compact @@ -361,6 +362,7 @@ discourse_ai: default: "" type: enum enum: "DiscourseAi::Configuration::PersonaEnumerator" + hidden: true ai_discord_allowed_guilds: type: list list_type: compact From 9bd915d901196e1875877fdd76345cffb1775870 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Mon, 7 Apr 2025 09:40:09 -0700 Subject: [PATCH 09/12] DEV: Add specs --- .../show/discourse-ai-features/index.gjs | 17 +++-- .../admin/ai_features_controller_spec.rb | 55 +++++++++++++++ .../ai_features_persona_serializer_spec.rb | 33 +++++++++ spec/system/admin_ai_features_spec.rb | 69 +++++++++++++++++++ .../page_objects/pages/admin_ai_features.rb | 51 ++++++++++++++ 5 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 spec/requests/admin/ai_features_controller_spec.rb create mode 100644 spec/serializers/ai_features_persona_serializer_spec.rb create mode 100644 spec/system/admin_ai_features_spec.rb create mode 100644 spec/system/page_objects/pages/admin_ai_features.rb diff --git a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs index 7e1db4f11..29f1bccac 100644 --- a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs +++ b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs @@ -61,7 +61,10 @@ export default RouteTemplate( {{#each this.configuredFeatures as |feature|}} - + @@ -72,7 +75,9 @@ export default RouteTemplate( {{feature.description}} - + - + {{#if (gt feature.persona.allowed_groups.length 0)}}
    {{#each feature.persona.allowed_groups as |group|}} @@ -91,7 +98,7 @@ export default RouteTemplate( +

    {{i18n "discourse_ai.features.list.unconfigured_features"}}

    diff --git a/spec/requests/admin/ai_features_controller_spec.rb b/spec/requests/admin/ai_features_controller_spec.rb new file mode 100644 index 000000000..4e518cefc --- /dev/null +++ b/spec/requests/admin/ai_features_controller_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +RSpec.describe DiscourseAi::Admin::AiFeaturesController do + let(:controller) { described_class.new } + fab!(:admin) + fab!(:group) + fab!(:llm_model) + fab!(:summarizer_persona) { Fabricate(:ai_persona) } + fab!(:alternate_summarizer_persona) { Fabricate(:ai_persona) } + + before do + sign_in(admin) + SiteSetting.ai_bot_enabled = true + SiteSetting.discourse_ai_enabled = true + end + + describe "#index" do + it "lists all features backed by personas" do + get "/admin/plugins/discourse-ai/ai-features.json" + + expect(response.status).to eq(200) + expect(response.parsed_body["ai_features"].count).to eq(4) + end + end + + describe "#edit" do + it "returns a success response" do + get "/admin/plugins/discourse-ai/ai-features/1/edit.json" + expect(response.parsed_body["name"]).to eq(I18n.t "discourse_ai.features.summarization.name") + end + end + + describe "#update" do + before do + SiteSetting.ai_summarization_persona = summarizer_persona.id + SiteSetting.ai_summarization_enabled = true + end + + it "updates the feature" do + expect(SiteSetting.ai_summarization_persona).to eq(summarizer_persona.id.to_s) + expect(SiteSetting.ai_summarization_enabled).to eq(true) + + put "/admin/plugins/discourse-ai/ai-features/1.json", + params: { + ai_feature: { + enabled: false, + persona_id: alternate_summarizer_persona.id, + }, + } + + expect(SiteSetting.ai_summarization_persona).to eq(alternate_summarizer_persona.id.to_s) + expect(SiteSetting.ai_summarization_enabled).to eq(false) + end + end +end diff --git a/spec/serializers/ai_features_persona_serializer_spec.rb b/spec/serializers/ai_features_persona_serializer_spec.rb new file mode 100644 index 000000000..677e2743c --- /dev/null +++ b/spec/serializers/ai_features_persona_serializer_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +RSpec.describe AiFeaturesPersonaSerializer do + fab!(:admin) + fab!(:ai_persona) + fab!(:group) + fab!(:group_2) { Fabricate(:group) } + + describe "serialized attributes" do + before do + ai_persona.allowed_group_ids = [group.id, group_2.id] + ai_persona.save! + end + + context "when there is a persona with allowed groups" do + let(:allowed_groups) do + Group + .where(id: ai_persona.allowed_group_ids) + .pluck(:id, :name) + .map { |id, name| { id: id, name: name } } + end + + it "display every participant" do + serialized = described_class.new(ai_persona, scope: Guardian.new(admin), root: nil) + expect(serialized.id).to eq(ai_persona.id) + expect(serialized.name).to eq(ai_persona.name) + expect(serialized.system_prompt).to eq(ai_persona.system_prompt) + expect(serialized.allowed_groups).to eq(allowed_groups) + expect(serialized.enabled).to eq(ai_persona.enabled) + end + end + end +end diff --git a/spec/system/admin_ai_features_spec.rb b/spec/system/admin_ai_features_spec.rb new file mode 100644 index 000000000..92d9266d1 --- /dev/null +++ b/spec/system/admin_ai_features_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +RSpec.describe "Admin AI features configuration", type: :system, js: true do + fab!(:admin) + fab!(:llm_model) + fab!(:summarization_persona) { Fabricate(:ai_persona) } + fab!(:group_1) { Fabricate(:group) } + fab!(:group_2) { Fabricate(:group) } + let(:page_header) { PageObjects::Components::DPageHeader.new } + let(:form) { PageObjects::Components::FormKit.new("form") } + let(:ai_features_page) { PageObjects::Pages::AdminAiFeatures.new } + + before do + summarization_persona.allowed_group_ids = [group_1.id, group_2.id] + summarization_persona.save! + assign_fake_provider_to(:ai_summarization_model) + SiteSetting.ai_summarization_enabled = true + SiteSetting.ai_summarization_persona = summarization_persona.id + sign_in(admin) + end + + it "lists all persona backed AI features separated by configured/unconfigured" do + ai_features_page.visit + expect( + 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_unconfigured_feature_items(3) + end + + it "lists the persona used for the corresponding AI feature" do + ai_features_page.visit + expect(ai_features_page).to have_feature_persona(summarization_persona.name) + end + + it "lists the groups allowed to use the AI feature" do + 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 + visit "/admin/plugins/discourse-ai/ai-features" + expect(page).to have_css(".d-breadcrumbs") + 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 + + it "can edit the AI feature" do + ai_features_page.visit + ai_features_page.click_edit_feature(I18n.t("discourse_ai.features.summarization.name")) + 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"), + ) + + form.field("persona_id").select(-6) + form.submit + expect(page).to have_current_path("/admin/plugins/discourse-ai/ai-features") + expect(ai_features_page).to have_feature_persona( + I18n.t("discourse_ai.ai_bot.personas.creative.name"), + ) + end +end diff --git a/spec/system/page_objects/pages/admin_ai_features.rb b/spec/system/page_objects/pages/admin_ai_features.rb new file mode 100644 index 000000000..e6ad9efd9 --- /dev/null +++ b/spec/system/page_objects/pages/admin_ai_features.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module PageObjects + module Pages + class AdminAiFeatures < PageObjects::Pages::Base + CONFIGURED_FEATURES_TABLE = ".ai-feature-list__configured-features .d-admin-table" + UNCONFIGURED_FEATURES_TABLE = ".ai-feature-list__unconfigured-features .d-admin-table" + + def visit + page.visit("/admin/plugins/discourse-ai/ai-features") + self + end + + def configured_features_table + page.find(CONFIGURED_FEATURES_TABLE) + end + + def unconfigured_features_table + page.find(UNCONFIGURED_FEATURES_TABLE) + end + + def has_configured_feature_items?(count) + page.has_css?("#{CONFIGURED_FEATURES_TABLE} .ai-feature-list__row", count: count) + end + + def has_unconfigured_feature_items?(count) + page.has_css?("#{UNCONFIGURED_FEATURES_TABLE} .ai-feature-list__row", count: count) + end + + def has_feature_persona?(name) + page.has_css?( + "#{CONFIGURED_FEATURES_TABLE} .ai-feature-list__persona .d-button-label ", + text: name, + ) + end + + def has_feature_groups?(groups) + listed_groups = page.find("#{CONFIGURED_FEATURES_TABLE} .ai-feature-list__groups") + list_items = listed_groups.all("li", visible: true).map(&:text) + + list_items.sort == groups.sort + end + + def click_edit_feature(feature_name) + page.find( + "#{CONFIGURED_FEATURES_TABLE} .ai-feature-list__row[data-feature-name='#{feature_name}'] .edit", + ).click + end + end + end +end From 0963a6aa4b8be9b0fbd305526b2dbc89e4f59b29 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Mon, 7 Apr 2025 09:58:39 -0700 Subject: [PATCH 10/12] DEV: Hide features tab for now --- .../initializers/admin-plugin-configuration-nav.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/assets/javascripts/initializers/admin-plugin-configuration-nav.js b/assets/javascripts/initializers/admin-plugin-configuration-nav.js index d9ca17a03..391f5cbc7 100644 --- a/assets/javascripts/initializers/admin-plugin-configuration-nav.js +++ b/assets/javascripts/initializers/admin-plugin-configuration-nav.js @@ -41,11 +41,12 @@ export default { route: "adminPlugins.show.discourse-ai-spam", description: "discourse_ai.spam.spam_description", }, - { - label: "discourse_ai.features.short_title", - route: "adminPlugins.show.discourse-ai-features", - description: "discourse_ai.features.description", - }, + // TODO(@keegan / @roman): Uncomment this when structured output is merged + // { + // label: "discourse_ai.features.short_title", + // route: "adminPlugins.show.discourse-ai-features", + // description: "discourse_ai.features.description", + // }, ]); }); }, From 19f6c6c0f99b4b0f3a9d28229cb8e4c3d9bcbcea Mon Sep 17 00:00:00 2001 From: Keegan George Date: Wed, 9 Apr 2025 09:51:57 -0700 Subject: [PATCH 11/12] FEATURE: Use `area` key from settings to show related settings by feature (#1248) This update makes use of the `area` key in `settings.yml` to show all settings related to particular features inside their feature tab editor. This allows admins to easily configure features and parse through a variety of different settings. --- ...plugins-show-discourse-ai-features-edit.js | 17 ++- .../admin/ai_features_controller.rb | 91 ++------------- .../discourse/admin/models/ai-feature.js | 1 + .../components/ai-feature-editor.gjs | 109 ++---------------- assets/stylesheets/common/ai-features.scss | 33 ++++++ config/locales/client.en.yml | 1 + config/locales/server.en.yml | 14 ++- config/routes.rb | 2 +- config/settings.yml | 24 ++-- lib/features.rb | 84 ++++++++++++++ plugin.rb | 5 + .../admin/ai_features_controller_spec.rb | 23 ---- spec/system/admin_ai_features_spec.rb | 9 +- 13 files changed, 189 insertions(+), 224 deletions(-) create mode 100644 lib/features.rb diff --git a/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js b/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js index ef2245f11..82e90e74e 100644 --- a/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js +++ b/admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-features-edit.js @@ -1,4 +1,6 @@ +import { ajax } from "discourse/lib/ajax"; import DiscourseRoute from "discourse/routes/discourse"; +import SiteSetting from "admin/models/site-setting"; export default class AdminPluginsShowDiscourseAiFeaturesEdit extends DiscourseRoute { async model(params) { @@ -6,7 +8,20 @@ export default class AdminPluginsShowDiscourseAiFeaturesEdit extends DiscourseRo "adminPlugins.show.discourse-ai-features" ); const id = parseInt(params.id, 10); + const currentFeature = allFeatures.find((feature) => feature.id === id); - return allFeatures.find((feature) => feature.id === id); + const { site_settings } = await ajax("/admin/config/site_settings.json", { + data: { + filter_area: `ai-features/${currentFeature.ref}`, + plugin: "discourse-ai", + category: "discourse_ai", + }, + }); + + currentFeature.feature_settings = site_settings.map((setting) => + SiteSetting.create(setting) + ); + + return currentFeature; } } diff --git a/app/controllers/discourse_ai/admin/ai_features_controller.rb b/app/controllers/discourse_ai/admin/ai_features_controller.rb index 154db086a..ad258c6c5 100644 --- a/app/controllers/discourse_ai/admin/ai_features_controller.rb +++ b/app/controllers/discourse_ai/admin/ai_features_controller.rb @@ -6,99 +6,30 @@ class AiFeaturesController < ::Admin::AdminController requires_plugin ::DiscourseAi::PLUGIN_NAME def index - render json: persona_backed_features + render json: serialize_features(DiscourseAi::Features.features) end def edit raise Discourse::InvalidParameters.new(:id) if params[:id].blank? - render json: find_feature_by_id(params[:id].to_i) + render json: serialize_feature(DiscourseAi::Features.find_feature_by_id(params[:id].to_i)) end - def update - raise Discourse::InvalidParameters.new(:id) if params[:id].blank? - raise Discourse::InvalidParameters.new(:ai_feature) if params[:ai_feature].blank? - if params[:ai_feature][:persona_id].blank? - raise Discourse::InvalidParameters.new(:persona_id) - end - raise Discourse::InvalidParameters.new(:enabled) if params[:ai_feature][:enabled].nil? - - feature = find_feature_by_id(params[:id].to_i) - enable_value = params[:ai_feature][:enabled] - persona_id = params[:ai_feature][:persona_id] - - SiteSetting.set_and_log(feature[:enable_setting][:name], enable_value, guardian.user) - SiteSetting.set_and_log(feature[:persona_setting][:name], persona_id, guardian.user) + private - render json: find_feature_by_id(params[:id].to_i) + def serialize_features(features) + features.map { |feature| feature.merge(persona: serialize_persona(feature[:persona])) } end - private + def serialize_feature(feature) + return nil if feature.blank? - # Eventually we may move this all to an active record model - # but for now we are just using a hash - # to store the features and their corresponding settings - def feature_config - [ - { - id: 1, - name_key: "discourse_ai.features.summarization.name", - description_key: "discourse_ai.features.summarization.description", - persona_setting_name: "ai_summarization_persona", - enable_setting_name: "ai_summarization_enabled", - }, - { - id: 2, - name_key: "discourse_ai.features.gists.name", - description_key: "discourse_ai.features.gists.description", - persona_setting_name: "ai_summary_gists_persona", - enable_setting_name: "ai_summary_gists_enabled", - }, - { - id: 3, - name_key: "discourse_ai.features.discoveries.name", - description_key: "discourse_ai.features.discoveries.description", - persona_setting_name: "ai_bot_discover_persona", - enable_setting_name: "ai_bot_enabled", - }, - { - id: 4, - name_key: "discourse_ai.features.discord_search.name", - description_key: "discourse_ai.features.discord_search.description", - persona_setting_name: "ai_discord_search_persona", - enable_setting_name: "ai_discord_search_enabled", - }, - ] + feature.merge(persona: serialize_persona(feature[:persona])) end - def persona_backed_features - feature_config.map do |feature| - { - id: feature[:id], - name: I18n.t(feature[:name_key]), - description: I18n.t(feature[:description_key]), - persona: - serialize_data( - AiPersona.find_by(id: SiteSetting.get(feature[:persona_setting_name])), - AiFeaturesPersonaSerializer, - root: false, - ), - persona_setting: { - name: feature[:persona_setting_name], - value: SiteSetting.get(feature[:persona_setting_name]), - 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 + def serialize_persona(persona) + return nil if persona.blank? - def find_feature_by_id(id) - lookup = persona_backed_features.index_by { |feature| feature[:id] } - lookup[id] + serialize_data(persona, AiFeaturesPersonaSerializer, root: false) end end end diff --git a/assets/javascripts/discourse/admin/models/ai-feature.js b/assets/javascripts/discourse/admin/models/ai-feature.js index 67d9decad..85dfa7ca9 100644 --- a/assets/javascripts/discourse/admin/models/ai-feature.js +++ b/assets/javascripts/discourse/admin/models/ai-feature.js @@ -5,6 +5,7 @@ export default class AiFeature extends RestModel { return this.getProperties( "id", "name", + "ref", "description", "enable_setting", "persona", diff --git a/assets/javascripts/discourse/components/ai-feature-editor.gjs b/assets/javascripts/discourse/components/ai-feature-editor.gjs index dbcb89fd4..6199fe39d 100644 --- a/assets/javascripts/discourse/components/ai-feature-editor.gjs +++ b/assets/javascripts/discourse/components/ai-feature-editor.gjs @@ -1,67 +1,13 @@ import Component from "@glimmer/component"; -import { tracked } from "@glimmer/tracking"; -import { action } from "@ember/object"; import { service } from "@ember/service"; -import { htmlSafe } from "@ember/template"; -import { eq } from "truth-helpers"; import BackButton from "discourse/components/back-button"; -import Form from "discourse/components/form"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import getURL from "discourse/lib/get-url"; -import discourseLater from "discourse/lib/later"; -import { i18n } from "discourse-i18n"; +import SiteSettingComponent from "admin/components/site-setting"; export default class AiFeatureEditor extends Component { @service toasts; @service currentUser; @service router; - @tracked isSaving = false; - - get formData() { - return { - enabled: this.args.model.enable_setting?.value, - persona_id: this.args.model.persona?.id, - }; - } - - @action - async save(formData) { - this.isSaving = true; - - try { - this.args.model.save({ - enabled: formData.enabled, - persona_id: parseInt(formData.persona_id, 10), - }); - - this.toasts.success({ - data: { - message: i18n("discourse_ai.features.editor.saved", { - feature_name: this.args.model.name, - }), - }, - duration: 2000, - }); - - discourseLater(() => { - this.router.transitionTo( - "adminPlugins.show.discourse-ai-features.index" - ); - }, 500); - } catch (error) { - popupAjaxError(error); - } finally { - this.isSaving = false; - } - } - - get personasHint() { - return i18n("discourse_ai.features.editor.persona_help", { - config_url: getURL("/admin/plugins/discourse-ai/ai-personas"), - }); - } - } diff --git a/assets/stylesheets/common/ai-features.scss b/assets/stylesheets/common/ai-features.scss index 5e1b01602..2ae68c8b5 100644 --- a/assets/stylesheets/common/ai-features.scss +++ b/assets/stylesheets/common/ai-features.scss @@ -30,3 +30,36 @@ } } } + +.ai-feature-editor { + &__header { + border-bottom: 1px solid var(--primary-low); + } + + .setting { + margin-block: 1.5rem; + } + + .setting-label { + font-size: var(--font-down-1-rem); + color: var(--primary-high); + + a[title="View change history"], + .history-icon { + display: none; + } + } + + .setting-value { + .desc { + font-size: var(--font-down-1-rem); + color: var(--primary-high-or-secondary-low); + } + } + + .setting-controls, + .setting-controls__undo { + font-size: var(--font-down-1-rem); + margin-top: 0.5rem; + } +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 8227595a1..1b3da23e6 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -182,6 +182,7 @@ en: enable_setting_help: "Toggles '%{setting}' setting" persona: "Persona" persona_help: "To create/edit personas go to the persona configuration page" + advanced_settings: "Advanced settings" save: "Save" saved: "%{feature_name} feature saved" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 1b06c5f54..8a74b3505 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -81,11 +81,12 @@ en: ai_embeddings_semantic_search_hyde_model: "Model used to expand keywords to get better results during a semantic search" ai_embeddings_per_post_enabled: Generate embeddings for each post - ai_summarization_enabled: "Enable the topic summarization module." - ai_summarization_model: "Model to use for summarization." + ai_summarization_enabled: "Enable the summarize feature" + ai_summarization_model: "Model to use for summarization" + ai_summarization_persona: "Persona to use for summarize feature" ai_custom_summarization_allowed_groups: "Groups allowed to use create new summaries." ai_pm_summarization_allowed_groups: "Groups allowed to create and view summaries in PMs." - ai_summary_gists_enabled: "Generate brief summaries of latest replies in topics automatically." + ai_summary_gists_enabled: "Generate brief summaries of latest replies in topics automatically" ai_summary_gists_allowed_groups: "Groups allowed to see gists in the hot topics list." ai_summary_backfill_maximum_topics_per_hour: "Number of topic summaries to backfill per hour." @@ -104,6 +105,13 @@ en: ai_google_custom_search_api_key: "API key for the Google Custom Search API see: https://developers.google.com/custom-search" ai_google_custom_search_cx: "CX for Google Custom Search API" + ai_discord_search_enabled: "Enables the Discord search feature" + ai_discord_app_id: "The ID of the Discord application you would like to connect Discord search to" + ai_discord_app_public_key: "The public key of the Discord application you would like to connect Discord search to" + ai_discord_search_mode: "Select the search mode to use for Discord search" + ai_discord_search_persona: "The persona to use for Discord search." + ai_discord_allowed_guilds: "Discord guilds (servers) where the bot is allowed to search" + reviewables: reasons: flagged_by_toxicity: The AI plugin flagged this after classifying it as toxic. diff --git a/config/routes.rb b/config/routes.rb index 2a2d3a16f..c5df0fc7d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -112,7 +112,7 @@ end resources :ai_features, - only: %i[index edit update], + only: %i[index edit], path: "ai-features", controller: "discourse_ai/admin/ai_features" end diff --git a/config/settings.yml b/config/settings.yml index fab3b641d..c8d9fb8dd 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -234,7 +234,7 @@ discourse_ai: default: false client: true validator: "DiscourseAi::Configuration::LlmDependencyValidator" - hidden: true + area: "ai-features/summarization" ai_summarization_model: default: "" allow_any: false @@ -246,11 +246,12 @@ discourse_ai: default: "-11" type: enum enum: "DiscourseAi::Configuration::PersonaEnumerator" - hidden: true + area: "ai-features/summarization" ai_pm_summarization_allowed_groups: type: group_list list_type: compact default: "" + area: "ai-features/summarization" ai_custom_summarization_allowed_groups: # Deprecated. TODO(roman): Remove 2025-09-01 type: group_list list_type: compact @@ -258,12 +259,12 @@ discourse_ai: hidden: true ai_summary_gists_enabled: default: false - hidden: true + area: "ai-features/gists" ai_summary_gists_persona: default: "-12" type: enum enum: "DiscourseAi::Configuration::PersonaEnumerator" - hidden: true + area: "ai-features/gists" ai_summary_gists_allowed_groups: # Deprecated. TODO(roman): Remove 2025-09-01 type: group_list list_type: compact @@ -278,17 +279,20 @@ discourse_ai: default: 30 min: 1 max: 10000 + area: "ai-features/summarization" ai_summary_backfill_maximum_topics_per_hour: default: 0 min: 0 max: 10000 + area: "ai-features/summarization" ai_summary_backfill_minimum_word_count: default: 200 - hidden: true + area: "ai-features/summarization" ai_bot_enabled: default: false client: true + area: "ai-features/discoveries" ai_bot_enable_chat_warning: default: false client: true @@ -327,9 +331,9 @@ discourse_ai: ai_bot_discover_persona: default: "" type: enum - hidden: true client: true enum: "DiscourseAi::Configuration::PersonaEnumerator" + area: "ai-features/discoveries" ai_automation_max_triage_per_minute: default: 60 hidden: true @@ -345,28 +349,32 @@ discourse_ai: ai_discord_search_enabled: default: false client: true - hidden: true + area: "ai-features/discord_search" ai_discord_app_id: default: "" client: false + area: "ai-features/discord_search" ai_discord_app_public_key: default: "" client: false + area: "ai-features/discord_search" ai_discord_search_mode: default: "search" type: enum choices: - search - persona + area: "ai-features/discord_search" ai_discord_search_persona: default: "" type: enum enum: "DiscourseAi::Configuration::PersonaEnumerator" - hidden: true + area: "ai-features/discord_search" ai_discord_allowed_guilds: type: list list_type: compact default: "" + area: "ai-features/discord_search" ai_spam_detection_enabled: default: false diff --git a/lib/features.rb b/lib/features.rb new file mode 100644 index 000000000..d3b999c25 --- /dev/null +++ b/lib/features.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module DiscourseAi + module Features + def self.feature_config + [ + { + id: 1, + name_ref: "summarization", + name_key: "discourse_ai.features.summarization.name", + description_key: "discourse_ai.features.summarization.description", + persona_setting_name: "ai_summarization_persona", + enable_setting_name: "ai_summarization_enabled", + }, + { + id: 2, + name_ref: "gists", + name_key: "discourse_ai.features.gists.name", + description_key: "discourse_ai.features.gists.description", + persona_setting_name: "ai_summary_gists_persona", + enable_setting_name: "ai_summary_gists_enabled", + }, + { + id: 3, + name_ref: "discoveries", + name_key: "discourse_ai.features.discoveries.name", + description_key: "discourse_ai.features.discoveries.description", + persona_setting_name: "ai_bot_discover_persona", + enable_setting_name: "ai_bot_enabled", + }, + { + id: 4, + name_ref: "discord_search", + name_key: "discourse_ai.features.discord_search.name", + description_key: "discourse_ai.features.discord_search.description", + persona_setting_name: "ai_discord_search_persona", + enable_setting_name: "ai_discord_search_enabled", + }, + ] + end + + def self.features + feature_config.map do |feature| + { + id: feature[:id], + ref: feature[:name_ref], + name: I18n.t(feature[:name_key]), + description: I18n.t(feature[:description_key]), + persona: AiPersona.find_by(id: SiteSetting.get(feature[:persona_setting_name])), + persona_setting: { + name: feature[:persona_setting_name], + value: SiteSetting.get(feature[:persona_setting_name]), + 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 + + def self.find_feature_by_id(id) + lookup = features.index_by { |f| f[:id] } + lookup[id] + end + + def self.find_feature_by_ref(name_ref) + lookup = features.index_by { |f| f[:ref] } + lookup[name_ref] + end + + def self.find_feature_id_by_ref(name_ref) + find_feature_by_ref(name_ref)&.dig(:id) + end + + def self.feature_area(name_ref) + name_ref = name_ref.to_s if name_ref.is_a?(Symbol) + find_feature_by_ref(name_ref) || raise(ArgumentError, "Feature not found: #{name_ref}") + "ai-features/#{name_ref}" + end + end +end diff --git a/plugin.rb b/plugin.rb index 552295717..4e2741993 100644 --- a/plugin.rb +++ b/plugin.rb @@ -70,6 +70,11 @@ def self.public_asset_path(name) Rails.autoloaders.main.push_dir(File.join(__dir__, "lib"), namespace: ::DiscourseAi) require_relative "lib/engine" +require_relative "lib/features" + +DiscourseAi::Features.feature_config.each do |feature| + register_site_setting_area("ai-features/#{feature[:name_ref]}") +end after_initialize do if defined?(Rack::MiniProfiler) diff --git a/spec/requests/admin/ai_features_controller_spec.rb b/spec/requests/admin/ai_features_controller_spec.rb index 4e518cefc..8265d856f 100644 --- a/spec/requests/admin/ai_features_controller_spec.rb +++ b/spec/requests/admin/ai_features_controller_spec.rb @@ -29,27 +29,4 @@ expect(response.parsed_body["name"]).to eq(I18n.t "discourse_ai.features.summarization.name") end end - - describe "#update" do - before do - SiteSetting.ai_summarization_persona = summarizer_persona.id - SiteSetting.ai_summarization_enabled = true - end - - it "updates the feature" do - expect(SiteSetting.ai_summarization_persona).to eq(summarizer_persona.id.to_s) - expect(SiteSetting.ai_summarization_enabled).to eq(true) - - put "/admin/plugins/discourse-ai/ai-features/1.json", - params: { - ai_feature: { - enabled: false, - persona_id: alternate_summarizer_persona.id, - }, - } - - expect(SiteSetting.ai_summarization_persona).to eq(alternate_summarizer_persona.id.to_s) - expect(SiteSetting.ai_summarization_enabled).to eq(false) - end - end end diff --git a/spec/system/admin_ai_features_spec.rb b/spec/system/admin_ai_features_spec.rb index 92d9266d1..613cd78cd 100644 --- a/spec/system/admin_ai_features_spec.rb +++ b/spec/system/admin_ai_features_spec.rb @@ -50,7 +50,7 @@ expect(page).to have_current_path("/admin/plugins") end - it "can edit the AI feature" do + it "shows edit page with settings" do ai_features_page.visit ai_features_page.click_edit_feature(I18n.t("discourse_ai.features.summarization.name")) expect(page).to have_current_path("/admin/plugins/discourse-ai/ai-features/1/edit") @@ -59,11 +59,6 @@ text: I18n.t("discourse_ai.features.summarization.name"), ) - form.field("persona_id").select(-6) - form.submit - expect(page).to have_current_path("/admin/plugins/discourse-ai/ai-features") - expect(ai_features_page).to have_feature_persona( - I18n.t("discourse_ai.ai_bot.personas.creative.name"), - ) + expect(page).to have_css(".setting") end end From db5bd8f413b554e1bc60b6eff5ecf9ac83a36f0d Mon Sep 17 00:00:00 2001 From: Keegan George Date: Wed, 9 Apr 2025 10:08:21 -0700 Subject: [PATCH 12/12] DEV: Remove/refactor redundant/no longer needed code --- .../show/discourse-ai-features/edit.gjs | 22 ++++++++++++-- .../show/discourse-ai-features/index.gjs | 1 - .../components/ai-feature-editor.gjs | 29 ------------------- config/locales/client.en.yml | 8 ----- 4 files changed, 20 insertions(+), 40 deletions(-) delete mode 100644 assets/javascripts/discourse/components/ai-feature-editor.gjs diff --git a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs index 78f026558..9513e024b 100644 --- a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs +++ b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/edit.gjs @@ -1,6 +1,24 @@ import RouteTemplate from "ember-route-template"; -import AiFeatureEditor from "discourse/plugins/discourse-ai/discourse/components/ai-feature-editor"; +import BackButton from "discourse/components/back-button"; +import SiteSettingComponent from "admin/components/site-setting"; export default RouteTemplate( - + ); diff --git a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs index 29f1bccac..9bf9fc9ce 100644 --- a/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs +++ b/admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-features/index.gjs @@ -10,7 +10,6 @@ import { i18n } from "discourse-i18n"; export default RouteTemplate( class extends Component { @service adminPluginNavManager; - @service currentUser; get tableHeaders() { const prefix = "discourse_ai.features.list.header"; diff --git a/assets/javascripts/discourse/components/ai-feature-editor.gjs b/assets/javascripts/discourse/components/ai-feature-editor.gjs deleted file mode 100644 index 6199fe39d..000000000 --- a/assets/javascripts/discourse/components/ai-feature-editor.gjs +++ /dev/null @@ -1,29 +0,0 @@ -import Component from "@glimmer/component"; -import { service } from "@ember/service"; -import BackButton from "discourse/components/back-button"; -import SiteSettingComponent from "admin/components/site-setting"; - -export default class AiFeatureEditor extends Component { - @service toasts; - @service currentUser; - @service router; - - -} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 1b3da23e6..a04777dea 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -177,14 +177,6 @@ en: set_up: "Set up" configured_features: "Configured features" unconfigured_features: "Unconfigured features" - editor: - enable_setting: "Enable" - enable_setting_help: "Toggles '%{setting}' setting" - persona: "Persona" - persona_help: "To create/edit personas go to the persona configuration page" - advanced_settings: "Advanced settings" - save: "Save" - saved: "%{feature_name} feature saved" modals: select_option: "Select an option..."