@oh-my-pi/omp-stats 13.3.13 → 13.4.0
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/omp-stats",
|
|
4
|
-
"version": "13.
|
|
4
|
+
"version": "13.4.0",
|
|
5
5
|
"description": "Local observability dashboard for pi AI usage statistics",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"build": "bun run build.ts"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@oh-my-pi/pi-ai": "13.
|
|
37
|
-
"@oh-my-pi/pi-utils": "13.
|
|
36
|
+
"@oh-my-pi/pi-ai": "13.4.0",
|
|
37
|
+
"@oh-my-pi/pi-utils": "13.4.0",
|
|
38
38
|
"@tailwindcss/node": "4",
|
|
39
39
|
"chart.js": "^4.5",
|
|
40
40
|
"date-fns": "^4.1",
|
|
@@ -13,6 +13,7 @@ import { format } from "date-fns";
|
|
|
13
13
|
import { useMemo } from "react";
|
|
14
14
|
import { Line } from "react-chartjs-2";
|
|
15
15
|
import type { ModelTimeSeriesPoint } from "../types";
|
|
16
|
+
import { useSystemTheme } from "../useSystemTheme";
|
|
16
17
|
|
|
17
18
|
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, Filler);
|
|
18
19
|
|
|
@@ -26,13 +27,34 @@ const MODEL_COLORS = [
|
|
|
26
27
|
"#60a5fa", // blue
|
|
27
28
|
];
|
|
28
29
|
|
|
30
|
+
const CHART_THEMES = {
|
|
31
|
+
dark: {
|
|
32
|
+
legendLabel: "#94a3b8",
|
|
33
|
+
tooltipBackground: "#16161e",
|
|
34
|
+
tooltipTitle: "#f8fafc",
|
|
35
|
+
tooltipBody: "#94a3b8",
|
|
36
|
+
tooltipBorder: "rgba(255, 255, 255, 0.1)",
|
|
37
|
+
grid: "rgba(255, 255, 255, 0.06)",
|
|
38
|
+
tick: "#64748b",
|
|
39
|
+
},
|
|
40
|
+
light: {
|
|
41
|
+
legendLabel: "#475569",
|
|
42
|
+
tooltipBackground: "#ffffff",
|
|
43
|
+
tooltipTitle: "#0f172a",
|
|
44
|
+
tooltipBody: "#334155",
|
|
45
|
+
tooltipBorder: "rgba(15, 23, 42, 0.18)",
|
|
46
|
+
grid: "rgba(15, 23, 42, 0.08)",
|
|
47
|
+
tick: "#64748b",
|
|
48
|
+
},
|
|
49
|
+
} as const;
|
|
29
50
|
interface ChartsContainerProps {
|
|
30
51
|
modelSeries: ModelTimeSeriesPoint[];
|
|
31
52
|
}
|
|
32
53
|
|
|
33
54
|
export function ChartsContainer({ modelSeries }: ChartsContainerProps) {
|
|
34
55
|
const chartData = useMemo(() => buildModelPreferenceSeries(modelSeries), [modelSeries]);
|
|
35
|
-
|
|
56
|
+
const theme = useSystemTheme();
|
|
57
|
+
const chartTheme = CHART_THEMES[theme];
|
|
36
58
|
const data = {
|
|
37
59
|
labels: chartData.data.map(d => format(new Date(d.timestamp), "MMM d")),
|
|
38
60
|
datasets: chartData.series.map((seriesName, index) => ({
|
|
@@ -60,7 +82,7 @@ export function ChartsContainer({ modelSeries }: ChartsContainerProps) {
|
|
|
60
82
|
position: "top" as const,
|
|
61
83
|
align: "start" as const,
|
|
62
84
|
labels: {
|
|
63
|
-
color:
|
|
85
|
+
color: chartTheme.legendLabel,
|
|
64
86
|
usePointStyle: true,
|
|
65
87
|
padding: 16,
|
|
66
88
|
font: { size: 12 },
|
|
@@ -68,10 +90,10 @@ export function ChartsContainer({ modelSeries }: ChartsContainerProps) {
|
|
|
68
90
|
},
|
|
69
91
|
},
|
|
70
92
|
tooltip: {
|
|
71
|
-
backgroundColor:
|
|
72
|
-
titleColor:
|
|
73
|
-
bodyColor:
|
|
74
|
-
borderColor:
|
|
93
|
+
backgroundColor: chartTheme.tooltipBackground,
|
|
94
|
+
titleColor: chartTheme.tooltipTitle,
|
|
95
|
+
bodyColor: chartTheme.tooltipBody,
|
|
96
|
+
borderColor: chartTheme.tooltipBorder,
|
|
75
97
|
borderWidth: 1,
|
|
76
98
|
padding: 12,
|
|
77
99
|
cornerRadius: 8,
|
|
@@ -87,21 +109,21 @@ export function ChartsContainer({ modelSeries }: ChartsContainerProps) {
|
|
|
87
109
|
scales: {
|
|
88
110
|
x: {
|
|
89
111
|
grid: {
|
|
90
|
-
color:
|
|
112
|
+
color: chartTheme.grid,
|
|
91
113
|
drawBorder: false,
|
|
92
114
|
},
|
|
93
115
|
ticks: {
|
|
94
|
-
color:
|
|
116
|
+
color: chartTheme.tick,
|
|
95
117
|
font: { size: 11 },
|
|
96
118
|
},
|
|
97
119
|
},
|
|
98
120
|
y: {
|
|
99
121
|
grid: {
|
|
100
|
-
color:
|
|
122
|
+
color: chartTheme.grid,
|
|
101
123
|
drawBorder: false,
|
|
102
124
|
},
|
|
103
125
|
ticks: {
|
|
104
|
-
color:
|
|
126
|
+
color: chartTheme.tick,
|
|
105
127
|
font: { size: 11 },
|
|
106
128
|
callback: (value: number | string) => `${value}%`,
|
|
107
129
|
},
|
|
@@ -13,6 +13,7 @@ import { ChevronDown, ChevronUp } from "lucide-react";
|
|
|
13
13
|
import { useMemo, useState } from "react";
|
|
14
14
|
import { Line } from "react-chartjs-2";
|
|
15
15
|
import type { ModelPerformancePoint, ModelStats } from "../types";
|
|
16
|
+
import { useSystemTheme } from "../useSystemTheme";
|
|
16
17
|
|
|
17
18
|
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
|
|
18
19
|
|
|
@@ -26,6 +27,28 @@ const MODEL_COLORS = [
|
|
|
26
27
|
"#60a5fa", // blue
|
|
27
28
|
];
|
|
28
29
|
|
|
30
|
+
const CHART_THEMES = {
|
|
31
|
+
dark: {
|
|
32
|
+
legendLabel: "#cbd5e1",
|
|
33
|
+
tooltipBackground: "#16161e",
|
|
34
|
+
tooltipTitle: "#f8fafc",
|
|
35
|
+
tooltipBody: "#94a3b8",
|
|
36
|
+
tooltipBorder: "rgba(255, 255, 255, 0.1)",
|
|
37
|
+
grid: "rgba(255, 255, 255, 0.06)",
|
|
38
|
+
tick: "#94a3b8",
|
|
39
|
+
},
|
|
40
|
+
light: {
|
|
41
|
+
legendLabel: "#334155",
|
|
42
|
+
tooltipBackground: "#ffffff",
|
|
43
|
+
tooltipTitle: "#0f172a",
|
|
44
|
+
tooltipBody: "#334155",
|
|
45
|
+
tooltipBorder: "rgba(15, 23, 42, 0.18)",
|
|
46
|
+
grid: "rgba(15, 23, 42, 0.08)",
|
|
47
|
+
tick: "#475569",
|
|
48
|
+
},
|
|
49
|
+
} as const;
|
|
50
|
+
|
|
51
|
+
type ChartTheme = (typeof CHART_THEMES)[keyof typeof CHART_THEMES];
|
|
29
52
|
interface ModelsTableProps {
|
|
30
53
|
models: ModelStats[];
|
|
31
54
|
performanceSeries: ModelPerformancePoint[];
|
|
@@ -45,6 +68,8 @@ export function ModelsTable({ models, performanceSeries }: ModelsTableProps) {
|
|
|
45
68
|
const [expandedKey, setExpandedKey] = useState<string | null>(null);
|
|
46
69
|
|
|
47
70
|
const performanceSeriesByKey = useMemo(() => buildModelPerformanceLookup(performanceSeries), [performanceSeries]);
|
|
71
|
+
const theme = useSystemTheme();
|
|
72
|
+
const chartTheme = CHART_THEMES[theme];
|
|
48
73
|
const sortedModels = [...models].sort(
|
|
49
74
|
(a, b) => b.totalInputTokens + b.totalOutputTokens - (a.totalInputTokens + a.totalOutputTokens),
|
|
50
75
|
);
|
|
@@ -173,7 +198,7 @@ export function ModelsTable({ models, performanceSeries }: ModelsTableProps) {
|
|
|
173
198
|
No data available
|
|
174
199
|
</div>
|
|
175
200
|
) : (
|
|
176
|
-
<PerformanceChart data={trendData} color={trendColor} />
|
|
201
|
+
<PerformanceChart data={trendData} color={trendColor} chartTheme={chartTheme} />
|
|
177
202
|
)}
|
|
178
203
|
</div>
|
|
179
204
|
</div>
|
|
@@ -225,9 +250,11 @@ function TrendChart({
|
|
|
225
250
|
function PerformanceChart({
|
|
226
251
|
data,
|
|
227
252
|
color,
|
|
253
|
+
chartTheme,
|
|
228
254
|
}: {
|
|
229
255
|
data: Array<{ timestamp: number; avgTtftSeconds: number | null; avgTokensPerSecond: number | null }>;
|
|
230
256
|
color: string;
|
|
257
|
+
chartTheme: ChartTheme;
|
|
231
258
|
}) {
|
|
232
259
|
const chartData = {
|
|
233
260
|
labels: data.map(d => format(new Date(d.timestamp), "MMM d")),
|
|
@@ -263,39 +290,39 @@ function PerformanceChart({
|
|
|
263
290
|
display: true,
|
|
264
291
|
position: "top" as const,
|
|
265
292
|
labels: {
|
|
266
|
-
color:
|
|
293
|
+
color: chartTheme.legendLabel,
|
|
267
294
|
usePointStyle: true,
|
|
268
295
|
padding: 16,
|
|
269
296
|
font: { size: 12 },
|
|
270
297
|
},
|
|
271
298
|
},
|
|
272
299
|
tooltip: {
|
|
273
|
-
backgroundColor:
|
|
274
|
-
titleColor:
|
|
275
|
-
bodyColor:
|
|
276
|
-
borderColor:
|
|
300
|
+
backgroundColor: chartTheme.tooltipBackground,
|
|
301
|
+
titleColor: chartTheme.tooltipTitle,
|
|
302
|
+
bodyColor: chartTheme.tooltipBody,
|
|
303
|
+
borderColor: chartTheme.tooltipBorder,
|
|
277
304
|
borderWidth: 1,
|
|
278
305
|
cornerRadius: 8,
|
|
279
306
|
},
|
|
280
307
|
},
|
|
281
308
|
scales: {
|
|
282
309
|
x: {
|
|
283
|
-
grid: { color:
|
|
284
|
-
ticks: { color:
|
|
310
|
+
grid: { color: chartTheme.grid },
|
|
311
|
+
ticks: { color: chartTheme.tick, font: { size: 11 } },
|
|
285
312
|
},
|
|
286
313
|
y: {
|
|
287
314
|
type: "linear" as const,
|
|
288
315
|
display: true,
|
|
289
316
|
position: "left" as const,
|
|
290
|
-
grid: { color:
|
|
291
|
-
ticks: { color:
|
|
317
|
+
grid: { color: chartTheme.grid },
|
|
318
|
+
ticks: { color: chartTheme.tick, font: { size: 11 } },
|
|
292
319
|
},
|
|
293
320
|
y1: {
|
|
294
321
|
type: "linear" as const,
|
|
295
322
|
display: true,
|
|
296
323
|
position: "right" as const,
|
|
297
324
|
grid: { drawOnChartArea: false },
|
|
298
|
-
ticks: { color:
|
|
325
|
+
ticks: { color: chartTheme.tick, font: { size: 11 } },
|
|
299
326
|
},
|
|
300
327
|
},
|
|
301
328
|
};
|
package/src/client/styles.css
CHANGED
|
@@ -1,32 +1,64 @@
|
|
|
1
1
|
@import "tailwindcss/index.css";
|
|
2
2
|
:root {
|
|
3
|
-
|
|
4
|
-
--bg-
|
|
5
|
-
--bg-
|
|
6
|
-
--bg-
|
|
7
|
-
--bg-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
--border-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
--text-
|
|
3
|
+
color-scheme: light;
|
|
4
|
+
--bg-page: #f8fafc;
|
|
5
|
+
--bg-surface: #ffffff;
|
|
6
|
+
--bg-elevated: #f1f5f9;
|
|
7
|
+
--bg-hover: rgba(15, 23, 42, 0.04);
|
|
8
|
+
--bg-active: rgba(15, 23, 42, 0.08);
|
|
9
|
+
|
|
10
|
+
--border-subtle: rgba(15, 23, 42, 0.08);
|
|
11
|
+
--border-default: rgba(15, 23, 42, 0.14);
|
|
12
|
+
|
|
13
|
+
--text-primary: #0f172a;
|
|
14
|
+
--text-secondary: #334155;
|
|
14
15
|
--text-muted: #64748b;
|
|
15
16
|
|
|
16
17
|
--accent-pink: #ec4899;
|
|
17
|
-
--accent-pink-glow: rgba(236, 72, 153, 0.
|
|
18
|
-
--accent-cyan: #
|
|
19
|
-
--accent-cyan-glow: rgba(
|
|
20
|
-
--accent-violet: #
|
|
21
|
-
--accent-green: #
|
|
22
|
-
--accent-amber: #
|
|
23
|
-
--accent-red: #
|
|
18
|
+
--accent-pink-glow: rgba(236, 72, 153, 0.26);
|
|
19
|
+
--accent-cyan: #0891b2;
|
|
20
|
+
--accent-cyan-glow: rgba(8, 145, 178, 0.24);
|
|
21
|
+
--accent-violet: #8b5cf6;
|
|
22
|
+
--accent-green: #16a34a;
|
|
23
|
+
--accent-amber: #d97706;
|
|
24
|
+
--accent-red: #dc2626;
|
|
25
|
+
|
|
26
|
+
--tab-active-highlight: inset 0 1px 0 rgba(15, 23, 42, 0.08);
|
|
24
27
|
|
|
25
28
|
--radius-sm: 6px;
|
|
26
29
|
--radius-md: 10px;
|
|
27
30
|
--radius-lg: 14px;
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
@media (prefers-color-scheme: dark) {
|
|
34
|
+
:root {
|
|
35
|
+
color-scheme: dark;
|
|
36
|
+
--bg-page: #0a0a0f;
|
|
37
|
+
--bg-surface: #111118;
|
|
38
|
+
--bg-elevated: #16161e;
|
|
39
|
+
--bg-hover: rgba(255, 255, 255, 0.03);
|
|
40
|
+
--bg-active: rgba(255, 255, 255, 0.06);
|
|
41
|
+
|
|
42
|
+
--border-subtle: rgba(255, 255, 255, 0.06);
|
|
43
|
+
--border-default: rgba(255, 255, 255, 0.1);
|
|
44
|
+
|
|
45
|
+
--text-primary: #f8fafc;
|
|
46
|
+
--text-secondary: #94a3b8;
|
|
47
|
+
--text-muted: #64748b;
|
|
48
|
+
|
|
49
|
+
--accent-pink: #ec4899;
|
|
50
|
+
--accent-pink-glow: rgba(236, 72, 153, 0.3);
|
|
51
|
+
--accent-cyan: #22d3ee;
|
|
52
|
+
--accent-cyan-glow: rgba(34, 211, 238, 0.3);
|
|
53
|
+
--accent-violet: #a78bfa;
|
|
54
|
+
--accent-green: #4ade80;
|
|
55
|
+
--accent-amber: #fbbf24;
|
|
56
|
+
--accent-red: #f87171;
|
|
57
|
+
|
|
58
|
+
--tab-active-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
30
62
|
@layer base {
|
|
31
63
|
* {
|
|
32
64
|
box-sizing: border-box;
|
|
@@ -140,7 +172,7 @@
|
|
|
140
172
|
.tab-btn.active {
|
|
141
173
|
color: var(--text-primary);
|
|
142
174
|
background: var(--bg-elevated);
|
|
143
|
-
box-shadow:
|
|
175
|
+
box-shadow: var(--tab-active-highlight);
|
|
144
176
|
}
|
|
145
177
|
|
|
146
178
|
.stat-card {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export type SystemTheme = "light" | "dark";
|
|
4
|
+
|
|
5
|
+
const DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
|
|
6
|
+
|
|
7
|
+
function getSystemTheme(): SystemTheme {
|
|
8
|
+
if (typeof window === "undefined") {
|
|
9
|
+
return "light";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return window.matchMedia(DARK_SCHEME_QUERY).matches ? "dark" : "light";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useSystemTheme(): SystemTheme {
|
|
16
|
+
const [theme, setTheme] = useState<SystemTheme>(() => getSystemTheme());
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const media = window.matchMedia(DARK_SCHEME_QUERY);
|
|
20
|
+
const applyTheme = () => {
|
|
21
|
+
setTheme(media.matches ? "dark" : "light");
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
applyTheme();
|
|
25
|
+
|
|
26
|
+
media.addEventListener("change", applyTheme);
|
|
27
|
+
return () => media.removeEventListener("change", applyTheme);
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
return theme;
|
|
31
|
+
}
|