diff --git a/assets/javascripts/discourse/components/ai-features-list.gjs b/assets/javascripts/discourse/components/ai-features-list.gjs
index 99f5d34c..8841482c 100644
--- a/assets/javascripts/discourse/components/ai-features-list.gjs
+++ b/assets/javascripts/discourse/components/ai-features-list.gjs
@@ -73,6 +73,11 @@ export default class AiFeaturesList extends Component {
return this.args.modules.sortBy("module_name");
}
+ @action
+ isSpamModule(aModule) {
+ return aModule.module_name === "spam";
+ }
+
@action
hasGroups(feature) {
return this.groupList(feature).length > 0;
@@ -106,12 +111,20 @@ export default class AiFeaturesList extends Component {
{{i18n
(concat "discourse_ai.features." module.module_name ".name")
}}
-
+ {{#if (this.isSpamModule module)}}
+
+ {{else}}
+
+ {{/if}}
{{i18n
(concat
@@ -194,24 +207,25 @@ export default class AiFeaturesList extends Component {
{{/if}}
- {{#if feature.personas}}
-
-
- {{i18n "discourse_ai.features.groups"}}
-
- {{#if (this.hasGroups feature)}}
-
- {{#each (this.groupList feature) as |group|}}
- - {{group.name}}
- {{/each}}
-
- {{else}}
+ {{#unless (this.isSpamModule module)}}
+ {{#if feature.personas}}
+
- {{i18n "discourse_ai.features.no_groups"}}
+ {{i18n "discourse_ai.features.groups"}}
+ {{#if (this.hasGroups feature)}}
+
+ {{#each (this.groupList feature) as |group|}}
+ - {{group.name}}
+ {{/each}}
+
+ {{else}}
+
+ {{i18n "discourse_ai.features.no_groups"}}
+
{{/if}}
- {{/if}}
+ {{/unless}}
{{/each}}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 95f1c98f..216bf27b 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -235,7 +235,7 @@ en:
ai_helper:
name: "Helper"
- description: "Assists users in community interaction, such as creating topics, writing posts, and reading content."
+ description: "Assists users in community interaction, such as creating topics, writing posts, and reading content"
proofread: Proofread text
title_suggestions: "Suggest titles"
explain: "Explain"
@@ -253,6 +253,11 @@ en:
post_raw_translator: "Post raw translator"
topic_title_translator: "Topic title translator"
short_text_translator: "Short text translator"
+
+ spam:
+ name: "Spam"
+ description: "Identifies potential spam using the selected LLM and flags it for site moderators to inspect in the review queue"
+ inspect_posts: "Inspect posts"
modals:
select_option: "Select an option..."
diff --git a/lib/configuration/feature.rb b/lib/configuration/feature.rb
index d3f71056..3493fdf6 100644
--- a/lib/configuration/feature.rb
+++ b/lib/configuration/feature.rb
@@ -131,6 +131,19 @@ module DiscourseAi
]
end
+ def spam_features
+ feature_cache[:spam] ||= [
+ new(
+ "inspect_posts",
+ nil,
+ DiscourseAi::Configuration::Module::SPAM_ID,
+ DiscourseAi::Configuration::Module::SPAM,
+ persona_ids_lookup: -> { [AiModerationSetting.spam&.ai_persona_id].compact },
+ llm_models_lookup: -> { [AiModerationSetting.spam&.llm_model].compact },
+ ),
+ ]
+ end
+
def lookup_bot_persona_ids
AiPersona
.where(enabled: true)
@@ -182,6 +195,7 @@ module DiscourseAi
ai_helper_features,
translation_features,
bot_features,
+ spam_features,
].flatten
end
diff --git a/lib/configuration/module.rb b/lib/configuration/module.rb
index 74921e1e..336bc3a7 100644
--- a/lib/configuration/module.rb
+++ b/lib/configuration/module.rb
@@ -10,8 +10,9 @@ module DiscourseAi
AI_HELPER = "ai_helper"
TRANSLATION = "translation"
BOT = "bot"
+ SPAM = "spam"
- NAMES = [SUMMARIZATION, SEARCH, DISCORD, INFERENCE, AI_HELPER, TRANSLATION, BOT].freeze
+ NAMES = [SUMMARIZATION, SEARCH, DISCORD, INFERENCE, AI_HELPER, TRANSLATION, BOT, SPAM].freeze
SUMMARIZATION_ID = 1
SEARCH_ID = 2
@@ -20,6 +21,7 @@ module DiscourseAi
AI_HELPER_ID = 5
TRANSLATION_ID = 6
BOT_ID = 7
+ SPAM_ID = 8
class << self
def all
@@ -27,46 +29,52 @@ module DiscourseAi
new(
SUMMARIZATION_ID,
SUMMARIZATION,
- "ai_summarization_enabled",
+ enabled_by_setting: "ai_summarization_enabled",
features: DiscourseAi::Configuration::Feature.summarization_features,
),
new(
SEARCH_ID,
SEARCH,
- "ai_bot_enabled",
+ enabled_by_setting: "ai_bot_enabled",
features: DiscourseAi::Configuration::Feature.search_features,
extra_check: -> { SiteSetting.ai_bot_discover_persona.present? },
),
new(
DISCORD_ID,
DISCORD,
- "ai_discord_search_enabled",
+ enabled_by_setting: "ai_discord_search_enabled",
features: DiscourseAi::Configuration::Feature.discord_features,
),
new(
INFERENCE_ID,
INFERENCE,
- "inferred_concepts_enabled",
+ enabled_by_setting: "inferred_concepts_enabled",
features: DiscourseAi::Configuration::Feature.inference_features,
),
new(
AI_HELPER_ID,
AI_HELPER,
- "ai_helper_enabled",
+ enabled_by_setting: "ai_helper_enabled",
features: DiscourseAi::Configuration::Feature.ai_helper_features,
),
new(
TRANSLATION_ID,
TRANSLATION,
- "ai_translation_enabled",
+ enabled_by_setting: "ai_translation_enabled",
features: DiscourseAi::Configuration::Feature.translation_features,
),
new(
BOT_ID,
BOT,
- "ai_bot_enabled",
+ enabled_by_setting: "ai_bot_enabled",
features: DiscourseAi::Configuration::Feature.bot_features,
),
+ new(
+ SPAM_ID,
+ SPAM,
+ enabled_by_setting: "ai_spam_detection_enabled",
+ features: DiscourseAi::Configuration::Feature.spam_features,
+ ),
]
end
@@ -75,7 +83,7 @@ module DiscourseAi
end
end
- def initialize(id, name, enabled_by_setting, features: [], extra_check: nil)
+ def initialize(id, name, enabled_by_setting: nil, features: [], extra_check: nil)
@id = id
@name = name
@enabled_by_setting = enabled_by_setting
@@ -86,6 +94,8 @@ module DiscourseAi
attr_reader :id, :name, :enabled_by_setting, :features
def enabled?
+ return @extra_check.call if enabled_by_setting.blank? && @extra_check.present?
+
enabled_setting = SiteSetting.get(enabled_by_setting)
if @extra_check
diff --git a/lib/personas/spam_detector.rb b/lib/personas/spam_detector.rb
index abf09d38..85f782a5 100644
--- a/lib/personas/spam_detector.rb
+++ b/lib/personas/spam_detector.rb
@@ -44,13 +44,13 @@ module DiscourseAi
- Site description: {site_description}
- Site top 10 categories: {top_categories}
- Format your response as a JSON object with a one key named "spam", which indicates if a post is spam or legitimate.
+ Format your response as a JSON object with a one key named "spam", which is a boolean that indicates if a post is spam or legitimate.
Your output should be in the following format:
- Where "xx" is true if the post is spam, or false if it's legitimate.
+ Where xx is true if the post is spam, or false if it's legitimate.
PROMPT
end
diff --git a/spec/requests/admin/ai_features_controller_spec.rb b/spec/requests/admin/ai_features_controller_spec.rb
index 7bb1bcc0..264207c8 100644
--- a/spec/requests/admin/ai_features_controller_spec.rb
+++ b/spec/requests/admin/ai_features_controller_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe DiscourseAi::Admin::AiFeaturesController do
get "/admin/plugins/discourse-ai/ai-features.json"
expect(response.status).to eq(200)
- expect(response.parsed_body["ai_features"].count).to eq(7)
+ expect(response.parsed_body["ai_features"].count).to eq(8)
end
end
diff --git a/spec/system/admin_ai_features_spec.rb b/spec/system/admin_ai_features_spec.rb
index be111785..c5cd9557 100644
--- a/spec/system/admin_ai_features_spec.rb
+++ b/spec/system/admin_ai_features_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe "Admin AI features configuration", type: :system, js: true do
ai_features_page.toggle_unconfigured
# this changes as we add more AI features
- expect(ai_features_page).to have_listed_modules(6)
+ expect(ai_features_page).to have_listed_modules(7)
end
it "lists the persona used for the corresponding AI feature" do