@linxiraos/pi-stats 1.1.26 → 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 (125) hide show
  1. package/CHANGELOG.md +12 -5
  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 +7 -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 +13 -1
  49. package/dist/types/user-metrics.d.ts +23 -1
  50. package/package.json +5 -5
  51. package/src/aggregator.ts +31 -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 +80 -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 +90 -73
  90. package/src/client/routes/OverviewRoute.tsx +109 -63
  91. package/src/client/routes/ProjectsRoute.tsx +73 -46
  92. package/src/client/routes/ProvidersRoute.tsx +151 -114
  93. package/src/client/routes/RequestsRoute.tsx +69 -46
  94. package/src/client/routes/ToolsRoute.tsx +108 -94
  95. package/src/client/routes/TracesRoute.tsx +25 -23
  96. package/src/client/styles.css +5 -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/index.ts +2 -1
  115. package/src/live.ts +36 -11
  116. package/src/locale.ts +29 -0
  117. package/src/parser.ts +37 -72
  118. package/src/port-conflict.ts +1 -1
  119. package/src/prompts/judge/frustration.json +19 -0
  120. package/src/rollup.ts +41 -20
  121. package/src/server.ts +67 -27
  122. package/src/shared-types.ts +7 -0
  123. package/src/trace.ts +54 -40
  124. package/src/types.ts +13 -1
  125. package/src/user-metrics.ts +220 -6
@@ -1,13 +1,14 @@
1
1
  import { useMemo, useState } from "react";
2
2
  import { getRecentRequests } from "../api";
3
+ import { t } from "../i18n";
3
4
  import {
4
5
  formatCompact,
5
6
  formatDurationMs,
7
+ formatErrorRate,
6
8
  formatEstimatedCost,
7
9
  formatFolder,
8
10
  formatInteger,
9
11
  formatMessageCost,
10
- formatPercent,
11
12
  formatRelativeTime,
12
13
  formatTimestamp,
13
14
  } from "../data/formatters";
@@ -47,7 +48,7 @@ type StatusFilter = "all" | RequestStatus;
47
48
  export function RequestsRoute({ active, range, onRequestClick }: RequestsRouteProps) {
48
49
  const [step, setStep] = useState(0);
49
50
  const limit = LOAD_STEPS[step];
50
- const log = useQuery(["requests-log", limit], () => getRecentRequests(limit), { enabled: active });
51
+ const log = useQuery(["requests-log", limit], ({ signal }) => getRecentRequests(limit, signal), { enabled: active });
51
52
  const [search, setSearch] = useState("");
52
53
  const [status, setStatus] = useState<StatusFilter>("all");
53
54
  const meta = rangeMeta(range);
@@ -88,7 +89,9 @@ export function RequestsRoute({ active, range, onRequestClick }: RequestsRoutePr
88
89
  disabled={log.refreshing}
89
90
  onClick={() => setStep(step + 1)}
90
91
  >
91
- {log.refreshing && log.stale ? "Loading…" : `Load latest ${formatInteger(nextStep)}`}
92
+ {log.refreshing && log.stale
93
+ ? t("requests.loading")
94
+ : t("requests.load-latest", { count: formatInteger(nextStep) })}
92
95
  </button>
93
96
  ) : undefined;
94
97
 
@@ -96,7 +99,7 @@ export function RequestsRoute({ active, range, onRequestClick }: RequestsRoutePr
96
99
  value,
97
100
  label: (
98
101
  <>
99
- {value === "all" ? "All" : REQUEST_STATUS[value].label}{" "}
102
+ {value === "all" ? t("requests.status-all") : REQUEST_STATUS[value].label}{" "}
100
103
  <span className="dim num">{formatCompact(counts[value])}</span>
101
104
  </>
102
105
  ),
@@ -105,8 +108,8 @@ export function RequestsRoute({ active, range, onRequestClick }: RequestsRoutePr
105
108
  return (
106
109
  <div className="page">
107
110
  <PageHeader
108
- title="Requests"
109
- description={`Every model call zeta made in ${meta.windowLabel}, newest first. Open a row for its full payload.`}
111
+ title={t("common.requests")}
112
+ description={t("requests.description", { window: t(meta.windowKey) })}
110
113
  />
111
114
 
112
115
  <QueryView query={log} skeleton={<Skeleton height={96} style={{ borderRadius: 12 }} />}>
@@ -114,38 +117,45 @@ export function RequestsRoute({ active, range, onRequestClick }: RequestsRoutePr
114
117
  <div data-stale={log.stale}>
115
118
  <StatGrid min={160}>
116
119
  <Stat
117
- label="Requests"
120
+ label={t("common.requests")}
118
121
  value={formatInteger(summary.requests)}
119
122
  hint={
120
123
  summary.oldest === null
121
- ? `none in ${meta.windowLabel}`
122
- : `since ${formatRelativeTime(summary.oldest)}`
124
+ ? t("requests.none-in-window", { window: t(meta.windowKey) })
125
+ : t("requests.since", { time: formatRelativeTime(summary.oldest) })
123
126
  }
124
127
  />
125
128
  <Stat
126
- label="Failed"
129
+ label={t("common.failed")}
127
130
  value={formatInteger(summary.failed)}
128
- hint={`${summary.requests > 0 ? formatPercent(summary.failed / summary.requests) : "–"} · ${formatInteger(summary.aborted)} aborted`}
131
+ hint={t("requests.failed-hint", {
132
+ rate: summary.requests > 0 ? formatErrorRate(summary.failed / summary.requests) : "–",
133
+ count: formatInteger(summary.aborted),
134
+ })}
129
135
  />
130
136
  <Stat
131
- label="Tokens"
132
- title="Total tokens across these requests"
137
+ label={t("common.tokens")}
138
+ title={t("requests.tokens-hint")}
133
139
  value={formatCompact(summary.tokens)}
134
140
  />
135
141
  <Stat
136
- label="API-equivalent cost"
137
- title="What these requests would cost at public API rates"
142
+ label={t("requests.api-equivalent-cost")}
143
+ title={t("requests.cost-hint")}
138
144
  value={formatEstimatedCost(summary.cost, summary.unpriced)}
139
- hint={summary.unpriced > 0 ? `${formatInteger(summary.unpriced)} unpriced` : undefined}
145
+ hint={
146
+ summary.unpriced > 0
147
+ ? t("requests.unpriced-n", { count: formatInteger(summary.unpriced) })
148
+ : undefined
149
+ }
140
150
  />
141
151
  <Stat
142
- label="Median duration"
152
+ label={t("requests.median-duration")}
143
153
  value={formatDurationMs(summary.medianDuration)}
144
- hint={`p95 ${formatDurationMs(summary.p95Duration)}`}
154
+ hint={t("requests.p95", { value: formatDurationMs(summary.p95Duration) })}
145
155
  />
146
156
  <Stat
147
- label="Median TTFT"
148
- title="Time to first token"
157
+ label={t("requests.median-ttft")}
158
+ title={t("requests.ttft-hint")}
149
159
  value={formatDurationMs(summary.medianTtft)}
150
160
  />
151
161
  </StatGrid>
@@ -155,20 +165,27 @@ export function RequestsRoute({ active, range, onRequestClick }: RequestsRoutePr
155
165
 
156
166
  <Card
157
167
  index={1}
158
- title="Request log"
168
+ title={t("requests.log-title")}
159
169
  description={
160
170
  log.data === null
161
- ? "Loading the latest requests…"
171
+ ? t("requests.log-loading")
162
172
  : complete
163
- ? `${formatInteger(filtered.length)} of ${formatInteger(summary.requests)} requests in ${meta.windowLabel}`
164
- : `${formatInteger(filtered.length)} of the latest ${formatInteger(summary.requests)} requests`
173
+ ? t("requests.log-count-in-window", {
174
+ shown: formatInteger(filtered.length),
175
+ total: formatInteger(summary.requests),
176
+ window: t(meta.windowKey),
177
+ })
178
+ : t("requests.log-count-latest", {
179
+ shown: formatInteger(filtered.length),
180
+ total: formatInteger(summary.requests),
181
+ })
165
182
  }
166
183
  actions={
167
184
  <>
168
- <SearchInput value={search} onChange={setSearch} placeholder="Model, provider or project" />
185
+ <SearchInput value={search} onChange={setSearch} placeholder={t("requests.search-placeholder")} />
169
186
  <Segmented
170
187
  size="sm"
171
- aria-label="Status"
188
+ aria-label={t("requests.status")}
172
189
  options={statusOptions}
173
190
  value={status}
174
191
  onChange={setStatus}
@@ -181,9 +198,11 @@ export function RequestsRoute({ active, range, onRequestClick }: RequestsRoutePr
181
198
  log.data !== null && !complete ? (
182
199
  <>
183
200
  <span>
184
- Showing the latest {formatInteger(view.loaded)} requests, back to{" "}
185
- {summary.oldest === null ? "–" : formatTimestamp(summary.oldest)}. Older requests in{" "}
186
- {meta.windowLabel} are not loaded.
201
+ {t("requests.footer", {
202
+ count: formatInteger(view.loaded),
203
+ time: summary.oldest === null ? "–" : formatTimestamp(summary.oldest),
204
+ window: t(meta.windowKey),
205
+ })}
187
206
  </span>
188
207
  {loadMore}
189
208
  </>
@@ -202,8 +221,12 @@ export function RequestsRoute({ active, range, onRequestClick }: RequestsRoutePr
202
221
  dense
203
222
  empty={
204
223
  <EmptyState
205
- title={summary.requests === 0 ? `No requests in ${meta.windowLabel}` : "No requests match"}
206
- hint={summary.requests === 0 ? undefined : "Clear the search or status filter."}
224
+ title={
225
+ summary.requests === 0
226
+ ? t("requests.empty-in-window", { window: t(meta.windowKey) })
227
+ : t("requests.empty-no-match")
228
+ }
229
+ hint={summary.requests === 0 ? undefined : t("requests.empty-no-match-hint")}
207
230
  />
208
231
  }
209
232
  />
@@ -217,13 +240,13 @@ export function RequestsRoute({ active, range, onRequestClick }: RequestsRoutePr
217
240
  const REQUEST_COLUMNS: readonly Column<MessageStats>[] = [
218
241
  {
219
242
  key: "model",
220
- header: "Model",
243
+ header: t("common.model"),
221
244
  sort: row => row.model,
222
245
  render: row => <LabelCell primary={<span className="mono">{row.model}</span>} secondary={row.provider} />,
223
246
  },
224
247
  {
225
248
  key: "time",
226
- header: "When",
249
+ header: t("requests.when"),
227
250
  sort: row => row.timestamp,
228
251
  render: row => (
229
252
  <span className="muted" title={formatTimestamp(row.timestamp)}>
@@ -233,7 +256,7 @@ const REQUEST_COLUMNS: readonly Column<MessageStats>[] = [
233
256
  },
234
257
  {
235
258
  key: "project",
236
- header: "Project",
259
+ header: t("common.project"),
237
260
  sort: row => row.folder,
238
261
  render: row => (
239
262
  <span className="mono muted truncate" title={row.folder} style={{ display: "block", maxWidth: 180 }}>
@@ -243,57 +266,57 @@ const REQUEST_COLUMNS: readonly Column<MessageStats>[] = [
243
266
  },
244
267
  {
245
268
  key: "input",
246
- header: "Input",
247
- title: "Uncached input tokens",
269
+ header: t("common.input"),
270
+ title: t("requests.uncached-input"),
248
271
  align: "right",
249
272
  sort: row => row.usage.input,
250
273
  render: row => <span className="num">{formatCompact(row.usage.input)}</span>,
251
274
  },
252
275
  {
253
276
  key: "cache",
254
- header: "Cache read",
255
- title: "Cache read tokens (hover a cell for cache writes)",
277
+ header: t("common.cache-read"),
278
+ title: t("requests.cache-read-title"),
256
279
  align: "right",
257
280
  sort: row => row.usage.cacheRead,
258
281
  render: row => (
259
- <span className="num" title={`Cache write: ${formatInteger(row.usage.cacheWrite)}`}>
282
+ <span className="num" title={t("requests.cache-write", { count: formatInteger(row.usage.cacheWrite) })}>
260
283
  {formatCompact(row.usage.cacheRead)}
261
284
  </span>
262
285
  ),
263
286
  },
264
287
  {
265
288
  key: "output",
266
- header: "Output",
289
+ header: t("common.output"),
267
290
  align: "right",
268
291
  sort: row => row.usage.output,
269
292
  render: row => <span className="num">{formatCompact(row.usage.output)}</span>,
270
293
  },
271
294
  {
272
295
  key: "cost",
273
- header: "Cost",
274
- title: "API-equivalent estimate",
296
+ header: t("common.cost"),
297
+ title: t("requests.cost-estimate"),
275
298
  align: "right",
276
299
  sort: row => row.usage.cost.total,
277
300
  render: row => <span className="num">{formatMessageCost(row, 4)}</span>,
278
301
  },
279
302
  {
280
303
  key: "duration",
281
- header: "Duration",
304
+ header: t("common.duration"),
282
305
  align: "right",
283
306
  sort: row => row.duration ?? -1,
284
307
  render: row => <span className="num">{formatDurationMs(row.duration)}</span>,
285
308
  },
286
309
  {
287
310
  key: "ttft",
288
- header: "TTFT",
289
- title: "Time to first token",
311
+ header: t("requests.ttft"),
312
+ title: t("requests.ttft-hint"),
290
313
  align: "right",
291
314
  sort: row => row.ttft ?? -1,
292
315
  render: row => <span className="num muted">{formatDurationMs(row.ttft)}</span>,
293
316
  },
294
317
  {
295
318
  key: "status",
296
- header: "Status",
319
+ header: t("requests.status"),
297
320
  sort: row => requestStatus(row),
298
321
  render: row => {
299
322
  const status = REQUEST_STATUS[requestStatus(row)];