feat: generate spam rules from text with ai
This commit is contained in:
+40
-2
@@ -29,6 +29,7 @@ const spamTesting = ref(false);
|
||||
const spamTestResult = ref<Record<string, any> | null>(null);
|
||||
const ruleForm = reactive({ type: 'keyword', pattern: '', score: 75 });
|
||||
const ruleSaving = ref(false);
|
||||
const generatedRuleHint = ref('');
|
||||
const spamRules = reactive({ keywords: [] as Record<string, any>[], regex: [] as Record<string, any>[], adblock_domains: [] as Record<string, any>[], counts: { keywords: 0, regex: 0, adblock_domains: 0 } });
|
||||
const rulesLoading = ref(false);
|
||||
const ruleKeyword = ref('');
|
||||
@@ -224,7 +225,11 @@ async function logout() {
|
||||
async function fetchJson(url: string, options?: RequestInit) {
|
||||
const res = await fetch(url, options);
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok || data.ok === false) throw new Error(data.error || data.message || `HTTP ${res.status}`);
|
||||
if (!res.ok || data.ok === false) {
|
||||
const err: any = new Error(data.error || data.message || `HTTP ${res.status}`);
|
||||
err.data = data;
|
||||
throw err;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
async function loadChatHistory() {
|
||||
@@ -441,6 +446,7 @@ async function saveSpamRule() {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(ruleForm)
|
||||
});
|
||||
generatedRuleHint.value = '';
|
||||
showToast(`规则已写入,关键词 ${result.rule_counts?.keywords || '-'} / 正则 ${result.rule_counts?.regex || '-'}`);
|
||||
ruleForm.pattern = '';
|
||||
await loadSpamRules();
|
||||
@@ -449,6 +455,37 @@ async function saveSpamRule() {
|
||||
finally { ruleSaving.value = false; }
|
||||
}
|
||||
|
||||
async function addSpamTextToRules() {
|
||||
if (!spamTestText.value.trim()) return showToast('请输入要入库的聊天内容');
|
||||
if (ruleSaving.value) return;
|
||||
ruleSaving.value = true;
|
||||
try {
|
||||
const result = await fetchJson('/api/spam-rules/from-text', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: spamTestText.value, use_ai: spamUseAi.value })
|
||||
});
|
||||
const generated = result.generated_rule || {};
|
||||
ruleForm.type = result.type || generated.type || 'keyword';
|
||||
ruleForm.pattern = result.pattern || generated.pattern || '';
|
||||
ruleForm.score = Number(result.score || generated.score || 90);
|
||||
generatedRuleHint.value = `${generated.ai_used === false ? '兜底生成' : 'AI生成'}:${ruleForm.type === 'regex' ? '正则' : '关键词'} / ${ruleForm.score} / ${ruleForm.pattern}${generated.reason ? `(${generated.reason})` : ''}`;
|
||||
showToast(`已${generated.ai_used === false ? '兜底' : 'AI'}生成并写入规则:${ruleForm.pattern}`);
|
||||
await loadSpamRules();
|
||||
await testSpamText();
|
||||
} catch (e: any) {
|
||||
const generated = e?.data?.generated_rule;
|
||||
if (generated?.pattern) {
|
||||
ruleForm.type = generated.type || 'keyword';
|
||||
ruleForm.pattern = generated.pattern;
|
||||
ruleForm.score = Number(generated.score || 90);
|
||||
generatedRuleHint.value = `已生成但未写入:${ruleForm.pattern}${generated.reason ? `(${generated.reason})` : ''}`;
|
||||
}
|
||||
showToast(`内容入库失败:${e.message}`);
|
||||
}
|
||||
finally { ruleSaving.value = false; }
|
||||
}
|
||||
|
||||
function reloadByGroup() { loadChatHistory(); loadBans(); loadStats(); loadShopItems(); loadShopTransactions(); loadLotteries(); }
|
||||
|
||||
function connectSSE() {
|
||||
@@ -878,8 +915,9 @@ onBeforeUnmount(() => {
|
||||
<div class="rule-action-bar">
|
||||
<label class="switch-line compact"><input v-model="spamUseAi" type="checkbox" /> AI 兜底</label>
|
||||
<SButtonLoading color="primary" :loading="spamTesting" :disabled="spamTesting" @click="testSpamText">开始检测</SButtonLoading>
|
||||
<SButtonLoading variant="outline" color="warning" :loading="ruleSaving" :disabled="ruleSaving || !spamTestText.trim()" @click="ruleForm.pattern = spamTestText; ruleForm.type = 'keyword'; ruleForm.score = 90; saveSpamRule()">内容入库</SButtonLoading>
|
||||
<SButtonLoading variant="outline" color="warning" :loading="ruleSaving" :disabled="ruleSaving || !spamTestText.trim()" @click="addSpamTextToRules">AI生成规则并入库</SButtonLoading>
|
||||
</div>
|
||||
<div v-if="generatedRuleHint" class="generated-rule-hint">{{ generatedRuleHint }}</div>
|
||||
<div class="rule-write-grid inline-rule-writer compact-rule-writer">
|
||||
<label>类型<select v-model="ruleForm.type" class="form-select"><option value="keyword">关键词</option><option value="regex">正则</option></select></label>
|
||||
<label>分数<SInput v-model="ruleForm.score" type="number" placeholder="75" /></label>
|
||||
|
||||
@@ -549,3 +549,5 @@ body { background: #f3f6fb; }
|
||||
.overview-ban-list .ban-meta { width: 100%; display: flex !important; align-items: center; flex-wrap: nowrap !important; gap: 5px !important; font-size: 10px !important; line-height: 1.2; white-space: nowrap !important; overflow: hidden; }
|
||||
.overview-ban-list .ban-meta > span { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.overview-ban-list .ban-meta .s-button { height: 22px; min-height: 22px; padding: 0 8px; font-size: 10px; }
|
||||
|
||||
.generated-rule-hint { margin-top: 6px; padding: 6px 9px; border-radius: 10px; background: #f0fdf4; color: #166534; border: 1px solid rgba(34,197,94,.24); font-size: 12px; line-height: 1.35; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
|
||||
Reference in New Issue
Block a user