// Agent Insights — live AI-agent usage analytics. // Answers: who I chat with most, who spends the most time, who's best // suited for what, and which models I use per agent. All derived from // real data via /api/agent-insights. function fmtDuration(sec) { sec = Math.max(0, Math.round(sec || 0)); const h = Math.floor(sec / 3600); const m = Math.floor((sec % 3600) / 60); const s = sec % 60; if (h > 0) return `${h}h ${m}m`; if (m > 0) return `${m}m ${s}s`; return `${s}s`; } function agentColor(agent) { const palette = { opencode: '#6c5ce7', hermes: '#00b894', gemini: '#0984e3', jarvis: '#fd79a8', kilocode: '#e17055', codex: '#fdcb6e', system: '#636e72', test: '#b2bec3', }; return palette[agent] || '#74b9ff'; } let _charts = {}; function destroyCharts() { Object.values(_charts).forEach(c => { try { c.destroy(); } catch (e) {} }); _charts = {}; } async function renderAgentInsights() { const content = document.getElementById('pageContent'); content.innerHTML = `
Analyzing agent usage…
`; let data; try { data = await api.getAgentInsights(); } catch (err) { document.getElementById('insightsBody').innerHTML = `
⚠
Could not load insights
${escapeHtml(err.message)}
`; return; } const t = data.totals || {}; const pa = data.per_agent || {}; const body = document.getElementById('insightsBody'); body.innerHTML = `
💬
${(t.total_user_turns||0).toLocaleString()}
Your chat turns
⏱
${fmtDuration(t.total_time_seconds)}
Total agent time
🤖
${t.agents_observed||0}
Agents observed
💰
$${(t.total_cost||0).toFixed(4)}
Tracked cost
💬 Most chatted-with agents
⏱ Agents spending the most time
🎯 Best suited for (derived from registry + router keywords + roles)
🧠 Models used per agent
📊 Per-agent breakdown

Generated ${formatDate(data.generated_at)} · data: chat-history, cost-history, agent-time, agent-registry, router-keywords

`; // Charts const chats = (data.most_chatted || []).slice(0, 8); const times = (data.most_time || []).slice(0, 8); _charts.chats = new Chart(document.getElementById('chatsChart'), { type: 'bar', data: { labels: chats.map(c => c.display_name || c.agent), datasets: [{ label: 'Your chat turns', data: chats.map(c => c.user_turns), backgroundColor: chats.map(c => agentColor(c.agent)), borderRadius: 6, }], }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, ticks: { precision: 0 } } } }, }); _charts.time = new Chart(document.getElementById('timeChart'), { type: 'bar', data: { labels: times.map(c => c.display_name || c.agent), datasets: [{ label: 'Time spent', data: times.map(c => Math.round(c.time_seconds / 60)), backgroundColor: times.map(c => agentColor(c.agent)), borderRadius: 6, }], }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { callbacks: { label: (ctx) => `${ctx.parsed.y} min` } } }, scales: { y: { beginAtZero: true, title: { display: true, text: 'minutes' } } } }, }); // Suite cards const suiteGrid = document.getElementById('suiteGrid'); const suiteAgents = Object.values(pa) .filter(a => (a.suite && (a.suite.description || a.suite.keywords?.length))) .sort((a, b) => (b.user_turns + b.time_seconds / 60) - (a.user_turns + a.time_seconds / 60)); if (suiteAgents.length === 0) { suiteGrid.innerHTML = '
No role data available yet.
'; } else { suiteGrid.innerHTML = suiteAgents.map(a => { const s = a.suite || {}; const kws = (s.keywords || []).slice(0, 8).map(k => `${escapeHtml(k)}`).join(''); return `
${escapeHtml(a.display_name || a.agent)} ${a.user_turns} chats · ${fmtDuration(a.time_seconds)}
${escapeHtml(s.description || s.role_hint || '—')}
${kws || 'no keywords'}
`; }).join(''); } // Models per agent const modelsRows = Object.values(pa) .filter(a => a.top_models && a.top_models.length) .sort((a, b) => b.tokens - a.tokens); document.getElementById('modelsTable').innerHTML = modelsRows.length ? `
${modelsRows.map(a => ` `).join('')}
AgentTop models (by uses)TokensCost
${escapeHtml(a.display_name || a.agent)} ${a.top_models.slice(0, 4).map(([m, c]) => `${escapeHtml(m)} ×${c}`).join(' ')} ${(a.tokens || 0).toLocaleString()} $${(a.cost || 0).toFixed(4)}
` : '
No cost/model data recorded yet.
'; // Full breakdown const rows = Object.values(pa).sort((a, b) => b.user_turns - a.user_turns); document.getElementById('breakdownTable').innerHTML = `
${rows.map(a => ` `).join('')}
AgentChat turnsMsgsTimeSessionsTop modelFirst seenLast seen
${escapeHtml(a.display_name || a.agent)} ${a.user_turns} ${a.chat_messages} ${fmtDuration(a.time_seconds)} ${a.sessions} ${a.top_models && a.top_models.length ? escapeHtml(a.top_models[0][0]) : '—'} ${a.first_seen ? formatDate(a.first_seen) : '—'} ${a.last_seen ? formatDate(a.last_seen) : '—'}
`; } async function recomputeAndRenderInsights() { try { await api.recomputeAgentTime(); } catch (e) {} await renderAgentInsights(); }