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:
@@ -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>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user