Files
Void-Homelab/public/views/cards/ai_usage.js

42 lines
2.3 KiB
JavaScript

// Sacred Valley card: AI Usage — Claude Code token usage + local (OpenClaw/Ollama)
// performance, summarised from the Homelab Monitor via /api/ai-usage.
import { el, mount } from '../../dom.js';
import { api } from '../../api.js';
let body;
const fmt = (n) => { n = Number(n) || 0; if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M'; if (n >= 1e3) return (n / 1e3).toFixed(1) + 'k'; return String(n); };
const dur = (ms) => ms == null ? '—' : (ms >= 1000 ? (ms / 1000).toFixed(1) + 's' : Math.round(ms) + 'ms');
const mono = (t, color) => el('span', { style: { fontFamily: 'var(--font-mono)', ...(color ? { color } : {}) } }, t);
async function load() {
if (!body) return;
try {
const u = await api.get('/api/ai-usage');
if (!u.ok) { mount(body, el('span', { class: 'muted' }, 'Monitor unreachable (' + (u.url || '') + ')')); return; }
const c = u.claude, l = u.local;
mount(body,
el('div', { class: 'aiu-sec' },
el('div', { class: 'aiu-h' }, 'Claude Code'),
el('div', { class: 'sv-row' }, el('span', { class: 'k' }, 'today'), mono(`${fmt(c.today.input)}${fmt(c.today.output)}`)),
el('div', { class: 'sv-row' }, el('span', { class: 'k' }, 'cache · turns'), mono(`${fmt(c.today.cache)} · ${c.today.turns}`)),
el('div', { class: 'sv-row' }, el('span', { class: 'k' }, 'week'), mono(`${fmt(c.week.input + c.week.output)} tok`)),
el('div', { class: 'sv-row' }, el('span', { class: 'k' }, 'top model'), mono((c.top_model || '—').replace(/^claude-/, ''), 'var(--accent)'))
),
el('div', { class: 'aiu-sec' },
el('div', { class: 'aiu-h' }, 'Local · OpenClaw'),
l.top
? el('div', {},
el('div', { class: 'sv-row' }, el('span', { class: 'k' }, l.top.model), mono(`${dur(l.top.p50_ms)} p50 · ${dur(l.top.p95_ms)} p95`)),
el('div', { class: 'sv-row' }, el('span', { class: 'k' }, 'runs · err'), mono(`${l.runs} · ${(l.top.error_rate * 100).toFixed(0)}%`)))
: el('span', { class: 'muted' }, 'No local runs yet')
),
el('a', { class: 'aiu-link', href: '#/ai-usage' }, 'Full dashboard ↗')
);
} catch { mount(body, el('span', { class: 'muted' }, 'No usage data')); }
}
export default {
id: 'ai-usage', title: 'AI Usage', size: 'm',
mount(e) { body = e; load(); }, start() {}, stop() { body = null; }
};