From 348b56183e4c3a6bac9e5ca76990844e0990d951 Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Wed, 28 Jan 2026 13:55:57 -0500 Subject: [PATCH] Add stats widget to session cards --- public/index.html | 172 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/public/index.html b/public/index.html index 9017b09..cf0c798 100644 --- a/public/index.html +++ b/public/index.html @@ -447,6 +447,59 @@ border-top: 1px solid #3a3a3a; } + .stats-widget { + padding: 8px 12px; + background: #1e1e1e; + border-bottom: 1px solid #333; + font-size: 12px; + color: #888; + } + + .stats-line { + display: flex; + gap: 12px; + flex-wrap: wrap; + margin-bottom: 4px; + } + + .stats-line:last-child { + margin-bottom: 0; + } + + .stats-item { + display: flex; + align-items: center; + gap: 4px; + } + + .stats-value { + color: #fff; + font-weight: 500; + } + + .mode-badge { + padding: 2px 6px; + border-radius: 3px; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + } + + .mode-plan { + background: #7c3aed; + color: white; + } + + .mode-auto { + background: #059669; + color: white; + } + + .model-name { + color: #666; + font-size: 11px; + } + .empty-state { padding: 32px 16px; text-align: center; @@ -471,6 +524,19 @@ border-color: #ff9800; } + .stats-widget { + background: #f5f5f5; + border-bottom-color: #e0e0e0; + } + + .stats-value { + color: #1a1a1a; + } + + .model-name { + color: #999; + } + .session-output { background: #f5f5f5; border-top-color: #e0e0e0; @@ -621,6 +687,15 @@ outputRenderedLength: 0, expanded: false, state: 'ready', + prompts: 0, + completions: 0, + tools: 0, + compressions: 0, + thinking_seconds: 0, + work_seconds: 0, + mode: 'normal', + model: null, + idle_since: null, }); renderSessions(); }); @@ -666,6 +741,23 @@ } }); + es.addEventListener('stats', (e) => { + const data = JSON.parse(e.data); + const session = state.sessions.get(data.session_id); + if (session) { + if (data.prompts !== undefined) session.prompts = data.prompts; + if (data.completions !== undefined) session.completions = data.completions; + if (data.tools !== undefined) session.tools = data.tools; + if (data.compressions !== undefined) session.compressions = data.compressions; + if (data.thinking_seconds !== undefined) session.thinking_seconds = data.thinking_seconds; + if (data.work_seconds !== undefined) session.work_seconds = data.work_seconds; + if (data.mode !== undefined) session.mode = data.mode; + if (data.model !== undefined) session.model = data.model; + if (data.idle_since !== undefined) session.idle_since = data.idle_since; + updateStatsWidget(data.session_id); + } + }); + state.eventSource = es; } @@ -692,6 +784,60 @@ return `${label}`; } + function formatDuration(seconds) { + if (seconds < 60) return `${seconds}s`; + if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; + return `${Math.floor(seconds / 3600)}h`; + } + + function renderStatsWidget(session) { + const lines = []; + + // Counts line + const counts = []; + if (session.prompts > 0) counts.push(`${session.prompts} prompts`); + if (session.completions > 0) counts.push(`${session.completions} completions`); + if (session.tools > 0) counts.push(`${session.tools} tools`); + if (session.compressions > 0) counts.push(`${session.compressions} compressions`); + + if (counts.length > 0) { + lines.push(`
${counts.join(' | ')}
`); + } + + // Timing line + const timing = []; + if (session.thinking_seconds > 0) timing.push(`${formatDuration(session.thinking_seconds)} thinking`); + if (session.idle_since) { + const idleSeconds = Math.floor((Date.now() - session.idle_since * 1000) / 1000); + if (idleSeconds > 0) timing.push(`${formatDuration(idleSeconds)} idle`); + } + + if (timing.length > 0) { + lines.push(`
${timing.join(' | ')}
`); + } + + // Mode badge and model name + const badges = []; + if (session.mode === 'plan') badges.push('Plan Mode'); + else if (session.mode === 'auto_accept') badges.push('Auto-Accept'); + + if (session.model) { + // "claude-opus-4-5-20251101" -> "opus-4-5" + const match = session.model.match(/claude-(\w+-[\d-]+)/); + if (match) badges.push(`${match[1]}`); + } + + if (badges.length > 0) { + lines.push(`
${badges.join(' ')}
`); + } + + if (lines.length === 0) { + return ''; + } + + return `
${lines.join('')}
`; + } + function renderSessions() { if (state.sessions.size === 0) { $sessions.innerHTML = '
No active sessions
'; @@ -710,6 +856,7 @@
+ ${renderStatsWidget(s)}
${escapeHtml(s.output)}
@@ -751,6 +898,31 @@ } } + function updateStatsWidget(sessionId) { + const session = state.sessions.get(sessionId); + if (!session) return; + + const $statsWidget = document.getElementById(`stats-${sessionId}`); + if ($statsWidget) { + const newHtml = renderStatsWidget(session); + if (newHtml) { + $statsWidget.outerHTML = newHtml; + } else { + $statsWidget.remove(); + } + } else if (session) { + // Widget doesn't exist but should - find the header and insert after it + const $sessionCard = document.querySelector(`[data-session="${sessionId}"]`); + if ($sessionCard) { + const $header = $sessionCard.querySelector('.session-header'); + const newHtml = renderStatsWidget(session); + if (newHtml && $header) { + $header.insertAdjacentHTML('afterend', newHtml); + } + } + } + } + function renderPrompts() { if (state.prompts.size === 0) { $prompts.innerHTML = '
No pending prompts
';