@theoremjs/react 0.0.0-stage → 0.3.1
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 +772 -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-images.d.ts +76 -0
- package/client/decision-images.js +139 -0
- package/client/decision-transport.d.ts +70 -0
- package/client/decision-transport.js +175 -0
- package/client/decode-composer-draft.d.ts +11 -0
- package/client/decode-composer-draft.js +12 -0
- package/client/embedding-input.d.ts +84 -0
- package/client/embedding-input.js +194 -0
- package/client/embedding-space.d.ts +82 -0
- package/client/embedding-space.js +389 -0
- package/client/embedding-transport.d.ts +40 -0
- package/client/embedding-transport.js +71 -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 +29 -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 +47 -0
- package/client/host-transport.d.ts +70 -0
- package/client/host-transport.js +75 -0
- package/client/image-output.d.ts +6 -0
- package/client/image-output.js +13 -0
- package/client/index.d.ts +28 -0
- package/client/index.js +22 -0
- package/client/ink-waveform.d.ts +17 -0
- package/client/ink-waveform.js +68 -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 +53 -0
- package/client/live/live-captions.js +174 -0
- package/client/live/live-mic-forward.d.ts +30 -0
- package/client/live/live-mic-forward.js +46 -0
- package/client/live/live-page-tool.d.ts +34 -0
- package/client/live/live-page-tool.js +11 -0
- package/client/live/live-state.d.ts +13 -0
- package/client/live/live-state.js +22 -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 +22 -0
- package/client/live/run-live-tool-call.js +70 -0
- package/client/live/use-live-caption-log.d.ts +17 -0
- package/client/live/use-live-caption-log.js +51 -0
- package/client/live-client.d.ts +189 -0
- package/client/live-client.js +829 -0
- package/client/live-errors.d.ts +2 -0
- package/client/live-errors.js +9 -0
- package/client/live-messages.d.ts +148 -0
- package/client/live-messages.js +100 -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/page-dispatch.d.ts +15 -0
- package/client/page-dispatch.js +63 -0
- package/client/pcm-downsample.d.ts +4 -0
- package/client/pcm-downsample.js +45 -0
- package/client/run-commit.d.ts +34 -0
- package/client/run-commit.js +49 -0
- package/client/run-session.d.ts +79 -0
- package/client/run-session.js +207 -0
- package/client/schema-fields.d.ts +63 -0
- package/client/schema-fields.js +199 -0
- package/client/shaped-data.d.ts +118 -0
- package/client/shaped-data.js +343 -0
- package/client/source-citations.d.ts +13 -0
- package/client/source-citations.js +56 -0
- package/client/space-geometry.d.ts +39 -0
- package/client/space-geometry.js +183 -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 +293 -0
- package/client/tool-resume.d.ts +50 -0
- package/client/tool-resume.js +14 -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 +440 -0
- package/client/trace-view.d.ts +130 -0
- package/client/trace-view.js +333 -0
- package/client/transcript-groups.d.ts +140 -0
- package/client/transcript-groups.js +217 -0
- package/client/transcript-schema.d.ts +10 -0
- package/client/transcript-schema.js +120 -0
- package/client/transcript-scroll.d.ts +7 -0
- package/client/transcript-scroll.js +6 -0
- package/client/transport.d.ts +264 -0
- package/client/transport.js +330 -0
- package/client/turn-client.d.ts +44 -0
- package/client/turn-client.js +88 -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 +162 -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 +21 -0
- package/components/InkWaveform.js +131 -0
- package/components/live/use-live-runner-controls.d.ts +47 -0
- package/components/live/use-live-runner-controls.js +96 -0
- package/components/live/use-live-runner-model.d.ts +49 -0
- package/components/live/use-live-runner-model.js +190 -0
- package/components/live/use-live-runner-ui.d.ts +67 -0
- package/components/live/use-live-runner-ui.js +167 -0
- package/components/live/use-live-session-client.d.ts +41 -0
- package/components/live/use-live-session-client.js +126 -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 +19 -0
- package/components/use-voice-playback.d.ts +17 -0
- package/components/use-voice-playback.js +68 -0
- package/hooks/use-described.d.ts +12 -0
- package/hooks/use-described.js +25 -0
- package/hooks/use-embedding-index.d.ts +27 -0
- package/hooks/use-embedding-index.js +43 -0
- package/hooks/use-one-call.d.ts +31 -0
- package/hooks/use-one-call.js +76 -0
- package/hooks/use-page-tool-warnings.d.ts +6 -0
- package/hooks/use-page-tool-warnings.js +25 -0
- package/hooks/use-second-ticker.d.ts +2 -0
- package/hooks/use-second-ticker.js +13 -0
- package/hooks/use-theorem-chat-actions.d.ts +79 -0
- package/hooks/use-theorem-chat-actions.js +287 -0
- package/hooks/use-theorem-chat-state.d.ts +66 -0
- package/hooks/use-theorem-chat-state.js +122 -0
- package/hooks/use-theorem-chat.d.ts +97 -0
- package/hooks/use-theorem-chat.js +528 -0
- package/hooks/use-theorem-decision.d.ts +18 -0
- package/hooks/use-theorem-decision.js +12 -0
- package/hooks/use-theorem-embedding.d.ts +18 -0
- package/hooks/use-theorem-embedding.js +12 -0
- package/hooks/use-theorem-host.d.ts +45 -0
- package/hooks/use-theorem-host.js +135 -0
- package/hooks/use-theorem-interface.d.ts +22 -0
- package/hooks/use-theorem-interface.js +15 -0
- package/hooks/use-trace-records.d.ts +4 -0
- package/hooks/use-trace-records.js +7 -0
- package/index.d.ts +17 -0
- package/index.js +17 -0
- package/live.d.ts +9 -0
- package/live.js +10 -0
- package/package.json +74 -4
- package/server/checkpoint-ownership.d.ts +6 -0
- package/server/checkpoint-ownership.js +42 -0
- package/server/conversation-store.d.ts +92 -0
- package/server/conversation-store.js +131 -0
- package/server/conversation.d.ts +193 -0
- package/server/conversation.js +552 -0
- package/server/credential-store.d.ts +24 -0
- package/server/credential-store.js +14 -0
- package/server/decision-handler.d.ts +42 -0
- package/server/decision-handler.js +46 -0
- package/server/embedding-handler.d.ts +33 -0
- package/server/embedding-handler.js +45 -0
- package/server/handler.d.ts +152 -0
- package/server/handler.js +771 -0
- package/server/json-response.d.ts +2 -0
- package/server/json-response.js +7 -0
- package/server/mod.d.ts +25 -0
- package/server/mod.js +23 -0
- package/server/page-dispatch.d.ts +22 -0
- package/server/page-dispatch.js +127 -0
- package/server/request-body.d.ts +2 -0
- package/server/request-body.js +42 -0
- package/server/request-check.d.ts +21 -0
- package/server/request-check.js +49 -0
- package/server/session-authority.d.ts +8 -0
- package/server/session-authority.js +78 -0
- package/server/session-boundary.d.ts +26 -0
- package/server/session-boundary.js +64 -0
- package/server/session-identity.d.ts +16 -0
- package/server/session-identity.js +41 -0
- package/server/session-limits.d.ts +12 -0
- package/server/session-limits.js +59 -0
- package/server/session-store.d.ts +123 -0
- package/server/session-store.js +264 -0
- package/server/single-action.d.ts +21 -0
- package/server/single-action.js +30 -0
- package/server/steer-inbox.d.ts +35 -0
- package/server/steer-inbox.js +52 -0
- package/server/tool-operations.d.ts +6 -0
- package/server/tool-operations.js +132 -0
- package/server/turn-input.d.ts +10 -0
- package/server/turn-input.js +29 -0
- package/styles/ink-controls.css +134 -0
- package/ui/CardWall.d.ts +10 -0
- package/ui/CardWall.js +61 -0
- package/ui/ChatComposerBar.d.ts +62 -0
- package/ui/ChatComposerBar.js +306 -0
- package/ui/ChatTranscript.d.ts +43 -0
- package/ui/ChatTranscript.js +527 -0
- package/ui/ConsoleFrame.d.ts +104 -0
- package/ui/ConsoleFrame.js +146 -0
- package/ui/DecisionAnswers.d.ts +53 -0
- package/ui/DecisionAnswers.js +233 -0
- package/ui/DecisionImages.d.ts +16 -0
- package/ui/DecisionImages.js +46 -0
- package/ui/EmbeddingDocuments.d.ts +78 -0
- package/ui/EmbeddingDocuments.js +278 -0
- package/ui/EmbeddingQuery.d.ts +27 -0
- package/ui/EmbeddingQuery.js +95 -0
- package/ui/EmbeddingResults.d.ts +41 -0
- package/ui/EmbeddingResults.js +165 -0
- package/ui/EmbeddingSpace.d.ts +26 -0
- package/ui/EmbeddingSpace.js +106 -0
- package/ui/LiveCaptionsPanel.d.ts +22 -0
- package/ui/LiveCaptionsPanel.js +27 -0
- package/ui/LiveRunner.d.ts +30 -0
- package/ui/LiveRunner.js +180 -0
- package/ui/PaneState.d.ts +33 -0
- package/ui/PaneState.js +50 -0
- package/ui/SchemaFields.d.ts +10 -0
- package/ui/SchemaFields.js +150 -0
- package/ui/ShapedData.d.ts +11 -0
- package/ui/ShapedData.js +196 -0
- package/ui/SidePanel.d.ts +126 -0
- package/ui/SidePanel.js +123 -0
- package/ui/StateFields.d.ts +44 -0
- package/ui/StateFields.js +139 -0
- package/ui/TheoremChat.d.ts +122 -0
- package/ui/TheoremChat.js +134 -0
- package/ui/TheoremDecision.d.ts +30 -0
- package/ui/TheoremDecision.js +130 -0
- package/ui/TheoremEmbedding.d.ts +27 -0
- package/ui/TheoremEmbedding.js +122 -0
- package/ui/TheoremHost.d.ts +55 -0
- package/ui/TheoremHost.js +240 -0
- package/ui/ToolGateCard.d.ts +32 -0
- package/ui/ToolGateCard.js +107 -0
- package/ui/ToolResult.d.ts +10 -0
- package/ui/ToolResult.js +62 -0
- package/ui/ToolResultCharts.d.ts +5 -0
- package/ui/ToolResultCharts.js +103 -0
- package/ui/TraceCharts.d.ts +13 -0
- package/ui/TraceCharts.js +266 -0
- package/ui/TraceGuardrails.d.ts +11 -0
- package/ui/TraceGuardrails.js +74 -0
- package/ui/TraceInspector.d.ts +16 -0
- package/ui/TraceInspector.js +206 -0
- package/ui/TraceInspectorPanel.d.ts +46 -0
- package/ui/TraceInspectorPanel.js +95 -0
- package/ui/TraceOverview.d.ts +38 -0
- package/ui/TraceOverview.js +126 -0
- package/ui/TraceSpanDetail.d.ts +10 -0
- package/ui/TraceSpanDetail.js +190 -0
- package/ui/TraceStory.d.ts +20 -0
- package/ui/TraceStory.js +108 -0
- package/ui/TraceValues.d.ts +36 -0
- package/ui/TraceValues.js +123 -0
- package/ui/TraceWaterfall.d.ts +13 -0
- package/ui/TraceWaterfall.js +135 -0
- package/ui/VoiceNote.d.ts +12 -0
- package/ui/VoiceNote.js +28 -0
- package/ui/approval-heading.d.ts +4 -0
- package/ui/approval-heading.js +9 -0
- package/ui/arrive.d.ts +18 -0
- package/ui/arrive.js +46 -0
- package/ui/built/theme.css +792 -0
- package/ui/built/theme.css.d.ts +9 -0
- package/ui/built/theorem.d.ts +13 -0
- package/ui/built/theorem.js +660 -0
- package/ui/built/theorem.variants.d.ts +22 -0
- package/ui/code-language.d.ts +12 -0
- package/ui/code-language.js +50 -0
- package/ui/disclosure-motion.d.ts +6 -0
- package/ui/disclosure-motion.js +207 -0
- package/ui/embedding-space-gl.d.ts +37 -0
- package/ui/embedding-space-gl.js +663 -0
- package/ui/embedding-space-view.d.ts +8 -0
- package/ui/embedding-space-view.js +8 -0
- package/ui/glide.d.ts +7 -0
- package/ui/glide.js +44 -0
- package/ui/icons.d.ts +4 -0
- package/ui/icons.js +38 -0
- package/ui/index.d.ts +36 -0
- package/ui/index.js +39 -0
- package/ui/labels-provider.d.ts +16 -0
- package/ui/labels-provider.js +64 -0
- package/ui/labels.d.ts +1442 -0
- package/ui/labels.js +1443 -0
- package/ui/markdown-plugins.d.ts +9 -0
- package/ui/markdown-plugins.js +13 -0
- package/ui/mcp-icon.d.ts +6 -0
- package/ui/mcp-icon.js +8 -0
- package/ui/row-keys.d.ts +19 -0
- package/ui/row-keys.js +33 -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 +105 -0
- package/ui/trace-palette.d.ts +10 -0
- package/ui/trace-palette.js +10 -0
- package/ui/trace-patterns.d.ts +40 -0
- package/ui/trace-patterns.js +57 -0
- package/ui/transcript-copy-text.d.ts +4 -0
- package/ui/transcript-copy-text.js +40 -0
- package/ui/wall-columns.d.ts +8 -0
- package/ui/wall-columns.js +19 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Grid } from '@astryxdesign/core/Grid';
|
|
3
|
+
import { HStack } from '@astryxdesign/core/HStack';
|
|
4
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
5
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
import { Bar, BarChart, Cell, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
|
|
8
|
+
import { isCallTrace, traceActor, } from "../client/trace-story.js";
|
|
9
|
+
import { traceSpans } from "../client/trace-view.js";
|
|
10
|
+
import { ChartFrame, ChartTip, FIRST_WIDTH_PX } from "./TraceOverview.js";
|
|
11
|
+
import { useTraceFormat } from "./TraceValues.js";
|
|
12
|
+
import { ACTOR_FILL, ChartCard, TRACE_GAP, TRACE_RADIUS, TraceSwatch, traceFill, } from "./trace-patterns.js";
|
|
13
|
+
/** Height of each column chart's plot, in pixels. */
|
|
14
|
+
const CHART_PX = 120;
|
|
15
|
+
/** Height of a share bar, in pixels. */
|
|
16
|
+
const SHARE_PX = 12;
|
|
17
|
+
/** Height of one row of the slowest-steps chart, in pixels. */
|
|
18
|
+
const SLOW_ROW_PX = 22;
|
|
19
|
+
/** Most steps the slowest-steps chart ranks. */
|
|
20
|
+
const SLOWEST = 5;
|
|
21
|
+
const TICK = { fill: 'var(--color-text-secondary)', fontSize: 11 };
|
|
22
|
+
/** Every bar: rounded, and parted from its neighbours by the card's background. */
|
|
23
|
+
const BAR = {
|
|
24
|
+
radius: TRACE_RADIUS,
|
|
25
|
+
stroke: TRACE_GAP,
|
|
26
|
+
strokeWidth: 2,
|
|
27
|
+
isAnimationActive: false,
|
|
28
|
+
};
|
|
29
|
+
const MODEL_CALLS = new Set(['call', 'response']);
|
|
30
|
+
const SPLIT_KEYS = ['model', 'tools', 'guardrails', 'hooks', 'other'];
|
|
31
|
+
const SPLIT_FILL = {
|
|
32
|
+
model: 'clear',
|
|
33
|
+
tools: 'hatch',
|
|
34
|
+
guardrails: 'shade1',
|
|
35
|
+
hooks: 'shade3',
|
|
36
|
+
other: 'shade2',
|
|
37
|
+
};
|
|
38
|
+
const TOKEN_KEYS = ['fresh', 'cached', 'output'];
|
|
39
|
+
const TOKEN_FILL = {
|
|
40
|
+
fresh: 'clear',
|
|
41
|
+
cached: 'hatch',
|
|
42
|
+
output: 'shade1',
|
|
43
|
+
};
|
|
44
|
+
const compact = new Intl.NumberFormat(undefined, { notation: 'compact', maximumFractionDigits: 1 });
|
|
45
|
+
const percent = new Intl.NumberFormat(undefined, { style: 'percent' });
|
|
46
|
+
function numberAt(node, key) {
|
|
47
|
+
const value = node.span.attributes[key];
|
|
48
|
+
return typeof value === 'number' ? value : undefined;
|
|
49
|
+
}
|
|
50
|
+
/** A turn's model calls, in the order they started. */
|
|
51
|
+
function modelCalls(root) {
|
|
52
|
+
return traceSpans([root])
|
|
53
|
+
.filter((node) => MODEL_CALLS.has(node.meta.type))
|
|
54
|
+
.sort((a, b) => a.startMs - b.startMs);
|
|
55
|
+
}
|
|
56
|
+
/** A chart's key: each series' swatch and name, with its total at the end. */
|
|
57
|
+
function Legend({ items, }) {
|
|
58
|
+
return (_jsx(VStack, { gap: 1, children: items.map((item) => (_jsxs(HStack, { gap: 2, align: "center", justify: "between", children: [_jsxs(HStack, { gap: 2, align: "center", children: [_jsx(TraceSwatch, { fill: item.fill }), _jsx(Text, { type: "supporting", color: "secondary", children: item.label })] }), _jsx(Text, { type: "supporting", hasTabularNumbers: true, children: item.value })] }, item.label))) }));
|
|
59
|
+
}
|
|
60
|
+
/** One bar split into shares, each rounded and parted from the next. */
|
|
61
|
+
function ShareBar({ parts, }) {
|
|
62
|
+
const total = parts.reduce((sum, part) => sum + part.value, 0);
|
|
63
|
+
return (_jsx(ChartFrame, { children: _jsx(ResponsiveContainer, { width: "100%", height: SHARE_PX, initialDimension: { width: FIRST_WIDTH_PX, height: SHARE_PX }, children: _jsxs(BarChart, { data: [Object.fromEntries(parts.map((part) => [part.key, part.value]))], layout: "vertical", margin: { top: 0, right: 0, bottom: 0, left: 0 }, barCategoryGap: 0, accessibilityLayer: false, children: [_jsx(XAxis, { type: "number", domain: [0, total], hide: true }), _jsx(YAxis, { type: "category", hide: true }), parts.map((part) => (_jsx(Bar, { dataKey: part.key, stackId: "share", fill: traceFill(part.fill), ...BAR }, part.key)))] }) }) }));
|
|
64
|
+
}
|
|
65
|
+
/** A share as a legend reads it; a sliver too thin to round up still says it is there. */
|
|
66
|
+
function shareText(share) {
|
|
67
|
+
return share > 0 && share < 0.01 ? `<${percent.format(0.01)}` : percent.format(share);
|
|
68
|
+
}
|
|
69
|
+
function sumOf(split) {
|
|
70
|
+
return SPLIT_KEYS.reduce((total, key) => total + split[key], 0);
|
|
71
|
+
}
|
|
72
|
+
/** Every place time went, however small: a guardrail's few milliseconds still get their line. */
|
|
73
|
+
function SplitLegend({ split, total }) {
|
|
74
|
+
const format = useTraceFormat();
|
|
75
|
+
const { t } = format;
|
|
76
|
+
return (_jsx(Legend, { items: SPLIT_KEYS.filter((key) => split[key] > 0).map((key) => ({
|
|
77
|
+
fill: SPLIT_FILL[key],
|
|
78
|
+
label: t(`@theorem.panel.trace.time.${key}`),
|
|
79
|
+
value: [format.duration(split[key]), shareText(split[key] / total)].join(t('@theorem.panel.trace.separator')),
|
|
80
|
+
})) }));
|
|
81
|
+
}
|
|
82
|
+
/** A turn's time by where it went: what share went to the model, the bar, and each share. */
|
|
83
|
+
function TimeCard({ split }) {
|
|
84
|
+
const format = useTraceFormat();
|
|
85
|
+
const { t } = format;
|
|
86
|
+
const total = sumOf(split);
|
|
87
|
+
if (total <= 0)
|
|
88
|
+
return null;
|
|
89
|
+
// why: A share under half a percent is too thin to draw; the legend still lists it.
|
|
90
|
+
const keys = SPLIT_KEYS.filter((key) => split[key] / total >= 0.005);
|
|
91
|
+
return (_jsxs(ChartCard, { title: t('@theorem.panel.trace.time'), value: format.duration(total), note: split.model > 0
|
|
92
|
+
? t('@theorem.panel.trace.charts.modelShare', {
|
|
93
|
+
share: percent.format(split.model / total),
|
|
94
|
+
})
|
|
95
|
+
: undefined, children: [_jsx(ShareBar, { parts: keys.map((key) => ({ key, fill: SPLIT_FILL[key], value: split[key] })) }), _jsx(SplitLegend, { split: split, total: total })] }));
|
|
96
|
+
}
|
|
97
|
+
/** A bar's tokens: read from cache, read fresh, written, and its cost when known. */
|
|
98
|
+
function tokenSplit(usage) {
|
|
99
|
+
const cached = usage.cached ?? 0;
|
|
100
|
+
return {
|
|
101
|
+
cached,
|
|
102
|
+
fresh: Math.max(0, (usage.input ?? 0) - cached),
|
|
103
|
+
output: usage.output ?? 0,
|
|
104
|
+
...(usage.cost !== undefined && { cost: usage.cost }),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function TokenTip({ active, payload }) {
|
|
108
|
+
const format = useTraceFormat();
|
|
109
|
+
const { t } = format;
|
|
110
|
+
const row = payload?.[0]?.payload;
|
|
111
|
+
if (!active || !row)
|
|
112
|
+
return null;
|
|
113
|
+
return (_jsx(ChartTip, { children: _jsxs(VStack, { gap: 0, children: [_jsx(Text, { weight: "medium", children: row.title }), _jsx(Text, { type: "supporting", hasTabularNumbers: true, children: [
|
|
114
|
+
format.duration(row.ms),
|
|
115
|
+
...(row.cost === undefined ? [] : [format.usd(row.cost)]),
|
|
116
|
+
].join(t('@theorem.panel.trace.separator')) }), _jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: t('@theorem.panel.trace.charts.tokens', {
|
|
117
|
+
cached: format.number(row.cached),
|
|
118
|
+
fresh: format.number(row.fresh),
|
|
119
|
+
output: format.number(row.output),
|
|
120
|
+
}) })] }) }));
|
|
121
|
+
}
|
|
122
|
+
/** Columns of stacked series, one per row; a column opens its row. */
|
|
123
|
+
function Columns({ rows, keys, fills, tip, }) {
|
|
124
|
+
return (_jsx(ChartFrame, { children: _jsx(ResponsiveContainer, { width: "100%", height: CHART_PX, initialDimension: { width: FIRST_WIDTH_PX, height: CHART_PX }, children: _jsxs(BarChart, { data: rows, margin: { top: 0, right: 0, bottom: 0, left: 0 }, accessibilityLayer: false, barCategoryGap: "24%", maxBarSize: 24, onClick: (state) => rows[Number(state?.activeIndex)]?.open(), style: { cursor: 'pointer' }, children: [_jsx(XAxis, { dataKey: "index", tick: TICK, tickLine: false, axisLine: false, height: 18 }), _jsx(YAxis, { hide: true }), _jsx(Tooltip, { content: tip, cursor: { fill: 'var(--color-overlay-hover)', radius: TRACE_RADIUS }, isAnimationActive: false }), keys.map((key) => (_jsx(Bar, { dataKey: key, stackId: "stack", fill: traceFill(fills[key] ?? 'clear'), ...BAR }, key)))] }) }) }));
|
|
125
|
+
}
|
|
126
|
+
/** Tokens stacked new, cached, and written per row: the total, the columns, and each series' sum. */
|
|
127
|
+
function TokensCard({ title, rows }) {
|
|
128
|
+
const format = useTraceFormat();
|
|
129
|
+
const { t } = format;
|
|
130
|
+
const sums = { fresh: 0, cached: 0, output: 0 };
|
|
131
|
+
for (const row of rows)
|
|
132
|
+
for (const key of TOKEN_KEYS)
|
|
133
|
+
sums[key] += row[key];
|
|
134
|
+
const total = sums.fresh + sums.cached + sums.output;
|
|
135
|
+
if (total === 0)
|
|
136
|
+
return null;
|
|
137
|
+
const input = sums.fresh + sums.cached;
|
|
138
|
+
const labels = { fresh: 'input', cached: 'cached', output: 'output' };
|
|
139
|
+
return (_jsxs(ChartCard, { title: title, value: format.number(total), ...(sums.cached > 0 && {
|
|
140
|
+
note: t('@theorem.panel.trace.charts.cachedShare', {
|
|
141
|
+
share: percent.format(sums.cached / input),
|
|
142
|
+
}),
|
|
143
|
+
}), children: [_jsx(Columns, { rows: rows, keys: TOKEN_KEYS.filter((key) => sums[key] > 0), fills: TOKEN_FILL, tip: (props) => _jsx(TokenTip, { ...props }) }), _jsx(Legend, { items: TOKEN_KEYS.filter((key) => sums[key] > 0).map((key) => ({
|
|
144
|
+
fill: TOKEN_FILL[key],
|
|
145
|
+
label: t(`@theorem.panel.trace.charts.${labels[key]}`),
|
|
146
|
+
value: compact.format(sums[key]),
|
|
147
|
+
})) })] }));
|
|
148
|
+
}
|
|
149
|
+
/** A turn's model calls as token rows. */
|
|
150
|
+
function useCallRows(root, onSelect) {
|
|
151
|
+
const { t } = useTraceFormat();
|
|
152
|
+
return useMemo(() => modelCalls(root).map((node, position) => ({
|
|
153
|
+
index: position + 1,
|
|
154
|
+
title: t('@theorem.panel.trace.charts.call', { index: position + 1 }),
|
|
155
|
+
ms: node.durationMs,
|
|
156
|
+
...tokenSplit({
|
|
157
|
+
input: numberAt(node, 'gen_ai.usage.input_tokens'),
|
|
158
|
+
cached: numberAt(node, 'gen_ai.usage.cache_read.input_tokens'),
|
|
159
|
+
output: numberAt(node, 'gen_ai.usage.output_tokens'),
|
|
160
|
+
cost: numberAt(node, 'theorem.usage.cost_usd'),
|
|
161
|
+
}),
|
|
162
|
+
open: () => onSelect(node),
|
|
163
|
+
})), [root, onSelect, t]);
|
|
164
|
+
}
|
|
165
|
+
function SlowTip({ active, payload }) {
|
|
166
|
+
const format = useTraceFormat();
|
|
167
|
+
const row = payload?.[0]?.payload;
|
|
168
|
+
if (!active || !row)
|
|
169
|
+
return null;
|
|
170
|
+
return (_jsx(ChartTip, { children: _jsxs(VStack, { gap: 0, children: [_jsx(Text, { weight: "medium", children: row.name }), _jsx(Text, { type: "supporting", hasTabularNumbers: true, children: format.duration(row.ms) })] }) }));
|
|
171
|
+
}
|
|
172
|
+
/** The steps that took longest, longest first, filled by who acted. A bar opens its step. */
|
|
173
|
+
function SlowestCard({ root, onSelect }) {
|
|
174
|
+
const format = useTraceFormat();
|
|
175
|
+
const { t } = format;
|
|
176
|
+
const rows = useMemo(() => {
|
|
177
|
+
const calls = modelCalls(root);
|
|
178
|
+
return traceSpans(root.children)
|
|
179
|
+
.filter((node) => node.meta.type === 'tool' || MODEL_CALLS.has(node.meta.type))
|
|
180
|
+
.sort((a, b) => b.durationMs - a.durationMs)
|
|
181
|
+
.slice(0, SLOWEST)
|
|
182
|
+
.map((node) => ({
|
|
183
|
+
id: node.id,
|
|
184
|
+
node,
|
|
185
|
+
name: node.meta.type === 'tool'
|
|
186
|
+
? (node.meta.subject ?? node.meta.label)
|
|
187
|
+
: t('@theorem.panel.trace.charts.call', { index: calls.indexOf(node) + 1 }),
|
|
188
|
+
ms: node.durationMs,
|
|
189
|
+
}));
|
|
190
|
+
}, [root, t]);
|
|
191
|
+
const top = rows[0];
|
|
192
|
+
if (!top)
|
|
193
|
+
return null;
|
|
194
|
+
const height = rows.length * SLOW_ROW_PX;
|
|
195
|
+
return (_jsx(ChartCard, { title: t('@theorem.panel.trace.charts.slowest'), value: format.duration(top.ms), note: top.name, children: _jsx(ChartFrame, { children: _jsx(ResponsiveContainer, { width: "100%", height: height, initialDimension: { width: FIRST_WIDTH_PX, height }, children: _jsxs(BarChart, { data: rows, layout: "vertical", margin: { top: 0, right: 48, bottom: 0, left: 0 }, accessibilityLayer: false, barCategoryGap: 5, onClick: (state) => {
|
|
196
|
+
const row = rows[Number(state?.activeIndex)];
|
|
197
|
+
if (row)
|
|
198
|
+
onSelect(row.node);
|
|
199
|
+
}, style: { cursor: 'pointer' }, children: [_jsx(XAxis, { type: "number", hide: true }), _jsx(YAxis, { type: "category", dataKey: "id", width: 104, tick: TICK, tickLine: false, axisLine: false, interval: 0, tickFormatter: (id) => {
|
|
200
|
+
const name = rows.find((row) => row.id === id)?.name ?? '';
|
|
201
|
+
return name.length > 15 ? `${name.slice(0, 14)}…` : name;
|
|
202
|
+
} }), _jsx(Tooltip, { content: (props) => _jsx(SlowTip, { ...props }), cursor: false, isAnimationActive: false }), _jsx(Bar, { dataKey: "ms", ...BAR, label: {
|
|
203
|
+
position: 'right',
|
|
204
|
+
fill: 'var(--color-text-secondary)',
|
|
205
|
+
fontSize: 11,
|
|
206
|
+
formatter: (value) => format.duration(Number(value)),
|
|
207
|
+
}, children: rows.map((row) => (_jsx(Cell, { fill: traceFill(ACTOR_FILL[traceActor(row.node)]) }, row.id))) })] }) }) }) }));
|
|
208
|
+
}
|
|
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();
|
|
212
|
+
const callRows = useCallRows(root, onSelect);
|
|
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 })] }));
|
|
214
|
+
}
|
|
215
|
+
function TurnTimeTip({ active, payload }) {
|
|
216
|
+
const format = useTraceFormat();
|
|
217
|
+
const { t } = format;
|
|
218
|
+
const row = payload?.[0]?.payload;
|
|
219
|
+
if (!active || !row)
|
|
220
|
+
return null;
|
|
221
|
+
return (_jsx(ChartTip, { children: _jsxs(VStack, { gap: 0, children: [_jsx(Text, { weight: "medium", children: row.title }), SPLIT_KEYS.filter((key) => row[key] > 0).map((key) => (_jsx(Text, { type: "supporting", hasTabularNumbers: true, children: [t(`@theorem.panel.trace.time.${key}`), format.duration(row[key])].join(t('@theorem.panel.trace.separator')) }, key)))] }) }));
|
|
222
|
+
}
|
|
223
|
+
/** Each turn's time by where it went, stacked: the conversation's total and each share's sum. */
|
|
224
|
+
function TurnTimeCard({ rows, isCalls }) {
|
|
225
|
+
const format = useTraceFormat();
|
|
226
|
+
const { t } = format;
|
|
227
|
+
const sums = { model: 0, tools: 0, guardrails: 0, hooks: 0, other: 0 };
|
|
228
|
+
for (const row of rows)
|
|
229
|
+
for (const key of SPLIT_KEYS)
|
|
230
|
+
sums[key] += row[key];
|
|
231
|
+
const total = sumOf(sums);
|
|
232
|
+
if (total <= 0)
|
|
233
|
+
return null;
|
|
234
|
+
return (_jsxs(ChartCard, { title: t(isCalls ? '@theorem.panel.trace.charts.callTime' : '@theorem.panel.trace.charts.turnTime'), value: format.duration(total), note: sums.model > 0
|
|
235
|
+
? t('@theorem.panel.trace.charts.modelShare', {
|
|
236
|
+
share: percent.format(sums.model / total),
|
|
237
|
+
})
|
|
238
|
+
: undefined, children: [_jsx(Columns, { rows: rows, keys: SPLIT_KEYS, fills: SPLIT_FILL, tip: (props) => _jsx(TurnTimeTip, { ...props }) }), _jsx(SplitLegend, { split: sums, total: total })] }));
|
|
239
|
+
}
|
|
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();
|
|
243
|
+
const isCalls = isCallTrace(turns);
|
|
244
|
+
const column = isCalls
|
|
245
|
+
? '@theorem.panel.trace.charts.hostCall'
|
|
246
|
+
: '@theorem.panel.trace.charts.turn';
|
|
247
|
+
const time = useMemo(() => turns.map((turn, position) => ({
|
|
248
|
+
index: position + 1,
|
|
249
|
+
title: t(column, { index: position + 1 }),
|
|
250
|
+
...turn.split,
|
|
251
|
+
open: () => onPick(position),
|
|
252
|
+
})), [turns, onPick, t, column]);
|
|
253
|
+
const tokens = useMemo(() => turns.map(({ totals }, position) => ({
|
|
254
|
+
index: position + 1,
|
|
255
|
+
title: t(column, { index: position + 1 }),
|
|
256
|
+
ms: totals.durationMs,
|
|
257
|
+
...tokenSplit({
|
|
258
|
+
input: totals.input?.value,
|
|
259
|
+
cached: totals.cached?.value,
|
|
260
|
+
output: totals.output?.value,
|
|
261
|
+
cost: totals.cost?.value,
|
|
262
|
+
}),
|
|
263
|
+
open: () => onPick(position),
|
|
264
|
+
})), [turns, onPick, t, column]);
|
|
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 })] }));
|
|
266
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TraceGuardrailCheck } from '../client/trace-story.ts';
|
|
2
|
+
import type { TraceNode } from '../client/trace-view.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Every guardrail check of the turn. The ones that acted lead, each with the
|
|
5
|
+
* rules it matched and why; the passes follow on one quiet line.
|
|
6
|
+
*/
|
|
7
|
+
export declare function TraceGuardrails({ checks, onSelect, }: {
|
|
8
|
+
checks: readonly TraceGuardrailCheck[];
|
|
9
|
+
/** Opens a check's step; without it a check is not a button. */
|
|
10
|
+
onSelect?: (node: TraceNode) => void;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Icon } from '@astryxdesign/core/Icon';
|
|
3
|
+
import { Item } from '@astryxdesign/core/Item';
|
|
4
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
5
|
+
import { Token } from '@astryxdesign/core/Token';
|
|
6
|
+
import { Tooltip as HoverTip } from '@astryxdesign/core/Tooltip';
|
|
7
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
8
|
+
import { IconBiohazard, IconEyeOff, IconHandStop, IconInfoCircle, IconShield, } from '@tabler/icons-react';
|
|
9
|
+
import { TOOL_RULES } from '@theoremjs/agents/guardrails';
|
|
10
|
+
import { Arrive, Stream } from "./arrive.js";
|
|
11
|
+
import { useTraceFormat } from "./TraceValues.js";
|
|
12
|
+
/** What a check is called: the timed check's own name, or else the text it looked at. */
|
|
13
|
+
function checkName(check) {
|
|
14
|
+
return check.checkLabel ?? check.stageLabel;
|
|
15
|
+
}
|
|
16
|
+
/** One rule a check matched: what it caught and how serious, why that matters, and the matched text when kept. The id is on hover, for finding the rule in code. */
|
|
17
|
+
function HitWhy({ hit, separator }) {
|
|
18
|
+
return (_jsxs(VStack, { gap: 0, children: [_jsx(HoverTip, { content: hit.rule, children: _jsx(Text, { type: "supporting", weight: "medium", children: [hit.ruleLabel, hit.severityLabel].filter(Boolean).join(separator) }) }), hit.ruleDoc ? (_jsx(Text, { type: "supporting", color: "secondary", children: hit.ruleDoc })) : null, hit.match ? (_jsx(Text, { type: "supporting", color: "secondary", maxLines: 2, children: `“${hit.match}”` })) : null] }));
|
|
19
|
+
}
|
|
20
|
+
/** Why a check acted: which check it was and on what, each rule it matched, and the guardrail's own reason. */
|
|
21
|
+
function CheckWhy({ check, format }) {
|
|
22
|
+
const separator = format.t('@theorem.panel.trace.separator');
|
|
23
|
+
return (_jsxs(VStack, { gap: 1, children: [_jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: [
|
|
24
|
+
checkName(check),
|
|
25
|
+
check.durationMs !== undefined && format.duration(check.durationMs),
|
|
26
|
+
check.tool && format.t('@theorem.panel.trace.guardrails.from', { tool: check.tool }),
|
|
27
|
+
]
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.join(separator) }), check.hits.map((hit) => (_jsx(HitWhy, { hit: hit, separator: separator }, `${hit.rule}:${hit.match ?? ''}`))), check.reason ? (_jsx(Text, { type: "supporting", color: "secondary", maxLines: 3, children: check.reason })) : null] }));
|
|
30
|
+
}
|
|
31
|
+
/** What a check did, as a token. A report of a call made after a remote read says so in place of "flagged". */
|
|
32
|
+
function ActionToken({ check, format }) {
|
|
33
|
+
if (check.action === 'block') {
|
|
34
|
+
return (_jsx(Token, { label: check.actionLabel, color: "red", icon: _jsx(Icon, { icon: IconHandStop, size: "sm" }) }));
|
|
35
|
+
}
|
|
36
|
+
if (check.action === 'redact') {
|
|
37
|
+
return (_jsx(Token, { label: check.actionLabel, color: "orange", icon: _jsx(Icon, { icon: IconEyeOff, size: "sm" }) }));
|
|
38
|
+
}
|
|
39
|
+
const rules = new Set(check.hits.map((hit) => hit.rule));
|
|
40
|
+
if (rules.has(TOOL_RULES.steeredTurn) || rules.has(TOOL_RULES.taintedTurn)) {
|
|
41
|
+
return (_jsx(Token, { label: format.t(rules.has(TOOL_RULES.steeredTurn)
|
|
42
|
+
? '@theorem.panel.trace.guardrails.steered'
|
|
43
|
+
: '@theorem.panel.trace.guardrails.tainted'), color: "yellow", icon: _jsx(Icon, { icon: IconBiohazard, size: "sm" }) }));
|
|
44
|
+
}
|
|
45
|
+
return (_jsx(Token, { label: check.actionLabel, color: "blue", icon: _jsx(Icon, { icon: IconInfoCircle, size: "sm" }) }));
|
|
46
|
+
}
|
|
47
|
+
function ActedCheck({ check, format, onSelect, }) {
|
|
48
|
+
return (_jsx(Item, { startContent: _jsx(Icon, { icon: IconShield, size: "sm", color: "secondary" }), label: _jsx(HoverTip, { content: check.actionDoc ?? check.actionLabel, children: _jsx(ActionToken, { check: check, format: format }) }), description: _jsx(CheckWhy, { check: check, format: format }), endContent: _jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: format.t('@theorem.panel.trace.offset', { duration: format.duration(check.offsetMs) }) }), align: "start", density: "compact", ...(onSelect ? { onClick: () => onSelect(check.node) } : {}) }));
|
|
49
|
+
}
|
|
50
|
+
/** "Input check 0.8ms, Result check ×3 0.4ms": the passes, one entry per kind of check with its count and total time. */
|
|
51
|
+
function passSummary(passes, format) {
|
|
52
|
+
const kinds = new Map();
|
|
53
|
+
for (const check of passes) {
|
|
54
|
+
const kind = kinds.get(checkName(check)) ?? { count: 0, ms: 0, timed: false };
|
|
55
|
+
kinds.set(checkName(check), {
|
|
56
|
+
count: kind.count + 1,
|
|
57
|
+
ms: kind.ms + (check.durationMs ?? 0),
|
|
58
|
+
timed: kind.timed || check.durationMs !== undefined,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return [...kinds]
|
|
62
|
+
.map(([name, { count, ms, timed }]) => [name, count > 1 && `×${count}`, timed && format.duration(ms)].filter(Boolean).join(' '))
|
|
63
|
+
.join(', ');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Every guardrail check of the turn. The ones that acted lead, each with the
|
|
67
|
+
* rules it matched and why; the passes follow on one quiet line.
|
|
68
|
+
*/
|
|
69
|
+
export function TraceGuardrails({ checks, onSelect, }) {
|
|
70
|
+
const format = useTraceFormat();
|
|
71
|
+
const acted = checks.filter((check) => check.action !== 'allow');
|
|
72
|
+
const passed = passSummary(checks.filter((check) => check.action === 'allow'), format);
|
|
73
|
+
return (_jsx(Stream, { children: _jsxs(VStack, { gap: 1, children: [acted.map((check) => (_jsx(Arrive, { children: _jsx(ActedCheck, { check: check, format: format, onSelect: onSelect }) }, check.id))), passed ? (_jsx(Arrive, { children: _jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: format.t('@theorem.panel.trace.guardrails.passed', { checks: passed }) }) })) : null] }) }));
|
|
74
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TraceRecord } from '@theoremjs/agents';
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
import type { TraceFeed } from '../client/trace-feed.ts';
|
|
4
|
+
/** The inspector over the records a feed has delivered; the panel loads this module when it first opens. */
|
|
5
|
+
export declare function TraceFeedBody({ traces, isWide, }: {
|
|
6
|
+
traces: TraceFeed | undefined;
|
|
7
|
+
isWide: boolean;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
/**
|
|
10
|
+
* Only what the records say of guardrails: how long the checks took, how many
|
|
11
|
+
* ran and acted, then each check. `head` leads it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function TraceGuardrailsBody({ records, head, }: {
|
|
14
|
+
records: readonly TraceRecord[];
|
|
15
|
+
head?: ReactNode;
|
|
16
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Badge } from '@astryxdesign/core/Badge';
|
|
3
|
+
import { EmptyState } from '@astryxdesign/core/EmptyState';
|
|
4
|
+
import { HStack } from '@astryxdesign/core/HStack';
|
|
5
|
+
import { Icon } from '@astryxdesign/core/Icon';
|
|
6
|
+
import { Item } from '@astryxdesign/core/Item';
|
|
7
|
+
import { PowerSearch, } from '@astryxdesign/core/PowerSearch';
|
|
8
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
9
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
10
|
+
import { IconActivity } from '@tabler/icons-react';
|
|
11
|
+
import { useMemo, useState } from 'react';
|
|
12
|
+
import { isCallTrace, traceGuardrails, traceLatency, traceStory, traceTurnRow, traceTurns, } from "../client/trace-story.js";
|
|
13
|
+
import { TRACE_SEARCH_OPERATORS, TRACE_TEXT_FIELD, traceMatchIds, traceSearchFields, traceSpans, traceTotals, traceTree, } from "../client/trace-view.js";
|
|
14
|
+
import { useTraceRecords } from "../hooks/use-trace-records.js";
|
|
15
|
+
import { Stream, useArrive } from "./arrive.js";
|
|
16
|
+
import { PaneLayout } from "./SidePanel.js";
|
|
17
|
+
import { ConversationCharts, TraceCharts } from "./TraceCharts.js";
|
|
18
|
+
import { TraceGuardrails } from "./TraceGuardrails.js";
|
|
19
|
+
import { GuardrailStats, TurnPicker, TurnStats } from "./TraceOverview.js";
|
|
20
|
+
import { TraceSpanDetail } from "./TraceSpanDetail.js";
|
|
21
|
+
import { ActorMark, TraceStory } from "./TraceStory.js";
|
|
22
|
+
import { useTraceFormat } from "./TraceValues.js";
|
|
23
|
+
import { TraceWaterfall } from "./TraceWaterfall.js";
|
|
24
|
+
import { TracePatterns } from "./trace-patterns.js";
|
|
25
|
+
function unitOf(t, field) {
|
|
26
|
+
switch (field.format) {
|
|
27
|
+
case 'milliseconds':
|
|
28
|
+
return t('@theorem.panel.trace.unit.milliseconds');
|
|
29
|
+
case 'seconds':
|
|
30
|
+
return t('@theorem.panel.trace.unit.seconds');
|
|
31
|
+
case 'usd':
|
|
32
|
+
return t('@theorem.panel.trace.unit.usd');
|
|
33
|
+
default:
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function operatorValue(t, field) {
|
|
38
|
+
switch (field.kind) {
|
|
39
|
+
case 'options':
|
|
40
|
+
return {
|
|
41
|
+
type: 'enum_list',
|
|
42
|
+
values: (field.options ?? []).map(({ value, label }) => ({ value, label })),
|
|
43
|
+
};
|
|
44
|
+
case 'number': {
|
|
45
|
+
const units = unitOf(t, field);
|
|
46
|
+
return units ? { type: 'float', units } : { type: 'float' };
|
|
47
|
+
}
|
|
48
|
+
case 'text':
|
|
49
|
+
return { type: 'string', isArbitraryStringAllowed: true };
|
|
50
|
+
case 'flag':
|
|
51
|
+
return { type: 'empty' };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function searchField(t, field) {
|
|
55
|
+
const value = operatorValue(t, field);
|
|
56
|
+
return {
|
|
57
|
+
key: field.key,
|
|
58
|
+
label: field.label,
|
|
59
|
+
description: field.doc,
|
|
60
|
+
...(field.group && { group: field.group }),
|
|
61
|
+
operators: TRACE_SEARCH_OPERATORS[field.kind].map((key) => ({
|
|
62
|
+
key,
|
|
63
|
+
value,
|
|
64
|
+
i18nKey: `@astryx.powersearch.operator.${key}`,
|
|
65
|
+
})),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** The search over the spans shown: any text first, then the fields the spans recorded. */
|
|
69
|
+
function searchConfig(t, nodes) {
|
|
70
|
+
return {
|
|
71
|
+
name: t('@theorem.panel.trace.search'),
|
|
72
|
+
contentSearchFieldKey: TRACE_TEXT_FIELD,
|
|
73
|
+
fields: [
|
|
74
|
+
searchField(t, {
|
|
75
|
+
key: TRACE_TEXT_FIELD,
|
|
76
|
+
label: t('@theorem.panel.trace.search.text'),
|
|
77
|
+
doc: t('@theorem.panel.trace.search.text.description'),
|
|
78
|
+
kind: 'text',
|
|
79
|
+
}),
|
|
80
|
+
...traceSearchFields(nodes).map((field) => searchField(t, field)),
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function SectionTitle({ title, children }) {
|
|
85
|
+
return (_jsxs(HStack, { gap: 2, align: "center", justify: "between", children: [_jsx(Text, { weight: "semibold", children: title }), children] }));
|
|
86
|
+
}
|
|
87
|
+
/** The turn open, or none for the whole conversation; a trace of one turn opens it. */
|
|
88
|
+
function useOpenTurn(count) {
|
|
89
|
+
const [picked, setPicked] = useState(null);
|
|
90
|
+
const index = count === 1 ? 0 : picked !== null && picked < count ? picked : null;
|
|
91
|
+
return { index, pick: setPicked };
|
|
92
|
+
}
|
|
93
|
+
/** How the row's turn ended: its failure, else its answer. */
|
|
94
|
+
function turnRowNote(row) {
|
|
95
|
+
if (row.outcome)
|
|
96
|
+
return _jsx(Badge, { variant: row.outcome.tone, label: row.outcome.label });
|
|
97
|
+
if (!row.answered)
|
|
98
|
+
return undefined;
|
|
99
|
+
return (_jsx(Text, { color: "secondary", maxLines: 2, hasTruncateTooltip: false, children: row.answered }));
|
|
100
|
+
}
|
|
101
|
+
/** A row's time, and its cost when it has one. */
|
|
102
|
+
function TurnRowTotals({ turn }) {
|
|
103
|
+
const format = useTraceFormat();
|
|
104
|
+
return (_jsxs(VStack, { gap: 0, hAlign: "end", children: [_jsx(Text, { type: "supporting", hasTabularNumbers: true, children: format.duration(turn.totals.durationMs) }), turn.totals.cost ? (_jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: format.sum(turn.totals.cost, format.usd) })) : null] }));
|
|
105
|
+
}
|
|
106
|
+
/** A row of the whole-trace list: a turn by what the person asked, a host's call by its tool. */
|
|
107
|
+
function TurnRow({ turn, index, isCall, onOpen, }) {
|
|
108
|
+
const { t } = useTraceFormat();
|
|
109
|
+
const arrive = useArrive();
|
|
110
|
+
const row = useMemo(() => traceTurnRow(turn, isCall), [turn, isCall]);
|
|
111
|
+
return (_jsx(Item, { className: arrive?.className, style: arrive?.style, startContent: _jsx(ActorMark, { actor: row.actor }), label: _jsx(Text, { weight: "medium", maxLines: 1, children: row.title ??
|
|
112
|
+
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
|
+
/** Every turn at once: the conversation's totals, each turn's time and tokens, and the turns themselves. */
|
|
115
|
+
function ConversationOverview({ turns, onPick, }) {
|
|
116
|
+
const { t } = useTraceFormat();
|
|
117
|
+
const nodes = useMemo(() => turns.map((turn) => turn.node), [turns]);
|
|
118
|
+
// why: Time between turns is the person's, not the agent's: the duration sums the turns.
|
|
119
|
+
const totals = useMemo(() => ({
|
|
120
|
+
...traceTotals(nodes),
|
|
121
|
+
durationMs: turns.reduce((total, turn) => total + turn.totals.durationMs, 0),
|
|
122
|
+
}), [nodes, turns]);
|
|
123
|
+
const tools = useMemo(() => traceSpans(nodes).filter((node) => node.meta.type === 'tool').length, [nodes]);
|
|
124
|
+
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
|
+
count: turns.length,
|
|
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))) }) })] })] }));
|
|
128
|
+
}
|
|
129
|
+
/** One turn at a glance, then its story, then its timeline under the search. */
|
|
130
|
+
function TurnOverview({ turns, index, onPick, onBack, selectedId, onSelect, }) {
|
|
131
|
+
const { t } = useTraceFormat();
|
|
132
|
+
const [filters, setFilters] = useState([]);
|
|
133
|
+
const turn = turns[index];
|
|
134
|
+
const root = turn?.node;
|
|
135
|
+
const config = useMemo(() => searchConfig(t, root ? [root] : []), [t, root]);
|
|
136
|
+
const matches = useMemo(() => (root ? traceMatchIds([root], filters) : undefined), [root, filters]);
|
|
137
|
+
const story = useMemo(() => (root ? traceStory(root) : []), [root]);
|
|
138
|
+
const latency = useMemo(() => (root ? traceLatency(root) : undefined), [root]);
|
|
139
|
+
const guardrails = useMemo(() => (root ? traceGuardrails(root) : []), [root]);
|
|
140
|
+
if (!turn || !root)
|
|
141
|
+
return null;
|
|
142
|
+
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
|
+
}
|
|
145
|
+
function EmptyTrace() {
|
|
146
|
+
const { t } = useTraceFormat();
|
|
147
|
+
return (_jsx(VStack, { height: "100%", vAlign: "center", padding: 4, children: _jsx(EmptyState, { icon: _jsx(Icon, { icon: IconActivity, size: "lg", color: "secondary" }), title: t('@theorem.panel.trace.empty.title'), description: t('@theorem.panel.trace.empty.description'), isCompact: true }) }));
|
|
148
|
+
}
|
|
149
|
+
/** The open span of the open turn; opening another turn closes it. */
|
|
150
|
+
function useOpenSpan(turns) {
|
|
151
|
+
const { index, pick } = useOpenTurn(turns.length);
|
|
152
|
+
const [openId, setOpenId] = useState(null);
|
|
153
|
+
const root = index === null ? undefined : turns[index]?.node;
|
|
154
|
+
const open = openId && root ? traceSpans([root]).find((node) => node.id === openId) : undefined;
|
|
155
|
+
return {
|
|
156
|
+
index,
|
|
157
|
+
root,
|
|
158
|
+
open,
|
|
159
|
+
select: (node) => setOpenId(node.id),
|
|
160
|
+
close: () => setOpenId(null),
|
|
161
|
+
pick: (next) => {
|
|
162
|
+
pick(next);
|
|
163
|
+
setOpenId(null);
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/** The open span beside or in place of the overview; nothing while none is open. */
|
|
168
|
+
function SpanPane({ open, turnStartMs, isWide, onBack, onOpen, }) {
|
|
169
|
+
if (!open)
|
|
170
|
+
return null;
|
|
171
|
+
return (_jsx(VStack, { padding: 4, children: _jsx(TraceSpanDetail, { node: open, turnStartMs: turnStartMs, isClose: isWide, onBack: onBack, onOpen: onOpen }) }));
|
|
172
|
+
}
|
|
173
|
+
/** The whole conversation, or the open turn with a way back when there are others. */
|
|
174
|
+
function Overview({ turns, index, pick, selectedId, select, }) {
|
|
175
|
+
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 }));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The panel's content. Narrow, the turn fills it and a span opens in its
|
|
181
|
+
* place; wide, an open span sits beside the turn.
|
|
182
|
+
*/
|
|
183
|
+
function TraceInspectorBody({ records, isWide, }) {
|
|
184
|
+
const tree = useMemo(() => traceTree(records), [records]);
|
|
185
|
+
const turns = useMemo(() => traceTurns(tree), [tree]);
|
|
186
|
+
const { t } = useTraceFormat();
|
|
187
|
+
const { index, root, open, select, close, pick } = useOpenSpan(turns);
|
|
188
|
+
if (turns.length === 0)
|
|
189
|
+
return _jsx(EmptyTrace, {});
|
|
190
|
+
const overview = (_jsx(Overview, { turns: turns, index: index, pick: pick, selectedId: open?.id, select: select }));
|
|
191
|
+
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
|
+
}
|
|
193
|
+
/** The inspector over the records a feed has delivered; the panel loads this module when it first opens. */
|
|
194
|
+
export function TraceFeedBody({ traces, isWide, }) {
|
|
195
|
+
return _jsx(TraceInspectorBody, { records: useTraceRecords(traces), isWide: isWide });
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Only what the records say of guardrails: how long the checks took, how many
|
|
199
|
+
* ran and acted, then each check. `head` leads it.
|
|
200
|
+
*/
|
|
201
|
+
export function TraceGuardrailsBody({ records, head, }) {
|
|
202
|
+
const { t } = useTraceFormat();
|
|
203
|
+
const roots = useMemo(() => traceTurns(traceTree(records)).map((turn) => turn.node), [records]);
|
|
204
|
+
const checks = useMemo(() => roots.flatMap(traceGuardrails), [roots]);
|
|
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] }));
|
|
206
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TraceRecord } from '@theoremjs/agents';
|
|
2
|
+
import type { ProfileObservabilityView } from '@theoremjs/agents/interface';
|
|
3
|
+
import { type ReactNode } from 'react';
|
|
4
|
+
import type { TraceFeed } from '../client/trace-feed.ts';
|
|
5
|
+
import { type SidePanelLabels } from './SidePanel.tsx';
|
|
6
|
+
export type TraceInspector = {
|
|
7
|
+
/** The header toggle; null when the host drives the trace or the profile records none. */
|
|
8
|
+
toggle: ReactNode;
|
|
9
|
+
/** The trace, while it's open; null otherwise. */
|
|
10
|
+
view: ReactNode;
|
|
11
|
+
/** Words for the docked panel. */
|
|
12
|
+
labels: SidePanelLabels;
|
|
13
|
+
/** The profile records traces, so a panel page can dock them even when the host drives the toggle. */
|
|
14
|
+
offered: boolean;
|
|
15
|
+
};
|
|
16
|
+
/** Where an open trace sits on this page. */
|
|
17
|
+
export declare function TracePlacement({ value, children, }: {
|
|
18
|
+
value: 'replace' | 'panel';
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
}): import("react").JSX.Element;
|
|
21
|
+
/**
|
|
22
|
+
* The trace inspector, offered when the profile records traces. By default the
|
|
23
|
+
* open trace takes the surface's place. Inside `TracePlacement value="panel"`
|
|
24
|
+
* it docks at the inline end instead, and the theme eases that panel's width.
|
|
25
|
+
* It reads the records the host delivers on `traces`; a host that delivers none
|
|
26
|
+
* shows the empty state. Pass `open` to drive it from the host's own control,
|
|
27
|
+
* which hides the built-in toggle. The open trace takes the surface's place,
|
|
28
|
+
* unless this page is inside `TracePlacement value="panel"`, where it docks.
|
|
29
|
+
*/
|
|
30
|
+
export declare function useTraceInspector(iface: {
|
|
31
|
+
observability?: Pick<ProfileObservabilityView, 'record'>;
|
|
32
|
+
}, traces?: TraceFeed, open?: boolean): TraceInspector;
|
|
33
|
+
/** The surface, or the trace in its place — or, on a docked page, beside it. */
|
|
34
|
+
export declare function WithTrace({ inspector, children, }: {
|
|
35
|
+
inspector: TraceInspector;
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
}): import("react").JSX.Element;
|
|
38
|
+
/**
|
|
39
|
+
* What `records` say of guardrails, for a host that holds a trace of its own
|
|
40
|
+
* (the studio's guardrail test): the checks' time and count, then each
|
|
41
|
+
* check, laid out as the trace lays them out. `head` leads it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function TraceGuardrailsView({ records, head, }: {
|
|
44
|
+
records: readonly TraceRecord[];
|
|
45
|
+
head?: ReactNode;
|
|
46
|
+
}): import("react").JSX.Element;
|