@oh-my-pi/omp-stats 18.0.8 → 18.0.9
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.
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* behavior series.
|
|
6
6
|
*/
|
|
7
7
|
export declare const MODEL_COLORS: string[];
|
|
8
|
+
export declare function buildModelColorLookup(records: readonly {
|
|
9
|
+
model: string;
|
|
10
|
+
provider: string;
|
|
11
|
+
totalRequests: number;
|
|
12
|
+
}[]): Map<string, string>;
|
|
8
13
|
export declare const CHART_THEMES: {
|
|
9
14
|
readonly dark: {
|
|
10
15
|
readonly legendLabel: "#a89fb3";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/omp-stats",
|
|
4
|
-
"version": "18.0.
|
|
4
|
+
"version": "18.0.9",
|
|
5
5
|
"description": "Local observability dashboard for pi AI usage statistics",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Stencil Labs, Inc.",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"fmt": "biome format --write ."
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@oh-my-pi/pi-ai": "18.0.
|
|
43
|
-
"@oh-my-pi/pi-catalog": "18.0.
|
|
44
|
-
"@oh-my-pi/pi-utils": "18.0.
|
|
42
|
+
"@oh-my-pi/pi-ai": "18.0.9",
|
|
43
|
+
"@oh-my-pi/pi-catalog": "18.0.9",
|
|
44
|
+
"@oh-my-pi/pi-utils": "18.0.9",
|
|
45
45
|
"@tailwindcss/node": "^4.3.2",
|
|
46
46
|
"chart.js": "^4.5.1",
|
|
47
47
|
"lucide-react": "^1.24.0",
|
|
@@ -20,6 +20,22 @@ export const MODEL_COLORS = [
|
|
|
20
20
|
"#ff6b7d", // rose
|
|
21
21
|
];
|
|
22
22
|
|
|
23
|
+
export function buildModelColorLookup(
|
|
24
|
+
records: readonly { model: string; provider: string; totalRequests: number }[],
|
|
25
|
+
): Map<string, string> {
|
|
26
|
+
const rankedRecords = [...records].sort(
|
|
27
|
+
(a, b) =>
|
|
28
|
+
b.totalRequests - a.totalRequests || `${a.model}::${a.provider}`.localeCompare(`${b.model}::${b.provider}`),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return new Map(
|
|
32
|
+
rankedRecords.map((record, index) => [
|
|
33
|
+
`${record.model}::${record.provider}`,
|
|
34
|
+
MODEL_COLORS[index % MODEL_COLORS.length],
|
|
35
|
+
]),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
export const CHART_THEMES = {
|
|
24
40
|
dark: {
|
|
25
41
|
legendLabel: "#a89fb3",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useMemo, useState } from "react";
|
|
2
2
|
import { Line } from "react-chartjs-2";
|
|
3
3
|
import { getModelDashboardStats } from "../api";
|
|
4
|
-
import { CHART_THEMES, MODEL_COLORS } from "../components/chart-shared";
|
|
4
|
+
import { buildModelColorLookup, CHART_THEMES, MODEL_COLORS } from "../components/chart-shared";
|
|
5
5
|
import {
|
|
6
6
|
DetailChartEmpty,
|
|
7
7
|
detailChartPlugins,
|
|
@@ -40,17 +40,23 @@ export function ModelsRoute({ active, range, refreshTrigger }: ModelsRouteProps)
|
|
|
40
40
|
pollMs: 30000,
|
|
41
41
|
enabled: active,
|
|
42
42
|
});
|
|
43
|
+
const modelColorLookup = useMemo(() => buildModelColorLookup(modelStats?.byModel ?? []), [modelStats?.byModel]);
|
|
43
44
|
|
|
44
45
|
return (
|
|
45
46
|
<div className="stats-route-container space-y-6">
|
|
46
47
|
<AsyncBoundary loading={loading} error={error} data={modelStats}>
|
|
47
48
|
{modelStats && (
|
|
48
49
|
<>
|
|
49
|
-
<ModelShareChart
|
|
50
|
+
<ModelShareChart
|
|
51
|
+
modelSeries={modelStats.modelSeries}
|
|
52
|
+
timeRange={range}
|
|
53
|
+
colorLookup={modelColorLookup}
|
|
54
|
+
/>
|
|
50
55
|
<ModelsTable
|
|
51
56
|
models={modelStats.byModel}
|
|
52
57
|
performanceSeries={modelStats.modelPerformanceSeries}
|
|
53
58
|
timeRange={range}
|
|
59
|
+
colorLookup={modelColorLookup}
|
|
54
60
|
/>
|
|
55
61
|
</>
|
|
56
62
|
)}
|
|
@@ -59,7 +65,15 @@ export function ModelsRoute({ active, range, refreshTrigger }: ModelsRouteProps)
|
|
|
59
65
|
);
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
function ModelShareChart({
|
|
68
|
+
function ModelShareChart({
|
|
69
|
+
modelSeries,
|
|
70
|
+
timeRange,
|
|
71
|
+
colorLookup,
|
|
72
|
+
}: {
|
|
73
|
+
modelSeries: ModelTimeSeriesPoint[];
|
|
74
|
+
timeRange: TimeRange;
|
|
75
|
+
colorLookup: ReadonlyMap<string, string>;
|
|
76
|
+
}) {
|
|
63
77
|
const theme = useSystemTheme();
|
|
64
78
|
const chartTheme = CHART_THEMES[theme];
|
|
65
79
|
const meta = rangeMeta(timeRange);
|
|
@@ -69,19 +83,25 @@ function ModelShareChart({ modelSeries, timeRange }: { modelSeries: ModelTimeSer
|
|
|
69
83
|
const data = useMemo(() => {
|
|
70
84
|
return {
|
|
71
85
|
labels: chartData.data.map(d => formatRangeTick(d.timestamp, timeRange)),
|
|
72
|
-
datasets: chartData.series.map((
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
datasets: chartData.series.map((series, index) => {
|
|
87
|
+
const fallbackColor = MODEL_COLORS[index % MODEL_COLORS.length];
|
|
88
|
+
const color = series.key ? (colorLookup.get(series.key) ?? fallbackColor) : fallbackColor;
|
|
89
|
+
const dataKey = series.key ?? series.label;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
label: series.label,
|
|
93
|
+
data: chartData.data.map(d => d[dataKey] ?? 0),
|
|
94
|
+
borderColor: color,
|
|
95
|
+
backgroundColor: `${color}20`,
|
|
96
|
+
fill: true,
|
|
97
|
+
tension: 0.4,
|
|
98
|
+
pointRadius: 0,
|
|
99
|
+
pointHoverRadius: 4,
|
|
100
|
+
borderWidth: 2,
|
|
101
|
+
};
|
|
102
|
+
}),
|
|
83
103
|
};
|
|
84
|
-
}, [chartData, timeRange]);
|
|
104
|
+
}, [chartData, colorLookup, timeRange]);
|
|
85
105
|
|
|
86
106
|
const options = useMemo(() => {
|
|
87
107
|
return {
|
|
@@ -166,7 +186,7 @@ function buildModelPreferenceSeries(
|
|
|
166
186
|
topN = 5,
|
|
167
187
|
): {
|
|
168
188
|
data: Array<Record<string, number>>;
|
|
169
|
-
series: string
|
|
189
|
+
series: Array<{ key?: string; label: string }>;
|
|
170
190
|
} {
|
|
171
191
|
if (points.length === 0) return { data: [], series: [] };
|
|
172
192
|
|
|
@@ -209,22 +229,26 @@ function buildModelPreferenceSeries(
|
|
|
209
229
|
total: 0,
|
|
210
230
|
};
|
|
211
231
|
bucket.total += point.requests;
|
|
212
|
-
const
|
|
213
|
-
bucket[
|
|
232
|
+
const seriesKey = topKeys.has(key) ? key : "Other";
|
|
233
|
+
bucket[seriesKey] = (bucket[seriesKey] ?? 0) + point.requests;
|
|
214
234
|
dataMap.set(point.timestamp, bucket);
|
|
215
235
|
}
|
|
216
236
|
|
|
217
|
-
const series = topEntries.map(entry =>
|
|
237
|
+
const series: Array<{ key?: string; label: string }> = topEntries.map(entry => ({
|
|
238
|
+
key: entry.key,
|
|
239
|
+
label: labelByKey.get(entry.key) ?? entry.model,
|
|
240
|
+
}));
|
|
218
241
|
if ([...dataMap.values()].some(row => (row.Other ?? 0) > 0)) {
|
|
219
|
-
series.push("Other");
|
|
242
|
+
series.push({ label: "Other" });
|
|
220
243
|
}
|
|
221
244
|
|
|
222
245
|
const data = [...dataMap.values()]
|
|
223
246
|
.sort((a, b) => (a.timestamp ?? 0) - (b.timestamp ?? 0))
|
|
224
247
|
.map(row => {
|
|
225
248
|
const total = row.total ?? 0;
|
|
226
|
-
for (const
|
|
227
|
-
|
|
249
|
+
for (const seriesItem of series) {
|
|
250
|
+
const seriesKey = seriesItem.key ?? seriesItem.label;
|
|
251
|
+
row[seriesKey] = total > 0 ? ((row[seriesKey] ?? 0) / total) * 100 : 0;
|
|
228
252
|
}
|
|
229
253
|
return row;
|
|
230
254
|
});
|
|
@@ -238,10 +262,12 @@ function ModelsTable({
|
|
|
238
262
|
models,
|
|
239
263
|
performanceSeries,
|
|
240
264
|
timeRange,
|
|
265
|
+
colorLookup,
|
|
241
266
|
}: {
|
|
242
267
|
models: ModelStats[];
|
|
243
268
|
performanceSeries: ModelPerformancePoint[];
|
|
244
269
|
timeRange: TimeRange;
|
|
270
|
+
colorLookup: ReadonlyMap<string, string>;
|
|
245
271
|
}) {
|
|
246
272
|
const [expandedKey, setExpandedKey] = useState<string | null>(null);
|
|
247
273
|
const meta = rangeMeta(timeRange);
|
|
@@ -280,7 +306,7 @@ function ModelsTable({
|
|
|
280
306
|
const key = `${model.model}::${model.provider}`;
|
|
281
307
|
const performance = performanceSeriesByKey.get(key);
|
|
282
308
|
const trendData = performance?.data ?? [];
|
|
283
|
-
const trendColor = MODEL_COLORS[index % MODEL_COLORS.length];
|
|
309
|
+
const trendColor = colorLookup.get(key) ?? MODEL_COLORS[index % MODEL_COLORS.length];
|
|
284
310
|
const isExpanded = expandedKey === key;
|
|
285
311
|
const errorRate = model.errorRate * 100;
|
|
286
312
|
|