@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.
- package/CHANGELOG.md +22 -0
- package/README.md +13 -11
- package/build.ts +5 -49
- package/dist/client/index.css +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/index.js +105 -126
- package/dist/types/aggregator.d.ts +25 -22
- package/dist/types/client/api.d.ts +11 -7
- package/dist/types/client/app/LiveChip.d.ts +2 -0
- package/dist/types/client/app/Shell.d.ts +16 -0
- package/dist/types/client/app/ThemeToggle.d.ts +1 -0
- package/dist/types/client/app/nav.d.ts +18 -0
- package/dist/types/client/charts/BarList.d.ts +17 -0
- package/dist/types/client/charts/Chart.d.ts +49 -0
- package/dist/types/client/charts/Legend.d.ts +16 -0
- package/dist/types/client/charts/ShareBar.d.ts +11 -0
- package/dist/types/client/charts/Sparkline.d.ts +11 -0
- package/dist/types/client/charts/TimeChart.d.ts +9 -0
- package/dist/types/client/charts/index.d.ts +7 -0
- package/dist/types/client/charts/types.d.ts +25 -0
- package/dist/types/client/charts/useWidth.d.ts +3 -0
- package/dist/types/client/data/colors.d.ts +25 -0
- package/dist/types/client/data/formatters.d.ts +9 -1
- package/dist/types/client/data/live.d.ts +29 -0
- package/dist/types/client/data/query.d.ts +35 -0
- package/dist/types/client/data/range.d.ts +31 -0
- package/dist/types/client/data/series.d.ts +28 -0
- package/dist/types/client/data/useHashRoute.d.ts +18 -4
- package/dist/types/client/data/view-models.d.ts +111 -28
- package/dist/types/client/index.d.ts +0 -1
- package/dist/types/client/routes/CostsRoute.d.ts +2 -2
- package/dist/types/client/routes/ErrorsRoute.d.ts +2 -2
- package/dist/types/client/routes/FrustrationRoute.d.ts +7 -0
- package/dist/types/client/routes/GainRoute.d.ts +1 -2
- package/dist/types/client/routes/ModelsRoute.d.ts +2 -2
- package/dist/types/client/routes/OverviewRoute.d.ts +1 -2
- package/dist/types/client/routes/ProjectsRoute.d.ts +2 -2
- package/dist/types/client/routes/ProvidersRoute.d.ts +2 -2
- package/dist/types/client/routes/RequestsRoute.d.ts +1 -2
- package/dist/types/client/routes/ToolsRoute.d.ts +2 -2
- package/dist/types/client/routes/TracesRoute.d.ts +2 -2
- package/dist/types/client/routes/index.d.ts +1 -1
- package/dist/types/client/traces/AggregatesPanel.d.ts +4 -3
- package/dist/types/client/traces/SummaryStrip.d.ts +2 -2
- package/dist/types/client/traces/trace-colors.d.ts +18 -12
- package/dist/types/client/ui/Badge.d.ts +19 -0
- package/dist/types/client/ui/Card.d.ts +25 -0
- package/dist/types/client/ui/Drawer.d.ts +25 -0
- package/dist/types/client/ui/JsonBlock.d.ts +2 -2
- package/dist/types/client/ui/Modal.d.ts +23 -0
- package/dist/types/client/ui/RequestDrawer.d.ts +10 -1
- package/dist/types/client/ui/SearchInput.d.ts +8 -0
- package/dist/types/client/ui/Segmented.d.ts +15 -0
- package/dist/types/client/ui/Stat.d.ts +36 -0
- package/dist/types/client/ui/States.d.ts +41 -0
- package/dist/types/client/ui/Table.d.ts +48 -0
- package/dist/types/client/ui/index.d.ts +9 -10
- package/dist/types/db.d.ts +90 -106
- package/dist/types/frustration.d.ts +70 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/live.d.ts +30 -0
- package/dist/types/rollup.d.ts +115 -0
- package/dist/types/server.d.ts +6 -1
- package/dist/types/shared-types.d.ts +116 -56
- package/dist/types/types.d.ts +4 -0
- package/dist/types/user-metrics.d.ts +8 -0
- package/package.json +7 -13
- package/src/aggregator.ts +276 -211
- package/src/client/App.tsx +38 -80
- package/src/client/api.ts +65 -7
- package/src/client/app/LiveChip.tsx +97 -0
- package/src/client/app/Shell.tsx +181 -0
- package/src/client/app/ThemeToggle.tsx +5 -2
- package/src/client/app/nav.ts +78 -0
- package/src/client/charts/BarList.tsx +48 -0
- package/src/client/charts/Chart.tsx +388 -0
- package/src/client/charts/Legend.tsx +61 -0
- package/src/client/charts/ShareBar.tsx +26 -0
- package/src/client/charts/Sparkline.tsx +38 -0
- package/src/client/charts/TimeChart.tsx +22 -0
- package/src/client/charts/index.ts +7 -0
- package/src/client/charts/types.ts +27 -0
- package/src/client/charts/useWidth.ts +19 -0
- package/src/client/data/colors.ts +45 -0
- package/src/client/data/formatters.ts +30 -9
- package/src/client/data/live.tsx +85 -0
- package/src/client/data/query.ts +166 -0
- package/src/client/data/range.ts +82 -0
- package/src/client/data/series.ts +85 -0
- package/src/client/data/useHashRoute.ts +41 -83
- package/src/client/data/view-models.ts +347 -140
- package/src/client/index.tsx +3 -6
- package/src/client/routes/CostsRoute.tsx +394 -222
- package/src/client/routes/ErrorsRoute.tsx +401 -98
- package/src/client/routes/FrustrationRoute.tsx +924 -0
- package/src/client/routes/GainRoute.tsx +209 -206
- package/src/client/routes/ModelsRoute.tsx +540 -431
- package/src/client/routes/OverviewRoute.tsx +283 -311
- package/src/client/routes/ProjectsRoute.tsx +298 -156
- package/src/client/routes/ProvidersRoute.tsx +948 -416
- package/src/client/routes/RequestsRoute.tsx +295 -107
- package/src/client/routes/ToolsRoute.tsx +495 -398
- package/src/client/routes/TracesRoute.tsx +136 -117
- package/src/client/routes/costs.css +24 -0
- package/src/client/routes/errors.css +60 -0
- package/src/client/routes/frustration.css +123 -0
- package/src/client/routes/index.ts +1 -1
- package/src/client/routes/models.css +43 -0
- package/src/client/routes/projects.css +6 -0
- package/src/client/routes/providers.css +28 -0
- package/src/client/routes/tools.css +14 -0
- package/src/client/styles.css +1396 -1299
- package/src/client/traces/AggregatesPanel.tsx +55 -74
- package/src/client/traces/Minimap.tsx +6 -15
- package/src/client/traces/SpanDrawer.tsx +128 -194
- package/src/client/traces/SummaryStrip.tsx +21 -24
- package/src/client/traces/TimelineCanvas.tsx +27 -44
- package/src/client/traces/TraceView.tsx +175 -130
- package/src/client/traces/TranscriptList.tsx +13 -41
- package/src/client/traces/trace-colors.ts +60 -53
- package/src/client/traces/traces.css +279 -0
- package/src/client/ui/Badge.tsx +29 -0
- package/src/client/ui/Card.tsx +61 -0
- package/src/client/ui/Drawer.tsx +93 -0
- package/src/client/ui/JsonBlock.tsx +26 -44
- package/src/client/ui/Modal.tsx +106 -0
- package/src/client/ui/RequestDrawer.tsx +163 -188
- package/src/client/ui/SearchInput.tsx +25 -0
- package/src/client/ui/Segmented.tsx +61 -0
- package/src/client/ui/Stat.tsx +104 -0
- package/src/client/ui/States.tsx +81 -0
- package/src/client/ui/Table.tsx +229 -0
- package/src/client/ui/index.ts +9 -10
- package/src/client/ui/modal.css +54 -0
- package/src/client/ui/request-drawer.css +40 -0
- package/src/db.ts +379 -983
- package/src/frustration.ts +473 -0
- package/src/index.ts +22 -22
- package/src/live.ts +259 -0
- package/src/parser.ts +55 -66
- package/src/rollup.ts +951 -0
- package/src/server.ts +143 -87
- package/src/shared-types.ts +120 -56
- package/src/trace.ts +14 -7
- package/src/types.ts +4 -0
- package/src/user-metrics.ts +13 -0
- package/dist/client/styles.css +0 -1976
- package/dist/types/client/app/AppLayout.d.ts +0 -16
- package/dist/types/client/app/NavRail.d.ts +0 -7
- package/dist/types/client/app/RangeControl.d.ts +0 -7
- package/dist/types/client/app/SyncButton.d.ts +0 -14
- package/dist/types/client/app/TopBar.d.ts +0 -15
- package/dist/types/client/app/routes.d.ts +0 -12
- package/dist/types/client/components/AgentTokenShare.d.ts +0 -5
- package/dist/types/client/components/chart-shared.d.ts +0 -178
- package/dist/types/client/components/models-table-shared.d.ts +0 -175
- package/dist/types/client/components/range-meta.d.ts +0 -21
- package/dist/types/client/data/charts.d.ts +0 -1
- package/dist/types/client/data/useResource.d.ts +0 -13
- package/dist/types/client/routes/BehaviorRoute.d.ts +0 -7
- package/dist/types/client/ui/AsyncBoundary.d.ts +0 -12
- package/dist/types/client/ui/DataTable.d.ts +0 -17
- package/dist/types/client/ui/EmptyState.d.ts +0 -7
- package/dist/types/client/ui/ErrorState.d.ts +0 -6
- package/dist/types/client/ui/MetricCluster.d.ts +0 -5
- package/dist/types/client/ui/Panel.d.ts +0 -7
- package/dist/types/client/ui/SegmentedControl.d.ts +0 -12
- package/dist/types/client/ui/Skeleton.d.ts +0 -8
- package/dist/types/client/ui/StatusPill.d.ts +0 -7
- package/src/client/app/AppLayout.tsx +0 -93
- package/src/client/app/NavRail.tsx +0 -44
- package/src/client/app/RangeControl.tsx +0 -39
- package/src/client/app/SyncButton.tsx +0 -75
- package/src/client/app/TopBar.tsx +0 -73
- package/src/client/app/routes.ts +0 -93
- package/src/client/components/AgentTokenShare.tsx +0 -68
- package/src/client/components/chart-shared.tsx +0 -273
- package/src/client/components/models-table-shared.tsx +0 -255
- package/src/client/components/range-meta.ts +0 -73
- package/src/client/data/charts.ts +0 -14
- package/src/client/data/useResource.ts +0 -154
- package/src/client/routes/BehaviorRoute.tsx +0 -623
- package/src/client/ui/AsyncBoundary.tsx +0 -54
- package/src/client/ui/DataTable.tsx +0 -122
- package/src/client/ui/EmptyState.tsx +0 -16
- package/src/client/ui/ErrorState.tsx +0 -25
- package/src/client/ui/MetricCluster.tsx +0 -92
- package/src/client/ui/Panel.tsx +0 -24
- package/src/client/ui/SegmentedControl.tsx +0 -36
- package/src/client/ui/Skeleton.tsx +0 -17
- package/src/client/ui/StatusPill.tsx +0 -15
- package/tailwind.config.js +0 -40
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
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
|
-
*
|
|
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`
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
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
|
|
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
|
|
99
|
-
*
|
|
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
|
|
106
|
-
export {};
|
|
109
|
+
export declare function getProviderWindowStats(range?: string | null, provider?: string | null): Promise<ProviderWindowStats>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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,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;
|
|
@@ -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,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,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
|
-
*
|
|
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
|
|
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
|
-
|
|
19
|
+
/** Deep-linked trace session file (traces only). */
|
|
8
20
|
session: string | null;
|
|
9
|
-
|
|
21
|
+
setSection: (section: DashboardSection) => void;
|
|
22
|
+
setRange: (range: TimeRange) => void;
|
|
23
|
+
setSession: (session: string | null) => void;
|
|
10
24
|
};
|