@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.3.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.3.13",
37
- "@oh-my-pi/pi-utils": "13.3.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: "#94a3b8",
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: "#16161e",
72
- titleColor: "#f8fafc",
73
- bodyColor: "#94a3b8",
74
- borderColor: "rgba(255, 255, 255, 0.1)",
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: "rgba(255, 255, 255, 0.06)",
112
+ color: chartTheme.grid,
91
113
  drawBorder: false,
92
114
  },
93
115
  ticks: {
94
- color: "#64748b",
116
+ color: chartTheme.tick,
95
117
  font: { size: 11 },
96
118
  },
97
119
  },
98
120
  y: {
99
121
  grid: {
100
- color: "rgba(255, 255, 255, 0.06)",
122
+ color: chartTheme.grid,
101
123
  drawBorder: false,
102
124
  },
103
125
  ticks: {
104
- color: "#64748b",
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: "#cbd5e1",
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: "#16161e",
274
- titleColor: "#f8fafc",
275
- bodyColor: "#94a3b8",
276
- borderColor: "rgba(255, 255, 255, 0.1)",
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: "rgba(255, 255, 255, 0.06)" },
284
- ticks: { color: "#94a3b8", font: { size: 11 } },
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: "rgba(255, 255, 255, 0.06)" },
291
- ticks: { color: "#94a3b8", font: { size: 11 } },
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: "#94a3b8", font: { size: 11 } },
325
+ ticks: { color: chartTheme.tick, font: { size: 11 } },
299
326
  },
300
327
  },
301
328
  };
@@ -1,32 +1,64 @@
1
1
  @import "tailwindcss/index.css";
2
2
  :root {
3
- --bg-page: #0a0a0f;
4
- --bg-surface: #111118;
5
- --bg-elevated: #16161e;
6
- --bg-hover: rgba(255, 255, 255, 0.03);
7
- --bg-active: rgba(255, 255, 255, 0.06);
8
-
9
- --border-subtle: rgba(255, 255, 255, 0.06);
10
- --border-default: rgba(255, 255, 255, 0.1);
11
-
12
- --text-primary: #f8fafc;
13
- --text-secondary: #94a3b8;
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.3);
18
- --accent-cyan: #22d3ee;
19
- --accent-cyan-glow: rgba(34, 211, 238, 0.3);
20
- --accent-violet: #a78bfa;
21
- --accent-green: #4ade80;
22
- --accent-amber: #fbbf24;
23
- --accent-red: #f87171;
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: inset 0 1px 0 rgba(255, 255, 255, 0.05);
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
+ }