refactor(web-ui): extract shared component primitives

Introduce components.js with barTrack, barRow, barRankList, metricPill,
metricStrip, and chartHeader helpers. Migrate dashboard.js and usage.js
to use these primitives, replacing 13 families of duplicated CSS
(stat-list, fw-bar, token-bar, metric-pill, chart-insight, chart-header,
usage-chart-total, etc.) with a unified .am-* namespace. Net: -256 lines.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-05-22 12:21:48 -07:00
parent 8753c0c9d5
commit c44e7fe72e
4 changed files with 384 additions and 538 deletions
+28 -64
View File
@@ -1,6 +1,7 @@
import { app, isRouteCurrent } from '../router.js';
import { api } from '../api.js';
import { escapeHTML, formatTokenCount, formatCost } from '../utils.js';
import { barTrack, barRankList, metricStrip, chartHeader } from '../components.js';
/* global uPlot */
@@ -104,7 +105,6 @@ function renderFrameworkBreakdown(byFw) {
el.innerHTML = entries.map(([name, stats]) => {
const total = stats.runs + stats.tools + stats.errors;
const pct = (total / maxTotal * 100).toFixed(1);
const cssClass = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
const active = (stats.active_sessions || 0) > 0;
return `
@@ -119,9 +119,7 @@ function renderFrameworkBreakdown(byFw) {
<span class="usage-fw-stat"><span class="usage-fw-stat-label">tools</span>${stats.tools || 0}</span>
${(stats.errors || 0) > 0 ? `<span class="usage-fw-stat error"><span class="usage-fw-stat-label">err</span>${stats.errors}</span>` : ''}
</div>
<div class="usage-fw-bar-track">
<div class="usage-fw-bar-fill ${escapeHTML(cssClass)}" style="width:${pct}%"></div>
</div>
${barTrack({ value: total, max: maxTotal, fwClass: cssClass, size: 'sm' })}
</div>`;
}).join('');
}
@@ -194,36 +192,22 @@ export async function renderUsage(routeToken) {
<div class="usage-section-row">
<div class="usage-panel usage-chart-panel">
<div class="usage-chart-header">
<span class="section-title" style="margin:0">7-Day Trend</span>
<div class="usage-chart-tabs" id="usage-chart-tabs">
<button class="usage-chart-tab active" data-mode="activity">Activity</button>
<button class="usage-chart-tab" data-mode="tokens">Tokens</button>
<button class="usage-chart-tab" data-mode="cost">Cost</button>
</div>
</div>
<div class="usage-chart-totals">
<span class="usage-chart-total-pill">
<span class="usage-chart-total-label">runs</span>
<strong>${t.runs}</strong>
</span>
<span class="usage-chart-total-pill">
<span class="usage-chart-total-label">tools</span>
<strong>${t.tools}</strong>
</span>
<span class="usage-chart-total-pill">
<span class="usage-chart-total-label">errors</span>
<strong class="${t.errors > 0 ? 'usage-total-errors' : ''}">${t.errors}</strong>
</span>
<span class="usage-chart-total-pill">
<span class="usage-chart-total-label">tokens</span>
<strong>${formatTokenCount(t.tokens)}</strong>
</span>
<span class="usage-chart-total-pill">
<span class="usage-chart-total-label">cost</span>
<strong>${formatCost(t.cost)}</strong>
</span>
</div>
${chartHeader({
title: '7-Day Trend',
controls: `
<div class="usage-chart-tabs" id="usage-chart-tabs">
<button class="usage-chart-tab active" data-mode="activity">Activity</button>
<button class="usage-chart-tab" data-mode="tokens">Tokens</button>
<button class="usage-chart-tab" data-mode="cost">Cost</button>
</div>`,
})}
${metricStrip([
{ label: 'runs', value: String(t.runs), variant: 'total' },
{ label: 'tools', value: String(t.tools), variant: 'total' },
{ label: 'errors', value: String(t.errors), variant: 'total', alert: t.errors > 0 },
{ label: 'tokens', value: formatTokenCount(t.tokens), variant: 'total' },
{ label: 'cost', value: formatCost(t.cost), variant: 'total' },
])}
<div id="usage-chart"></div>
</div>
<div class="usage-panel usage-fw-panel">
@@ -238,40 +222,20 @@ export async function renderUsage(routeToken) {
<div class="usage-section-row">
<div class="usage-panel">
<div class="section-title">Top Models <span class="count">${models.length}</span></div>
${models.length === 0 ? '<p class="empty-state" style="padding:1rem">No model data</p>' : `
<ul class="stat-list">
${models.map(m => {
const pct = (m.count / maxModel * 100).toFixed(1);
return `<li>
<div class="stat-list-header">
<span class="stat-list-name">${escapeHTML(m.name)}</span>
<span class="stat-list-count">${m.count}</span>
</div>
<div class="stat-list-bar-track">
<div class="stat-list-bar-fill model" style="width:${pct}%"></div>
</div>
</li>`;
}).join('')}
</ul>`}
${barRankList(models, {
mapItem: m => ({ name: m.name, count: m.count, modifier: 'model' }),
maxOverride: maxModel,
emptyText: 'No model data',
})}
</div>
<div class="usage-panel">
<div class="section-title">Top Tools <span class="count">${tools.length}</span></div>
${tools.length === 0 ? '<p class="empty-state" style="padding:1rem">No tool data</p>' : `
<ul class="stat-list">
${tools.map(t => {
const pct = (t.count / maxTool * 100).toFixed(1);
return `<li>
<div class="stat-list-header">
<span class="stat-list-name">${escapeHTML(t.name)}</span>
<span class="stat-list-count">${t.count}</span>
</div>
<div class="stat-list-bar-track">
<div class="stat-list-bar-fill" style="width:${pct}%"></div>
</div>
</li>`;
}).join('')}
</ul>`}
${barRankList(tools, {
mapItem: x => ({ name: x.name, count: x.count }),
maxOverride: maxTool,
emptyText: 'No tool data',
})}
</div>
</div>
`;