@theoremjs/react 0.3.4-next.616.g595707c → 0.3.4-next.620.g9fe1aff
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/TraceInspector.js +38 -11
- package/ui/TraceInspectorPanel.js +3 -34
- package/ui/TraceOverview.d.ts +3 -2
- package/ui/TraceOverview.js +3 -3
- package/ui/TraceSpanDetail.js +38 -2
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.620.g9fe1aff",
|
|
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.620.g9fe1aff",
|
|
64
|
+
"@theoremjs/web": "^0.3.4-next.620.g9fe1aff"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@astryxdesign/core": {
|
package/ui/TraceInspector.js
CHANGED
|
@@ -3,11 +3,12 @@ import { Badge } from '@astryxdesign/core/Badge';
|
|
|
3
3
|
import { EmptyState } from '@astryxdesign/core/EmptyState';
|
|
4
4
|
import { HStack } from '@astryxdesign/core/HStack';
|
|
5
5
|
import { Icon } from '@astryxdesign/core/Icon';
|
|
6
|
+
import { IconButton } from '@astryxdesign/core/IconButton';
|
|
6
7
|
import { Item } from '@astryxdesign/core/Item';
|
|
7
8
|
import { PowerSearch, } from '@astryxdesign/core/PowerSearch';
|
|
8
9
|
import { Text } from '@astryxdesign/core/Text';
|
|
9
10
|
import { VStack } from '@astryxdesign/core/VStack';
|
|
10
|
-
import { IconActivity } from '@tabler/icons-react';
|
|
11
|
+
import { IconActivity, IconDownload } from '@tabler/icons-react';
|
|
11
12
|
import { useMemo, useState } from 'react';
|
|
12
13
|
import { isCallTrace, traceGuardrails, traceLatency, traceStory, traceTurnRow, traceTurns, } from "../client/trace-story.js";
|
|
13
14
|
import { TRACE_SEARCH_OPERATORS, TRACE_TEXT_FIELD, traceMatchIds, traceSearchFields, traceSpans, traceTotals, traceTree, } from "../client/trace-view.js";
|
|
@@ -112,7 +113,7 @@ function TurnRow({ turn, index, isCall, onOpen, }) {
|
|
|
112
113
|
t(isCall ? '@theorem.panel.trace.charts.hostCall' : '@theorem.panel.trace.charts.turn', { index: index + 1 }) }), description: turnRowNote(row), endContent: _jsx(TurnRowTotals, { turn: turn }), align: "start", density: "compact", onClick: onOpen }));
|
|
113
114
|
}
|
|
114
115
|
/** Every turn at once: the conversation's totals, each turn's time and tokens, and the turns themselves. */
|
|
115
|
-
function ConversationOverview({ turns, onPick, }) {
|
|
116
|
+
function ConversationOverview({ turns, onPick, end, }) {
|
|
116
117
|
const { t } = useTraceFormat();
|
|
117
118
|
const nodes = useMemo(() => turns.map((turn) => turn.node), [turns]);
|
|
118
119
|
// why: Time between turns is the person's, not the agent's: the duration sums the turns.
|
|
@@ -122,12 +123,12 @@ function ConversationOverview({ turns, onPick, }) {
|
|
|
122
123
|
}), [nodes, turns]);
|
|
123
124
|
const tools = useMemo(() => traceSpans(nodes).filter((node) => node.meta.type === 'tool').length, [nodes]);
|
|
124
125
|
const isCalls = isCallTrace(turns);
|
|
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', {
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
return (_jsxs(VStack, { gap: 5, padding: 4, children: [_jsxs(HStack, { gap: 1, align: "center", justify: "between", children: [_jsx(Text, { weight: "semibold", children: t(isCalls ? '@theorem.panel.trace.calls.count' : '@theorem.panel.trace.conversation', {
|
|
127
|
+
count: turns.length,
|
|
128
|
+
}) }), end] }), _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))) }) })] })] }));
|
|
128
129
|
}
|
|
129
130
|
/** One turn at a glance, then its story, then its timeline under the search. */
|
|
130
|
-
function TurnOverview({ turns, index, onPick, onBack, selectedId, onSelect, }) {
|
|
131
|
+
function TurnOverview({ turns, index, onPick, onBack, selectedId, onSelect, end, }) {
|
|
131
132
|
const { t } = useTraceFormat();
|
|
132
133
|
const [filters, setFilters] = useState([]);
|
|
133
134
|
const turn = turns[index];
|
|
@@ -140,7 +141,7 @@ function TurnOverview({ turns, index, onPick, onBack, selectedId, onSelect, }) {
|
|
|
140
141
|
if (!turn || !root)
|
|
141
142
|
return null;
|
|
142
143
|
const tools = traceSpans([root]).filter((node) => node.meta.type === 'tool').length;
|
|
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] }));
|
|
144
|
+
return (_jsxs(VStack, { gap: 5, padding: 4, children: [_jsx(TurnPicker, { turns: turns, index: index, onChange: onPick, onBack: onBack, end: end }), _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] }));
|
|
144
145
|
}
|
|
145
146
|
function EmptyTrace() {
|
|
146
147
|
const { t } = useTraceFormat();
|
|
@@ -171,10 +172,36 @@ function SpanPane({ open, turnStartMs, isWide, onBack, onOpen, }) {
|
|
|
171
172
|
return (_jsx(VStack, { padding: 4, children: _jsx(TraceSpanDetail, { node: open, turnStartMs: turnStartMs, isClose: isWide, onBack: onBack, onOpen: onOpen }) }));
|
|
172
173
|
}
|
|
173
174
|
/** The whole conversation, or the open turn with a way back when there are others. */
|
|
174
|
-
function Overview({ turns, index, pick, selectedId, select, }) {
|
|
175
|
+
function Overview({ turns, index, pick, selectedId, select, end, }) {
|
|
175
176
|
if (index === null)
|
|
176
|
-
return _jsx(ConversationOverview, { turns: turns, onPick: pick });
|
|
177
|
-
return (_jsx(TurnOverview, { turns: turns, index: index, onPick: pick, onBack: turns.length > 1 ? () => pick(null) : undefined, selectedId: selectedId, onSelect: select }));
|
|
177
|
+
return _jsx(ConversationOverview, { turns: turns, onPick: pick, end: end });
|
|
178
|
+
return (_jsx(TurnOverview, { turns: turns, index: index, onPick: pick, onBack: turns.length > 1 ? () => pick(null) : undefined, selectedId: selectedId, onSelect: select, end: end }));
|
|
179
|
+
}
|
|
180
|
+
/** The agent the records ran, which names their file. */
|
|
181
|
+
function tracedAgent(records) {
|
|
182
|
+
for (const record of records) {
|
|
183
|
+
for (const span of record.spans) {
|
|
184
|
+
const name = span.attributes['gen_ai.agent.name'];
|
|
185
|
+
if (typeof name === 'string' && name)
|
|
186
|
+
return name;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return 'trace';
|
|
190
|
+
}
|
|
191
|
+
/** Saves the records on screen as `<agent>-<time>.trace.json`, the file the studio imports. */
|
|
192
|
+
function TraceDownload({ records }) {
|
|
193
|
+
const { t } = useTraceFormat();
|
|
194
|
+
const label = t('@theorem.panel.trace.download');
|
|
195
|
+
return (_jsx(IconButton, { label: label, variant: "ghost", size: "sm", tooltip: label, icon: _jsx(Icon, { icon: IconDownload }), onClick: () => {
|
|
196
|
+
const stamp = new Date().toISOString().replaceAll(':', '-').slice(0, 19);
|
|
197
|
+
const file = new Blob([JSON.stringify(records, null, 2)], { type: 'application/json' });
|
|
198
|
+
const url = URL.createObjectURL(file);
|
|
199
|
+
const link = document.createElement('a');
|
|
200
|
+
link.href = url;
|
|
201
|
+
link.download = `${tracedAgent(records)}-${stamp}.trace.json`;
|
|
202
|
+
link.click();
|
|
203
|
+
URL.revokeObjectURL(url);
|
|
204
|
+
} }));
|
|
178
205
|
}
|
|
179
206
|
/**
|
|
180
207
|
* The panel's content. Narrow, the turn fills it and a span opens in its
|
|
@@ -187,7 +214,7 @@ function TraceInspectorBody({ records, isWide, }) {
|
|
|
187
214
|
const { index, root, open, select, close, pick } = useOpenSpan(turns);
|
|
188
215
|
if (turns.length === 0)
|
|
189
216
|
return _jsx(EmptyTrace, {});
|
|
190
|
-
const overview = (_jsx(Overview, { turns: turns, index: index, pick: pick, selectedId: open?.id, select: select }));
|
|
217
|
+
const overview = (_jsx(Overview, { turns: turns, index: index, pick: pick, selectedId: open?.id, select: select, end: _jsx(TraceDownload, { records: records }) }));
|
|
191
218
|
return (_jsx(PaneLayout, { isWide: isWide, label: t('@theorem.panel.trace.name'), detailLabel: open?.meta.label ?? '', lead: _jsx(TracePatterns, {}), overview: overview, detail: open && (_jsx(SpanPane, { open: open, turnStartMs: root?.startMs ?? 0, isWide: isWide, onBack: close, onOpen: select })) }));
|
|
192
219
|
}
|
|
193
220
|
/** The inspector over the records a feed has delivered; the panel loads this module when it first opens. */
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Icon } from '@astryxdesign/core/Icon';
|
|
3
|
-
import {
|
|
4
|
-
import { IconActivity, IconDownload } from '@tabler/icons-react';
|
|
3
|
+
import { IconActivity } from '@tabler/icons-react';
|
|
5
4
|
import { cloneElement, createContext, isValidElement, lazy, Suspense, use, useCallback, useId, useRef, useState, } from 'react';
|
|
6
|
-
import { useTraceRecords } from "../hooks/use-trace-records.js";
|
|
7
5
|
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
8
6
|
import { InPlace, isSidePanelWide, RaisedPane, SidePanel, SidePanelHeader, SidePanelToggle, useSidePanel, useWidth, } from "./SidePanel.js";
|
|
9
7
|
// why: The inspector and its charts are fetched when the trace first opens, not with the chat.
|
|
@@ -45,9 +43,9 @@ export function useTraceInspector(iface, traces, open) {
|
|
|
45
43
|
const view = isOpen ? openTrace(placement === 'panel', id, traces, toggle) : null;
|
|
46
44
|
return { toggle, view, labels, offered: true };
|
|
47
45
|
}
|
|
48
|
-
/** The open trace: filling the host's docked panel, or as its own panel;
|
|
46
|
+
/** The open trace: filling the host's docked panel, or as its own panel; the toggle on top. */
|
|
49
47
|
function openTrace(docked, id, traces, toggle) {
|
|
50
|
-
const header = (
|
|
48
|
+
const header = toggle ? _jsx(SidePanelHeader, { floating: true, children: toggle }) : null;
|
|
51
49
|
if (docked)
|
|
52
50
|
return _jsx(TraceFill, { id: id, header: header, traces: traces });
|
|
53
51
|
return _jsx(TraceView, { id: id, header: header, traces: traces });
|
|
@@ -57,35 +55,6 @@ function TraceFill({ id, header, traces, }) {
|
|
|
57
55
|
const { ref, width } = useWidth();
|
|
58
56
|
return (_jsxs("div", { id: id, ref: ref, style: { height: '100%', position: 'relative' }, children: [header, _jsx(Suspense, { fallback: null, children: _jsx(TraceFeedBody, { traces: traces, isWide: isSidePanelWide(width) }) })] }));
|
|
59
57
|
}
|
|
60
|
-
/** The agent the records ran, which names their file. */
|
|
61
|
-
function tracedAgent(records) {
|
|
62
|
-
for (const record of records) {
|
|
63
|
-
for (const span of record.spans) {
|
|
64
|
-
const name = span.attributes['gen_ai.agent.name'];
|
|
65
|
-
if (typeof name === 'string' && name)
|
|
66
|
-
return name;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return 'trace';
|
|
70
|
-
}
|
|
71
|
-
/** Saves the records on screen as `<agent>-<time>.trace.json`, the file the studio imports. */
|
|
72
|
-
function TraceDownload({ traces }) {
|
|
73
|
-
const t = useLabels();
|
|
74
|
-
const records = useTraceRecords(traces);
|
|
75
|
-
if (records.length === 0)
|
|
76
|
-
return null;
|
|
77
|
-
const label = t('@theorem.panel.trace.download');
|
|
78
|
-
return (_jsx(IconButton, { label: label, variant: "ghost", tooltip: label, icon: _jsx(Icon, { icon: IconDownload, size: "sm" }), onClick: () => {
|
|
79
|
-
const stamp = new Date().toISOString().replaceAll(':', '-').slice(0, 19);
|
|
80
|
-
const file = new Blob([JSON.stringify(records, null, 2)], { type: 'application/json' });
|
|
81
|
-
const url = URL.createObjectURL(file);
|
|
82
|
-
const link = document.createElement('a');
|
|
83
|
-
link.href = url;
|
|
84
|
-
link.download = `${tracedAgent(records)}-${stamp}.trace.json`;
|
|
85
|
-
link.click();
|
|
86
|
-
URL.revokeObjectURL(url);
|
|
87
|
-
} }));
|
|
88
|
-
}
|
|
89
58
|
/** The surface, with the trace docked at its inline end. A host-driven trace stays in place. */
|
|
90
59
|
function TraceDock({ inspector, children }) {
|
|
91
60
|
const layoutRef = useRef(null);
|
package/ui/TraceOverview.d.ts
CHANGED
|
@@ -15,13 +15,14 @@ export declare function ChartFrame({ children }: {
|
|
|
15
15
|
export declare function ChartTip({ children }: {
|
|
16
16
|
children: ReactNode;
|
|
17
17
|
}): import("react").JSX.Element;
|
|
18
|
-
/** Which turn is open: its name,
|
|
19
|
-
export declare function TurnPicker({ turns, index, onChange, onBack, }: {
|
|
18
|
+
/** Which turn is open: its name, the previous and next turn, then `end`. */
|
|
19
|
+
export declare function TurnPicker({ turns, index, onChange, onBack, end, }: {
|
|
20
20
|
turns: readonly TraceTurn[];
|
|
21
21
|
index: number;
|
|
22
22
|
onChange: (index: number) => void;
|
|
23
23
|
/** Leaves the turn for the whole conversation; absent when there is only the one turn. */
|
|
24
24
|
onBack?: (() => void) | undefined;
|
|
25
|
+
end?: ReactNode;
|
|
25
26
|
}): import("react").JSX.Element | null;
|
|
26
27
|
/** A turn's guardrail figures alone, as {@link TurnStats} lays them out. */
|
|
27
28
|
export declare function GuardrailStats({ latency }: {
|
package/ui/TraceOverview.js
CHANGED
|
@@ -42,8 +42,8 @@ export function ChartTip({ children }) {
|
|
|
42
42
|
pointerEvents: 'none',
|
|
43
43
|
}, children: children }));
|
|
44
44
|
}
|
|
45
|
-
/** Which turn is open: its name,
|
|
46
|
-
export function TurnPicker({ turns, index, onChange, onBack, }) {
|
|
45
|
+
/** Which turn is open: its name, the previous and next turn, then `end`. */
|
|
46
|
+
export function TurnPicker({ turns, index, onChange, onBack, end, }) {
|
|
47
47
|
const { t } = useTraceFormat();
|
|
48
48
|
const turn = turns[index];
|
|
49
49
|
if (!turn)
|
|
@@ -53,7 +53,7 @@ export function TurnPicker({ turns, index, onChange, onBack, }) {
|
|
|
53
53
|
label: turn.node.meta.label,
|
|
54
54
|
index: index + 1,
|
|
55
55
|
count: turns.length,
|
|
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) })] })] })] }));
|
|
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
|
function Stat({ label, value, detail, }) {
|
|
59
59
|
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]
|
package/ui/TraceSpanDetail.js
CHANGED
|
@@ -137,13 +137,49 @@ function ProsePanel({ attribute, text }) {
|
|
|
137
137
|
return null;
|
|
138
138
|
return (_jsx(PanePanel, { ...label(attribute), children: _jsx(Prose, { text: text }) }));
|
|
139
139
|
}
|
|
140
|
+
/** Content folded under a title, with its count when it is a list. */
|
|
141
|
+
function Folded({ title, count, children, }) {
|
|
142
|
+
return (_jsx(Collapsible, { defaultIsOpen: false, trigger: _jsx(Text, { weight: "medium", children: count === undefined ? title : `${title} · ${String(count)}` }), children: _jsx(Card, { padding: 3, variant: "muted", style: { background: 'var(--color-background-surface)' }, children: children }) }));
|
|
143
|
+
}
|
|
144
|
+
/** The system prompt a model call was sent, part by part. */
|
|
145
|
+
function SystemInstructions({ node }) {
|
|
146
|
+
const parts = inlineContent(node.record, node.span.attributes['gen_ai.system_instructions']);
|
|
147
|
+
const texts = (Array.isArray(parts) ? parts.filter(isObject) : []).flatMap((part) => typeof part.content === 'string' && part.content ? [part.content] : []);
|
|
148
|
+
if (texts.length === 0)
|
|
149
|
+
return null;
|
|
150
|
+
return (_jsx(Folded, { title: label('gen_ai.system_instructions').title, children: _jsx(VStack, { gap: 3, children: keyedByContent(texts, (text) => text).map(({ item: text, key }) => (_jsx(Prose, { text: text }, key))) }) }));
|
|
151
|
+
}
|
|
152
|
+
/** The tools a model call was offered: each one's name, what it does, and what it takes. */
|
|
153
|
+
function ToolDefinitions({ node }) {
|
|
154
|
+
const value = storedValue(node, 'gen_ai.tool.definitions');
|
|
155
|
+
const tools = Array.isArray(value) ? value.filter(isObject) : [];
|
|
156
|
+
if (tools.length === 0)
|
|
157
|
+
return null;
|
|
158
|
+
return (_jsx(Folded, { title: label('gen_ai.tool.definitions').title, count: tools.length, children: _jsx(VStack, { gap: 3, children: keyedByContent(tools, (tool) => String(tool.name)).map(({ item: tool, key }) => (_jsxs(VStack, { gap: 1, children: [_jsx(Text, { weight: "medium", children: String(tool.name ?? '') }), typeof tool.description === 'string' ? (_jsx(Text, { color: "secondary", children: tool.description })) : null, tool.parameters === undefined ? null : _jsx(ShapedData, { value: tool.parameters })] }, key))) }) }));
|
|
159
|
+
}
|
|
160
|
+
/** What a model call was given besides its messages: the system prompt and the tools. */
|
|
161
|
+
function CallContext({ node }) {
|
|
162
|
+
return (_jsxs(_Fragment, { children: [_jsx(SystemInstructions, { node: node }), _jsx(ToolDefinitions, { node: node })] }));
|
|
163
|
+
}
|
|
164
|
+
/** A turn's first model call: the one whose system prompt and tools the turn opened with. */
|
|
165
|
+
function firstCall(node) {
|
|
166
|
+
for (const child of node.children) {
|
|
167
|
+
if ('gen_ai.system_instructions' in child.span.attributes)
|
|
168
|
+
return child;
|
|
169
|
+
const found = firstCall(child);
|
|
170
|
+
if (found)
|
|
171
|
+
return found;
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
140
175
|
function TurnIO({ node }) {
|
|
141
|
-
|
|
176
|
+
const call = firstCall(node);
|
|
177
|
+
return (_jsxs(_Fragment, { children: [call ? _jsx(CallContext, { node: call }) : null, _jsx(ProsePanel, { attribute: "gen_ai.input.messages", text: messageText(node, 'gen_ai.input.messages', 'user') }), _jsx(ProsePanel, { attribute: "gen_ai.output.messages", text: messageText(node, 'gen_ai.output.messages', 'assistant') })] }));
|
|
142
178
|
}
|
|
143
179
|
function CallIO({ node }) {
|
|
144
180
|
const input = messagesOf(node, 'gen_ai.input.messages');
|
|
145
181
|
const output = messagesOf(node, 'gen_ai.output.messages');
|
|
146
|
-
return (_jsxs(_Fragment, { children: [output.length > 0 ? (_jsx(PanePanel, { ...label('gen_ai.output.messages'), children: _jsx(MessageList, { messages: output }) })) : null, input.length > 0 ? (_jsx(
|
|
182
|
+
return (_jsxs(_Fragment, { children: [output.length > 0 ? (_jsx(PanePanel, { ...label('gen_ai.output.messages'), children: _jsx(MessageList, { messages: output }) })) : null, _jsx(CallContext, { node: node }), input.length > 0 ? (_jsx(Folded, { title: label('gen_ai.input.messages').title, count: input.length, children: _jsx(MessageList, { messages: input }) })) : null] }));
|
|
147
183
|
}
|
|
148
184
|
function ToolIO({ node }) {
|
|
149
185
|
const { attributes } = node.span;
|