Skip to content

Commit 8a82aca

Browse files
committed
feat: hover tooltips on cache/token stats in WebUI
Wrap each stat segment in <span title="..."> so users see descriptions on hover (e.g. 'Cache write: tokens stored on first cache-controlled request' for 'stored'). Also replaces Unicode symbols (⊙ ⊕ ⊡) in session stats with text labels matching the per-message style.
1 parent 94e8943 commit 8a82aca

1 file changed

Lines changed: 15 additions & 13 deletions

File tree

cmd/odek/ui/index.html

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,8 @@
801801
letter-spacing: 0.2px;
802802
user-select: none;
803803
}
804+
.msg-stats span { cursor: help; border-bottom: 1px dotted transparent; transition: border-color 0.15s; }
805+
.msg-stats span:hover { border-bottom-color: var(--text-tertiary); }
804806
.msg.assistant .msg-stats { border-top-color: rgba(255,255,255,0.06); }
805807

806808
/* ── Session Stats (top bar) ── */
@@ -1395,22 +1397,22 @@ <h3>Delete session?</h3>
13951397
if (lastAssistant) {
13961398
const stats = document.createElement('div');
13971399
stats.className = 'msg-stats';
1398-
const parts = [];
1399-
parts.push('⚡ ' + (event.latency < 1 ? (event.latency * 1000).toFixed(0) + 'ms' : event.latency.toFixed(1) + 's'));
1400-
if (event.contextTokens != null) parts.push(formatNum(event.contextTokens) + ' in');
1401-
if (event.outputTokens != null) parts.push(formatNum(event.outputTokens) + ' out');
1400+
const spans = [];
1401+
spans.push('<span title="Response time">⚡ ' + (event.latency < 1 ? (event.latency * 1000).toFixed(0) + 'ms' : event.latency.toFixed(1) + 's') + '</span>');
1402+
if (event.contextTokens != null) spans.push('<span title="Input tokens (prompt)">' + formatNum(event.contextTokens) + ' in</span>');
1403+
if (event.outputTokens != null) spans.push('<span title="Output tokens (completion)">' + formatNum(event.outputTokens) + ' out</span>');
14021404
// Cache metrics — show only when non-zero
1403-
if (event.cacheCreationTokens > 0) parts.push(formatNum(event.cacheCreationTokens) + ' stored');
1404-
if (event.cacheReadTokens > 0) parts.push(formatNum(event.cacheReadTokens) + ' read');
1405-
if (event.cachedTokens > 0) parts.push(formatNum(event.cachedTokens) + ' cached');
1406-
stats.textContent = parts.join(' · ');
1405+
if (event.cacheCreationTokens > 0) spans.push('<span title="Cache write: tokens stored on first cache-controlled request">' + formatNum(event.cacheCreationTokens) + ' stored</span>');
1406+
if (event.cacheReadTokens > 0) spans.push('<span title="Cache hit: tokens served from cache on subsequent requests">' + formatNum(event.cacheReadTokens) + ' read</span>');
1407+
if (event.cachedTokens > 0) spans.push('<span title="Cached tokens (automatic prefix match)">' + formatNum(event.cachedTokens) + ' cached</span>');
1408+
stats.innerHTML = spans.join(' · ');
14071409
lastAssistant.appendChild(stats);
14081410
}
14091411
}
14101412
// Update session-level token stats in top bar
14111413
const sessionStatsEl = document.getElementById('session-stats');
14121414
if (event.sessionContextTokens != null && event.sessionOutputTokens != null) {
1413-
const sessParts = ['∑ ' + formatNum(event.sessionContextTokens) + ' in · ' + formatNum(event.sessionOutputTokens) + ' out'];
1415+
const sessSpans = ['<span title="Session total input tokens">∑ ' + formatNum(event.sessionContextTokens) + ' in</span>', '<span title="Session total output tokens">' + formatNum(event.sessionOutputTokens) + ' out</span>'];
14141416
if (event.cacheReadTokens > 0 || event.cacheCreationTokens > 0 || event.cachedTokens > 0) {
14151417
// Count total session cache stats (accumulated across the session)
14161418
// We store cache totals on the session-stats element's dataset
@@ -1421,11 +1423,11 @@ <h3>Delete session?</h3>
14211423
el.dataset.cacheCreate = cc;
14221424
el.dataset.cacheRead = cr;
14231425
el.dataset.cached = cd;
1424-
if (cr > 0) sessParts.push('' + formatNum(cr));
1425-
if (cc > 0) sessParts.push('' + formatNum(cc));
1426-
if (cd > 0) sessParts.push('' + formatNum(cd));
1426+
if (cr > 0) sessSpans.push('<span title="Session total cache hits">' + formatNum(cr) + ' read</span>');
1427+
if (cc > 0) sessSpans.push('<span title="Session total cache writes">' + formatNum(cc) + ' stored</span>');
1428+
if (cd > 0) sessSpans.push('<span title="Session total cached tokens (automatic)">' + formatNum(cd) + ' cached</span>');
14271429
}
1428-
sessionStatsEl.textContent = sessParts.join(' · ');
1430+
sessionStatsEl.innerHTML = sessSpans.join(' · ');
14291431
sessionStatsEl.classList.add('visible');
14301432
}
14311433
if (sessionId) loadSessions();

0 commit comments

Comments
 (0)