From 886cd0d301bebfce1dea78b29c191e67c616dd83 Mon Sep 17 00:00:00 2001 From: ngfchl Date: Tue, 7 Jul 2026 15:32:36 +0800 Subject: [PATCH] feat: add spam rule tester page --- docker-compose.yaml | 1 + front/src/App.vue | 44 +++- front/src/style.css | 19 ++ rules/spam_keywords.txt | 3 + services/spam_detector.py | 9 + web/api.py | 60 +++++ web/static/assets/index-C6kPOPdz.js | 328 +++++++++++++++++++++++++++ web/static/assets/index-DfsWb80Q.css | 1 + web/static/index.html | 4 +- 9 files changed, 464 insertions(+), 5 deletions(-) create mode 100644 web/static/assets/index-C6kPOPdz.js create mode 100644 web/static/assets/index-DfsWb80Q.css diff --git a/docker-compose.yaml b/docker-compose.yaml index 7a996f2..01b8664 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,6 +15,7 @@ services: KNOWLEDGE_SEARCH_MODE: lite volumes: - ./logs:/app/logs + - ./rules:/app/rules - ./knowledge/index:/app/knowledge/index - ./knowledge/models:/app/knowledge/models - ./knowledge/qa_cache.json:/app/knowledge/qa_cache.json diff --git a/front/src/App.vue b/front/src/App.vue index 303623f..2c72fd4 100644 --- a/front/src/App.vue +++ b/front/src/App.vue @@ -19,9 +19,15 @@ const shopItems = ref([]); const shopTransactions = ref[]>([]); const lotteries = ref([]); const lotteryParticipants = ref[]>([]); +const spamTestText = ref(''); +const spamUseAi = ref(false); +const spamTesting = ref(false); +const spamTestResult = ref | null>(null); +const ruleForm = reactive({ type: 'keyword', pattern: '', score: 75 }); +const ruleSaving = ref(false); const shopForm = reactive({ item_key: '', name: '', cost: 0, desc: '', enabled: true }); const lotteryForm = reactive({ id: 0, title: '', prize: '', description: '', condition_type: 'all', condition_value: 0, draw_type: 'people', end_type: 'people', end_value: 3, max_participants: 3, draw_at: '', status: 'active' }); -const activePage = ref<'dashboard' | 'shop' | 'lottery' | 'bans'>('dashboard'); +const activePage = ref<'dashboard' | 'shop' | 'lottery' | 'bans' | 'rules'>('dashboard'); const activeTab = reactive({ shop: 'items', lottery: 'events' }); const toast = ref(''); const confirmState = reactive({ open: false, title: '', message: '', confirmText: '确认', danger: false, resolver: null as null | ((value: boolean) => void) }); @@ -95,10 +101,10 @@ function toDatetimeLocal(date: Date) { } const pageTitle = computed(() => ({ - dashboard: '群组安全总览', shop: '商城管理', lottery: '抽奖管理', bans: '封禁记录' + dashboard: '群组安全总览', shop: '商城管理', lottery: '抽奖管理', bans: '封禁记录', rules: '规则测试' }[activePage.value])); const pageDesc = computed(() => ({ - dashboard: '实时审计聊天、封禁、日志和运营活动', shop: '商品 CRUD 与兑换交易记录', lottery: '抽奖项目、开奖条件与参与记录', bans: '封禁审计与手动解封' + dashboard: '实时审计聊天、封禁、日志和运营活动', shop: '商品 CRUD 与兑换交易记录', lottery: '抽奖项目、开奖条件与参与记录', bans: '封禁审计与手动解封', rules: '检测广告/灰产文本并维护规则库' }[activePage.value])); function goPage(page: typeof activePage.value) { activePage.value = page; @@ -315,6 +321,37 @@ async function drawLottery(item: LotteryItem) { catch (e: any) { showToast(`开奖失败:${e.message}`); } finally { item._loading = false; } } + +async function testSpamText() { + if (!spamTestText.value.trim()) return showToast('请输入要检测的文本'); + if (spamTesting.value) return; + spamTesting.value = true; + try { + spamTestResult.value = await fetchJson('/api/spam-test', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ text: spamTestText.value, use_ai: spamUseAi.value }) + }); + } catch (e: any) { showToast(`检测失败:${e.message}`); } + finally { spamTesting.value = false; } +} +async function saveSpamRule() { + if (!ruleForm.pattern.trim()) return showToast('请输入规则内容'); + if (ruleSaving.value) return; + ruleSaving.value = true; + try { + const result = await fetchJson('/api/spam-rules', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(ruleForm) + }); + showToast(`规则已写入,关键词 ${result.rule_counts?.keywords || '-'} / 正则 ${result.rule_counts?.regex || '-'}`); + ruleForm.pattern = ''; + await testSpamText(); + } catch (e: any) { showToast(`写入规则失败:${e.message}`); } + finally { ruleSaving.value = false; } +} + function reloadByGroup() { loadChatHistory(); loadBans(); loadStats(); loadShopItems(); loadShopTransactions(); loadLotteries(); } function connectSSE() { @@ -485,6 +522,7 @@ onBeforeUnmount(() => { +