@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,150 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Banner } from '@astryxdesign/core/Banner';
|
|
3
|
+
import { EmptyState } from '@astryxdesign/core/EmptyState';
|
|
4
|
+
import { HStack } from '@astryxdesign/core/HStack';
|
|
5
|
+
import { List, ListItem } from '@astryxdesign/core/List';
|
|
6
|
+
import { Selector } from '@astryxdesign/core/Selector';
|
|
7
|
+
import { Spinner } from '@astryxdesign/core/Spinner';
|
|
8
|
+
import { StatusDot } from '@astryxdesign/core/StatusDot';
|
|
9
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
10
|
+
import { Token } from '@astryxdesign/core/Token';
|
|
11
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
12
|
+
import { useLocale } from '@astryxdesign/core/i18n';
|
|
13
|
+
import { useMemo, useState } from 'react';
|
|
14
|
+
import { hostCallFailure, hostCallStatus, hostConsoleView } from "../client/host-call.js";
|
|
15
|
+
import { createHostTransport } from "../client/host-transport.js";
|
|
16
|
+
import { missingFields } from "../client/schema-fields.js";
|
|
17
|
+
import { isRow, json } from "../client/shaped-data.js";
|
|
18
|
+
import { useTheoremHost } from "../hooks/use-theorem-host.js";
|
|
19
|
+
import { workDuration } from "./labels.js";
|
|
20
|
+
import { ConsoleFrame, JsonRequest, RequestCard, RunBar } from "./ConsoleFrame.js";
|
|
21
|
+
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
22
|
+
import { SchemaFields } from "./SchemaFields.js";
|
|
23
|
+
import { TheoremThemeProvider } from "./theme.js";
|
|
24
|
+
import { ApprovalCard, AuthChallengeCard } from "./ToolGateCard.js";
|
|
25
|
+
import { ToolResult } from "./ToolResult.js";
|
|
26
|
+
import { useTraceInspector } from "./TraceInspectorPanel.js";
|
|
27
|
+
/** Calls listed under the console before older ones drop off the page. */
|
|
28
|
+
const MAX_EARLIER = 8;
|
|
29
|
+
const STATUS_DOT = {
|
|
30
|
+
running: 'accent',
|
|
31
|
+
gate: 'warning',
|
|
32
|
+
complete: 'success',
|
|
33
|
+
error: 'error',
|
|
34
|
+
cancel: 'neutral',
|
|
35
|
+
};
|
|
36
|
+
/** The request's text as the input a call sends, and what it still lacks. */
|
|
37
|
+
function checkRequest(t, tool, text) {
|
|
38
|
+
let input;
|
|
39
|
+
try {
|
|
40
|
+
input = JSON.parse(text);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return { ok: false, note: t('@theorem.decision.invalid_json') };
|
|
44
|
+
}
|
|
45
|
+
if (!isRow(input))
|
|
46
|
+
return { ok: false, note: t('@theorem.decision.invalid_json') };
|
|
47
|
+
const missing = missingFields(tool.inputSchema, input);
|
|
48
|
+
if (missing.length > 0)
|
|
49
|
+
return { ok: false, note: t('@theorem.host.missing', { fields: missing.join(', ') }), input };
|
|
50
|
+
return { ok: true, input };
|
|
51
|
+
}
|
|
52
|
+
/** A tool's kind, and its access when it can change things. */
|
|
53
|
+
function ToolTokens({ tool }) {
|
|
54
|
+
const t = useLabels();
|
|
55
|
+
return (_jsxs(HStack, { gap: 1, wrap: "wrap", children: [_jsx(Token, { label: t(`@theorem.host.kind.${tool.kind}`), size: "sm", color: "gray" }), tool.access === 'read-only' ? null : (_jsx(Token, { label: t(`@theorem.host.access.${tool.access}`), size: "sm", color: tool.access === 'destructive' ? 'red' : 'orange' }))] }));
|
|
56
|
+
}
|
|
57
|
+
/** The gate a call paused on, answered in place. */
|
|
58
|
+
function CallGate({ call, onAnswer }) {
|
|
59
|
+
const state = call.call?.state;
|
|
60
|
+
if (state?.phase !== 'gate')
|
|
61
|
+
return null;
|
|
62
|
+
if (state.gate.kind === 'auth') {
|
|
63
|
+
return (_jsx(AuthChallengeCard, { gate: state.gate, toolName: call.name, submitted: call.answering === 'auth', onAuthenticated: (secret) => void onAnswer(call.id, { action: 'auth', secret }) }));
|
|
64
|
+
}
|
|
65
|
+
return (_jsx(ApprovalCard, { gate: state.gate, toolName: call.name, input: call.input, decided: call.answering === 'auth' ? null : call.answering, onDecision: (action) => void onAnswer(call.id, { action }) }));
|
|
66
|
+
}
|
|
67
|
+
/** One call's outcome: its gate, its failure, or its output, then a line for the tool, time, and status. */
|
|
68
|
+
function CallOutcome({ call, onAnswer }) {
|
|
69
|
+
const t = useLabels();
|
|
70
|
+
const state = call.call?.state;
|
|
71
|
+
const failure = hostCallFailure(call);
|
|
72
|
+
const meta = [call.name, call.elapsedMs == null ? null : workDuration(t, call.elapsedMs), t(`@theorem.host.status.${hostCallStatus(call)}`)].filter(Boolean);
|
|
73
|
+
return (_jsxs(VStack, { gap: 3, children: [_jsx(CallGate, { call: call, onAnswer: onAnswer }), failure ? _jsx(Banner, { status: "error", title: failure.title, description: failure.description }) : null, state?.phase === 'complete' ? _jsx(ToolResult, { output: state.output, parts: state.parts }) : null, call.isRunning ? null : (_jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: meta.join(' · ') }))] }));
|
|
74
|
+
}
|
|
75
|
+
/** This page's earlier calls, newest first; picking one shows it again with its request. */
|
|
76
|
+
function EarlierCalls({ calls, onPick }) {
|
|
77
|
+
const t = useLabels();
|
|
78
|
+
const locale = useLocale();
|
|
79
|
+
const time = useMemo(() => new Intl.DateTimeFormat(locale, { timeStyle: 'medium' }), [locale]);
|
|
80
|
+
if (calls.length === 0)
|
|
81
|
+
return null;
|
|
82
|
+
return (_jsxs(VStack, { gap: 1, children: [_jsx(Text, { type: "supporting", color: "secondary", as: "h3", children: t('@theorem.host.earlier') }), _jsx(List, { hasDividers: true, density: "compact", children: calls.slice(0, MAX_EARLIER).map((call) => {
|
|
83
|
+
const status = hostCallStatus(call);
|
|
84
|
+
return (_jsx(ListItem, { label: call.name, startContent: _jsx(StatusDot, { variant: STATUS_DOT[status], label: t(`@theorem.host.status.${status}`), isPulsing: call.isRunning }), endContent: _jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: time.format(call.startedAt) }), onClick: () => onPick(call) }, call.id));
|
|
85
|
+
}) })] }));
|
|
86
|
+
}
|
|
87
|
+
/** The picked tool's request, as fields or JSON, and Run. */
|
|
88
|
+
function ToolRequest({ tools, tool, check, text, view, shown, onText, onPick, onView, onRun, onStop }) {
|
|
89
|
+
const t = useLabels();
|
|
90
|
+
const fieldsInput = 'input' in check ? check.input : undefined;
|
|
91
|
+
const showFields = view === 'fields' && fieldsInput !== undefined;
|
|
92
|
+
return (_jsxs(RequestCard, { title: t('@theorem.host.request'), view: showFields ? 'fields' : 'json', hasFields: fieldsInput !== undefined, onView: onView, onRun: onRun, children: [_jsxs(VStack, { gap: 2, children: [_jsx(Selector, { label: t('@theorem.host.tool'), size: "sm", options: tools.map((option) => ({ value: option.name, label: option.name, description: option.description })), value: tool.name, hasSearch: tools.length > 8, description: tool.description, onChange: onPick }), _jsx(ToolTokens, { tool: tool })] }), showFields ? (_jsx(SchemaFields
|
|
93
|
+
// A new tool starts a new form, so inputs don't carry state across schemas.
|
|
94
|
+
, { schema: tool.inputSchema, value: fieldsInput, onChange: (next) => {
|
|
95
|
+
onText(json(next));
|
|
96
|
+
} }, tool.name)) : (_jsx(JsonRequest, { label: t('@theorem.host.request'), text: text, isInvalid: fieldsInput === undefined, onChange: onText })), _jsx(RunBar, { note: check.ok ? t('@theorem.host.shortcut') : check.note, isReady: check.ok, isRunning: shown?.isRunning === true, runLabel: t('@theorem.host.run'), runningLabel: t('@theorem.host.running'), stopLabel: t('@theorem.host.stop'), onRun: onRun, onStop: () => {
|
|
97
|
+
if (shown)
|
|
98
|
+
onStop(shown.id);
|
|
99
|
+
} })] }));
|
|
100
|
+
}
|
|
101
|
+
function HostBody({ transport, iface, host, maxWidth = '960px', trace, className, style }) {
|
|
102
|
+
const t = useLabels();
|
|
103
|
+
const [picked, setPicked] = useState(iface.tools[0]?.name);
|
|
104
|
+
// Each tool keeps its own request while you move between them.
|
|
105
|
+
const [drafts, setDrafts] = useState({});
|
|
106
|
+
const [shownId, setShownId] = useState(null);
|
|
107
|
+
const [view, setView] = useState('fields');
|
|
108
|
+
const inspector = useTraceInspector(iface, transport.traces, trace);
|
|
109
|
+
const { tool, text, shown, earlier } = hostConsoleView({ tools: iface.tools, picked, drafts, calls: host.calls, shownId });
|
|
110
|
+
const check = tool ? checkRequest(t, tool, text) : null;
|
|
111
|
+
const setText = (next) => {
|
|
112
|
+
if (tool)
|
|
113
|
+
setDrafts((previous) => ({ ...previous, [tool.name]: next }));
|
|
114
|
+
};
|
|
115
|
+
const run = () => {
|
|
116
|
+
if (tool && check?.ok && !shown?.isRunning)
|
|
117
|
+
setShownId(host.run(tool.name, check.input));
|
|
118
|
+
};
|
|
119
|
+
const pick = (call) => {
|
|
120
|
+
setPicked(call.name);
|
|
121
|
+
setDrafts((previous) => ({ ...previous, [call.name]: json(call.input) }));
|
|
122
|
+
setShownId(call.id);
|
|
123
|
+
};
|
|
124
|
+
return (_jsxs(ConsoleFrame, { inspector: inspector, maxWidth: maxWidth, className: className, style: style, children: [tool && check ? (_jsx(ToolRequest, { tools: iface.tools, tool: tool, check: check, text: text, view: view, shown: shown, onText: setText, onPick: setPicked, onView: setView, onRun: run, onStop: host.cancel })) : (_jsx(EmptyState, { title: t('@theorem.host.no_tools') })), shown ? _jsx(CallOutcome, { call: shown, onAnswer: host.answer }) : null, _jsx(EarlierCalls, { calls: earlier, onPick: pick })] }));
|
|
125
|
+
}
|
|
126
|
+
function HostForTransport(props) {
|
|
127
|
+
const t = useLabels();
|
|
128
|
+
const host = useTheoremHost(props.transport);
|
|
129
|
+
if (host.describeFailure)
|
|
130
|
+
return _jsx(Banner, { status: "error", title: host.describeFailure.error });
|
|
131
|
+
if (!host.iface)
|
|
132
|
+
return _jsx(Spinner, { size: "lg", label: t('@theorem.chat.loading') });
|
|
133
|
+
return _jsx(HostBody, { ...props, iface: host.iface, host: host });
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Drop-in console for a host profile served by `createTheoremHostHandler`:
|
|
137
|
+
* pick a tool, fill in the request its input schema draws, Run (⌘/Ctrl+Enter),
|
|
138
|
+
* and read the response laid out from what it returned — figures, charts,
|
|
139
|
+
* images, and the data itself. Gates are answered in place.
|
|
140
|
+
*
|
|
141
|
+
* ```tsx
|
|
142
|
+
* <TheoremHost endpoint="/api/host" />
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export function TheoremHost({ endpoint, http, transport, theme, mode, labels, ...rest }) {
|
|
146
|
+
const resolved = useMemo(() => transport ?? createHostTransport({ ...http, endpoint }),
|
|
147
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- `http` identity is caller-owned; endpoint drives reconnection.
|
|
148
|
+
[transport, endpoint]);
|
|
149
|
+
return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(HostForTransport, { ...rest, transport: resolved }) }) }));
|
|
150
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ToolGate } from '@theoremjs/agents/kernel';
|
|
2
|
+
import type { ToolDecisionAction } from '../client/tool-resume.ts';
|
|
3
|
+
export type ToolDecision = ToolDecisionAction;
|
|
4
|
+
export type ApprovalCardProps = {
|
|
5
|
+
gate: ToolGate;
|
|
6
|
+
toolName: string;
|
|
7
|
+
input?: unknown;
|
|
8
|
+
/** The decision on its way; it shows in place of the buttons until the gate settles, or they return. */
|
|
9
|
+
decided?: ToolDecision | null;
|
|
10
|
+
onDecision?: (action: ToolDecision) => void;
|
|
11
|
+
};
|
|
12
|
+
export declare function ApprovalCard(props: ApprovalCardProps): import("react").JSX.Element;
|
|
13
|
+
/** A gate that needs the user to sign in first. */
|
|
14
|
+
export type AuthGate = Extract<ToolGate, {
|
|
15
|
+
kind: 'auth';
|
|
16
|
+
}>;
|
|
17
|
+
export type AuthChallengeCardProps = {
|
|
18
|
+
gate: AuthGate;
|
|
19
|
+
toolName: string;
|
|
20
|
+
/**
|
|
21
|
+
* Signed in: `secret` is the key the user typed, for the server to save; after
|
|
22
|
+
* an OAuth sign-in (the popup's `notifyOAuthComplete`) there is none.
|
|
23
|
+
*/
|
|
24
|
+
onAuthenticated?: (secret?: string) => void;
|
|
25
|
+
/** Signed in, and the answer is on its way; the sign-in returns if it fails. */
|
|
26
|
+
submitted?: boolean;
|
|
27
|
+
};
|
|
28
|
+
export declare function AuthChallengeCard(props: AuthChallengeCardProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,100 @@
|
|
|
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 { CodeBlock } from '@astryxdesign/core/CodeBlock';
|
|
6
|
+
import { Collapsible } from '@astryxdesign/core/Collapsible';
|
|
7
|
+
import { Heading } from '@astryxdesign/core/Heading';
|
|
8
|
+
import { HStack } from '@astryxdesign/core/HStack';
|
|
9
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
10
|
+
import { TextInput } from '@astryxdesign/core/TextInput';
|
|
11
|
+
import { Token } from '@astryxdesign/core/Token';
|
|
12
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
13
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
14
|
+
import { isOAuthComplete } from "../client/oauth-popup.js";
|
|
15
|
+
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
16
|
+
/** What an approval means is the tool registrant's call: `session_consent` lasts the session. */
|
|
17
|
+
function decisionLabel(t, decision, gate) {
|
|
18
|
+
if (decision === 'deny')
|
|
19
|
+
return t('@theorem.gate.approval.denied');
|
|
20
|
+
return t(gate.permission === 'session_consent'
|
|
21
|
+
? '@theorem.gate.approval.approved_session'
|
|
22
|
+
: '@theorem.gate.approval.approved_once');
|
|
23
|
+
}
|
|
24
|
+
function formatInput(value) {
|
|
25
|
+
if (typeof value === 'string')
|
|
26
|
+
return value;
|
|
27
|
+
if (value === undefined || value === null)
|
|
28
|
+
return '';
|
|
29
|
+
return JSON.stringify(value, null, 2);
|
|
30
|
+
}
|
|
31
|
+
function CardHeader(props) {
|
|
32
|
+
return (_jsxs(HStack, { justify: "between", align: "center", gap: 2, children: [_jsxs(VStack, { gap: 1, children: [_jsx(Badge, { variant: "warning", label: props.badge }), _jsxs(Heading, { level: 4, children: [props.title, " ", _jsx("code", { children: props.toolName })] })] }), _jsx(Badge, { variant: "neutral", label: props.tag })] }));
|
|
33
|
+
}
|
|
34
|
+
export function ApprovalCard(props) {
|
|
35
|
+
return (_jsx(TheoremLabelsProvider, { children: _jsx(ApprovalBody, { ...props }) }));
|
|
36
|
+
}
|
|
37
|
+
function ApprovalBody({ gate, toolName, input, decided = null, onDecision }) {
|
|
38
|
+
const t = useLabels();
|
|
39
|
+
const args = formatInput(input);
|
|
40
|
+
return (_jsx(Card, { padding: 4, children: _jsxs(VStack, { gap: 3, children: [_jsx(CardHeader, { badge: t('@theorem.gate.approval.badge'), title: t('@theorem.gate.approval.title'), toolName: toolName, tag: t(`@theorem.gate.tag.${gate.permission ?? gate.kind}`) }), gate.summary ? _jsx(Text, { children: gate.summary }) : null, args ? (_jsx(Collapsible, { trigger: _jsx(Text, { size: "sm", children: t('@theorem.gate.approval.input') }), children: _jsx(CodeBlock, { code: args, language: "json", size: "sm" }) })) : null, decided === null ? (_jsxs(HStack, { gap: 2, justify: "end", children: [_jsx(Button, { label: t('@theorem.gate.approval.deny'), variant: "ghost", onClick: () => onDecision?.('deny') }), _jsx(Button, { label: t('@theorem.gate.approval.approve'), variant: "primary", onClick: () => onDecision?.('allow') })] })) : (_jsx(Badge, { variant: decided === 'deny' ? 'error' : 'success', label: decisionLabel(t, decided, gate) }))] }) }));
|
|
41
|
+
}
|
|
42
|
+
function AuthChallengeDetails({ challenge }) {
|
|
43
|
+
const t = useLabels();
|
|
44
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { children: challenge.message }), challenge.resource ? (_jsx(Text, { size: "sm", color: "secondary", children: t('@theorem.gate.auth.resource', { resource: challenge.resource }) })) : null, challenge.requiredScopes?.length ? (_jsx(HStack, { gap: 1, wrap: "wrap", children: challenge.requiredScopes.map((scope) => (_jsx(Token, { label: scope, size: "sm" }, scope))) })) : null] }));
|
|
45
|
+
}
|
|
46
|
+
export function AuthChallengeCard(props) {
|
|
47
|
+
return (_jsx(TheoremLabelsProvider, { children: _jsx(AuthChallengeBody, { ...props }) }));
|
|
48
|
+
}
|
|
49
|
+
/** Wait for the sign-in popup to report its slot signed in. */
|
|
50
|
+
function useOAuthPopup(slot, onComplete) {
|
|
51
|
+
const [popup, setPopup] = useState(null);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!popup)
|
|
54
|
+
return;
|
|
55
|
+
function onMessage(event) {
|
|
56
|
+
if (!popup || !isOAuthComplete(event, { popup, slot, origin: globalThis.location.origin }))
|
|
57
|
+
return;
|
|
58
|
+
setPopup(null);
|
|
59
|
+
onComplete();
|
|
60
|
+
}
|
|
61
|
+
globalThis.addEventListener('message', onMessage);
|
|
62
|
+
return () => globalThis.removeEventListener('message', onMessage);
|
|
63
|
+
}, [popup, slot, onComplete]);
|
|
64
|
+
return (url) => setPopup(globalThis.open(url, '_blank', 'width=600,height=700'));
|
|
65
|
+
}
|
|
66
|
+
function AuthChallengeBody({ gate, toolName, onAuthenticated, submitted = false }) {
|
|
67
|
+
const t = useLabels();
|
|
68
|
+
const challenge = gate.authChallenge;
|
|
69
|
+
const { authType, slot } = challenge;
|
|
70
|
+
const [secret, setSecret] = useState('');
|
|
71
|
+
const onOAuthComplete = useCallback(() => onAuthenticated?.(), [onAuthenticated]);
|
|
72
|
+
const openSignIn = useOAuthPopup(slot, onOAuthComplete);
|
|
73
|
+
function submit() {
|
|
74
|
+
const value = secret.trim();
|
|
75
|
+
if (!value)
|
|
76
|
+
return;
|
|
77
|
+
// The key goes to the server once; the browser keeps no copy.
|
|
78
|
+
setSecret('');
|
|
79
|
+
onAuthenticated?.(value);
|
|
80
|
+
}
|
|
81
|
+
return (_jsx(Card, { padding: 4, children: _jsxs(VStack, { gap: 3, children: [_jsx(CardHeader, { badge: t('@theorem.gate.auth.badge'), title: t('@theorem.gate.auth.title'), toolName: toolName, tag: t(`@theorem.gate.tag.${authType}`) }), _jsx(AuthChallengeDetails, { challenge: challenge }), _jsx(AuthAction, { authType: authType, authUrl: challenge.authorizationUrl, slot: slot, secret: secret, submitted: submitted, onSecretChange: setSecret, onSubmit: submit, onOpenSignIn: openSignIn })] }) }));
|
|
82
|
+
}
|
|
83
|
+
function AuthAction(props) {
|
|
84
|
+
const t = useLabels();
|
|
85
|
+
if (props.submitted) {
|
|
86
|
+
return _jsx(Badge, { variant: "success", label: t('@theorem.gate.auth.provided', { slot: props.slot }) });
|
|
87
|
+
}
|
|
88
|
+
if (props.authType === 'oauth2') {
|
|
89
|
+
if (!props.authUrl) {
|
|
90
|
+
return _jsx(Text, { color: "secondary", children: t('@theorem.gate.auth.no_oauth') });
|
|
91
|
+
}
|
|
92
|
+
return (_jsx(Button, { label: t('@theorem.gate.auth.authorize'), variant: "primary", onClick: () => {
|
|
93
|
+
if (props.authUrl)
|
|
94
|
+
props.onOpenSignIn(props.authUrl);
|
|
95
|
+
} }));
|
|
96
|
+
}
|
|
97
|
+
const label = t(props.authType === 'api_key' ? '@theorem.gate.auth.api_key' : '@theorem.gate.auth.bearer');
|
|
98
|
+
const note = t('@theorem.gate.auth.secret_note').trim();
|
|
99
|
+
return (_jsxs(HStack, { gap: 2, align: "end", children: [_jsx(TextInput, { type: "password", label: label, ...(note ? { description: note } : {}), value: props.secret, placeholder: t('@theorem.gate.auth.secret_placeholder', { slot: props.slot }), autoComplete: "off", onChange: props.onSecretChange, onEnter: props.onSubmit, width: "100%" }), _jsx(Button, { label: t('@theorem.gate.auth.submit'), variant: "primary", isDisabled: !props.secret.trim(), onClick: props.onSubmit })] }));
|
|
100
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ResultMedia } from '../client/tool-result.ts';
|
|
2
|
+
/**
|
|
3
|
+
* A tool's output under one Response heading, led by what answers it (figures,
|
|
4
|
+
* charts, the images and audio it returned), then the data itself drawn by its shape, with its JSON one
|
|
5
|
+
* tap away. A payload too big to lay out, or a render that throws, shows as JSON.
|
|
6
|
+
*/
|
|
7
|
+
export declare function ToolResult({ output, parts }: {
|
|
8
|
+
output: unknown;
|
|
9
|
+
parts?: readonly ResultMedia[];
|
|
10
|
+
}): import("react").JSX.Element;
|
package/ui/ToolResult.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AspectRatio } from '@astryxdesign/core/AspectRatio';
|
|
3
|
+
import { ClickableCard } from '@astryxdesign/core/ClickableCard';
|
|
4
|
+
import { Grid } from '@astryxdesign/core/Grid';
|
|
5
|
+
import { Lightbox } from '@astryxdesign/core/Lightbox';
|
|
6
|
+
import { Skeleton } from '@astryxdesign/core/Skeleton';
|
|
7
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
8
|
+
import { useLocale } from '@astryxdesign/core/i18n';
|
|
9
|
+
import { lazy, Suspense, useMemo, useState } from 'react';
|
|
10
|
+
import { withUnit } from "../client/shaped-data.js";
|
|
11
|
+
import { hasLead, layoutResult, returnedMedia, } from "../client/tool-result.js";
|
|
12
|
+
import { useLabels } from "./labels-provider.js";
|
|
13
|
+
import { ShapedData } from "./ShapedData.js";
|
|
14
|
+
import { VoiceNote } from "./VoiceNote.js";
|
|
15
|
+
import { ChartCard } from "./trace-patterns.js";
|
|
16
|
+
const ToolResultCharts = lazy(() => import("./ToolResultCharts.js").then((module) => ({ default: module.ToolResultCharts })));
|
|
17
|
+
/** Narrowest an image card gets. */
|
|
18
|
+
const IMAGE_CARD_PX = 200;
|
|
19
|
+
/** Height a chart card holds while its module loads. */
|
|
20
|
+
const CHART_CARD_PX = 236;
|
|
21
|
+
function Images({ images }) {
|
|
22
|
+
const [open, setOpen] = useState(null);
|
|
23
|
+
return (_jsxs(_Fragment, { children: [_jsx(Grid, { columns: { minWidth: IMAGE_CARD_PX, repeat: 'fill' }, gap: 3, children: images.map((image, index) => (_jsx(ClickableCard, { label: image.alt || 'Open image', padding: 0, onClick: () => setOpen(index), children: _jsx(AspectRatio, { ratio: 4 / 3, fit: "cover", children: _jsx("img", { src: image.src, alt: image.alt, loading: "lazy" }) }) }, image.src))) }), _jsx(Lightbox, { isOpen: open !== null, onOpenChange: (isOpen) => {
|
|
24
|
+
if (!isOpen)
|
|
25
|
+
setOpen(null);
|
|
26
|
+
}, media: images.map((image) => ({ src: image.src, alt: image.alt })), index: open ?? 0, onIndexChange: setOpen })] }));
|
|
27
|
+
}
|
|
28
|
+
function Figures({ figures }) {
|
|
29
|
+
const locale = useLocale();
|
|
30
|
+
const decimal = useMemo(() => new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }), [locale]);
|
|
31
|
+
if (figures.length === 0)
|
|
32
|
+
return null;
|
|
33
|
+
return (_jsx(Grid, { columns: { minWidth: 160, repeat: 'fill' }, gap: 3, children: figures.map((figure) => (_jsx(ChartCard, { title: figure.label, value: withUnit(decimal.format(figure.value), figure.unit) }, figure.key))) }));
|
|
34
|
+
}
|
|
35
|
+
/** Charts load with their module; cards hold their place until then. */
|
|
36
|
+
function Charts({ charts }) {
|
|
37
|
+
if (charts.length === 0)
|
|
38
|
+
return null;
|
|
39
|
+
return (_jsx(Suspense, { fallback: _jsx(Grid, { columns: { minWidth: 260, repeat: 'fill' }, gap: 3, children: charts.map((chart) => (_jsx(Skeleton, { height: CHART_CARD_PX }, chart.key))) }), children: _jsx(ToolResultCharts, { charts: charts }) }));
|
|
40
|
+
}
|
|
41
|
+
/** What answers the call, laid out from the output: its figures as stat cards, its series as charts, the images and audio it returned. */
|
|
42
|
+
function ResultLead({ layout, parts = [] }) {
|
|
43
|
+
const t = useLabels();
|
|
44
|
+
const media = useMemo(() => returnedMedia(layout, parts, t('@theorem.host.image')), [layout, parts, t]);
|
|
45
|
+
if (!hasLead(layout, media))
|
|
46
|
+
return null;
|
|
47
|
+
return (_jsxs(VStack, { gap: 3, children: [_jsx(Figures, { figures: layout.figures }), _jsx(Charts, { charts: layout.charts }), media.images.length > 0 ? _jsx(Images, { images: media.images }) : null, media.audio.map((clip, index) => (_jsx(VoiceNote, { src: clip.src, mimeType: clip.mimeType }, index)))] }));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A tool's output under one Response heading, led by what answers it (figures,
|
|
51
|
+
* charts, the images and audio it returned), then the data itself drawn by its shape, with its JSON one
|
|
52
|
+
* tap away. A payload too big to lay out, or a render that throws, shows as JSON.
|
|
53
|
+
*/
|
|
54
|
+
export function ToolResult({ output, parts }) {
|
|
55
|
+
const t = useLabels();
|
|
56
|
+
const layout = useMemo(() => layoutResult(output), [output]);
|
|
57
|
+
// A figure's card is its reading: the fields below don't repeat it.
|
|
58
|
+
const ledKeys = useMemo(() => layout.figures.map((figure) => figure.key), [layout]);
|
|
59
|
+
return _jsx(ShapedData, { value: output, title: t('@theorem.host.response'), lead: _jsx(ResultLead, { layout: layout, parts: parts }), ledKeys: ledKeys });
|
|
60
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
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 { useLocale } from '@astryxdesign/core/i18n';
|
|
7
|
+
import { useMemo } from 'react';
|
|
8
|
+
import { Bar, BarChart, Cell, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
|
|
9
|
+
import { withUnit } from "../client/shaped-data.js";
|
|
10
|
+
import { timeFormats } from "../client/tool-result.js";
|
|
11
|
+
import { ChartFrame, ChartTip, FIRST_WIDTH_PX } from "./TraceOverview.js";
|
|
12
|
+
import { ChartCard, TRACE_GAP, TRACE_RADIUS, TraceSwatch, traceFill } from "./trace-patterns.js";
|
|
13
|
+
/**
|
|
14
|
+
* A tool result's charts, drawn as the trace draws its own: the same card,
|
|
15
|
+
* fills, ticks, and hover card. Recharts loads with this module, only when a
|
|
16
|
+
* result has a chart.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
/** Height of a time chart's plot, in pixels. */
|
|
21
|
+
const CHART_PX = 140;
|
|
22
|
+
/** Height of one row of a ranking, in pixels. */
|
|
23
|
+
const BAR_ROW_PX = 22;
|
|
24
|
+
const TICK = { fill: 'var(--color-text-secondary)', fontSize: 11 };
|
|
25
|
+
const BAR = { radius: TRACE_RADIUS, stroke: TRACE_GAP, strokeWidth: 2, isAnimationActive: false };
|
|
26
|
+
/** Each overlaid series, in the trace's order. */
|
|
27
|
+
const SERIES_FILL = ['clear', 'shade1', 'shade2'];
|
|
28
|
+
function useFormats(chart) {
|
|
29
|
+
const locale = useLocale();
|
|
30
|
+
return useMemo(() => {
|
|
31
|
+
const decimal = new Intl.NumberFormat(locale, { maximumFractionDigits: 2 });
|
|
32
|
+
const number = (value, unit) => withUnit(decimal.format(value), unit);
|
|
33
|
+
const dates = chart.kind === 'time' ? chart.rows.map((row) => String(row[chart.x])) : [];
|
|
34
|
+
const options = timeFormats(dates);
|
|
35
|
+
const tick = new Intl.DateTimeFormat(locale, options.tick);
|
|
36
|
+
const when = new Intl.DateTimeFormat(locale, options.when);
|
|
37
|
+
const read = (format) => (text) => {
|
|
38
|
+
const at = Date.parse(text);
|
|
39
|
+
return Number.isNaN(at) ? text : format.format(at);
|
|
40
|
+
};
|
|
41
|
+
return { number, tick: read(tick), when: read(when) };
|
|
42
|
+
}, [chart, locale]);
|
|
43
|
+
}
|
|
44
|
+
function TimeTip({ active, payload, label, chart, formats }) {
|
|
45
|
+
if (!active || !payload?.length)
|
|
46
|
+
return null;
|
|
47
|
+
return (_jsx(ChartTip, { children: _jsxs(VStack, { gap: 0, children: [_jsx(Text, { weight: "medium", children: formats.when(String(label)) }), chart.series.map((series, index) => {
|
|
48
|
+
const value = payload.find((entry) => entry.dataKey === series.key)?.value;
|
|
49
|
+
return typeof value === 'number' ? (_jsxs(HStack, { gap: 2, vAlign: "center", children: [chart.series.length > 1 ? _jsx(TraceSwatch, { fill: SERIES_FILL[index] ?? 'clear' }) : null, _jsxs(Text, { type: "supporting", hasTabularNumbers: true, children: [chart.series.length > 1 ? `${series.label} ` : '', formats.number(value, series.unit)] })] }, series.key)) : null;
|
|
50
|
+
})] }) }));
|
|
51
|
+
}
|
|
52
|
+
/** One or more series over time: the first reading as the figure, the span of every reading as its note. */
|
|
53
|
+
function TimeCard({ chart }) {
|
|
54
|
+
const formats = useFormats(chart);
|
|
55
|
+
const [lead] = chart.series;
|
|
56
|
+
const values = chart.series.flatMap((series) => chart.rows.map((row) => row[series.key]).filter((value) => typeof value === 'number'));
|
|
57
|
+
const first = lead ? chart.rows.map((row) => row[lead.key]).find((value) => typeof value === 'number') : undefined;
|
|
58
|
+
if (!lead || first === undefined)
|
|
59
|
+
return null;
|
|
60
|
+
const unit = lead.unit;
|
|
61
|
+
const range = `${formats.number(Math.min(...values))}–${formats.number(Math.max(...values), unit)}`;
|
|
62
|
+
return (_jsxs(ChartCard, { title: chart.title, value: formats.number(first, unit), note: range, children: [_jsx(ChartFrame, { children: _jsx(ResponsiveContainer, { width: "100%", height: CHART_PX, initialDimension: { width: FIRST_WIDTH_PX, height: CHART_PX }, children: _jsxs(LineChart, { data: chart.rows, margin: { top: 4, right: 4, bottom: 0, left: 0 }, accessibilityLayer: false, children: [_jsx(XAxis, { dataKey: chart.x, tick: TICK, tickLine: false, axisLine: false, tickFormatter: formats.tick, minTickGap: 16 }), _jsx(YAxis, { tick: TICK, tickLine: false, axisLine: false, width: 36, domain: ['auto', 'auto'], tickFormatter: (value) => formats.number(value) }), _jsx(Tooltip, { content: (props) => _jsx(TimeTip, { ...props, chart: chart, formats: formats }), cursor: { stroke: 'var(--color-border-emphasized)' }, isAnimationActive: false }), chart.series.map((series, index) => (_jsx(Line, { type: "monotone", dataKey: series.key, stroke: traceFill(SERIES_FILL[index] ?? 'clear'), strokeWidth: 2, dot: false, activeDot: { r: 3, strokeWidth: 0 }, connectNulls: true, isAnimationActive: false }, series.key)))] }) }) }), chart.series.length > 1 ? (_jsx(HStack, { gap: 3, wrap: "wrap", children: chart.series.map((series, index) => (_jsxs(HStack, { gap: 2, vAlign: "center", children: [_jsx(TraceSwatch, { fill: SERIES_FILL[index] ?? 'clear' }), _jsx(Text, { type: "supporting", color: "secondary", children: series.label })] }, series.key))) })) : null] }));
|
|
63
|
+
}
|
|
64
|
+
function BarTip({ active, payload, chart, formats }) {
|
|
65
|
+
const row = payload?.[0]?.payload;
|
|
66
|
+
if (!active || !row)
|
|
67
|
+
return null;
|
|
68
|
+
return (_jsx(ChartTip, { children: _jsxs(VStack, { gap: 0, children: [_jsx(Text, { weight: "medium", children: row.label }), _jsx(Text, { type: "supporting", hasTabularNumbers: true, children: formats.number(row.value, chart.series.unit) })] }) }));
|
|
69
|
+
}
|
|
70
|
+
/** Rows ranked by one number, largest first, drawn like the trace's slowest steps. */
|
|
71
|
+
function CategoryCard({ chart }) {
|
|
72
|
+
const formats = useFormats(chart);
|
|
73
|
+
const rows = useMemo(() => chart.rows.map((row, index) => ({ ...row, id: String(index) })), [chart]);
|
|
74
|
+
const [top] = rows;
|
|
75
|
+
if (!top)
|
|
76
|
+
return null;
|
|
77
|
+
const height = rows.length * BAR_ROW_PX;
|
|
78
|
+
return (_jsx(ChartCard, { title: chart.title, value: formats.number(top.value, chart.series.unit), note: top.label, 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: 56, bottom: 0, left: 0 }, accessibilityLayer: false, barCategoryGap: 5, 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) => {
|
|
79
|
+
const label = rows[Number(id)]?.label ?? '';
|
|
80
|
+
return label.length > 15 ? `${label.slice(0, 14)}…` : label;
|
|
81
|
+
} }), _jsx(Tooltip, { content: (props) => _jsx(BarTip, { ...props, chart: chart, formats: formats }), cursor: { fill: 'var(--color-overlay-hover)', radius: TRACE_RADIUS }, isAnimationActive: false }), _jsx(Bar, { dataKey: "value", ...BAR, label: { position: 'right', fill: 'var(--color-text-secondary)', fontSize: 11, formatter: (value) => formats.number(Number(value), chart.series.unit) }, children: rows.map((row, index) => (_jsx(Cell, { fill: traceFill(index === 0 ? 'clear' : 'shade2') }, row.id))) })] }) }) }) }));
|
|
82
|
+
}
|
|
83
|
+
/** Each chart in its card, side by side where the column is wide enough. */
|
|
84
|
+
export function ToolResultCharts({ charts }) {
|
|
85
|
+
return (_jsx(Grid, { columns: { minWidth: 260, repeat: 'fill' }, gap: 3, children: charts.map((chart) => (chart.kind === 'time' ? _jsx(TimeCard, { chart: chart }, chart.key) : _jsx(CategoryCard, { chart: chart }, chart.key))) }));
|
|
86
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type TraceTimeSplit, type TraceTurn } from '../client/trace-story.ts';
|
|
2
|
+
import { type TraceNode } from '../client/trace-view.ts';
|
|
3
|
+
/** Where a turn's time, tokens and slowest steps went. A bar opens its span. */
|
|
4
|
+
export declare function TraceCharts({ root, split, onSelect, }: {
|
|
5
|
+
root: TraceNode;
|
|
6
|
+
split: TraceTimeSplit;
|
|
7
|
+
onSelect: (node: TraceNode) => void;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
/** The whole conversation: each turn's time and tokens. A column opens its turn. */
|
|
10
|
+
export declare function ConversationCharts({ turns, onPick }: {
|
|
11
|
+
turns: readonly TraceTurn[];
|
|
12
|
+
onPick: (index: number) => void;
|
|
13
|
+
}): import("react").JSX.Element;
|