feat: unify card runtime and event-driven web ui
Some checks failed
CI / Backend Checks (push) Failing after 36s
CI / Frontend Checks (push) Failing after 40s

This commit is contained in:
kacper 2026-04-06 15:42:53 -04:00
parent 0edf8c3fef
commit 4dfb7ca3cc
105 changed files with 17382 additions and 8505 deletions

View file

@ -1,282 +1,30 @@
<div data-litellm-usage-card style="font-family: var(--card-font, 'Iosevka', 'SF Mono', ui-monospace, Menlo, Consolas, monospace); background:#ffffff; color:#111827; padding:14px 16px;">
<div data-litellm-usage-card style="font-family: var(--card-font, 'Iosevka', 'SF Mono', ui-monospace, Menlo, Consolas, monospace); background:var(--theme-card-neutral-bg); color:var(--theme-card-neutral-text); padding:14px 16px; border:1px solid var(--theme-card-neutral-border);">
<div style="display:flex; align-items:flex-start; justify-content:space-between; gap:12px; margin-bottom:10px;">
<div data-usage-subtitle style="font-size:0.86rem; line-height:1.35; color:#4b5563; font-weight:600;">Loading…</div>
<span data-usage-status style="font-size:0.8rem; line-height:1.2; font-weight:700; color:#6b7280; white-space:nowrap;">Loading…</span>
<div data-usage-subtitle style="font-size:0.86rem; line-height:1.35; color:var(--theme-card-neutral-subtle); font-weight:600;">Loading…</div>
<span data-usage-status style="font-size:0.8rem; line-height:1.2; font-weight:700; color:var(--theme-status-muted); white-space:nowrap;">Loading…</span>
</div>
<div data-usage-grid style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:12px;">
<section style="min-width:0;">
<div style="font-size:0.74rem; line-height:1.2; text-transform:uppercase; letter-spacing:0.04em; color:#6b7280;">Last 24h</div>
<div style="font-size:0.74rem; line-height:1.2; text-transform:uppercase; letter-spacing:0.04em; color:var(--theme-card-neutral-muted);">Last 24h</div>
<div style="display:flex; align-items:flex-end; gap:6px; margin-top:4px;">
<span data-usage-tokens-24h style="font-size:2rem; line-height:0.95; font-weight:800; letter-spacing:-0.04em;">--</span>
<span style="font-size:0.9rem; line-height:1.2; font-weight:700; color:#4b5563; padding-bottom:0.22rem;">tokens</span>
<span style="font-size:0.9rem; line-height:1.2; font-weight:700; color:var(--theme-card-neutral-subtle); padding-bottom:0.22rem;">tokens</span>
</div>
<div data-usage-power-24h style="margin-top:4px; font-size:0.9rem; line-height:1.3; color:#1f2937; font-weight:700;">--</div>
<div data-usage-window-24h style="margin-top:2px; font-size:0.76rem; line-height:1.3; color:#6b7280;">--</div>
<div data-usage-power-24h style="margin-top:4px; font-size:0.9rem; line-height:1.3; color:var(--theme-card-neutral-text); font-weight:700;">--</div>
<div data-usage-window-24h style="margin-top:2px; font-size:0.76rem; line-height:1.3; color:var(--theme-card-neutral-muted);">--</div>
</section>
<section data-usage-month-section style="min-width:0;">
<div style="font-size:0.74rem; line-height:1.2; text-transform:uppercase; letter-spacing:0.04em; color:#6b7280;">This Month</div>
<div style="font-size:0.74rem; line-height:1.2; text-transform:uppercase; letter-spacing:0.04em; color:var(--theme-card-neutral-muted);">This Month</div>
<div style="display:flex; align-items:flex-end; gap:6px; margin-top:4px;">
<span data-usage-tokens-month style="font-size:2rem; line-height:0.95; font-weight:800; letter-spacing:-0.04em;">--</span>
<span style="font-size:0.9rem; line-height:1.2; font-weight:700; color:#4b5563; padding-bottom:0.22rem;">tokens</span>
<span style="font-size:0.9rem; line-height:1.2; font-weight:700; color:var(--theme-card-neutral-subtle); padding-bottom:0.22rem;">tokens</span>
</div>
<div data-usage-power-month style="margin-top:4px; font-size:0.9rem; line-height:1.3; color:#1f2937; font-weight:700;">--</div>
<div data-usage-window-month style="margin-top:2px; font-size:0.76rem; line-height:1.3; color:#6b7280;">--</div>
<div data-usage-power-month style="margin-top:4px; font-size:0.9rem; line-height:1.3; color:var(--theme-card-neutral-text); font-weight:700;">--</div>
<div data-usage-window-month style="margin-top:2px; font-size:0.76rem; line-height:1.3; color:var(--theme-card-neutral-muted);">--</div>
</section>
</div>
<div style="margin-top:10px; font-size:0.82rem; line-height:1.35; color:#6b7280;">Updated <span data-usage-updated>--</span></div>
<div style="margin-top:10px; font-size:0.82rem; line-height:1.35; color:var(--theme-card-neutral-muted);">Updated <span data-usage-updated>--</span></div>
</div>
<script>
(() => {
const script = document.currentScript;
const root = script?.closest('[data-nanobot-card-root]');
const state = window.__nanobotGetCardState?.(script) || {};
if (!(root instanceof HTMLElement)) return;
const subtitleEl = root.querySelector('[data-usage-subtitle]');
const statusEl = root.querySelector('[data-usage-status]');
const updatedEl = root.querySelector('[data-usage-updated]');
const gridEl = root.querySelector('[data-usage-grid]');
const monthSectionEl = root.querySelector('[data-usage-month-section]');
const tokens24hEl = root.querySelector('[data-usage-tokens-24h]');
const power24hEl = root.querySelector('[data-usage-power-24h]');
const window24hEl = root.querySelector('[data-usage-window-24h]');
const tokensMonthEl = root.querySelector('[data-usage-tokens-month]');
const powerMonthEl = root.querySelector('[data-usage-power-month]');
const windowMonthEl = root.querySelector('[data-usage-window-month]');
if (!(subtitleEl instanceof HTMLElement) || !(statusEl instanceof HTMLElement) || !(updatedEl instanceof HTMLElement) || !(gridEl instanceof HTMLElement) || !(monthSectionEl instanceof HTMLElement) || !(tokens24hEl instanceof HTMLElement) || !(power24hEl instanceof HTMLElement) || !(window24hEl instanceof HTMLElement) || !(tokensMonthEl instanceof HTMLElement) || !(powerMonthEl instanceof HTMLElement) || !(windowMonthEl instanceof HTMLElement)) return;
const subtitle = typeof state.subtitle === 'string' ? state.subtitle : '';
const configuredToolName24h = typeof state.tool_name_24h === 'string'
? state.tool_name_24h.trim()
: typeof state.tool_name === 'string'
? state.tool_name.trim()
: '';
const configuredToolNameMonth = typeof state.tool_name_month === 'string'
? state.tool_name_month.trim()
: '';
const rawToolArguments24h = state && typeof state.tool_arguments_24h === 'object' && state.tool_arguments_24h && !Array.isArray(state.tool_arguments_24h)
? state.tool_arguments_24h
: state && typeof state.tool_arguments === 'object' && state.tool_arguments && !Array.isArray(state.tool_arguments)
? state.tool_arguments
: {};
const rawToolArgumentsMonth = state && typeof state.tool_arguments_month === 'object' && state.tool_arguments_month && !Array.isArray(state.tool_arguments_month)
? state.tool_arguments_month
: {};
const source24h = typeof state.source_url_24h === 'string' ? state.source_url_24h.trim() : '';
const sourceMonth = typeof state.source_url_month === 'string' ? state.source_url_month.trim() : '';
const refreshMsRaw = Number(state.refresh_ms);
const refreshMs = Number.isFinite(refreshMsRaw) && refreshMsRaw >= 60000 ? refreshMsRaw : 15 * 60 * 1000;
const tokenFormatter = new Intl.NumberFormat([], { notation: 'compact', maximumFractionDigits: 1 });
const kwhFormatter = new Intl.NumberFormat([], { minimumFractionDigits: 1, maximumFractionDigits: 1 });
const moneyFormatter = new Intl.NumberFormat([], { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 });
subtitleEl.textContent = subtitle || 'LiteLLM activity vs local UPS energy';
const hasMonthSource = Boolean(
sourceMonth ||
configuredToolNameMonth ||
Object.keys(rawToolArgumentsMonth).length,
);
if (!hasMonthSource) {
monthSectionEl.style.display = 'none';
gridEl.style.gridTemplateColumns = 'minmax(0, 1fr)';
}
const updateLiveContent = (snapshot) => {
window.__nanobotSetCardLiveContent?.(script, snapshot);
};
const setStatus = (label, color) => {
statusEl.textContent = label;
statusEl.style.color = color;
};
const parseLocalTimestamp = (raw) => {
if (typeof raw !== 'string' || !raw.trim()) return null;
const match = raw.trim().match(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) ([+-]\d{2})(\d{2})$/);
if (!match) return null;
const value = `${match[1]}T${match[2]}${match[3]}:${match[4]}`;
const parsed = new Date(value);
return Number.isNaN(parsed.getTime()) ? null : parsed;
};
const formatRangeLabel = (payload, fallbackLabel) => {
const startRaw = typeof payload?.range_start_local === 'string' ? payload.range_start_local : '';
const endRaw = typeof payload?.range_end_local === 'string' ? payload.range_end_local : '';
const start = parseLocalTimestamp(startRaw);
const end = parseLocalTimestamp(endRaw);
if (!(start instanceof Date) || Number.isNaN(start.getTime()) || !(end instanceof Date) || Number.isNaN(end.getTime())) {
return fallbackLabel;
}
return `${start.toLocaleDateString([], { month: 'short', day: 'numeric' })} to ${end.toLocaleDateString([], { month: 'short', day: 'numeric' })}`;
};
const renderSection = (elements, payload, fallbackLabel) => {
const tokens = Number(payload?.total_tokens_processed);
const kwh = Number(payload?.total_ups_kwh_in_range);
const localCost = Number(payload?.local_cost_usd_in_range);
elements.tokens.textContent = Number.isFinite(tokens) ? tokenFormatter.format(tokens) : '--';
elements.power.textContent =
Number.isFinite(kwh) && Number.isFinite(localCost)
? `${kwhFormatter.format(kwh)} kWh · ${moneyFormatter.format(localCost)}`
: Number.isFinite(kwh)
? `${kwhFormatter.format(kwh)} kWh`
: '--';
elements.window.textContent = formatRangeLabel(payload, fallbackLabel);
};
const blankSection = (elements, fallbackLabel) => {
elements.tokens.textContent = '--';
elements.power.textContent = '--';
elements.window.textContent = fallbackLabel;
};
const shellEscape = (value) => `'${String(value ?? '').replace(/'/g, `'\"'\"'`)}'`;
const buildLegacyExecCommand = (rawUrl) => {
if (typeof rawUrl !== 'string' || !rawUrl.startsWith('/script/proxy/')) return '';
const [pathPart, queryPart = ''] = rawUrl.split('?', 2);
const relativeScript = pathPart.slice('/script/proxy/'.length).replace(/^\/+/, '');
if (!relativeScript) return '';
const params = new URLSearchParams(queryPart);
const args = params.getAll('arg').map((value) => value.trim()).filter(Boolean);
const scriptPath = `$HOME/.nanobot/workspace/${relativeScript}`;
return `python3 ${scriptPath}${args.length ? ` ${args.map(shellEscape).join(' ')}` : ''}`;
};
const resolveToolCall = (toolName, toolArguments, legacySourceUrl) => {
if (toolName) {
return {
toolName,
toolArguments,
};
}
const legacyCommand = buildLegacyExecCommand(legacySourceUrl);
if (!legacyCommand) return null;
return {
toolName: 'exec',
toolArguments: { command: legacyCommand },
};
};
const loadPayload = async (toolCall) => {
if (!toolCall) throw new Error('Missing tool_name/tool_arguments');
if (!window.__nanobotCallToolAsync) throw new Error('Async tool helper unavailable');
const toolResult = await window.__nanobotCallToolAsync(
toolCall.toolName,
toolCall.toolArguments,
{ timeoutMs: 180000 },
);
if (toolResult && toolResult.parsed && typeof toolResult.parsed === 'object' && !Array.isArray(toolResult.parsed)) {
return toolResult.parsed;
}
if (typeof toolResult?.content === 'string' && toolResult.content.trim()) {
const parsed = JSON.parse(toolResult.content);
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
return parsed;
}
}
throw new Error('Tool returned invalid JSON');
};
const refresh = async () => {
setStatus('Refreshing', '#6b7280');
try {
const toolCall24h = resolveToolCall(configuredToolName24h, rawToolArguments24h, source24h);
const toolCallMonth = hasMonthSource
? resolveToolCall(configuredToolNameMonth, rawToolArgumentsMonth, sourceMonth)
: null;
const jobs = [loadPayload(toolCall24h), hasMonthSource ? loadPayload(toolCallMonth) : Promise.resolve(null)];
const results = await Promise.allSettled(jobs);
const twentyFourHour = results[0].status === 'fulfilled' ? results[0].value : null;
const month = hasMonthSource && results[1].status === 'fulfilled' ? results[1].value : null;
if (twentyFourHour) {
renderSection(
{ tokens: tokens24hEl, power: power24hEl, window: window24hEl },
twentyFourHour,
'Last 24 hours',
);
} else {
blankSection(
{ tokens: tokens24hEl, power: power24hEl, window: window24hEl },
'Last 24 hours',
);
}
if (hasMonthSource && month) {
renderSection(
{ tokens: tokensMonthEl, power: powerMonthEl, window: windowMonthEl },
month,
'This month',
);
} else if (hasMonthSource) {
blankSection(
{ tokens: tokensMonthEl, power: powerMonthEl, window: windowMonthEl },
'This month',
);
}
const successCount = [twentyFourHour, month].filter(Boolean).length;
const expectedCount = hasMonthSource ? 2 : 1;
if (successCount === expectedCount) {
setStatus('Live', '#047857');
} else if (successCount === 1) {
setStatus('Partial', '#b45309');
} else {
setStatus('Unavailable', '#b91c1c');
}
const updatedText = new Date().toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
updatedEl.textContent = updatedText;
updateLiveContent({
kind: 'litellm_ups_usage',
subtitle: subtitleEl.textContent || null,
status: statusEl.textContent || null,
updated_at: updatedText,
last_24h: twentyFourHour
? {
total_tokens_processed: Number(twentyFourHour.total_tokens_processed) || 0,
total_ups_kwh_in_range: Number(twentyFourHour.total_ups_kwh_in_range) || 0,
local_cost_usd_in_range: Number(twentyFourHour.local_cost_usd_in_range) || 0,
range_start_local: twentyFourHour.range_start_local || null,
range_end_local: twentyFourHour.range_end_local || null,
}
: null,
this_month: month
? {
total_tokens_processed: Number(month.total_tokens_processed) || 0,
total_ups_kwh_in_range: Number(month.total_ups_kwh_in_range) || 0,
local_cost_usd_in_range: Number(month.local_cost_usd_in_range) || 0,
range_start_local: month.range_start_local || null,
range_end_local: month.range_end_local || null,
}
: null,
});
} catch (error) {
const errorText = String(error);
blankSection({ tokens: tokens24hEl, power: power24hEl, window: window24hEl }, 'Last 24 hours');
if (hasMonthSource) {
blankSection({ tokens: tokensMonthEl, power: powerMonthEl, window: windowMonthEl }, 'This month');
}
updatedEl.textContent = errorText;
setStatus('Unavailable', '#b91c1c');
updateLiveContent({
kind: 'litellm_ups_usage',
subtitle: subtitleEl.textContent || null,
status: 'Unavailable',
updated_at: errorText,
last_24h: null,
this_month: null,
error: errorText,
});
}
};
void refresh();
window.setInterval(() => { void refresh(); }, refreshMs);
})();
</script>