@theoremjs/react 0.3.4-next.608.gd007c52 → 0.3.4-next.612.g21d312d
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 +3 -3
- package/ui/CardWall.js +34 -17
- package/ui/ConsoleFrame.js +3 -1
- package/ui/SidePanel.js +3 -2
- package/ui/ToolResult.js +1 -1
- package/ui/TraceCharts.d.ts +6 -17
- package/ui/TraceCharts.js +10 -17
- package/ui/TraceInspector.js +4 -8
- package/ui/TraceOverview.d.ts +7 -12
- package/ui/TraceOverview.js +12 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theoremjs/react",
|
|
3
|
-
"version": "0.3.4-next.
|
|
3
|
+
"version": "0.3.4-next.612.g21d312d",
|
|
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-next.
|
|
64
|
-
"@theoremjs/web": "^0.3.4-next.
|
|
63
|
+
"@theoremjs/agents": "^0.3.4-next.612.g21d312d",
|
|
64
|
+
"@theoremjs/web": "^0.3.4-next.612.g21d312d"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@astryxdesign/core": {
|
package/ui/CardWall.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { VStack } from '@astryxdesign/core/VStack';
|
|
3
3
|
import { Children, isValidElement, useLayoutEffect, useRef, useState } from 'react';
|
|
4
|
+
import { flushSync } from 'react-dom';
|
|
4
5
|
import { columnCount, wallPlaces } from "./wall-columns.js";
|
|
5
6
|
function sameWall(a, b) {
|
|
6
7
|
return (a.width === b.width &&
|
|
@@ -22,6 +23,21 @@ export function CardWall({ minWidth, children }) {
|
|
|
22
23
|
const wallRef = useRef(null);
|
|
23
24
|
const slots = useRef([]);
|
|
24
25
|
const observer = useRef(null);
|
|
26
|
+
const slotRefs = useRef([]);
|
|
27
|
+
// why: One callback per place, so a re-render does not detach and watch every slot again.
|
|
28
|
+
const slotRef = (at) => {
|
|
29
|
+
slotRefs.current[at] ??= (slot) => {
|
|
30
|
+
slots.current[at] = slot;
|
|
31
|
+
// why: A card that mounts later is measured once it is watched.
|
|
32
|
+
if (slot)
|
|
33
|
+
observer.current?.observe(slot);
|
|
34
|
+
return () => {
|
|
35
|
+
if (slot)
|
|
36
|
+
observer.current?.unobserve(slot);
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
return slotRefs.current[at];
|
|
40
|
+
};
|
|
25
41
|
const [wall, setWall] = useState({ width: 0, gap: 0, heights: [] });
|
|
26
42
|
const count = columnCount(wall.width, minWidth, wall.gap);
|
|
27
43
|
const columnWidth = (wall.width - wall.gap * (count - 1)) / count;
|
|
@@ -33,16 +49,26 @@ export function CardWall({ minWidth, children }) {
|
|
|
33
49
|
const node = wallRef.current;
|
|
34
50
|
if (!node)
|
|
35
51
|
return;
|
|
36
|
-
const
|
|
52
|
+
const sizes = new WeakMap();
|
|
53
|
+
let width = 0;
|
|
54
|
+
// why: Sizes come only from the observer, after the browser's own layout: reading them here
|
|
55
|
+
// would force a layout, and inside a closed disclosure lay out what is hidden.
|
|
56
|
+
const watch = new ResizeObserver((entries) => {
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const box = entry.borderBoxSize[0];
|
|
59
|
+
if (entry.target === node)
|
|
60
|
+
width = entry.contentBoxSize[0]?.inlineSize ?? width;
|
|
61
|
+
else if (box)
|
|
62
|
+
sizes.set(entry.target, box.blockSize);
|
|
63
|
+
}
|
|
37
64
|
const next = {
|
|
38
|
-
width
|
|
65
|
+
width,
|
|
39
66
|
gap: Number.parseFloat(getComputedStyle(node).rowGap) || 0,
|
|
40
|
-
heights: slots.current.map((slot) => slot
|
|
67
|
+
heights: slots.current.map((slot) => (slot ? sizes.get(slot) : undefined)),
|
|
41
68
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const watch = new ResizeObserver(measure);
|
|
69
|
+
// why: Placed in the same frame it was measured, so no frame shows the cards unplaced.
|
|
70
|
+
flushSync(() => setWall((shown) => (sameWall(shown, next) ? shown : next)));
|
|
71
|
+
});
|
|
46
72
|
watch.observe(node);
|
|
47
73
|
for (const slot of slots.current)
|
|
48
74
|
if (slot)
|
|
@@ -55,16 +81,7 @@ export function CardWall({ minWidth, children }) {
|
|
|
55
81
|
}, []);
|
|
56
82
|
return (_jsx(VStack, { ref: wallRef, gap: 3, style: isPlaced ? { position: 'relative', height } : undefined, children: cards.map((card, at) => {
|
|
57
83
|
const place = places[at];
|
|
58
|
-
return (_jsx("div", { ref: (
|
|
59
|
-
slots.current[at] = slot;
|
|
60
|
-
// why: A card that mounts later is measured once it is watched.
|
|
61
|
-
if (slot)
|
|
62
|
-
observer.current?.observe(slot);
|
|
63
|
-
return () => {
|
|
64
|
-
if (slot)
|
|
65
|
-
observer.current?.unobserve(slot);
|
|
66
|
-
};
|
|
67
|
-
}, style: isPlaced && place
|
|
84
|
+
return (_jsx("div", { ref: slotRef(at), style: isPlaced && place
|
|
68
85
|
? {
|
|
69
86
|
position: 'absolute',
|
|
70
87
|
top: place.top,
|
package/ui/ConsoleFrame.js
CHANGED
|
@@ -6,6 +6,7 @@ import { HStack } from '@astryxdesign/core/HStack';
|
|
|
6
6
|
import { useLocale } from '@astryxdesign/core/i18n';
|
|
7
7
|
import { Layout, LayoutContent } from '@astryxdesign/core/Layout';
|
|
8
8
|
import { useResizable } from '@astryxdesign/core/Resizable';
|
|
9
|
+
import { percent, pixel } from '@astryxdesign/core/Resizable/utils';
|
|
9
10
|
import { SegmentedControl, SegmentedControlItem } from '@astryxdesign/core/SegmentedControl';
|
|
10
11
|
import { Text } from '@astryxdesign/core/Text';
|
|
11
12
|
import { TextArea } from '@astryxdesign/core/TextArea';
|
|
@@ -79,7 +80,8 @@ function ConsoleColumns({ requestLabel = 'Request', layoutRef, children, }) {
|
|
|
79
80
|
const requestPanel = useResizable({
|
|
80
81
|
defaultSize: basisWidth > 0 ? Math.round(basisWidth * 0.4) : '40%',
|
|
81
82
|
minSize: 280,
|
|
82
|
-
|
|
83
|
+
// why: In a narrow pair 70% is below the minimum; the maximum never is.
|
|
84
|
+
maxSize: percent(70, { min: pixel(280) }),
|
|
83
85
|
containerRef: pairRef,
|
|
84
86
|
});
|
|
85
87
|
const [request, response] = Children.toArray(children);
|
package/ui/SidePanel.js
CHANGED
|
@@ -4,6 +4,7 @@ import { HStack } from '@astryxdesign/core/HStack';
|
|
|
4
4
|
import { IconButton } from '@astryxdesign/core/IconButton';
|
|
5
5
|
import { Layout, LayoutContent, LayoutHeader, LayoutPanel } from '@astryxdesign/core/Layout';
|
|
6
6
|
import { ResizeHandle, useResizable, } from '@astryxdesign/core/Resizable';
|
|
7
|
+
import { percent, pixel } from '@astryxdesign/core/Resizable/utils';
|
|
7
8
|
import { ScrollableArea } from '@astryxdesign/core/ScrollableArea';
|
|
8
9
|
import { StackItem } from '@astryxdesign/core/Stack';
|
|
9
10
|
import { Text } from '@astryxdesign/core/Text';
|
|
@@ -13,12 +14,12 @@ import { useCallback, useId, useState } from 'react';
|
|
|
13
14
|
/**
|
|
14
15
|
* `useResizable` sizing for side panels: a third of the layout (pass the
|
|
15
16
|
* Layout as `containerRef` so the percentages resolve), dragged between a
|
|
16
|
-
* readable minimum and half.
|
|
17
|
+
* readable minimum and half, or the minimum when half is less.
|
|
17
18
|
*/
|
|
18
19
|
const SIDE_PANEL_SIZING = {
|
|
19
20
|
defaultSize: '33%',
|
|
20
21
|
minSize: 280,
|
|
21
|
-
maxSize:
|
|
22
|
+
maxSize: percent(50, { min: pixel(280) }),
|
|
22
23
|
};
|
|
23
24
|
/** Whether a side panel is wide enough for two readable columns: twice its minimum. */
|
|
24
25
|
export function isSidePanelWide(size) {
|
package/ui/ToolResult.js
CHANGED
|
@@ -32,7 +32,7 @@ function ResultLead({ layout, parts = [], }) {
|
|
|
32
32
|
if (!hasLead(layout, media))
|
|
33
33
|
return null;
|
|
34
34
|
const { images, audio } = media;
|
|
35
|
-
return (_jsxs(VStack, { gap: 3, children: [_jsxs(CardWall, { minWidth: CARD_PX, children: [layout.figures.map((figure) => (_jsx(Arrive, { children: _jsx(ChartCard, { title: figure.label, value: withUnit(decimal.format(figure.value), figure.unit) }) }, `figure:${figure.key}`))), layout.charts.map((chart) => (_jsx(
|
|
35
|
+
return (_jsxs(VStack, { gap: 3, children: [_jsxs(CardWall, { minWidth: CARD_PX, children: [layout.figures.map((figure) => (_jsx(Arrive, { children: _jsx(ChartCard, { title: figure.label, value: withUnit(decimal.format(figure.value), figure.unit) }) }, `figure:${figure.key}`))), layout.charts.map((chart) => (_jsx(Suspense, { fallback: _jsx(Arrive, { children: _jsx(Skeleton, { height: CHART_CARD_PX }) }), children: _jsx(Arrive, { children: _jsx(ToolResultChart, { chart: chart }) }) }, `chart:${chart.key}`))), images.map((image, index) => (_jsx(Arrive, { children: _jsx(ClickableCard, { label: image.alt || 'Open image', padding: 0, onClick: () => setOpen(index), children: _jsx(AspectRatio, { ratio: 4 / 3, fit: "cover", children: _jsx("img", { src: image.src, alt: image.alt, loading: "lazy" }) }) }) }, `image:${image.src}`)))] }), images.length > 0 ? (_jsx(Lightbox, { isOpen: open !== null, onOpenChange: (isOpen) => {
|
|
36
36
|
if (!isOpen)
|
|
37
37
|
setOpen(null);
|
|
38
38
|
}, media: images.map((image) => ({ src: image.src, alt: image.alt })), index: open ?? 0, onIndexChange: setOpen })) : null, keyedByContent(audio, (clip) => clip.src).map(({ item: clip, key }) => (_jsx(Arrive, { children: _jsx(VoiceNote, { src: clip.src, mimeType: clip.mimeType }) }, key)))] }));
|
package/ui/TraceCharts.d.ts
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type TraceNode
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
* one wall. A bar opens its span.
|
|
6
|
-
*/
|
|
7
|
-
export declare function TraceCharts({ root, split, totals, tools, latency, onSelect, }: {
|
|
1
|
+
import { type TraceTimeSplit, type TraceTurn } from '../client/trace-story.ts';
|
|
2
|
+
import { type TraceNode } from '../client/trace-view.ts';
|
|
3
|
+
/** Where a turn's time, tokens, and slowest steps went. A bar opens its span. */
|
|
4
|
+
export declare function TraceCharts({ root, split, onSelect, }: {
|
|
8
5
|
root: TraceNode;
|
|
9
6
|
split: TraceTimeSplit;
|
|
10
|
-
totals: TraceTotals;
|
|
11
|
-
tools: number;
|
|
12
|
-
latency?: TraceLatency;
|
|
13
7
|
onSelect: (node: TraceNode) => void;
|
|
14
8
|
}): import("react").JSX.Element;
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
* one wall. A column opens its turn.
|
|
18
|
-
*/
|
|
19
|
-
export declare function ConversationCharts({ turns, totals, tools, onPick, }: {
|
|
9
|
+
/** The whole conversation: each turn's time and tokens. A column opens its turn. */
|
|
10
|
+
export declare function ConversationCharts({ turns, onPick, }: {
|
|
20
11
|
turns: readonly TraceTurn[];
|
|
21
|
-
totals: TraceTotals;
|
|
22
|
-
tools: number;
|
|
23
12
|
onPick: (index: number) => void;
|
|
24
13
|
}): import("react").JSX.Element;
|
package/ui/TraceCharts.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Grid } from '@astryxdesign/core/Grid';
|
|
2
3
|
import { HStack } from '@astryxdesign/core/HStack';
|
|
3
4
|
import { Text } from '@astryxdesign/core/Text';
|
|
4
5
|
import { VStack } from '@astryxdesign/core/VStack';
|
|
@@ -6,8 +7,7 @@ import { useMemo } from 'react';
|
|
|
6
7
|
import { Bar, BarChart, Cell, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
|
|
7
8
|
import { isCallTrace, traceActor, } from "../client/trace-story.js";
|
|
8
9
|
import { traceSpans } from "../client/trace-view.js";
|
|
9
|
-
import {
|
|
10
|
-
import { ChartFrame, ChartTip, FIRST_WIDTH_PX, TRACE_CARD_PX, turnStats, } from "./TraceOverview.js";
|
|
10
|
+
import { ChartFrame, ChartTip, FIRST_WIDTH_PX } from "./TraceOverview.js";
|
|
11
11
|
import { useTraceFormat } from "./TraceValues.js";
|
|
12
12
|
import { ACTOR_FILL, ChartCard, TRACE_GAP, TRACE_RADIUS, TraceSwatch, traceFill, } from "./trace-patterns.js";
|
|
13
13
|
/** Height of each column chart's plot, in pixels. */
|
|
@@ -206,14 +206,11 @@ function SlowestCard({ root, onSelect }) {
|
|
|
206
206
|
formatter: (value) => format.duration(Number(value)),
|
|
207
207
|
}, children: rows.map((row) => (_jsx(Cell, { fill: traceFill(ACTOR_FILL[traceActor(row.node)]) }, row.id))) })] }) }) }) }));
|
|
208
208
|
}
|
|
209
|
-
/**
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
*/
|
|
213
|
-
export function TraceCharts({ root, split, totals, tools, latency, onSelect, }) {
|
|
214
|
-
const format = useTraceFormat();
|
|
209
|
+
/** Where a turn's time, tokens, and slowest steps went. A bar opens its span. */
|
|
210
|
+
export function TraceCharts({ root, split, onSelect, }) {
|
|
211
|
+
const { t } = useTraceFormat();
|
|
215
212
|
const callRows = useCallRows(root, onSelect);
|
|
216
|
-
return (_jsxs(
|
|
213
|
+
return (_jsxs(Grid, { columns: { minWidth: 260, repeat: 'fit' }, gap: 3, children: [_jsx(TimeCard, { split: split }), _jsx(TokensCard, { title: t('@theorem.panel.trace.charts.calls'), rows: callRows }), _jsx(SlowestCard, { root: root, onSelect: onSelect })] }));
|
|
217
214
|
}
|
|
218
215
|
function TurnTimeTip({ active, payload }) {
|
|
219
216
|
const format = useTraceFormat();
|
|
@@ -240,13 +237,9 @@ function TurnTimeCard({ rows, isCalls }) {
|
|
|
240
237
|
})
|
|
241
238
|
: undefined, children: [_jsx(Columns, { rows: rows, keys: SPLIT_KEYS, fills: SPLIT_FILL, tip: (props) => _jsx(TurnTimeTip, { ...props }) }), _jsx(SplitLegend, { split: sums, total: total })] }));
|
|
242
239
|
}
|
|
243
|
-
/**
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
*/
|
|
247
|
-
export function ConversationCharts({ turns, totals, tools, onPick, }) {
|
|
248
|
-
const format = useTraceFormat();
|
|
249
|
-
const { t } = format;
|
|
240
|
+
/** The whole conversation: each turn's time and tokens. A column opens its turn. */
|
|
241
|
+
export function ConversationCharts({ turns, onPick, }) {
|
|
242
|
+
const { t } = useTraceFormat();
|
|
250
243
|
const isCalls = isCallTrace(turns);
|
|
251
244
|
const column = isCalls
|
|
252
245
|
? '@theorem.panel.trace.charts.hostCall'
|
|
@@ -269,5 +262,5 @@ export function ConversationCharts({ turns, totals, tools, onPick, }) {
|
|
|
269
262
|
}),
|
|
270
263
|
open: () => onPick(position),
|
|
271
264
|
})), [turns, onPick, t, column]);
|
|
272
|
-
return (_jsxs(
|
|
265
|
+
return (_jsxs(Grid, { columns: { minWidth: 260, repeat: 'fit' }, gap: 3, children: [_jsx(TurnTimeCard, { rows: time, isCalls: isCalls }), _jsx(TokensCard, { title: t('@theorem.panel.trace.charts.turnTokens'), rows: tokens })] }));
|
|
273
266
|
}
|
package/ui/TraceInspector.js
CHANGED
|
@@ -13,11 +13,10 @@ import { isCallTrace, traceGuardrails, traceLatency, traceStory, traceTurnRow, t
|
|
|
13
13
|
import { TRACE_SEARCH_OPERATORS, TRACE_TEXT_FIELD, traceMatchIds, traceSearchFields, traceSpans, traceTotals, traceTree, } from "../client/trace-view.js";
|
|
14
14
|
import { useTraceRecords } from "../hooks/use-trace-records.js";
|
|
15
15
|
import { Stream, useArrive } from "./arrive.js";
|
|
16
|
-
import { CardWall } from "./CardWall.js";
|
|
17
16
|
import { PaneLayout } from "./SidePanel.js";
|
|
18
17
|
import { ConversationCharts, TraceCharts } from "./TraceCharts.js";
|
|
19
18
|
import { TraceGuardrails } from "./TraceGuardrails.js";
|
|
20
|
-
import {
|
|
19
|
+
import { GuardrailStats, TurnPicker, TurnStats } from "./TraceOverview.js";
|
|
21
20
|
import { TraceSpanDetail } from "./TraceSpanDetail.js";
|
|
22
21
|
import { ActorMark, TraceStory } from "./TraceStory.js";
|
|
23
22
|
import { useTraceFormat } from "./TraceValues.js";
|
|
@@ -125,7 +124,7 @@ function ConversationOverview({ turns, onPick, }) {
|
|
|
125
124
|
const isCalls = isCallTrace(turns);
|
|
126
125
|
return (_jsxs(VStack, { gap: 5, padding: 4, children: [_jsx(Text, { weight: "semibold", children: t(isCalls ? '@theorem.panel.trace.calls.count' : '@theorem.panel.trace.conversation', {
|
|
127
126
|
count: turns.length,
|
|
128
|
-
}) }),
|
|
127
|
+
}) }), _jsxs(VStack, { gap: 3, children: [_jsx(TurnStats, { totals: totals, tools: tools }), _jsx(ConversationCharts, { turns: turns, onPick: onPick })] }), _jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t(isCalls ? '@theorem.panel.trace.calls' : '@theorem.panel.trace.turns') }), _jsx(Stream, { children: _jsx(VStack, { gap: 0, children: turns.map((turn, index) => (_jsx(TurnRow, { turn: turn, index: index, isCall: isCalls, onOpen: () => onPick(index) }, turn.node.id))) }) })] })] }));
|
|
129
128
|
}
|
|
130
129
|
/** One turn at a glance, then its story, then its timeline under the search. */
|
|
131
130
|
function TurnOverview({ turns, index, onPick, onBack, selectedId, onSelect, }) {
|
|
@@ -141,7 +140,7 @@ function TurnOverview({ turns, index, onPick, onBack, selectedId, onSelect, }) {
|
|
|
141
140
|
if (!turn || !root)
|
|
142
141
|
return null;
|
|
143
142
|
const tools = traceSpans([root]).filter((node) => node.meta.type === 'tool').length;
|
|
144
|
-
return (_jsxs(VStack, { gap: 5, padding: 4, children: [_jsx(TurnPicker, { turns: turns, index: index, onChange: onPick, onBack: onBack }),
|
|
143
|
+
return (_jsxs(VStack, { gap: 5, padding: 4, children: [_jsx(TurnPicker, { turns: turns, index: index, onChange: onPick, onBack: onBack }), _jsxs(VStack, { gap: 3, children: [_jsx(TurnStats, { totals: turn.totals, tools: tools, latency: latency }), _jsx(TraceCharts, { root: root, split: turn.split, onSelect: onSelect })] }), _jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.timeline') }), _jsx(PowerSearch, { config: config, filters: filters, onChange: setFilters, label: t('@theorem.panel.trace.search'), isLabelHidden: true, placeholder: t('@theorem.panel.trace.search.placeholder'), resultCount: matches?.size, size: "sm" }), _jsx(TraceWaterfall, { root: root, selectedId: selectedId, matches: matches, onSelect: onSelect }, root.id)] }), story.length > 0 ? (_jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.story') }), _jsx(TraceStory, { steps: story, selectedId: selectedId, matches: matches, onSelect: onSelect })] })) : null, guardrails.length > 0 ? (_jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.guardrails') }), _jsx(TraceGuardrails, { checks: guardrails, onSelect: onSelect })] })) : null] }));
|
|
145
144
|
}
|
|
146
145
|
function EmptyTrace() {
|
|
147
146
|
const { t } = useTraceFormat();
|
|
@@ -203,8 +202,5 @@ export function TraceGuardrailsBody({ records, head, }) {
|
|
|
203
202
|
const { t } = useTraceFormat();
|
|
204
203
|
const roots = useMemo(() => traceTurns(traceTree(records)).map((turn) => turn.node), [records]);
|
|
205
204
|
const checks = useMemo(() => roots.flatMap(traceGuardrails), [roots]);
|
|
206
|
-
|
|
207
|
-
.map((root) => ({ id: root.id, latency: traceLatency(root) }))
|
|
208
|
-
.filter(({ latency }) => latency.guardrails), [roots]);
|
|
209
|
-
return (_jsxs(VStack, { gap: 5, padding: 4, children: [head, timed.length > 0 ? (_jsx(CardWall, { minWidth: TRACE_CARD_PX, children: timed.map(({ id, latency }) => (_jsx(GuardrailStat, { latency: latency }, id))) })) : null, checks.length > 0 ? (_jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.guardrails') }), _jsx(TraceGuardrails, { checks: checks })] })) : null] }));
|
|
205
|
+
return (_jsxs(VStack, { gap: 5, padding: 4, children: [head, roots.map((root) => (_jsx(GuardrailStats, { latency: traceLatency(root) }, root.id))), checks.length > 0 ? (_jsxs(VStack, { gap: 2, children: [_jsx(SectionTitle, { title: t('@theorem.panel.trace.guardrails') }), _jsx(TraceGuardrails, { checks: checks })] })) : null] }));
|
|
210
206
|
}
|
package/ui/TraceOverview.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import { type TraceLatency, type TraceTurn } from '../client/trace-story.ts';
|
|
3
3
|
import type { TraceTotals } from '../client/trace-view.ts';
|
|
4
|
-
import { useTraceFormat } from './TraceValues.tsx';
|
|
5
4
|
/** Width a chart draws at before it has measured its column: the panel's narrowest. */
|
|
6
5
|
export declare const FIRST_WIDTH_PX = 280;
|
|
7
6
|
/**
|
|
@@ -24,20 +23,16 @@ export declare function TurnPicker({ turns, index, onChange, onBack, }: {
|
|
|
24
23
|
/** Leaves the turn for the whole conversation; absent when there is only the one turn. */
|
|
25
24
|
onBack?: (() => void) | undefined;
|
|
26
25
|
}): import("react").JSX.Element | null;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export declare const TRACE_CARD_PX = 260;
|
|
30
|
-
/** A turn's guardrail figure alone, the card {@link turnStats} gives it. */
|
|
31
|
-
export declare function GuardrailStat({ latency }: {
|
|
26
|
+
/** A turn's guardrail figures alone, as {@link TurnStats} lays them out. */
|
|
27
|
+
export declare function GuardrailStats({ latency }: {
|
|
32
28
|
latency: TraceLatency;
|
|
33
|
-
}): import("react").JSX.Element;
|
|
29
|
+
}): import("react").JSX.Element | null;
|
|
34
30
|
/**
|
|
35
|
-
* Duration, first text, cost, tokens, steps, and guardrails of the open turn
|
|
36
|
-
*
|
|
37
|
-
* nothing.
|
|
31
|
+
* Duration, first text, cost, tokens, steps, and guardrails of the open turn;
|
|
32
|
+
* a total the trace did not record is left out.
|
|
38
33
|
*/
|
|
39
|
-
export declare function
|
|
34
|
+
export declare function TurnStats({ totals, tools, latency, }: {
|
|
40
35
|
totals: TraceTotals;
|
|
41
36
|
tools: number;
|
|
42
37
|
latency?: TraceLatency;
|
|
43
|
-
}):
|
|
38
|
+
}): import("react").JSX.Element;
|
package/ui/TraceOverview.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button } from '@astryxdesign/core/Button';
|
|
3
3
|
import { Card } from '@astryxdesign/core/Card';
|
|
4
|
+
import { Grid } from '@astryxdesign/core/Grid';
|
|
4
5
|
import { HStack } from '@astryxdesign/core/HStack';
|
|
5
6
|
import { Icon } from '@astryxdesign/core/Icon';
|
|
6
7
|
import { IconButton } from '@astryxdesign/core/IconButton';
|
|
@@ -60,7 +61,7 @@ function Stat({ label, value, detail, }) {
|
|
|
60
61
|
.filter(Boolean)
|
|
61
62
|
.map((line) => (_jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: line }, String(line)))) })] }) }));
|
|
62
63
|
}
|
|
63
|
-
function FirstTextStat({ latency, format
|
|
64
|
+
function FirstTextStat({ latency, format }) {
|
|
64
65
|
if (latency?.firstTextMs === undefined)
|
|
65
66
|
return null;
|
|
66
67
|
const { t } = format;
|
|
@@ -108,25 +109,18 @@ function GuardrailsStat({ latency, format, }) {
|
|
|
108
109
|
flagged: guardrails.flagged,
|
|
109
110
|
}) }));
|
|
110
111
|
}
|
|
111
|
-
/**
|
|
112
|
-
export
|
|
113
|
-
/** A turn's guardrail figure alone, the card {@link turnStats} gives it. */
|
|
114
|
-
export function GuardrailStat({ latency }) {
|
|
112
|
+
/** A turn's guardrail figures alone, as {@link TurnStats} lays them out. */
|
|
113
|
+
export function GuardrailStats({ latency }) {
|
|
115
114
|
const format = useTraceFormat();
|
|
116
|
-
|
|
115
|
+
if (!latency.guardrails)
|
|
116
|
+
return null;
|
|
117
|
+
return (_jsx(Grid, { columns: { minWidth: 120, repeat: 'fit' }, gap: 2, children: _jsx(GuardrailsStat, { latency: latency, format: format }) }));
|
|
117
118
|
}
|
|
118
119
|
/**
|
|
119
|
-
* Duration, first text, cost, tokens, steps, and guardrails of the open turn
|
|
120
|
-
*
|
|
121
|
-
* nothing.
|
|
120
|
+
* Duration, first text, cost, tokens, steps, and guardrails of the open turn;
|
|
121
|
+
* a total the trace did not record is left out.
|
|
122
122
|
*/
|
|
123
|
-
export function
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
_jsx(FirstTextStat, { latency: latency, format: format }, "first-text"),
|
|
127
|
-
_jsx(CostStat, { totals: totals, format: format }, "cost"),
|
|
128
|
-
_jsx(TokensStat, { totals: totals, format: format }, "tokens"),
|
|
129
|
-
_jsx(StepsStat, { totals: totals, tools: tools, format: format }, "steps"),
|
|
130
|
-
_jsx(GuardrailsStat, { latency: latency, format: format }, "guardrails"),
|
|
131
|
-
];
|
|
123
|
+
export function TurnStats({ totals, tools, latency, }) {
|
|
124
|
+
const format = useTraceFormat();
|
|
125
|
+
return (_jsxs(Grid, { columns: { minWidth: 120, repeat: 'fit' }, gap: 2, children: [_jsx(Stat, { 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 })] }));
|
|
132
126
|
}
|