@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,474 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { AspectRatio } from '@astryxdesign/core/AspectRatio';
|
|
3
|
+
import { ChatMessage, ChatMessageBubble, ChatMessageList, ChatMessageMetadata, ChatToolCalls, } from '@astryxdesign/core/Chat';
|
|
4
|
+
import { Citation } from '@astryxdesign/core/Citation';
|
|
5
|
+
import { ClickableCard } from '@astryxdesign/core/ClickableCard';
|
|
6
|
+
import { Collapsible } from '@astryxdesign/core/Collapsible';
|
|
7
|
+
import { HStack } from '@astryxdesign/core/HStack';
|
|
8
|
+
import { Icon } from '@astryxdesign/core/Icon';
|
|
9
|
+
import { IconButton } from '@astryxdesign/core/IconButton';
|
|
10
|
+
import { useLightbox } from '@astryxdesign/core/Lightbox';
|
|
11
|
+
import { useStreamingText } from '@astryxdesign/core/hooks';
|
|
12
|
+
import { Markdown } from '@astryxdesign/core/Markdown';
|
|
13
|
+
import { Skeleton } from '@astryxdesign/core/Skeleton';
|
|
14
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
15
|
+
import { Thumbnail } from '@astryxdesign/core/Thumbnail';
|
|
16
|
+
import { Timestamp } from '@astryxdesign/core/Timestamp';
|
|
17
|
+
import { Token } from '@astryxdesign/core/Token';
|
|
18
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
19
|
+
import { IconCheck, IconCopy } from '@tabler/icons-react';
|
|
20
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
21
|
+
import { citationsFromBlock } from "../client/source-citations.js";
|
|
22
|
+
import { assistantTurnCopyText, assistantTurnTiming, composeAssistantTurn, groupTranscriptBlocks, pendingPromptOf, promptReplyKey, replyKey, toolCallLabel, workStatus, } from "../client/transcript-groups.js";
|
|
23
|
+
import { useDisclosureMotion } from "./disclosure-motion.js";
|
|
24
|
+
import { workDuration, workStatusLabel } from "./labels.js";
|
|
25
|
+
import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
|
|
26
|
+
import { ShapedData } from "./ShapedData.js";
|
|
27
|
+
import { transcriptBlockCopyText } from "./transcript-copy-text.js";
|
|
28
|
+
import { ApprovalCard, AuthChallengeCard } from "./ToolGateCard.js";
|
|
29
|
+
import { VoiceNote } from "./VoiceNote.js";
|
|
30
|
+
/** Largest a generated image renders in the transcript. */
|
|
31
|
+
const GENERATED_IMAGE_MAX_WIDTH = 512;
|
|
32
|
+
/** First-seen time per group key, so timestamps don't jump while streaming. */
|
|
33
|
+
function useFirstSeen(keys) {
|
|
34
|
+
const times = useRef(new Map());
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
if (!times.current.has(key))
|
|
38
|
+
times.current.set(key, now);
|
|
39
|
+
}
|
|
40
|
+
return (key) => times.current.get(key) ?? now;
|
|
41
|
+
}
|
|
42
|
+
/** `Date.now()`, refreshed every second while `isActive`. */
|
|
43
|
+
function useSecondTicker(isActive) {
|
|
44
|
+
const [now, setNow] = useState(() => Date.now());
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!isActive)
|
|
47
|
+
return;
|
|
48
|
+
setNow(Date.now());
|
|
49
|
+
const timer = globalThis.setInterval(() => setNow(Date.now()), 1_000);
|
|
50
|
+
return () => globalThis.clearInterval(timer);
|
|
51
|
+
}, [isActive]);
|
|
52
|
+
return now;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Each turn's span, keyed by the user group that started it. A turn stops at
|
|
56
|
+
* a gate and streams again under the same prompt once it's answered; the wait
|
|
57
|
+
* in between counts as paused, not worked.
|
|
58
|
+
*/
|
|
59
|
+
function useTurnSpans(streaming, lastUserKey) {
|
|
60
|
+
const spans = useRef(new Map());
|
|
61
|
+
// The first send mounts the transcript already streaming; that's the start.
|
|
62
|
+
const wasStreaming = useRef(false);
|
|
63
|
+
if (lastUserKey && streaming !== wasStreaming.current) {
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
const span = spans.current.get(lastUserKey);
|
|
66
|
+
if (streaming) {
|
|
67
|
+
const pausedMs = span?.endedAt === undefined ? 0 : span.pausedMs + now - span.endedAt;
|
|
68
|
+
spans.current.set(lastUserKey, { pausedMs });
|
|
69
|
+
}
|
|
70
|
+
else if (span) {
|
|
71
|
+
spans.current.set(lastUserKey, { ...span, endedAt: now });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
wasStreaming.current = streaming;
|
|
75
|
+
return spans.current;
|
|
76
|
+
}
|
|
77
|
+
function CopyButton({ text }) {
|
|
78
|
+
const t = useLabels();
|
|
79
|
+
const [copied, setCopied] = useState(false);
|
|
80
|
+
if (!text.trim())
|
|
81
|
+
return null;
|
|
82
|
+
const label = t(copied ? '@theorem.transcript.copied' : '@theorem.transcript.copy');
|
|
83
|
+
return (_jsx(IconButton, { label: label, tooltip: label, size: "sm", variant: "ghost", icon: _jsx(Icon, { icon: copied ? IconCheck : IconCopy, size: "xsm", color: "secondary" }), onClick: () => {
|
|
84
|
+
void navigator.clipboard.writeText(text).then(() => {
|
|
85
|
+
setCopied(true);
|
|
86
|
+
globalThis.setTimeout(() => setCopied(false), 1500);
|
|
87
|
+
});
|
|
88
|
+
} }));
|
|
89
|
+
}
|
|
90
|
+
const MINUTE_MS = 60_000;
|
|
91
|
+
/**
|
|
92
|
+
* Astryx's relative time, counted in minutes: "now" for the first minute, then
|
|
93
|
+
* "1m ago", "5m ago", … (its own formatter shows "15s ago" in between).
|
|
94
|
+
*/
|
|
95
|
+
function MessageTime({ at }) {
|
|
96
|
+
const t = useLabels();
|
|
97
|
+
const [isFresh, setIsFresh] = useState(() => Date.now() - at < MINUTE_MS);
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (!isFresh)
|
|
100
|
+
return;
|
|
101
|
+
const timer = globalThis.setTimeout(() => setIsFresh(false), Math.max(0, at + MINUTE_MS - Date.now()));
|
|
102
|
+
return () => globalThis.clearTimeout(timer);
|
|
103
|
+
}, [isFresh, at]);
|
|
104
|
+
if (isFresh) {
|
|
105
|
+
return (_jsx(Text, { type: "supporting", color: "secondary", children: t('@theorem.transcript.now') }));
|
|
106
|
+
}
|
|
107
|
+
return _jsx(Timestamp, { value: at, format: "relative_short", isLive: true });
|
|
108
|
+
}
|
|
109
|
+
/** A message's time, copy button and status; a failure says why on the line beneath. */
|
|
110
|
+
function MessageChrome(props) {
|
|
111
|
+
const failed = props.error !== undefined;
|
|
112
|
+
const metadata = (_jsx(ChatMessageMetadata, { timestamp: _jsx(MessageTime, { at: props.at }), footer: _jsx(CopyButton, { text: props.copyText }), status: failed ? 'error' : props.status }));
|
|
113
|
+
if (!failed)
|
|
114
|
+
return metadata;
|
|
115
|
+
return (_jsxs(VStack, { gap: 1, children: [metadata, _jsx(Text, { type: "supporting", color: "secondary", children: props.error })] }));
|
|
116
|
+
}
|
|
117
|
+
function dataUrl(mimeType, data) {
|
|
118
|
+
return data === undefined ? undefined : `data:${mimeType};base64,${data}`;
|
|
119
|
+
}
|
|
120
|
+
function ImageAttachment({ src, name }) {
|
|
121
|
+
const lightbox = useLightbox({ media: { src, alt: name, caption: name }, hasZoom: true });
|
|
122
|
+
return (_jsxs(_Fragment, { children: [_jsx(Thumbnail, { src: src, alt: name, label: name, onClick: () => lightbox.open() }), lightbox.element] }));
|
|
123
|
+
}
|
|
124
|
+
function UserBlock({ block, metadata }) {
|
|
125
|
+
if (block.kind === 'user-text')
|
|
126
|
+
return _jsx(ChatMessageBubble, { metadata: metadata, children: block.text });
|
|
127
|
+
if (block.kind !== 'user-attachment' && block.kind !== 'user-voice')
|
|
128
|
+
return null;
|
|
129
|
+
return _jsx(UserFile, { block: block });
|
|
130
|
+
}
|
|
131
|
+
/** An attached image or voice note inline; any other file as a Token. */
|
|
132
|
+
function UserFile({ block }) {
|
|
133
|
+
const src = dataUrl(block.mimeType, block.data);
|
|
134
|
+
if (src && block.mimeType.startsWith('image/')) {
|
|
135
|
+
return _jsx(ImageAttachment, { src: src, name: block.name });
|
|
136
|
+
}
|
|
137
|
+
if (src && block.mimeType.startsWith('audio/')) {
|
|
138
|
+
return _jsx(VoiceNote, { src: src, mimeType: block.mimeType });
|
|
139
|
+
}
|
|
140
|
+
return _jsx(Token, { label: block.name, description: block.mimeType });
|
|
141
|
+
}
|
|
142
|
+
function mediaSrc(block) {
|
|
143
|
+
return block.url ?? dataUrl(block.mimeType, block.data);
|
|
144
|
+
}
|
|
145
|
+
function lightboxMedia(t, src, mimeType) {
|
|
146
|
+
if (mimeType.startsWith('image/'))
|
|
147
|
+
return { src, alt: t('@theorem.transcript.generated_image'), type: 'image' };
|
|
148
|
+
if (mimeType.startsWith('video/'))
|
|
149
|
+
return { src, alt: t('@theorem.transcript.generated_video'), type: 'video' };
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
/** Image or video media: shown as Thumbnails in a gallery row, not as its own body row. */
|
|
153
|
+
function galleryItemOf(t, block) {
|
|
154
|
+
if (block.kind !== 'media')
|
|
155
|
+
return undefined;
|
|
156
|
+
const src = mediaSrc(block);
|
|
157
|
+
const media = src ? lightboxMedia(t, src, block.mimeType) : undefined;
|
|
158
|
+
return media ? { media, preview: block.previewUrl ?? media.src } : undefined;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Consecutive images and videos as one wrapping row of Astryx Thumbnails that
|
|
162
|
+
* share a Lightbox gallery (Astryx's `useLightbox({ media: [...] })` pattern).
|
|
163
|
+
*/
|
|
164
|
+
function MediaGallery({ items }) {
|
|
165
|
+
const lightbox = useLightbox({ media: items.map((item) => item.media), hasZoom: true });
|
|
166
|
+
return (_jsxs(HStack, { gap: 2, wrap: "wrap", children: [items.map(({ media: item, preview }, i) => (_jsx(Thumbnail, { src: item.type === 'image' ? preview : undefined, alt: item.alt, label: item.alt, onClick: () => lightbox.open(i) }, item.src))), lightbox.element] }));
|
|
167
|
+
}
|
|
168
|
+
/** One generated image, framed to its natural ratio once loaded (the pinned ratio until then). */
|
|
169
|
+
function GeneratedImage(props) {
|
|
170
|
+
const t = useLabels();
|
|
171
|
+
const [natural, setNatural] = useState();
|
|
172
|
+
return (_jsx(ClickableCard, { label: t('@theorem.transcript.open_generated_image'), onClick: props.onOpen, padding: 0, width: "100%", maxWidth: GENERATED_IMAGE_MAX_WIDTH, children: _jsx(AspectRatio, { ratio: natural ?? props.ratio ?? 1, fit: "cover", children: _jsx("img", { src: props.item.preview, alt: props.item.media.alt, onLoad: (event) => {
|
|
173
|
+
const { naturalWidth, naturalHeight } = event.currentTarget;
|
|
174
|
+
if (naturalWidth > 0 && naturalHeight > 0)
|
|
175
|
+
setNatural(naturalWidth / naturalHeight);
|
|
176
|
+
} }) }) }));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Image-profile gallery: images are the answer, so they show large at their
|
|
180
|
+
* own ratio. Anything else (video) keeps the square Thumbnail.
|
|
181
|
+
*/
|
|
182
|
+
function GeneratedGallery({ items, ratio }) {
|
|
183
|
+
const lightbox = useLightbox({ media: items.map((item) => item.media), hasZoom: true });
|
|
184
|
+
return (_jsxs(VStack, { gap: 2, width: "100%", children: [items.map((item, i) => item.media.type === 'image' ? (_jsx(GeneratedImage, { item: item, ratio: ratio, onOpen: () => lightbox.open(i) }, item.media.src)) : (_jsx(Thumbnail, { alt: item.media.alt, label: item.media.alt, onClick: () => lightbox.open(i) }, item.media.src))), lightbox.element] }));
|
|
185
|
+
}
|
|
186
|
+
/** An image takes seconds: a slow, smooth breath rather than the Skeleton's quick stepped blink. */
|
|
187
|
+
const GENERATING_PULSE = { animationDuration: '1.8s', animationTimingFunction: 'ease-in-out' };
|
|
188
|
+
/** Placeholder shaped like the image being generated. */
|
|
189
|
+
function GeneratingImage({ ratio }) {
|
|
190
|
+
const t = useLabels();
|
|
191
|
+
return (_jsx(VStack, { width: "100%", maxWidth: GENERATED_IMAGE_MAX_WIDTH, children: _jsx(AspectRatio, { ratio: ratio ?? 1, children: _jsx(Skeleton, { width: "100%", height: "100%", radius: 3, style: GENERATING_PULSE, "aria-label": t('@theorem.transcript.generating_image') }) }) }));
|
|
192
|
+
}
|
|
193
|
+
function MediaBlock({ block }) {
|
|
194
|
+
const src = mediaSrc(block);
|
|
195
|
+
if (src && block.mimeType.startsWith('audio/')) {
|
|
196
|
+
return _jsx(VoiceNote, { src: src, mimeType: block.mimeType });
|
|
197
|
+
}
|
|
198
|
+
return _jsx(Text, { color: "secondary", children: block.mimeType });
|
|
199
|
+
}
|
|
200
|
+
/** Runs of images/videos become one gallery row; everything else is a row of its own. */
|
|
201
|
+
function bodyRows(t, body) {
|
|
202
|
+
const rows = [];
|
|
203
|
+
for (const block of body) {
|
|
204
|
+
const item = galleryItemOf(t, block);
|
|
205
|
+
const last = rows.at(-1);
|
|
206
|
+
if (item && last?.kind === 'gallery')
|
|
207
|
+
last.items.push(item);
|
|
208
|
+
else if (item)
|
|
209
|
+
rows.push({ kind: 'gallery', items: [item] });
|
|
210
|
+
else
|
|
211
|
+
rows.push({ kind: 'block', block });
|
|
212
|
+
}
|
|
213
|
+
return rows;
|
|
214
|
+
}
|
|
215
|
+
function Sources({ block }) {
|
|
216
|
+
const t = useLabels();
|
|
217
|
+
const citations = citationsFromBlock(block);
|
|
218
|
+
if (citations.length === 0)
|
|
219
|
+
return null;
|
|
220
|
+
return (_jsx(HStack, { gap: 1, wrap: "wrap", "aria-label": t('@theorem.transcript.sources'), children: citations.map((citation, i) => (_jsx(Citation, { source: { title: citation.title, url: citation.href, src: citation.icon }, number: i + 1 }, citation.key))) }));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Streamed text (the reply, or the latest thought). Guardrails release text in batches (the last one when
|
|
224
|
+
* the stream closes), and Astryx's reveal snaps to the full text once
|
|
225
|
+
* `isStreaming` goes false — so keep the reveal on until Astryx's own
|
|
226
|
+
* `useStreamingText` has caught up with everything received.
|
|
227
|
+
*/
|
|
228
|
+
function StreamedMarkdown({ text, streaming, density, components, }) {
|
|
229
|
+
const [revealing, setRevealing] = useState(streaming);
|
|
230
|
+
if (streaming && !revealing)
|
|
231
|
+
setRevealing(true);
|
|
232
|
+
const shown = useStreamingText(text, revealing);
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (!streaming && shown.length >= text.length)
|
|
235
|
+
setRevealing(false);
|
|
236
|
+
}, [streaming, shown, text]);
|
|
237
|
+
return (_jsx(Markdown, { isStreaming: revealing, density: density, components: components, children: text }));
|
|
238
|
+
}
|
|
239
|
+
function BodyBlock({ block, streaming }) {
|
|
240
|
+
if (block.kind === 'text')
|
|
241
|
+
return _jsx(StreamedMarkdown, { text: block.text, streaming: streaming });
|
|
242
|
+
if (block.kind === 'tool')
|
|
243
|
+
return _jsx(ToolCall, { tool: block.tool });
|
|
244
|
+
return _jsx(ResultBlock, { block: block });
|
|
245
|
+
}
|
|
246
|
+
/** Non-streaming answer rows: errors, sources, structured output and media. */
|
|
247
|
+
function ResultBlock({ block }) {
|
|
248
|
+
switch (block.kind) {
|
|
249
|
+
// A failure shows on the message's metadata line (MessageChrome).
|
|
250
|
+
case 'error':
|
|
251
|
+
return null;
|
|
252
|
+
case 'citation':
|
|
253
|
+
return _jsx(Sources, { block: block });
|
|
254
|
+
case 'structured':
|
|
255
|
+
return _jsx(ShapedData, { value: block.value });
|
|
256
|
+
case 'media':
|
|
257
|
+
return _jsx(MediaBlock, { block: block });
|
|
258
|
+
default:
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Astryx sets a call's name in the code font; labels are sentences, so the row takes the body
|
|
263
|
+
// font and the detail restores the code font kept on the wrapper.
|
|
264
|
+
const CODE_FONT_KEPT = {
|
|
265
|
+
display: 'contents',
|
|
266
|
+
'--theorem-font-code': 'var(--font-family-code)',
|
|
267
|
+
};
|
|
268
|
+
const CODE_FONT_AS_BODY = { '--font-family-code': 'var(--font-family-body)' };
|
|
269
|
+
const CODE_FONT_RESTORED = { '--font-family-code': 'var(--theorem-font-code)' };
|
|
270
|
+
function ToolCalls({ calls }) {
|
|
271
|
+
return (_jsx("div", { style: CODE_FONT_KEPT, children: _jsx(ChatToolCalls, { calls: calls, style: CODE_FONT_AS_BODY }) }));
|
|
272
|
+
}
|
|
273
|
+
/** A call's detail: what it ran with, then what came back. */
|
|
274
|
+
function toolDetail(t, tool, result) {
|
|
275
|
+
const input = tool.edited ? (_jsx(ShapedData, { value: tool.edited.to, title: t('@theorem.transcript.tool_input_edited') })) : (_jsx(ShapedData, { value: tool.arguments, title: t('@theorem.transcript.tool_input') }));
|
|
276
|
+
return (_jsxs(VStack, { gap: 2, style: CODE_FONT_RESTORED, children: [_jsx(Text, { type: "code", size: "sm", color: "secondary", children: tool.name }), input, result] }));
|
|
277
|
+
}
|
|
278
|
+
/** How long a finished call ran, when both ends were seen. */
|
|
279
|
+
function toolDuration(t, tool) {
|
|
280
|
+
if (tool.startedAt === undefined || tool.endedAt === undefined)
|
|
281
|
+
return {};
|
|
282
|
+
return { duration: workDuration(t, tool.endedAt - tool.startedAt) };
|
|
283
|
+
}
|
|
284
|
+
function toolCallItem(t, id, tool) {
|
|
285
|
+
const base = { key: id, name: toolCallLabel(tool) };
|
|
286
|
+
const { state } = tool;
|
|
287
|
+
switch (state?.phase) {
|
|
288
|
+
case 'error':
|
|
289
|
+
// What failed, as the tool reported it; the whole failure is in the detail.
|
|
290
|
+
return {
|
|
291
|
+
...base,
|
|
292
|
+
status: 'error',
|
|
293
|
+
target: state.failure.message,
|
|
294
|
+
errorMessage: state.failure.message,
|
|
295
|
+
resultDetail: toolDetail(t, tool, _jsx(ShapedData, { value: state.failure, title: t('@theorem.transcript.tool_error') })),
|
|
296
|
+
};
|
|
297
|
+
case 'complete':
|
|
298
|
+
return {
|
|
299
|
+
...base,
|
|
300
|
+
status: 'complete',
|
|
301
|
+
...toolDuration(t, tool),
|
|
302
|
+
resultDetail: toolDetail(t, tool, state.output === undefined ? undefined : (_jsx(ShapedData, { value: state.output, title: t('@theorem.transcript.tool_output') }))),
|
|
303
|
+
};
|
|
304
|
+
case 'running':
|
|
305
|
+
return { ...base, status: 'running', resultDetail: toolDetail(t, tool) };
|
|
306
|
+
default:
|
|
307
|
+
return { ...base, status: 'pending', resultDetail: toolDetail(t, tool) };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function ToolCall({ tool }) {
|
|
311
|
+
const t = useLabels();
|
|
312
|
+
return _jsx(ToolCalls, { calls: [toolCallItem(t, tool.name, tool)] });
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Thinking reads as quieter text than the reply. Astryx has no reasoning
|
|
316
|
+
* component, and Markdown draws its own Text, so restyle through its
|
|
317
|
+
* documented `components` seam.
|
|
318
|
+
*/
|
|
319
|
+
const THOUGHT_MARKDOWN = {
|
|
320
|
+
paragraph: ({ children }) => (_jsx(Text, { size: "sm", color: "secondary", display: "block", as: "p", children: children })),
|
|
321
|
+
heading: ({ children }) => (_jsx(Text, { size: "sm", color: "secondary", weight: "semibold", display: "block", as: "p", children: children })),
|
|
322
|
+
};
|
|
323
|
+
function TraceList({ items, streaming }) {
|
|
324
|
+
const t = useLabels();
|
|
325
|
+
const rows = [];
|
|
326
|
+
let tools = [];
|
|
327
|
+
const flush = () => {
|
|
328
|
+
if (tools.length === 0)
|
|
329
|
+
return;
|
|
330
|
+
rows.push(_jsx(ToolCalls, { calls: tools }, tools[0]?.key));
|
|
331
|
+
tools = [];
|
|
332
|
+
};
|
|
333
|
+
for (const [i, item] of items.entries()) {
|
|
334
|
+
if (item.kind === 'tool') {
|
|
335
|
+
tools.push(toolCallItem(t, item.id, item.block.tool));
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
flush();
|
|
339
|
+
rows.push(_jsx(StreamedMarkdown, { text: item.text, streaming: streaming && i === items.length - 1, density: "compact", components: THOUGHT_MARKDOWN }, item.id));
|
|
340
|
+
}
|
|
341
|
+
flush();
|
|
342
|
+
return _jsx(VStack, { gap: 2, children: rows });
|
|
343
|
+
}
|
|
344
|
+
function GateCard({ block, handlers }) {
|
|
345
|
+
const { tool } = block;
|
|
346
|
+
if (tool.state?.phase !== 'gate')
|
|
347
|
+
return null;
|
|
348
|
+
const { gate } = tool.state;
|
|
349
|
+
const index = handlers.indexOf(block);
|
|
350
|
+
const answer = handlers.answering?.callId === tool.callId ? handlers.answering.action : null;
|
|
351
|
+
if (gate.kind === 'auth') {
|
|
352
|
+
return (_jsx(AuthChallengeCard, { gate: gate, toolName: tool.name, submitted: answer === 'auth', onAuthenticated: (secret) => handlers.onAuthenticated?.(index, secret) }));
|
|
353
|
+
}
|
|
354
|
+
return (_jsx(ApprovalCard, { gate: gate, toolName: tool.name, input: tool.arguments, decided: answer === 'auth' ? null : answer, onDecision: (action) => handlers.onToolDecision?.(index, action) }));
|
|
355
|
+
}
|
|
356
|
+
/** Live elapsed time while the turn streams; its final duration once it ends. */
|
|
357
|
+
/** Live: counted from the start. Stopped: the work its blocks record. */
|
|
358
|
+
function useTurnElapsed(streaming, startedAt, workedMs) {
|
|
359
|
+
const now = useSecondTicker(streaming && startedAt !== undefined);
|
|
360
|
+
if (!streaming)
|
|
361
|
+
return workedMs;
|
|
362
|
+
return startedAt !== undefined ? now - startedAt : undefined;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* The turn's work status; with a trace, a Collapsible over its thinking and
|
|
366
|
+
* tool calls, open while the turn runs so they can be followed, folded away
|
|
367
|
+
* when it ends.
|
|
368
|
+
*/
|
|
369
|
+
function TurnStatus(props) {
|
|
370
|
+
const { status, streaming } = props;
|
|
371
|
+
const [traceOpen, setTraceOpen] = useState(streaming);
|
|
372
|
+
const [wasStreaming, setWasStreaming] = useState(streaming);
|
|
373
|
+
if (wasStreaming !== streaming) {
|
|
374
|
+
setWasStreaming(streaming);
|
|
375
|
+
setTraceOpen(streaming);
|
|
376
|
+
}
|
|
377
|
+
if (!status)
|
|
378
|
+
return null;
|
|
379
|
+
const label = (_jsx(Text, { size: "sm", color: "secondary", children: status }));
|
|
380
|
+
if (!props.hasTrace)
|
|
381
|
+
return label;
|
|
382
|
+
return (_jsx(Collapsible, { trigger: label, isOpen: traceOpen, onOpenChange: setTraceOpen, children: _jsx(TraceList, { items: props.trace, streaming: streaming }) }));
|
|
383
|
+
}
|
|
384
|
+
function BodyRowView({ row, streaming, imageOutput }) {
|
|
385
|
+
if (row.kind === 'block')
|
|
386
|
+
return _jsx(BodyBlock, { block: row.block, streaming: streaming });
|
|
387
|
+
if (imageOutput)
|
|
388
|
+
return _jsx(GeneratedGallery, { items: row.items, ratio: imageOutput.ratio });
|
|
389
|
+
return _jsx(MediaGallery, { items: row.items });
|
|
390
|
+
}
|
|
391
|
+
/** An image profile's placeholder until the turn's first image arrives. */
|
|
392
|
+
function PendingImage(props) {
|
|
393
|
+
if (!props.imageOutput || !props.streaming || props.rows.some((row) => row.kind === 'gallery'))
|
|
394
|
+
return null;
|
|
395
|
+
return _jsx(GeneratingImage, { ratio: props.imageOutput.ratio });
|
|
396
|
+
}
|
|
397
|
+
function AssistantTurn(props) {
|
|
398
|
+
const t = useLabels();
|
|
399
|
+
const elapsedMs = useTurnElapsed(props.streaming, props.startedAt, props.workedMs);
|
|
400
|
+
const { trace, gatedTools, body, hasTrace } = composeAssistantTurn(props.blocks);
|
|
401
|
+
const rows = bodyRows(t, body);
|
|
402
|
+
const status = workStatusLabel(t, workStatus({ streaming: props.streaming, hasTrace, elapsedMs }));
|
|
403
|
+
const copyText = assistantTurnCopyText(body.length > 0 ? body : props.blocks);
|
|
404
|
+
return (_jsx(ChatMessage, { sender: "assistant", name: props.handle, metadata: props.streaming ? undefined : _jsx(MessageChrome, { at: props.at, copyText: copyText, error: props.error }), children: _jsxs(VStack, { gap: 3, width: "100%", children: [_jsx(TurnStatus, { status: status, trace: trace, hasTrace: hasTrace, streaming: props.streaming }), gatedTools.map((block) => (_jsx(GateCard, { block: block, handlers: props.handlers }, block.id))), rows.map((row, i) => (_jsx(BodyRowView
|
|
405
|
+
// By position: the committed turn re-mints block ids, and a
|
|
406
|
+
// remount would cut the text reveal short.
|
|
407
|
+
, { row: row, streaming: props.streaming && i === rows.length - 1, imageOutput: props.imageOutput }, i))), _jsx(PendingImage, { imageOutput: props.imageOutput, streaming: props.streaming, rows: rows })] }) }));
|
|
408
|
+
}
|
|
409
|
+
function UserTurn(props) {
|
|
410
|
+
const t = useLabels();
|
|
411
|
+
const copyText = props.blocks
|
|
412
|
+
.map((block) => transcriptBlockCopyText(t, block))
|
|
413
|
+
.filter(Boolean)
|
|
414
|
+
.join('\n\n');
|
|
415
|
+
const chrome = _jsx(MessageChrome, { at: props.at, copyText: copyText, status: props.status, error: props.error });
|
|
416
|
+
// Astryx: metadata goes on the last bubble, or on the message when the last
|
|
417
|
+
// content is unbubbled (an attachment or voice note).
|
|
418
|
+
const last = props.blocks.at(-1);
|
|
419
|
+
const lastIsBubble = last?.kind === 'user-text';
|
|
420
|
+
return (_jsx(ChatMessage, { sender: "user", metadata: lastIsBubble ? undefined : chrome, children: props.blocks.map((block) => (_jsx(UserBlock, { block: block, metadata: block === last && lastIsBubble ? chrome : undefined }, block.id))) }));
|
|
421
|
+
}
|
|
422
|
+
/** Why a turn failed: its error block's message. */
|
|
423
|
+
function failureOf(group) {
|
|
424
|
+
return group?.blocks.findLast((block) => block.kind === 'error')?.message;
|
|
425
|
+
}
|
|
426
|
+
/** A reply that is nothing but its failure: the turn failed before the model answered. */
|
|
427
|
+
function isBareFailure(group) {
|
|
428
|
+
return group?.kind === 'assistant' && group.blocks.every((block) => block.kind === 'error');
|
|
429
|
+
}
|
|
430
|
+
/** Theorem transcript blocks rendered as Astryx chat messages. */
|
|
431
|
+
export function ChatTranscript(props) {
|
|
432
|
+
return (_jsx(TheoremLabelsProvider, { children: _jsx(ChatTranscriptBody, { ...props }) }));
|
|
433
|
+
}
|
|
434
|
+
function ChatTranscriptBody({ blocks, handle, streaming = false, delivery, onToolDecision, answering = null, onAuthenticated, emptyState, imageOutput, }) {
|
|
435
|
+
const groups = useMemo(() => groupTranscriptBlocks(blocks), [blocks]);
|
|
436
|
+
const timeOf = useFirstSeen(groups.map((group, index) => (group.kind === 'user' ? group.key : replyKey(groups, index))));
|
|
437
|
+
const spans = useTurnSpans(streaming, groups.findLast((group) => group.kind === 'user')?.key);
|
|
438
|
+
const pendingPrompt = streaming ? pendingPromptOf(groups) : undefined;
|
|
439
|
+
const listRef = useRef(null);
|
|
440
|
+
// Disclosures ease open and shut in place; opening one never moves the transcript.
|
|
441
|
+
useDisclosureMotion(listRef);
|
|
442
|
+
const handlers = {
|
|
443
|
+
indexOf: (block) => blocks.findIndex((entry) => entry.id === block.id),
|
|
444
|
+
answering,
|
|
445
|
+
onToolDecision,
|
|
446
|
+
onAuthenticated,
|
|
447
|
+
};
|
|
448
|
+
const turn = { handle, handlers, imageOutput };
|
|
449
|
+
const lastUser = groups.findLastIndex((group) => group.kind === 'user');
|
|
450
|
+
const userTurn = (group, index) => {
|
|
451
|
+
// A turn that failed before any reply shows it on the message itself.
|
|
452
|
+
const next = groups[index + 1];
|
|
453
|
+
const error = isBareFailure(next) ? failureOf(next) : undefined;
|
|
454
|
+
const status = index === lastUser ? (delivery ?? undefined) : undefined;
|
|
455
|
+
return _jsx(UserTurn, { blocks: group.blocks, at: timeOf(group.key), status: status, error: error }, group.key);
|
|
456
|
+
};
|
|
457
|
+
const turns = groups.flatMap((group, index) => {
|
|
458
|
+
if (group.kind === 'user')
|
|
459
|
+
return userTurn(group, index);
|
|
460
|
+
if (isBareFailure(group) && groups[index - 1]?.kind === 'user')
|
|
461
|
+
return [];
|
|
462
|
+
const { key, live, endedAt, ...timing } = assistantTurnTiming({ groups, index, streaming, timeOf, spans });
|
|
463
|
+
// A reply is dated when it last stopped: a reply that just finished reads "now".
|
|
464
|
+
const at = endedAt ?? timeOf(key);
|
|
465
|
+
return (_jsx(AssistantTurn, { ...turn, ...timing, blocks: group.blocks, streaming: live, at: at, error: failureOf(group) }, key));
|
|
466
|
+
});
|
|
467
|
+
// Nothing streamed back yet: show the reply's "Working…" status right away.
|
|
468
|
+
// It sits in the same keyed list as the streamed reply, so the reply stays
|
|
469
|
+
// one message; a remount would read to the layout as a new message.
|
|
470
|
+
if (pendingPrompt) {
|
|
471
|
+
turns.push(_jsx(AssistantTurn, { ...turn, blocks: [], streaming: true, at: timeOf(pendingPrompt.key), startedAt: timeOf(pendingPrompt.key) }, promptReplyKey(pendingPrompt)));
|
|
472
|
+
}
|
|
473
|
+
return (_jsx(ChatMessageList, { ref: listRef, isStreaming: streaming, emptyState: emptyState, children: turns }));
|
|
474
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from 'react';
|
|
2
|
+
import { type TraceInspector } from './TraceInspectorPanel.tsx';
|
|
3
|
+
export type ConsoleFrameProps = {
|
|
4
|
+
inspector: TraceInspector;
|
|
5
|
+
maxWidth: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
style?: CSSProperties;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* A console's page, for decisions and hosts alike: the trace's toggle in the
|
|
12
|
+
* header, and one raised panel, flush with the host's, scrolling inside.
|
|
13
|
+
*/
|
|
14
|
+
export declare function ConsoleFrame({ inspector, maxWidth, className, style, children }: ConsoleFrameProps): import("react").JSX.Element;
|
|
15
|
+
export type ConsoleView = 'fields' | 'json';
|
|
16
|
+
export type RequestCardProps = {
|
|
17
|
+
title: string;
|
|
18
|
+
/** The view shown: fields only while the request parses. */
|
|
19
|
+
view: ConsoleView;
|
|
20
|
+
hasFields: boolean;
|
|
21
|
+
onView: (next: ConsoleView) => void;
|
|
22
|
+
/** Runs on ⌘/Ctrl+Enter anywhere in the card. */
|
|
23
|
+
onRun: () => void;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
};
|
|
26
|
+
/** The request's card: its heading with the Fields/JSON switch, then the request and its run bar. */
|
|
27
|
+
export declare function RequestCard({ title, view, hasFields, onView, onRun, children }: RequestCardProps): import("react").JSX.Element;
|
|
28
|
+
/** The request as JSON text, marked while it doesn't parse. */
|
|
29
|
+
export declare function JsonRequest({ label, text, isInvalid, onChange }: {
|
|
30
|
+
label: string;
|
|
31
|
+
text: string;
|
|
32
|
+
isInvalid: boolean;
|
|
33
|
+
onChange: (next: string) => void;
|
|
34
|
+
}): import("react").JSX.Element;
|
|
35
|
+
export type RunBarProps = {
|
|
36
|
+
/** The shortcut, or what the request still lacks. */
|
|
37
|
+
note: string | null;
|
|
38
|
+
isReady: boolean;
|
|
39
|
+
isRunning: boolean;
|
|
40
|
+
runLabel: string;
|
|
41
|
+
runningLabel: string;
|
|
42
|
+
stopLabel: string;
|
|
43
|
+
onRun: () => void;
|
|
44
|
+
onStop: () => void;
|
|
45
|
+
};
|
|
46
|
+
/** A note on the request, Stop while it runs, and Run. */
|
|
47
|
+
export declare function RunBar({ note, isReady, isRunning, runLabel, runningLabel, stopLabel, onRun, onStop }: RunBarProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '@astryxdesign/core/Button';
|
|
3
|
+
import { Card } from '@astryxdesign/core/Card';
|
|
4
|
+
import { Center } from '@astryxdesign/core/Center';
|
|
5
|
+
import { HStack } from '@astryxdesign/core/HStack';
|
|
6
|
+
import { Layout, LayoutContent } from '@astryxdesign/core/Layout';
|
|
7
|
+
import { SegmentedControl, SegmentedControlItem } from '@astryxdesign/core/SegmentedControl';
|
|
8
|
+
import { Text } from '@astryxdesign/core/Text';
|
|
9
|
+
import { TextArea } from '@astryxdesign/core/TextArea';
|
|
10
|
+
import { VStack } from '@astryxdesign/core/VStack';
|
|
11
|
+
import { useLabels } from "./labels-provider.js";
|
|
12
|
+
import { RAISED, SidePanelHeader } from "./SidePanel.js";
|
|
13
|
+
import { WithTrace } from "./TraceInspectorPanel.js";
|
|
14
|
+
/**
|
|
15
|
+
* A console's page, for decisions and hosts alike: the trace's toggle in the
|
|
16
|
+
* header, and one raised panel, flush with the host's, scrolling inside.
|
|
17
|
+
*/
|
|
18
|
+
export function ConsoleFrame({ inspector, maxWidth, className, style, children }) {
|
|
19
|
+
return (_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { height: "fill", className: className, style: style, header: inspector.toggle ? _jsx(SidePanelHeader, { children: inspector.toggle }) : undefined, content: _jsx(LayoutContent, { padding: 0, isScrollable: false, children: _jsx(VStack, { height: "100%", paddingInline: 3, children: _jsx(Card, { variant: "transparent", height: "100%", padding: 0, style: { ...RAISED, overflowY: 'auto' }, children: _jsx(Center, { axis: "horizontal", width: "100%", children: _jsx(VStack, { width: "100%", maxWidth: maxWidth, gap: 3, padding: 4, children: children }) }) }) }) }) }) }));
|
|
20
|
+
}
|
|
21
|
+
/** ⌘/Ctrl+Enter runs the console's request. */
|
|
22
|
+
function runShortcut(run) {
|
|
23
|
+
return (event) => {
|
|
24
|
+
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
run();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** The request's card: its heading with the Fields/JSON switch, then the request and its run bar. */
|
|
31
|
+
export function RequestCard({ title, view, hasFields, onView, onRun, children }) {
|
|
32
|
+
const t = useLabels();
|
|
33
|
+
return (_jsx(Card, { variant: "muted", padding: 4, style: { background: 'var(--color-background-surface)' }, children: _jsxs(VStack, { gap: 3, onKeyDown: runShortcut(onRun), children: [_jsxs(HStack, { gap: 2, hAlign: "between", vAlign: "center", children: [_jsx(Text, { type: "supporting", color: "secondary", as: "h3", children: title }), _jsxs(SegmentedControl, { label: t('@theorem.decision.view'), size: "sm", value: view, onChange: (next) => {
|
|
34
|
+
onView(next === 'json' ? 'json' : 'fields');
|
|
35
|
+
}, children: [_jsx(SegmentedControlItem, { value: "fields", label: t('@theorem.decision.fields'), isDisabled: !hasFields }), _jsx(SegmentedControlItem, { value: "json", label: t('@theorem.data.json') })] })] }), children] }) }));
|
|
36
|
+
}
|
|
37
|
+
/** The field reads as code: the body font is swapped for the code font inside it. */
|
|
38
|
+
const CODE_FONT = { '--font-family-body': 'var(--font-family-code)' };
|
|
39
|
+
/** The request as JSON text, marked while it doesn't parse. */
|
|
40
|
+
export function JsonRequest({ label, text, isInvalid, onChange }) {
|
|
41
|
+
return (_jsx(TextArea, { label: label, isLabelHidden: true, value: text, onChange: onChange, rows: 8, hasSpellCheck: false, status: isInvalid ? { type: 'error' } : undefined, style: CODE_FONT }));
|
|
42
|
+
}
|
|
43
|
+
/** A note on the request, Stop while it runs, and Run. */
|
|
44
|
+
export function RunBar({ note, isReady, isRunning, runLabel, runningLabel, stopLabel, onRun, onStop }) {
|
|
45
|
+
return (_jsxs(HStack, { gap: 3, hAlign: "between", vAlign: "center", children: [_jsx(Text, { type: "supporting", color: "secondary", style: isReady ? undefined : { color: 'var(--color-error)' }, hasTabularNumbers: true, children: note }), _jsxs(HStack, { gap: 2, vAlign: "center", children: [isRunning ? _jsx(Button, { label: stopLabel, variant: "ghost", onClick: onStop }) : null, _jsx(Button, { label: isRunning ? runningLabel : runLabel, variant: "primary", isLoading: isRunning, isDisabled: !isReady, onClick: onRun })] })] }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DecisionResult } from '@theoremjs/agents';
|
|
2
|
+
import type { DecisionInterface } from '../client/decision-transport.ts';
|
|
3
|
+
export type TheoremDecisionAnswersProps = {
|
|
4
|
+
iface: DecisionInterface;
|
|
5
|
+
result: DecisionResult;
|
|
6
|
+
/** The round trip, for the line under the answers. */
|
|
7
|
+
elapsedMs?: number | null;
|
|
8
|
+
/** A newer decision is running: the answers dim until it lands. */
|
|
9
|
+
isStale?: boolean;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* A decision's answers, one card per question in the order the profile asks
|
|
13
|
+
* them, and one quiet line for the model, time, tokens, and cost.
|
|
14
|
+
*/
|
|
15
|
+
export declare function TheoremDecisionAnswers({ iface, result, elapsedMs, isStale }: TheoremDecisionAnswersProps): import("react").JSX.Element;
|