@oh-my-pi/omp-stats 15.0.0 → 15.0.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/package.json +5 -5
- package/src/client/components/BehaviorChart.tsx +85 -270
- package/src/client/components/BehaviorModelsTable.tsx +151 -274
- package/src/client/components/CostChart.tsx +85 -246
- package/src/client/components/ModelsTable.tsx +130 -246
- package/src/client/components/RequestDetail.tsx +0 -2
- package/src/client/components/chart-shared.tsx +320 -0
- package/src/client/components/models-table-shared.tsx +275 -0
- package/src/client/types.ts +21 -121
- package/src/db.ts +41 -1
- package/src/parser.ts +39 -5
- package/src/shared-types.ts +204 -0
- package/src/types.ts +16 -201
|
@@ -9,24 +9,29 @@ import {
|
|
|
9
9
|
Tooltip,
|
|
10
10
|
} from "chart.js";
|
|
11
11
|
import { format } from "date-fns";
|
|
12
|
-
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
13
12
|
import { useMemo, useState } from "react";
|
|
14
13
|
import { Line } from "react-chartjs-2";
|
|
15
14
|
import type { BehaviorModelStats, BehaviorTimeSeriesPoint } from "../types";
|
|
16
15
|
import { useSystemTheme } from "../useSystemTheme";
|
|
16
|
+
import {
|
|
17
|
+
DetailChartEmpty,
|
|
18
|
+
detailChartPlugins,
|
|
19
|
+
detailChartScalesSingleAxis,
|
|
20
|
+
ExpandableModelRow,
|
|
21
|
+
lineSeriesStyle,
|
|
22
|
+
MiniSparkline,
|
|
23
|
+
MODEL_COLORS,
|
|
24
|
+
ModelNameCell,
|
|
25
|
+
ModelTableBody,
|
|
26
|
+
ModelTableHeader,
|
|
27
|
+
ModelTableShell,
|
|
28
|
+
TABLE_CHART_THEMES,
|
|
29
|
+
type TableChartTheme,
|
|
30
|
+
TrendEmpty,
|
|
31
|
+
} from "./models-table-shared";
|
|
17
32
|
|
|
18
33
|
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
|
|
19
34
|
|
|
20
|
-
const MODEL_COLORS = [
|
|
21
|
-
"#a78bfa", // violet
|
|
22
|
-
"#22d3ee", // cyan
|
|
23
|
-
"#ec4899", // pink
|
|
24
|
-
"#4ade80", // green
|
|
25
|
-
"#fbbf24", // amber
|
|
26
|
-
"#f87171", // red
|
|
27
|
-
"#60a5fa", // blue
|
|
28
|
-
];
|
|
29
|
-
|
|
30
35
|
const SERIES_COLORS = {
|
|
31
36
|
yelling: "#fbbf24", // amber
|
|
32
37
|
profanity: "#f87171", // red
|
|
@@ -34,29 +39,6 @@ const SERIES_COLORS = {
|
|
|
34
39
|
frustration: "#22d3ee", // cyan - new semantic signals
|
|
35
40
|
} as const;
|
|
36
41
|
|
|
37
|
-
const CHART_THEMES = {
|
|
38
|
-
dark: {
|
|
39
|
-
legendLabel: "#cbd5e1",
|
|
40
|
-
tooltipBackground: "#16161e",
|
|
41
|
-
tooltipTitle: "#f8fafc",
|
|
42
|
-
tooltipBody: "#94a3b8",
|
|
43
|
-
tooltipBorder: "rgba(255, 255, 255, 0.1)",
|
|
44
|
-
grid: "rgba(255, 255, 255, 0.06)",
|
|
45
|
-
tick: "#94a3b8",
|
|
46
|
-
},
|
|
47
|
-
light: {
|
|
48
|
-
legendLabel: "#334155",
|
|
49
|
-
tooltipBackground: "#ffffff",
|
|
50
|
-
tooltipTitle: "#0f172a",
|
|
51
|
-
tooltipBody: "#334155",
|
|
52
|
-
tooltipBorder: "rgba(15, 23, 42, 0.18)",
|
|
53
|
-
grid: "rgba(15, 23, 42, 0.08)",
|
|
54
|
-
tick: "#475569",
|
|
55
|
-
},
|
|
56
|
-
} as const;
|
|
57
|
-
|
|
58
|
-
type ChartTheme = (typeof CHART_THEMES)[keyof typeof CHART_THEMES];
|
|
59
|
-
|
|
60
42
|
interface BehaviorModelsTableProps {
|
|
61
43
|
models: BehaviorModelStats[];
|
|
62
44
|
behaviorSeries: BehaviorTimeSeriesPoint[];
|
|
@@ -107,7 +89,7 @@ function formatRate(total: number, messages: number): string {
|
|
|
107
89
|
export function BehaviorModelsTable({ models, behaviorSeries }: BehaviorModelsTableProps) {
|
|
108
90
|
const [expandedKey, setExpandedKey] = useState<string | null>(null);
|
|
109
91
|
const theme = useSystemTheme();
|
|
110
|
-
const chartTheme =
|
|
92
|
+
const chartTheme = TABLE_CHART_THEMES[theme];
|
|
111
93
|
|
|
112
94
|
const trendByKey = useMemo(() => buildTrendLookup(behaviorSeries), [behaviorSeries]);
|
|
113
95
|
|
|
@@ -119,153 +101,137 @@ export function BehaviorModelsTable({ models, behaviorSeries }: BehaviorModelsTa
|
|
|
119
101
|
});
|
|
120
102
|
|
|
121
103
|
return (
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<div className="text-right">Anguish %</div>
|
|
140
|
-
<div className="text-right">Frustration %</div>
|
|
141
|
-
<div className="text-right">Hits %</div>
|
|
142
|
-
<div className="text-center">Trend</div>
|
|
143
|
-
<div />
|
|
144
|
-
</div>
|
|
104
|
+
<ModelTableShell
|
|
105
|
+
title="Behavior by Model"
|
|
106
|
+
subtitle="How often each model elicited a tantrum — rates are per user message"
|
|
107
|
+
>
|
|
108
|
+
<ModelTableHeader
|
|
109
|
+
gridTemplate={GRID_TEMPLATE}
|
|
110
|
+
columns={[
|
|
111
|
+
{ label: "Model" },
|
|
112
|
+
{ label: "Messages", align: "right" },
|
|
113
|
+
{ label: "CAPS %", align: "right" },
|
|
114
|
+
{ label: "Profanity %", align: "right" },
|
|
115
|
+
{ label: "Anguish %", align: "right" },
|
|
116
|
+
{ label: "Frustration %", align: "right" },
|
|
117
|
+
{ label: "Hits %", align: "right" },
|
|
118
|
+
{ label: "Trend", align: "center" },
|
|
119
|
+
]}
|
|
120
|
+
/>
|
|
145
121
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
122
|
+
<ModelTableBody>
|
|
123
|
+
{sortedModels.map((model, index) => {
|
|
124
|
+
const key = `${model.model}::${model.provider}`;
|
|
125
|
+
const trend = trendByKey.get(key)?.data ?? [];
|
|
126
|
+
const trendColor = MODEL_COLORS[index % MODEL_COLORS.length];
|
|
127
|
+
const isExpanded = expandedKey === key;
|
|
128
|
+
const totalFrustration = model.totalNegation + model.totalRepetition + model.totalBlame;
|
|
129
|
+
const totalHits = model.totalYelling + model.totalProfanity + model.totalAnguish + totalFrustration;
|
|
154
130
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
131
|
+
return (
|
|
132
|
+
<ExpandableModelRow
|
|
133
|
+
key={key}
|
|
134
|
+
gridTemplate={GRID_TEMPLATE}
|
|
135
|
+
isExpanded={isExpanded}
|
|
136
|
+
onToggle={() => setExpandedKey(isExpanded ? null : key)}
|
|
137
|
+
cells={[
|
|
138
|
+
<ModelNameCell key="name" model={model.model} provider={model.provider} />,
|
|
139
|
+
<div key="messages" className="text-right text-[var(--text-secondary)] font-mono text-sm">
|
|
140
|
+
{formatInt(model.totalMessages)}
|
|
141
|
+
</div>,
|
|
142
|
+
<div key="caps" className="text-right text-[var(--text-secondary)] font-mono text-sm">
|
|
143
|
+
{formatRate(model.totalYelling, model.totalMessages)}
|
|
144
|
+
</div>,
|
|
145
|
+
<div key="profanity" className="text-right text-[var(--text-secondary)] font-mono text-sm">
|
|
146
|
+
{formatRate(model.totalProfanity, model.totalMessages)}
|
|
147
|
+
</div>,
|
|
148
|
+
<div key="anguish" className="text-right text-[var(--text-secondary)] font-mono text-sm">
|
|
149
|
+
{formatRate(model.totalAnguish, model.totalMessages)}
|
|
150
|
+
</div>,
|
|
151
|
+
<div key="frustration" className="text-right text-[var(--text-secondary)] font-mono text-sm">
|
|
152
|
+
{formatRate(totalFrustration, model.totalMessages)}
|
|
153
|
+
</div>,
|
|
154
|
+
<div key="hits" className="text-right text-[var(--text-secondary)] font-mono text-sm">
|
|
155
|
+
{formatRate(totalHits, model.totalMessages)}
|
|
156
|
+
</div>,
|
|
157
|
+
]}
|
|
158
|
+
trendCell={
|
|
159
|
+
trend.length === 0 ? (
|
|
160
|
+
<TrendEmpty />
|
|
161
|
+
) : (
|
|
162
|
+
<MiniSparkline
|
|
163
|
+
timestamps={trend.map(d => d.timestamp)}
|
|
164
|
+
values={trend.map(d => d.total)}
|
|
165
|
+
color={trendColor}
|
|
166
|
+
/>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
expandedContent={
|
|
170
|
+
<div className="grid gap-4" style={{ gridTemplateColumns: "220px 1fr" }}>
|
|
171
|
+
<div className="space-y-4 text-sm">
|
|
172
|
+
<DetailRow
|
|
173
|
+
label="Yelling (CAPS)"
|
|
174
|
+
total={model.totalYelling}
|
|
175
|
+
messages={model.totalMessages}
|
|
176
|
+
valueClass="text-[var(--accent-amber,#fbbf24)]"
|
|
177
|
+
/>
|
|
178
|
+
<DetailRow
|
|
179
|
+
label="Profanity"
|
|
180
|
+
total={model.totalProfanity}
|
|
181
|
+
messages={model.totalMessages}
|
|
182
|
+
valueClass="text-[var(--accent-red,#f87171)]"
|
|
183
|
+
/>
|
|
184
|
+
<DetailRow
|
|
185
|
+
label="Anguish (!!!, nooo, dude, ..)"
|
|
186
|
+
total={model.totalAnguish}
|
|
187
|
+
messages={model.totalMessages}
|
|
188
|
+
valueClass="text-[var(--accent-violet,#a78bfa)]"
|
|
189
|
+
/>
|
|
190
|
+
<DetailRow
|
|
191
|
+
label="Negation (no/nope/wrong)"
|
|
192
|
+
total={model.totalNegation}
|
|
193
|
+
messages={model.totalMessages}
|
|
194
|
+
valueClass="text-[var(--accent-cyan,#22d3ee)]"
|
|
195
|
+
/>
|
|
196
|
+
<DetailRow
|
|
197
|
+
label="Repetition (i meant, still doesnt)"
|
|
198
|
+
total={model.totalRepetition}
|
|
199
|
+
messages={model.totalMessages}
|
|
200
|
+
valueClass="text-[var(--accent-cyan,#22d3ee)]"
|
|
201
|
+
/>
|
|
202
|
+
<DetailRow
|
|
203
|
+
label="Blame (you didnt, stop X-ing)"
|
|
204
|
+
total={model.totalBlame}
|
|
205
|
+
messages={model.totalMessages}
|
|
206
|
+
valueClass="text-[var(--accent-cyan,#22d3ee)]"
|
|
207
|
+
/>
|
|
208
|
+
<DetailRow
|
|
209
|
+
label="Avg chars / msg"
|
|
210
|
+
total={model.totalChars}
|
|
211
|
+
messages={model.totalMessages}
|
|
212
|
+
valueClass="text-[var(--text-secondary)]"
|
|
213
|
+
mode="average"
|
|
214
|
+
/>
|
|
195
215
|
</div>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
<DetailRow
|
|
203
|
-
label="Yelling (CAPS)"
|
|
204
|
-
total={model.totalYelling}
|
|
205
|
-
messages={model.totalMessages}
|
|
206
|
-
valueClass="text-[var(--accent-amber,#fbbf24)]"
|
|
207
|
-
/>
|
|
208
|
-
<DetailRow
|
|
209
|
-
label="Profanity"
|
|
210
|
-
total={model.totalProfanity}
|
|
211
|
-
messages={model.totalMessages}
|
|
212
|
-
valueClass="text-[var(--accent-red,#f87171)]"
|
|
213
|
-
/>
|
|
214
|
-
<DetailRow
|
|
215
|
-
label="Anguish (!!!, nooo, dude, ..)"
|
|
216
|
-
total={model.totalAnguish}
|
|
217
|
-
messages={model.totalMessages}
|
|
218
|
-
valueClass="text-[var(--accent-violet,#a78bfa)]"
|
|
219
|
-
/>
|
|
220
|
-
<DetailRow
|
|
221
|
-
label="Negation (no/nope/wrong)"
|
|
222
|
-
total={model.totalNegation}
|
|
223
|
-
messages={model.totalMessages}
|
|
224
|
-
valueClass="text-[var(--accent-cyan,#22d3ee)]"
|
|
225
|
-
/>
|
|
226
|
-
<DetailRow
|
|
227
|
-
label="Repetition (i meant, still doesnt)"
|
|
228
|
-
total={model.totalRepetition}
|
|
229
|
-
messages={model.totalMessages}
|
|
230
|
-
valueClass="text-[var(--accent-cyan,#22d3ee)]"
|
|
231
|
-
/>
|
|
232
|
-
<DetailRow
|
|
233
|
-
label="Blame (you didnt, stop X-ing)"
|
|
234
|
-
total={model.totalBlame}
|
|
235
|
-
messages={model.totalMessages}
|
|
236
|
-
valueClass="text-[var(--accent-cyan,#22d3ee)]"
|
|
237
|
-
/>
|
|
238
|
-
<DetailRow
|
|
239
|
-
label="Avg chars / msg"
|
|
240
|
-
total={model.totalChars}
|
|
241
|
-
messages={model.totalMessages}
|
|
242
|
-
valueClass="text-[var(--text-secondary)]"
|
|
243
|
-
mode="average"
|
|
244
|
-
/>
|
|
245
|
-
</div>
|
|
246
|
-
<div className="h-[200px]">
|
|
247
|
-
{trend.length === 0 ? (
|
|
248
|
-
<div className="h-full flex items-center justify-center text-[var(--text-muted)] text-sm">
|
|
249
|
-
No data available
|
|
250
|
-
</div>
|
|
251
|
-
) : (
|
|
252
|
-
<BreakdownChart data={trend} chartTheme={chartTheme} />
|
|
253
|
-
)}
|
|
254
|
-
</div>
|
|
255
|
-
</div>
|
|
216
|
+
<div className="h-[200px]">
|
|
217
|
+
{trend.length === 0 ? (
|
|
218
|
+
<DetailChartEmpty />
|
|
219
|
+
) : (
|
|
220
|
+
<BreakdownChart data={trend} chartTheme={chartTheme} />
|
|
221
|
+
)}
|
|
256
222
|
</div>
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
</
|
|
268
|
-
</
|
|
223
|
+
</div>
|
|
224
|
+
}
|
|
225
|
+
/>
|
|
226
|
+
);
|
|
227
|
+
})}
|
|
228
|
+
{sortedModels.length === 0 ? (
|
|
229
|
+
<div className="border-t border-[var(--border-subtle)] px-5 py-8 text-center text-[var(--text-muted)] text-sm">
|
|
230
|
+
No user behavior recorded for this range yet.
|
|
231
|
+
</div>
|
|
232
|
+
) : null}
|
|
233
|
+
</ModelTableBody>
|
|
234
|
+
</ModelTableShell>
|
|
269
235
|
);
|
|
270
236
|
}
|
|
271
237
|
|
|
@@ -302,111 +268,22 @@ function DetailRow({
|
|
|
302
268
|
);
|
|
303
269
|
}
|
|
304
270
|
|
|
305
|
-
function
|
|
271
|
+
function BreakdownChart({ data, chartTheme }: { data: DailyPoint[]; chartTheme: TableChartTheme }) {
|
|
306
272
|
const chartData = {
|
|
307
273
|
labels: data.map(d => format(new Date(d.timestamp), "MMM d")),
|
|
308
274
|
datasets: [
|
|
309
|
-
{
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
tension: 0.4,
|
|
314
|
-
pointRadius: 0,
|
|
315
|
-
borderWidth: 2,
|
|
316
|
-
},
|
|
275
|
+
{ label: "CAPS", data: data.map(d => d.yelling), ...lineSeriesStyle(SERIES_COLORS.yelling) },
|
|
276
|
+
{ label: "Profanity", data: data.map(d => d.profanity), ...lineSeriesStyle(SERIES_COLORS.profanity) },
|
|
277
|
+
{ label: "Anguish", data: data.map(d => d.anguish), ...lineSeriesStyle(SERIES_COLORS.anguish) },
|
|
278
|
+
{ label: "Frustration", data: data.map(d => d.frustration), ...lineSeriesStyle(SERIES_COLORS.frustration) },
|
|
317
279
|
],
|
|
318
280
|
};
|
|
319
281
|
|
|
320
282
|
const options = {
|
|
321
283
|
responsive: true,
|
|
322
284
|
maintainAspectRatio: false,
|
|
323
|
-
plugins:
|
|
324
|
-
scales:
|
|
325
|
-
x: { display: false },
|
|
326
|
-
y: { display: false, min: 0 },
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
return <Line data={chartData} options={options} />;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function BreakdownChart({ data, chartTheme }: { data: DailyPoint[]; chartTheme: ChartTheme }) {
|
|
334
|
-
const chartData = {
|
|
335
|
-
labels: data.map(d => format(new Date(d.timestamp), "MMM d")),
|
|
336
|
-
datasets: [
|
|
337
|
-
{
|
|
338
|
-
label: "CAPS",
|
|
339
|
-
data: data.map(d => d.yelling),
|
|
340
|
-
borderColor: SERIES_COLORS.yelling,
|
|
341
|
-
backgroundColor: "transparent",
|
|
342
|
-
tension: 0.4,
|
|
343
|
-
pointRadius: 0,
|
|
344
|
-
borderWidth: 2,
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
label: "Profanity",
|
|
348
|
-
data: data.map(d => d.profanity),
|
|
349
|
-
borderColor: SERIES_COLORS.profanity,
|
|
350
|
-
backgroundColor: "transparent",
|
|
351
|
-
tension: 0.4,
|
|
352
|
-
pointRadius: 0,
|
|
353
|
-
borderWidth: 2,
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
label: "Anguish",
|
|
357
|
-
data: data.map(d => d.anguish),
|
|
358
|
-
borderColor: SERIES_COLORS.anguish,
|
|
359
|
-
backgroundColor: "transparent",
|
|
360
|
-
tension: 0.4,
|
|
361
|
-
pointRadius: 0,
|
|
362
|
-
borderWidth: 2,
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
label: "Frustration",
|
|
366
|
-
data: data.map(d => d.frustration),
|
|
367
|
-
borderColor: SERIES_COLORS.frustration,
|
|
368
|
-
backgroundColor: "transparent",
|
|
369
|
-
tension: 0.4,
|
|
370
|
-
pointRadius: 0,
|
|
371
|
-
borderWidth: 2,
|
|
372
|
-
},
|
|
373
|
-
],
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
const options = {
|
|
377
|
-
responsive: true,
|
|
378
|
-
maintainAspectRatio: false,
|
|
379
|
-
plugins: {
|
|
380
|
-
legend: {
|
|
381
|
-
display: true,
|
|
382
|
-
position: "top" as const,
|
|
383
|
-
labels: {
|
|
384
|
-
color: chartTheme.legendLabel,
|
|
385
|
-
usePointStyle: true,
|
|
386
|
-
padding: 16,
|
|
387
|
-
font: { size: 12 },
|
|
388
|
-
},
|
|
389
|
-
},
|
|
390
|
-
tooltip: {
|
|
391
|
-
backgroundColor: chartTheme.tooltipBackground,
|
|
392
|
-
titleColor: chartTheme.tooltipTitle,
|
|
393
|
-
bodyColor: chartTheme.tooltipBody,
|
|
394
|
-
borderColor: chartTheme.tooltipBorder,
|
|
395
|
-
borderWidth: 1,
|
|
396
|
-
cornerRadius: 8,
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
scales: {
|
|
400
|
-
x: {
|
|
401
|
-
grid: { color: chartTheme.grid },
|
|
402
|
-
ticks: { color: chartTheme.tick, font: { size: 11 } },
|
|
403
|
-
},
|
|
404
|
-
y: {
|
|
405
|
-
grid: { color: chartTheme.grid },
|
|
406
|
-
ticks: { color: chartTheme.tick, font: { size: 11 } },
|
|
407
|
-
min: 0,
|
|
408
|
-
},
|
|
409
|
-
},
|
|
285
|
+
plugins: detailChartPlugins(chartTheme),
|
|
286
|
+
scales: detailChartScalesSingleAxis(chartTheme),
|
|
410
287
|
};
|
|
411
288
|
|
|
412
289
|
return <Line data={chartData} options={options} />;
|