@theoremjs/react 0.3.4-next.871.gea9bbcc → 0.3.4
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/README.md +2 -0
- package/package.json +3 -3
- package/ui/RadialCharts.d.ts +30 -0
- package/ui/RadialCharts.js +69 -0
- package/ui/RankedBars.d.ts +31 -0
- package/ui/RankedBars.js +43 -0
- package/ui/TraceCharts.js +17 -31
- package/ui/TraceOverview.d.ts +6 -0
- package/ui/TraceOverview.js +8 -7
- package/ui/index.d.ts +4 -0
- package/ui/index.js +4 -0
package/README.md
CHANGED
|
@@ -153,6 +153,8 @@ The profile, the keys, and the system prompt stay on the server. The browser rec
|
|
|
153
153
|
| `@theoremjs/react/ui/icons` | The icon set of the theme |
|
|
154
154
|
| `@theoremjs/react/client` | `@theoremjs/web/client`: the client logic, without the hooks |
|
|
155
155
|
|
|
156
|
+
To draw your own figures like the trace panel does, `@theoremjs/react/ui` also exports its chart parts. `ChartCard` holds a chart under its name and one figure. `ChartStat` is one figure in a card. `RankedBars` draws one bar per `RankedBar`, in the order given, with a `TraceFill` for each. `RadialBars` draws the same rows as rings. `Sunburst` draws `SunburstNode` parts as rings, each child outside its parent.
|
|
157
|
+
|
|
156
158
|
## Chat
|
|
157
159
|
|
|
158
160
|
`<TheoremChat />` accepts these props. All props are optional.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theoremjs/react",
|
|
3
|
-
"version": "0.3.4
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "React bindings for Theorem: headless hooks + transport, an Astryx chat UI, and a Web-standard server handler.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"@stylexjs/stylex": "0.19.0",
|
|
61
61
|
"react": "^19.0.0",
|
|
62
62
|
"react-dom": "^19.0.0",
|
|
63
|
-
"@theoremjs/agents": "^0.3.4
|
|
64
|
-
"@theoremjs/web": "^0.3.4
|
|
63
|
+
"@theoremjs/agents": "^0.3.4",
|
|
64
|
+
"@theoremjs/web": "^0.3.4"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@astryxdesign/core": {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type RankedBar } from './RankedBars.tsx';
|
|
2
|
+
import { type TraceFill } from './trace-patterns.tsx';
|
|
3
|
+
/**
|
|
4
|
+
* One ring per row, the first outermost, each drawn round its track as far as its value goes, with
|
|
5
|
+
* a key beside it in the same order. `max` fixes the scale, for figures such as rates that share one.
|
|
6
|
+
*/
|
|
7
|
+
export declare function RadialBars({ rows, format, max, }: {
|
|
8
|
+
rows: readonly RankedBar[];
|
|
9
|
+
format: (value: number) => string;
|
|
10
|
+
max?: number;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
12
|
+
/** One part of a {@link Sunburst}: a leaf has a value, a branch the sum of its children's. */
|
|
13
|
+
export interface SunburstNode {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
value?: number;
|
|
17
|
+
/** Default: the trace's solid fills in turn, a child taking its parent's. */
|
|
18
|
+
fill?: TraceFill;
|
|
19
|
+
/** A line under the value in the part's hover card. */
|
|
20
|
+
detail?: string;
|
|
21
|
+
children?: SunburstNode[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Rings of parts, the inner ring the top parts and each ring further out their children, each part
|
|
25
|
+
* as wide as its share, with the top parts keyed beside it. Every part is named on hover.
|
|
26
|
+
*/
|
|
27
|
+
export declare function Sunburst({ parts, format, }: {
|
|
28
|
+
parts: readonly SunburstNode[];
|
|
29
|
+
format: (value: number) => string;
|
|
30
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { HStack } from '@astryxdesign/core/HStack';
|
|
3
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
4
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
5
|
+
import { Cell, PolarAngleAxis, RadialBar, RadialBarChart, SunburstChart, Tooltip, } from 'recharts';
|
|
6
|
+
import { paintOf, RankedTip } from "./RankedBars.js";
|
|
7
|
+
import { ChartFrame } from "./TraceOverview.js";
|
|
8
|
+
import { TRACE_GAP, traceFill } from "./trace-patterns.js";
|
|
9
|
+
/** Width and height of a radial chart, in pixels. */
|
|
10
|
+
const RADIAL_PX = 180;
|
|
11
|
+
/** The solid fills in order, for parts that name none. */
|
|
12
|
+
const RAMP = ['clear', 'shade1', 'shade2', 'shade3'];
|
|
13
|
+
function fillAt(fill, index) {
|
|
14
|
+
return fill ?? RAMP[index % RAMP.length] ?? 'clear';
|
|
15
|
+
}
|
|
16
|
+
/** A key's mark: a small rounded square in any paint. */
|
|
17
|
+
function Swatch({ paint }) {
|
|
18
|
+
return (_jsx("svg", { "aria-hidden": true, width: 10, height: 10, style: { display: 'block', flexShrink: 0 }, children: _jsx("rect", { width: 10, height: 10, rx: 2, fill: paint }) }));
|
|
19
|
+
}
|
|
20
|
+
/** Each row's mark, name and value, in the order given. */
|
|
21
|
+
function RadialKey({ rows, format, }) {
|
|
22
|
+
return (_jsx(VStack, { gap: 1, children: rows.map((row) => (_jsxs(HStack, { gap: 2, align: "center", justify: "between", children: [_jsxs(HStack, { gap: 2, align: "center", children: [_jsx(Swatch, { paint: row.paint }), _jsx(Text, { type: "supporting", color: "secondary", children: row.name })] }), _jsx(Text, { type: "supporting", hasTabularNumbers: true, children: format(row.value) })] }, row.id))) }));
|
|
23
|
+
}
|
|
24
|
+
/** A radial chart, centred, beside its key. */
|
|
25
|
+
function RadialFrame({ chart, keyed }) {
|
|
26
|
+
return (_jsxs(HStack, { gap: 4, align: "center", wrap: "wrap", children: [_jsx(ChartFrame, { children: chart }), _jsx("div", { style: { flex: '1 1 140px', minWidth: 0 }, children: keyed })] }));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* One ring per row, the first outermost, each drawn round its track as far as its value goes, with
|
|
30
|
+
* a key beside it in the same order. `max` fixes the scale, for figures such as rates that share one.
|
|
31
|
+
*/
|
|
32
|
+
export function RadialBars({ rows, format, max, }) {
|
|
33
|
+
const painted = rows.map((row, index) => ({
|
|
34
|
+
...row,
|
|
35
|
+
paint: paintOf(row, fillAt(row.fill, index)),
|
|
36
|
+
}));
|
|
37
|
+
const rings = [...painted].reverse();
|
|
38
|
+
return (_jsx(RadialFrame, { chart: _jsxs(RadialBarChart, { width: RADIAL_PX, height: RADIAL_PX, data: rings, innerRadius: "22%", outerRadius: "100%", startAngle: 90, endAngle: -270, barCategoryGap: 1, accessibilityLayer: false, children: [_jsx(PolarAngleAxis, { type: "number", domain: [0, max ?? 'auto'], tick: false, axisLine: false }), _jsx(RadialBar, { dataKey: "value", background: { fill: 'var(--color-background-gray)' }, cornerRadius: 4, stroke: TRACE_GAP, isAnimationActive: false, children: rings.map((row) => (_jsx(Cell, { fill: row.paint }, row.id))) }), _jsx(Tooltip, { content: (props) => _jsx(RankedTip, { ...props, format: format }), cursor: false, isAnimationActive: false })] }), keyed: _jsx(RadialKey, { rows: painted, format: format }) }));
|
|
39
|
+
}
|
|
40
|
+
function total(nodes) {
|
|
41
|
+
return nodes.reduce((sum, node) => sum + (node.value ?? 0), 0);
|
|
42
|
+
}
|
|
43
|
+
function sunburstPart(node, fill) {
|
|
44
|
+
const children = node.children ? sunburstData(node.children, fill) : undefined;
|
|
45
|
+
return {
|
|
46
|
+
id: node.id,
|
|
47
|
+
name: node.name,
|
|
48
|
+
value: node.value ?? total(children ?? []),
|
|
49
|
+
fill: traceFill(fill),
|
|
50
|
+
detail: node.detail,
|
|
51
|
+
children,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function sunburstData(nodes, inherited) {
|
|
55
|
+
return nodes.map((node, index) => sunburstPart(node, node.fill ?? inherited ?? fillAt(undefined, index)));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Rings of parts, the inner ring the top parts and each ring further out their children, each part
|
|
59
|
+
* as wide as its share, with the top parts keyed beside it. Every part is named on hover.
|
|
60
|
+
*/
|
|
61
|
+
export function Sunburst({ parts, format, }) {
|
|
62
|
+
const top = sunburstData(parts);
|
|
63
|
+
return (_jsx(RadialFrame, { chart: _jsx(SunburstChart, { width: RADIAL_PX, height: RADIAL_PX, data: { name: '', value: total(top), children: top }, innerRadius: RADIAL_PX * 0.11, ringPadding: 2, stroke: TRACE_GAP, textOptions: { fill: 'transparent' }, children: _jsx(Tooltip, { content: (props) => _jsx(RankedTip, { ...props, format: format }), isAnimationActive: false }) }), keyed: _jsx(RadialKey, { rows: top.map((part) => ({
|
|
64
|
+
id: String(part.id),
|
|
65
|
+
name: part.name,
|
|
66
|
+
value: part.value ?? 0,
|
|
67
|
+
paint: String(part.fill),
|
|
68
|
+
})), format: format }) }));
|
|
69
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type TooltipContentProps } from 'recharts';
|
|
2
|
+
import { type TraceFill } from './trace-patterns.tsx';
|
|
3
|
+
/** One bar of {@link RankedBars}. */
|
|
4
|
+
export interface RankedBar {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
value: number;
|
|
8
|
+
/** Default `clear`. */
|
|
9
|
+
fill?: TraceFill;
|
|
10
|
+
/** Any CSS colour, in place of `fill`: for a bar shaded by its own value. */
|
|
11
|
+
color?: string;
|
|
12
|
+
/** A line under the value in the bar's hover card. */
|
|
13
|
+
detail?: string;
|
|
14
|
+
}
|
|
15
|
+
/** A bar's paint: its colour, else its fill. */
|
|
16
|
+
export declare function paintOf(row: RankedBar, fill?: TraceFill): string;
|
|
17
|
+
/** A bar's hover card: its name, its value, and its detail. */
|
|
18
|
+
export declare function RankedTip({ active, payload, format, }: TooltipContentProps & {
|
|
19
|
+
format: (value: number) => string;
|
|
20
|
+
}): import("react").JSX.Element | null;
|
|
21
|
+
/**
|
|
22
|
+
* Bars in the order given, one per row, named on the left and valued on the right, drawn like the
|
|
23
|
+
* trace's charts. `max` fixes the scale, for figures such as rates that share one; a bar opens
|
|
24
|
+
* `onSelect` when given.
|
|
25
|
+
*/
|
|
26
|
+
export declare function RankedBars({ rows, format, max, onSelect, }: {
|
|
27
|
+
rows: readonly RankedBar[];
|
|
28
|
+
format: (value: number) => string;
|
|
29
|
+
max?: number;
|
|
30
|
+
onSelect?: (row: RankedBar) => void;
|
|
31
|
+
}): import("react").JSX.Element;
|
package/ui/RankedBars.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
3
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
4
|
+
import { Bar, BarChart, Cell, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
|
|
5
|
+
import { ChartFrame, ChartTip, FIRST_WIDTH_PX } from "./TraceOverview.js";
|
|
6
|
+
import { TRACE_GAP, TRACE_RADIUS, traceFill } from "./trace-patterns.js";
|
|
7
|
+
/** A bar's paint: its colour, else its fill. */
|
|
8
|
+
export function paintOf(row, fill = 'clear') {
|
|
9
|
+
return row.color ?? traceFill(row.fill ?? fill);
|
|
10
|
+
}
|
|
11
|
+
/** Height of one bar's row, in pixels. */
|
|
12
|
+
const ROW_PX = 22;
|
|
13
|
+
/** Longest name the axis shows whole; the hover card shows the rest. */
|
|
14
|
+
const NAME_CHARS = 15;
|
|
15
|
+
const TICK = { fill: 'var(--color-text-secondary)', fontSize: 11 };
|
|
16
|
+
/** A bar's hover card: its name, its value, and its detail. */
|
|
17
|
+
export function RankedTip({ active, payload, format, }) {
|
|
18
|
+
const row = payload?.[0]?.payload;
|
|
19
|
+
if (!active || !row)
|
|
20
|
+
return null;
|
|
21
|
+
return (_jsx(ChartTip, { children: _jsxs(VStack, { gap: 0, children: [_jsx(Text, { weight: "medium", children: row.name }), _jsx(Text, { type: "supporting", hasTabularNumbers: true, children: format(row.value) }), row.detail ? (_jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: row.detail })) : null] }) }));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Bars in the order given, one per row, named on the left and valued on the right, drawn like the
|
|
25
|
+
* trace's charts. `max` fixes the scale, for figures such as rates that share one; a bar opens
|
|
26
|
+
* `onSelect` when given.
|
|
27
|
+
*/
|
|
28
|
+
export function RankedBars({ rows, format, max, onSelect, }) {
|
|
29
|
+
const height = rows.length * ROW_PX;
|
|
30
|
+
return (_jsx(ChartFrame, { children: _jsx(ResponsiveContainer, { width: "100%", height: height, initialDimension: { width: FIRST_WIDTH_PX, height }, children: _jsxs(BarChart, { data: [...rows], layout: "vertical", margin: { top: 0, right: 48, bottom: 0, left: 0 }, accessibilityLayer: false, barCategoryGap: 5, onClick: (state) => {
|
|
31
|
+
const row = rows[Number(state?.activeIndex)];
|
|
32
|
+
if (row && onSelect)
|
|
33
|
+
onSelect(row);
|
|
34
|
+
}, style: onSelect ? { cursor: 'pointer' } : undefined, children: [_jsx(XAxis, { type: "number", hide: true, domain: [0, max ?? 'auto'] }), _jsx(YAxis, { type: "category", dataKey: "id", width: 104, tick: TICK, tickLine: false, axisLine: false, interval: 0, tickFormatter: (id) => {
|
|
35
|
+
const name = rows.find((row) => row.id === id)?.name ?? '';
|
|
36
|
+
return name.length > NAME_CHARS ? `${name.slice(0, NAME_CHARS - 1)}…` : name;
|
|
37
|
+
} }), _jsx(Tooltip, { content: (props) => _jsx(RankedTip, { ...props, format: format }), cursor: false, isAnimationActive: false }), _jsx(Bar, { dataKey: "value", radius: TRACE_RADIUS, stroke: TRACE_GAP, strokeWidth: 2, isAnimationActive: false, minPointSize: 2, label: {
|
|
38
|
+
position: 'right',
|
|
39
|
+
fill: 'var(--color-text-secondary)',
|
|
40
|
+
fontSize: 11,
|
|
41
|
+
formatter: (value) => format(Number(value)),
|
|
42
|
+
}, children: rows.map((row) => (_jsx(Cell, { fill: paintOf(row) }, row.id))) })] }) }) }));
|
|
43
|
+
}
|
package/ui/TraceCharts.js
CHANGED
|
@@ -4,9 +4,10 @@ import { HStack } from '@astryxdesign/core/HStack';
|
|
|
4
4
|
import { Text } from '@astryxdesign/core/Text';
|
|
5
5
|
import { VStack } from '@astryxdesign/core/VStack';
|
|
6
6
|
import { useMemo } from 'react';
|
|
7
|
-
import { Bar, BarChart,
|
|
7
|
+
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
|
|
8
8
|
import { isCallTrace, traceActor, } from "../client/trace-story.js";
|
|
9
9
|
import { traceSpans } from "../client/trace-view.js";
|
|
10
|
+
import { RankedBars } from "./RankedBars.js";
|
|
10
11
|
import { ChartFrame, ChartTip, FIRST_WIDTH_PX } from "./TraceOverview.js";
|
|
11
12
|
import { useTraceFormat } from "./TraceValues.js";
|
|
12
13
|
import { ACTOR_FILL, ChartCard, TRACE_GAP, TRACE_RADIUS, TraceSwatch, traceFill, } from "./trace-patterns.js";
|
|
@@ -14,8 +15,6 @@ import { ACTOR_FILL, ChartCard, TRACE_GAP, TRACE_RADIUS, TraceSwatch, traceFill,
|
|
|
14
15
|
const CHART_PX = 120;
|
|
15
16
|
/** Height of a share bar, in pixels. */
|
|
16
17
|
const SHARE_PX = 12;
|
|
17
|
-
/** Height of one row of the slowest-steps chart, in pixels. */
|
|
18
|
-
const SLOW_ROW_PX = 22;
|
|
19
18
|
/** Most steps the slowest-steps chart ranks. */
|
|
20
19
|
const SLOWEST = 5;
|
|
21
20
|
const TICK = { fill: 'var(--color-text-secondary)', fontSize: 11 };
|
|
@@ -162,49 +161,36 @@ function useCallRows(root, onSelect) {
|
|
|
162
161
|
open: () => onSelect(node),
|
|
163
162
|
})), [root, onSelect, t]);
|
|
164
163
|
}
|
|
165
|
-
function SlowTip({ active, payload }) {
|
|
166
|
-
const format = useTraceFormat();
|
|
167
|
-
const row = payload?.[0]?.payload;
|
|
168
|
-
if (!active || !row)
|
|
169
|
-
return null;
|
|
170
|
-
return (_jsx(ChartTip, { children: _jsxs(VStack, { gap: 0, children: [_jsx(Text, { weight: "medium", children: row.name }), _jsx(Text, { type: "supporting", hasTabularNumbers: true, children: format.duration(row.ms) })] }) }));
|
|
171
|
-
}
|
|
172
164
|
/** The steps that took longest, longest first, filled by who acted. A bar opens its step. */
|
|
173
165
|
function SlowestCard({ root, onSelect }) {
|
|
174
166
|
const format = useTraceFormat();
|
|
175
167
|
const { t } = format;
|
|
176
|
-
const
|
|
168
|
+
const slowest = useMemo(() => {
|
|
177
169
|
const calls = modelCalls(root);
|
|
178
170
|
return traceSpans(root.children)
|
|
179
171
|
.filter((node) => node.meta.type === 'tool' || MODEL_CALLS.has(node.meta.type))
|
|
180
172
|
.sort((a, b) => b.durationMs - a.durationMs)
|
|
181
173
|
.slice(0, SLOWEST)
|
|
182
174
|
.map((node) => ({
|
|
183
|
-
id: node.id,
|
|
184
175
|
node,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
:
|
|
188
|
-
|
|
176
|
+
bar: {
|
|
177
|
+
id: node.id,
|
|
178
|
+
name: node.meta.type === 'tool'
|
|
179
|
+
? (node.meta.subject ?? node.meta.label)
|
|
180
|
+
: t('@theorem.panel.trace.charts.call', { index: calls.indexOf(node) + 1 }),
|
|
181
|
+
value: node.durationMs,
|
|
182
|
+
fill: ACTOR_FILL[traceActor(node)],
|
|
183
|
+
},
|
|
189
184
|
}));
|
|
190
185
|
}, [root, t]);
|
|
191
|
-
const top =
|
|
186
|
+
const top = slowest[0];
|
|
192
187
|
if (!top)
|
|
193
188
|
return null;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}, style: { cursor: 'pointer' }, children: [_jsx(XAxis, { type: "number", hide: true }), _jsx(YAxis, { type: "category", dataKey: "id", width: 104, tick: TICK, tickLine: false, axisLine: false, interval: 0, tickFormatter: (id) => {
|
|
200
|
-
const name = rows.find((row) => row.id === id)?.name ?? '';
|
|
201
|
-
return name.length > 15 ? `${name.slice(0, 14)}…` : name;
|
|
202
|
-
} }), _jsx(Tooltip, { content: (props) => _jsx(SlowTip, { ...props }), cursor: false, isAnimationActive: false }), _jsx(Bar, { dataKey: "ms", ...BAR, label: {
|
|
203
|
-
position: 'right',
|
|
204
|
-
fill: 'var(--color-text-secondary)',
|
|
205
|
-
fontSize: 11,
|
|
206
|
-
formatter: (value) => format.duration(Number(value)),
|
|
207
|
-
}, children: rows.map((row) => (_jsx(Cell, { fill: traceFill(ACTOR_FILL[traceActor(row.node)]) }, row.id))) })] }) }) }) }));
|
|
189
|
+
return (_jsx(ChartCard, { title: t('@theorem.panel.trace.charts.slowest'), value: format.duration(top.bar.value), note: top.bar.name, children: _jsx(RankedBars, { rows: slowest.map(({ bar }) => bar), format: format.duration, onSelect: (bar) => {
|
|
190
|
+
const step = slowest.find((each) => each.bar.id === bar.id);
|
|
191
|
+
if (step)
|
|
192
|
+
onSelect(step.node);
|
|
193
|
+
} }) }));
|
|
208
194
|
}
|
|
209
195
|
/** Where a turn's time, tokens, and slowest steps went. A bar opens its span. */
|
|
210
196
|
export function TraceCharts({ root, split, onSelect, }) {
|
package/ui/TraceOverview.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export declare function TurnPicker({ turns, index, onChange, onBack, end, }: {
|
|
|
24
24
|
onBack?: (() => void) | undefined;
|
|
25
25
|
end?: ReactNode;
|
|
26
26
|
}): import("react").JSX.Element | null;
|
|
27
|
+
/** One figure in a card: its name, the figure, and lines that say more. */
|
|
28
|
+
export declare function ChartStat({ label, value, detail, }: {
|
|
29
|
+
label: string;
|
|
30
|
+
value: ReactNode;
|
|
31
|
+
detail?: ReactNode | readonly ReactNode[];
|
|
32
|
+
}): import("react").JSX.Element;
|
|
27
33
|
/** A turn's guardrail figures alone, as {@link TurnStats} lays them out. */
|
|
28
34
|
export declare function GuardrailStats({ latency }: {
|
|
29
35
|
latency: TraceLatency;
|
package/ui/TraceOverview.js
CHANGED
|
@@ -55,7 +55,8 @@ export function TurnPicker({ turns, index, onChange, onBack, end, }) {
|
|
|
55
55
|
count: turns.length,
|
|
56
56
|
}) }), turn.node.meta.subject ? (_jsx(Text, { color: "secondary", maxLines: 1, children: turn.node.meta.subject })) : null] }), _jsxs(HStack, { gap: 0, children: [_jsx(IconButton, { label: t(isCalls ? '@theorem.panel.trace.calls.previous' : '@theorem.panel.trace.previous'), icon: _jsx(Icon, { icon: IconChevronLeft }), variant: "ghost", size: "sm", isDisabled: index === 0, onClick: () => onChange(index - 1) }), _jsx(IconButton, { label: t(isCalls ? '@theorem.panel.trace.calls.next' : '@theorem.panel.trace.next'), icon: _jsx(Icon, { icon: IconChevronRight }), variant: "ghost", size: "sm", isDisabled: index === turns.length - 1, onClick: () => onChange(index + 1) }), end] })] })] }));
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
/** One figure in a card: its name, the figure, and lines that say more. */
|
|
59
|
+
export function ChartStat({ label, value, detail, }) {
|
|
59
60
|
return (_jsx(Card, { padding: 3, variant: "muted", style: { background: 'var(--color-background-surface)' }, children: _jsxs(VStack, { gap: 1, children: [_jsx(Text, { type: "supporting", color: "secondary", children: label }), _jsx(Text, { type: "large", weight: "semibold", hasTabularNumbers: true, children: value }), _jsx(VStack, { gap: 0, children: [detail]
|
|
60
61
|
.flat()
|
|
61
62
|
.filter(Boolean)
|
|
@@ -65,7 +66,7 @@ function FirstTextStat({ latency, format }) {
|
|
|
65
66
|
if (latency?.firstTextMs === undefined)
|
|
66
67
|
return null;
|
|
67
68
|
const { t } = format;
|
|
68
|
-
return (_jsx(
|
|
69
|
+
return (_jsx(ChartStat, { label: t('@theorem.panel.trace.firstText'), value: format.duration(latency.firstTextMs), detail: [
|
|
69
70
|
latency.tokensPerSecond !== undefined &&
|
|
70
71
|
t('@theorem.panel.trace.rate', {
|
|
71
72
|
rate: format.number(Math.round(latency.tokensPerSecond)),
|
|
@@ -77,7 +78,7 @@ function FirstTextStat({ latency, format }) {
|
|
|
77
78
|
function CostStat({ totals, format }) {
|
|
78
79
|
if (!totals.cost)
|
|
79
80
|
return null;
|
|
80
|
-
return (_jsx(
|
|
81
|
+
return (_jsx(ChartStat, { label: traceAttributeMeta('theorem.usage.cost_usd')?.label ?? '', value: format.sum(totals.cost, format.usd) }));
|
|
81
82
|
}
|
|
82
83
|
/** A token total as its card reads it: "–" when no call reported it. */
|
|
83
84
|
function tokenSide(total, format) {
|
|
@@ -88,7 +89,7 @@ function TokensStat({ totals, format }) {
|
|
|
88
89
|
if (!input && !output)
|
|
89
90
|
return null;
|
|
90
91
|
const all = [input, output].reduce((sum, total) => sum + (total?.value ?? 0), 0);
|
|
91
|
-
return (_jsx(
|
|
92
|
+
return (_jsx(ChartStat, { label: format.t('@theorem.panel.trace.tokens'), value: format.number(all), detail: format.t('@theorem.panel.trace.tokens.detail', {
|
|
92
93
|
input: tokenSide(input, format),
|
|
93
94
|
output: tokenSide(output, format),
|
|
94
95
|
}) }));
|
|
@@ -98,13 +99,13 @@ function StepsStat({ totals, tools, format, }) {
|
|
|
98
99
|
// why: With no model, every step is a tool call the heading already counts.
|
|
99
100
|
if (totals.calls === 0 && totals.errors === 0)
|
|
100
101
|
return null;
|
|
101
|
-
return (_jsx(
|
|
102
|
+
return (_jsx(ChartStat, { label: t('@theorem.panel.trace.steps'), value: format.number(totals.calls + tools), detail: totals.errors > 0 ? (_jsx("span", { style: { color: TRACE_HUE.oxblood }, children: t('@theorem.panel.trace.failed', { count: totals.errors }) })) : (t('@theorem.panel.trace.steps.detail', { calls: totals.calls, tools })) }));
|
|
102
103
|
}
|
|
103
104
|
function GuardrailsStat({ latency, format, }) {
|
|
104
105
|
const guardrails = latency?.guardrails;
|
|
105
106
|
if (!guardrails)
|
|
106
107
|
return null;
|
|
107
|
-
return (_jsx(
|
|
108
|
+
return (_jsx(ChartStat, { label: format.t('@theorem.panel.trace.guardrails'), value: format.duration(guardrails.ms), detail: format.t('@theorem.panel.trace.guardrails.detail', {
|
|
108
109
|
checks: guardrails.checks,
|
|
109
110
|
flagged: guardrails.flagged,
|
|
110
111
|
}) }));
|
|
@@ -122,5 +123,5 @@ export function GuardrailStats({ latency }) {
|
|
|
122
123
|
*/
|
|
123
124
|
export function TurnStats({ totals, tools, latency, }) {
|
|
124
125
|
const format = useTraceFormat();
|
|
125
|
-
return (_jsxs(Grid, { columns: { minWidth: 120, repeat: 'fit' }, gap: 2, children: [_jsx(
|
|
126
|
+
return (_jsxs(Grid, { columns: { minWidth: 120, repeat: 'fit' }, gap: 2, children: [_jsx(ChartStat, { label: TRACE_FIELDS.duration.label, value: format.duration(totals.durationMs) }), _jsx(FirstTextStat, { latency: latency, format: format }), _jsx(CostStat, { totals: totals, format: format }), _jsx(TokensStat, { totals: totals, format: format }), _jsx(StepsStat, { totals: totals, tools: tools, format: format }), _jsx(GuardrailsStat, { latency: latency, format: format })] }));
|
|
126
127
|
}
|
package/ui/index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export { LiveCaptionsPanel, type LiveCaptionsPanelProps } from './LiveCaptionsPa
|
|
|
25
25
|
export { composerDrawerLabel, type LabelText, liveStateLabel, THEOREM_UI_CATALOG, type TheoremLabelKey, type TheoremLabelOverrides, type TheoremLabels, type TheoremLabelValues, type TheoremUiCatalog, voiceNoteName, workStatusLabel, } from './labels.ts';
|
|
26
26
|
export { TheoremLabelsProvider, type TheoremLabelsProviderProps, useLabels, } from './labels-provider.tsx';
|
|
27
27
|
export { PaneFailure, PaneLoading, PaneState } from './PaneState.tsx';
|
|
28
|
+
export { RadialBars, Sunburst, type SunburstNode } from './RadialCharts.tsx';
|
|
29
|
+
export { type RankedBar, RankedBars } from './RankedBars.tsx';
|
|
28
30
|
export { InPlace, PaneLayout, PanePanel, Prose, RaisedPane } from './SidePanel.tsx';
|
|
29
31
|
export { DEFAULT_CHAT_MAX_WIDTH, TheoremChat, type TheoremChatHandle, type TheoremChatProps, } from './TheoremChat.tsx';
|
|
30
32
|
export { TheoremDecision, type TheoremDecisionProps } from './TheoremDecision.tsx';
|
|
@@ -33,4 +35,6 @@ export { TheoremHost, type TheoremHostProps } from './TheoremHost.tsx';
|
|
|
33
35
|
export { ApprovalCard, type ApprovalCardProps, AuthChallengeCard, type AuthChallengeCardProps, type ToolDecision, } from './ToolGateCard.tsx';
|
|
34
36
|
export { ToolResult } from './ToolResult.tsx';
|
|
35
37
|
export { TraceGuardrailsView, TracePlacement, useTraceInspector } from './TraceInspectorPanel.tsx';
|
|
38
|
+
export { ChartStat } from './TraceOverview.tsx';
|
|
36
39
|
export { TheoremThemeProvider, type TheoremThemeProviderProps } from './theme.tsx';
|
|
40
|
+
export { ChartCard, type TraceFill } from './trace-patterns.tsx';
|
package/ui/index.js
CHANGED
|
@@ -28,6 +28,8 @@ export { LiveCaptionsPanel } from "./LiveCaptionsPanel.js";
|
|
|
28
28
|
export { composerDrawerLabel, liveStateLabel, THEOREM_UI_CATALOG, voiceNoteName, workStatusLabel, } from "./labels.js";
|
|
29
29
|
export { TheoremLabelsProvider, useLabels, } from "./labels-provider.js";
|
|
30
30
|
export { PaneFailure, PaneLoading, PaneState } from "./PaneState.js";
|
|
31
|
+
export { RadialBars, Sunburst } from "./RadialCharts.js";
|
|
32
|
+
export { RankedBars } from "./RankedBars.js";
|
|
31
33
|
export { InPlace, PaneLayout, PanePanel, Prose, RaisedPane } from "./SidePanel.js";
|
|
32
34
|
export { DEFAULT_CHAT_MAX_WIDTH, TheoremChat, } from "./TheoremChat.js";
|
|
33
35
|
export { TheoremDecision } from "./TheoremDecision.js";
|
|
@@ -36,4 +38,6 @@ export { TheoremHost } from "./TheoremHost.js";
|
|
|
36
38
|
export { ApprovalCard, AuthChallengeCard, } from "./ToolGateCard.js";
|
|
37
39
|
export { ToolResult } from "./ToolResult.js";
|
|
38
40
|
export { TraceGuardrailsView, TracePlacement, useTraceInspector } from "./TraceInspectorPanel.js";
|
|
41
|
+
export { ChartStat } from "./TraceOverview.js";
|
|
39
42
|
export { TheoremThemeProvider } from "./theme.js";
|
|
43
|
+
export { ChartCard } from "./trace-patterns.js";
|