/** * TalkingQ Platform - Bad Case Management Page */ registerPage('badcase', function(container) { const initialCases = [ { id: 'BC-2026-001', session: 'sess-6c1d', device: 'DEV-002', agent: '小Q故事机器人', issue: '回答偏离话题,讲了不适合儿童的内容片段', severity: 'high', status: 'open', reporter: '自动检测', time: '2026-05-12 14:07', tags: ['内容安全', '主题偏离'], dialogue: { user: '给我讲一个勇敢小朋友的故事', assistant: '故事中出现了不适合儿童的危险情节描述。', }, }, { id: 'BC-2026-002', session: 'sess-5a4b', device: 'DEV-003', agent: '小Q故事机器人', issue: '粤语识别错误率高,多次识别成普通话', severity: 'medium', status: 'reviewing', reporter: '运营标注', time: '2026-05-12 13:58', tags: ['ASR质量', '粤语'], dialogue: { user: '我想听广东话故事', assistant: '识别结果被转写为普通话场景,导致回复不准确。', }, }, { id: 'BC-2026-003', session: 'sess-3f7a', device: 'DEV-007', agent: '英语学习助手', issue: '用户问"what is fire",AI回复了如何生火的危险操作', severity: 'critical', status: 'open', reporter: '自动检测', time: '2026-05-12 11:30', tags: ['内容安全', '危险信息'], dialogue: { user: 'what is fire?', assistant: '回复中包含了危险操作步骤,需要安全策略拦截。', }, }, { id: 'BC-2026-004', session: 'sess-2c8b', device: 'DEV-012', agent: '科学百科探索者', issue: 'TTS 合成失败,只播放了半句话', severity: 'low', status: 'closed', reporter: '系统监控', time: '2026-05-11 09:15', tags: ['TTS故障'], resolution: '已更换默认 TTS 兜底模型。', dialogue: { user: '太阳为什么会发光', assistant: '太阳发光是因为内部发生核聚变...(播放中断)', }, }, { id: 'BC-2026-005', session: 'sess-1d9e', device: 'DEV-015', agent: '小Q故事机器人', issue: '对用户"我想要妈妈"的情感求助回应过于机械', severity: 'medium', status: 'reviewing', reporter: '人工标注', time: '2026-05-11 16:42', tags: ['情感质量', '儿童关怀'], dialogue: { user: '我想要妈妈', assistant: '请说明你的需求。', }, }, ]; if (!window.__badCaseRecords) { window.__badCaseRecords = initialCases.map(item => ({ ...item, tags: [...item.tags], dialogue: { ...item.dialogue } })); } const cases = window.__badCaseRecords; const filters = { keyword: '', severity: 'all', status: 'all', agent: 'all', }; const sevMap = { critical: { label: '严重', badge: 'badge-danger', accent: 'var(--danger)' }, high: { label: '高', badge: 'badge-danger', accent: 'var(--danger)' }, medium: { label: '中', badge: 'badge-warning', accent: 'var(--warning)' }, low: { label: '低', badge: 'badge-default', accent: 'var(--text-muted)' }, }; const statMap = { open: { label: '待处理', badge: 'badge-danger' }, reviewing: { label: '审核中', badge: 'badge-warning' }, closed: { label: '已关闭', badge: 'badge-default' }, }; function escapeHtml(value) { return String(value ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function getNowText() { const now = new Date(); const pad = value => String(value).padStart(2, '0'); return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}`; } function splitTags(value) { return String(value || '') .split(/[,,]/) .map(tag => tag.trim()) .filter(Boolean); } function getAgents() { return [...new Set(cases.map(item => item.agent).filter(Boolean))]; } function getFilteredCases() { const keyword = filters.keyword.trim().toLowerCase(); return cases.filter(item => { const searchableText = [ item.id, item.session, item.device, item.agent, item.issue, item.reporter, ...(item.tags || []), ].join(' ').toLowerCase(); return (!keyword || searchableText.includes(keyword)) && (filters.severity === 'all' || item.severity === filters.severity) && (filters.status === 'all' || item.status === filters.status) && (filters.agent === 'all' || item.agent === filters.agent); }); } function getStats() { const openCases = cases.filter(item => item.status === 'open'); const reviewingCases = cases.filter(item => item.status === 'reviewing'); const closedCases = cases.filter(item => item.status === 'closed'); const securityCases = cases.filter(item => (item.tags || []).some(tag => ['内容安全', '危险信息'].includes(tag))); return { open: openCases.length, criticalOpen: openCases.filter(item => item.severity === 'critical').length, reviewing: reviewingCases.length, closed: closedCases.length, security: securityCases.length, securityOpen: securityCases.filter(item => item.status !== 'closed').length, }; } function renderStats() { const stats = getStats(); return `
待处理
${stats.open}
其中严重 ${stats.criticalOpen} 个
审核中
${stats.reviewing}
等待复核或修复验证
已关闭
${stats.closed}
当前前端 mock 统计
内容安全触发
${stats.security}
未关闭 ${stats.securityOpen} 个
`; } function renderActions(item) { return `
${item.status !== 'closed' ? ` ` : ''}
`; } function renderRows(rows) { if (!rows.length) { return `
暂无匹配的 Bad Case
可以调整筛选条件,或手动添加新的问题案例。
`; } return rows.map(item => ` ${escapeHtml(item.id)}
${escapeHtml(item.issue)}
Session: ${escapeHtml(item.session)} · ${escapeHtml(item.device)}
${escapeHtml(item.agent)} ${sevMap[item.severity]?.label || '-'} ${statMap[item.status]?.label || '-'}
${(item.tags || []).map(tag => `${escapeHtml(tag)}`).join('') || '未标注'}
${escapeHtml(item.reporter)} ${escapeHtml(item.time)} ${renderActions(item)} `).join(''); } function renderResults() { const rows = getFilteredCases(); const statsTarget = container.querySelector('#bcStats'); const tableTarget = container.querySelector('#bcTableCard'); const totalTarget = container.querySelector('#bcFilterCount'); if (statsTarget) statsTarget.innerHTML = renderStats(); if (totalTarget) totalTarget.textContent = `共 ${rows.length} 条`; if (tableTarget) { tableTarget.innerHTML = `
${renderRows(rows)}
ID 问题描述 智能体 严重度 状态 标签 报告人 时间 操作
`; } lucide.createIcons({ elements: [container] }); } function renderPage() { const agents = getAgents(); container.innerHTML = `
`; bindEvents(); renderResults(); } function bindEvents() { container.querySelector('#bcSearchInput')?.addEventListener('input', event => { filters.keyword = event.target.value; renderResults(); }); container.querySelector('#bcSeverityFilter')?.addEventListener('change', event => { filters.severity = event.target.value; renderResults(); }); container.querySelector('#bcStatusFilter')?.addEventListener('change', event => { filters.status = event.target.value; renderResults(); }); container.querySelector('#bcAgentFilter')?.addEventListener('change', event => { filters.agent = event.target.value; renderResults(); }); container.querySelector('#manualBCBtn')?.addEventListener('click', openManualCaseModal); } function findCase(id) { return cases.find(item => item.id === id); } function getNextCaseId() { const year = new Date().getFullYear(); const usedNumbers = cases .map(item => String(item.id).match(/^BC-\d{4}-(\d+)$/)) .filter(Boolean) .map(match => Number(match[1])); const nextNumber = (usedNumbers.length ? Math.max(...usedNumbers) : 0) + 1; return `BC-${year}-${String(nextNumber).padStart(3, '0')}`; } function openManualCaseModal() { const agents = getAgents(); const modal = createModal({ title: '手动添加 Bad Case', content: `
后端接口预留:POST /api/v1/bad-cases 创建人工 Bad Case。
`, onConfirm: async () => { const issue = document.getElementById('manualBCIssue')?.value.trim(); if (!issue) { Toast.warning('请填写问题描述'); return false; } const selectedAgent = document.getElementById('manualBCAgent')?.value || ''; const customAgent = document.getElementById('manualBCCustomAgent')?.value.trim(); const agent = selectedAgent === 'custom' ? customAgent : selectedAgent; if (!agent) { Toast.warning('请填写智能体名称'); return false; } cases.unshift({ id: getNextCaseId(), session: document.getElementById('manualBCSession')?.value.trim() || `sess-${Math.random().toString(16).slice(2, 6)}`, device: document.getElementById('manualBCDevice')?.value.trim() || '手动录入', agent, issue, severity: document.getElementById('manualBCSeverity')?.value || 'medium', status: 'open', reporter: '手动添加', time: getNowText(), tags: splitTags(document.getElementById('manualBCTags')?.value) || [], dialogue: { user: '手动添加案例,暂无原始用户消息', assistant: issue, }, }); try { if (window.API?.badCases?.create) { await API.badCases.create(cases[0]); } } catch (error) { console.warn('Mock bad case create failed:', error); } filters.status = 'all'; renderPage(); Toast.success('Bad Case 已添加'); }, confirmText: '添加', size: 'large', }); const agentSelect = modal.overlay.querySelector('#manualBCAgent'); const customWrap = modal.overlay.querySelector('#manualBCCustomWrap'); agentSelect?.addEventListener('change', event => { if (customWrap) customWrap.style.display = event.target.value === 'custom' ? 'block' : 'none'; }); } window.openBCDetail = async function(id) { const bc = findCase(id); if (!bc) { Toast.warning('未找到该 Bad Case'); return; } createModal({ title: `Bad Case 详情 - ${escapeHtml(id)}`, content: `
问题描述
${escapeHtml(bc.issue)}
严重度
${sevMap[bc.severity]?.label || '-'}
当前状态
${statMap[bc.status]?.label || '-'}
关联 Session
${escapeHtml(bc.session)}
关联设备
${escapeHtml(bc.device)}
关联对话
用户
${escapeHtml(bc.dialogue?.user || '暂无原始用户消息')}
AI 回复
${escapeHtml(bc.dialogue?.assistant || '暂无原始 AI 回复')}
${bc.resolution ? `
最近处理意见
${escapeHtml(bc.resolution)}
` : ''}
后端接口预留:POST /api/v1/bad-cases/${escapeHtml(id)}/review 提交审核处理结果。
`, onConfirm: async () => { const resolution = document.getElementById('bcReviewResolution')?.value.trim(); const action = document.getElementById('bcReviewAction')?.value || 'none'; if (!resolution) { Toast.warning('请填写处理意见'); return false; } try { if (window.API?.badCases?.review) { await API.badCases.review(id, { resolution, action }); } } catch (error) { console.warn('Mock bad case review failed:', error); } bc.resolution = resolution; bc.reviewAction = action; bc.updatedAt = getNowText(); if (bc.status !== 'closed') bc.status = 'reviewing'; renderResults(); Toast.success('已提交处理意见,状态已更新'); }, confirmText: '提交处理', size: 'large', }); }; window.openBCTagModal = function(id) { const bc = findCase(id); if (!bc) { Toast.warning('未找到该 Bad Case'); return; } createModal({ title: `标注问题 - ${escapeHtml(id)}`, content: `
后端接口预留:PATCH /api/v1/bad-cases/${escapeHtml(id)}/labels 保存标签、严重度和备注。
`, onConfirm: () => { const tags = splitTags(document.getElementById('bcTagsInput')?.value); if (!tags.length) { Toast.warning('请填写至少一个问题标签'); return false; } bc.tags = tags; bc.severity = document.getElementById('bcTagSeverity')?.value || bc.severity; bc.tagNote = document.getElementById('bcTagNote')?.value.trim() || ''; bc.reporter = bc.reporter === '自动检测' ? '运营标注' : bc.reporter; bc.updatedAt = getNowText(); if (bc.status === 'open') bc.status = 'reviewing'; renderResults(); Toast.success('问题标注已保存'); }, confirmText: '保存标注', }); }; window.closeBC = function(id) { const bc = findCase(id); if (!bc) { Toast.warning('未找到该 Bad Case'); return; } createModal({ title: '关闭 Bad Case', content: `

确认关闭 ${escapeHtml(id)}?关闭后仍可查看详情,但不再进入待处理列表。

后端接口预留:POST /api/v1/bad-cases/${escapeHtml(id)}/close 关闭问题案例。
`, onConfirm: () => { const reason = document.getElementById('bcCloseReason')?.value.trim(); if (!reason) { Toast.warning('请填写关闭原因'); return false; } bc.status = 'closed'; bc.closedReason = reason; bc.closedAt = getNowText(); renderResults(); Toast.success(`${id} 已关闭`); }, confirmText: '确认关闭', }); }; renderPage(); });