From 608dedff8f96913e51452e1e0fdf73b7f5a5eeb1 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 8 Jun 2026 14:51:18 +1000 Subject: [PATCH] feat(sv-card): open AI Usage dashboard via in-Void #/ai-usage route --- public/views/cards/ai_usage.js | 2 +- tests/frontend/ai_usage_card.test.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/frontend/ai_usage_card.test.js diff --git a/public/views/cards/ai_usage.js b/public/views/cards/ai_usage.js index 9432bfa..2ce9e63 100644 --- a/public/views/cards/ai_usage.js +++ b/public/views/cards/ai_usage.js @@ -30,7 +30,7 @@ async function load() { 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: 'http://192.168.1.212:8080/', target: '_blank', rel: 'noopener' }, 'Full dashboard ↗') + el('a', { class: 'aiu-link', href: '#/ai-usage' }, 'Full dashboard ↗') ); } catch { mount(body, el('span', { class: 'muted' }, 'No usage data')); } } diff --git a/tests/frontend/ai_usage_card.test.js b/tests/frontend/ai_usage_card.test.js new file mode 100644 index 0000000..cbeacbe --- /dev/null +++ b/tests/frontend/ai_usage_card.test.js @@ -0,0 +1,28 @@ +import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest'; +import { JSDOM } from 'jsdom'; + +vi.mock('../../public/api.js', () => ({ api: { get: vi.fn().mockResolvedValue({ + ok: true, + claude: { today: { input: 1, output: 2, cache: 3, turns: 4 }, week: { input: 5, output: 6 }, top_model: 'claude-opus-4-8' }, + local: { top: { model: 'llama', p50_ms: 100, p95_ms: 200, error_rate: 0 }, runs: 3 } +}) } })); + +let card; +beforeAll(async () => { + const dom = new JSDOM('', { url: 'http://localhost/' }); + global.window = dom.window; global.document = dom.window.document; + global.Node = dom.window.Node; global.location = dom.window.location; + card = (await import('../../public/views/cards/ai_usage.js')).default; +}); +afterAll(() => { delete global.window; delete global.document; delete global.Node; delete global.location; }); + +describe('AI Usage SV card link', () => { + it('points the Full dashboard link at the in-Void #/ai-usage route', async () => { + const e = document.createElement('div'); + card.mount(e); + await new Promise(r => setTimeout(r, 0)); // let load() resolve + const link = e.querySelector('a.aiu-link'); + expect(link.getAttribute('href')).toBe('#/ai-usage'); + expect(link.hasAttribute('target')).toBe(false); + }); +});