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: - {"spam": "xx"} + {"spam": xx} - 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