@linxiraos/pi-stats 1.1.27 → 1.2.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.
Files changed (124) hide show
  1. package/CHANGELOG.md +10 -7
  2. package/build.ts +4 -0
  3. package/dist/client/favicon.ico +0 -0
  4. package/dist/client/index.css +1 -1
  5. package/dist/client/index.html +1 -0
  6. package/dist/client/index.js +96 -96
  7. package/dist/types/aggregator.d.ts +4 -1
  8. package/dist/types/client/api.d.ts +5 -1
  9. package/dist/types/client/app/nav.d.ts +5 -2
  10. package/dist/types/client/data/formatters.d.ts +5 -2
  11. package/dist/types/client/data/live.d.ts +16 -11
  12. package/dist/types/client/data/query-store.d.ts +39 -0
  13. package/dist/types/client/data/query.d.ts +10 -5
  14. package/dist/types/client/data/range.d.ts +4 -3
  15. package/dist/types/client/i18n/format.d.ts +9 -0
  16. package/dist/types/client/i18n/index.d.ts +18 -0
  17. package/dist/types/client/i18n/messages/charts.d.ts +8 -0
  18. package/dist/types/client/i18n/messages/common.d.ts +153 -0
  19. package/dist/types/client/i18n/messages/costs.d.ts +92 -0
  20. package/dist/types/client/i18n/messages/en.d.ts +1255 -0
  21. package/dist/types/client/i18n/messages/errors.d.ts +78 -0
  22. package/dist/types/client/i18n/messages/frustration.d.ts +191 -0
  23. package/dist/types/client/i18n/messages/gain.d.ts +54 -0
  24. package/dist/types/client/i18n/messages/models.d.ts +94 -0
  25. package/dist/types/client/i18n/messages/nav.d.ts +46 -0
  26. package/dist/types/client/i18n/messages/overview.d.ts +80 -0
  27. package/dist/types/client/i18n/messages/projects.d.ts +84 -0
  28. package/dist/types/client/i18n/messages/providers.d.ts +142 -0
  29. package/dist/types/client/i18n/messages/requests.d.ts +72 -0
  30. package/dist/types/client/i18n/messages/tools.d.ts +119 -0
  31. package/dist/types/client/i18n/messages/traces.d.ts +144 -0
  32. package/dist/types/client/i18n/messages/ui.d.ts +44 -0
  33. package/dist/types/client/i18n/messages/zh-CN.d.ts +13 -0
  34. package/dist/types/client/i18n/types.d.ts +15 -0
  35. package/dist/types/client/traces/Minimap.d.ts +2 -0
  36. package/dist/types/client/traces/TimelineCanvas.d.ts +29 -8
  37. package/dist/types/client/traces/TranscriptList.d.ts +3 -1
  38. package/dist/types/db.d.ts +14 -21
  39. package/dist/types/embedded-client.d.ts +5 -0
  40. package/dist/types/frustration.d.ts +18 -9
  41. package/dist/types/live.d.ts +5 -3
  42. package/dist/types/locale.d.ts +16 -0
  43. package/dist/types/parser.d.ts +0 -17
  44. package/dist/types/rollup.d.ts +9 -1
  45. package/dist/types/server.d.ts +6 -0
  46. package/dist/types/shared-types.d.ts +7 -0
  47. package/dist/types/trace.d.ts +18 -6
  48. package/dist/types/types.d.ts +12 -0
  49. package/dist/types/user-metrics.d.ts +23 -1
  50. package/package.json +4 -4
  51. package/src/aggregator.ts +28 -11
  52. package/src/client/api.ts +40 -5
  53. package/src/client/app/LiveChip.tsx +14 -13
  54. package/src/client/app/Shell.tsx +19 -13
  55. package/src/client/app/ThemeToggle.tsx +4 -3
  56. package/src/client/app/nav.ts +19 -16
  57. package/src/client/data/formatters.ts +73 -13
  58. package/src/client/data/live.tsx +85 -45
  59. package/src/client/data/query-store.ts +108 -0
  60. package/src/client/data/query.ts +47 -56
  61. package/src/client/data/range.ts +34 -14
  62. package/src/client/data/series.ts +7 -1
  63. package/src/client/favicon.ico +0 -0
  64. package/src/client/i18n/format.ts +28 -0
  65. package/src/client/i18n/index.ts +53 -0
  66. package/src/client/i18n/messages/charts.ts +8 -0
  67. package/src/client/i18n/messages/common.ts +157 -0
  68. package/src/client/i18n/messages/costs.ts +104 -0
  69. package/src/client/i18n/messages/en.ts +40 -0
  70. package/src/client/i18n/messages/errors.ts +80 -0
  71. package/src/client/i18n/messages/frustration.ts +198 -0
  72. package/src/client/i18n/messages/gain.ts +54 -0
  73. package/src/client/i18n/messages/models.ts +94 -0
  74. package/src/client/i18n/messages/nav.ts +46 -0
  75. package/src/client/i18n/messages/overview.ts +90 -0
  76. package/src/client/i18n/messages/projects.ts +84 -0
  77. package/src/client/i18n/messages/providers.ts +165 -0
  78. package/src/client/i18n/messages/requests.ts +73 -0
  79. package/src/client/i18n/messages/tools.ts +120 -0
  80. package/src/client/i18n/messages/traces.ts +160 -0
  81. package/src/client/i18n/messages/ui.ts +46 -0
  82. package/src/client/i18n/messages/zh-CN.ts +43 -0
  83. package/src/client/i18n/types.ts +19 -0
  84. package/src/client/index.tsx +4 -0
  85. package/src/client/routes/CostsRoute.tsx +61 -48
  86. package/src/client/routes/ErrorsRoute.tsx +71 -47
  87. package/src/client/routes/FrustrationRoute.tsx +131 -116
  88. package/src/client/routes/GainRoute.tsx +42 -30
  89. package/src/client/routes/ModelsRoute.tsx +87 -71
  90. package/src/client/routes/OverviewRoute.tsx +107 -62
  91. package/src/client/routes/ProjectsRoute.tsx +71 -45
  92. package/src/client/routes/ProvidersRoute.tsx +149 -113
  93. package/src/client/routes/RequestsRoute.tsx +68 -45
  94. package/src/client/routes/ToolsRoute.tsx +105 -92
  95. package/src/client/routes/TracesRoute.tsx +25 -23
  96. package/src/client/styles.css +4 -2
  97. package/src/client/traces/AggregatesPanel.tsx +19 -8
  98. package/src/client/traces/Minimap.tsx +65 -27
  99. package/src/client/traces/SpanDrawer.tsx +46 -21
  100. package/src/client/traces/SummaryStrip.tsx +27 -11
  101. package/src/client/traces/TimelineCanvas.tsx +245 -116
  102. package/src/client/traces/TraceView.tsx +51 -36
  103. package/src/client/traces/TranscriptList.tsx +20 -11
  104. package/src/client/traces/traces.css +26 -0
  105. package/src/client/ui/JsonBlock.tsx +3 -2
  106. package/src/client/ui/Modal.tsx +3 -2
  107. package/src/client/ui/RequestDrawer.tsx +33 -30
  108. package/src/client/ui/SearchInput.tsx +2 -1
  109. package/src/client/ui/States.tsx +3 -2
  110. package/src/client/ui/Table.tsx +7 -3
  111. package/src/db.ts +150 -135
  112. package/src/embedded-client.ts +9 -0
  113. package/src/frustration.ts +9 -30
  114. package/src/live.ts +36 -11
  115. package/src/locale.ts +29 -0
  116. package/src/parser.ts +37 -72
  117. package/src/port-conflict.ts +1 -1
  118. package/src/prompts/judge/frustration.json +19 -0
  119. package/src/rollup.ts +41 -20
  120. package/src/server.ts +67 -27
  121. package/src/shared-types.ts +7 -0
  122. package/src/trace.ts +54 -40
  123. package/src/types.ts +12 -0
  124. package/src/user-metrics.ts +220 -6
@@ -1,6 +1,7 @@
1
1
  import { ArrowUpRight, X } from "lucide-react";
2
2
  import { useMemo, useState } from "react";
3
3
  import { getRecentErrors } from "../api";
4
+ import { t } from "../i18n";
4
5
  import { BarList } from "../charts";
5
6
  import { modelKey } from "../data/colors";
6
7
  import {
@@ -44,7 +45,9 @@ const LOAD_STEPS = [50, 200, 1_000] as const;
44
45
  export function ErrorsRoute({ active, range, onRequestClick }: ErrorsRouteProps) {
45
46
  const [step, setStep] = useState(0);
46
47
  const limit = LOAD_STEPS[step];
47
- const errors = useQuery(["errors", range, limit], () => getRecentErrors(range, limit), { enabled: active });
48
+ const errors = useQuery(["errors", range, limit], ({ signal }) => getRecentErrors(range, limit, signal), {
49
+ enabled: active,
50
+ });
48
51
  const [selectedSignature, setSelectedSignature] = useState<string | null>(null);
49
52
  const [selectedModel, setSelectedModel] = useState<string | null>(null);
50
53
  const [search, setSearch] = useState("");
@@ -97,9 +100,7 @@ export function ErrorsRoute({ active, range, onRequestClick }: ErrorsRouteProps)
97
100
  const footer =
98
101
  errors.data !== null && !complete ? (
99
102
  <>
100
- <span>
101
- Showing the latest {formatInteger(loaded)} failures in {meta.windowLabel}; older ones are not loaded.
102
- </span>
103
+ <span>{t("errors.footer", { count: formatInteger(loaded), window: t(meta.windowKey) })}</span>
103
104
  {nextStep !== null && (
104
105
  <button
105
106
  type="button"
@@ -108,7 +109,9 @@ export function ErrorsRoute({ active, range, onRequestClick }: ErrorsRouteProps)
108
109
  disabled={errors.refreshing}
109
110
  onClick={() => setStep(step + 1)}
110
111
  >
111
- {errors.refreshing && errors.stale ? "Loading…" : `Load latest ${formatInteger(nextStep)}`}
112
+ {errors.refreshing && errors.stale
113
+ ? t("errors.loading")
114
+ : t("errors.load-latest", { count: formatInteger(nextStep) })}
112
115
  </button>
113
116
  )}
114
117
  </>
@@ -120,35 +123,44 @@ export function ErrorsRoute({ active, range, onRequestClick }: ErrorsRouteProps)
120
123
 
121
124
  return (
122
125
  <div className="page">
123
- <PageHeader
124
- title="Errors"
125
- description={`Failed model requests in ${meta.windowLabel}, grouped by error signature. Open a failure for its full payload.`}
126
- />
126
+ <PageHeader title={t("errors.title")} description={t("errors.description", { window: t(meta.windowKey) })} />
127
127
 
128
128
  <QueryView query={errors} skeleton={<Skeleton height={96} style={{ borderRadius: 12 }} />}>
129
129
  {() => (
130
130
  <div data-stale={errors.stale}>
131
131
  <StatGrid min={170}>
132
132
  <Stat
133
- label="Failures"
133
+ label={t("errors.failures")}
134
134
  value={formatInteger(loaded)}
135
- hint={complete ? `in ${meta.windowLabel}` : `latest ${formatInteger(loaded)} loaded`}
135
+ hint={
136
+ complete
137
+ ? t("errors.hint-in-window", { window: t(meta.windowKey) })
138
+ : t("errors.hint-latest-loaded", { count: formatInteger(loaded) })
139
+ }
136
140
  />
137
141
  <Stat
138
- label="Signatures"
139
- title="Distinct error messages after normalizing ids and numbers"
142
+ label={t("errors.signatures")}
143
+ title={t("errors.signatures-hint")}
140
144
  value={formatInteger(view.groups.length)}
141
- hint={view.groups[0] ? `top: ${formatInteger(view.groups[0].count)} failures` : undefined}
145
+ hint={
146
+ view.groups[0]
147
+ ? t("errors.signatures-top", { count: formatInteger(view.groups[0].count) })
148
+ : undefined
149
+ }
142
150
  />
143
151
  <Stat
144
- label="Affected models"
152
+ label={t("errors.affected-models")}
145
153
  value={formatInteger(view.models.length)}
146
154
  hint={view.models[0]?.model}
147
155
  />
148
156
  <Stat
149
- label="Last failure"
157
+ label={t("errors.last-failure")}
150
158
  value={view.newest ? formatRelativeTime(view.newest.timestamp) : "–"}
151
- hint={view.newest ? formatTimestamp(view.newest.timestamp) : `none in ${meta.windowLabel}`}
159
+ hint={
160
+ view.newest
161
+ ? formatTimestamp(view.newest.timestamp)
162
+ : t("errors.none-in-window", { window: t(meta.windowKey) })
163
+ }
152
164
  />
153
165
  </StatGrid>
154
166
  </div>
@@ -158,8 +170,8 @@ export function ErrorsRoute({ active, range, onRequestClick }: ErrorsRouteProps)
158
170
  <div className="grid grid-main-side">
159
171
  <Card
160
172
  index={1}
161
- title="Error signatures"
162
- description="Same message with ids and counters normalized. Select one to filter the failures below."
173
+ title={t("errors.error-signatures")}
174
+ description={t("errors.signatures-card-description")}
163
175
  flush
164
176
  stale={errors.stale}
165
177
  >
@@ -169,8 +181,8 @@ export function ErrorsRoute({ active, range, onRequestClick }: ErrorsRouteProps)
169
181
  isEmpty={rows => rows.length === 0}
170
182
  empty={
171
183
  <EmptyState
172
- title={`No failures in ${meta.windowLabel}`}
173
- hint="Every request succeeded or was aborted."
184
+ title={t("errors.empty-no-failures", { window: t(meta.windowKey) })}
185
+ hint={t("errors.empty-all-succeeded")}
174
186
  />
175
187
  }
176
188
  >
@@ -196,15 +208,15 @@ export function ErrorsRoute({ active, range, onRequestClick }: ErrorsRouteProps)
196
208
 
197
209
  <Card
198
210
  index={2}
199
- title="By model"
200
- description="Failures per model. Select one to filter."
211
+ title={t("errors.by-model")}
212
+ description={t("errors.by-model-description")}
201
213
  stale={errors.stale}
202
214
  >
203
215
  <QueryView
204
216
  query={errors}
205
217
  skeleton={<Skeleton height={180} />}
206
218
  isEmpty={rows => rows.length === 0}
207
- empty={<EmptyState title="No affected models" />}
219
+ empty={<EmptyState title={t("errors.empty-no-affected-models")} />}
208
220
  >
209
221
  {() => (
210
222
  <BarList
@@ -228,11 +240,14 @@ export function ErrorsRoute({ active, range, onRequestClick }: ErrorsRouteProps)
228
240
 
229
241
  <Card
230
242
  index={3}
231
- title="Failures"
243
+ title={t("errors.failures")}
232
244
  description={
233
245
  errors.data === null
234
- ? "Loading failures…"
235
- : `${formatInteger(filtered.length)} of ${formatInteger(loaded)} failures, newest first`
246
+ ? t("errors.loading-failures")
247
+ : t("errors.filtered-of-total", {
248
+ shown: formatInteger(filtered.length),
249
+ total: formatInteger(loaded),
250
+ })
236
251
  }
237
252
  actions={
238
253
  <>
@@ -259,7 +274,7 @@ export function ErrorsRoute({ active, range, onRequestClick }: ErrorsRouteProps)
259
274
  <X size={12} />
260
275
  </button>
261
276
  )}
262
- <SearchInput value={search} onChange={setSearch} placeholder="Message, model or project" />
277
+ <SearchInput value={search} onChange={setSearch} placeholder={t("errors.search-placeholder")} />
263
278
  </>
264
279
  }
265
280
  flush
@@ -270,7 +285,7 @@ export function ErrorsRoute({ active, range, onRequestClick }: ErrorsRouteProps)
270
285
  query={errors}
271
286
  skeleton={<TableSkeleton rows={10} />}
272
287
  isEmpty={rows => rows.length === 0}
273
- empty={<EmptyState title={`No failures in ${meta.windowLabel}`} />}
288
+ empty={<EmptyState title={t("errors.empty-no-failures", { window: t(meta.windowKey) })} />}
274
289
  >
275
290
  {() => (
276
291
  <Table
@@ -281,7 +296,7 @@ export function ErrorsRoute({ active, range, onRequestClick }: ErrorsRouteProps)
281
296
  initialSort={{ key: "time", dir: "desc" }}
282
297
  limit={50}
283
298
  dense
284
- empty={<EmptyState title="No failures match" hint="Clear the search or the selected filters." />}
299
+ empty={<EmptyState title={t("errors.no-match")} hint={t("errors.no-match-hint")} />}
285
300
  />
286
301
  )}
287
302
  </QueryView>
@@ -294,7 +309,7 @@ function buildGroupColumns(maxCount: number): Column<ErrorGroupView>[] {
294
309
  return [
295
310
  {
296
311
  key: "signature",
297
- header: "Signature",
312
+ header: t("errors.signature"),
298
313
  wrap: true,
299
314
  sort: group => group.signature,
300
315
  render: group => (
@@ -305,18 +320,22 @@ function buildGroupColumns(maxCount: number): Column<ErrorGroupView>[] {
305
320
  },
306
321
  {
307
322
  key: "models",
308
- header: "Models",
323
+ header: t("common.models"),
309
324
  sort: group => group.models.length,
310
325
  render: group => (
311
326
  <LabelCell
312
327
  primary={<span className="mono">{group.models[0]?.model}</span>}
313
- secondary={group.models.length > 1 ? `+${group.models.length - 1} more` : group.models[0]?.provider}
328
+ secondary={
329
+ group.models.length > 1
330
+ ? t("errors.more-models", { count: formatInteger(group.models.length - 1) })
331
+ : group.models[0]?.provider
332
+ }
314
333
  />
315
334
  ),
316
335
  },
317
336
  {
318
337
  key: "last",
319
- header: "Last seen",
338
+ header: t("errors.last-seen"),
320
339
  sort: group => group.lastSeen,
321
340
  render: group => (
322
341
  <span className="muted" title={formatTimestamp(group.lastSeen)}>
@@ -326,7 +345,7 @@ function buildGroupColumns(maxCount: number): Column<ErrorGroupView>[] {
326
345
  },
327
346
  {
328
347
  key: "count",
329
- header: "Failures",
348
+ header: t("errors.failures"),
330
349
  align: "right",
331
350
  width: 120,
332
351
  sort: group => group.count,
@@ -341,15 +360,20 @@ function SignatureDetail({ group, onRequestClick }: { group: ErrorGroupView; onR
341
360
  const latestId = group.latest.id;
342
361
  return (
343
362
  <div className="errors-detail">
344
- <pre className="code-block errors-detail-message">{group.latest.errorMessage ?? "Unknown error"}</pre>
363
+ <pre className="code-block errors-detail-message">
364
+ {group.latest.errorMessage ?? t("errors.unknown-error")}
365
+ </pre>
345
366
  <div className="errors-detail-meta">
346
367
  <span className="muted">
347
- {formatInteger(group.count)} failures · first {formatTimestamp(group.firstSeen)} · last{" "}
348
- {formatTimestamp(group.lastSeen)}
368
+ {t("errors.group-meta", {
369
+ count: formatInteger(group.count),
370
+ first: formatTimestamp(group.firstSeen),
371
+ last: formatTimestamp(group.lastSeen),
372
+ })}
349
373
  </span>
350
374
  {latestId !== undefined && (
351
375
  <button type="button" className="btn" data-size="sm" onClick={() => onRequestClick(latestId)}>
352
- Open latest <ArrowUpRight size={13} />
376
+ {t("errors.open-latest")} <ArrowUpRight size={13} />
353
377
  </button>
354
378
  )}
355
379
  </div>
@@ -368,7 +392,7 @@ function SignatureDetail({ group, onRequestClick }: { group: ErrorGroupView; onR
368
392
  const FAILURE_COLUMNS: readonly Column<MessageStats>[] = [
369
393
  {
370
394
  key: "time",
371
- header: "When",
395
+ header: t("errors.when"),
372
396
  width: 120,
373
397
  sort: row => row.timestamp,
374
398
  render: row => (
@@ -379,23 +403,23 @@ const FAILURE_COLUMNS: readonly Column<MessageStats>[] = [
379
403
  },
380
404
  {
381
405
  key: "model",
382
- header: "Model",
406
+ header: t("common.model"),
383
407
  sort: row => row.model,
384
408
  render: row => <LabelCell primary={<span className="mono">{row.model}</span>} secondary={row.provider} />,
385
409
  },
386
410
  {
387
411
  key: "error",
388
- header: "Error",
412
+ header: t("common.error"),
389
413
  sort: row => row.errorMessage ?? "",
390
414
  render: row => (
391
415
  <span className="errors-message" title={row.errorMessage ?? undefined}>
392
- {row.errorMessage ?? "Unknown error"}
416
+ {row.errorMessage ?? t("errors.unknown-error")}
393
417
  </span>
394
418
  ),
395
419
  },
396
420
  {
397
421
  key: "project",
398
- header: "Project",
422
+ header: t("common.project"),
399
423
  sort: row => row.folder,
400
424
  render: row => (
401
425
  <span className="mono muted" title={row.folder}>
@@ -405,15 +429,15 @@ const FAILURE_COLUMNS: readonly Column<MessageStats>[] = [
405
429
  },
406
430
  {
407
431
  key: "tokens",
408
- header: "Tokens",
432
+ header: t("common.tokens"),
409
433
  align: "right",
410
434
  sort: row => row.usage.totalTokens,
411
435
  render: row => <span className="num">{formatInteger(row.usage.totalTokens)}</span>,
412
436
  },
413
437
  {
414
438
  key: "cost",
415
- header: "Cost",
416
- title: "API-equivalent estimate",
439
+ header: t("common.cost"),
440
+ title: t("errors.cost-hint"),
417
441
  align: "right",
418
442
  sort: row => row.usage.cost.total,
419
443
  render: row => <span className="num">{formatMessageCost(row, 4)}</span>,