Skip to content

Title the usage chart, and show each day's cost - #209

Open
lkiesow wants to merge 3 commits into
mainfrom
chart-labels
Open

lkiesow wants to merge 3 commits into
mainfrom
chart-labels

Conversation

@lkiesow

@lkiesow lkiesow commented Oct 30, 2026

Copy link
Copy Markdown
Member

Note

Depends on #206 and #208, which need to be reviewed and merged first. This branch is built on top of them, so its diff also contains their commits.

The usage chart on the dashboard had no title, and a bar's popover showed only a date and a bare number. Users had to guess what was being counted and over which period. The window is the last 30 days, not the current month, and the day-of-month labels on the axis made that easy to get wrong.

Changes

  • The chart has a heading, "Tokens per day, last 30 days", with a help button. The help text says that the chart counts the tokens of all the user's keys per UTC day, and that the allowance is measured in cost, not tokens.
  • A bar's popover shows the unit and the day's cost: 2026-10-25: 12,316,429 tokens · $0.21.
  • The total above the chart shows the window's cost as well: 35,384,081 tokens · $12.34 in total. This connects the chart to the allowance bars above it.
  • The chart has an accessible label.

LiteLLM's /user/daily/activity already returned each day's spend alongside the tokens, but the portal didn't read it. DailyUsage and the chart bars now carry it, and costs are formatted with the configured BUDGET_UNIT, like the allowance figures.

Testing

  • Unit tests cover reading spend, including a day split over two pages, and summing it into daily, weekly and monthly bars.
  • The template test checks the heading, the total and a bar's popover text.
  • The end-to-end test checks that LiteLLM reports a cost for each day with traffic. The dev stack's models have prices, so it always should.
  • The browser test checks the new popover format.

A help popover closed the moment the pointer left its button, so it was
gone before the pointer could cross the gap onto it. Someone using a
screen magnifier, who moves the pointer to pan the view, could not read
a popover wider than the magnified area.

Leaving the button now hides the popover only after 300 ms, and the
pointer on the popover keeps it open until it leaves the popover too,
as WCAG 1.4.13 asks of content shown on hover. Escape still hides it.
The bars carried their day and count in a title attribute, which a
touch screen never shows and which looked nothing like the rest of the
page. They now use data-help, and help.js serves any element with it.

A tap opened and closed the popover at once: it sends mouseenter, and
focuses a button, both of which opened it, just before the click that
toggled it shut. Only a mouse now hovers, and the click goes by the
state before the press. Tapping another bar moves the popover there.
The chart had no title, and a bar's popover showed a date and a bare
number, so it was left to the user to guess what was counted and over
which period. The window is the last 30 days, not the month, which the
day-of-month axis made easy to mistake. The chart now has a heading that
says so, with a help button explaining that it counts every key's tokens
per UTC day, and that the allowance is measured in cost.

LiteLLM's daily activity carries each day's spend next to its tokens,
which was not read. A bar now shows both, "2026-10-25: 12,316,429 tokens
· $0.21", and the total above the chart shows the window's cost, which
ties the chart to the allowance bars above it.

@Odrec Odrec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading spend from the daily activity is right, and it is summed the same way as the tokens, split pages included. Two of the new figures can be wrong, and some of the new wiring has no test.

  • The heading always says "per day", but usageBars draws weeks above 62 days and months above 730, as usage-limits.md documents for USAGE_HISTORY_DAYS. With 365 the card reads "Tokens per day, last 365 days" over 53 weekly bars, and the help says to tap a day. The "30 days" in dash.usagestats.note is still hard-coded, so with any other window the note and the heading disagree.
  • The total covers one day more than the bars. History asks for start_date = today − 30 (usage.go:83, pinned by history_test.go:69), LiteLLM's start_date is inclusive (checked on the dev gateway), so 31 days come back; usageBars draws 30 and userUsage adds all of them into Total and now Spend. A day at today − 30 with 1,000 tokens and $5 plus 10 tokens and $0.01 today gives Total 1010 and Spend 5.01 against bars of 10 and $0.01. The token total and the model table did this before, but with the cost and "last 30 days" above it the page now states it. start_date = today − (days − 1) fixes all three.
  • Remove rep.Spend += d.Spend in userUsage: go test ./internal/handlers/ stays green and the total always reads $0.00, since TestDashboardRendersUsage builds the report by hand. Remove rep.WindowDays = …: stays green, and the heading says "last 0 days". Drop || d.Spend > 0 in History: go test ./internal/litellm/ stays green. Remove the chart's role="img" aria-label: stays green. Misspell the chart help key: stays green, because TestDashboardRendersNoRawCatalogueKeys strips tags and never sees data-help.
  • The aria-label repeats the heading right above it, so a screen reader hears the title twice and no figure; the total would give it something to say.

Comment thread internal/i18n/messages.go
"dash.usagestats.note": {DE: "Ihr Kontingent wird in Kosten gemessen; die Verlaufsgrafik zeigt die Tokens, die Sie in den letzten 30 Tagen mit all Ihren Schlüsseln verbraucht haben, und läuft mit einem neu erzeugten Schlüssel weiter. Das längste Kontingent gilt für Sie, nicht für den einzelnen Schlüssel: Ein neu erzeugter Schlüssel setzt es nicht zurück.", EN: "Your allowance is measured in cost; the history chart shows the tokens you used over the last 30 days with all your keys, and carries on when you generate a new key. The longest allowance applies to you rather than to a single key, so generating a new key does not reset it."},
"dash.usagestats.perkey": {DE: "Kürzere Kontingente gelten pro Schlüssel und schützen vor unbeabsichtigt hohem Verbrauch; mit einem neuen Schlüssel beginnen sie von vorn.", EN: "Shorter ones apply per key, as a guard against runaway spend, and start over with a new key."},
"dash.usagestats.total": {DE: "insgesamt", EN: "in total"},
"dash.usagestats.chart": {DE: "Tokens pro Tag, letzte %d Tage", EN: "Tokens per day, last %d days"},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above 62 days the bars are weeks and above 730 months (usageBars), so this reads "per day" over weekly bars. The noun could follow the bucket size, carried in usageReport.

Comment thread internal/handlers/ui.go
rep.WindowDays = u.usage.windowDays
for _, d := range days {
rep.Total += d.Tokens
rep.Spend += d.Spend

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sums every day History returned, 31 of them (start_date = today − 30 is inclusive), while the bars show 30. Asking for today − (days − 1) in usage.go:83 keeps the total, the bars and the model table on one window. Removing this line also leaves go test ./internal/handlers/ green.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants