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.
This commit is contained in:
Keegan George 2025-01-10 02:17:06 +09:00 committed by GitHub
parent 24b69bf840
commit b24669c810
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 164 additions and 2 deletions

View File

@ -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

View File

@ -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

View File

@ -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 {
<template>
<div class="ai-spam">
<section class="ai-spam__settings">
<div class="ai-spam__errors">
{{#each this.errors as |e|}}
<div class="alert alert-error">
{{dIcon "triangle-exclamation"}}
<p>{{e.message}}</p>
<DButton
@action={{e.button.action}}
@translatedLabel={{e.button.label}}
/>
</div>
{{/each}}
</div>
<DPageSubheader
@titleLabel={{i18n "discourse_ai.spam.title"}}
@descriptionLabel={{i18n "discourse_ai.spam.spam_description"}}
/>
<div class="control-group ai-spam__enabled">
<DToggleSwitch
class="ai-spam__toggle"

View File

@ -45,6 +45,18 @@
&__stats {
margin-top: 2em;
}
&__errors {
.alert {
display: flex;
align-items: center;
gap: 0.5rem;
.btn {
margin-left: auto;
}
}
}
}
.spam-test-modal {

View File

@ -160,6 +160,11 @@ en:
stat_tooltips:
incorrectly_flagged: "Items that the AI bot flagged as spam where moderators disagreed"
missed_spam: "Items flagged by the community as spam that were not detected by the AI bot, which moderators agreed with"
errors:
scan_not_admin:
message: "Warning: spam scanning will not work correctly because the spam scan account is not an admin"
action: "Fix"
resolved: "The error has been resolved!"
usage:
short_title: "Usage"

View File

@ -255,6 +255,10 @@ en:
spam_detection:
flag_reason: "Flagged as spam by <a href='%{url}'>Discourse AI</a>"
silence_reason: "User silenced automatically by <a href='%{url}'>Discourse AI</a>"
invalid_error_type: "Invalid error type provided"
unexpected: "An unexpected error occured"
bot_user_update_failed: "Failed to update the spam scanning bot user"
ai_bot:
reply_error: "Sorry, it looks like our system encountered an unexpected issue while trying to reply.\n\n[details='Error details']\n%{details}\n[/details]"
default_pm_prefix: "[Untitled AI bot PM]"

View File

@ -83,6 +83,7 @@ Discourse::Application.routes.draw do
get "/ai-spam", to: "discourse_ai/admin/ai_spam#show"
put "/ai-spam", to: "discourse_ai/admin/ai_spam#update"
post "/ai-spam/test", to: "discourse_ai/admin/ai_spam#test"
post "/ai-spam/fix-errors", to: "discourse_ai/admin/ai_spam#fix_errors"
resources :ai_llms,
only: %i[index new create edit update destroy],

View File

@ -241,6 +241,16 @@ module DiscourseAi
end
end
def self.fix_spam_scanner_not_admin
user = DiscourseAi::AiModeration::SpamScanner.flagging_user
if user.present?
user.update!(admin: true)
else
raise Discourse::NotFound
end
end
private
def self.check_if_spam(result)

View File

@ -306,4 +306,49 @@ RSpec.describe DiscourseAi::Admin::AiSpamController do
end
end
end
describe "#fix_errors" do
fab!(:setting) do
AiModerationSetting.create(
{
setting_type: :spam,
llm_model_id: llm_model.id,
data: {
custom_instructions: "custom instructions",
},
},
)
fab!(:llm_model)
before do
sign_in(admin)
DiscourseAi::AiModeration::SpamScanner.flagging_user.update!(admin: false)
end
it "resolves spam scanner not admin error" do
post "/admin/plugins/discourse-ai/ai-spam/fix-errors",
params: {
error: "spam_scanner_not_admin",
}
expect(response.status).to eq(200)
expect(DiscourseAi::AiModeration::SpamScanner.flagging_user.reload.admin).to eq(true)
end
it "returns an error when it can't update the user" do
DiscourseAi::AiModeration::SpamScanner.flagging_user.destroy
post "/admin/plugins/discourse-ai/ai-spam/fix-errors",
params: {
error: "spam_scanner_not_admin",
}
expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to be_present
expect(response.parsed_body["errors"].first).to eq(
I18n.t("discourse_ai.spam_detection.bot_user_update_failed"),
)
end
end
end
end