@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.
- package/CHANGELOG.md +12 -5
- package/build.ts +4 -0
- package/dist/client/favicon.ico +0 -0
- package/dist/client/index.css +1 -1
- package/dist/client/index.html +1 -0
- package/dist/client/index.js +96 -96
- package/dist/types/aggregator.d.ts +4 -1
- package/dist/types/client/api.d.ts +5 -1
- package/dist/types/client/app/nav.d.ts +5 -2
- package/dist/types/client/data/formatters.d.ts +7 -2
- package/dist/types/client/data/live.d.ts +16 -11
- package/dist/types/client/data/query-store.d.ts +39 -0
- package/dist/types/client/data/query.d.ts +10 -5
- package/dist/types/client/data/range.d.ts +4 -3
- package/dist/types/client/i18n/format.d.ts +9 -0
- package/dist/types/client/i18n/index.d.ts +18 -0
- package/dist/types/client/i18n/messages/charts.d.ts +8 -0
- package/dist/types/client/i18n/messages/common.d.ts +153 -0
- package/dist/types/client/i18n/messages/costs.d.ts +92 -0
- package/dist/types/client/i18n/messages/en.d.ts +1255 -0
- package/dist/types/client/i18n/messages/errors.d.ts +78 -0
- package/dist/types/client/i18n/messages/frustration.d.ts +191 -0
- package/dist/types/client/i18n/messages/gain.d.ts +54 -0
- package/dist/types/client/i18n/messages/models.d.ts +94 -0
- package/dist/types/client/i18n/messages/nav.d.ts +46 -0
- package/dist/types/client/i18n/messages/overview.d.ts +80 -0
- package/dist/types/client/i18n/messages/projects.d.ts +84 -0
- package/dist/types/client/i18n/messages/providers.d.ts +142 -0
- package/dist/types/client/i18n/messages/requests.d.ts +72 -0
- package/dist/types/client/i18n/messages/tools.d.ts +119 -0
- package/dist/types/client/i18n/messages/traces.d.ts +144 -0
- package/dist/types/client/i18n/messages/ui.d.ts +44 -0
- package/dist/types/client/i18n/messages/zh-CN.d.ts +13 -0
- package/dist/types/client/i18n/types.d.ts +15 -0
- package/dist/types/client/traces/Minimap.d.ts +2 -0
- package/dist/types/client/traces/TimelineCanvas.d.ts +29 -8
- package/dist/types/client/traces/TranscriptList.d.ts +3 -1
- package/dist/types/db.d.ts +14 -21
- package/dist/types/embedded-client.d.ts +5 -0
- package/dist/types/frustration.d.ts +18 -9
- package/dist/types/live.d.ts +5 -3
- package/dist/types/locale.d.ts +16 -0
- package/dist/types/parser.d.ts +0 -17
- package/dist/types/rollup.d.ts +9 -1
- package/dist/types/server.d.ts +6 -0
- package/dist/types/shared-types.d.ts +7 -0
- package/dist/types/trace.d.ts +18 -6
- package/dist/types/types.d.ts +13 -1
- package/dist/types/user-metrics.d.ts +23 -1
- package/package.json +5 -5
- package/src/aggregator.ts +31 -11
- package/src/client/api.ts +40 -5
- package/src/client/app/LiveChip.tsx +14 -13
- package/src/client/app/Shell.tsx +19 -13
- package/src/client/app/ThemeToggle.tsx +4 -3
- package/src/client/app/nav.ts +19 -16
- package/src/client/data/formatters.ts +80 -13
- package/src/client/data/live.tsx +85 -45
- package/src/client/data/query-store.ts +108 -0
- package/src/client/data/query.ts +47 -56
- package/src/client/data/range.ts +34 -14
- package/src/client/data/series.ts +7 -1
- package/src/client/favicon.ico +0 -0
- package/src/client/i18n/format.ts +28 -0
- package/src/client/i18n/index.ts +53 -0
- package/src/client/i18n/messages/charts.ts +8 -0
- package/src/client/i18n/messages/common.ts +157 -0
- package/src/client/i18n/messages/costs.ts +104 -0
- package/src/client/i18n/messages/en.ts +40 -0
- package/src/client/i18n/messages/errors.ts +80 -0
- package/src/client/i18n/messages/frustration.ts +198 -0
- package/src/client/i18n/messages/gain.ts +54 -0
- package/src/client/i18n/messages/models.ts +94 -0
- package/src/client/i18n/messages/nav.ts +46 -0
- package/src/client/i18n/messages/overview.ts +90 -0
- package/src/client/i18n/messages/projects.ts +84 -0
- package/src/client/i18n/messages/providers.ts +165 -0
- package/src/client/i18n/messages/requests.ts +73 -0
- package/src/client/i18n/messages/tools.ts +120 -0
- package/src/client/i18n/messages/traces.ts +160 -0
- package/src/client/i18n/messages/ui.ts +46 -0
- package/src/client/i18n/messages/zh-CN.ts +43 -0
- package/src/client/i18n/types.ts +19 -0
- package/src/client/index.tsx +4 -0
- package/src/client/routes/CostsRoute.tsx +61 -48
- package/src/client/routes/ErrorsRoute.tsx +71 -47
- package/src/client/routes/FrustrationRoute.tsx +131 -116
- package/src/client/routes/GainRoute.tsx +42 -30
- package/src/client/routes/ModelsRoute.tsx +90 -73
- package/src/client/routes/OverviewRoute.tsx +109 -63
- package/src/client/routes/ProjectsRoute.tsx +73 -46
- package/src/client/routes/ProvidersRoute.tsx +151 -114
- package/src/client/routes/RequestsRoute.tsx +69 -46
- package/src/client/routes/ToolsRoute.tsx +108 -94
- package/src/client/routes/TracesRoute.tsx +25 -23
- package/src/client/styles.css +5 -2
- package/src/client/traces/AggregatesPanel.tsx +19 -8
- package/src/client/traces/Minimap.tsx +65 -27
- package/src/client/traces/SpanDrawer.tsx +46 -21
- package/src/client/traces/SummaryStrip.tsx +27 -11
- package/src/client/traces/TimelineCanvas.tsx +245 -116
- package/src/client/traces/TraceView.tsx +51 -36
- package/src/client/traces/TranscriptList.tsx +20 -11
- package/src/client/traces/traces.css +26 -0
- package/src/client/ui/JsonBlock.tsx +3 -2
- package/src/client/ui/Modal.tsx +3 -2
- package/src/client/ui/RequestDrawer.tsx +33 -30
- package/src/client/ui/SearchInput.tsx +2 -1
- package/src/client/ui/States.tsx +3 -2
- package/src/client/ui/Table.tsx +7 -3
- package/src/db.ts +150 -135
- package/src/embedded-client.ts +9 -0
- package/src/frustration.ts +9 -30
- package/src/index.ts +2 -1
- package/src/live.ts +36 -11
- package/src/locale.ts +29 -0
- package/src/parser.ts +37 -72
- package/src/port-conflict.ts +1 -1
- package/src/prompts/judge/frustration.json +19 -0
- package/src/rollup.ts +41 -20
- package/src/server.ts +67 -27
- package/src/shared-types.ts +7 -0
- package/src/trace.ts +54 -40
- package/src/types.ts +13 -1
- package/src/user-metrics.ts +220 -6
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { formatDurationMs, formatInteger } from "../data/formatters";
|
|
7
|
+
import { t } from "../i18n";
|
|
7
8
|
import type { TraceToolStat } from "../types";
|
|
8
9
|
import { Badge, Card, type Column, MeterCell, Table } from "../ui";
|
|
9
10
|
|
|
@@ -16,21 +17,26 @@ export function AggregatesPanel({ toolStats, index }: AggregatesPanelProps) {
|
|
|
16
17
|
if (toolStats.length === 0) return null;
|
|
17
18
|
const maxTotal = Math.max(...toolStats.map(stat => stat.totalMs));
|
|
18
19
|
const columns: Column<TraceToolStat>[] = [
|
|
19
|
-
{
|
|
20
|
+
{
|
|
21
|
+
key: "tool",
|
|
22
|
+
header: t("trace.tool"),
|
|
23
|
+
render: row => <span className="mono">{row.tool}</span>,
|
|
24
|
+
sort: row => row.tool,
|
|
25
|
+
},
|
|
20
26
|
{
|
|
21
27
|
key: "calls",
|
|
22
|
-
header: "
|
|
28
|
+
header: t("trace.calls"),
|
|
23
29
|
align: "right",
|
|
24
30
|
render: row => <span className="num">{formatInteger(row.calls)}</span>,
|
|
25
31
|
sort: row => row.calls,
|
|
26
32
|
},
|
|
27
33
|
{
|
|
28
34
|
key: "errors",
|
|
29
|
-
header: "
|
|
35
|
+
header: t("trace.errors"),
|
|
30
36
|
align: "right",
|
|
31
37
|
render: row =>
|
|
32
38
|
row.errors > 0 ? (
|
|
33
|
-
<Badge tone="bad">{formatInteger(row.errors)}
|
|
39
|
+
<Badge tone="bad">{t("trace.failed-n", { count: formatInteger(row.errors) })}</Badge>
|
|
34
40
|
) : (
|
|
35
41
|
<span className="num dim">0</span>
|
|
36
42
|
),
|
|
@@ -38,7 +44,7 @@ export function AggregatesPanel({ toolStats, index }: AggregatesPanelProps) {
|
|
|
38
44
|
},
|
|
39
45
|
{
|
|
40
46
|
key: "total",
|
|
41
|
-
header: "
|
|
47
|
+
header: t("common.total"),
|
|
42
48
|
align: "right",
|
|
43
49
|
width: 200,
|
|
44
50
|
render: row => (
|
|
@@ -48,21 +54,26 @@ export function AggregatesPanel({ toolStats, index }: AggregatesPanelProps) {
|
|
|
48
54
|
},
|
|
49
55
|
{
|
|
50
56
|
key: "avg",
|
|
51
|
-
header: "
|
|
57
|
+
header: t("trace.avg"),
|
|
52
58
|
align: "right",
|
|
53
59
|
render: row => <span className="num">{formatDurationMs(row.calls > 0 ? row.totalMs / row.calls : 0)}</span>,
|
|
54
60
|
sort: row => (row.calls > 0 ? row.totalMs / row.calls : 0),
|
|
55
61
|
},
|
|
56
62
|
{
|
|
57
63
|
key: "max",
|
|
58
|
-
header: "
|
|
64
|
+
header: t("trace.max"),
|
|
59
65
|
align: "right",
|
|
60
66
|
render: row => <span className="num">{formatDurationMs(row.maxMs)}</span>,
|
|
61
67
|
sort: row => row.maxMs,
|
|
62
68
|
},
|
|
63
69
|
];
|
|
64
70
|
return (
|
|
65
|
-
<Card
|
|
71
|
+
<Card
|
|
72
|
+
title={t("trace.tool-aggregates")}
|
|
73
|
+
description={t("trace.tool-aggregates-desc", { count: toolStats.length })}
|
|
74
|
+
flush
|
|
75
|
+
index={index}
|
|
76
|
+
>
|
|
66
77
|
<Table columns={columns} rows={toolStats} rowKey={row => row.tool} dense limit={12} />
|
|
67
78
|
</Card>
|
|
68
79
|
);
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Timeline overview strip: per-category density over the full virtual domain
|
|
3
3
|
* plus a draggable/resizable viewport brush, mirroring DevTools' overview.
|
|
4
|
+
* The density layer is painted once per trace/scale/size/theme into an
|
|
5
|
+
* offscreen canvas; pan and zoom only blit it and redraw the brush.
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
8
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
9
|
+
import { t } from "../i18n";
|
|
7
10
|
import type { TraceSpanKind, TraceTrack } from "../types";
|
|
8
11
|
import type { TimelineViewport } from "./TimelineCanvas";
|
|
9
12
|
import type { TraceScale } from "./time-scale";
|
|
10
|
-
import { useTraceTheme } from "./trace-colors";
|
|
13
|
+
import { type TraceTheme, useTraceTheme } from "./trace-colors";
|
|
11
14
|
|
|
12
15
|
export interface MinimapProps {
|
|
13
16
|
tracks: TraceTrack[];
|
|
@@ -23,6 +26,59 @@ const MIN_WINDOW_U = 10;
|
|
|
23
26
|
|
|
24
27
|
type DragMode = "move" | "left" | "right" | "create";
|
|
25
28
|
|
|
29
|
+
/** Paint the density strips (bucketed span coverage per pixel per category) at device resolution. */
|
|
30
|
+
function paintDensity(
|
|
31
|
+
tracks: TraceTrack[],
|
|
32
|
+
scale: TraceScale,
|
|
33
|
+
width: number,
|
|
34
|
+
colors: TraceTheme,
|
|
35
|
+
): OffscreenCanvas | HTMLCanvasElement {
|
|
36
|
+
const dpr = devicePixelRatio;
|
|
37
|
+
const pixelWidth = Math.floor(width * dpr);
|
|
38
|
+
const pixelHeight = Math.floor(HEIGHT * dpr);
|
|
39
|
+
let layer: OffscreenCanvas | HTMLCanvasElement;
|
|
40
|
+
let ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D | null;
|
|
41
|
+
if (typeof OffscreenCanvas === "undefined") {
|
|
42
|
+
const canvas = document.createElement("canvas");
|
|
43
|
+
canvas.width = pixelWidth;
|
|
44
|
+
canvas.height = pixelHeight;
|
|
45
|
+
layer = canvas;
|
|
46
|
+
ctx = canvas.getContext("2d");
|
|
47
|
+
} else {
|
|
48
|
+
const canvas = new OffscreenCanvas(pixelWidth, pixelHeight);
|
|
49
|
+
layer = canvas;
|
|
50
|
+
ctx = canvas.getContext("2d");
|
|
51
|
+
}
|
|
52
|
+
if (!ctx) return layer;
|
|
53
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
54
|
+
|
|
55
|
+
const [d0, d1] = scale.domain;
|
|
56
|
+
const dSpan = Math.max(d1 - d0, 1e-9);
|
|
57
|
+
const buckets = STRIP_KINDS.map(() => new Float32Array(width));
|
|
58
|
+
for (const track of tracks) {
|
|
59
|
+
for (const span of track.spans) {
|
|
60
|
+
const k = STRIP_KINDS.indexOf(span.kind);
|
|
61
|
+
if (k === -1) continue;
|
|
62
|
+
const strip = buckets[k];
|
|
63
|
+
const x0 = Math.max(0, Math.floor(((scale.toU(span.start) - d0) / dSpan) * width));
|
|
64
|
+
const x1 = Math.min(width - 1, Math.ceil(((scale.toU(span.end) - d0) / dSpan) * width));
|
|
65
|
+
for (let x = x0; x <= x1; x++) strip[x] = Math.min(1, strip[x] + 0.34);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const stripH = HEIGHT / STRIP_KINDS.length;
|
|
69
|
+
for (let k = 0; k < STRIP_KINDS.length; k++) {
|
|
70
|
+
const strip = buckets[k];
|
|
71
|
+
ctx.fillStyle = colors.category[STRIP_KINDS[k]];
|
|
72
|
+
for (let x = 0; x < width; x++) {
|
|
73
|
+
if (strip[x] <= 0) continue;
|
|
74
|
+
ctx.globalAlpha = 0.25 + strip[x] * 0.75;
|
|
75
|
+
ctx.fillRect(x, k * stripH + 1, 1, stripH - 2);
|
|
76
|
+
}
|
|
77
|
+
ctx.globalAlpha = 1;
|
|
78
|
+
}
|
|
79
|
+
return layer;
|
|
80
|
+
}
|
|
81
|
+
|
|
26
82
|
export function Minimap({ tracks, scale, viewport, onViewportChange }: MinimapProps) {
|
|
27
83
|
const colors = useTraceTheme();
|
|
28
84
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
@@ -44,36 +100,18 @@ export function Minimap({ tracks, scale, viewport, onViewportChange }: MinimapPr
|
|
|
44
100
|
const toX = useCallback((u: number) => ((u - d0) / dSpan) * width, [d0, dSpan, width]);
|
|
45
101
|
const toU = useCallback((x: number) => d0 + (x / Math.max(width, 1)) * dSpan, [d0, dSpan, width]);
|
|
46
102
|
|
|
103
|
+
const density = useMemo(() => paintDensity(tracks, scale, width, colors), [tracks, scale, width, colors]);
|
|
104
|
+
|
|
47
105
|
useEffect(() => {
|
|
48
106
|
const canvas = canvasRef.current;
|
|
49
107
|
if (!canvas) return;
|
|
50
108
|
const ctx = canvas.getContext("2d");
|
|
51
109
|
if (!ctx) return;
|
|
52
110
|
const dpr = devicePixelRatio;
|
|
111
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
112
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
113
|
+
ctx.drawImage(density, 0, 0);
|
|
53
114
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
54
|
-
ctx.clearRect(0, 0, width, HEIGHT);
|
|
55
|
-
|
|
56
|
-
// Density strips: bucket span coverage per pixel per category.
|
|
57
|
-
const stripH = HEIGHT / STRIP_KINDS.length;
|
|
58
|
-
for (let k = 0; k < STRIP_KINDS.length; k++) {
|
|
59
|
-
const kind = STRIP_KINDS[k];
|
|
60
|
-
const buckets = new Float32Array(width);
|
|
61
|
-
for (const track of tracks) {
|
|
62
|
-
for (const span of track.spans) {
|
|
63
|
-
if (span.kind !== kind) continue;
|
|
64
|
-
const x0 = Math.max(0, Math.floor(toX(scale.toU(span.start))));
|
|
65
|
-
const x1 = Math.min(width - 1, Math.ceil(toX(scale.toU(span.end))));
|
|
66
|
-
for (let x = x0; x <= x1; x++) buckets[x] = Math.min(1, buckets[x] + 0.34);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
ctx.fillStyle = colors.category[kind];
|
|
70
|
-
for (let x = 0; x < width; x++) {
|
|
71
|
-
if (buckets[x] <= 0) continue;
|
|
72
|
-
ctx.globalAlpha = 0.25 + buckets[x] * 0.75;
|
|
73
|
-
ctx.fillRect(x, k * stripH + 1, 1, stripH - 2);
|
|
74
|
-
}
|
|
75
|
-
ctx.globalAlpha = 1;
|
|
76
|
-
}
|
|
77
115
|
|
|
78
116
|
// Viewport brush.
|
|
79
117
|
const vx0 = toX(viewport.u0);
|
|
@@ -85,7 +123,7 @@ export function Minimap({ tracks, scale, viewport, onViewportChange }: MinimapPr
|
|
|
85
123
|
ctx.fillStyle = colors.selection;
|
|
86
124
|
ctx.fillRect(vx0, 0, 2, HEIGHT);
|
|
87
125
|
ctx.fillRect(vx1 - 2, 0, 2, HEIGHT);
|
|
88
|
-
}, [
|
|
126
|
+
}, [density, viewport, colors, toX]);
|
|
89
127
|
|
|
90
128
|
const handlePointerDown = (event: React.PointerEvent<HTMLCanvasElement>) => {
|
|
91
129
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
@@ -146,7 +184,7 @@ export function Minimap({ tracks, scale, viewport, onViewportChange }: MinimapPr
|
|
|
146
184
|
className="traces-minimap"
|
|
147
185
|
style={{ height: HEIGHT }}
|
|
148
186
|
role="slider"
|
|
149
|
-
aria-label="
|
|
187
|
+
aria-label={t("trace.minimap-aria")}
|
|
150
188
|
aria-valuemin={0}
|
|
151
189
|
aria-valuemax={100}
|
|
152
190
|
aria-valuenow={Math.round(((viewport.u0 - d0) / dSpan) * 100)}
|
|
@@ -8,6 +8,7 @@ import { Check, Copy } from "lucide-react";
|
|
|
8
8
|
import { type ReactNode, useEffect, useRef, useState } from "react";
|
|
9
9
|
import { getSessionEntryDetail } from "../api";
|
|
10
10
|
import { formatDurationMs, formatInteger } from "../data/formatters";
|
|
11
|
+
import { t } from "../i18n";
|
|
11
12
|
import type { TraceSpan, TraceTrack } from "../types";
|
|
12
13
|
import { Badge, Drawer, ErrorState, JsonBlock, KeyValues, Skeleton } from "../ui";
|
|
13
14
|
|
|
@@ -112,32 +113,44 @@ export function SpanDrawer({ span, track, onClose, onOpenChildTrack }: SpanDrawe
|
|
|
112
113
|
const metrics: Array<{ key: string; label: ReactNode; value: ReactNode }> = [
|
|
113
114
|
{
|
|
114
115
|
key: "duration",
|
|
115
|
-
label: "
|
|
116
|
+
label: t("common.duration"),
|
|
116
117
|
value: (
|
|
117
118
|
<span className="num">
|
|
118
119
|
{formatDurationMs(span.end - span.start)}
|
|
119
|
-
{span.unterminated && <span className="muted"> · unterminated</span>}
|
|
120
|
+
{span.unterminated && <span className="muted"> · {t("trace.unterminated")}</span>}
|
|
120
121
|
</span>
|
|
121
122
|
),
|
|
122
123
|
},
|
|
123
|
-
{
|
|
124
|
-
|
|
124
|
+
{
|
|
125
|
+
key: "start",
|
|
126
|
+
label: t("trace.start"),
|
|
127
|
+
value: <span className="num">{new Date(span.start).toLocaleTimeString()}</span>,
|
|
128
|
+
},
|
|
129
|
+
{ key: "track", label: t("trace.track"), value: <span className="mono">{track?.label ?? "-"}</span> },
|
|
125
130
|
];
|
|
126
131
|
if (span.kind === "model") {
|
|
127
132
|
metrics.push(
|
|
128
133
|
{
|
|
129
134
|
key: "tokens",
|
|
130
|
-
label: "
|
|
135
|
+
label: t("common.tokens"),
|
|
131
136
|
value: <span className="num">{formatInteger(span.tokens ?? 0)}</span>,
|
|
132
137
|
},
|
|
133
|
-
{ key: "cost", label: "
|
|
134
|
-
{
|
|
135
|
-
|
|
138
|
+
{ key: "cost", label: t("common.cost"), value: <span className="num">${(span.cost ?? 0).toFixed(4)}</span> },
|
|
139
|
+
{
|
|
140
|
+
key: "ttft",
|
|
141
|
+
label: t("trace.ttft"),
|
|
142
|
+
value: <span className="num">{formatDurationMs(span.ttft ?? null)}</span>,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
key: "model",
|
|
146
|
+
label: t("common.model"),
|
|
147
|
+
value: <span className="mono">{msg?.model ?? span.model ?? "-"}</span>,
|
|
148
|
+
},
|
|
136
149
|
);
|
|
137
150
|
if (usage)
|
|
138
151
|
metrics.push({
|
|
139
152
|
key: "split",
|
|
140
|
-
label: "
|
|
153
|
+
label: t("trace.io-cached"),
|
|
141
154
|
value: (
|
|
142
155
|
<span className="num">
|
|
143
156
|
{formatInteger(usage.input ?? 0)} / {formatInteger(usage.output ?? 0)} /{" "}
|
|
@@ -146,9 +159,17 @@ export function SpanDrawer({ span, track, onClose, onOpenChildTrack }: SpanDrawe
|
|
|
146
159
|
),
|
|
147
160
|
});
|
|
148
161
|
if (msg?.provider)
|
|
149
|
-
metrics.push({
|
|
162
|
+
metrics.push({
|
|
163
|
+
key: "provider",
|
|
164
|
+
label: t("common.provider"),
|
|
165
|
+
value: <span className="mono">{msg.provider}</span>,
|
|
166
|
+
});
|
|
150
167
|
if (msg?.stopReason)
|
|
151
|
-
metrics.push({
|
|
168
|
+
metrics.push({
|
|
169
|
+
key: "stop",
|
|
170
|
+
label: t("trace.stop-reason"),
|
|
171
|
+
value: <span className="mono">{msg.stopReason}</span>,
|
|
172
|
+
});
|
|
152
173
|
}
|
|
153
174
|
|
|
154
175
|
return (
|
|
@@ -159,7 +180,7 @@ export function SpanDrawer({ span, track, onClose, onOpenChildTrack }: SpanDrawe
|
|
|
159
180
|
subtitle={
|
|
160
181
|
<span className="row" style={{ gap: 6 }}>
|
|
161
182
|
<Badge tone={span.isError ? "bad" : "neutral"}>
|
|
162
|
-
{span.isError ? `${span.kind} · error` : span.kind}
|
|
183
|
+
{span.isError ? `${span.kind} · ${t("common.error")}` : span.kind}
|
|
163
184
|
</Badge>
|
|
164
185
|
</span>
|
|
165
186
|
}
|
|
@@ -170,8 +191,8 @@ export function SpanDrawer({ span, track, onClose, onOpenChildTrack }: SpanDrawe
|
|
|
170
191
|
data-variant="ghost"
|
|
171
192
|
data-icon="true"
|
|
172
193
|
onClick={handleCopy}
|
|
173
|
-
aria-label="
|
|
174
|
-
title="
|
|
194
|
+
aria-label={t("trace.copy-json")}
|
|
195
|
+
title={t("trace.copy-json")}
|
|
175
196
|
>
|
|
176
197
|
{copied ? <Check size={15} /> : <Copy size={15} />}
|
|
177
198
|
</button>
|
|
@@ -182,7 +203,7 @@ export function SpanDrawer({ span, track, onClose, onOpenChildTrack }: SpanDrawe
|
|
|
182
203
|
|
|
183
204
|
{msg?.errorMessage && (
|
|
184
205
|
<div className="stack" style={{ gap: 6 }}>
|
|
185
|
-
<div className="section-label tone-bad">
|
|
206
|
+
<div className="section-label tone-bad">{t("trace.error-message")}</div>
|
|
186
207
|
<pre className="code-block traces-pre">{msg.errorMessage}</pre>
|
|
187
208
|
</div>
|
|
188
209
|
)}
|
|
@@ -191,7 +212,7 @@ export function SpanDrawer({ span, track, onClose, onOpenChildTrack }: SpanDrawe
|
|
|
191
212
|
<div className="stack" style={{ gap: 8 }}>
|
|
192
213
|
{span.detail && (
|
|
193
214
|
<>
|
|
194
|
-
<div className="section-label">
|
|
215
|
+
<div className="section-label">{t("trace.task")}</div>
|
|
195
216
|
<pre className="code-block traces-pre">{span.detail}</pre>
|
|
196
217
|
</>
|
|
197
218
|
)}
|
|
@@ -203,7 +224,7 @@ export function SpanDrawer({ span, track, onClose, onOpenChildTrack }: SpanDrawe
|
|
|
203
224
|
data-size="sm"
|
|
204
225
|
onClick={() => onOpenChildTrack(span.childTrackId ?? "")}
|
|
205
226
|
>
|
|
206
|
-
|
|
227
|
+
{t("trace.open-child-track")} <span className="mono">{span.childTrackId}</span>
|
|
207
228
|
</button>
|
|
208
229
|
</div>
|
|
209
230
|
)}
|
|
@@ -212,7 +233,7 @@ export function SpanDrawer({ span, track, onClose, onOpenChildTrack }: SpanDrawe
|
|
|
212
233
|
|
|
213
234
|
{span.kind === "tool" && span.detail && (
|
|
214
235
|
<div className="stack" style={{ gap: 6 }}>
|
|
215
|
-
<div className="section-label">
|
|
236
|
+
<div className="section-label">{t("trace.args")}</div>
|
|
216
237
|
<pre className="code-block traces-pre">{span.detail}</pre>
|
|
217
238
|
</div>
|
|
218
239
|
)}
|
|
@@ -223,20 +244,24 @@ export function SpanDrawer({ span, track, onClose, onOpenChildTrack }: SpanDrawe
|
|
|
223
244
|
<Skeleton height={160} />
|
|
224
245
|
</div>
|
|
225
246
|
)}
|
|
226
|
-
{error && <ErrorState error={
|
|
247
|
+
{error && <ErrorState error={t("trace.load-failed", { message: error.message })} />}
|
|
227
248
|
|
|
228
249
|
{!loading &&
|
|
229
250
|
entry !== null &&
|
|
230
251
|
textBlocks(msg?.content).map((block, index) => (
|
|
231
252
|
<div key={`${block.kind}-${index}`} className="stack" style={{ gap: 6 }}>
|
|
232
|
-
<div className="section-label">
|
|
253
|
+
<div className="section-label">
|
|
254
|
+
{block.kind === "thinking" ? t("trace.thinking") : t("trace.text")}
|
|
255
|
+
</div>
|
|
233
256
|
<pre className="code-block traces-pre" data-scroll="true">
|
|
234
257
|
{block.text}
|
|
235
258
|
</pre>
|
|
236
259
|
</div>
|
|
237
260
|
))}
|
|
238
261
|
|
|
239
|
-
{!loading && entry !== null &&
|
|
262
|
+
{!loading && entry !== null && (
|
|
263
|
+
<JsonBlock data={entry} title={t("trace.raw-entry")} initialCollapsed={true} />
|
|
264
|
+
)}
|
|
240
265
|
</div>
|
|
241
266
|
</Drawer>
|
|
242
267
|
);
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { formatCompact, formatElapsed, formatEstimatedCost, formatInteger, formatPercent } from "../data/formatters";
|
|
7
|
+
import { t } from "../i18n";
|
|
7
8
|
import type { TraceSummary } from "../types";
|
|
8
9
|
import { Stat, StatGrid } from "../ui";
|
|
9
10
|
|
|
@@ -12,23 +13,38 @@ export interface SummaryStripProps {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export function SummaryStrip({ summary }: SummaryStripProps) {
|
|
15
|
-
const share = (ms: number) =>
|
|
16
|
+
const share = (ms: number) =>
|
|
17
|
+
summary.wallMs > 0 ? t("trace.of-wall", { percent: formatPercent(ms / summary.wallMs) }) : undefined;
|
|
16
18
|
return (
|
|
17
19
|
<StatGrid min={112}>
|
|
18
|
-
<Stat size="sm" label="
|
|
19
|
-
<Stat size="sm" label="Model time" value={formatElapsed(summary.modelMs)} hint={share(summary.modelMs)} />
|
|
20
|
-
<Stat size="sm" label="Tool time" value={formatElapsed(summary.toolMs)} hint={share(summary.toolMs)} />
|
|
21
|
-
<Stat size="sm" label="Idle" value={formatElapsed(summary.idleMs)} hint={share(summary.idleMs)} />
|
|
22
|
-
<Stat size="sm" label="Turns" value={formatInteger(summary.turns)} />
|
|
20
|
+
<Stat size="sm" label={t("trace.wall-time")} value={formatElapsed(summary.wallMs)} />
|
|
23
21
|
<Stat
|
|
24
22
|
size="sm"
|
|
25
|
-
label="
|
|
23
|
+
label={t("trace.model-time")}
|
|
24
|
+
value={formatElapsed(summary.modelMs)}
|
|
25
|
+
hint={share(summary.modelMs)}
|
|
26
|
+
/>
|
|
27
|
+
<Stat
|
|
28
|
+
size="sm"
|
|
29
|
+
label={t("trace.tool-time")}
|
|
30
|
+
value={formatElapsed(summary.toolMs)}
|
|
31
|
+
hint={share(summary.toolMs)}
|
|
32
|
+
/>
|
|
33
|
+
<Stat size="sm" label={t("trace.idle")} value={formatElapsed(summary.idleMs)} hint={share(summary.idleMs)} />
|
|
34
|
+
<Stat size="sm" label={t("trace.turns")} value={formatInteger(summary.turns)} />
|
|
35
|
+
<Stat
|
|
36
|
+
size="sm"
|
|
37
|
+
label={t("common.requests")}
|
|
26
38
|
value={formatInteger(summary.requests)}
|
|
27
|
-
hint={
|
|
39
|
+
hint={t("trace.tool-calls-n", { count: formatInteger(summary.toolCalls) })}
|
|
40
|
+
/>
|
|
41
|
+
<Stat size="sm" label={t("trace.agents")} value={formatInteger(summary.subagents)} />
|
|
42
|
+
<Stat size="sm" label={t("common.tokens")} value={formatCompact(summary.totalTokens)} />
|
|
43
|
+
<Stat
|
|
44
|
+
size="sm"
|
|
45
|
+
label={t("common.cost")}
|
|
46
|
+
value={formatEstimatedCost(summary.costTotal, summary.unpricedRequests)}
|
|
28
47
|
/>
|
|
29
|
-
<Stat size="sm" label="Agents" value={formatInteger(summary.subagents)} />
|
|
30
|
-
<Stat size="sm" label="Tokens" value={formatCompact(summary.totalTokens)} />
|
|
31
|
-
<Stat size="sm" label="Cost" value={formatEstimatedCost(summary.costTotal, summary.unpricedRequests)} />
|
|
32
48
|
</StatGrid>
|
|
33
49
|
);
|
|
34
50
|
}
|