| ... | @@ -68,12 +68,15 @@ export function UsagePage() { | ... | @@ -68,12 +68,15 @@ export function UsagePage() { |
| 68 | : enumerateHourStarts(window.sinceTime, window.untilTime), | 68 | : enumerateHourStarts(window.sinceTime, window.untilTime), |
| 69 | [window.sinceTime, window.untilTime], | 69 | [window.sinceTime, window.untilTime], |
| 70 | ); | 70 | ); |
| 71 | // Newest first: the window can run 90 periods, so the interesting end | 71 | // The hourly window is small enough to render every period: the table then |
| 72 | // belongs at the top of the table. | 72 | // reads chronologically like the chart, instead of jumping between the hours |
| 73 | const breakdownPeriods = useMemo<readonly (DailyTotals | HourlyTotals)[]>( | 73 | // that happened to have activity. Daily windows can run 90 periods, so those |
| 74 | () => (isPast24Hours ? merged.hourly : merged.daily).toReversed(), | 74 | // stay newest-first with the interesting end on top. |
| 75 | [isPast24Hours, merged.daily, merged.hourly], | 75 | const breakdownPeriods = useMemo<readonly (DailyTotals | HourlyTotals)[]>(() => { |
| 76 | ); | 76 | if (!isPast24Hours) return merged.daily.toReversed(); |
| | 77 | const byHour = new Map(merged.hourly.map((entry) => [entry.hourStart, entry])); |
| | 78 | return hours.map((hourStart) => byHour.get(hourStart) ?? zeroHour(hourStart)); |
| | 79 | }, [isPast24Hours, merged.daily, merged.hourly, hours]); |
| 77 | | 80 | |
| 78 | const selectWindow = (days: number) => { | 81 | const selectWindow = (days: number) => { |
| 79 | setWindowSelection({ | 82 | setWindowSelection({ |
| ... | @@ -435,6 +438,17 @@ export function UsagePage() { | ... | @@ -435,6 +438,17 @@ export function UsagePage() { |
| 435 | ); | 438 | ); |
| 436 | } | 439 | } |
| 437 | | 440 | |
| | 441 | /** A zero-filled hourly period so the breakdown lists every hour in the window. */ |
| | 442 | function zeroHour(hourStart: string): HourlyTotals { |
| | 443 | return { |
| | 444 | day: "", |
| | 445 | hourStart, |
| | 446 | costUsd: 0, |
| | 447 | totalTokens: 0, |
| | 448 | byProvider: new Map<UsageProviderKind, { costUsd: number; totalTokens: number }>(), |
| | 449 | }; |
| | 450 | } |
| | 451 | |
| 438 | /** Brand mark for the harness a row belongs to. */ | 452 | /** Brand mark for the harness a row belongs to. */ |
| 439 | function ProviderMark({ | 453 | function ProviderMark({ |
| 440 | provider, | 454 | provider, |