From b24669c81099f6f5fc160314083193b3f8dcec8f Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 10 Jan 2025 02:17:06 +0900 Subject: [PATCH] DEV: Add structure for errors in spam (#1054) This update adds some structure for handling errors in the spam config while also handling a specific error related to the spam scanning user not being an admin account. --- .../discourse_ai/admin/ai_spam_controller.rb | 25 +++++++++ app/serializers/ai_spam_serializer.rb | 9 ++- .../discourse/components/ai-spam.gjs | 55 ++++++++++++++++++- .../stylesheets/modules/llms/common/spam.scss | 12 ++++ config/locales/client.en.yml | 5 ++ config/locales/server.en.yml | 4 ++ config/routes.rb | 1 + lib/ai_moderation/spam_scanner.rb | 10 ++++ .../requests/admin/ai_spam_controller_spec.rb | 45 +++++++++++++++ 9 files changed, 164 insertions(+), 2 deletions(-) diff --git a/app/controllers/discourse_ai/admin/ai_spam_controller.rb b/app/controllers/discourse_ai/admin/ai_spam_controller.rb index 138908eb..6676ab5c 100644 --- a/app/controllers/discourse_ai/admin/ai_spam_controller.rb +++ b/app/controllers/discourse_ai/admin/ai_spam_controller.rb @@ -86,6 +86,31 @@ module DiscourseAi render json: result end + def fix_errors + case params[:error] + when "spam_scanner_not_admin" + begin + DiscourseAi::AiModeration::SpamScanner.fix_spam_scanner_not_admin + render json: success_json + rescue ActiveRecord::RecordInvalid + render_json_error( + I18n.t("discourse_ai.spam_detection.bot_user_update_failed"), + status: :unprocessable_entity, + ) + rescue StandardError + render_json_error( + I18n.t("discourse_ai.spam_detection.unexpected"), + status: :internal_server_error, + ) + end + else + render_json_error( + I18n.t("discourse_ai.spam_detection.invalid_error_type"), + status: :bad_request, + ) + end + end + private def allowed_params diff --git a/app/serializers/ai_spam_serializer.rb b/app/serializers/ai_spam_serializer.rb index 14f1c474..022d1dac 100644 --- a/app/serializers/ai_spam_serializer.rb +++ b/app/serializers/ai_spam_serializer.rb @@ -7,7 +7,8 @@ class AiSpamSerializer < ApplicationSerializer :available_llms, :stats, :flagging_username, - :spam_score_type + :spam_score_type, + :spam_scanning_user def is_enabled object[:enabled] @@ -47,4 +48,10 @@ class AiSpamSerializer < ApplicationSerializer def settings object[:settings] end + + def spam_scanning_user + user = DiscourseAi::AiModeration::SpamScanner.flagging_user + + user.serializable_hash(only: %i[id username name admin]) if user.present? + end end diff --git a/assets/javascripts/discourse/components/ai-spam.gjs b/assets/javascripts/discourse/components/ai-spam.gjs index d7f7a9af..b5425d2e 100644 --- a/assets/javascripts/discourse/components/ai-spam.gjs +++ b/assets/javascripts/discourse/components/ai-spam.gjs @@ -13,6 +13,7 @@ import DTooltip from "discourse/components/d-tooltip"; import withEventValue from "discourse/helpers/with-event-value"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import dIcon from "discourse-common/helpers/d-icon"; import i18n from "discourse-common/helpers/i18n"; import getURL from "discourse-common/lib/get-url"; import AdminConfigAreaCard from "admin/components/admin-config-area-card"; @@ -35,10 +36,51 @@ export default class AiSpam extends Component { @tracked isEnabled = false; @tracked selectedLLM = null; @tracked customInstructions = ""; + @tracked errors = []; constructor() { super(...arguments); this.initializeFromModel(); + + if (this.args.model?.spam_scanning_user?.admin === false) { + this.errors.push({ + message: i18n("discourse_ai.spam.errors.scan_not_admin.message"), + button: { + label: i18n("discourse_ai.spam.errors.scan_not_admin.action"), + action: this.fixScanUserNotAdmin, + }, + }); + } + } + + @action + async fixScanUserNotAdmin() { + const spamScanningUser = this.args.model.spam_scanning_user; + if (!spamScanningUser || spamScanningUser.admin) { + return; + } + try { + const response = await ajax( + `/admin/plugins/discourse-ai/ai-spam/fix-errors`, + { + type: "POST", + data: { + error: "spam_scanner_not_admin", + }, + } + ); + + if (response.success) { + this.toasts.success({ + data: { message: i18n("discourse_ai.spam.errors.resolved") }, + duration: 2000, + }); + } + } catch (error) { + popupAjaxError(error); + } finally { + window.location.reload(); + } } @action @@ -165,11 +207,22 @@ export default class AiSpam extends Component {