@oh-my-pi/omp-stats 16.0.3 → 16.0.5
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 +15 -0
- package/build.ts +11 -0
- package/dist/client/index.css +1 -1
- package/dist/client/index.html +11 -0
- package/dist/client/index.js +108 -108
- package/dist/client/styles.css +1070 -631
- package/dist/types/client/api.d.ts +19 -10
- package/dist/types/client/app/AppLayout.d.ts +16 -0
- package/dist/types/client/app/NavRail.d.ts +7 -0
- package/dist/types/client/app/RangeControl.d.ts +7 -0
- package/dist/types/client/app/SyncButton.d.ts +14 -0
- package/dist/types/client/app/ThemeToggle.d.ts +1 -0
- package/dist/types/client/app/TopBar.d.ts +15 -0
- package/dist/types/client/app/routes.d.ts +12 -0
- package/dist/types/client/components/chart-shared.d.ts +26 -40
- package/dist/types/client/components/models-table-shared.d.ts +20 -40
- package/dist/types/client/data/charts.d.ts +1 -0
- package/dist/types/client/data/formatters.d.ts +7 -0
- package/dist/types/client/data/useHashRoute.d.ts +8 -0
- package/dist/types/client/data/useResource.d.ts +13 -0
- package/dist/types/client/data/view-models.d.ts +37 -0
- package/dist/types/client/index.d.ts +1 -0
- package/dist/types/client/routes/BehaviorRoute.d.ts +7 -0
- package/dist/types/client/routes/CostsRoute.d.ts +7 -0
- package/dist/types/client/routes/ErrorsRoute.d.ts +8 -0
- package/dist/types/client/routes/ModelsRoute.d.ts +7 -0
- package/dist/types/client/routes/OverviewRoute.d.ts +8 -0
- package/dist/types/client/routes/ProjectsRoute.d.ts +7 -0
- package/dist/types/client/routes/RequestsRoute.d.ts +8 -0
- package/dist/types/client/routes/index.d.ts +7 -0
- package/dist/types/client/ui/AsyncBoundary.d.ts +12 -0
- package/dist/types/client/ui/DataTable.d.ts +17 -0
- package/dist/types/client/ui/EmptyState.d.ts +7 -0
- package/dist/types/client/ui/ErrorState.d.ts +6 -0
- package/dist/types/client/ui/JsonBlock.d.ts +7 -0
- package/dist/types/client/ui/MetricCluster.d.ts +5 -0
- package/dist/types/client/ui/Panel.d.ts +7 -0
- package/dist/types/client/ui/RequestDrawer.d.ts +5 -0
- package/dist/types/client/ui/SegmentedControl.d.ts +12 -0
- package/dist/types/client/ui/Skeleton.d.ts +8 -0
- package/dist/types/client/ui/StatusPill.d.ts +7 -0
- package/dist/types/client/ui/index.d.ts +11 -0
- package/dist/types/client/useSystemTheme.d.ts +9 -0
- package/package.json +4 -4
- package/src/aggregator.ts +4 -3
- package/src/client/App.tsx +89 -207
- package/src/client/api.ts +55 -37
- package/src/client/app/AppLayout.tsx +93 -0
- package/src/client/app/NavRail.tsx +44 -0
- package/src/client/app/RangeControl.tsx +39 -0
- package/src/client/app/SyncButton.tsx +75 -0
- package/src/client/app/ThemeToggle.tsx +37 -0
- package/src/client/app/TopBar.tsx +73 -0
- package/src/client/app/routes.ts +50 -0
- package/src/client/components/chart-shared.tsx +28 -91
- package/src/client/components/models-table-shared.tsx +9 -29
- package/src/client/components/range-meta.ts +3 -2
- package/src/client/data/charts.ts +14 -0
- package/src/client/data/formatters.ts +38 -0
- package/src/client/data/useHashRoute.ts +85 -0
- package/src/client/data/useResource.ts +154 -0
- package/src/client/data/view-models.ts +178 -0
- package/src/client/index.tsx +4 -0
- package/src/client/routes/BehaviorRoute.tsx +623 -0
- package/src/client/routes/CostsRoute.tsx +234 -0
- package/src/client/routes/ErrorsRoute.tsx +118 -0
- package/src/client/routes/ModelsRoute.tsx +430 -0
- package/src/client/routes/OverviewRoute.tsx +332 -0
- package/src/client/routes/ProjectsRoute.tsx +163 -0
- package/src/client/routes/RequestsRoute.tsx +123 -0
- package/src/client/routes/index.ts +7 -0
- package/src/client/styles.css +1242 -225
- package/src/client/ui/AsyncBoundary.tsx +54 -0
- package/src/client/ui/DataTable.tsx +122 -0
- package/src/client/ui/EmptyState.tsx +16 -0
- package/src/client/ui/ErrorState.tsx +25 -0
- package/src/client/ui/JsonBlock.tsx +75 -0
- package/src/client/ui/MetricCluster.tsx +67 -0
- package/src/client/ui/Panel.tsx +24 -0
- package/src/client/ui/RequestDrawer.tsx +208 -0
- package/src/client/ui/SegmentedControl.tsx +36 -0
- package/src/client/ui/Skeleton.tsx +17 -0
- package/src/client/ui/StatusPill.tsx +15 -0
- package/src/client/ui/index.ts +11 -0
- package/src/client/useSystemTheme.ts +73 -17
- package/dist/types/client/components/BehaviorChart.d.ts +0 -6
- package/dist/types/client/components/BehaviorModelsTable.d.ts +0 -7
- package/dist/types/client/components/BehaviorSummary.d.ts +0 -7
- package/dist/types/client/components/ChartsContainer.d.ts +0 -7
- package/dist/types/client/components/CostChart.d.ts +0 -6
- package/dist/types/client/components/CostSummary.d.ts +0 -6
- package/dist/types/client/components/Header.d.ts +0 -12
- package/dist/types/client/components/ModelsTable.d.ts +0 -8
- package/dist/types/client/components/RequestDetail.d.ts +0 -6
- package/dist/types/client/components/RequestList.d.ts +0 -8
- package/dist/types/client/components/StatsGrid.d.ts +0 -6
- package/src/client/components/BehaviorChart.tsx +0 -189
- package/src/client/components/BehaviorModelsTable.tsx +0 -342
- package/src/client/components/BehaviorSummary.tsx +0 -95
- package/src/client/components/ChartsContainer.tsx +0 -221
- package/src/client/components/CostChart.tsx +0 -171
- package/src/client/components/CostSummary.tsx +0 -53
- package/src/client/components/Header.tsx +0 -72
- package/src/client/components/ModelsTable.tsx +0 -265
- package/src/client/components/RequestDetail.tsx +0 -172
- package/src/client/components/RequestList.tsx +0 -73
- package/src/client/components/StatsGrid.tsx +0 -135
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { Clock, Coins, FileJson, Gauge, Hash, Star, X, Zap } from "lucide-react";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
import { getRequestDetails } from "../api";
|
|
4
|
-
import type { RequestDetails } from "../types";
|
|
5
|
-
|
|
6
|
-
interface RequestDetailProps {
|
|
7
|
-
id: number;
|
|
8
|
-
onClose: () => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function RequestDetail({ id, onClose }: RequestDetailProps) {
|
|
12
|
-
const [details, setDetails] = useState<RequestDetails | null>(null);
|
|
13
|
-
const [loading, setLoading] = useState(true);
|
|
14
|
-
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
getRequestDetails(id)
|
|
17
|
-
.then(setDetails)
|
|
18
|
-
.catch(console.error)
|
|
19
|
-
.finally(() => setLoading(false));
|
|
20
|
-
}, [id]);
|
|
21
|
-
|
|
22
|
-
if (!details && loading) {
|
|
23
|
-
return (
|
|
24
|
-
<div className="fixed inset-0 bg-[var(--bg-overlay)] flex justify-center items-center z-[100]">
|
|
25
|
-
<div className="surface px-8 py-6">
|
|
26
|
-
<div className="flex items-center gap-3 text-[var(--text-secondary)]">
|
|
27
|
-
<div className="w-5 h-5 border-2 border-[var(--border-default)] border-t-[var(--accent-cyan)] rounded-full spin" />
|
|
28
|
-
<span>Loading...</span>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!details) return null;
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<div
|
|
39
|
-
role="presentation"
|
|
40
|
-
className="fixed inset-0 bg-[var(--bg-overlay)] backdrop-blur-sm flex justify-end z-[100] animate-fade-in"
|
|
41
|
-
onClick={onClose}
|
|
42
|
-
>
|
|
43
|
-
<div
|
|
44
|
-
role="dialog"
|
|
45
|
-
aria-modal="true"
|
|
46
|
-
className="w-[600px] max-w-full bg-[var(--bg-page)] h-full overflow-y-auto border-l border-[var(--border-subtle)] animate-slide-up"
|
|
47
|
-
onClick={e => e.stopPropagation()}
|
|
48
|
-
>
|
|
49
|
-
{/* Header */}
|
|
50
|
-
<div className="sticky top-0 bg-[var(--bg-page)]/95 backdrop-blur border-b border-[var(--border-subtle)] px-6 py-4 flex justify-between items-center z-10">
|
|
51
|
-
<div className="flex items-center gap-3">
|
|
52
|
-
<div className="w-8 h-8 rounded-[var(--radius-sm)] bg-gradient-to-br from-[var(--accent-pink)]/20 to-[var(--accent-cyan)]/20 flex items-center justify-center">
|
|
53
|
-
<FileJson size={16} className="text-[var(--accent-cyan)]" />
|
|
54
|
-
</div>
|
|
55
|
-
<h2 className="text-lg font-semibold text-[var(--text-primary)]">Request Details</h2>
|
|
56
|
-
</div>
|
|
57
|
-
<button
|
|
58
|
-
type="button"
|
|
59
|
-
onClick={onClose}
|
|
60
|
-
className="p-2 rounded-[var(--radius-sm)] text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-hover)] transition-colors"
|
|
61
|
-
>
|
|
62
|
-
<X size={20} />
|
|
63
|
-
</button>
|
|
64
|
-
</div>
|
|
65
|
-
|
|
66
|
-
<div className="p-6 space-y-6">
|
|
67
|
-
{/* Model Info */}
|
|
68
|
-
<div className="surface p-5">
|
|
69
|
-
<div className="flex items-center justify-between mb-4">
|
|
70
|
-
<div>
|
|
71
|
-
<div className="text-2xl font-bold text-[var(--text-primary)]">{details.model}</div>
|
|
72
|
-
<div className="text-sm text-[var(--text-muted)]">{details.provider}</div>
|
|
73
|
-
</div>
|
|
74
|
-
{details.errorMessage ? (
|
|
75
|
-
<span className="badge badge-error">Error</span>
|
|
76
|
-
) : (
|
|
77
|
-
<span className="badge badge-success">Success</span>
|
|
78
|
-
)}
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
{/* Stats Grid */}
|
|
83
|
-
<div className="grid grid-cols-2 gap-4">
|
|
84
|
-
<div className="surface p-4">
|
|
85
|
-
<div className="flex items-center gap-2 text-[var(--text-muted)] mb-2">
|
|
86
|
-
<Coins size={14} />
|
|
87
|
-
<span className="text-xs uppercase tracking-wide">Cost</span>
|
|
88
|
-
</div>
|
|
89
|
-
<div className="text-xl font-semibold text-[var(--text-primary)]">
|
|
90
|
-
${details.usage.cost.total.toFixed(4)}
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
<div className="surface p-4">
|
|
95
|
-
<div className="flex items-center gap-2 text-[var(--text-muted)] mb-2">
|
|
96
|
-
<Star size={14} />
|
|
97
|
-
<span className="text-xs uppercase tracking-wide">Premium Reqs</span>
|
|
98
|
-
</div>
|
|
99
|
-
<div className="text-xl font-semibold text-[var(--text-primary)]">
|
|
100
|
-
{(details.usage.premiumRequests ?? 0).toLocaleString()}
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
<div className="surface p-4">
|
|
104
|
-
<div className="flex items-center gap-2 text-[var(--text-muted)] mb-2">
|
|
105
|
-
<Hash size={14} />
|
|
106
|
-
<span className="text-xs uppercase tracking-wide">Tokens</span>
|
|
107
|
-
</div>
|
|
108
|
-
<div className="text-xl font-semibold text-[var(--text-primary)]">
|
|
109
|
-
{details.usage.totalTokens.toLocaleString()}
|
|
110
|
-
</div>
|
|
111
|
-
<div className="text-xs text-[var(--text-muted)] mt-1">
|
|
112
|
-
{details.usage.input.toLocaleString()} in · {details.usage.output.toLocaleString()} out
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
|
|
116
|
-
<div className="surface p-4">
|
|
117
|
-
<div className="flex items-center gap-2 text-[var(--text-muted)] mb-2">
|
|
118
|
-
<Clock size={14} />
|
|
119
|
-
<span className="text-xs uppercase tracking-wide">Duration</span>
|
|
120
|
-
</div>
|
|
121
|
-
<div className="text-xl font-semibold text-[var(--text-primary)]">
|
|
122
|
-
{details.duration ? `${(details.duration / 1000).toFixed(2)}s` : "-"}
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
|
|
126
|
-
<div className="surface p-4">
|
|
127
|
-
<div className="flex items-center gap-2 text-[var(--text-muted)] mb-2">
|
|
128
|
-
<Zap size={14} />
|
|
129
|
-
<span className="text-xs uppercase tracking-wide">TTFT</span>
|
|
130
|
-
</div>
|
|
131
|
-
<div className="text-xl font-semibold text-[var(--text-primary)]">
|
|
132
|
-
{details.ttft ? `${(details.ttft / 1000).toFixed(2)}s` : "-"}
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
|
|
137
|
-
{/* Tokens/Sec */}
|
|
138
|
-
{details.duration && details.usage.output > 0 && (
|
|
139
|
-
<div className="surface p-4">
|
|
140
|
-
<div className="flex items-center justify-between">
|
|
141
|
-
<div className="flex items-center gap-2 text-[var(--text-muted)]">
|
|
142
|
-
<Gauge size={14} />
|
|
143
|
-
<span className="text-xs uppercase tracking-wide">Throughput</span>
|
|
144
|
-
</div>
|
|
145
|
-
<span className="text-2xl font-bold gradient-text">
|
|
146
|
-
{((details.usage.output * 1000) / details.duration).toFixed(1)}
|
|
147
|
-
</span>
|
|
148
|
-
</div>
|
|
149
|
-
<div className="text-xs text-[var(--text-muted)] mt-1 text-right">tokens/second</div>
|
|
150
|
-
</div>
|
|
151
|
-
)}
|
|
152
|
-
|
|
153
|
-
{/* Output */}
|
|
154
|
-
<div>
|
|
155
|
-
<h3 className="text-sm font-semibold text-[var(--text-primary)] mb-3">Output</h3>
|
|
156
|
-
<pre className="surface bg-[var(--bg-elevated)] p-4 rounded-[var(--radius-md)] text-sm font-mono text-[var(--text-secondary)] overflow-x-auto">
|
|
157
|
-
{JSON.stringify(details.output, null, 2)}
|
|
158
|
-
</pre>
|
|
159
|
-
</div>
|
|
160
|
-
|
|
161
|
-
{/* Raw Metadata */}
|
|
162
|
-
<div>
|
|
163
|
-
<h3 className="text-sm font-semibold text-[var(--text-primary)] mb-3">Raw Metadata</h3>
|
|
164
|
-
<pre className="surface bg-[var(--bg-elevated)] p-4 rounded-[var(--radius-md)] text-xs font-mono text-[var(--text-muted)] overflow-x-auto">
|
|
165
|
-
{JSON.stringify(details, null, 2)}
|
|
166
|
-
</pre>
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
171
|
-
);
|
|
172
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { formatDistanceToNow } from "date-fns";
|
|
2
|
-
import { CheckCircle2, XCircle } from "lucide-react";
|
|
3
|
-
import type { MessageStats } from "../types";
|
|
4
|
-
|
|
5
|
-
interface RequestListProps {
|
|
6
|
-
requests: MessageStats[];
|
|
7
|
-
onSelect: (req: MessageStats) => void;
|
|
8
|
-
title: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function RequestList({ requests, onSelect, title }: RequestListProps) {
|
|
12
|
-
return (
|
|
13
|
-
<div className="surface overflow-hidden flex flex-col h-full">
|
|
14
|
-
<div className="px-5 py-4 border-b border-[var(--border-subtle)]">
|
|
15
|
-
<h3 className="text-sm font-semibold text-[var(--text-primary)]">{title}</h3>
|
|
16
|
-
</div>
|
|
17
|
-
<div className="overflow-auto flex-1">
|
|
18
|
-
<table className="w-full">
|
|
19
|
-
<thead className="bg-[var(--bg-elevated)] sticky top-0 z-10">
|
|
20
|
-
<tr>
|
|
21
|
-
<th className="text-left py-3 px-4 table-header">Model</th>
|
|
22
|
-
<th className="text-left py-3 px-4 table-header">Time</th>
|
|
23
|
-
<th className="text-right py-3 px-4 table-header">Tokens</th>
|
|
24
|
-
<th className="text-right py-3 px-4 table-header">Cost</th>
|
|
25
|
-
<th className="text-right py-3 px-4 table-header">Duration</th>
|
|
26
|
-
<th className="text-center py-3 px-4 table-header">Status</th>
|
|
27
|
-
</tr>
|
|
28
|
-
</thead>
|
|
29
|
-
<tbody>
|
|
30
|
-
{requests.map(req => (
|
|
31
|
-
<tr
|
|
32
|
-
key={`${req.sessionFile}-${req.entryId}`}
|
|
33
|
-
onClick={() => onSelect(req)}
|
|
34
|
-
className="table-row cursor-pointer border-b border-[var(--border-subtle)] last:border-b-0"
|
|
35
|
-
>
|
|
36
|
-
<td className="py-3 px-4">
|
|
37
|
-
<div className="font-medium text-[var(--text-primary)] text-sm">{req.model}</div>
|
|
38
|
-
<div className="text-xs text-[var(--text-muted)]">{req.provider}</div>
|
|
39
|
-
</td>
|
|
40
|
-
<td className="py-3 px-4 text-sm text-[var(--text-secondary)]">
|
|
41
|
-
{formatDistanceToNow(req.timestamp, { addSuffix: true })}
|
|
42
|
-
</td>
|
|
43
|
-
<td className="py-3 px-4 text-right text-sm text-[var(--text-secondary)] font-mono">
|
|
44
|
-
{req.usage.totalTokens.toLocaleString()}
|
|
45
|
-
</td>
|
|
46
|
-
<td className="py-3 px-4 text-right text-sm text-[var(--text-secondary)] font-mono">
|
|
47
|
-
${req.usage.cost.total.toFixed(4)}
|
|
48
|
-
</td>
|
|
49
|
-
<td className="py-3 px-4 text-right text-sm text-[var(--text-secondary)] font-mono">
|
|
50
|
-
{req.duration ? `${(req.duration / 1000).toFixed(1)}s` : "-"}
|
|
51
|
-
</td>
|
|
52
|
-
<td className="py-3 px-4 text-center">
|
|
53
|
-
{req.errorMessage ? (
|
|
54
|
-
<XCircle size={16} className="text-[var(--accent-red)] mx-auto" />
|
|
55
|
-
) : (
|
|
56
|
-
<CheckCircle2 size={16} className="text-[var(--accent-green)] mx-auto" />
|
|
57
|
-
)}
|
|
58
|
-
</td>
|
|
59
|
-
</tr>
|
|
60
|
-
))}
|
|
61
|
-
{requests.length === 0 && (
|
|
62
|
-
<tr>
|
|
63
|
-
<td colSpan={6} className="py-12 text-center text-[var(--text-muted)] text-sm">
|
|
64
|
-
No requests found
|
|
65
|
-
</td>
|
|
66
|
-
</tr>
|
|
67
|
-
)}
|
|
68
|
-
</tbody>
|
|
69
|
-
</table>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { Activity, AlertCircle, BarChart3, Database, Download, Server, Star, Upload, Zap } from "lucide-react";
|
|
2
|
-
import type { AggregatedStats } from "../types";
|
|
3
|
-
|
|
4
|
-
interface StatsGridProps {
|
|
5
|
-
stats: AggregatedStats;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const compactNumberFormatter = new Intl.NumberFormat(undefined, {
|
|
9
|
-
notation: "compact",
|
|
10
|
-
maximumFractionDigits: 1,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
function formatCompactNumber(value: number): string {
|
|
14
|
-
return compactNumberFormatter.format(value);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function formatExactNumber(value: number): string {
|
|
18
|
-
return value.toLocaleString();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const totalPromptCompletionTokens = (stats: AggregatedStats) => stats.totalInputTokens + stats.totalOutputTokens;
|
|
22
|
-
|
|
23
|
-
const statConfig = [
|
|
24
|
-
{
|
|
25
|
-
key: "requests",
|
|
26
|
-
title: "Total Requests",
|
|
27
|
-
icon: Server,
|
|
28
|
-
color: "var(--accent-violet)",
|
|
29
|
-
getValue: (s: AggregatedStats) => s.totalRequests.toLocaleString(),
|
|
30
|
-
getDetail: (s: AggregatedStats) =>
|
|
31
|
-
`${s.successfulRequests.toLocaleString()} success · ${s.failedRequests.toLocaleString()} errors`,
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
key: "cost",
|
|
35
|
-
title: "Total Cost",
|
|
36
|
-
icon: Activity,
|
|
37
|
-
color: "var(--accent-pink)",
|
|
38
|
-
getValue: (s: AggregatedStats) => `$${s.totalCost.toFixed(2)}`,
|
|
39
|
-
getDetail: (s: AggregatedStats) =>
|
|
40
|
-
s.totalRequests > 0 ? `$${(s.totalCost / s.totalRequests).toFixed(4)} avg/req` : "-",
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
key: "premiumRequests",
|
|
44
|
-
title: "Premium Reqs",
|
|
45
|
-
icon: Star,
|
|
46
|
-
color: "var(--accent-amber)",
|
|
47
|
-
getValue: (s: AggregatedStats) => formatExactNumber(s.totalPremiumRequests),
|
|
48
|
-
getDetail: (s: AggregatedStats) =>
|
|
49
|
-
s.totalRequests > 0 ? `${((s.totalPremiumRequests / s.totalRequests) * 100).toFixed(1)}% of requests` : "-",
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
key: "cache",
|
|
53
|
-
title: "Cache Rate",
|
|
54
|
-
icon: Database,
|
|
55
|
-
color: "var(--accent-cyan)",
|
|
56
|
-
getValue: (s: AggregatedStats) => `${(s.cacheRate * 100).toFixed(1)}%`,
|
|
57
|
-
getDetail: (s: AggregatedStats) => `${formatCompactNumber(s.totalCacheReadTokens)} cached tokens`,
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
key: "inputTokens",
|
|
61
|
-
title: "Input Tokens",
|
|
62
|
-
icon: Download,
|
|
63
|
-
color: "var(--accent-violet)",
|
|
64
|
-
getValue: (s: AggregatedStats) => formatExactNumber(s.totalInputTokens),
|
|
65
|
-
getDetail: (s: AggregatedStats) =>
|
|
66
|
-
totalPromptCompletionTokens(s) > 0
|
|
67
|
-
? `${((s.totalInputTokens / totalPromptCompletionTokens(s)) * 100).toFixed(1)}% of prompt+completion`
|
|
68
|
-
: "-",
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
key: "outputTokens",
|
|
72
|
-
title: "Output Tokens",
|
|
73
|
-
icon: Upload,
|
|
74
|
-
color: "var(--accent-pink)",
|
|
75
|
-
getValue: (s: AggregatedStats) => formatExactNumber(s.totalOutputTokens),
|
|
76
|
-
getDetail: (s: AggregatedStats) =>
|
|
77
|
-
totalPromptCompletionTokens(s) > 0
|
|
78
|
-
? `${((s.totalOutputTokens / totalPromptCompletionTokens(s)) * 100).toFixed(1)}% of prompt+completion`
|
|
79
|
-
: "-",
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
key: "errors",
|
|
83
|
-
title: "Error Rate",
|
|
84
|
-
icon: AlertCircle,
|
|
85
|
-
color: "var(--accent-red)",
|
|
86
|
-
getValue: (s: AggregatedStats) => `${(s.errorRate * 100).toFixed(1)}%`,
|
|
87
|
-
getDetail: (s: AggregatedStats) => `${s.failedRequests.toLocaleString()} failed requests`,
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
key: "tokens",
|
|
91
|
-
title: "Tokens/Sec",
|
|
92
|
-
icon: BarChart3,
|
|
93
|
-
color: "var(--accent-green)",
|
|
94
|
-
getValue: (s: AggregatedStats) => s.avgTokensPerSecond?.toFixed(1) ?? "-",
|
|
95
|
-
getDetail: (s: AggregatedStats) =>
|
|
96
|
-
`${formatCompactNumber(totalPromptCompletionTokens(s))} total prompt+completion`,
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
key: "ttft",
|
|
100
|
-
title: "TTFT",
|
|
101
|
-
icon: Zap,
|
|
102
|
-
color: "var(--accent-amber)",
|
|
103
|
-
getValue: (s: AggregatedStats) => (s.avgTtft ? `${(s.avgTtft / 1000).toFixed(2)}s` : "-"),
|
|
104
|
-
getDetail: () => "Time to first token",
|
|
105
|
-
},
|
|
106
|
-
];
|
|
107
|
-
|
|
108
|
-
export function StatsGrid({ stats }: StatsGridProps) {
|
|
109
|
-
return (
|
|
110
|
-
<div className="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-9 gap-4 mb-8">
|
|
111
|
-
{statConfig.map(stat => {
|
|
112
|
-
const Icon = stat.icon;
|
|
113
|
-
return (
|
|
114
|
-
<div key={stat.key} className="stat-card group">
|
|
115
|
-
<div className="flex items-center justify-between mb-3">
|
|
116
|
-
<span className="text-sm font-medium text-[var(--text-secondary)]">{stat.title}</span>
|
|
117
|
-
<div
|
|
118
|
-
className="p-2 rounded-[var(--radius-sm)] transition-colors"
|
|
119
|
-
style={{ backgroundColor: `${stat.color}15` }}
|
|
120
|
-
>
|
|
121
|
-
<Icon
|
|
122
|
-
size={18}
|
|
123
|
-
style={{ color: stat.color }}
|
|
124
|
-
className="transition-transform group-hover:scale-110"
|
|
125
|
-
/>
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
<div className="text-2xl font-bold text-[var(--text-primary)] mb-1">{stat.getValue(stats)}</div>
|
|
129
|
-
<div className="text-xs text-[var(--text-muted)] truncate">{stat.getDetail(stats)}</div>
|
|
130
|
-
</div>
|
|
131
|
-
);
|
|
132
|
-
})}
|
|
133
|
-
</div>
|
|
134
|
-
);
|
|
135
|
-
}
|