/** * MetricsDashboard — Agent Metrics 可视化仪表盘 * 展示效率概览、工具热力图、Token 趋势 */ import { getMetricsHistory, aggregateMetrics, generateImprovementSuggestions } from '../services/agent-metrics.js'; import { logInfo } from '../services/log-service.js'; let metricsModalEl: HTMLElement | null = null; export function initMetricsDashboard(): void { metricsModalEl = document.querySelector('#metricsDashboardModal'); document.querySelector('#btnMetrics')?.addEventListener('click', openMetricsDashboard); document.querySelector('#btnCloseMetrics')?.addEventListener('click', closeMetricsDashboard); if (metricsModalEl) { metricsModalEl.addEventListener('click', (e) => { if (e.target === metricsModalEl) closeMetricsDashboard(); }); } } function openMetricsDashboard(): void { if (!metricsModalEl) return; metricsModalEl.style.display = ''; renderMetricsDashboard(); } function closeMetricsDashboard(): void { if (metricsModalEl) metricsModalEl.style.display = 'none'; } function renderMetricsDashboard(): void { const history = getMetricsHistory(); const agg = aggregateMetrics(); const suggestions = generateImprovementSuggestions(); // ── 效率概览 ── const overviewEl = document.querySelector('#mdOverview'); if (overviewEl) { if (agg.totalSessions === 0) { overviewEl.innerHTML = '

暂无度量数据,开始对话后将有统计数据

'; } else { const successRate = (agg.toolSuccessRate * 100).toFixed(1); const avgIter = agg.avgIterationsPerTask.toFixed(1); const tokenEff = agg.tokenEfficiency.toFixed(2); overviewEl.innerHTML = `
${agg.totalSessions}
会话总数
${avgIter}
平均迭代轮次
${successRate}%
工具成功率
${tokenEff}
Token 效率
`; } } // ── 工具热力图 ── const toolHeatmapEl = document.querySelector('#mdToolHeatmap'); if (toolHeatmapEl) { if (history.length === 0) { toolHeatmapEl.innerHTML = '

暂无工具调用数据

'; } else { const toolStats = new Map(); for (const session of history) { for (const tc of session.toolCalls) { if (!toolStats.has(tc.name)) toolStats.set(tc.name, { success: 0, error: 0, cancelled: 0 }); const stat = toolStats.get(tc.name)!; if (tc.status === 'success') stat.success++; else if (tc.status === 'error') stat.error++; else stat.cancelled++; } } const sortedTools = [...toolStats.entries()].sort((a, b) => { const aTotal = a[1].success + a[1].error + a[1].cancelled; const bTotal = b[1].success + b[1].error + b[1].cancelled; return bTotal - aTotal; }); const maxTotal = sortedTools.length > 0 ? Math.max(...sortedTools.map(([_, s]) => s.success + s.error + s.cancelled)) : 1; toolHeatmapEl.innerHTML = sortedTools.slice(0, 20).map(([name, stat]) => { const total = stat.success + stat.error + stat.cancelled; const successPct = (stat.success / maxTotal * 100).toFixed(1); const errorPct = (stat.error / maxTotal * 100).toFixed(1); const cancelPct = (stat.cancelled / maxTotal * 100).toFixed(1); return `
${name}
${stat.success}/${total}
`; }).join(''); } } // ── Token 趋势 ── const tokenTrendEl = document.querySelector('#mdTokenTrend'); if (tokenTrendEl) { if (history.length === 0) { tokenTrendEl.innerHTML = '

暂无 Token 趋势数据

'; } else { const recent = history.slice(-20); const maxTokens = Math.max(...recent.map(s => s.totalInputTokens + s.totalOutputTokens), 1); tokenTrendEl.innerHTML = recent.map((s, i) => { const inputPct = (s.totalInputTokens / maxTokens * 100).toFixed(1); const outputPct = (s.totalOutputTokens / maxTokens * 100).toFixed(1); return `
${i + 1}
`; }).join(''); } } // ── 改进建议 ── const suggestionsEl = document.querySelector('#mdSuggestions'); if (suggestionsEl) { if (suggestions.length === 0) { suggestionsEl.innerHTML = '

暂无改进建议

'; } else { suggestionsEl.innerHTML = suggestions.map(s => `
${s.severity} ${s.message}
`).join(''); } } logInfo('Metrics 仪表盘已刷新', `${history.length} 个会话记录`); }