@theoremjs/react 0.0.0-stage → 0.3.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/LICENSE +21 -0
- package/README.md +246 -2
- package/client/attachment-hover-preview.d.ts +11 -0
- package/client/attachment-hover-preview.js +36 -0
- package/client/audio-level.d.ts +5 -0
- package/client/audio-level.js +24 -0
- package/client/composer-attachments.d.ts +30 -0
- package/client/composer-attachments.js +33 -0
- package/client/composer-drawer.d.ts +19 -0
- package/client/composer-drawer.js +15 -0
- package/client/composer-hints.d.ts +25 -0
- package/client/composer-hints.js +20 -0
- package/client/composer-primary.d.ts +16 -0
- package/client/composer-primary.js +25 -0
- package/client/decision-transport.d.ts +48 -0
- package/client/decision-transport.js +102 -0
- package/client/decode-composer-draft.d.ts +11 -0
- package/client/decode-composer-draft.js +12 -0
- package/client/encode-composer-draft.d.ts +9 -0
- package/client/encode-composer-draft.js +34 -0
- package/client/encode-files.d.ts +18 -0
- package/client/encode-files.js +54 -0
- package/client/failure.d.ts +32 -0
- package/client/failure.js +34 -0
- package/client/generation-selection.d.ts +15 -0
- package/client/generation-selection.js +24 -0
- package/client/host-call.d.ts +51 -0
- package/client/host-call.js +42 -0
- package/client/host-transport.d.ts +68 -0
- package/client/host-transport.js +74 -0
- package/client/image-output.d.ts +6 -0
- package/client/image-output.js +13 -0
- package/client/index.d.ts +20 -0
- package/client/index.js +18 -0
- package/client/ink-waveform.d.ts +17 -0
- package/client/ink-waveform.js +69 -0
- package/client/live/apply-live-turn-tool-event.d.ts +14 -0
- package/client/live/apply-live-turn-tool-event.js +39 -0
- package/client/live/live-captions.d.ts +23 -0
- package/client/live/live-captions.js +70 -0
- package/client/live/live-mic-forward.d.ts +29 -0
- package/client/live/live-mic-forward.js +37 -0
- package/client/live/live-state.d.ts +11 -0
- package/client/live/live-state.js +20 -0
- package/client/live/live-tool.d.ts +11 -0
- package/client/live/live-tool.js +1 -0
- package/client/live/live-video.d.ts +10 -0
- package/client/live/live-video.js +71 -0
- package/client/live/run-live-tool-call.d.ts +17 -0
- package/client/live/run-live-tool-call.js +32 -0
- package/client/live-client.d.ts +141 -0
- package/client/live-client.js +638 -0
- package/client/live-errors.d.ts +2 -0
- package/client/live-errors.js +9 -0
- package/client/live-messages.d.ts +110 -0
- package/client/live-messages.js +67 -0
- package/client/mic-capture.d.ts +4 -0
- package/client/mic-capture.js +6 -0
- package/client/oauth-popup.d.ts +26 -0
- package/client/oauth-popup.js +30 -0
- package/client/pcm-downsample.d.ts +4 -0
- package/client/pcm-downsample.js +45 -0
- package/client/run-commit.d.ts +61 -0
- package/client/run-commit.js +124 -0
- package/client/run-session.d.ts +80 -0
- package/client/run-session.js +215 -0
- package/client/schema-fields.d.ts +63 -0
- package/client/schema-fields.js +178 -0
- package/client/shaped-data.d.ts +118 -0
- package/client/shaped-data.js +314 -0
- package/client/source-citations.d.ts +13 -0
- package/client/source-citations.js +56 -0
- package/client/state-fields.d.ts +16 -0
- package/client/state-fields.js +44 -0
- package/client/tool-result.d.ts +78 -0
- package/client/tool-result.js +275 -0
- package/client/tool-resume.d.ts +33 -0
- package/client/tool-resume.js +11 -0
- package/client/trace-feed.d.ts +13 -0
- package/client/trace-feed.js +18 -0
- package/client/trace-story.d.ts +163 -0
- package/client/trace-story.js +416 -0
- package/client/trace-view.d.ts +130 -0
- package/client/trace-view.js +293 -0
- package/client/transcript-groups.d.ts +114 -0
- package/client/transcript-groups.js +167 -0
- package/client/transcript-scroll.d.ts +7 -0
- package/client/transcript-scroll.js +6 -0
- package/client/transport.d.ts +176 -0
- package/client/transport.js +256 -0
- package/client/turn-client.d.ts +35 -0
- package/client/turn-client.js +93 -0
- package/client/voice-label.d.ts +2 -0
- package/client/voice-label.js +15 -0
- package/client/voice-recorder.d.ts +27 -0
- package/client/voice-recorder.js +165 -0
- package/client/walk-away.d.ts +34 -0
- package/client/walk-away.js +48 -0
- package/client/wire-line.d.ts +56 -0
- package/client/wire-line.js +90 -0
- package/client/worklets/mic-capture.js +16 -0
- package/components/InkWaveform.d.ts +10 -0
- package/components/InkWaveform.js +108 -0
- package/components/live/use-live-runner-controls.d.ts +48 -0
- package/components/live/use-live-runner-controls.js +100 -0
- package/components/live/use-live-runner-model.d.ts +45 -0
- package/components/live/use-live-runner-model.js +175 -0
- package/components/live/use-live-runner-ui.d.ts +65 -0
- package/components/live/use-live-runner-ui.js +159 -0
- package/components/live/use-live-session-client.d.ts +41 -0
- package/components/live/use-live-session-client.js +123 -0
- package/components/use-composer-voice.d.ts +28 -0
- package/components/use-composer-voice.js +90 -0
- package/components/use-editor-selection.d.ts +3 -0
- package/components/use-editor-selection.js +16 -0
- package/components/use-voice-playback.d.ts +16 -0
- package/components/use-voice-playback.js +68 -0
- package/hooks/use-described.d.ts +8 -0
- package/hooks/use-described.js +23 -0
- package/hooks/use-theorem-chat-actions.d.ts +75 -0
- package/hooks/use-theorem-chat-actions.js +265 -0
- package/hooks/use-theorem-chat-state.d.ts +66 -0
- package/hooks/use-theorem-chat-state.js +118 -0
- package/hooks/use-theorem-chat.d.ts +63 -0
- package/hooks/use-theorem-chat.js +340 -0
- package/hooks/use-theorem-decision.d.ts +23 -0
- package/hooks/use-theorem-decision.js +75 -0
- package/hooks/use-theorem-host.d.ts +44 -0
- package/hooks/use-theorem-host.js +117 -0
- package/hooks/use-theorem-interface.d.ts +19 -0
- package/hooks/use-theorem-interface.js +25 -0
- package/hooks/use-trace-records.d.ts +4 -0
- package/hooks/use-trace-records.js +7 -0
- package/index.d.ts +14 -0
- package/index.js +14 -0
- package/live.d.ts +8 -0
- package/live.js +10 -0
- package/package.json +73 -4
- package/server/credential-store.d.ts +24 -0
- package/server/credential-store.js +14 -0
- package/server/decision-handler.d.ts +34 -0
- package/server/decision-handler.js +55 -0
- package/server/handler.d.ts +114 -0
- package/server/handler.js +568 -0
- package/server/json-response.d.ts +2 -0
- package/server/json-response.js +7 -0
- package/server/mod.d.ts +16 -0
- package/server/mod.js +15 -0
- package/server/request-check.d.ts +16 -0
- package/server/request-check.js +34 -0
- package/server/session-store.d.ts +74 -0
- package/server/session-store.js +49 -0
- package/server/steer-inbox.d.ts +35 -0
- package/server/steer-inbox.js +52 -0
- package/server/walk-away.d.ts +22 -0
- package/server/walk-away.js +41 -0
- package/styles/ink-controls.css +134 -0
- package/ui/ChatComposerBar.d.ts +39 -0
- package/ui/ChatComposerBar.js +251 -0
- package/ui/ChatTranscript.d.ts +30 -0
- package/ui/ChatTranscript.js +474 -0
- package/ui/ConsoleFrame.d.ts +47 -0
- package/ui/ConsoleFrame.js +46 -0
- package/ui/DecisionAnswers.d.ts +15 -0
- package/ui/DecisionAnswers.js +102 -0
- package/ui/LiveRunner.d.ts +28 -0
- package/ui/LiveRunner.js +193 -0
- package/ui/SchemaFields.d.ts +10 -0
- package/ui/SchemaFields.js +122 -0
- package/ui/ShapedData.d.ts +11 -0
- package/ui/ShapedData.js +151 -0
- package/ui/SidePanel.d.ts +57 -0
- package/ui/SidePanel.js +50 -0
- package/ui/StateFields.d.ts +36 -0
- package/ui/StateFields.js +96 -0
- package/ui/TheoremChat.d.ts +71 -0
- package/ui/TheoremChat.js +146 -0
- package/ui/TheoremDecision.d.ts +38 -0
- package/ui/TheoremDecision.js +87 -0
- package/ui/TheoremHost.d.ts +38 -0
- package/ui/TheoremHost.js +150 -0
- package/ui/ToolGateCard.d.ts +28 -0
- package/ui/ToolGateCard.js +100 -0
- package/ui/ToolResult.d.ts +10 -0
- package/ui/ToolResult.js +60 -0
- package/ui/ToolResultCharts.d.ts +5 -0
- package/ui/ToolResultCharts.js +86 -0
- package/ui/TraceCharts.d.ts +13 -0
- package/ui/TraceCharts.js +235 -0
- package/ui/TraceGuardrails.d.ts +10 -0
- package/ui/TraceGuardrails.js +56 -0
- package/ui/TraceInspector.d.ts +6 -0
- package/ui/TraceInspector.js +189 -0
- package/ui/TraceInspectorPanel.d.ts +24 -0
- package/ui/TraceInspectorPanel.js +57 -0
- package/ui/TraceOverview.d.ts +34 -0
- package/ui/TraceOverview.js +96 -0
- package/ui/TraceSpanDetail.d.ts +10 -0
- package/ui/TraceSpanDetail.js +188 -0
- package/ui/TraceStory.d.ts +20 -0
- package/ui/TraceStory.js +107 -0
- package/ui/TraceValues.d.ts +36 -0
- package/ui/TraceValues.js +113 -0
- package/ui/TraceWaterfall.d.ts +13 -0
- package/ui/TraceWaterfall.js +132 -0
- package/ui/VoiceNote.d.ts +12 -0
- package/ui/VoiceNote.js +28 -0
- package/ui/built/theme.css +774 -0
- package/ui/built/theorem.d.ts +13 -0
- package/ui/built/theorem.js +631 -0
- package/ui/built/theorem.variants.d.ts +16 -0
- package/ui/disclosure-motion.d.ts +6 -0
- package/ui/disclosure-motion.js +163 -0
- package/ui/icons.d.ts +4 -0
- package/ui/icons.js +37 -0
- package/ui/index.d.ts +25 -0
- package/ui/index.js +29 -0
- package/ui/labels-provider.d.ts +16 -0
- package/ui/labels-provider.js +61 -0
- package/ui/labels.d.ts +1153 -0
- package/ui/labels.js +603 -0
- package/ui/theme.d.ts +10 -0
- package/ui/theme.js +13 -0
- package/ui/theorem-theme.d.ts +10 -0
- package/ui/theorem-theme.js +83 -0
- package/ui/trace-palette.d.ts +24 -0
- package/ui/trace-palette.js +24 -0
- package/ui/trace-patterns.d.ts +32 -0
- package/ui/trace-patterns.js +44 -0
- package/ui/transcript-copy-text.d.ts +4 -0
- package/ui/transcript-copy-text.js +39 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '@astryxdesign/core/Button';
|
|
3
|
+
import { Card } from '@astryxdesign/core/Card';
|
|
4
|
+
import { Grid } from '@astryxdesign/core/Grid';
|
|
5
|
+
import { HStack } from '@astryxdesign/core/HStack';
|
|
6
|
+
import { Icon } from '@astryxdesign/core/Icon';
|
|
7
|
+
import { IconButton } from '@astryxdesign/core/IconButton';
|
|
8
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
9
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
10
|
+
import { IconArrowLeft, IconChevronLeft, IconChevronRight } from '@tabler/icons-react';
|
|
11
|
+
import { TRACE_FIELDS, traceAttributeMeta } from '@theoremjs/agents';
|
|
12
|
+
import { isCallTrace } from "../client/trace-story.js";
|
|
13
|
+
import { TRACE_HUE } from "./trace-palette.js";
|
|
14
|
+
import { useTraceFormat } from "./TraceValues.js";
|
|
15
|
+
/** Width a chart draws at before it has measured its column: the panel's narrowest. */
|
|
16
|
+
export const FIRST_WIDTH_PX = 280;
|
|
17
|
+
/**
|
|
18
|
+
* Holds a chart. Recharts makes its layers focusable, so a click would move
|
|
19
|
+
* focus into the SVG and draw the browser's ring; a press here keeps focus
|
|
20
|
+
* where it was. Clicks and pointer drags still reach the chart.
|
|
21
|
+
*/
|
|
22
|
+
export function ChartFrame({ children }) {
|
|
23
|
+
return _jsx("div", { onMouseDown: (event) => event.preventDefault(), children: children });
|
|
24
|
+
}
|
|
25
|
+
/** A chart's hover card, drawn like Astryx's own tooltip. */
|
|
26
|
+
export function ChartTip({ children }) {
|
|
27
|
+
return (_jsx("div", { style: {
|
|
28
|
+
background: 'var(--color-background-popover)',
|
|
29
|
+
border: '1px solid var(--color-border)',
|
|
30
|
+
borderRadius: 'var(--radius-element)',
|
|
31
|
+
padding: '6px 8px',
|
|
32
|
+
boxShadow: 'var(--shadow-med)',
|
|
33
|
+
pointerEvents: 'none',
|
|
34
|
+
}, children: children }));
|
|
35
|
+
}
|
|
36
|
+
/** Which turn is open: its name, and the previous and next turn. */
|
|
37
|
+
export function TurnPicker({ turns, index, onChange, onBack, }) {
|
|
38
|
+
const { t } = useTraceFormat();
|
|
39
|
+
const turn = turns[index];
|
|
40
|
+
if (!turn)
|
|
41
|
+
return null;
|
|
42
|
+
const isCalls = isCallTrace(turns);
|
|
43
|
+
return (_jsxs(VStack, { gap: 1, hAlign: "start", children: [onBack ? (_jsx(Button, { label: t(isCalls ? '@theorem.panel.trace.calls.back' : '@theorem.panel.trace.conversation.back'), variant: "ghost", size: "sm", icon: _jsx(Icon, { icon: IconArrowLeft }), onClick: onBack })) : null, _jsxs(HStack, { gap: 1, align: "center", justify: "between", width: "100%", children: [_jsxs(HStack, { gap: 2, align: "center", children: [_jsx(Text, { weight: "semibold", children: t('@theorem.panel.trace.turn', { label: turn.node.meta.label, index: index + 1, count: turns.length }) }), 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) })] })] })] }));
|
|
44
|
+
}
|
|
45
|
+
function Stat({ label, value, detail }) {
|
|
46
|
+
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]
|
|
47
|
+
.flat()
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.map((line) => (_jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: line }, String(line)))) })] }) }));
|
|
50
|
+
}
|
|
51
|
+
function FirstTextStat({ latency, format }) {
|
|
52
|
+
if (latency?.firstTextMs === undefined)
|
|
53
|
+
return null;
|
|
54
|
+
const { t } = format;
|
|
55
|
+
return (_jsx(Stat, { label: t('@theorem.panel.trace.firstText'), value: format.duration(latency.firstTextMs), detail: [
|
|
56
|
+
latency.tokensPerSecond !== undefined && t('@theorem.panel.trace.rate', { rate: format.number(Math.round(latency.tokensPerSecond)) }),
|
|
57
|
+
latency.heldMs && t('@theorem.panel.trace.held', { duration: format.duration(latency.heldMs) }),
|
|
58
|
+
] }));
|
|
59
|
+
}
|
|
60
|
+
function CostStat({ totals, format }) {
|
|
61
|
+
if (!totals.cost)
|
|
62
|
+
return null;
|
|
63
|
+
return _jsx(Stat, { label: traceAttributeMeta('theorem.usage.cost_usd')?.label ?? '', value: format.sum(totals.cost, format.usd) });
|
|
64
|
+
}
|
|
65
|
+
/** A token total as its card reads it: "–" when no call reported it. */
|
|
66
|
+
function tokenSide(total, format) {
|
|
67
|
+
return total ? format.sum(total, format.number) : '–';
|
|
68
|
+
}
|
|
69
|
+
function TokensStat({ totals, format }) {
|
|
70
|
+
const { input, output } = totals;
|
|
71
|
+
if (!input && !output)
|
|
72
|
+
return null;
|
|
73
|
+
const all = [input, output].reduce((sum, total) => sum + (total?.value ?? 0), 0);
|
|
74
|
+
return (_jsx(Stat, { label: format.t('@theorem.panel.trace.tokens'), value: format.number(all), detail: format.t('@theorem.panel.trace.tokens.detail', { input: tokenSide(input, format), output: tokenSide(output, format) }) }));
|
|
75
|
+
}
|
|
76
|
+
function StepsStat({ totals, tools, format }) {
|
|
77
|
+
const { t } = format;
|
|
78
|
+
// With no model, every step is a tool call the heading already counts.
|
|
79
|
+
if (totals.calls === 0 && totals.errors === 0)
|
|
80
|
+
return null;
|
|
81
|
+
return (_jsx(Stat, { 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 })) }));
|
|
82
|
+
}
|
|
83
|
+
function GuardrailsStat({ latency, format }) {
|
|
84
|
+
const guardrails = latency?.guardrails;
|
|
85
|
+
if (!guardrails)
|
|
86
|
+
return null;
|
|
87
|
+
return (_jsx(Stat, { label: format.t('@theorem.panel.trace.guardrails'), value: format.duration(guardrails.ms), detail: format.t('@theorem.panel.trace.guardrails.detail', { checks: guardrails.checks, flagged: guardrails.flagged }) }));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Duration, first text, cost, tokens, steps and guardrails of the open turn;
|
|
91
|
+
* a total the trace did not record is left out.
|
|
92
|
+
*/
|
|
93
|
+
export function TurnStats({ totals, tools, latency }) {
|
|
94
|
+
const format = useTraceFormat();
|
|
95
|
+
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 })] }));
|
|
96
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type TraceNode } from '../client/trace-view.ts';
|
|
2
|
+
/** One span in full: who acted and how it ended, what went in and came out, its tokens, then every detail it recorded. */
|
|
3
|
+
export declare function TraceSpanDetail({ node, turnStartMs, isClose, onBack, onOpen, }: {
|
|
4
|
+
node: TraceNode;
|
|
5
|
+
turnStartMs: number;
|
|
6
|
+
/** In the wide panel the span sits beside the turn: close, not back. */
|
|
7
|
+
isClose?: boolean;
|
|
8
|
+
onBack: () => void;
|
|
9
|
+
onOpen: (node: TraceNode) => void;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Badge } from '@astryxdesign/core/Badge';
|
|
3
|
+
import { Button } from '@astryxdesign/core/Button';
|
|
4
|
+
import { Card } from '@astryxdesign/core/Card';
|
|
5
|
+
import { Collapsible } from '@astryxdesign/core/Collapsible';
|
|
6
|
+
import { HStack } from '@astryxdesign/core/HStack';
|
|
7
|
+
import { Icon } from '@astryxdesign/core/Icon';
|
|
8
|
+
import { Item } from '@astryxdesign/core/Item';
|
|
9
|
+
import { MetadataList, MetadataListItem } from '@astryxdesign/core/MetadataList';
|
|
10
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
11
|
+
import { Token } from '@astryxdesign/core/Token';
|
|
12
|
+
import { Tooltip as HoverTip } from '@astryxdesign/core/Tooltip';
|
|
13
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
14
|
+
import { IconArrowLeft, IconX } from '@tabler/icons-react';
|
|
15
|
+
import { inlineContent, TRACE_ATTRIBUTE_GROUPS, TRACE_FIELDS, TRACE_SPAN_TYPES, TRACE_STATUS, traceAttributeMeta, traceEventAttributeMeta, traceEventMeta, } from '@theoremjs/agents';
|
|
16
|
+
import { messageText, storedValue, traceActor, traceOutcome } from "../client/trace-story.js";
|
|
17
|
+
import { nanosToMs } from "../client/trace-view.js";
|
|
18
|
+
import { ShapedData } from "./ShapedData.js";
|
|
19
|
+
import { ActorMark } from "./TraceStory.js";
|
|
20
|
+
import { attributeSections, TRACE_LABEL_PX, TraceAttributeList, TraceValue, useTraceFormat } from "./TraceValues.js";
|
|
21
|
+
/** A span's own field, read as an attribute of that format. */
|
|
22
|
+
function fieldMeta(field, format) {
|
|
23
|
+
return { ...field, format, group: 'record' };
|
|
24
|
+
}
|
|
25
|
+
const SPAN_FIELD_META = {
|
|
26
|
+
type: { ...fieldMeta(TRACE_FIELDS.type, 'text'), options: TRACE_SPAN_TYPES },
|
|
27
|
+
status: { ...fieldMeta(TRACE_FIELDS.status, 'text'), options: TRACE_STATUS },
|
|
28
|
+
start: fieldMeta(TRACE_FIELDS.start, 'time'),
|
|
29
|
+
traceId: fieldMeta(TRACE_FIELDS.traceId, 'id'),
|
|
30
|
+
spanId: fieldMeta(TRACE_FIELDS.spanId, 'id'),
|
|
31
|
+
};
|
|
32
|
+
function spanFieldMeta(key) {
|
|
33
|
+
return key in SPAN_FIELD_META ? SPAN_FIELD_META[key] : undefined;
|
|
34
|
+
}
|
|
35
|
+
function Section({ title, children }) {
|
|
36
|
+
return (_jsx(Collapsible, { defaultIsOpen: false, trigger: _jsx(Text, { weight: "medium", children: title }), children: children }));
|
|
37
|
+
}
|
|
38
|
+
function EventList({ node }) {
|
|
39
|
+
const format = useTraceFormat();
|
|
40
|
+
return (_jsx(VStack, { gap: 3, children: node.span.events.map((event, index) => {
|
|
41
|
+
const offset = format.t('@theorem.panel.trace.offset', {
|
|
42
|
+
duration: format.duration(nanosToMs(event.timeUnixNano) - node.startMs),
|
|
43
|
+
});
|
|
44
|
+
const label = traceEventMeta(event.name)?.label ?? event.name;
|
|
45
|
+
return (_jsxs(VStack, { gap: 1, children: [_jsxs(HStack, { gap: 2, align: "center", children: [_jsx(Text, { weight: "medium", children: label }), _jsx(Text, { type: "supporting", hasTabularNumbers: true, children: offset })] }), _jsx(TraceAttributeList, { record: node.record, attributes: event.attributes, metaOf: (key) => traceEventAttributeMeta(event.name, key) })] }, `${event.name}:${event.timeUnixNano}:${index}`));
|
|
46
|
+
}) }));
|
|
47
|
+
}
|
|
48
|
+
function LinkList({ node }) {
|
|
49
|
+
return (_jsx(VStack, { gap: 3, children: node.span.links.map((link) => (_jsx(TraceAttributeList, { record: node.record, attributes: { traceId: link.traceId, spanId: link.spanId, ...link.attributes }, metaOf: (key) => spanFieldMeta(key) ?? traceAttributeMeta(key) }, `${link.traceId}:${link.spanId}`))) }));
|
|
50
|
+
}
|
|
51
|
+
const SPAN_FIELD_VALUES = {
|
|
52
|
+
type: (node) => node.meta.type,
|
|
53
|
+
start: (node) => node.span.startTimeUnixNano,
|
|
54
|
+
traceId: (node) => node.span.traceId,
|
|
55
|
+
spanId: (node) => node.span.spanId,
|
|
56
|
+
};
|
|
57
|
+
/** What the span was, how long it took, what it cost and when it started. */
|
|
58
|
+
function spanFacts(node, turnStartMs, format) {
|
|
59
|
+
const cost = node.span.attributes['theorem.usage.cost_usd'];
|
|
60
|
+
return [
|
|
61
|
+
node.meta.label,
|
|
62
|
+
format.duration(node.durationMs),
|
|
63
|
+
...(typeof cost === 'number' ? [format.usd(cost)] : []),
|
|
64
|
+
format.t('@theorem.panel.trace.offset', { duration: format.duration(node.startMs - turnStartMs) }),
|
|
65
|
+
].join(format.t('@theorem.panel.trace.separator'));
|
|
66
|
+
}
|
|
67
|
+
function OutcomeBadge({ outcome }) {
|
|
68
|
+
if (!outcome)
|
|
69
|
+
return null;
|
|
70
|
+
return (_jsx(HoverTip, { content: outcome.doc ?? outcome.label, children: _jsx(Badge, { variant: outcome.tone, label: outcome.label }) }));
|
|
71
|
+
}
|
|
72
|
+
/** The span's status message, unless the outcome badge already says it. */
|
|
73
|
+
function StatusMessage({ node, outcome }) {
|
|
74
|
+
const message = node.span.status.message;
|
|
75
|
+
if (!message || message.toLowerCase() === outcome?.label.toLowerCase())
|
|
76
|
+
return null;
|
|
77
|
+
return _jsx(Text, { color: "secondary", children: message });
|
|
78
|
+
}
|
|
79
|
+
/** Back (or close), then who acted, what it was, how it ended, how long it took and what it cost. */
|
|
80
|
+
function SpanHeader({ node, turnStartMs, onBack, isClose }) {
|
|
81
|
+
const format = useTraceFormat();
|
|
82
|
+
const outcome = traceOutcome(node);
|
|
83
|
+
return (_jsxs(VStack, { gap: 2, children: [_jsx(HStack, { children: _jsx(Button, { label: format.t(isClose ? '@theorem.panel.trace.close' : '@theorem.panel.trace.back'), variant: "ghost", size: "sm", icon: _jsx(Icon, { icon: isClose ? IconX : IconArrowLeft }), onClick: onBack }) }), _jsx(Item, { startContent: _jsx(ActorMark, { actor: traceActor(node) }), label: _jsx(Text, { weight: "semibold", children: node.meta.subject ?? node.meta.label }), description: spanFacts(node, turnStartMs, format), endContent: _jsx(OutcomeBadge, { outcome: outcome }), align: "start" }), _jsx(StatusMessage, { node: node, outcome: outcome })] }));
|
|
84
|
+
}
|
|
85
|
+
function Panel({ title, doc, children }) {
|
|
86
|
+
return (_jsx(Card, { padding: 3, variant: "muted", style: { background: 'var(--color-background-surface)' }, children: _jsxs(VStack, { gap: 2, children: [_jsx(HoverTip, { content: doc ?? title, children: _jsx(Text, { type: "supporting", color: "secondary", children: title }) }), children] }) }));
|
|
87
|
+
}
|
|
88
|
+
function Prose({ text }) {
|
|
89
|
+
return _jsx("span", { style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }, children: text });
|
|
90
|
+
}
|
|
91
|
+
function parsed(value) {
|
|
92
|
+
if (typeof value !== 'string')
|
|
93
|
+
return value;
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(value);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function isObject(value) {
|
|
102
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
103
|
+
}
|
|
104
|
+
function ToolCallPart({ part }) {
|
|
105
|
+
return (_jsxs(VStack, { gap: 1, children: [_jsx(Text, { weight: "medium", children: String(part.name ?? '') }), _jsx(ShapedData, { value: parsed(part.arguments) })] }));
|
|
106
|
+
}
|
|
107
|
+
/** One message part: its text, or a tool call's name and arguments, or what a tool returned. */
|
|
108
|
+
function PartView({ part }) {
|
|
109
|
+
if (part.type === 'text' && typeof part.content === 'string')
|
|
110
|
+
return _jsx(Prose, { text: part.content });
|
|
111
|
+
if (part.type === 'tool_call')
|
|
112
|
+
return _jsx(ToolCallPart, { part: part });
|
|
113
|
+
const body = part.response ?? part.result ?? part.content;
|
|
114
|
+
return body === undefined ? null : _jsx(ShapedData, { value: parsed(body) });
|
|
115
|
+
}
|
|
116
|
+
/** Messages as the model saw them: each one's role, then its parts. */
|
|
117
|
+
function MessageList({ messages }) {
|
|
118
|
+
return (_jsx(VStack, { gap: 3, children: messages.map((message, index) => (_jsxs(VStack, { gap: 1, children: [_jsx(HStack, { children: _jsx(Token, { label: String(message.role ?? ''), size: "sm", color: "gray" }) }), Array.isArray(message.parts)
|
|
119
|
+
? message.parts.filter(isObject).map((part, at) => _jsx(PartView, { part: part }, at))
|
|
120
|
+
: null] }, index))) }));
|
|
121
|
+
}
|
|
122
|
+
function messagesOf(node, key) {
|
|
123
|
+
const value = inlineContent(node.record, node.span.attributes[key]);
|
|
124
|
+
return Array.isArray(value) ? value.filter(isObject) : [];
|
|
125
|
+
}
|
|
126
|
+
function label(key) {
|
|
127
|
+
const meta = traceAttributeMeta(key);
|
|
128
|
+
return meta ? { title: meta.label, doc: meta.doc } : { title: key };
|
|
129
|
+
}
|
|
130
|
+
/** A stored value under its attribute's name; nothing when the span has none. */
|
|
131
|
+
function DataPanel({ attribute, value }) {
|
|
132
|
+
if (value === undefined)
|
|
133
|
+
return null;
|
|
134
|
+
return (_jsx(Panel, { ...label(attribute), children: _jsx(ShapedData, { value: value }) }));
|
|
135
|
+
}
|
|
136
|
+
/** Text under its attribute's name; nothing when there is none. */
|
|
137
|
+
function ProsePanel({ attribute, text }) {
|
|
138
|
+
if (typeof text !== 'string' || !text)
|
|
139
|
+
return null;
|
|
140
|
+
return (_jsx(Panel, { ...label(attribute), children: _jsx(Prose, { text: text }) }));
|
|
141
|
+
}
|
|
142
|
+
function TurnIO({ node }) {
|
|
143
|
+
return (_jsxs(_Fragment, { children: [_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') })] }));
|
|
144
|
+
}
|
|
145
|
+
function CallIO({ node }) {
|
|
146
|
+
const input = messagesOf(node, 'gen_ai.input.messages');
|
|
147
|
+
const output = messagesOf(node, 'gen_ai.output.messages');
|
|
148
|
+
return (_jsxs(_Fragment, { children: [output.length > 0 ? (_jsx(Panel, { ...label('gen_ai.output.messages'), children: _jsx(MessageList, { messages: output }) })) : null, input.length > 0 ? (_jsx(Collapsible, { defaultIsOpen: false, trigger: _jsx(Text, { weight: "medium", children: `${label('gen_ai.input.messages').title} · ${input.length}` }), children: _jsx(Card, { padding: 3, variant: "muted", style: { background: 'var(--color-background-surface)' }, children: _jsx(MessageList, { messages: input }) }) })) : null] }));
|
|
149
|
+
}
|
|
150
|
+
function ToolIO({ node }) {
|
|
151
|
+
const { attributes } = node.span;
|
|
152
|
+
const result = 'theorem.tool.data' in attributes ? 'theorem.tool.data' : 'gen_ai.tool.call.result';
|
|
153
|
+
return (_jsxs(_Fragment, { children: [_jsx(DataPanel, { attribute: "gen_ai.tool.call.arguments", value: storedValue(node, 'gen_ai.tool.call.arguments') }), _jsx(DataPanel, { attribute: result, value: storedValue(node, result) }), _jsx(ProsePanel, { attribute: "exception.message", text: 'exception.message' in attributes ? storedValue(node, 'exception.message') : undefined })] }));
|
|
154
|
+
}
|
|
155
|
+
/** What went in and what came out, read the way a person would: text, messages, or a tool's data. */
|
|
156
|
+
function SpanIO({ node }) {
|
|
157
|
+
switch (node.meta.type) {
|
|
158
|
+
case 'turn':
|
|
159
|
+
return _jsx(TurnIO, { node: node });
|
|
160
|
+
case 'call':
|
|
161
|
+
case 'response':
|
|
162
|
+
return _jsx(CallIO, { node: node });
|
|
163
|
+
case 'tool':
|
|
164
|
+
return _jsx(ToolIO, { node: node });
|
|
165
|
+
default:
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** The span's own fields: its type, start and IDs. */
|
|
170
|
+
function SpanFields({ node }) {
|
|
171
|
+
return (_jsx(MetadataList, { columns: "single", label: { position: 'start', width: TRACE_LABEL_PX }, children: Object.keys(SPAN_FIELD_VALUES).map((key) => (_jsx(MetadataListItem, { label: SPAN_FIELD_META[key].label, children: _jsx(TraceValue, { record: node.record, meta: SPAN_FIELD_META[key], value: SPAN_FIELD_VALUES[key](node) }) }, key))) }));
|
|
172
|
+
}
|
|
173
|
+
/** The record's host resource and request metadata, shown on the span that roots the record. */
|
|
174
|
+
function RecordSections({ node }) {
|
|
175
|
+
const { record } = node;
|
|
176
|
+
if (record.spans[0]?.spanId !== node.span.spanId)
|
|
177
|
+
return null;
|
|
178
|
+
return (_jsxs(_Fragment, { children: [Object.keys(record.resource).length > 0 ? (_jsx(Section, { title: TRACE_FIELDS.resource.label, children: _jsx(TraceAttributeList, { record: record, attributes: record.resource, metaOf: traceAttributeMeta }) })) : null, record.metadata ? (_jsx(Section, { title: TRACE_FIELDS.metadata.label, children: _jsx(TraceAttributeList, { record: record, attributes: record.metadata, metaOf: () => undefined }) })) : null] }));
|
|
179
|
+
}
|
|
180
|
+
/** One span in full: who acted and how it ended, what went in and came out, its tokens, then every detail it recorded. */
|
|
181
|
+
export function TraceSpanDetail({ node, turnStartMs, isClose = false, onBack, onOpen, }) {
|
|
182
|
+
const format = useTraceFormat();
|
|
183
|
+
const { t } = format;
|
|
184
|
+
const { span, record } = node;
|
|
185
|
+
const sections = attributeSections(span.attributes);
|
|
186
|
+
const usage = sections.find((section) => section.key === 'usage');
|
|
187
|
+
return (_jsxs(VStack, { gap: 4, children: [_jsx(SpanHeader, { node: node, turnStartMs: turnStartMs, onBack: onBack, isClose: isClose }), _jsx(SpanIO, { node: node }), usage ? (_jsx(Panel, { title: TRACE_ATTRIBUTE_GROUPS.usage.label, doc: TRACE_ATTRIBUTE_GROUPS.usage.doc, children: _jsx(TraceAttributeList, { record: record, attributes: usage.attributes, metaOf: traceAttributeMeta }) })) : null, node.children.length > 0 ? (_jsx(Panel, { title: TRACE_FIELDS.children.label, doc: TRACE_FIELDS.children.doc, children: _jsx(VStack, { gap: 0, children: node.children.map((child) => (_jsx(Item, { startContent: _jsx(ActorMark, { actor: traceActor(child) }), label: child.meta.subject ?? child.meta.label, description: child.meta.label, endContent: _jsx(Text, { type: "supporting", hasTabularNumbers: true, children: format.duration(child.durationMs) }), density: "compact", onClick: () => onOpen(child) }, child.id))) }) })) : null, _jsx(Collapsible, { defaultIsOpen: false, trigger: _jsx(Text, { weight: "medium", children: t('@theorem.panel.trace.details') }), children: _jsxs(VStack, { gap: 3, children: [_jsx(SpanFields, { node: node }), sections.filter((section) => section !== usage).map((section) => (_jsx(Section, { title: section.key === 'other' ? t('@theorem.panel.trace.other') : TRACE_ATTRIBUTE_GROUPS[section.key].label, children: _jsx(TraceAttributeList, { record: record, attributes: section.attributes, metaOf: traceAttributeMeta }) }, section.key))), span.events.length > 0 ? (_jsx(Section, { title: TRACE_FIELDS.events.label, children: _jsx(EventList, { node: node }) })) : null, span.links.length > 0 ? (_jsx(Section, { title: TRACE_FIELDS.links.label, children: _jsx(LinkList, { node: node }) })) : null, _jsx(RecordSections, { node: node })] }) })] }));
|
|
188
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IconUser } from '@tabler/icons-react';
|
|
2
|
+
import { type TraceActor, type TraceStep } from '../client/trace-story.ts';
|
|
3
|
+
import type { TraceNode } from '../client/trace-view.ts';
|
|
4
|
+
/** An actor's icon, in the text's own colour. */
|
|
5
|
+
export declare function ActorMark({ actor, icon }: {
|
|
6
|
+
actor: TraceActor;
|
|
7
|
+
icon?: typeof IconUser;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
/**
|
|
10
|
+
* The turn, told in order: what the user asked, each model call with the tool
|
|
11
|
+
* calls it asked for set in under it, what changed course, and how it ended.
|
|
12
|
+
* A line opens its span.
|
|
13
|
+
*/
|
|
14
|
+
export declare function TraceStory({ steps, selectedId, matches, onSelect, }: {
|
|
15
|
+
steps: readonly TraceStep[];
|
|
16
|
+
selectedId: string | undefined;
|
|
17
|
+
/** Spans the search matched; the others dim. Absent when nothing is searched. */
|
|
18
|
+
matches: ReadonlySet<string> | undefined;
|
|
19
|
+
onSelect: (node: TraceNode) => void;
|
|
20
|
+
}): import("react").JSX.Element;
|
package/ui/TraceStory.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Badge } from '@astryxdesign/core/Badge';
|
|
3
|
+
import { HStack } from '@astryxdesign/core/HStack';
|
|
4
|
+
import { Icon } from '@astryxdesign/core/Icon';
|
|
5
|
+
import { Item } from '@astryxdesign/core/Item';
|
|
6
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
7
|
+
import { Tooltip } from '@astryxdesign/core/Tooltip';
|
|
8
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
9
|
+
import { IconAlertTriangle, IconBolt, IconInputAi, IconMessageCircle, IconServer, IconTool, IconUser, } from '@tabler/icons-react';
|
|
10
|
+
import { toolArguments, traceActor, traceOutcome } from "../client/trace-story.js";
|
|
11
|
+
import { useTraceFormat } from "./TraceValues.js";
|
|
12
|
+
/** Each actor's mark, shared by the story and the turn list. */
|
|
13
|
+
const ACTOR_ICON = {
|
|
14
|
+
user: IconUser,
|
|
15
|
+
model: IconInputAi,
|
|
16
|
+
tool: IconTool,
|
|
17
|
+
host: IconServer,
|
|
18
|
+
theorem: IconBolt,
|
|
19
|
+
error: IconAlertTriangle,
|
|
20
|
+
};
|
|
21
|
+
/** An actor's icon, in the text's own colour. */
|
|
22
|
+
export function ActorMark({ actor, icon }) {
|
|
23
|
+
return _jsx(Icon, { icon: icon ?? ACTOR_ICON[actor], size: "sm", color: "secondary" });
|
|
24
|
+
}
|
|
25
|
+
/** "city: Lisbon · to: USD": a tool call's plain arguments on one line. */
|
|
26
|
+
function argumentsLine(node, separator) {
|
|
27
|
+
return toolArguments(node)
|
|
28
|
+
.map(([key, value]) => (key ? `${key}: ${value}` : value))
|
|
29
|
+
.join(separator);
|
|
30
|
+
}
|
|
31
|
+
/** Duration, then cost when the span reported one. */
|
|
32
|
+
function StepMeasure({ node }) {
|
|
33
|
+
const format = useTraceFormat();
|
|
34
|
+
const cost = node.span.attributes['theorem.usage.cost_usd'];
|
|
35
|
+
return (_jsxs(VStack, { gap: 0, hAlign: "end", children: [_jsx(Text, { type: "supporting", hasTabularNumbers: true, children: format.duration(node.durationMs) }), typeof cost === 'number' ? (_jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: format.usd(cost) })) : null] }));
|
|
36
|
+
}
|
|
37
|
+
function Offset({ ms }) {
|
|
38
|
+
const format = useTraceFormat();
|
|
39
|
+
return (_jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: format.t('@theorem.panel.trace.offset', { duration: format.duration(ms) }) }));
|
|
40
|
+
}
|
|
41
|
+
/** A span step's line: the tools a call asked for or that it wrote, a tool's arguments, else the span's own label. */
|
|
42
|
+
function spanLine(step, format) {
|
|
43
|
+
const { node } = step;
|
|
44
|
+
if (node.meta.type === 'tool')
|
|
45
|
+
return argumentsLine(node, format.t('@theorem.panel.trace.separator'));
|
|
46
|
+
if (node.meta.type !== 'call' && node.meta.type !== 'response')
|
|
47
|
+
return node.meta.label;
|
|
48
|
+
return step.requested > 0
|
|
49
|
+
? format.t('@theorem.panel.trace.story.requested', { count: step.requested })
|
|
50
|
+
: format.t('@theorem.panel.trace.story.wrote');
|
|
51
|
+
}
|
|
52
|
+
/** What a span step says under its name: how it ended when that is news, then its line. */
|
|
53
|
+
function spanDescription(step, format) {
|
|
54
|
+
const outcome = traceOutcome(step.node);
|
|
55
|
+
const line = spanLine(step, format);
|
|
56
|
+
if (!outcome)
|
|
57
|
+
return line;
|
|
58
|
+
return (_jsxs(HStack, { gap: 1, align: "center", wrap: "wrap", children: [_jsx(Tooltip, { content: outcome.doc ?? outcome.label, children: _jsx(Badge, { variant: outcome.tone, label: outcome.label }) }), line ? _jsx(Text, { type: "supporting", children: line }) : null] }));
|
|
59
|
+
}
|
|
60
|
+
function Quote({ text }) {
|
|
61
|
+
return (_jsx(Text, { color: "secondary", maxLines: 3, hasTruncateTooltip: false, children: text }));
|
|
62
|
+
}
|
|
63
|
+
function AskItem({ step, isSelected, select }) {
|
|
64
|
+
const { t } = useTraceFormat();
|
|
65
|
+
return (_jsx(Item, { startContent: _jsx(ActorMark, { actor: "user" }), label: _jsx(Text, { weight: "medium", children: t('@theorem.panel.trace.story.asked') }), description: _jsx(Quote, { text: step.text }), endContent: _jsx(Offset, { ms: 0 }), align: "start", density: "compact", onClick: select, isSelected: isSelected }));
|
|
66
|
+
}
|
|
67
|
+
function SpanItem({ step, isSelected, select }) {
|
|
68
|
+
const format = useTraceFormat();
|
|
69
|
+
return (_jsx(Item, { startContent: _jsx(ActorMark, { actor: traceActor(step.node) }), label: _jsx(Text, { weight: "medium", maxLines: 1, children: step.node.meta.subject ?? step.node.meta.label }), description: spanDescription(step, format), endContent: _jsx(StepMeasure, { node: step.node }), align: "start", density: "compact", onClick: select, isSelected: isSelected }));
|
|
70
|
+
}
|
|
71
|
+
function EventItem({ step, isSelected, select }) {
|
|
72
|
+
const { t } = useTraceFormat();
|
|
73
|
+
return (_jsx(Item, { startContent: _jsx(ActorMark, { actor: "theorem" }), label: _jsx(Tooltip, { content: step.doc, children: _jsx(Text, { weight: "medium", children: step.label }) }), description: step.detail.join(t('@theorem.panel.trace.separator')), endContent: _jsx(Offset, { ms: step.offsetMs }), align: "start", density: "compact", onClick: select, isSelected: isSelected }));
|
|
74
|
+
}
|
|
75
|
+
function AnswerItem({ step, isSelected, select }) {
|
|
76
|
+
const { t } = useTraceFormat();
|
|
77
|
+
const failed = step.node.span.status.code === 'ERROR';
|
|
78
|
+
const answer = step.text ? _jsx(Quote, { text: step.text }) : undefined;
|
|
79
|
+
return (_jsx(Item, { startContent: _jsx(ActorMark, { actor: failed ? 'error' : 'model', icon: failed ? undefined : IconMessageCircle }), label: _jsx(Text, { weight: "medium", children: t(failed ? '@theorem.panel.trace.story.failed' : '@theorem.panel.trace.story.answered') }), description: failed ? traceOutcome(step.node)?.label : answer, endContent: _jsx(Offset, { ms: step.offsetMs }), align: "start", density: "compact", onClick: select, isSelected: isSelected }));
|
|
80
|
+
}
|
|
81
|
+
function StepBody({ step, isSelected, select }) {
|
|
82
|
+
switch (step.kind) {
|
|
83
|
+
case 'ask':
|
|
84
|
+
return _jsx(AskItem, { step: step, isSelected: isSelected, select: select });
|
|
85
|
+
case 'span':
|
|
86
|
+
return _jsx(SpanItem, { step: step, isSelected: isSelected, select: select });
|
|
87
|
+
case 'event':
|
|
88
|
+
return _jsx(EventItem, { step: step, isSelected: isSelected, select: select });
|
|
89
|
+
case 'answer':
|
|
90
|
+
return _jsx(AnswerItem, { step: step, isSelected: isSelected, select: select });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function StepItem({ step, isSelected, isDimmed, onSelect, }) {
|
|
94
|
+
return (_jsx("div", { style: {
|
|
95
|
+
paddingInlineStart: step.kind === 'span' && step.nested ? 20 : 0,
|
|
96
|
+
opacity: isDimmed ? 0.35 : 1,
|
|
97
|
+
transition: 'opacity 120ms ease',
|
|
98
|
+
}, children: _jsx(StepBody, { step: step, isSelected: isSelected, select: () => onSelect(step.node) }) }));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The turn, told in order: what the user asked, each model call with the tool
|
|
102
|
+
* calls it asked for set in under it, what changed course, and how it ended.
|
|
103
|
+
* A line opens its span.
|
|
104
|
+
*/
|
|
105
|
+
export function TraceStory({ steps, selectedId, matches, onSelect, }) {
|
|
106
|
+
return (_jsx(VStack, { gap: 0, children: steps.map((step) => (_jsx(StepItem, { step: step, isSelected: step.kind === 'span' ? step.node.id === selectedId : false, isDimmed: matches !== undefined && !matches.has(step.node.id), onSelect: onSelect }, step.id))) }));
|
|
107
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type TraceAttributeGroup, type TraceAttributeMeta, type TraceRecord } from '@theoremjs/agents';
|
|
2
|
+
import { type TraceSum, type TraceTokenSum } from '../client/trace-view.ts';
|
|
3
|
+
import { type LabelText } from './labels.ts';
|
|
4
|
+
/** Width of every label column in the panel's lists, so they line up. */
|
|
5
|
+
export declare const TRACE_LABEL_PX = 120;
|
|
6
|
+
export type AttributeSection = {
|
|
7
|
+
key: TraceAttributeGroup | 'other';
|
|
8
|
+
attributes: Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
/** A span's attributes by catalog group, in the catalog's order; keys it does not name last. */
|
|
11
|
+
export declare function attributeSections(attributes: Readonly<Record<string, unknown>>): AttributeSection[];
|
|
12
|
+
/** Formats for trace values, in the viewer's locale. */
|
|
13
|
+
export interface TraceFormat {
|
|
14
|
+
t: LabelText;
|
|
15
|
+
number(value: number): string;
|
|
16
|
+
usd(value: number): string;
|
|
17
|
+
duration(ms: number): string;
|
|
18
|
+
list(items: readonly string[]): string;
|
|
19
|
+
/** A total, worded as partial or estimated when it is. */
|
|
20
|
+
sum(sum: TraceSum | TraceTokenSum, format: (value: number) => string): string;
|
|
21
|
+
}
|
|
22
|
+
export declare function useTraceFormat(): TraceFormat;
|
|
23
|
+
/** One attribute value, read by its catalog entry; a key the catalog does not name reads by its shape. */
|
|
24
|
+
export declare function TraceValue({ record, meta, value, }: {
|
|
25
|
+
record: TraceRecord;
|
|
26
|
+
meta: TraceAttributeMeta | undefined;
|
|
27
|
+
value: unknown;
|
|
28
|
+
}): import("react").JSX.Element;
|
|
29
|
+
/** Attributes as label and value rows, worded by `metaOf`; a key it does not name shows as recorded. */
|
|
30
|
+
export declare function TraceAttributeList({ record, attributes, metaOf, maxNumOfItems, title, }: {
|
|
31
|
+
record: TraceRecord;
|
|
32
|
+
attributes: Readonly<Record<string, unknown>>;
|
|
33
|
+
metaOf: (key: string) => TraceAttributeMeta | undefined;
|
|
34
|
+
maxNumOfItems?: number;
|
|
35
|
+
title?: string;
|
|
36
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '@astryxdesign/core/Button';
|
|
3
|
+
import { CodeBlock } from '@astryxdesign/core/CodeBlock';
|
|
4
|
+
import { useLocale } from '@astryxdesign/core/i18n';
|
|
5
|
+
import { MetadataList, MetadataListItem } from '@astryxdesign/core/MetadataList';
|
|
6
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
7
|
+
import { Timestamp } from '@astryxdesign/core/Timestamp';
|
|
8
|
+
import { Tooltip } from '@astryxdesign/core/Tooltip';
|
|
9
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
10
|
+
import { useMemo, useState } from 'react';
|
|
11
|
+
import { inlineContent, TRACE_ATTRIBUTE_GROUPS, traceAttributeMeta, } from '@theoremjs/agents';
|
|
12
|
+
import { nanosToMs, traceValueShape } from "../client/trace-view.js";
|
|
13
|
+
import { workDuration } from "./labels.js";
|
|
14
|
+
import { useLabels } from "./labels-provider.js";
|
|
15
|
+
/** Width of every label column in the panel's lists, so they line up. */
|
|
16
|
+
export const TRACE_LABEL_PX = 120;
|
|
17
|
+
/** A span's attributes by catalog group, in the catalog's order; keys it does not name last. */
|
|
18
|
+
export function attributeSections(attributes) {
|
|
19
|
+
const byGroup = new Map();
|
|
20
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
21
|
+
const group = traceAttributeMeta(key)?.group ?? 'other';
|
|
22
|
+
byGroup.set(group, { ...byGroup.get(group), [key]: value });
|
|
23
|
+
}
|
|
24
|
+
const order = [...Object.keys(TRACE_ATTRIBUTE_GROUPS), 'other'];
|
|
25
|
+
return order.flatMap((key) => {
|
|
26
|
+
const grouped = byGroup.get(key);
|
|
27
|
+
return grouped ? [{ key, attributes: grouped }] : [];
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export function useTraceFormat() {
|
|
31
|
+
const t = useLabels();
|
|
32
|
+
const locale = useLocale();
|
|
33
|
+
return useMemo(() => {
|
|
34
|
+
const number = new Intl.NumberFormat(locale);
|
|
35
|
+
const usd = new Intl.NumberFormat(locale, { style: 'currency', currency: 'USD', maximumFractionDigits: 6 });
|
|
36
|
+
const list = new Intl.ListFormat(locale, { type: 'conjunction' });
|
|
37
|
+
return {
|
|
38
|
+
t,
|
|
39
|
+
number: (value) => number.format(value),
|
|
40
|
+
usd: (value) => usd.format(value),
|
|
41
|
+
duration: (ms) => workDuration(t, ms),
|
|
42
|
+
list: (items) => list.format(items),
|
|
43
|
+
sum(total, format) {
|
|
44
|
+
const value = 'estimated' in total && total.estimated ? t('@theorem.panel.trace.about', { value: format(total.value) }) : format(total.value);
|
|
45
|
+
return total.complete ? value : t('@theorem.panel.trace.at_least', { value });
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}, [t, locale]);
|
|
49
|
+
}
|
|
50
|
+
/** A value as it was stored, with its references rebuilt, shown on click. */
|
|
51
|
+
function StoredText({ record, value }) {
|
|
52
|
+
const { t } = useTraceFormat();
|
|
53
|
+
const [isShown, setIsShown] = useState(false);
|
|
54
|
+
const inlined = inlineContent(record, value);
|
|
55
|
+
return (_jsxs(VStack, { gap: 1, children: [_jsx(Button, { label: t(isShown ? '@theorem.panel.trace.hide_text' : '@theorem.panel.trace.show_text'), variant: "ghost", size: "sm", "aria-expanded": isShown, onClick: () => setIsShown((shown) => !shown) }), isShown ? (_jsx(CodeBlock, { code: typeof inlined === 'string' ? inlined : JSON.stringify(inlined, null, 2), language: typeof inlined === 'string' ? 'text' : 'json', isWrapped: true, size: "sm", hasCopyButton: true })) : null] }));
|
|
56
|
+
}
|
|
57
|
+
/** An option's label, with its meaning on hover. */
|
|
58
|
+
function OptionValue({ option }) {
|
|
59
|
+
return (_jsx(Tooltip, { content: option.doc, children: _jsx(Text, { children: option.label }) }));
|
|
60
|
+
}
|
|
61
|
+
function formatNumber(format, meta, value) {
|
|
62
|
+
switch (meta?.format) {
|
|
63
|
+
case 'usd':
|
|
64
|
+
return format.usd(value);
|
|
65
|
+
case 'milliseconds':
|
|
66
|
+
return format.duration(value);
|
|
67
|
+
case 'seconds':
|
|
68
|
+
return format.duration(value * 1_000);
|
|
69
|
+
default:
|
|
70
|
+
return format.number(value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function StringValue({ meta, value }) {
|
|
74
|
+
const option = meta?.options?.[value];
|
|
75
|
+
if (option)
|
|
76
|
+
return _jsx(OptionValue, { option: option });
|
|
77
|
+
if (meta?.format === 'time')
|
|
78
|
+
return _jsx(Timestamp, { value: nanosToMs(value), format: "system_date_time", size: "base", color: "primary" });
|
|
79
|
+
return _jsx(Text, { wordBreak: "break-word", children: value });
|
|
80
|
+
}
|
|
81
|
+
function ScalarValue({ meta, value }) {
|
|
82
|
+
const format = useTraceFormat();
|
|
83
|
+
if (typeof value === 'boolean')
|
|
84
|
+
return _jsx(Text, { children: format.t(value ? '@theorem.panel.trace.yes' : '@theorem.panel.trace.no') });
|
|
85
|
+
return _jsx(Text, { hasTabularNumbers: true, children: formatNumber(format, meta, value) });
|
|
86
|
+
}
|
|
87
|
+
function ListValue({ meta, value }) {
|
|
88
|
+
const format = useTraceFormat();
|
|
89
|
+
return _jsx(Text, { children: format.list(value.map((item) => meta?.options?.[item]?.label ?? item)) });
|
|
90
|
+
}
|
|
91
|
+
/** One attribute value, read by its catalog entry; a key the catalog does not name reads by its shape. */
|
|
92
|
+
export function TraceValue({ record, meta, value, }) {
|
|
93
|
+
const shape = traceValueShape(meta, value);
|
|
94
|
+
switch (shape.kind) {
|
|
95
|
+
case 'text':
|
|
96
|
+
return _jsx(StringValue, { meta: meta, value: shape.value });
|
|
97
|
+
case 'scalar':
|
|
98
|
+
return _jsx(ScalarValue, { meta: meta, value: shape.value });
|
|
99
|
+
case 'list':
|
|
100
|
+
return _jsx(ListValue, { meta: meta, value: shape.value });
|
|
101
|
+
case 'fields':
|
|
102
|
+
return (_jsx(VStack, { gap: 2, children: shape.items.map((item, index) => (_jsx(TraceAttributeList, { record: record, attributes: item, metaOf: (key) => shape.fields[key] }, index))) }));
|
|
103
|
+
case 'stored':
|
|
104
|
+
return _jsx(StoredText, { record: record, value: value });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Attributes as label and value rows, worded by `metaOf`; a key it does not name shows as recorded. */
|
|
108
|
+
export function TraceAttributeList({ record, attributes, metaOf, maxNumOfItems, title, }) {
|
|
109
|
+
const entries = Object.entries(attributes);
|
|
110
|
+
if (entries.length === 0)
|
|
111
|
+
return null;
|
|
112
|
+
return (_jsx(MetadataList, { columns: "single", label: { position: 'start', width: TRACE_LABEL_PX }, maxNumOfItems: maxNumOfItems, title: title, children: entries.map(([key, value]) => (_jsx(MetadataListItem, { label: metaOf(key)?.label ?? key, children: _jsx(TraceValue, { record: record, meta: metaOf(key), value: value }) }, key))) }));
|
|
113
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TraceNode } from '../client/trace-view.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Every span of the turn on one time axis, nested by depth and coloured by
|
|
4
|
+
* who acted. Above it, how busy the model and tools were over the turn; drag
|
|
5
|
+
* across that strip to zoom the rows into the window, as in a performance
|
|
6
|
+
* profile. A bar opens its span.
|
|
7
|
+
*/
|
|
8
|
+
export declare function TraceWaterfall({ root, selectedId, matches, onSelect, }: {
|
|
9
|
+
root: TraceNode;
|
|
10
|
+
selectedId: string | undefined;
|
|
11
|
+
matches: ReadonlySet<string> | undefined;
|
|
12
|
+
onSelect: (node: TraceNode) => void;
|
|
13
|
+
}): import("react").JSX.Element;
|