@oh-my-pi/omp-stats 18.3.5 → 18.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +13 -11
  3. package/build.ts +5 -49
  4. package/dist/client/index.css +1 -1
  5. package/dist/client/index.html +2 -2
  6. package/dist/client/index.js +105 -126
  7. package/dist/types/aggregator.d.ts +25 -22
  8. package/dist/types/client/api.d.ts +11 -7
  9. package/dist/types/client/app/LiveChip.d.ts +2 -0
  10. package/dist/types/client/app/Shell.d.ts +16 -0
  11. package/dist/types/client/app/ThemeToggle.d.ts +1 -0
  12. package/dist/types/client/app/nav.d.ts +18 -0
  13. package/dist/types/client/charts/BarList.d.ts +17 -0
  14. package/dist/types/client/charts/Chart.d.ts +49 -0
  15. package/dist/types/client/charts/Legend.d.ts +16 -0
  16. package/dist/types/client/charts/ShareBar.d.ts +11 -0
  17. package/dist/types/client/charts/Sparkline.d.ts +11 -0
  18. package/dist/types/client/charts/TimeChart.d.ts +9 -0
  19. package/dist/types/client/charts/index.d.ts +7 -0
  20. package/dist/types/client/charts/types.d.ts +25 -0
  21. package/dist/types/client/charts/useWidth.d.ts +3 -0
  22. package/dist/types/client/data/colors.d.ts +25 -0
  23. package/dist/types/client/data/formatters.d.ts +9 -1
  24. package/dist/types/client/data/live.d.ts +29 -0
  25. package/dist/types/client/data/query.d.ts +35 -0
  26. package/dist/types/client/data/range.d.ts +31 -0
  27. package/dist/types/client/data/series.d.ts +28 -0
  28. package/dist/types/client/data/useHashRoute.d.ts +18 -4
  29. package/dist/types/client/data/view-models.d.ts +111 -28
  30. package/dist/types/client/index.d.ts +0 -1
  31. package/dist/types/client/routes/CostsRoute.d.ts +2 -2
  32. package/dist/types/client/routes/ErrorsRoute.d.ts +2 -2
  33. package/dist/types/client/routes/FrustrationRoute.d.ts +7 -0
  34. package/dist/types/client/routes/GainRoute.d.ts +1 -2
  35. package/dist/types/client/routes/ModelsRoute.d.ts +2 -2
  36. package/dist/types/client/routes/OverviewRoute.d.ts +1 -2
  37. package/dist/types/client/routes/ProjectsRoute.d.ts +2 -2
  38. package/dist/types/client/routes/ProvidersRoute.d.ts +2 -2
  39. package/dist/types/client/routes/RequestsRoute.d.ts +1 -2
  40. package/dist/types/client/routes/ToolsRoute.d.ts +2 -2
  41. package/dist/types/client/routes/TracesRoute.d.ts +2 -2
  42. package/dist/types/client/routes/index.d.ts +1 -1
  43. package/dist/types/client/traces/AggregatesPanel.d.ts +4 -3
  44. package/dist/types/client/traces/SummaryStrip.d.ts +2 -2
  45. package/dist/types/client/traces/trace-colors.d.ts +18 -12
  46. package/dist/types/client/ui/Badge.d.ts +19 -0
  47. package/dist/types/client/ui/Card.d.ts +25 -0
  48. package/dist/types/client/ui/Drawer.d.ts +25 -0
  49. package/dist/types/client/ui/JsonBlock.d.ts +2 -2
  50. package/dist/types/client/ui/Modal.d.ts +23 -0
  51. package/dist/types/client/ui/RequestDrawer.d.ts +10 -1
  52. package/dist/types/client/ui/SearchInput.d.ts +8 -0
  53. package/dist/types/client/ui/Segmented.d.ts +15 -0
  54. package/dist/types/client/ui/Stat.d.ts +36 -0
  55. package/dist/types/client/ui/States.d.ts +41 -0
  56. package/dist/types/client/ui/Table.d.ts +48 -0
  57. package/dist/types/client/ui/index.d.ts +9 -10
  58. package/dist/types/db.d.ts +90 -106
  59. package/dist/types/frustration.d.ts +70 -0
  60. package/dist/types/index.d.ts +3 -1
  61. package/dist/types/live.d.ts +30 -0
  62. package/dist/types/rollup.d.ts +115 -0
  63. package/dist/types/server.d.ts +6 -1
  64. package/dist/types/shared-types.d.ts +116 -56
  65. package/dist/types/types.d.ts +4 -0
  66. package/dist/types/user-metrics.d.ts +8 -0
  67. package/package.json +7 -13
  68. package/src/aggregator.ts +276 -211
  69. package/src/client/App.tsx +38 -80
  70. package/src/client/api.ts +65 -7
  71. package/src/client/app/LiveChip.tsx +97 -0
  72. package/src/client/app/Shell.tsx +181 -0
  73. package/src/client/app/ThemeToggle.tsx +5 -2
  74. package/src/client/app/nav.ts +78 -0
  75. package/src/client/charts/BarList.tsx +48 -0
  76. package/src/client/charts/Chart.tsx +388 -0
  77. package/src/client/charts/Legend.tsx +61 -0
  78. package/src/client/charts/ShareBar.tsx +26 -0
  79. package/src/client/charts/Sparkline.tsx +38 -0
  80. package/src/client/charts/TimeChart.tsx +22 -0
  81. package/src/client/charts/index.ts +7 -0
  82. package/src/client/charts/types.ts +27 -0
  83. package/src/client/charts/useWidth.ts +19 -0
  84. package/src/client/data/colors.ts +45 -0
  85. package/src/client/data/formatters.ts +30 -9
  86. package/src/client/data/live.tsx +85 -0
  87. package/src/client/data/query.ts +166 -0
  88. package/src/client/data/range.ts +82 -0
  89. package/src/client/data/series.ts +85 -0
  90. package/src/client/data/useHashRoute.ts +41 -83
  91. package/src/client/data/view-models.ts +347 -140
  92. package/src/client/index.tsx +3 -6
  93. package/src/client/routes/CostsRoute.tsx +394 -222
  94. package/src/client/routes/ErrorsRoute.tsx +401 -98
  95. package/src/client/routes/FrustrationRoute.tsx +924 -0
  96. package/src/client/routes/GainRoute.tsx +209 -206
  97. package/src/client/routes/ModelsRoute.tsx +540 -431
  98. package/src/client/routes/OverviewRoute.tsx +283 -311
  99. package/src/client/routes/ProjectsRoute.tsx +298 -156
  100. package/src/client/routes/ProvidersRoute.tsx +948 -416
  101. package/src/client/routes/RequestsRoute.tsx +295 -107
  102. package/src/client/routes/ToolsRoute.tsx +495 -398
  103. package/src/client/routes/TracesRoute.tsx +136 -117
  104. package/src/client/routes/costs.css +24 -0
  105. package/src/client/routes/errors.css +60 -0
  106. package/src/client/routes/frustration.css +123 -0
  107. package/src/client/routes/index.ts +1 -1
  108. package/src/client/routes/models.css +43 -0
  109. package/src/client/routes/projects.css +6 -0
  110. package/src/client/routes/providers.css +28 -0
  111. package/src/client/routes/tools.css +14 -0
  112. package/src/client/styles.css +1396 -1299
  113. package/src/client/traces/AggregatesPanel.tsx +55 -74
  114. package/src/client/traces/Minimap.tsx +6 -15
  115. package/src/client/traces/SpanDrawer.tsx +128 -194
  116. package/src/client/traces/SummaryStrip.tsx +21 -24
  117. package/src/client/traces/TimelineCanvas.tsx +27 -44
  118. package/src/client/traces/TraceView.tsx +175 -130
  119. package/src/client/traces/TranscriptList.tsx +13 -41
  120. package/src/client/traces/trace-colors.ts +60 -53
  121. package/src/client/traces/traces.css +279 -0
  122. package/src/client/ui/Badge.tsx +29 -0
  123. package/src/client/ui/Card.tsx +61 -0
  124. package/src/client/ui/Drawer.tsx +93 -0
  125. package/src/client/ui/JsonBlock.tsx +26 -44
  126. package/src/client/ui/Modal.tsx +106 -0
  127. package/src/client/ui/RequestDrawer.tsx +163 -188
  128. package/src/client/ui/SearchInput.tsx +25 -0
  129. package/src/client/ui/Segmented.tsx +61 -0
  130. package/src/client/ui/Stat.tsx +104 -0
  131. package/src/client/ui/States.tsx +81 -0
  132. package/src/client/ui/Table.tsx +229 -0
  133. package/src/client/ui/index.ts +9 -10
  134. package/src/client/ui/modal.css +54 -0
  135. package/src/client/ui/request-drawer.css +40 -0
  136. package/src/db.ts +379 -983
  137. package/src/frustration.ts +473 -0
  138. package/src/index.ts +22 -22
  139. package/src/live.ts +259 -0
  140. package/src/parser.ts +55 -66
  141. package/src/rollup.ts +951 -0
  142. package/src/server.ts +143 -87
  143. package/src/shared-types.ts +120 -56
  144. package/src/trace.ts +14 -7
  145. package/src/types.ts +4 -0
  146. package/src/user-metrics.ts +13 -0
  147. package/dist/client/styles.css +0 -1976
  148. package/dist/types/client/app/AppLayout.d.ts +0 -16
  149. package/dist/types/client/app/NavRail.d.ts +0 -7
  150. package/dist/types/client/app/RangeControl.d.ts +0 -7
  151. package/dist/types/client/app/SyncButton.d.ts +0 -14
  152. package/dist/types/client/app/TopBar.d.ts +0 -15
  153. package/dist/types/client/app/routes.d.ts +0 -12
  154. package/dist/types/client/components/AgentTokenShare.d.ts +0 -5
  155. package/dist/types/client/components/chart-shared.d.ts +0 -178
  156. package/dist/types/client/components/models-table-shared.d.ts +0 -175
  157. package/dist/types/client/components/range-meta.d.ts +0 -21
  158. package/dist/types/client/data/charts.d.ts +0 -1
  159. package/dist/types/client/data/useResource.d.ts +0 -13
  160. package/dist/types/client/routes/BehaviorRoute.d.ts +0 -7
  161. package/dist/types/client/ui/AsyncBoundary.d.ts +0 -12
  162. package/dist/types/client/ui/DataTable.d.ts +0 -17
  163. package/dist/types/client/ui/EmptyState.d.ts +0 -7
  164. package/dist/types/client/ui/ErrorState.d.ts +0 -6
  165. package/dist/types/client/ui/MetricCluster.d.ts +0 -5
  166. package/dist/types/client/ui/Panel.d.ts +0 -7
  167. package/dist/types/client/ui/SegmentedControl.d.ts +0 -12
  168. package/dist/types/client/ui/Skeleton.d.ts +0 -8
  169. package/dist/types/client/ui/StatusPill.d.ts +0 -7
  170. package/src/client/app/AppLayout.tsx +0 -93
  171. package/src/client/app/NavRail.tsx +0 -44
  172. package/src/client/app/RangeControl.tsx +0 -39
  173. package/src/client/app/SyncButton.tsx +0 -75
  174. package/src/client/app/TopBar.tsx +0 -73
  175. package/src/client/app/routes.ts +0 -93
  176. package/src/client/components/AgentTokenShare.tsx +0 -68
  177. package/src/client/components/chart-shared.tsx +0 -273
  178. package/src/client/components/models-table-shared.tsx +0 -255
  179. package/src/client/components/range-meta.ts +0 -73
  180. package/src/client/data/charts.ts +0 -14
  181. package/src/client/data/useResource.ts +0 -154
  182. package/src/client/routes/BehaviorRoute.tsx +0 -623
  183. package/src/client/ui/AsyncBoundary.tsx +0 -54
  184. package/src/client/ui/DataTable.tsx +0 -122
  185. package/src/client/ui/EmptyState.tsx +0 -16
  186. package/src/client/ui/ErrorState.tsx +0 -25
  187. package/src/client/ui/MetricCluster.tsx +0 -92
  188. package/src/client/ui/Panel.tsx +0 -24
  189. package/src/client/ui/SegmentedControl.tsx +0 -36
  190. package/src/client/ui/Skeleton.tsx +0 -17
  191. package/src/client/ui/StatusPill.tsx +0 -15
  192. package/tailwind.config.js +0 -40
@@ -1,4 +1,5 @@
1
- import type { BehaviorDashboardStats, DashboardStats, FolderStats, MessageStats, ProviderDashboardStats, RequestDetails, ToolDashboardStats } from "./types.js";
1
+ import { type RangeWindow } from "./rollup.js";
2
+ import type { DashboardStats, FolderStats, MessageStats, ProviderDashboardStats, ProviderWindowStats, RequestDetails, ToolDashboardStats } from "./types.js";
2
3
  /**
3
4
  * Serialize stats ingestion and archive reconciliation across processes.
4
5
  * The lock covers file discovery, parsing, and the final SQLite write so a
@@ -11,7 +12,7 @@ export declare function withStatsSyncLock<T>(dbPath: string, fn: () => Promise<T
11
12
  * Progress event emitted after each session file is fully processed.
12
13
  * `current` is the number of files completed (skipped + parsed),
13
14
  * `total` is the size of the work set. `processed` is the running total
14
- * of inserted rows.
15
+ * of inserted rows. Parsed files are reported only after their batch commits.
15
16
  */
16
17
  export interface SyncProgress {
17
18
  current: number;
@@ -25,9 +26,14 @@ export interface SyncOptions {
25
26
  /**
26
27
  * Worker pool size. Defaults to a sensible value derived from the host
27
28
  * (capped to avoid drowning a small machine in workers). Set to `1` to
28
- * force serial parsing without spawning workers.
29
+ * parse on the calling thread without spawning workers. File I/O is pipelined.
29
30
  */
30
31
  workers?: number;
32
+ /**
33
+ * Sync only these transcripts (e.g. files a watcher saw change) instead of
34
+ * listing every session. Missing files are skipped.
35
+ */
36
+ files?: readonly string[];
31
37
  }
32
38
  /**
33
39
  * Smoke test: spawns one sync worker, pings it, asserts the pong response,
@@ -51,9 +57,11 @@ export declare function smokeTestSyncWorker({ timeoutMs }?: {
51
57
  /**
52
58
  * Sync all session files to the database.
53
59
  *
54
- * `workers: 1` parses inline. Larger pools fan parsing out across workers
60
+ * `workers: 1` pipelines file reads and parses inline, committing in discovery order.
61
+ * Larger pools fan parsing out across workers
55
62
  * (one in-flight job per worker) while DB writes and offset bookkeeping stay on
56
- * the calling thread so the single SQLite handle stays uncontended.
63
+ * the calling thread. Bounded batches commit rows and cursors atomically without
64
+ * holding a database transaction open during file I/O.
57
65
  * `onProgress` fires once per completed file (skipped files included so the
58
66
  * bar walks at a steady rate).
59
67
  */
@@ -61,17 +69,8 @@ export declare function syncAllSessions(opts?: SyncOptions): Promise<{
61
69
  processed: number;
62
70
  files: number;
63
71
  }>;
64
- interface TimeRangeConfig {
65
- timeSeriesHours: number;
66
- timeSeriesBucketMs: number;
67
- modelSeriesDays: number;
68
- modelSeriesBucketMs: number;
69
- modelPerformanceDays: number;
70
- modelPerformanceBucketMs: number;
71
- costSeriesDays: number;
72
- cutoff: number | null;
73
- }
74
- export declare function getTimeRangeConfig(range?: string | null): TimeRangeConfig;
72
+ /** Resolve a `?range=` value (default 24h) to its cutoff and series bucket size. */
73
+ export declare function getTimeRangeConfig(range?: string | null): RangeWindow;
75
74
  /**
76
75
  * Get all dashboard stats.
77
76
  */
@@ -87,20 +86,24 @@ export declare function getRequestDetails(id: number): Promise<RequestDetails |
87
86
  * Get the current message count in the database.
88
87
  */
89
88
  export declare function getTotalMessageCount(): Promise<number>;
90
- export declare function getBehaviorDashboardStats(range?: string | null): Promise<BehaviorDashboardStats>;
91
89
  /**
92
90
  * Get the tools dashboard payload: per-tool totals, per-(tool, model)
93
- * breakdown, and the call time series (bucketed like the model series).
91
+ * breakdown, and the call time series.
94
92
  */
95
93
  export declare function getToolDashboardStats(range?: string | null): Promise<ToolDashboardStats>;
96
94
  /**
97
95
  * Get the providers dashboard payload: per-provider totals, peak-burn-hours
98
- * histogram, provider token time series, and subscription-window analytics
99
- * (utilization series + insights) derived from recorded usage-limit snapshots.
96
+ * histogram and provider token time series. Subscription windows are served
97
+ * separately by {@link getProviderWindowStats}.
98
+ */
99
+ export declare function getProviderDashboardStats(range?: string | null): Promise<ProviderDashboardStats>;
100
+ /**
101
+ * Subscription-window analytics derived from recorded usage-limit snapshots:
102
+ * insights for every provider window, plus utilization series for `provider`
103
+ * only (all series can number in the thousands).
100
104
  *
101
105
  * Window token estimates use broker-held fleet token burn when a broker is
102
106
  * configured — the window fractions cover every install sharing the broker's
103
107
  * credentials, so dividing them into local-only tokens would undercount.
104
108
  */
105
- export declare function getProviderDashboardStats(range?: string | null): Promise<ProviderDashboardStats>;
106
- export {};
109
+ export declare function getProviderWindowStats(range?: string | null, provider?: string | null): Promise<ProviderWindowStats>;
@@ -1,4 +1,4 @@
1
- import type { BehaviorDashboardStats, CostDashboardStats, FolderStats, GainDashboardStats, MessageStats, ModelDashboardStats, OverviewStats, ProviderDashboardStats, RequestDetails, SessionSummary, SessionTrace, TimeRange, ToolDashboardStats } from "./types.js";
1
+ import type { CostDashboardStats, FolderStats, FrustrationDashboardStats, FrustrationEstimate, FrustrationJobStatus, GainDashboardStats, LiveStatus, MessageStats, ModelDashboardStats, OverviewStats, ProviderDashboardStats, ProviderWindowStats, RequestDetails, SessionSummary, SessionTrace, TimeRange, ToolDashboardStats } from "./types.js";
2
2
  export declare class ApiError extends Error {
3
3
  status: number;
4
4
  endpoint: string;
@@ -10,16 +10,20 @@ export declare function getCostDashboardStats(range?: TimeRange, signal?: AbortS
10
10
  export declare function getRecentRequests(limit?: number, signal?: AbortSignal): Promise<MessageStats[]>;
11
11
  export declare function getRecentErrors(range?: TimeRange, limit?: number, signal?: AbortSignal): Promise<MessageStats[]>;
12
12
  export declare function getRequestDetails(id: number, signal?: AbortSignal): Promise<RequestDetails>;
13
- export declare function sync(signal?: AbortSignal): Promise<{
14
- processed: number;
15
- files: number;
16
- totalMessages: number;
17
- }>;
18
- export declare function getBehaviorDashboardStats(range?: TimeRange, signal?: AbortSignal): Promise<BehaviorDashboardStats>;
13
+ /** Ask the server to start a background sync; progress arrives on the `/api/events` stream. */
14
+ export declare function requestSync(): Promise<LiveStatus>;
15
+ export declare function getFrustrationDashboardStats(range?: TimeRange, signal?: AbortSignal): Promise<FrustrationDashboardStats>;
16
+ /** Pre-run cost quote for judging every unjudged message in `range`. */
17
+ export declare function getFrustrationEstimate(range: TimeRange, signal?: AbortSignal): Promise<FrustrationEstimate>;
18
+ /** Start the judge run over the unjudged messages in `range`. Spends money. */
19
+ export declare function startFrustrationRun(range: TimeRange): Promise<FrustrationJobStatus>;
20
+ export declare function cancelFrustrationRun(): Promise<FrustrationJobStatus>;
19
21
  export declare function getFolderStats(range?: TimeRange, signal?: AbortSignal): Promise<FolderStats[]>;
20
22
  export declare function getGainDashboardStats(range?: TimeRange, project?: string | null, signal?: AbortSignal): Promise<GainDashboardStats>;
21
23
  export declare function getToolDashboardStats(range?: TimeRange, signal?: AbortSignal): Promise<ToolDashboardStats>;
22
24
  export declare function getProviderDashboardStats(range?: TimeRange, signal?: AbortSignal): Promise<ProviderDashboardStats>;
25
+ /** Subscription-window insights (all providers) plus utilization series for `provider` only. */
26
+ export declare function getProviderWindowStats(range: TimeRange, provider: string | null, signal?: AbortSignal): Promise<ProviderWindowStats>;
23
27
  export declare function getSessions(limit?: number, q?: string, signal?: AbortSignal): Promise<SessionSummary[]>;
24
28
  export declare function getSessionTrace(file: string, signal?: AbortSignal): Promise<SessionTrace>;
25
29
  /** Fetch one full journal entry for the span drawer. Entries are opaque JSON. */
@@ -0,0 +1,2 @@
1
+ /** Topbar ingest status; click to sync now. */
2
+ export declare function LiveChip(): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { type ReactNode } from "react";
2
+ import type { TimeRange } from "../types.js";
3
+ import { type DashboardSection } from "./nav.js";
4
+ export interface ShellProps {
5
+ section: DashboardSection;
6
+ onSectionChange: (section: DashboardSection) => void;
7
+ range: TimeRange;
8
+ onRangeChange: (range: TimeRange) => void;
9
+ children: ReactNode;
10
+ }
11
+ /**
12
+ * Fixed glass topbar + naked sidebar framing one inset content panel. Owns
13
+ * the global keyboard shortcuts: `1`–`6` pick a range, `g` then a letter jumps
14
+ * to a page.
15
+ */
16
+ export declare function Shell({ section, onSectionChange, range, onRangeChange, children }: ShellProps): import("react").JSX.Element;
@@ -1 +1,2 @@
1
+ /** Cycles system → light → dark. */
1
2
  export declare function ThemeToggle(): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { type LucideIcon } from "lucide-react";
2
+ export type DashboardSection = "overview" | "models" | "providers" | "costs" | "requests" | "errors" | "traces" | "tools" | "frustration" | "projects" | "gain";
3
+ export interface NavItem {
4
+ id: DashboardSection;
5
+ label: string;
6
+ icon: LucideIcon;
7
+ /** Second key of the `g <key>` jump shortcut. */
8
+ hotkey: string;
9
+ }
10
+ export interface NavGroup {
11
+ heading: string;
12
+ items: readonly NavItem[];
13
+ }
14
+ /** Sidebar structure; the order here is the order on screen. */
15
+ export declare const NAV: readonly NavGroup[];
16
+ export declare const NAV_ITEMS: readonly NavItem[];
17
+ export declare const SECTIONS: readonly DashboardSection[];
18
+ export declare function navItem(section: DashboardSection): NavItem;
@@ -0,0 +1,17 @@
1
+ import type { ReactNode } from "react";
2
+ export interface BarListItem {
3
+ key: string;
4
+ label: ReactNode;
5
+ value: number;
6
+ /** Formatted figure; defaults to `value.toLocaleString()`. */
7
+ display?: string;
8
+ color?: string;
9
+ }
10
+ export interface BarListProps {
11
+ items: readonly BarListItem[];
12
+ /** Scale maximum; defaults to the largest value. */
13
+ max?: number;
14
+ onSelect?: (key: string) => void;
15
+ }
16
+ /** Ranked rows with a proportional tinted fill behind each label. */
17
+ export declare function BarList({ items, max, onSelect }: BarListProps): import("react").JSX.Element;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Core SVG chart: categorical x slots, stacked or grouped bars, stacked areas
3
+ * and lines, an optional right axis, a hover cursor and a floating tooltip.
4
+ * Marks are crisp and unsmoothed — square bars, straight segments.
5
+ *
6
+ * `TimeChart` wraps this for bucketed time series; use `Chart` directly for
7
+ * categorical axes (hour of day, model names).
8
+ */
9
+ import { type ReactNode } from "react";
10
+ import type { ChartKind, ChartSeries, ReferenceLine } from "./types.js";
11
+ export interface ChartProps {
12
+ /** Number of x positions; every series' `values` has this length. */
13
+ slots: number;
14
+ /** Axis label for a slot. */
15
+ tickLabel: (index: number) => string;
16
+ /** Tooltip heading for a slot; defaults to `tickLabel`. */
17
+ tooltipTitle?: (index: number) => string;
18
+ series: readonly ChartSeries[];
19
+ /** Mark for series without their own `kind`. Default `"bars"`. */
20
+ kind?: ChartKind;
21
+ /** Stack left-axis bars/areas. Default true. */
22
+ stacked?: boolean;
23
+ /** Plot height in px (axes included). Default 220. */
24
+ height?: number;
25
+ /** Left-axis and tooltip formatter. Default compact number. */
26
+ format?: (value: number) => string;
27
+ formatRight?: (value: number) => string;
28
+ /** Tooltip value formatter (left axis); defaults to `format`. Use for full precision. */
29
+ formatTooltip?: (value: number) => string;
30
+ /** Fixed axis maxima (e.g. 1 for fractions); otherwise nice-rounded from data. */
31
+ yMax?: number;
32
+ yMaxRight?: number;
33
+ /** Series keys to omit (legend toggles). */
34
+ hidden?: ReadonlySet<string>;
35
+ /** Show a total row in the tooltip. Default: stacked with 2+ series. */
36
+ showTotal?: boolean;
37
+ references?: readonly ReferenceLine[];
38
+ onSelect?: (index: number) => void;
39
+ /** Shown over empty axes when every visible value is zero. */
40
+ emptyLabel?: ReactNode;
41
+ /** Extra tooltip rows for a slot (below the series rows). */
42
+ tooltipExtra?: (index: number) => ReactNode;
43
+ }
44
+ export declare function Chart({ slots, tickLabel, tooltipTitle, series, kind, stacked, height, format, formatRight, formatTooltip, yMax, yMaxRight, hidden, showTotal, references, onSelect, emptyLabel, tooltipExtra, }: ChartProps): import("react").JSX.Element;
45
+ /** Round an axis maximum up to 1/2/2.5/5 × 10ⁿ and return evenly spaced ticks from 0. */
46
+ export declare function niceScale(max: number): {
47
+ max: number;
48
+ ticks: number[];
49
+ };
@@ -0,0 +1,16 @@
1
+ export interface LegendItem {
2
+ key: string;
3
+ label: string;
4
+ color: string;
5
+ /** Optional right-hand figure (e.g. the series total). */
6
+ value?: string;
7
+ }
8
+ export interface LegendProps {
9
+ items: readonly LegendItem[];
10
+ /** Keys currently hidden; with `onToggle` the items become buttons. */
11
+ hidden?: ReadonlySet<string>;
12
+ onToggle?: (key: string) => void;
13
+ }
14
+ export declare function Legend({ items, hidden, onToggle }: LegendProps): import("react").JSX.Element;
15
+ /** Hidden-key set for toggleable legends. */
16
+ export declare function useHiddenSeries(): [ReadonlySet<string>, (key: string) => void];
@@ -0,0 +1,11 @@
1
+ export interface ShareSegment {
2
+ key: string;
3
+ label: string;
4
+ value: number;
5
+ color: string;
6
+ }
7
+ /** Single horizontal bar split proportionally among segments (zero segments omitted). */
8
+ export declare function ShareBar({ segments, height }: {
9
+ segments: readonly ShareSegment[];
10
+ height?: number;
11
+ }): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ export interface SparklineProps {
2
+ values: readonly number[];
3
+ color?: string;
4
+ height?: number;
5
+ /** Fixed width; omit to fill the container. */
6
+ width?: number;
7
+ /** Fill under the line. Default true. */
8
+ area?: boolean;
9
+ }
10
+ /** Axis-free trend line for stat tiles and table cells. */
11
+ export declare function Sparkline({ values, color, height, width, area }: SparklineProps): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { type ChartProps } from "./Chart.js";
2
+ export interface TimeChartProps extends Omit<ChartProps, "slots" | "tickLabel" | "tooltipTitle" | "onSelect"> {
3
+ /** Ascending bucket starts (see `bucketAxis`); series values align with them. */
4
+ buckets: readonly number[];
5
+ bucketMs: number;
6
+ onSelectBucket?: (bucketStart: number) => void;
7
+ }
8
+ /** {@link Chart} over a bucketed time axis. */
9
+ export declare function TimeChart({ buckets, bucketMs, onSelectBucket, ...rest }: TimeChartProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ export * from "./BarList.js";
2
+ export * from "./Chart.js";
3
+ export * from "./Legend.js";
4
+ export * from "./ShareBar.js";
5
+ export * from "./Sparkline.js";
6
+ export * from "./TimeChart.js";
7
+ export * from "./types.js";
@@ -0,0 +1,25 @@
1
+ /** One plotted series. `values` align index-for-index with the chart's x slots. */
2
+ export interface ChartSeries {
3
+ key: string;
4
+ label: string;
5
+ color: string;
6
+ /** `null` leaves a gap (lines) or an empty slot (bars). */
7
+ values: readonly (number | null)[];
8
+ /** Overrides the chart-level `kind` for this series (e.g. a line over bars). */
9
+ kind?: ChartKind;
10
+ /** Plot against the right axis (uses `formatRight`). Right-axis series never stack. */
11
+ axis?: "left" | "right";
12
+ /** Diagonal hatching instead of a solid fill (bars/areas). */
13
+ pattern?: "hatch";
14
+ /** Dashed stroke (lines). */
15
+ dashed?: boolean;
16
+ /** Leave this series out of the tooltip (e.g. a trend line that repeats a bar). */
17
+ tooltip?: false;
18
+ }
19
+ export type ChartKind = "bars" | "area" | "line";
20
+ /** Horizontal guide drawn across the plot (e.g. a 100% capacity line). */
21
+ export interface ReferenceLine {
22
+ value: number;
23
+ label?: string;
24
+ axis?: "left" | "right";
25
+ }
@@ -0,0 +1,3 @@
1
+ import { type RefObject } from "react";
2
+ /** Track an element's content width (ResizeObserver). Returns 0 until measured. */
3
+ export declare function useWidth<T extends HTMLElement>(): [RefObject<T | null>, number];
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Categorical series colors. Every multi-series chart and table swatch draws
3
+ * from {@link SERIES_COLORS}; ranking keeps the busiest series on the most
4
+ * legible hues and the same key on the same hue across charts of one page.
5
+ */
6
+ /** Ten mid-luminance hues that read on both the black and the white chassis. */
7
+ export declare const SERIES_COLORS: string[];
8
+ /** Neutral used for "Other" rollups and unknown buckets. */
9
+ export declare const OTHER_COLOR = "#6c6c74";
10
+ /** Stable identity of a model across providers: `model::provider`. */
11
+ export declare function modelKey(model: string, provider: string): string;
12
+ /**
13
+ * Assign colors by descending weight (ties broken by key) so a model's hue is
14
+ * determined by its rank, not by response order. Used by every per-model view.
15
+ */
16
+ export declare function buildModelColorLookup(records: readonly {
17
+ model: string;
18
+ provider: string;
19
+ totalRequests: number;
20
+ }[]): Map<string, string>;
21
+ /** Generic form of {@link buildModelColorLookup} for any keyed, weighted series. */
22
+ export declare function buildColorLookup(items: readonly {
23
+ key: string;
24
+ weight: number;
25
+ }[]): Map<string, string>;
@@ -5,14 +5,22 @@ export declare function formatCost(value: number, digits?: number): string;
5
5
  /** Format an API-equivalent estimate, using N/A when all usage is unpriced. */
6
6
  export declare function formatEstimatedCost(value: number, unpricedRequests: number, digits?: number): string;
7
7
  /**
8
- * Format one request's cost, distinguishing unpriced usage from free usage.
8
+ * Whether one request's zero cost means unknown spend rather than free usage.
9
9
  * Mirrors the server's `unpricedRequestSql`: `xai-oauth` is billed through a
10
10
  * subscription, and `costUnpriced` marks a row the ingest path could not price
11
11
  * (a scheduled card with no recoverable request timestamp).
12
12
  */
13
+ export declare function isUnpricedMessage(message: Pick<MessageStats, "provider" | "usage" | "costUnpriced">): boolean;
14
+ /** Format one request's cost, distinguishing unpriced usage from free usage. */
13
15
  export declare function formatMessageCost(message: Pick<MessageStats, "provider" | "usage" | "costUnpriced">, digits?: number): string;
14
16
  export declare function formatPercent(value: number, digits?: number): string;
15
17
  export declare function formatDurationMs(value: number | null, digits?: number): string;
18
+ /** Wall-clock length: seconds below a minute, then `17m 41s` / `2h 05m`. */
19
+ export declare function formatElapsed(ms: number): string;
16
20
  export declare function formatTokensPerSecond(value: number | null): string;
17
21
  export declare function formatRelativeTime(timestamp: number): string;
22
+ /** Absolute local date and time to the second ("Sep 28, 14:03:22"). */
23
+ export declare function formatTimestamp(timestamp: number): string;
24
+ /** Project folder as stored by ingest (`/work-pi/`), without the slashes. */
25
+ export declare function formatFolder(folder: string): string;
18
26
  export declare function formatBytes(value: number): string;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Live dashboard state streamed from the server (`/api/events`): the data
3
+ * version every query revalidates against, background ingest progress, and
4
+ * the rollup indexing backlog.
5
+ *
6
+ * Any component can `useLive()`; queries (see `useQuery`) refetch whenever
7
+ * `version` advances, so rows ingested in the background repaint the active
8
+ * page without a manual refresh.
9
+ */
10
+ import { type ReactNode } from "react";
11
+ import type { LiveSyncStatus } from "../types.js";
12
+ export interface LiveState {
13
+ /**
14
+ * Client-side data version; bumps whenever the server's version changes
15
+ * (including after reconnecting to a restarted server).
16
+ */
17
+ version: number;
18
+ sync: LiveSyncStatus;
19
+ /** Hours whose rollups are still being built; wide ranges may be incomplete. */
20
+ indexingHours: number;
21
+ /** The event stream is open. */
22
+ connected: boolean;
23
+ /** Ask the server to ingest new session data now. */
24
+ requestSync: () => void;
25
+ }
26
+ export declare function LiveProvider({ children }: {
27
+ children: ReactNode;
28
+ }): import("react").JSX.Element;
29
+ export declare function useLive(): LiveState;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `useQuery`: cached, live-revalidating data fetching for dashboard pages.
3
+ *
4
+ * - Results are cached per key for the page session, so revisiting a page or
5
+ * a range renders instantly from memory.
6
+ * - A key change keeps the previous key's data on screen (`stale: true`) until
7
+ * the new data lands, so charts morph instead of flashing skeletons.
8
+ * - Every cached entry remembers the live data version it was fetched at
9
+ * (see `useLive`); when the version advances, enabled queries refetch in the
10
+ * background, throttled so a burst of ingest batches costs one request.
11
+ * - Identical keys share one in-flight request.
12
+ */
13
+ export interface QueryOptions {
14
+ /** Only enabled queries fetch; hidden (kept-alive) pages pass `false`. Default true. */
15
+ enabled?: boolean;
16
+ /** Additionally refetch on this interval while enabled and the tab is visible. */
17
+ pollMs?: number;
18
+ }
19
+ export interface QueryResult<T> {
20
+ /** Data for the current key, or the previous key's data while it loads. */
21
+ data: T | null;
22
+ error: Error | null;
23
+ /** Nothing to show yet (first load of this query). */
24
+ loading: boolean;
25
+ /** `data` belongs to a previous key; the current key is still loading. */
26
+ stale: boolean;
27
+ /** A background request is in flight. */
28
+ refreshing: boolean;
29
+ /** When the shown data was fetched. */
30
+ updatedAt: number | null;
31
+ refetch: () => void;
32
+ }
33
+ /** Warm the cache for a key the user is likely to open next. No-op if cached for this version. */
34
+ export declare function prefetchQuery<T>(key: readonly unknown[], fetcher: () => Promise<T>, version: number): void;
35
+ export declare function useQuery<T>(key: readonly unknown[], fetcher: () => Promise<T>, options?: QueryOptions): QueryResult<T>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Time-range metadata shared by every page: labels, the server's bucket size
3
+ * for the range (mirrors `getTimeRangeConfig` in `aggregator.ts`), tick
4
+ * formatting, and the contiguous bucket axis charts plot against.
5
+ */
6
+ import type { TimeRange } from "../types.js";
7
+ export interface RangeMeta {
8
+ /** Short selector label ("24h"). */
9
+ label: string;
10
+ /** Prose label for subtitles ("the last 24 hours"). */
11
+ windowLabel: string;
12
+ /** Span covered by the range; `null` for all time. */
13
+ spanMs: number | null;
14
+ /** Bucket size of the server's primary time series for this range. */
15
+ bucketMs: number;
16
+ }
17
+ /** Ranges in selector order. */
18
+ export declare const TIME_RANGES: readonly TimeRange[];
19
+ export declare function rangeMeta(range: TimeRange): RangeMeta;
20
+ /** Tick/tooltip label for a bucket start, precise enough for the bucket size. */
21
+ export declare function formatBucket(timestamp: number, bucketMs: number): string;
22
+ /** Compact axis tick for a bucket start. */
23
+ export declare function formatTick(timestamp: number, bucketMs: number): string;
24
+ /**
25
+ * Contiguous, ascending bucket starts covering `range`, aligned like the
26
+ * server's `(timestamp / bucketMs) * bucketMs`. For all time the axis starts at
27
+ * the earliest data point, so gaps between sessions still render as zeros;
28
+ * it is capped at {@link MAX_BUCKETS} so a stray epoch-zero timestamp cannot
29
+ * explode the axis into decades of empty days.
30
+ */
31
+ export declare function bucketAxis(range: TimeRange, dataTimestamps: Iterable<number>, bucketMs?: number, now?: number): number[];
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Turn the server's long-format bucket rows (`{ timestamp, key…, value }`)
3
+ * into dense chart series aligned to a bucket axis (see `bucketAxis`).
4
+ */
5
+ import type { ChartSeries } from "../charts/types.js";
6
+ /** Sum `value(point)` into a dense array aligned to `buckets`. Out-of-axis points are dropped. */
7
+ export declare function densify<P extends {
8
+ timestamp: number;
9
+ }>(points: readonly P[], buckets: readonly number[], value: (point: P) => number): number[];
10
+ export interface PivotOptions<P> {
11
+ buckets: readonly number[];
12
+ /** Series identity for a row. */
13
+ key: (point: P) => string;
14
+ /** Display label for a series key; defaults to the key. */
15
+ label?: (key: string) => string;
16
+ value: (point: P) => number;
17
+ /** Keep the `limit` largest series (by total); fold the rest into "Other". */
18
+ limit?: number;
19
+ /** Fixed colors per key (e.g. from `buildModelColorLookup`); falls back to rank order. */
20
+ colors?: ReadonlyMap<string, string>;
21
+ }
22
+ /**
23
+ * Pivot long rows into one dense series per key, ranked by total (largest
24
+ * first). Series beyond `limit` merge into a trailing "Other" series.
25
+ */
26
+ export declare function pivotSeries<P extends {
27
+ timestamp: number;
28
+ }>(points: readonly P[], opts: PivotOptions<P>): ChartSeries[];
@@ -1,10 +1,24 @@
1
- import type { DashboardSection } from "../app/routes.js";
1
+ import { type DashboardSection } from "../app/nav.js";
2
2
  import type { TimeRange } from "../types.js";
3
+ export interface HashRoute {
4
+ section: DashboardSection;
5
+ range: TimeRange;
6
+ /** Deep-linked trace session file (traces only). */
7
+ session: string | null;
8
+ }
9
+ export declare function parseHash(hash: string): HashRoute;
10
+ export declare function buildHash({ section, range, session }: HashRoute): string;
11
+ /**
12
+ * Dashboard location in the URL hash (`#/models?range=7d`). Setters update
13
+ * state synchronously and push a history entry, so the UI never waits on the
14
+ * `hashchange` round trip.
15
+ */
3
16
  export declare function useHashRoute(): {
4
17
  section: DashboardSection;
5
- setSection: (newSection: DashboardSection) => void;
6
18
  range: TimeRange;
7
- setRange: (newRange: string) => void;
19
+ /** Deep-linked trace session file (traces only). */
8
20
  session: string | null;
9
- setSession: (file: string | null) => void;
21
+ setSection: (section: DashboardSection) => void;
22
+ setRange: (range: TimeRange) => void;
23
+ setSession: (session: string | null) => void;
10
24
  };