From e0d86e7265f3058b376c3bdf9acfe9742635e914 Mon Sep 17 00:00:00 2001
From: Keegan George
Date: Mon, 7 Apr 2025 10:41:56 -0700
Subject: [PATCH 1/8] FEATURE: Show relevant settings in features tab only
This update hides feature related settings from main UI and only shows it in the features tab. This will help with having an overwhelming amount of settings in the settings tab and will make it easier to parse different settings.
---
.../discourse_ai/admin/ai_features_controller.rb | 13 +++++++++++++
.../discourse/admin/models/ai-feature.js | 3 ++-
.../initializers/admin-plugin-configuration-nav.js | 11 +++++------
config/settings.yml | 2 ++
4 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/app/controllers/discourse_ai/admin/ai_features_controller.rb b/app/controllers/discourse_ai/admin/ai_features_controller.rb
index 154db086a..eece23655 100644
--- a/app/controllers/discourse_ai/admin/ai_features_controller.rb
+++ b/app/controllers/discourse_ai/admin/ai_features_controller.rb
@@ -45,6 +45,11 @@ def feature_config
description_key: "discourse_ai.features.summarization.description",
persona_setting_name: "ai_summarization_persona",
enable_setting_name: "ai_summarization_enabled",
+ additional_settings: %w[
+ ai_summary_backfill_topic_max_age_days
+ ai_summary_backfill_maximum_topics_per_hour
+ ai_summary_backfill_minimum_word_count
+ ],
},
{
id: 2,
@@ -92,6 +97,14 @@ def persona_backed_features
value: SiteSetting.get(feature[:enable_setting_name]),
type: SiteSetting.type_supervisor.get_type(feature[:enable_setting_name]),
},
+ additional_settings:
+ (feature[:additional_settings] || []).map do |setting_name|
+ {
+ name: setting_name,
+ value: SiteSetting.get(setting_name),
+ type: SiteSetting.type_supervisor.get_type(setting_name),
+ }
+ 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..6b888cc1f 100644
--- a/assets/javascripts/discourse/admin/models/ai-feature.js
+++ b/assets/javascripts/discourse/admin/models/ai-feature.js
@@ -8,7 +8,8 @@ export default class AiFeature extends RestModel {
"description",
"enable_setting",
"persona",
- "persona_setting"
+ "persona_setting",
+ "additional_settings"
);
}
}
diff --git a/assets/javascripts/initializers/admin-plugin-configuration-nav.js b/assets/javascripts/initializers/admin-plugin-configuration-nav.js
index 391f5cbc7..d9ca17a03 100644
--- a/assets/javascripts/initializers/admin-plugin-configuration-nav.js
+++ b/assets/javascripts/initializers/admin-plugin-configuration-nav.js
@@ -41,12 +41,11 @@ export default {
route: "adminPlugins.show.discourse-ai-spam",
description: "discourse_ai.spam.spam_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",
- // },
+ {
+ label: "discourse_ai.features.short_title",
+ route: "adminPlugins.show.discourse-ai-features",
+ description: "discourse_ai.features.description",
+ },
]);
});
},
diff --git a/config/settings.yml b/config/settings.yml
index fab3b641d..6714797b1 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -278,10 +278,12 @@ discourse_ai:
default: 30
min: 1
max: 10000
+ hidden: true
ai_summary_backfill_maximum_topics_per_hour:
default: 0
min: 0
max: 10000
+ hidden: true
ai_summary_backfill_minimum_word_count:
default: 200
hidden: true
From aad15b413ecfe402904850d63cd76f2b33673254 Mon Sep 17 00:00:00 2001
From: Keegan George
Date: Mon, 7 Apr 2025 11:14:11 -0700
Subject: [PATCH 2/8] DEV: Add Discord search related settings
---
.../discourse_ai/admin/ai_features_controller.rb | 7 +++++++
config/locales/client.en.yml | 1 +
config/settings.yml | 4 ++++
3 files changed, 12 insertions(+)
diff --git a/app/controllers/discourse_ai/admin/ai_features_controller.rb b/app/controllers/discourse_ai/admin/ai_features_controller.rb
index eece23655..016b7c424 100644
--- a/app/controllers/discourse_ai/admin/ai_features_controller.rb
+++ b/app/controllers/discourse_ai/admin/ai_features_controller.rb
@@ -49,6 +49,7 @@ def feature_config
ai_summary_backfill_topic_max_age_days
ai_summary_backfill_maximum_topics_per_hour
ai_summary_backfill_minimum_word_count
+ ai_pm_summarization_allowed_groups
],
},
{
@@ -71,6 +72,12 @@ def feature_config
description_key: "discourse_ai.features.discord_search.description",
persona_setting_name: "ai_discord_search_persona",
enable_setting_name: "ai_discord_search_enabled",
+ additional_settings: %w[
+ ai_discord_app_id
+ ai_discord_app_public_key
+ ai_discord_search_mode
+ ai_discord_allowed_guilds
+ ],
},
]
end
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/settings.yml b/config/settings.yml
index 6714797b1..0e046bf2b 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -351,15 +351,18 @@ discourse_ai:
ai_discord_app_id:
default: ""
client: false
+ hidden: true
ai_discord_app_public_key:
default: ""
client: false
+ hidden: true
ai_discord_search_mode:
default: "search"
type: enum
choices:
- search
- persona
+ hidden: true
ai_discord_search_persona:
default: ""
type: enum
@@ -369,6 +372,7 @@ discourse_ai:
type: list
list_type: compact
default: ""
+ hidden: true
ai_spam_detection_enabled:
default: false
From 5e0bed17fd07e117bb53d2585281ede186660aa9 Mon Sep 17 00:00:00 2001
From: Keegan George
Date: Mon, 7 Apr 2025 15:57:34 -0700
Subject: [PATCH 3/8] WIP: Settings areas
---
.../admin/ai_features_controller.rb | 99 +++----------------
.../discourse/admin/models/ai-feature.js | 1 +
.../components/ai-feature-editor.gjs | 51 +++++++++-
config/settings.yml | 12 +--
lib/features.rb | 92 +++++++++++++++++
plugin.rb | 5 +
6 files changed, 168 insertions(+), 92 deletions(-)
create mode 100644 lib/features.rb
diff --git a/app/controllers/discourse_ai/admin/ai_features_controller.rb b/app/controllers/discourse_ai/admin/ai_features_controller.rb
index 016b7c424..5ab4ae30a 100644
--- a/app/controllers/discourse_ai/admin/ai_features_controller.rb
+++ b/app/controllers/discourse_ai/admin/ai_features_controller.rb
@@ -6,12 +6,12 @@ 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
@@ -22,103 +22,32 @@ def update
end
raise Discourse::InvalidParameters.new(:enabled) if params[:ai_feature][:enabled].nil?
- feature = find_feature_by_id(params[:id].to_i)
+ feature = DiscourseAi::Features.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)
- 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
private
- # 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",
- additional_settings: %w[
- ai_summary_backfill_topic_max_age_days
- ai_summary_backfill_maximum_topics_per_hour
- ai_summary_backfill_minimum_word_count
- ai_pm_summarization_allowed_groups
- ],
- },
- {
- 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",
- additional_settings: %w[
- ai_discord_app_id
- ai_discord_app_public_key
- ai_discord_search_mode
- ai_discord_allowed_guilds
- ],
- },
- ]
+ def serialize_features(features)
+ features.map { |feature| 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]),
- },
- additional_settings:
- (feature[:additional_settings] || []).map do |setting_name|
- {
- name: setting_name,
- value: SiteSetting.get(setting_name),
- type: SiteSetting.type_supervisor.get_type(setting_name),
- }
- end,
- }
- end
+ def serialize_feature(feature)
+ return nil if feature.blank?
+
+ feature.merge(persona: serialize_persona(feature[:persona]))
end
- def find_feature_by_id(id)
- lookup = persona_backed_features.index_by { |feature| feature[:id] }
- lookup[id]
+ def serialize_persona(persona)
+ return nil if persona.blank?
+
+ 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 6b888cc1f..2188d3384 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..2b8497eab 100644
--- a/assets/javascripts/discourse/components/ai-feature-editor.gjs
+++ b/assets/javascripts/discourse/components/ai-feature-editor.gjs
@@ -3,25 +3,36 @@ 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 { eq, gt } from "truth-helpers";
import BackButton from "discourse/components/back-button";
import Form from "discourse/components/form";
+import { ajax } from "discourse/lib/ajax";
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";
+import SiteSetting from "admin/models/site-setting";
export default class AiFeatureEditor extends Component {
@service toasts;
@service currentUser;
@service router;
+ @tracked settings = null;
+ @tracked isLoading = false;
@tracked isSaving = false;
+ constructor() {
+ super(...arguments);
+ this.#loadSettings();
+ }
+
get formData() {
return {
enabled: this.args.model.enable_setting?.value,
persona_id: this.args.model.persona?.id,
+ additional_settings: this.args.model.additional_settings,
};
}
@@ -62,6 +73,33 @@ export default class AiFeatureEditor extends Component {
});
}
+ async #loadSettings() {
+ this.isLoading = true;
+
+ try {
+ const result = await ajax("/admin/config/site_settings.json", {
+ data: {
+ filter_area: `ai-features/${this.args.model.ref}`,
+ plugin: "discourse-ai",
+ category: "discourse_ai",
+ },
+ });
+
+ const settings = result.site_settings;
+ const settingsMap = settings.map((setting) =>
+ SiteSetting.create(setting)
+ );
+ this.settings = settingsMap;
+
+ console.log(this.settings);
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.warn(`Failed to load settings with error: ${error}`);
+ } finally {
+ this.isLoading = false;
+ }
+ }
+
+
+
+ {{#unless this.isLoading}}
+ {{#each this.settings as |setting|}}
+ {{#if setting}}
+
+ {{/if}}
+ {{/each}}
+ {{/unless}}
}
diff --git a/config/settings.yml b/config/settings.yml
index 0e046bf2b..544bc45eb 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -347,32 +347,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
- hidden: true
+ area: "ai-features/discord_search"
ai_discord_app_public_key:
default: ""
client: false
- hidden: true
+ area: "ai-features/discord_search"
ai_discord_search_mode:
default: "search"
type: enum
choices:
- search
- persona
- hidden: true
+ 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: ""
- hidden: true
+ 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..87d32df80
--- /dev/null
+++ b/lib/features.rb
@@ -0,0 +1,92 @@
+# 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]),
+ },
+ additional_settings:
+ (feature[:additional_settings] || []).map do |setting_name|
+ {
+ name: setting_name,
+ value: SiteSetting.get(setting_name),
+ type: SiteSetting.type_supervisor.get_type(setting_name),
+ }
+ end,
+ }
+ 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)
From ce346b115ff0afa05b734005e893a0bf0a7c60f8 Mon Sep 17 00:00:00 2001
From: Keegan George
Date: Tue, 8 Apr 2025 07:42:30 -0700
Subject: [PATCH 4/8] WIP
---
.../components/ai-feature-editor.gjs | 31 +++++++++---------
assets/stylesheets/common/ai-features.scss | 32 +++++++++++++++++++
config/locales/server.en.yml | 6 ++++
config/settings.yml | 15 +++++----
4 files changed, 62 insertions(+), 22 deletions(-)
diff --git a/assets/javascripts/discourse/components/ai-feature-editor.gjs b/assets/javascripts/discourse/components/ai-feature-editor.gjs
index 2b8497eab..33cc44454 100644
--- a/assets/javascripts/discourse/components/ai-feature-editor.gjs
+++ b/assets/javascripts/discourse/components/ai-feature-editor.gjs
@@ -3,7 +3,7 @@ import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { htmlSafe } from "@ember/template";
-import { eq, gt } from "truth-helpers";
+import { eq } from "truth-helpers";
import BackButton from "discourse/components/back-button";
import Form from "discourse/components/form";
import { ajax } from "discourse/lib/ajax";
@@ -89,9 +89,8 @@ export default class AiFeatureEditor extends Component {
const settingsMap = settings.map((setting) =>
SiteSetting.create(setting)
);
- this.settings = settingsMap;
- console.log(this.settings);
+ this.settings = settingsMap;
} catch (error) {
// eslint-disable-next-line no-console
console.warn(`Failed to load settings with error: ${error}`);
@@ -110,7 +109,7 @@ export default class AiFeatureEditor extends Component {
{{@model.description}}
-
-
-
- {{#unless this.isLoading}}
- {{#each this.settings as |setting|}}
- {{#if setting}}
-
- {{/if}}
- {{/each}}
- {{/unless}}
+ --}}
+
+
+ {{i18n "discourse_ai.features.editor.advanced_settings"}}
+ {{#unless this.isLoading}}
+ {{#each this.settings as |setting|}}
+
+
+
+ {{/each}}
+ {{/unless}}
+
}
diff --git a/assets/stylesheets/common/ai-features.scss b/assets/stylesheets/common/ai-features.scss
index 5e1b01602..0cd65e67e 100644
--- a/assets/stylesheets/common/ai-features.scss
+++ b/assets/stylesheets/common/ai-features.scss
@@ -30,3 +30,35 @@
}
}
}
+
+.ai-feature-editor__advanced-settings {
+ margin-block: 1rem;
+ padding-block: 1rem;
+
+ .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/server.en.yml b/config/locales/server.en.yml
index 1b06c5f54..5801137dd 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -104,6 +104,12 @@ 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_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/settings.yml b/config/settings.yml
index 544bc45eb..c975da9a5 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -235,6 +235,7 @@ discourse_ai:
client: true
validator: "DiscourseAi::Configuration::LlmDependencyValidator"
hidden: true
+ area: "ai-features/summarization"
ai_summarization_model:
default: ""
allow_any: false
@@ -246,11 +247,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
@@ -259,11 +261,12 @@ discourse_ai:
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,15 +281,15 @@ discourse_ai:
default: 30
min: 1
max: 10000
- hidden: true
+ area: "ai-features/summarization"
ai_summary_backfill_maximum_topics_per_hour:
default: 0
min: 0
max: 10000
- hidden: true
+ area: "ai-features/summarization"
ai_summary_backfill_minimum_word_count:
default: 200
- hidden: true
+ area: "ai-features/summarization"
ai_bot_enabled:
default: false
@@ -329,9 +332,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
From 3203da6bc29badee36478a4a1a670ec73a448da3 Mon Sep 17 00:00:00 2001
From: Keegan George
Date: Tue, 8 Apr 2025 13:41:50 -0700
Subject: [PATCH 5/8] DEV: Use settings `area` param instead
---
...plugins-show-discourse-ai-features-edit.js | 16 +-
.../admin/ai_features_controller.rb | 18 ---
.../discourse/admin/models/ai-feature.js | 1 -
.../components/ai-feature-editor.gjs | 150 +-----------------
assets/stylesheets/common/ai-features.scss | 7 +-
config/locales/server.en.yml | 8 +-
config/routes.rb | 2 +-
config/settings.yml | 3 +-
lib/features.rb | 8 -
9 files changed, 32 insertions(+), 181 deletions(-)
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..210cfce8f 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,19 @@ 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 5ab4ae30a..ad258c6c5 100644
--- a/app/controllers/discourse_ai/admin/ai_features_controller.rb
+++ b/app/controllers/discourse_ai/admin/ai_features_controller.rb
@@ -14,24 +14,6 @@ def edit
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 = DiscourseAi::Features.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)
-
- render json: serialize_feature(DiscourseAi::Features.find_feature_by_id(params[:id].to_i))
- end
-
private
def serialize_features(features)
diff --git a/assets/javascripts/discourse/admin/models/ai-feature.js b/assets/javascripts/discourse/admin/models/ai-feature.js
index 2188d3384..9203031cb 100644
--- a/assets/javascripts/discourse/admin/models/ai-feature.js
+++ b/assets/javascripts/discourse/admin/models/ai-feature.js
@@ -10,7 +10,6 @@ export default class AiFeature extends RestModel {
"enable_setting",
"persona",
"persona_setting",
- "additional_settings"
);
}
}
diff --git a/assets/javascripts/discourse/components/ai-feature-editor.gjs b/assets/javascripts/discourse/components/ai-feature-editor.gjs
index 33cc44454..6e22d2b64 100644
--- a/assets/javascripts/discourse/components/ai-feature-editor.gjs
+++ b/assets/javascripts/discourse/components/ai-feature-editor.gjs
@@ -1,104 +1,16 @@
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 { ajax } from "discourse/lib/ajax";
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";
-import SiteSetting from "admin/models/site-setting";
export default class AiFeatureEditor extends Component {
@service toasts;
@service currentUser;
@service router;
- @tracked settings = null;
- @tracked isLoading = false;
- @tracked isSaving = false;
-
- constructor() {
- super(...arguments);
- this.#loadSettings();
- }
-
- get formData() {
- return {
- enabled: this.args.model.enable_setting?.value,
- persona_id: this.args.model.persona?.id,
- additional_settings: this.args.model.additional_settings,
- };
- }
-
- @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"),
- });
- }
-
- async #loadSettings() {
- this.isLoading = true;
-
- try {
- const result = await ajax("/admin/config/site_settings.json", {
- data: {
- filter_area: `ai-features/${this.args.model.ref}`,
- plugin: "discourse-ai",
- category: "discourse_ai",
- },
- });
-
- const settings = result.site_settings;
- const settingsMap = settings.map((setting) =>
- SiteSetting.create(setting)
- );
-
- this.settings = settingsMap;
- } catch (error) {
- // eslint-disable-next-line no-console
- console.warn(`Failed to load settings with error: ${error}`);
- } finally {
- this.isLoading = false;
- }
- }
-
{{@model.description}}
- {{!--
-
-
- {{/if}}
-
-
-
- {{#each this.currentUser.ai_enabled_personas as |persona|}}
-
- {{persona.name}}
-
- {{/each}}
-
-
-
-
-
-
- --}}
-
-
- {{i18n "discourse_ai.features.editor.advanced_settings"}}
- {{#unless this.isLoading}}
- {{#each this.settings as |setting|}}
-
-
-
- {{/each}}
- {{/unless}}
+
+ {{#each @model.feature_settings as |setting|}}
+
+
+
+ {{/each}}
}
diff --git a/assets/stylesheets/common/ai-features.scss b/assets/stylesheets/common/ai-features.scss
index 0cd65e67e..2ae68c8b5 100644
--- a/assets/stylesheets/common/ai-features.scss
+++ b/assets/stylesheets/common/ai-features.scss
@@ -31,9 +31,10 @@
}
}
-.ai-feature-editor__advanced-settings {
- margin-block: 1rem;
- padding-block: 1rem;
+.ai-feature-editor {
+ &__header {
+ border-bottom: 1px solid var(--primary-low);
+ }
.setting {
margin-block: 1.5rem;
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 5801137dd..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,7 @@ 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"
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 c975da9a5..c8d9fb8dd 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -234,7 +234,6 @@ discourse_ai:
default: false
client: true
validator: "DiscourseAi::Configuration::LlmDependencyValidator"
- hidden: true
area: "ai-features/summarization"
ai_summarization_model:
default: ""
@@ -260,7 +259,6 @@ discourse_ai:
hidden: true
ai_summary_gists_enabled:
default: false
- hidden: true
area: "ai-features/gists"
ai_summary_gists_persona:
default: "-12"
@@ -294,6 +292,7 @@ discourse_ai:
ai_bot_enabled:
default: false
client: true
+ area: "ai-features/discoveries"
ai_bot_enable_chat_warning:
default: false
client: true
diff --git a/lib/features.rb b/lib/features.rb
index 87d32df80..d3b999c25 100644
--- a/lib/features.rb
+++ b/lib/features.rb
@@ -57,14 +57,6 @@ def self.features
value: SiteSetting.get(feature[:enable_setting_name]),
type: SiteSetting.type_supervisor.get_type(feature[:enable_setting_name]),
},
- additional_settings:
- (feature[:additional_settings] || []).map do |setting_name|
- {
- name: setting_name,
- value: SiteSetting.get(setting_name),
- type: SiteSetting.type_supervisor.get_type(setting_name),
- }
- end,
}
end
end
From a9f5453d22cdb5ea156295d321fe72691fafc2b4 Mon Sep 17 00:00:00 2001
From: Keegan George
Date: Wed, 9 Apr 2025 09:24:19 -0700
Subject: [PATCH 6/8] FIX: Specs
---
.../discourse/admin/models/ai-feature.js | 2 +-
.../components/ai-feature-editor.gjs | 3 ---
.../admin/ai_features_controller_spec.rb | 23 -------------------
spec/system/admin_ai_features_spec.rb | 9 ++------
4 files changed, 3 insertions(+), 34 deletions(-)
diff --git a/assets/javascripts/discourse/admin/models/ai-feature.js b/assets/javascripts/discourse/admin/models/ai-feature.js
index 9203031cb..85dfa7ca9 100644
--- a/assets/javascripts/discourse/admin/models/ai-feature.js
+++ b/assets/javascripts/discourse/admin/models/ai-feature.js
@@ -9,7 +9,7 @@ export default class AiFeature extends RestModel {
"description",
"enable_setting",
"persona",
- "persona_setting",
+ "persona_setting"
);
}
}
diff --git a/assets/javascripts/discourse/components/ai-feature-editor.gjs b/assets/javascripts/discourse/components/ai-feature-editor.gjs
index 6e22d2b64..6199fe39d 100644
--- a/assets/javascripts/discourse/components/ai-feature-editor.gjs
+++ b/assets/javascripts/discourse/components/ai-feature-editor.gjs
@@ -1,9 +1,6 @@
import Component from "@glimmer/component";
-import { action } from "@ember/object";
import { service } from "@ember/service";
import BackButton from "discourse/components/back-button";
-import { popupAjaxError } from "discourse/lib/ajax-error";
-import getURL from "discourse/lib/get-url";
import SiteSettingComponent from "admin/components/site-setting";
export default class AiFeatureEditor extends Component {
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 f90db17dcc53c3f4a6da3506d29e7adcef099e07 Mon Sep 17 00:00:00 2001
From: Keegan George
Date: Wed, 9 Apr 2025 09:27:09 -0700
Subject: [PATCH 7/8] =?UTF-8?q?Make=20it=20pretty=20=F0=9F=92=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../admin-plugins-show-discourse-ai-features-edit.js | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
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 210cfce8f..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
@@ -15,11 +15,12 @@ export default class AdminPluginsShowDiscourseAiFeaturesEdit extends DiscourseRo
filter_area: `ai-features/${currentFeature.ref}`,
plugin: "discourse-ai",
category: "discourse_ai",
- }
+ },
});
-
- currentFeature.feature_settings = site_settings.map((setting) => SiteSetting.create(setting));
+ currentFeature.feature_settings = site_settings.map((setting) =>
+ SiteSetting.create(setting)
+ );
return currentFeature;
}
From a1aa882f984f2243e909b38aea874f186a104869 Mon Sep 17 00:00:00 2001
From: Keegan George
Date: Wed, 9 Apr 2025 09:40:46 -0700
Subject: [PATCH 8/8] DEV: Keep feature tab hidden 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",
+ // },
]);
});
},