Files
TalkingQ_Dashboard/scripts/badcase.js
“yuanmengy” a225272d1e data&device ready
2026-06-05 17:11:56 +08:00

274 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* TalkingQ Platform - Bad Case Management Page
*/
registerPage('badcase', function(container) {
const mockCases = [
{ 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: ['内容安全', '主题偏离'] },
{ 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质量', '粤语'] },
{ 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: ['内容安全', '危险信息'] },
{ 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故障'] },
{ 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: ['情感质量', '儿童关怀'] },
];
const sevMap = { critical: { label: '严重', badge: 'badge-danger' }, high: { label: '高', badge: 'badge-danger' }, medium: { label: '中', badge: 'badge-warning' }, low: { label: '低', badge: 'badge-default' } };
const statMap = { open: { label: '待处理', badge: 'badge-danger' }, reviewing: { label: '审核中', badge: 'badge-warning' }, closed: { label: '已关闭', badge: 'badge-default' } };
container.innerHTML = `
<div class="page-header">
<div>
<div class="page-title">Bad Case 管理</div>
<div class="page-desc">收集、标注和修复 AI 回复质量问题,持续优化智能体表现</div>
</div>
<div class="page-actions">
<button class="btn btn-outline" onclick="Toast.info('导出 Bad Case 数据...')">
<i data-lucide="download"></i> 导出
</button>
<button class="btn btn-primary" id="manualBCBtn">
<i data-lucide="plus"></i> 手动添加
</button>
</div>
</div>
<!-- Stats -->
<div class="kpi-grid" style="grid-template-columns:repeat(4,1fr);margin-bottom:20px">
<div class="kpi-card">
<div class="kpi-icon"><i data-lucide="alert-triangle"></i></div>
<div class="kpi-label">待处理</div>
<div class="kpi-value" style="color:var(--danger)">23</div>
<div class="kpi-change down">其中严重 3 个</div>
</div>
<div class="kpi-card">
<div class="kpi-icon"><i data-lucide="search"></i></div>
<div class="kpi-label">审核中</div>
<div class="kpi-value" style="color:var(--warning)">18</div>
<div class="kpi-change">平均处理 2.3h</div>
</div>
<div class="kpi-card">
<div class="kpi-icon"><i data-lucide="check-circle"></i></div>
<div class="kpi-label">本月已关闭</div>
<div class="kpi-value" style="color:var(--success)">142</div>
<div class="kpi-change up">解决率 86%</div>
</div>
<div class="kpi-card">
<div class="kpi-icon"><i data-lucide="shield-alert"></i></div>
<div class="kpi-label">内容安全触发</div>
<div class="kpi-value" style="color:var(--danger)">7</div>
<div class="kpi-change down">需优先处理</div>
</div>
</div>
<!-- Filters -->
<div class="filter-bar">
<div class="search-input">
<i data-lucide="search"></i>
<input type="text" placeholder="搜索 Bad Case 内容/ID...">
</div>
<select class="filter-select">
<option>全部严重度</option>
<option>严重</option>
<option>高</option>
<option>中</option>
<option>低</option>
</select>
<select class="filter-select">
<option>全部状态</option>
<option>待处理</option>
<option>审核中</option>
<option>已关闭</option>
</select>
<select class="filter-select">
<option>全部智能体</option>
<option>小Q故事机器人</option>
<option>英语学习助手</option>
<option>科学百科探索者</option>
</select>
</div>
<!-- Table -->
<div class="card" style="padding:0;overflow:hidden">
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>ID</th>
<th>问题描述</th>
<th>智能体</th>
<th>严重度</th>
<th>状态</th>
<th>标签</th>
<th>报告人</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${mockCases.map(c => `
<tr>
<td style="font-family:monospace;font-size:12px;color:var(--primary)">${c.id}</td>
<td style="max-width:280px">
<div style="font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis" title="${c.issue}">${c.issue}</div>
<div style="font-size:11px;color:var(--text-muted)">Session: ${c.session} · ${c.device}</div>
</td>
<td style="font-size:12px">${c.agent}</td>
<td><span class="badge ${sevMap[c.severity]?.badge}">${sevMap[c.severity]?.label}</span></td>
<td><span class="badge ${statMap[c.status]?.badge}">${statMap[c.status]?.label}</span></td>
<td>
<div style="display:flex;gap:4px;flex-wrap:wrap">
${c.tags.map(t => `<span class="badge badge-purple" style="font-size:11px">${t}</span>`).join('')}
</div>
</td>
<td style="font-size:12px">${c.reporter}</td>
<td style="font-size:12px;color:var(--text-muted)">${c.time}</td>
<td>
<div style="display:flex;gap:4px">
<button class="btn btn-ghost btn-sm" data-tooltip="查看详情" onclick="openBCDetail('${c.id}')"><i data-lucide="eye"></i></button>
${c.status !== 'closed' ? `
<button class="btn btn-ghost btn-sm" data-tooltip="标注问题" onclick="openBCTagModal('${c.id}')"><i data-lucide="tag"></i></button>
<button class="btn btn-outline btn-sm" style="color:var(--success)" data-tooltip="关闭案例" onclick="closeBC('${c.id}', this)">关闭</button>
` : ''}
</div>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`;
lucide.createIcons({ elements: [container] });
window.openBCDetail = (id) => {
const bc = mockCases.find(c => c.id === id);
if (!bc) return;
createModal({
title: `Bad Case 详情 - ${id}`,
content: `
<div style="margin-bottom:12px">
<div class="form-label">问题描述</div>
<div style="padding:10px;background:#fff2f0;border-radius:var(--radius-sm);font-size:13px">${bc.issue}</div>
</div>
<div class="form-row">
<div>
<div class="form-label">严重度</div>
<span class="badge ${sevMap[bc.severity]?.badge}">${sevMap[bc.severity]?.label}</span>
</div>
<div>
<div class="form-label">关联 Session</div>
<span style="font-family:monospace;font-size:12px;color:var(--primary)">${bc.session}</span>
</div>
</div>
<div class="form-group" style="margin-top:12px">
<label class="form-label">处理意见</label>
<textarea class="form-control" placeholder="描述如何修复此问题(例如:调整 Prompt 中的内容审核规则)..."></textarea>
</div>
<div class="form-group">
<label class="form-label">关联 Prompt 修改</label>
<select class="form-control">
<option>不关联</option>
<option>修改系统 Prompt - 内容安全规则</option>
<option>修改 Prompt - 情感回应策略</option>
</select>
</div>
<div class="api-note">
<i data-lucide="info"></i>
<span>处理后通过 <code>POST /api/v1/bad-cases/${id}/review</code> 提交审核结果</span>
</div>`,
onConfirm: async () => {
await API.badCases.review(id, { resolution: 'fixed' });
Toast.success('已提交处理意见');
},
confirmText: '提交处理',
});
};
window.openBCTagModal = (id) => {
const bc = mockCases.find(c => c.id === id);
if (!bc) return;
createModal({
title: `标注问题 - ${id}`,
content: `
<div class="form-group">
<label class="form-label">问题标签<span class="required">*</span></label>
<input type="text" class="form-control" id="bcTagsInput" value="${bc.tags.join(', ')}" placeholder="例:内容安全, ASR质量">
</div>
<div class="form-group">
<label class="form-label">标注备注</label>
<textarea class="form-control" rows="3" id="bcTagNote" placeholder="补充问题原因或建议修复方向"></textarea>
</div>`,
onConfirm: () => {
const tags = document.getElementById('bcTagsInput')?.value.trim();
if (!tags) {
Toast.warning('请填写至少一个问题标签');
return false;
}
bc.tags = tags.split(',').map(t => t.trim()).filter(Boolean);
Toast.success('问题标注已保存');
},
confirmText: '保存标注',
});
};
window.closeBC = (id, btn) => {
const bc = mockCases.find(c => c.id === id);
if (!bc) return;
createModal({
title: '关闭 Bad Case',
content: `
<p style="font-size:14px;margin-bottom:10px">确认关闭 <strong>${id}</strong></p>
<div class="form-group">
<label class="form-label">关闭原因</label>
<textarea class="form-control" rows="3" id="bcCloseReason" placeholder="说明问题已如何处理"></textarea>
</div>`,
onConfirm: () => {
bc.status = 'closed';
const row = btn?.closest('tr');
const statusCell = row?.querySelector('td:nth-child(5)');
if (statusCell) statusCell.innerHTML = '<span class="badge badge-default">已关闭</span>';
const actions = btn?.closest('div');
if (actions) actions.innerHTML = `<button class="btn btn-ghost btn-sm" data-tooltip="查看详情" onclick="openBCDetail('${id}')"><i data-lucide="eye"></i></button>`;
lucide.createIcons();
Toast.success(`${id} 已关闭`);
},
confirmText: '确认关闭',
});
};
document.getElementById('manualBCBtn')?.addEventListener('click', () => {
createModal({
title: '手动添加 Bad Case',
content: `
<div class="form-group">
<label class="form-label">关联 Session ID</label>
<input type="text" class="form-control" id="manualBCSession" placeholder="sess-xxxxxx">
</div>
<div class="form-group">
<label class="form-label">问题描述<span class="required">*</span></label>
<textarea class="form-control" id="manualBCIssue" placeholder="描述 AI 回复的具体问题..."></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">严重度</label>
<select class="form-control">
<option>低</option><option>中</option><option>高</option><option>严重</option>
</select>
</div>
<div class="form-group">
<label class="form-label">问题标签</label>
<input type="text" class="form-control" placeholder="内容安全, ASR质量...">
</div>
</div>`,
onConfirm: async () => {
const issue = document.getElementById('manualBCIssue')?.value.trim();
if (!issue) {
Toast.warning('请填写问题描述');
return false;
}
Toast.success('Bad Case 已添加');
},
confirmText: '添加',
});
});
});