feat: add warn category for minor rule violations
- Add "warn" status between "clean" and "flagged" for minor violations - Update AI analyzer system prompt with community rules and warn category - Warn: profanity, OOT, tone issues - requires warning but not deletion - Flagged: NSFW, illegal, hacking, scam, harassment, violence, SARA - requires review/deletion - Update types to support warn status in MessageRecord and AIAnalysisUpdate - Update client UI to show three panels: All Messages, Warned, Flagged - Warned messages show in right-top panel for quick review - Flagged messages show in right-bottom panel for moderation action This resolves: - Need to distinguish between minor and severe violations - Moderators can now warn users before taking action - Better moderation workflow with three-tier system Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
54a4096323
commit
93eb2303c7
4 files changed
+37
-16
No files matched your search
+3
-3
@@ -47,7 +47,7 @@
|
||||
<div class="content-card" style="margin-top:18px"><div class="card-title"><h2>Participants</h2><span class="mini">speaking now</span></div><div id="userList" class="participants"></div></div>
|
||||
</section>
|
||||
|
||||
<section id="text" class="tab-content"><div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;height:100%"><div class="content-card"><div class="card-title"><h2>All Messages</h2><span class="mini">all captures</span></div><div id="textList" class="feed"><div class="empty">Select channel to view text captures</div></div></div><div class="content-card"><div class="card-title"><h2>Flagged</h2><span class="mini">AI flagged only</span></div><div id="flaggedList" class="feed"><div class=Line truncated
|
||||
<section id="text" class="tab-content"><div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;height:100%"><div class="content-card"><div class="card-title"><h2>All Messages</h2><span class="mini">all captures</span></div><div id="textList" class="feed"><div class="empty">Select channel to view text captures</div></div></div><div style="display:grid;grid-template-rows:1fr 1fr;gap:16px"><div class="content-card"><div class="card-title"><h2>Warned</h2><span class="mini">minor violatiLine truncated
|
||||
</main>
|
||||
|
||||
<script>
|
||||
@@ -71,7 +71,7 @@
|
||||
const SAMPLE_RATE = 24000;
|
||||
const CHANNELS = 1;
|
||||
const el = {
|
||||
wsDot: document.getElementById('wsDot'), wsStatusText: document.getElementById('wsStatusText'), activeTabLabel: document.getElementById('activeTabLabel'), errorBox: document.getElementById('errorBox'), guildSelect: document.getElementById('guildSelect'), channelSelect: document.getElementById('channelSelect'), channelFilter: document.getElementById('channelFilter'), joinVoiceBtn: document.getElementById('joinVoiceBtn'), disconnectVoiceBtn: document.getElementById('disconnectVoiceBtn'), voLine truncated
|
||||
wsDot: document.getElementById('wsDot'), wsStatusText: document.getElementById('wsStatusText'), activeTabLabel: document.getElementById('activeTabLabel'), errorBox: document.getElementById('errorBox'), guildSelect: document.getElementById('guildSelect'), channelSelect: document.getElementById('channelSelect'), channelFilter: document.getElementById('channelFilter'), joinVoiceBtn: document.getElementById('joinVoiceBtn'), disconnectVoiceBtn: document.getElementById('disconnectVoiceBtn'), voLine truncated
|
||||
};
|
||||
for (let i = 0; i < 32; i++) { const bar = document.createElement('div'); bar.className = 'bar'; el.visualizer.appendChild(bar); }
|
||||
const bars = [...document.querySelectorAll('.bar')];
|
||||
@@ -130,7 +130,7 @@
|
||||
|
||||
function renderUsers(users) { el.userList.replaceChildren(); if (users.length === 0) return appendEmpty(el.userList, 'No active speakers'); for (const user of users) { const row = document.createElement('div'); row.className = `user-item${user.speaking ? ' speaking' : ''}`; const img = document.createElement('img'); img.src = user.avatar || ''; img.alt = ''; const name = document.createElement('span'); name.textContent = user.username; row.append(img, name); el.userList.appendChild(row); } Line truncated
|
||||
async function fetchText() { if (!state.selectedTextChannel) return renderText(); const result = await apiRequest(`/api/messages?channel=${encodeURIComponent(state.selectedTextChannel)}&type=text&limit=80`); state.text = result.data || []; renderText(); }
|
||||
function renderText() { el.textList.replaceChildren(); el.flaggedList.replaceChildren(); if (!state.selectedTextChannel) { appendEmpty(el.textList, 'Select channel to view text captures'); appendEmpty(el.flaggedList, 'No flagged messages'); return; } if (state.text.length === 0) { appendEmpty(el.textList, 'No text captures yet'); appendEmpty(el.flaggedList, 'No flagged messages'); return; } const flaggedMessages = []; for (const msg of state.text) { const metadata = parseMetadata(msg.metadaLine truncated
|
||||
function renderText() { el.textList.replaceChildren(); el.warnedList.replaceChildren(); el.flaggedList.replaceChildren(); if (!state.selectedTextChannel) { appendEmpty(el.textList, 'Select channel to view text captures'); appendEmpty(el.warnedList, 'No warned messages'); appendEmpty(el.flaggedList, 'No flagged messages'); return; } if (state.text.length === 0) { appendEmpty(el.textList, 'No text captures yet'); appendEmpty(el.warnedList, 'No warned messages'); appendEmpty(el.flaggedList, 'NLine truncated
|
||||
function appendAIAnalysis(card, msg) { const status = msg.ai_status || 'pending'; const wrap = document.createElement('div'); wrap.className = 'badges'; const badge = document.createElement('span'); badge.className = `badge ${status === 'flagged' ? 'delete' : status === 'clean' ? 'edit' : ''}`; badge.textContent = `AI: ${status}`; wrap.appendChild(badge); if (msg.ai_moderation_flags) { const flags = document.createElement('span'); flags.className = 'badge delete'; try { flags.textContent = Line truncated
|
||||
function appendMedia(card, metadata) { const stickers = document.createElement('div'); stickers.className = 'sticker-strip'; for (const sticker of metadata.stickers || []) { const img = document.createElement('img'); img.className = 'sticker-img'; img.src = sticker.url; img.alt = sticker.name; stickers.appendChild(img); } if (stickers.childElementCount) card.appendChild(stickers); const embeds = document.createElement('div'); embeds.className = 'feed'; for (const embed of metadata.embeds ||Line truncated
|
||||
|
||||
|
||||
Reference in new issue
Block a user