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 <noreply@anthropic.com>
This commit is contained in:
@@ -9,21 +9,26 @@ import {API, POLL} from "./components/config.js";
|
|||||||
```js
|
```js
|
||||||
const progState = (async function*() {
|
const progState = (async function*() {
|
||||||
while (true) {
|
while (true) {
|
||||||
let data = [], ok = false;
|
let data = [], tokenEvents = [], ok = false;
|
||||||
try {
|
try {
|
||||||
const r = await fetch(`${API}/progress/?limit=500`);
|
const [r1, r2] = await Promise.all([
|
||||||
ok = r.ok;
|
fetch(`${API}/progress/?limit=500`),
|
||||||
data = ok ? await r.json() : [];
|
fetch(`${API}/token-events/?limit=1000`),
|
||||||
|
]);
|
||||||
|
ok = r1.ok;
|
||||||
|
data = ok ? await r1.json() : [];
|
||||||
|
tokenEvents = r2.ok ? await r2.json() : [];
|
||||||
} catch {}
|
} catch {}
|
||||||
yield {data, ok, ts: new Date()};
|
yield {data, tokenEvents, ok, ts: new Date()};
|
||||||
await new Promise(res => setTimeout(res, POLL));
|
await new Promise(res => setTimeout(res, POLL));
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const data = progState.data ?? [];
|
const data = progState.data ?? [];
|
||||||
const _ok = progState.ok ?? false;
|
const tokenEvents = progState.tokenEvents ?? [];
|
||||||
|
const _ok = progState.ok ?? false;
|
||||||
const _ts = progState.ts;
|
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`<p style="color:gray">No token data in the last 30 days.</p>`
|
||||||
|
: 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
|
## Event Log
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|||||||
Reference in New Issue
Block a user