
## Summary
Add LM Studio-style real-time token throughput (token/s) and **total token countdisplay in the assistant message footer, showingXX.X token/s · XXXX token` format.
Motivation / Use Case
When using LLM chat interfaces (especially for cost monitoring and performance tuning), users need quick visibility into:
- Token generation speed — how fast the model is responding
- Total tokens consumed per turn — for cost tracking and context window awareness
LM Studio already provides this UX pattern and users familiar with it expect similar information density. Currently Hermes WebUI shows input/output token counts but lacks:
- Real-time generation speed (
token/s)
- Aggregated total token count in a prominent position
Proposed Behavior
Display Format
┌─────────────────────────────────────────────────────┐
│ [Assistant response content...] │
├─────────────────────────────────────────────────────┤
│ 15.0k in · 115 out 50.4 token/s · 15.1k token │
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ │
│ Speed (TPS) + Total Tokens │
└─────────────────────────────────────────────────────┘
Two States
| State |
Display |
When |
| Streaming (in progress) |
XX.X token/s only |
metering events fire, _turnUsage not yet available |
| Completed (done) |
XX.X token/s · XXXX token |
done event fires, _turnUsage populated |
Formatting Rules
- Speed first, token count second (LM Studio convention)
- Separator:
· (middle dot, consistent with existing in · out format)
- Unit: full word
token (not abbreviated tok)
- Large numbers: auto-format with suffixes (
15000 → 15.0k, 2500000 → 2.5M)
- Position: inside
.msg-foot, after .msg-usage-inline or .msg-time element
- Style: muted color, small font (12px), semi-transparent (opacity 0.85), bold weight
Technical Approach
Data Sources (Already Available)
| Data |
Source Event |
Field |
input_tokens |
done |
_turnUsage.input_tokens |
output_tokens |
done |
_turnUsage.output_tokens |
total_tokens |
computed |
input_tokens + output_tokens |
tokens/sec |
metering |
calculated from token delta / time delta |
Implementation Scope
Single file modification: static/messages.js
-
New helper function _fmtTokens(n) — ~7 lines
- Formats large numbers with k/M suffixes
-
Enhanced function _injectLiveTpsToMessage(tps) — ~15 lines modification
- Reads last assistant message's
_turnUsage from S.messages global array
- Computes
total_tokens = input + output
- Conditionally renders speed-only or speed+tokens format
- Creates/updates
.msg-speed-inline DOM element
Total change: ~22 lines of code across 2 functions in 1 file.
No Backend Changes Required
All data is already emitted by the backend via WebSocket events (metering, done). This is purely a frontend enhancement.
Reference Implementation
A working proof-of-concept is available at:
🔗 messages.js (fork with enhancement)
Note: This is a fork for demonstration only. The relevant changes are in _fmtTokens() and _injectLiveTpsToMessage() functions (~22 lines total, approximately lines 1199-1240).
Tested on Hermes Agent v0.13.0 (2026.5.7) with the following verified results:
- ✅ Streaming phase shows speed only
- ✅ Completion phase shows
XX.X token/s · XXXX token
- ✅ Multi-turn conversations display independently per message
- ✅ No duplicate elements, no layout breakage
- ✅ Works with existing
in · out usage display
- ✅ Zero backend changes needed
Key Code Snippet (Reference)
// Helper: format large numbers
function _fmtTokens(n) {
if (!n || n < 0) return '0';
if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'k';
return String(n);
}
// Enhanced injection (core logic)
function _injectLiveTpsToMessage(tps) {
const activeFoot = document.querySelector('.assistant-turn:last-child .msg-foot');
if (!activeFoot) return;
// Get usage from global S.messages array (reliable data source)
const lastAsstMsg = S.messages && Array.isArray(S.messages)
? [...S.messages].reverse().find(m => m.role === 'assistant') : null;
const usage = lastAsstMsg?._turnUsage || null;
const totalTok = (usage?.input_tokens || 0) + (usage?.output_tokens || 0);
const tpsVal = typeof tps === 'number' ? tps.toFixed(1) : '—';
// ... DOM element creation/update ...
// Render: speed + token (LM Studio style)
speedEl.textContent = totalTok > 0
? `${tpsVal} token/s · ${_fmtTokens(totalTok)} token`
: `${tpsVal} token/s`;
}
Alternatives Considered
| Option |
Pros |
Cons |
Verdict |
| A: Current proposal (DOM injection in messages.js) |
Minimal change, zero backend impact |
Not persisted on page refresh |
✅ Selected |
| B: Modify ui.js renderMessages() |
Persisted on refresh |
Higher risk of breaking changes, larger diff |
❌ Rejected |
| C: Backend-side footer enhancement |
Always available |
Requires server code change, more complex |
❌ Overkill |
| D: Browser extension |
No source change needed |
Maintenance burden, distribution problem |
❌ Impractical |
Known Limitations
- Non-persistent on refresh: Speed/token info disappears on page reload (same behavior as current TPS display). Fixing this would require modifying
ui.js render logic.
- Requires metering events: If streaming doesn't emit metering events, speed won't show (token count from
done event still works).
Screenshots / Mockup
During streaming:
15.0k in · 115 out 50.4 token/s 10:42 AM
↑
Speed only (usage not yet available)
After completion:
15.0k in · 115 out 50.4 token/s · 15.1k token 10:43 AM
^^^^^^^^^^^^^^^^^^^^^^^^
Speed + Total Token Count
Checklist for Implementation
Labels Suggested
enhancement ui good first issue webui
Note: This is a ready-to-implement feature request with a working reference implementation. The approach requires modifying only messages.js (~22 lines) with no backend changes.
Add LM Studio-style real-time token throughput (
token/s) and **total token countdisplay in the assistant message footer, showingXX.X token/s · XXXX token` format.Motivation / Use Case
When using LLM chat interfaces (especially for cost monitoring and performance tuning), users need quick visibility into:
LM Studio already provides this UX pattern and users familiar with it expect similar information density. Currently Hermes WebUI shows
input/outputtoken counts but lacks:token/s)Proposed Behavior
Display Format
Two States
XX.X token/sonlymeteringevents fire,_turnUsagenot yet availableXX.X token/s · XXXX tokendoneevent fires,_turnUsagepopulatedFormatting Rules
·(middle dot, consistent with existingin · outformat)token(not abbreviatedtok)15000→15.0k,2500000→2.5M).msg-foot, after.msg-usage-inlineor.msg-timeelementTechnical Approach
Data Sources (Already Available)
input_tokensdone_turnUsage.input_tokensoutput_tokensdone_turnUsage.output_tokenstotal_tokensinput_tokens + output_tokenstokens/secmeteringImplementation Scope
Single file modification:
static/messages.jsNew helper function
_fmtTokens(n)— ~7 linesEnhanced function
_injectLiveTpsToMessage(tps)— ~15 lines modification_turnUsagefromS.messagesglobal arraytotal_tokens = input + output.msg-speed-inlineDOM elementTotal change: ~22 lines of code across 2 functions in 1 file.
No Backend Changes Required
All data is already emitted by the backend via WebSocket events (
metering,done). This is purely a frontend enhancement.Reference Implementation
A working proof-of-concept is available at:
🔗 messages.js (fork with enhancement)
Tested on Hermes Agent v0.13.0 (2026.5.7) with the following verified results:
XX.X token/s · XXXX tokenin · outusage displayKey Code Snippet (Reference)
Alternatives Considered
Known Limitations
ui.jsrender logic.doneevent still works).Screenshots / Mockup
During streaming:
After completion:
Checklist for Implementation
_fmtTokens()helper function tomessages.js_injectLiveTpsToMessage()with token count logicLabels Suggested
enhancementuigood first issuewebuiNote: This is a ready-to-implement feature request with a working reference implementation. The approach requires modifying only
messages.js(~22 lines) with no backend changes.