diff --git a/app/controllers/discourse_ai/admin/ai_spam_controller.rb b/app/controllers/discourse_ai/admin/ai_spam_controller.rb
index 9b8d72da..19490391 100644
--- a/app/controllers/discourse_ai/admin/ai_spam_controller.rb
+++ b/app/controllers/discourse_ai/admin/ai_spam_controller.rb
@@ -11,6 +11,13 @@ module DiscourseAi
def update
initial_settings = AiModerationSetting.spam
+
+ initial_data = {
+ custom_instructions: initial_settings&.data&.dig("custom_instructions"),
+ llm_model_id: initial_settings&.llm_model_id,
+ ai_persona_id: initial_settings&.ai_persona_id,
+ }
+
initial_custom_instructions = initial_settings&.data&.dig("custom_instructions")
initial_llm_model_id = initial_settings&.llm_model_id
@@ -29,6 +36,22 @@ module DiscourseAi
)
end
end
+
+ if allowed_params.key?(:ai_persona_id)
+ updated_params[:ai_persona_id] = allowed_params[:ai_persona_id]
+ persona = AiPersona.find_by(id: allowed_params[:ai_persona_id])
+ if persona.nil? ||
+ persona.response_format.to_a.none? { |rf|
+ rf["key"] == "spam" && rf["type"] == "boolean"
+ }
+ return(
+ render_json_error(
+ I18n.t("discourse_ai.llm.configuration.invalid_persona_response_format"),
+ status: 422,
+ )
+ )
+ end
+ end
updated_params[:data] = {
custom_instructions: allowed_params[:custom_instructions],
} if allowed_params.key?(:custom_instructions)
@@ -41,7 +64,7 @@ module DiscourseAi
AiModerationSetting.create!(updated_params.merge(setting_type: :spam))
end
- log_ai_spam_update(initial_llm_model_id, initial_custom_instructions, allowed_params)
+ log_ai_spam_update(initial_data, allowed_params)
end
is_enabled = ActiveModel::Type::Boolean.new.cast(allowed_params[:is_enabled])
@@ -119,9 +142,10 @@ module DiscourseAi
private
- def log_ai_spam_update(initial_llm_model_id, initial_custom_instructions, params)
+ def log_ai_spam_update(initial_data, params)
changes_to_log = {}
+ initial_llm_model_id = initial_data[:llm_model_id]
if params.key?(:llm_model_id) && initial_llm_model_id.to_s != params[:llm_model_id].to_s
old_model_name =
LlmModel.find_by(id: initial_llm_model_id)&.display_name || initial_llm_model_id
@@ -131,11 +155,22 @@ module DiscourseAi
changes_to_log[:llm_model_id] = "#{old_model_name} → #{new_model_name}"
end
+ initial_custom_instructions = initial_data[:custom_instructions]
if params.key?(:custom_instructions) &&
initial_custom_instructions != params[:custom_instructions]
changes_to_log[:custom_instructions] = params[:custom_instructions]
end
+ initial_ai_persona_id = initial_data[:ai_persona_id]
+ if params.key?(:ai_persona_id) && initial_ai_persona_id.to_s != params[:ai_persona_id].to_s
+ old_persona_name =
+ AiPersona.find_by(id: initial_ai_persona_id)&.name || initial_ai_persona_id
+ new_persona_name =
+ AiPersona.find_by(id: params[:ai_persona_id])&.name || params[:ai_persona_id]
+
+ changes_to_log[:ai_persona_id] = "#{old_persona_name} → #{new_persona_name}"
+ end
+
if changes_to_log.present?
changes_to_log[:subject] = I18n.t("discourse_ai.spam_detection.logging_subject")
logger = DiscourseAi::Utils::AiStaffActionLogger.new(current_user)
@@ -144,7 +179,7 @@ module DiscourseAi
end
def allowed_params
- params.permit(:is_enabled, :llm_model_id, :custom_instructions)
+ params.permit(:is_enabled, :llm_model_id, :custom_instructions, :ai_persona_id)
end
def spam_config
diff --git a/app/models/ai_moderation_setting.rb b/app/models/ai_moderation_setting.rb
index 8b440725..596ba131 100644
--- a/app/models/ai_moderation_setting.rb
+++ b/app/models/ai_moderation_setting.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class AiModerationSetting < ActiveRecord::Base
belongs_to :llm_model
+ belongs_to :ai_persona
validates :llm_model_id, presence: true
validates :setting_type, presence: true
@@ -19,12 +20,13 @@ end
#
# Table name: ai_moderation_settings
#
-# id :bigint not null, primary key
-# setting_type :enum not null
-# data :jsonb
-# llm_model_id :bigint not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# id :bigint not null, primary key
+# setting_type :enum not null
+# data :jsonb
+# llm_model_id :bigint not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# ai_persona_id :bigint default(-31), not null
#
# Indexes
#
diff --git a/app/serializers/ai_spam_serializer.rb b/app/serializers/ai_spam_serializer.rb
index 022d1dac..dc1212d3 100644
--- a/app/serializers/ai_spam_serializer.rb
+++ b/app/serializers/ai_spam_serializer.rb
@@ -8,7 +8,9 @@ class AiSpamSerializer < ApplicationSerializer
:stats,
:flagging_username,
:spam_score_type,
- :spam_scanning_user
+ :spam_scanning_user,
+ :ai_persona_id,
+ :available_personas
def is_enabled
object[:enabled]
@@ -18,6 +20,11 @@ class AiSpamSerializer < ApplicationSerializer
settings&.llm_model&.id
end
+ def ai_persona_id
+ settings&.ai_persona&.id ||
+ DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::SpamDetector]
+ end
+
def custom_instructions
settings&.custom_instructions
end
@@ -28,6 +35,12 @@ class AiSpamSerializer < ApplicationSerializer
.map { |hash| { id: hash[:value], name: hash[:name] } }
end
+ def available_personas
+ DiscourseAi::Configuration::PersonaEnumerator.values.map do |h|
+ { id: h[:value], name: h[:name] }
+ end
+ end
+
def flagging_username
object[:flagging_username]
end
diff --git a/assets/javascripts/discourse/components/ai-spam.gjs b/assets/javascripts/discourse/components/ai-spam.gjs
index 5c6d883a..068c715e 100644
--- a/assets/javascripts/discourse/components/ai-spam.gjs
+++ b/assets/javascripts/discourse/components/ai-spam.gjs
@@ -35,6 +35,7 @@ export default class AiSpam extends Component {
};
@tracked isEnabled = false;
@tracked selectedLLM = null;
+ @tracked selectedPersonaId = null;
@tracked customInstructions = "";
@tracked errors = [];
@@ -98,6 +99,7 @@ export default class AiSpam extends Component {
}
this.customInstructions = model.custom_instructions;
this.stats = model.stats;
+ this.selectedPersonaId = model.ai_persona_id;
}
get availableLLMs() {
@@ -133,6 +135,11 @@ export default class AiSpam extends Component {
this.selectedLLM = value;
}
+ @action
+ async updatePersona(value) {
+ this.selectedPersonaId = value;
+ }
+
@action
async save() {
try {
@@ -141,6 +148,7 @@ export default class AiSpam extends Component {
data: {
llm_model_id: this.llmId,
custom_instructions: this.customInstructions,
+ ai_persona_id: this.selectedPersonaId,
},
});
this.toasts.success({
@@ -256,6 +264,18 @@ export default class AiSpam extends Component {
{{/if}}
+
+
+
+
+