data&device ready

This commit is contained in:
“yuanmengy”
2026-06-05 17:11:56 +08:00
commit a225272d1e
29 changed files with 49446 additions and 0 deletions

273
scripts/badcase.js Normal file
View File

@@ -0,0 +1,273 @@
/**
* 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: '添加',
});
});
});