From b54ee8149ca6c61baa6e69adb8f2659a8757cf1f Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 30 Mar 2026 00:42:09 +0200 Subject: [PATCH] feat(dashboard): add tokens consumed per day chart to Progress page Fetches /token-events/?limit=1000 in parallel with progress events and renders a second area+line chart (amber) below the events-per-day chart, aggregating tokens_in + tokens_out per calendar day over the same 30-day window. Co-Authored-By: Claude Sonnet 4.6 --- state-hub/dashboard/src/progress.md | 47 ++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/state-hub/dashboard/src/progress.md b/state-hub/dashboard/src/progress.md index 8394032..6237803 100644 --- a/state-hub/dashboard/src/progress.md +++ b/state-hub/dashboard/src/progress.md @@ -9,21 +9,26 @@ import {API, POLL} from "./components/config.js"; ```js const progState = (async function*() { while (true) { - let data = [], ok = false; + let data = [], tokenEvents = [], ok = false; try { - const r = await fetch(`${API}/progress/?limit=500`); - ok = r.ok; - data = ok ? await r.json() : []; + const [r1, r2] = await Promise.all([ + fetch(`${API}/progress/?limit=500`), + fetch(`${API}/token-events/?limit=1000`), + ]); + ok = r1.ok; + data = ok ? await r1.json() : []; + tokenEvents = r2.ok ? await r2.json() : []; } catch {} - yield {data, ok, ts: new Date()}; + yield {data, tokenEvents, ok, ts: new Date()}; await new Promise(res => setTimeout(res, POLL)); } })(); ``` ```js -const data = progState.data ?? []; -const _ok = progState.ok ?? false; +const data = progState.data ?? []; +const tokenEvents = progState.tokenEvents ?? []; +const _ok = progState.ok ?? false; const _ts = progState.ts; ``` @@ -79,6 +84,34 @@ display(byDay.length === 0 ); ``` +```js +const tokensByDay = Object.entries( + tokenEvents + .filter(e => new Date(e.created_at) >= cutoff) + .reduce((acc, e) => { + const day = e.created_at.slice(0, 10); + acc[day] = (acc[day] ?? 0) + (e.tokens_in || 0) + (e.tokens_out || 0); + return acc; + }, {}) +).sort().map(([day, tokens]) => ({day, tokens})); + +display(tokensByDay.length === 0 + ? html`

No token data in the last 30 days.

` + : Plot.plot({ + title: "Tokens consumed per day (30-day window)", + x: {label: "Date", tickRotate: -30}, + y: {label: "Tokens", grid: true, tickFormat: "~s"}, + marks: [ + Plot.areaY(tokensByDay, {x: "day", y: "tokens", fill: "#f28e2b", fillOpacity: 0.3}), + Plot.lineY(tokensByDay, {x: "day", y: "tokens", stroke: "#f28e2b"}), + Plot.ruleY([0]), + ], + marginBottom: 60, + width: 750, + }) +); +``` + ## Event Log ```js