@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,240 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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 { useLocale } from '@astryxdesign/core/i18n';
|
|
6
|
+
import { List, ListItem } from '@astryxdesign/core/List';
|
|
7
|
+
import { Selector, SelectorOption } from '@astryxdesign/core/Selector';
|
|
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 { IconMathFunction, IconWorld } from '@tabler/icons-react';
|
|
13
|
+
import { useMemo, useRef, 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 { toolCallLabel } from "../client/transcript-groups.js";
|
|
19
|
+
import { useSecondTicker } from "../hooks/use-second-ticker.js";
|
|
20
|
+
import { useTheoremHost } from "../hooks/use-theorem-host.js";
|
|
21
|
+
import { Arrive, Stream } from "./arrive.js";
|
|
22
|
+
import { ConsoleFrame, JsonRequest, RequestCard, ResponseColumn, RunBar, } from "./ConsoleFrame.js";
|
|
23
|
+
import { liveDuration, workDuration } from "./labels.js";
|
|
24
|
+
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
25
|
+
import { MarkdownPluginsProvider } from "./markdown-plugins.js";
|
|
26
|
+
import { IconMcp } from "./mcp-icon.js";
|
|
27
|
+
import { PaneFailure, PaneLoading } from "./PaneState.js";
|
|
28
|
+
import { SchemaFields } from "./SchemaFields.js";
|
|
29
|
+
import { ApprovalCard, AuthChallengeCard } from "./ToolGateCard.js";
|
|
30
|
+
import { ToolResult } from "./ToolResult.js";
|
|
31
|
+
import { useTraceInspector } from "./TraceInspectorPanel.js";
|
|
32
|
+
import { TheoremThemeProvider } from "./theme.js";
|
|
33
|
+
/** Calls listed under the console before older ones drop off the page. */
|
|
34
|
+
const MAX_EARLIER = 8;
|
|
35
|
+
const STATUS_DOT = {
|
|
36
|
+
running: 'accent',
|
|
37
|
+
gate: 'warning',
|
|
38
|
+
complete: 'success',
|
|
39
|
+
error: 'error',
|
|
40
|
+
cancel: 'neutral',
|
|
41
|
+
};
|
|
42
|
+
/** The request's text as the input a call sends, and what it still lacks. */
|
|
43
|
+
function checkRequest(t, tool, text) {
|
|
44
|
+
let input;
|
|
45
|
+
try {
|
|
46
|
+
input = JSON.parse(text);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { ok: false, note: t('@theorem.decision.invalid_json') };
|
|
50
|
+
}
|
|
51
|
+
if (!isRow(input))
|
|
52
|
+
return { ok: false, note: t('@theorem.decision.invalid_json') };
|
|
53
|
+
const missing = missingFields(tool.inputSchema, input);
|
|
54
|
+
if (missing.length > 0)
|
|
55
|
+
return { ok: false, note: t('@theorem.host.missing', { fields: missing.join(', ') }), input };
|
|
56
|
+
return { ok: true, input };
|
|
57
|
+
}
|
|
58
|
+
/** The kind's icon. The same glyphs the studio uses for a tool's type. */
|
|
59
|
+
const KIND_ICON = {
|
|
60
|
+
function: IconMathFunction,
|
|
61
|
+
http: IconWorld,
|
|
62
|
+
mcp: IconMcp,
|
|
63
|
+
};
|
|
64
|
+
/** The most room a row of the tool menu takes, and the lines of description it shows. */
|
|
65
|
+
const TOOL_MENU_PX = 440;
|
|
66
|
+
const TOOL_MENU_LINES = 2;
|
|
67
|
+
/** Kind and description, shown on the dropdown row and nowhere else. */
|
|
68
|
+
function toolOptionDescription(t, tool) {
|
|
69
|
+
const kind = t(`@theorem.host.kind.${tool.kind}`);
|
|
70
|
+
return tool.description ? `${kind} · ${tool.description}` : kind;
|
|
71
|
+
}
|
|
72
|
+
/** A tool's access when it can change things. Its kind stays in the dropdown. */
|
|
73
|
+
function ToolTokens({ tool }) {
|
|
74
|
+
const t = useLabels();
|
|
75
|
+
if (tool.access === 'read-only')
|
|
76
|
+
return null;
|
|
77
|
+
return (_jsx(HStack, { gap: 1, wrap: "wrap", children: _jsx(Token, { label: t(`@theorem.tool.access.${tool.access}`), size: "sm", color: tool.access === 'destructive' ? 'red' : 'orange' }) }));
|
|
78
|
+
}
|
|
79
|
+
/** The tool's activity while it runs, and its activityPast once it has finished, with the clock beside it. */
|
|
80
|
+
function callActivity(t, call, now) {
|
|
81
|
+
const label = call.call ? toolCallLabel(call.call) : call.name;
|
|
82
|
+
const duration = call.isRunning
|
|
83
|
+
? liveDuration(t, now - call.startedAt)
|
|
84
|
+
: call.elapsedMs == null
|
|
85
|
+
? null
|
|
86
|
+
: workDuration(t, call.elapsedMs);
|
|
87
|
+
return { label, duration };
|
|
88
|
+
}
|
|
89
|
+
/** The gate a call paused on, answered in place. */
|
|
90
|
+
function CallGate({ call, onAnswer, }) {
|
|
91
|
+
const state = call.call?.state;
|
|
92
|
+
if (state?.phase !== 'gate')
|
|
93
|
+
return null;
|
|
94
|
+
if (state.gate.kind === 'auth') {
|
|
95
|
+
return (_jsx(AuthChallengeCard, { gate: state.gate, toolName: call.name, submitted: call.answering === 'auth', onAuthenticated: (secret) => void onAnswer(call.id, { action: 'auth', secret }) }));
|
|
96
|
+
}
|
|
97
|
+
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 }) }));
|
|
98
|
+
}
|
|
99
|
+
/** A call is settled once it failed, completed, or stopped running. */
|
|
100
|
+
function callSettled(call) {
|
|
101
|
+
const state = call.call?.state;
|
|
102
|
+
return Boolean(hostCallFailure(call)) || state?.phase === 'complete' || !call.isRunning;
|
|
103
|
+
}
|
|
104
|
+
/** A running call with nothing else in the column fills the card, the way an empty trace does. */
|
|
105
|
+
function responseFills(call) {
|
|
106
|
+
const state = call.call?.state;
|
|
107
|
+
return !callSettled(call) && state?.phase !== 'gate';
|
|
108
|
+
}
|
|
109
|
+
/** A settled call: its failure or its output, then the activity line once it has stopped. */
|
|
110
|
+
function CallSettled({ call, activity }) {
|
|
111
|
+
const state = call.call?.state;
|
|
112
|
+
const failure = hostCallFailure(call);
|
|
113
|
+
return (_jsx(Stream, { children: _jsxs(VStack, { gap: 3, children: [failure ? (_jsx(Arrive, { children: _jsx(Banner, { status: "error", role: "alert", title: failure.title, description: failure.description }) })) : null, state?.phase === 'complete' ? (_jsx(ToolResult, { output: state.output, parts: state.parts })) : null, call.isRunning ? null : _jsx(Arrive, { children: activity })] }) }));
|
|
114
|
+
}
|
|
115
|
+
/** One call's outcome: its gate, its failure, or its output, then the tool's activity and the time. */
|
|
116
|
+
function CallOutcome({ call, onAnswer, }) {
|
|
117
|
+
const t = useLabels();
|
|
118
|
+
const now = useSecondTicker(call.isRunning);
|
|
119
|
+
const { label, duration } = callActivity(t, call, now);
|
|
120
|
+
if (responseFills(call))
|
|
121
|
+
return _jsx(PaneLoading, { label: label, since: call.startedAt });
|
|
122
|
+
const activity = (_jsx(Text, { size: "sm", color: "secondary", children: duration ? t('@theorem.host.finished', { activity: label, duration }) : label }));
|
|
123
|
+
return (_jsxs(VStack, { gap: 3, children: [_jsx(CallGate, { call: call, onAnswer: onAnswer }), callSettled(call) ? _jsx(CallSettled, { call: call, activity: activity }) : null] }));
|
|
124
|
+
}
|
|
125
|
+
/** This page's earlier calls, newest first; picking one shows it again with its request. */
|
|
126
|
+
function EarlierCalls({ calls, onPick, }) {
|
|
127
|
+
const t = useLabels();
|
|
128
|
+
const locale = useLocale();
|
|
129
|
+
const time = useMemo(() => new Intl.DateTimeFormat(locale, { timeStyle: 'medium' }), [locale]);
|
|
130
|
+
if (calls.length === 0)
|
|
131
|
+
return null;
|
|
132
|
+
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) => {
|
|
133
|
+
const status = hostCallStatus(call);
|
|
134
|
+
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));
|
|
135
|
+
}) })] }));
|
|
136
|
+
}
|
|
137
|
+
/** The picked tool's request, as fields or JSON, and Run. */
|
|
138
|
+
function ToolRequest({ tools, tool, check, text, view, shown, onText, onPick, onView, onRun, onStop, }) {
|
|
139
|
+
const t = useLabels();
|
|
140
|
+
const fieldsInput = 'input' in check ? check.input : undefined;
|
|
141
|
+
const showFields = view === 'fields' && fieldsInput !== undefined;
|
|
142
|
+
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) => ({
|
|
143
|
+
value: option.name,
|
|
144
|
+
label: option.name,
|
|
145
|
+
icon: KIND_ICON[option.kind],
|
|
146
|
+
description: toolOptionDescription(t, option),
|
|
147
|
+
})), value: tool.name, hasSearch: tools.length > 8, renderOption: (option) => (_jsx("div", { style: { width: TOOL_MENU_PX, maxWidth: '100%', minWidth: 0 }, children: _jsx(SelectorOption, { icon: option.icon, label: option.label, description: option.description && (_jsx(Text, { type: "supporting", color: "secondary", maxLines: TOOL_MENU_LINES, children: option.description })) }) })), onChange: onPick }), _jsx(ToolTokens, { tool: tool })] }), showFields ? (_jsx(SchemaFields
|
|
148
|
+
// why: A new tool starts a new form, so inputs don't carry state across schemas.
|
|
149
|
+
, { schema: tool.inputSchema, value: fieldsInput, onChange: (next) => {
|
|
150
|
+
onText(json(next));
|
|
151
|
+
} }, 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: () => {
|
|
152
|
+
if (shown)
|
|
153
|
+
onStop(shown.id);
|
|
154
|
+
} })] }));
|
|
155
|
+
}
|
|
156
|
+
/** Starts the request as a call, and returns its id. Nothing starts while one is on screen running. */
|
|
157
|
+
function startCall(host, tool, check, shown) {
|
|
158
|
+
if (!tool || !check?.ok || shown?.isRunning)
|
|
159
|
+
return null;
|
|
160
|
+
return host.run(tool.name, check.input);
|
|
161
|
+
}
|
|
162
|
+
/** What the console is showing: the picked tool, its request, and the call on screen. */
|
|
163
|
+
function useHostConsole(iface, host) {
|
|
164
|
+
const t = useLabels();
|
|
165
|
+
const [picked, setPicked] = useState(iface.tools[0]?.name);
|
|
166
|
+
const [drafts, setDrafts] = useState({});
|
|
167
|
+
const [shownId, setShownId] = useState(null);
|
|
168
|
+
const [view, setView] = useState('fields');
|
|
169
|
+
const { tool, text, shown, earlier } = hostConsoleView({
|
|
170
|
+
tools: iface.tools,
|
|
171
|
+
picked,
|
|
172
|
+
drafts,
|
|
173
|
+
calls: host.calls,
|
|
174
|
+
shownId,
|
|
175
|
+
});
|
|
176
|
+
const check = tool ? checkRequest(t, tool, text) : null;
|
|
177
|
+
const setText = (next) => {
|
|
178
|
+
if (tool)
|
|
179
|
+
setDrafts((previous) => ({ ...previous, [tool.name]: next }));
|
|
180
|
+
};
|
|
181
|
+
const run = () => {
|
|
182
|
+
const id = startCall(host, tool, check, shown);
|
|
183
|
+
if (id !== null)
|
|
184
|
+
setShownId(id);
|
|
185
|
+
};
|
|
186
|
+
const pick = (call) => {
|
|
187
|
+
setPicked(call.name);
|
|
188
|
+
setDrafts((previous) => ({ ...previous, [call.name]: json(call.input) }));
|
|
189
|
+
setShownId(call.id);
|
|
190
|
+
};
|
|
191
|
+
return { tool, text, shown, earlier, check, view, setView, setPicked, setText, run, pick };
|
|
192
|
+
}
|
|
193
|
+
/** The call on screen, then this page's earlier ones. */
|
|
194
|
+
function HostCalls({ shown, earlier, onAnswer, onPick, }) {
|
|
195
|
+
return (_jsxs(_Fragment, { children: [shown ? _jsx(CallOutcome, { call: shown, onAnswer: onAnswer }) : null, _jsx(EarlierCalls, { calls: earlier, onPick: onPick })] }));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The frame's second child. A call, not a component: the frame counts its
|
|
199
|
+
* children to choose a layout, and no calls yet has to count as none.
|
|
200
|
+
*/
|
|
201
|
+
function hostResponse(columns, label, shown, earlier, calls) {
|
|
202
|
+
if (!columns)
|
|
203
|
+
return calls;
|
|
204
|
+
const alone = earlier.length === 0;
|
|
205
|
+
if (!shown && alone)
|
|
206
|
+
return null;
|
|
207
|
+
return (_jsx(ResponseColumn, { label: label, fill: shown != null && responseFills(shown) && alone, children: calls }));
|
|
208
|
+
}
|
|
209
|
+
function HostBody({ transport, iface, host, maxWidth = '960px', trace, flush, columns = false, className, style, }) {
|
|
210
|
+
const t = useLabels();
|
|
211
|
+
const inspector = useTraceInspector(iface, transport.traces, trace);
|
|
212
|
+
const desk = useHostConsole(iface, host);
|
|
213
|
+
const { tool, check, shown, earlier } = desk;
|
|
214
|
+
return (_jsxs(ConsoleFrame, { inspector: inspector, maxWidth: maxWidth, flush: flush, columns: columns, requestLabel: t('@theorem.host.request'), className: className, style: style, children: [tool && check ? (_jsx(ToolRequest, { tools: iface.tools, tool: tool, check: check, text: desk.text, view: desk.view, shown: shown, onText: desk.setText, onPick: desk.setPicked, onView: desk.setView, onRun: desk.run, onStop: host.cancel })) : (_jsx(EmptyState, { title: t('@theorem.host.no_tools') })), hostResponse(columns, t('@theorem.host.response'), shown, earlier, _jsx(HostCalls, { shown: shown, earlier: earlier, onAnswer: host.answer, onPick: desk.pick }))] }));
|
|
215
|
+
}
|
|
216
|
+
function HostForTransport(props) {
|
|
217
|
+
const t = useLabels();
|
|
218
|
+
const host = useTheoremHost(props.transport, props.pageTools);
|
|
219
|
+
if (host.describeFailure)
|
|
220
|
+
return _jsx(PaneFailure, { title: host.describeFailure.error });
|
|
221
|
+
if (!host.iface)
|
|
222
|
+
return _jsx(PaneLoading, { label: t('@theorem.chat.loading') });
|
|
223
|
+
return _jsx(HostBody, { ...props, iface: host.iface, host: host });
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Drop-in console for a host profile served by `createTheoremHostHandler`:
|
|
227
|
+
* pick a tool, fill in the request its input schema draws, Run (⌘/Ctrl+Enter),
|
|
228
|
+
* and read the response laid out from what it returned — figures, charts,
|
|
229
|
+
* images, and the data itself. Gates are answered in place.
|
|
230
|
+
*
|
|
231
|
+
* ```tsx
|
|
232
|
+
* <TheoremHost endpoint="/api/host" />
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
export function TheoremHost({ endpoint, http, transport, theme, mode, labels, detectCodeLanguage, ...rest }) {
|
|
236
|
+
const httpRef = useRef(http);
|
|
237
|
+
httpRef.current = http;
|
|
238
|
+
const resolved = useMemo(() => transport ?? createHostTransport({ ...httpRef.current, endpoint }), [transport, endpoint]);
|
|
239
|
+
return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(MarkdownPluginsProvider, { detectCodeLanguage: detectCodeLanguage, children: _jsx(HostForTransport, { ...rest, transport: resolved }) }) }) }));
|
|
240
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ToolGate } from '@theoremjs/agents/kernel';
|
|
2
|
+
import type { ToolDecisionAction } from '../client/tool-resume.ts';
|
|
3
|
+
export type ToolDecision = ToolDecisionAction;
|
|
4
|
+
/** The widest a gate card gets, in pixels. */
|
|
5
|
+
export declare const GATE_CARD_WIDTH = 400;
|
|
6
|
+
export type ApprovalCardProps = {
|
|
7
|
+
gate: ToolGate;
|
|
8
|
+
toolName: string;
|
|
9
|
+
/** The agent asking, as its name is shown (`@concierge`); the heading names it. */
|
|
10
|
+
agent?: string;
|
|
11
|
+
input?: unknown;
|
|
12
|
+
/** The decision on its way; its button shows loading and the other is disabled until the gate settles. */
|
|
13
|
+
decided?: ToolDecision | null;
|
|
14
|
+
onDecision?: (action: ToolDecision) => void;
|
|
15
|
+
};
|
|
16
|
+
export declare function ApprovalCard(props: ApprovalCardProps): import("react").JSX.Element;
|
|
17
|
+
/** A gate that needs the user to sign in first. */
|
|
18
|
+
export type AuthGate = Extract<ToolGate, {
|
|
19
|
+
kind: 'auth';
|
|
20
|
+
}>;
|
|
21
|
+
export type AuthChallengeCardProps = {
|
|
22
|
+
gate: AuthGate;
|
|
23
|
+
toolName: string;
|
|
24
|
+
/**
|
|
25
|
+
* Signed in: `secret` is the key the user typed, for the server to save; after
|
|
26
|
+
* an OAuth sign-in (the popup's `notifyOAuthComplete`) there is none.
|
|
27
|
+
*/
|
|
28
|
+
onAuthenticated?: (secret?: string) => void;
|
|
29
|
+
/** Signed in, and the answer is on its way; the sign-in returns if it fails. */
|
|
30
|
+
submitted?: boolean;
|
|
31
|
+
};
|
|
32
|
+
export declare function AuthChallengeCard(props: AuthChallengeCardProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,107 @@
|
|
|
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 { HStack } from '@astryxdesign/core/HStack';
|
|
8
|
+
import { Icon } from '@astryxdesign/core/Icon';
|
|
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 { IconEye, IconFlame, IconPencil } from '@tabler/icons-react';
|
|
14
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
15
|
+
import { isOAuthComplete } from "../client/oauth-popup.js";
|
|
16
|
+
import { humanize } from "../client/shaped-data.js";
|
|
17
|
+
import { approvalHeading } from "./approval-heading.js";
|
|
18
|
+
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
19
|
+
/** The widest a gate card gets, in pixels. */
|
|
20
|
+
export const GATE_CARD_WIDTH = 400;
|
|
21
|
+
function formatInput(value) {
|
|
22
|
+
if (typeof value === 'string')
|
|
23
|
+
return value;
|
|
24
|
+
if (value === undefined || value === null)
|
|
25
|
+
return '';
|
|
26
|
+
return JSON.stringify(value, null, 2);
|
|
27
|
+
}
|
|
28
|
+
/** The one frame of every gate card: what is asked, a tag beside it, and what answers it. */
|
|
29
|
+
function GateCard(props) {
|
|
30
|
+
return (_jsx(Card, { variant: "bubble", elevation: "med", maxWidth: GATE_CARD_WIDTH, children: _jsxs(VStack, { gap: 3, children: [_jsxs(HStack, { justify: "between", align: "start", gap: 3, children: [_jsx(Text, { type: "large", weight: "semibold", children: props.heading }), props.tag] }), props.children] }) }));
|
|
31
|
+
}
|
|
32
|
+
const ACCESS_ICON = {
|
|
33
|
+
'read-only': IconEye,
|
|
34
|
+
'read-write': IconPencil,
|
|
35
|
+
destructive: IconFlame,
|
|
36
|
+
};
|
|
37
|
+
function ToolAccessTag({ access }) {
|
|
38
|
+
const t = useLabels();
|
|
39
|
+
return (_jsxs(HStack, { gap: 1, align: "center", children: [_jsx(Icon, { icon: ACCESS_ICON[access], size: "sm", color: access === 'destructive' ? 'error' : 'secondary' }), _jsx(Text, { type: "supporting", textWrap: "nowrap", children: t(`@theorem.tool.access.${access}`) })] }));
|
|
40
|
+
}
|
|
41
|
+
export function ApprovalCard(props) {
|
|
42
|
+
return (_jsx(TheoremLabelsProvider, { children: _jsx(ApprovalBody, { ...props }) }));
|
|
43
|
+
}
|
|
44
|
+
function ApprovalBody({ gate, toolName, agent, input, decided = null, onDecision, }) {
|
|
45
|
+
const t = useLabels();
|
|
46
|
+
const args = formatInput(gate.preparedArguments ?? input);
|
|
47
|
+
return (_jsxs(GateCard, { heading: approvalHeading(t, gate, toolName, agent), tag: gate.access ? _jsx(ToolAccessTag, { access: gate.access }) : null, children: [gate.summary ? _jsx(Text, { color: "secondary", children: gate.summary }) : null, args ? (_jsx(Collapsible, { defaultIsOpen: false, trigger: _jsx(Text, { type: "supporting", children: t('@theorem.gate.approval.input') }), children: _jsx(CodeBlock, { code: args, language: "json", size: "sm" }) })) : null, _jsxs(HStack, { gap: 2, justify: "end", children: [_jsx(Button, { label: t('@theorem.gate.approval.deny'), variant: "ghost", isLoading: decided === 'deny', isDisabled: decided === 'allow', onClick: () => onDecision?.('deny') }), _jsx(Button, { label: t('@theorem.gate.approval.approve'), variant: "primary", isLoading: decided === 'allow', isDisabled: decided === 'deny', onClick: () => onDecision?.('allow') })] })] }));
|
|
48
|
+
}
|
|
49
|
+
function AuthChallengeDetails({ challenge }) {
|
|
50
|
+
const t = useLabels();
|
|
51
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { color: "secondary", children: challenge.message }), challenge.resource ? (_jsx(Text, { type: "supporting", 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] }));
|
|
52
|
+
}
|
|
53
|
+
export function AuthChallengeCard(props) {
|
|
54
|
+
return (_jsx(TheoremLabelsProvider, { children: _jsx(AuthChallengeBody, { ...props }) }));
|
|
55
|
+
}
|
|
56
|
+
/** Wait for the sign-in popup to report its slot signed in. */
|
|
57
|
+
function useOAuthPopup(slot, onComplete) {
|
|
58
|
+
const [popup, setPopup] = useState(null);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!popup)
|
|
61
|
+
return;
|
|
62
|
+
function onMessage(event) {
|
|
63
|
+
if (!popup || !isOAuthComplete(event, { popup, slot, origin: globalThis.location.origin }))
|
|
64
|
+
return;
|
|
65
|
+
setPopup(null);
|
|
66
|
+
onComplete();
|
|
67
|
+
}
|
|
68
|
+
globalThis.addEventListener('message', onMessage);
|
|
69
|
+
return () => globalThis.removeEventListener('message', onMessage);
|
|
70
|
+
}, [popup, slot, onComplete]);
|
|
71
|
+
return (url) => setPopup(globalThis.open(url, '_blank', 'width=600,height=700'));
|
|
72
|
+
}
|
|
73
|
+
function AuthChallengeBody({ gate, toolName, onAuthenticated, submitted = false, }) {
|
|
74
|
+
const t = useLabels();
|
|
75
|
+
const challenge = gate.authChallenge;
|
|
76
|
+
const { authType, slot } = challenge;
|
|
77
|
+
const [secret, setSecret] = useState('');
|
|
78
|
+
const onOAuthComplete = useCallback(() => onAuthenticated?.(), [onAuthenticated]);
|
|
79
|
+
const openSignIn = useOAuthPopup(slot, onOAuthComplete);
|
|
80
|
+
function submit() {
|
|
81
|
+
const value = secret.trim();
|
|
82
|
+
if (!value)
|
|
83
|
+
return;
|
|
84
|
+
// why: The key goes to the server once; the browser keeps no copy.
|
|
85
|
+
setSecret('');
|
|
86
|
+
onAuthenticated?.(value);
|
|
87
|
+
}
|
|
88
|
+
return (_jsxs(GateCard, { heading: t('@theorem.gate.auth.title', { tool: humanize(toolName).toLowerCase() }), tag: _jsx(Text, { type: "supporting", textWrap: "nowrap", children: t(`@theorem.gate.tag.${authType}`) }), children: [_jsx(AuthChallengeDetails, { challenge: challenge }), _jsx(AuthAction, { authType: authType, authUrl: challenge.authorizationUrl, slot: slot, secret: secret, submitted: submitted, onSecretChange: setSecret, onSubmit: submit, onOpenSignIn: openSignIn })] }));
|
|
89
|
+
}
|
|
90
|
+
function AuthAction(props) {
|
|
91
|
+
const t = useLabels();
|
|
92
|
+
if (props.submitted) {
|
|
93
|
+
return (_jsx(Badge, { variant: "success", label: t('@theorem.gate.auth.provided', { slot: props.slot }) }));
|
|
94
|
+
}
|
|
95
|
+
if (props.authType === 'oauth2') {
|
|
96
|
+
if (!props.authUrl) {
|
|
97
|
+
return _jsx(Text, { color: "secondary", children: t('@theorem.gate.auth.no_oauth') });
|
|
98
|
+
}
|
|
99
|
+
return (_jsx(HStack, { justify: "end", children: _jsx(Button, { label: t('@theorem.gate.auth.authorize'), variant: "primary", onClick: () => {
|
|
100
|
+
if (props.authUrl)
|
|
101
|
+
props.onOpenSignIn(props.authUrl);
|
|
102
|
+
} }) }));
|
|
103
|
+
}
|
|
104
|
+
const label = t(props.authType === 'api_key' ? '@theorem.gate.auth.api_key' : '@theorem.gate.auth.bearer');
|
|
105
|
+
const note = t('@theorem.gate.auth.secret_note').trim();
|
|
106
|
+
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 })] }));
|
|
107
|
+
}
|
|
@@ -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,62 @@
|
|
|
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 { useLocale } from '@astryxdesign/core/i18n';
|
|
6
|
+
import { Lightbox } from '@astryxdesign/core/Lightbox';
|
|
7
|
+
import { Skeleton } from '@astryxdesign/core/Skeleton';
|
|
8
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
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 { Arrive } from "./arrive.js";
|
|
13
|
+
import { useLabels } from "./labels-provider.js";
|
|
14
|
+
import { keyedByContent } from "./row-keys.js";
|
|
15
|
+
import { ShapedData } from "./ShapedData.js";
|
|
16
|
+
import { ChartCard } from "./trace-patterns.js";
|
|
17
|
+
import { VoiceNote } from "./VoiceNote.js";
|
|
18
|
+
const ToolResultCharts = lazy(() => import("./ToolResultCharts.js").then((module) => ({ default: module.ToolResultCharts })));
|
|
19
|
+
/** Narrowest an image card gets. */
|
|
20
|
+
const IMAGE_CARD_PX = 200;
|
|
21
|
+
/** Height a chart card holds while its module loads. */
|
|
22
|
+
const CHART_CARD_PX = 236;
|
|
23
|
+
function Images({ images }) {
|
|
24
|
+
const [open, setOpen] = useState(null);
|
|
25
|
+
return (_jsxs(_Fragment, { children: [_jsx(Grid, { columns: { minWidth: IMAGE_CARD_PX, repeat: 'fill' }, gap: 3, children: images.map((image, index) => (_jsx(Arrive, { children: _jsx(ClickableCard, { label: image.alt || 'Open image', padding: 0, onClick: () => setOpen(index), children: _jsx(AspectRatio, { ratio: 4 / 3, fit: "cover", children: _jsx("img", { src: image.src, alt: image.alt, loading: "lazy" }) }) }) }, image.src))) }), _jsx(Lightbox, { isOpen: open !== null, onOpenChange: (isOpen) => {
|
|
26
|
+
if (!isOpen)
|
|
27
|
+
setOpen(null);
|
|
28
|
+
}, media: images.map((image) => ({ src: image.src, alt: image.alt })), index: open ?? 0, onIndexChange: setOpen })] }));
|
|
29
|
+
}
|
|
30
|
+
function Figures({ figures }) {
|
|
31
|
+
const locale = useLocale();
|
|
32
|
+
const decimal = useMemo(() => new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }), [locale]);
|
|
33
|
+
if (figures.length === 0)
|
|
34
|
+
return null;
|
|
35
|
+
return (_jsx(Grid, { columns: { minWidth: 160, repeat: 'fill' }, gap: 3, children: figures.map((figure) => (_jsx(Arrive, { children: _jsx(ChartCard, { title: figure.label, value: withUnit(decimal.format(figure.value), figure.unit) }) }, figure.key))) }));
|
|
36
|
+
}
|
|
37
|
+
/** Charts load with their module; cards hold their place until then. */
|
|
38
|
+
function Charts({ charts }) {
|
|
39
|
+
if (charts.length === 0)
|
|
40
|
+
return null;
|
|
41
|
+
return (_jsx(Suspense, { fallback: _jsx(Grid, { columns: { minWidth: 260, repeat: 'fill' }, gap: 3, children: charts.map((chart) => (_jsx(Arrive, { children: _jsx(Skeleton, { height: CHART_CARD_PX }) }, chart.key))) }), children: _jsx(ToolResultCharts, { charts: charts }) }));
|
|
42
|
+
}
|
|
43
|
+
/** What answers the call, laid out from the output: its figures as stat cards, its series as charts, the images, and audio it returned. */
|
|
44
|
+
function ResultLead({ layout, parts = [], }) {
|
|
45
|
+
const t = useLabels();
|
|
46
|
+
const media = useMemo(() => returnedMedia(layout, parts, t('@theorem.host.image')), [layout, parts, t]);
|
|
47
|
+
if (!hasLead(layout, media))
|
|
48
|
+
return null;
|
|
49
|
+
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, keyedByContent(media.audio, (clip) => clip.src).map(({ item: clip, key }) => (_jsx(Arrive, { children: _jsx(VoiceNote, { src: clip.src, mimeType: clip.mimeType }) }, key)))] }));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A tool's output under one Response heading, led by what answers it (figures,
|
|
53
|
+
* charts, the images and audio it returned), then the data itself drawn by its shape, with its JSON one
|
|
54
|
+
* tap away. A payload too big to lay out, or a render that throws, shows as JSON.
|
|
55
|
+
*/
|
|
56
|
+
export function ToolResult({ output, parts }) {
|
|
57
|
+
const t = useLabels();
|
|
58
|
+
const layout = useMemo(() => layoutResult(output), [output]);
|
|
59
|
+
// why: A figure's card is its reading: the fields below don't repeat it.
|
|
60
|
+
const ledKeys = useMemo(() => layout.figures.map((figure) => figure.key), [layout]);
|
|
61
|
+
return (_jsx(ShapedData, { value: output, title: t('@theorem.host.response'), lead: _jsx(ResultLead, { layout: layout, parts: parts }), ledKeys: ledKeys }));
|
|
62
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { HStack } from '@astryxdesign/core/HStack';
|
|
3
|
+
import { useLocale } from '@astryxdesign/core/i18n';
|
|
4
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
5
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
import { Bar, BarChart, Cell, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
|
|
8
|
+
import { withUnit } from "../client/shaped-data.js";
|
|
9
|
+
import { timeFormats } from "../client/tool-result.js";
|
|
10
|
+
import { Arrive } from "./arrive.js";
|
|
11
|
+
import { CardWall } from "./CardWall.js";
|
|
12
|
+
import { ChartFrame, ChartTip, FIRST_WIDTH_PX } from "./TraceOverview.js";
|
|
13
|
+
import { ChartCard, TRACE_GAP, TRACE_RADIUS, TraceSwatch, traceFill, } from "./trace-patterns.js";
|
|
14
|
+
/**
|
|
15
|
+
* A tool result's charts, drawn as the trace draws its own: the same card,
|
|
16
|
+
* fills, ticks, and hover card. Recharts loads with this module, only when a
|
|
17
|
+
* result has a chart.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
/** Height of a time chart's plot, in pixels. */
|
|
22
|
+
const CHART_PX = 140;
|
|
23
|
+
/** Height of one row of a ranking, in pixels. */
|
|
24
|
+
const BAR_ROW_PX = 22;
|
|
25
|
+
const TICK = { fill: 'var(--color-text-secondary)', fontSize: 11 };
|
|
26
|
+
const BAR = {
|
|
27
|
+
radius: TRACE_RADIUS,
|
|
28
|
+
stroke: TRACE_GAP,
|
|
29
|
+
strokeWidth: 2,
|
|
30
|
+
isAnimationActive: false,
|
|
31
|
+
};
|
|
32
|
+
/** Each overlaid series, in the trace's order. */
|
|
33
|
+
const SERIES_FILL = ['clear', 'shade1', 'shade2'];
|
|
34
|
+
function useFormats(chart) {
|
|
35
|
+
const locale = useLocale();
|
|
36
|
+
return useMemo(() => {
|
|
37
|
+
const decimal = new Intl.NumberFormat(locale, { maximumFractionDigits: 2 });
|
|
38
|
+
const number = (value, unit) => withUnit(decimal.format(value), unit);
|
|
39
|
+
const dates = chart.kind === 'time' ? chart.rows.map((row) => String(row[chart.x])) : [];
|
|
40
|
+
const options = timeFormats(dates);
|
|
41
|
+
const tick = new Intl.DateTimeFormat(locale, options.tick);
|
|
42
|
+
const when = new Intl.DateTimeFormat(locale, options.when);
|
|
43
|
+
const read = (format) => (text) => {
|
|
44
|
+
const at = Date.parse(text);
|
|
45
|
+
return Number.isNaN(at) ? text : format.format(at);
|
|
46
|
+
};
|
|
47
|
+
return { number, tick: read(tick), when: read(when) };
|
|
48
|
+
}, [chart, locale]);
|
|
49
|
+
}
|
|
50
|
+
function TimeTip({ active, payload, label, chart, formats, }) {
|
|
51
|
+
if (!active || !payload?.length)
|
|
52
|
+
return null;
|
|
53
|
+
return (_jsx(ChartTip, { children: _jsxs(VStack, { gap: 0, children: [_jsx(Text, { weight: "medium", children: formats.when(String(label)) }), chart.series.map((series, index) => {
|
|
54
|
+
const value = payload.find((entry) => entry.dataKey === series.key)?.value;
|
|
55
|
+
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;
|
|
56
|
+
})] }) }));
|
|
57
|
+
}
|
|
58
|
+
/** One or more series over time: the first reading as the figure, the span of every reading as its note. */
|
|
59
|
+
function TimeCard({ chart }) {
|
|
60
|
+
const formats = useFormats(chart);
|
|
61
|
+
const [lead] = chart.series;
|
|
62
|
+
const values = chart.series.flatMap((series) => chart.rows
|
|
63
|
+
.map((row) => row[series.key])
|
|
64
|
+
.filter((value) => typeof value === 'number'));
|
|
65
|
+
const first = lead
|
|
66
|
+
? chart.rows
|
|
67
|
+
.map((row) => row[lead.key])
|
|
68
|
+
.find((value) => typeof value === 'number')
|
|
69
|
+
: undefined;
|
|
70
|
+
if (!lead || first === undefined)
|
|
71
|
+
return null;
|
|
72
|
+
const unit = lead.unit;
|
|
73
|
+
const range = `${formats.number(Math.min(...values))}–${formats.number(Math.max(...values), unit)}`;
|
|
74
|
+
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] }));
|
|
75
|
+
}
|
|
76
|
+
function BarTip({ active, payload, chart, formats, }) {
|
|
77
|
+
const row = payload?.[0]?.payload;
|
|
78
|
+
if (!active || !row)
|
|
79
|
+
return null;
|
|
80
|
+
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) })] }) }));
|
|
81
|
+
}
|
|
82
|
+
/** Rows ranked by one number, largest first, drawn like the trace's slowest steps. */
|
|
83
|
+
function CategoryCard({ chart }) {
|
|
84
|
+
const formats = useFormats(chart);
|
|
85
|
+
const rows = useMemo(() => chart.rows.map((row, index) => ({ ...row, id: String(index) })), [chart]);
|
|
86
|
+
const [top] = rows;
|
|
87
|
+
if (!top)
|
|
88
|
+
return null;
|
|
89
|
+
const height = rows.length * BAR_ROW_PX;
|
|
90
|
+
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) => {
|
|
91
|
+
const label = rows[Number(id)]?.label ?? '';
|
|
92
|
+
return label.length > 15 ? `${label.slice(0, 14)}…` : label;
|
|
93
|
+
} }), _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: {
|
|
94
|
+
position: 'right',
|
|
95
|
+
fill: 'var(--color-text-secondary)',
|
|
96
|
+
fontSize: 11,
|
|
97
|
+
formatter: (value) => formats.number(Number(value), chart.series.unit),
|
|
98
|
+
}, children: rows.map((row, index) => (_jsx(Cell, { fill: traceFill(index === 0 ? 'clear' : 'shade2') }, row.id))) })] }) }) }) }));
|
|
99
|
+
}
|
|
100
|
+
/** Each chart in its card, side by side where the column is wide enough. */
|
|
101
|
+
export function ToolResultCharts({ charts }) {
|
|
102
|
+
return (_jsx(CardWall, { minWidth: 260, children: charts.map((chart) => (_jsx(Arrive, { children: chart.kind === 'time' ? _jsx(TimeCard, { chart: chart }) : _jsx(CategoryCard, { chart: chart }) }, chart.key))) }));
|
|
103
|
+
}
|
|
@@ -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;
|