@theoremjs/react 0.3.0 → 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.
Files changed (258) hide show
  1. package/README.md +728 -202
  2. package/client/composer-hints.js +1 -1
  3. package/client/decision-images.d.ts +76 -0
  4. package/client/decision-images.js +139 -0
  5. package/client/decision-transport.d.ts +26 -4
  6. package/client/decision-transport.js +79 -6
  7. package/client/embedding-input.d.ts +84 -0
  8. package/client/embedding-input.js +194 -0
  9. package/client/embedding-space.d.ts +82 -0
  10. package/client/embedding-space.js +389 -0
  11. package/client/embedding-transport.d.ts +40 -0
  12. package/client/embedding-transport.js +71 -0
  13. package/client/failure.d.ts +7 -10
  14. package/client/failure.js +9 -9
  15. package/client/host-call.d.ts +1 -1
  16. package/client/host-call.js +7 -2
  17. package/client/host-transport.d.ts +2 -0
  18. package/client/host-transport.js +3 -2
  19. package/client/index.d.ts +19 -11
  20. package/client/index.js +14 -10
  21. package/client/ink-waveform.d.ts +1 -1
  22. package/client/ink-waveform.js +1 -2
  23. package/client/live/apply-live-turn-tool-event.js +1 -1
  24. package/client/live/live-captions.d.ts +30 -0
  25. package/client/live/live-captions.js +105 -1
  26. package/client/live/live-mic-forward.d.ts +1 -0
  27. package/client/live/live-mic-forward.js +11 -2
  28. package/client/live/live-page-tool.d.ts +34 -0
  29. package/client/live/live-page-tool.js +11 -0
  30. package/client/live/live-state.d.ts +4 -2
  31. package/client/live/live-state.js +2 -0
  32. package/client/live/live-video.js +1 -1
  33. package/client/live/run-live-tool-call.d.ts +7 -2
  34. package/client/live/run-live-tool-call.js +48 -10
  35. package/client/live/use-live-caption-log.d.ts +17 -0
  36. package/client/live/use-live-caption-log.js +51 -0
  37. package/client/live-client.d.ts +52 -4
  38. package/client/live-client.js +250 -59
  39. package/client/live-messages.d.ts +51 -13
  40. package/client/live-messages.js +42 -9
  41. package/client/page-dispatch.d.ts +15 -0
  42. package/client/page-dispatch.js +63 -0
  43. package/client/run-commit.d.ts +16 -43
  44. package/client/run-commit.js +26 -101
  45. package/client/run-session.d.ts +40 -41
  46. package/client/run-session.js +104 -112
  47. package/client/schema-fields.js +27 -6
  48. package/client/shaped-data.js +37 -8
  49. package/client/source-citations.js +1 -1
  50. package/client/space-geometry.d.ts +39 -0
  51. package/client/space-geometry.js +183 -0
  52. package/client/state-fields.js +1 -1
  53. package/client/tool-result.js +30 -12
  54. package/client/tool-resume.d.ts +18 -1
  55. package/client/tool-resume.js +4 -1
  56. package/client/trace-feed.d.ts +1 -1
  57. package/client/trace-story.d.ts +2 -2
  58. package/client/trace-story.js +43 -19
  59. package/client/trace-view.d.ts +2 -2
  60. package/client/trace-view.js +56 -16
  61. package/client/transcript-groups.d.ts +28 -2
  62. package/client/transcript-groups.js +63 -13
  63. package/client/transcript-schema.d.ts +10 -0
  64. package/client/transcript-schema.js +120 -0
  65. package/client/transport.d.ts +114 -26
  66. package/client/transport.js +106 -32
  67. package/client/turn-client.d.ts +17 -8
  68. package/client/turn-client.js +26 -31
  69. package/client/voice-recorder.d.ts +1 -1
  70. package/client/voice-recorder.js +2 -5
  71. package/client/wire-line.js +5 -5
  72. package/client/worklets/mic-capture.js +9 -9
  73. package/components/InkWaveform.d.ts +12 -1
  74. package/components/InkWaveform.js +65 -42
  75. package/components/live/use-live-runner-controls.d.ts +3 -4
  76. package/components/live/use-live-runner-controls.js +6 -10
  77. package/components/live/use-live-runner-model.d.ts +10 -6
  78. package/components/live/use-live-runner-model.js +29 -14
  79. package/components/live/use-live-runner-ui.d.ts +10 -8
  80. package/components/live/use-live-runner-ui.js +19 -11
  81. package/components/live/use-live-session-client.d.ts +4 -4
  82. package/components/live/use-live-session-client.js +11 -8
  83. package/components/use-composer-voice.d.ts +4 -4
  84. package/components/use-composer-voice.js +8 -8
  85. package/components/use-editor-selection.js +4 -1
  86. package/components/use-voice-playback.d.ts +2 -1
  87. package/components/use-voice-playback.js +4 -4
  88. package/hooks/use-described.d.ts +5 -1
  89. package/hooks/use-described.js +11 -9
  90. package/hooks/use-embedding-index.d.ts +27 -0
  91. package/hooks/use-embedding-index.js +43 -0
  92. package/hooks/use-one-call.d.ts +31 -0
  93. package/hooks/use-one-call.js +76 -0
  94. package/hooks/use-page-tool-warnings.d.ts +6 -0
  95. package/hooks/use-page-tool-warnings.js +25 -0
  96. package/hooks/use-second-ticker.d.ts +2 -0
  97. package/hooks/use-second-ticker.js +13 -0
  98. package/hooks/use-theorem-chat-actions.d.ts +25 -21
  99. package/hooks/use-theorem-chat-actions.js +67 -45
  100. package/hooks/use-theorem-chat-state.d.ts +6 -6
  101. package/hooks/use-theorem-chat-state.js +13 -9
  102. package/hooks/use-theorem-chat.d.ts +48 -14
  103. package/hooks/use-theorem-chat.js +329 -141
  104. package/hooks/use-theorem-decision.d.ts +5 -10
  105. package/hooks/use-theorem-decision.js +4 -67
  106. package/hooks/use-theorem-embedding.d.ts +18 -0
  107. package/hooks/use-theorem-embedding.js +12 -0
  108. package/hooks/use-theorem-host.d.ts +5 -4
  109. package/hooks/use-theorem-host.js +26 -8
  110. package/hooks/use-theorem-interface.d.ts +6 -3
  111. package/hooks/use-theorem-interface.js +12 -22
  112. package/index.d.ts +6 -3
  113. package/index.js +5 -2
  114. package/live.d.ts +1 -0
  115. package/live.js +1 -1
  116. package/package.json +6 -5
  117. package/server/checkpoint-ownership.d.ts +6 -0
  118. package/server/checkpoint-ownership.js +42 -0
  119. package/server/conversation-store.d.ts +92 -0
  120. package/server/conversation-store.js +131 -0
  121. package/server/conversation.d.ts +193 -0
  122. package/server/conversation.js +552 -0
  123. package/server/decision-handler.d.ts +12 -4
  124. package/server/decision-handler.js +29 -38
  125. package/server/embedding-handler.d.ts +33 -0
  126. package/server/embedding-handler.js +45 -0
  127. package/server/handler.d.ts +58 -20
  128. package/server/handler.js +441 -238
  129. package/server/mod.d.ts +15 -6
  130. package/server/mod.js +13 -5
  131. package/server/page-dispatch.d.ts +22 -0
  132. package/server/page-dispatch.js +127 -0
  133. package/server/request-body.d.ts +2 -0
  134. package/server/request-body.js +42 -0
  135. package/server/request-check.d.ts +6 -1
  136. package/server/request-check.js +27 -12
  137. package/server/session-authority.d.ts +8 -0
  138. package/server/session-authority.js +78 -0
  139. package/server/session-boundary.d.ts +26 -0
  140. package/server/session-boundary.js +64 -0
  141. package/server/session-identity.d.ts +16 -0
  142. package/server/session-identity.js +41 -0
  143. package/server/session-limits.d.ts +12 -0
  144. package/server/session-limits.js +59 -0
  145. package/server/session-store.d.ts +69 -20
  146. package/server/session-store.js +220 -5
  147. package/server/single-action.d.ts +21 -0
  148. package/server/single-action.js +30 -0
  149. package/server/steer-inbox.js +1 -1
  150. package/server/tool-operations.d.ts +6 -0
  151. package/server/tool-operations.js +132 -0
  152. package/server/turn-input.d.ts +10 -0
  153. package/server/turn-input.js +29 -0
  154. package/styles/ink-controls.css +78 -78
  155. package/ui/CardWall.d.ts +10 -0
  156. package/ui/CardWall.js +61 -0
  157. package/ui/ChatComposerBar.d.ts +28 -5
  158. package/ui/ChatComposerBar.js +85 -30
  159. package/ui/ChatTranscript.d.ts +14 -1
  160. package/ui/ChatTranscript.js +110 -57
  161. package/ui/ConsoleFrame.d.ts +61 -4
  162. package/ui/ConsoleFrame.js +109 -9
  163. package/ui/DecisionAnswers.d.ts +40 -2
  164. package/ui/DecisionAnswers.js +166 -35
  165. package/ui/DecisionImages.d.ts +16 -0
  166. package/ui/DecisionImages.js +46 -0
  167. package/ui/EmbeddingDocuments.d.ts +78 -0
  168. package/ui/EmbeddingDocuments.js +278 -0
  169. package/ui/EmbeddingQuery.d.ts +27 -0
  170. package/ui/EmbeddingQuery.js +95 -0
  171. package/ui/EmbeddingResults.d.ts +41 -0
  172. package/ui/EmbeddingResults.js +165 -0
  173. package/ui/EmbeddingSpace.d.ts +26 -0
  174. package/ui/EmbeddingSpace.js +106 -0
  175. package/ui/LiveCaptionsPanel.d.ts +22 -0
  176. package/ui/LiveCaptionsPanel.js +27 -0
  177. package/ui/LiveRunner.d.ts +6 -4
  178. package/ui/LiveRunner.js +42 -55
  179. package/ui/PaneState.d.ts +33 -0
  180. package/ui/PaneState.js +50 -0
  181. package/ui/SchemaFields.d.ts +1 -1
  182. package/ui/SchemaFields.js +54 -26
  183. package/ui/ShapedData.js +73 -28
  184. package/ui/SidePanel.d.ts +74 -5
  185. package/ui/SidePanel.js +82 -9
  186. package/ui/StateFields.d.ts +10 -2
  187. package/ui/StateFields.js +56 -13
  188. package/ui/TheoremChat.d.ts +65 -14
  189. package/ui/TheoremChat.js +66 -78
  190. package/ui/TheoremDecision.d.ts +12 -20
  191. package/ui/TheoremDecision.js +78 -35
  192. package/ui/TheoremEmbedding.d.ts +27 -0
  193. package/ui/TheoremEmbedding.js +122 -0
  194. package/ui/TheoremHost.d.ts +22 -5
  195. package/ui/TheoremHost.js +127 -37
  196. package/ui/ToolGateCard.d.ts +5 -1
  197. package/ui/ToolGateCard.js +30 -23
  198. package/ui/ToolResult.js +12 -10
  199. package/ui/ToolResultCharts.js +29 -12
  200. package/ui/TraceCharts.d.ts +2 -2
  201. package/ui/TraceCharts.js +48 -17
  202. package/ui/TraceGuardrails.d.ts +3 -2
  203. package/ui/TraceGuardrails.js +24 -6
  204. package/ui/TraceInspector.d.ts +11 -1
  205. package/ui/TraceInspector.js +45 -28
  206. package/ui/TraceInspectorPanel.d.ts +31 -9
  207. package/ui/TraceInspectorPanel.js +77 -39
  208. package/ui/TraceOverview.d.ts +7 -3
  209. package/ui/TraceOverview.js +44 -14
  210. package/ui/TraceSpanDetail.js +24 -22
  211. package/ui/TraceStory.js +9 -8
  212. package/ui/TraceValues.js +19 -9
  213. package/ui/TraceWaterfall.js +12 -9
  214. package/ui/VoiceNote.js +4 -4
  215. package/ui/approval-heading.d.ts +4 -0
  216. package/ui/approval-heading.js +9 -0
  217. package/ui/arrive.d.ts +18 -0
  218. package/ui/arrive.js +46 -0
  219. package/ui/built/theme.css +86 -68
  220. package/ui/built/theme.css.d.ts +9 -0
  221. package/ui/built/theorem.d.ts +3 -3
  222. package/ui/built/theorem.js +34 -5
  223. package/ui/built/theorem.variants.d.ts +8 -2
  224. package/ui/code-language.d.ts +12 -0
  225. package/ui/code-language.js +50 -0
  226. package/ui/disclosure-motion.js +53 -9
  227. package/ui/embedding-space-gl.d.ts +37 -0
  228. package/ui/embedding-space-gl.js +663 -0
  229. package/ui/embedding-space-view.d.ts +8 -0
  230. package/ui/embedding-space-view.js +8 -0
  231. package/ui/glide.d.ts +7 -0
  232. package/ui/glide.js +44 -0
  233. package/ui/icons.js +2 -1
  234. package/ui/index.d.ts +20 -9
  235. package/ui/index.js +20 -10
  236. package/ui/labels-provider.js +5 -2
  237. package/ui/labels.d.ts +374 -85
  238. package/ui/labels.js +1028 -188
  239. package/ui/markdown-plugins.d.ts +9 -0
  240. package/ui/markdown-plugins.js +13 -0
  241. package/ui/mcp-icon.d.ts +6 -0
  242. package/ui/mcp-icon.js +8 -0
  243. package/ui/row-keys.d.ts +19 -0
  244. package/ui/row-keys.js +33 -0
  245. package/ui/theme.d.ts +1 -1
  246. package/ui/theme.js +2 -2
  247. package/ui/theorem-theme.js +32 -10
  248. package/ui/trace-palette.d.ts +2 -16
  249. package/ui/trace-palette.js +2 -16
  250. package/ui/trace-patterns.d.ts +11 -3
  251. package/ui/trace-patterns.js +16 -3
  252. package/ui/transcript-copy-text.js +1 -0
  253. package/ui/wall-columns.d.ts +8 -0
  254. package/ui/wall-columns.js +19 -0
  255. package/client/walk-away.d.ts +0 -34
  256. package/client/walk-away.js +0 -48
  257. package/server/walk-away.d.ts +0 -22
  258. package/server/walk-away.js +0 -41
@@ -0,0 +1,106 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text } from '@astryxdesign/core/Text';
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { createSpaceRenderer } from "./embedding-space-gl.js";
5
+ /** Times the GPU may drop the space before it is listed instead. */
6
+ const LOSSES = 2;
7
+ /** Pixels a press moves before it counts as turning the sphere, not pointing. */
8
+ const DRAG_PX = 3;
9
+ function isStill() {
10
+ return globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
11
+ }
12
+ function SpaceCanvas({ space, query, matches, hovered, onHover, nameOf, label, onUnavailable, onLost, }) {
13
+ const canvasRef = useRef(null);
14
+ const rendererRef = useRef(null);
15
+ const press = useRef(null);
16
+ const failed = useRef(onUnavailable);
17
+ failed.current = onUnavailable;
18
+ const lost = useRef(onLost);
19
+ lost.current = onLost;
20
+ useEffect(() => {
21
+ const canvas = canvasRef.current;
22
+ if (!canvas)
23
+ return;
24
+ try {
25
+ rendererRef.current = createSpaceRenderer(canvas, { still: isStill() });
26
+ }
27
+ catch {
28
+ failed.current();
29
+ return;
30
+ }
31
+ const onContextLost = (event) => {
32
+ event.preventDefault();
33
+ lost.current();
34
+ };
35
+ canvas.addEventListener('webglcontextlost', onContextLost);
36
+ return () => {
37
+ canvas.removeEventListener('webglcontextlost', onContextLost);
38
+ rendererRef.current?.destroy();
39
+ rendererRef.current = null;
40
+ };
41
+ }, []);
42
+ useEffect(() => {
43
+ rendererRef.current?.setSpace(space);
44
+ }, [space]);
45
+ useEffect(() => {
46
+ rendererRef.current?.setFocus({ query, matches, hovered });
47
+ }, [query, matches, hovered]);
48
+ const at = (event) => {
49
+ const box = event.currentTarget.getBoundingClientRect();
50
+ return { x: event.clientX - box.left, y: event.clientY - box.top };
51
+ };
52
+ return (_jsxs("div", { style: { position: 'relative', width: '100%', aspectRatio: '1 / 1', maxHeight: 460 }, children: [_jsx("canvas", { ref: canvasRef, role: "img", "aria-label": label, style: {
53
+ display: 'block',
54
+ width: '100%',
55
+ height: '100%',
56
+ cursor: hovered === null ? 'grab' : 'pointer',
57
+ // why: a vertical swipe still scrolls the page; only a sideways one turns the sphere.
58
+ touchAction: 'pan-y',
59
+ }, onPointerDown: (event) => {
60
+ event.currentTarget.setPointerCapture(event.pointerId);
61
+ press.current = { ...at(event), moved: false };
62
+ }, onPointerMove: (event) => {
63
+ const point = at(event);
64
+ const held = press.current;
65
+ if (held) {
66
+ const dx = point.x - held.x;
67
+ const dy = point.y - held.y;
68
+ if (!held.moved && Math.hypot(dx, dy) < DRAG_PX)
69
+ return;
70
+ held.moved = true;
71
+ rendererRef.current?.drag(dx, dy);
72
+ press.current = { ...point, moved: true };
73
+ return;
74
+ }
75
+ onHover(rendererRef.current?.pick(point.x, point.y) ?? null);
76
+ }, onPointerUp: () => {
77
+ press.current = null;
78
+ rendererRef.current?.release();
79
+ }, onPointerCancel: () => {
80
+ press.current = null;
81
+ rendererRef.current?.release();
82
+ }, onPointerLeave: () => {
83
+ if (!press.current)
84
+ onHover(null);
85
+ } }), hovered !== null ? (_jsx("div", { style: { position: 'absolute', left: 0, right: 0, bottom: 0, pointerEvents: 'none' }, children: _jsx(Text, { type: "supporting", maxLines: 1, children: nameOf(hovered) }) })) : null] }));
86
+ }
87
+ /**
88
+ * The index drawn as a space: each document a dot in a ball, placed by the
89
+ * three directions the vectors differ most along, with a circle round the rim
90
+ * in each plane two of them span. Each document is joined to its nearest
91
+ * neighbour by cosine similarity when that pair stands out from the index's
92
+ * typical pair; documents the lines connect share a colour, and a query's
93
+ * white dot has a line to each match that stands out the same way. Drag to turn
94
+ * it; point at a dot to name its document.
95
+ */
96
+ export function EmbeddingSpaceView(props) {
97
+ // why: a lost GPU context does not come back on the same canvas; a new one asks again.
98
+ const [generation, setGeneration] = useState(0);
99
+ const onLost = () => {
100
+ if (generation >= LOSSES)
101
+ props.onUnavailable();
102
+ else
103
+ setGeneration(generation + 1);
104
+ };
105
+ return _jsx(SpaceCanvas, { ...props, onLost: onLost }, generation);
106
+ }
@@ -0,0 +1,22 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type LiveCaptionState, type LiveCaptionTurn } from '../client/live/live-captions.ts';
3
+ export type LiveCaptionsPanelProps = {
4
+ /** Profile handle. The transcript shows it as the agent's name. */
5
+ handle: string;
6
+ captions: LiveCaptionState;
7
+ pastCalls: readonly (readonly LiveCaptionTurn[])[];
8
+ draftText: string;
9
+ onDraftTextChange: (text: string) => void;
10
+ onSubmit: (text: string) => void;
11
+ /** Off while no call is connected. */
12
+ isDisabled: boolean;
13
+ /** Omit the text field when the profile takes no text. */
14
+ showComposer?: boolean;
15
+ /** Drawn above the transcript. The live runner puts the camera here. */
16
+ leading?: ReactNode;
17
+ };
18
+ /**
19
+ * The live call's captions: the chat transcript, an empty state, and the text
20
+ * composer. The same panel the live runner docks on the right.
21
+ */
22
+ export declare function LiveCaptionsPanel(props: LiveCaptionsPanelProps): import("react").JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { EmptyState } from '@astryxdesign/core/EmptyState';
3
+ import { Icon } from '@astryxdesign/core/Icon';
4
+ import { StackItem } from '@astryxdesign/core/Stack';
5
+ import { VStack } from '@astryxdesign/core/VStack';
6
+ import { IconSubtitles } from '@tabler/icons-react';
7
+ import { liveCaptionStreaming, liveCaptionTranscript, } from "../client/live/live-captions.js";
8
+ import { ChatComposerBar } from "./ChatComposerBar.js";
9
+ import { ChatTranscript } from "./ChatTranscript.js";
10
+ import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
11
+ import { PanelScroll } from "./SidePanel.js";
12
+ /**
13
+ * The live call's captions: the chat transcript, an empty state, and the text
14
+ * composer. The same panel the live runner docks on the right.
15
+ */
16
+ export function LiveCaptionsPanel(props) {
17
+ return (_jsx(TheoremLabelsProvider, { children: _jsx(LiveCaptionsBody, { ...props }) }));
18
+ }
19
+ function LiveCaptionsBody({ handle, captions, pastCalls, draftText, onDraftTextChange, onSubmit, isDisabled, showComposer = true, leading, }) {
20
+ const t = useLabels();
21
+ const agentName = t('@theorem.agent.handle', { handle });
22
+ const { blocks, callStarts } = liveCaptionTranscript(pastCalls, captions);
23
+ const newSession = t('@theorem.live.new_session');
24
+ const dividers = Object.fromEntries(callStarts.map((id) => [id, newSession]));
25
+ const composer = showComposer ? (_jsx(ChatComposerBar, { handle: handle, draftText: draftText, onDraftTextChange: onDraftTextChange, onSubmit: onSubmit, isDisabled: isDisabled })) : null;
26
+ return (_jsxs(VStack, { height: "100%", children: [leading, _jsx(StackItem, { size: "fill", children: blocks.length > 0 ? (_jsx(PanelScroll, { label: t('@theorem.panel.captions.name'), children: _jsx(ChatTranscript, { blocks: blocks, dividers: dividers, handle: agentName, streaming: liveCaptionStreaming(captions) }) })) : (_jsx(VStack, { height: "100%", vAlign: "center", padding: 4, children: _jsx(EmptyState, { icon: _jsx(Icon, { icon: IconSubtitles, size: "lg", color: "secondary" }), title: t('@theorem.panel.captions.empty.title'), description: t('@theorem.panel.captions.empty.description'), isCompact: true }) })) }), composer] }));
27
+ }
@@ -1,5 +1,6 @@
1
1
  import type { DefinedTheme } from '@astryxdesign/core/theme';
2
2
  import type { LiveProfileInterface } from '@theoremjs/agents/interface';
3
+ import type { LiveCallOptions } from '../client/live/live-page-tool.ts';
3
4
  import type { LiveConnection } from '../client/live-client.ts';
4
5
  import { type TheoremLabels } from './labels.ts';
5
6
  export type LiveRunnerProps = {
@@ -12,12 +13,13 @@ export type LiveRunnerProps = {
12
13
  /** Replacement lines by locale, as on `TheoremChat`. */
13
14
  labels?: TheoremLabels;
14
15
  /**
15
- * Show the trace in place of the call, from the host's own control; the
16
- * built-in trace toggle then hides. Omit to keep the toggle. Needs a profile
17
- * that records traces.
16
+ * Drive the trace from the host's own control. The built-in toggle hides.
17
+ * The open trace takes the call's place, unless this page is inside
18
+ * `TracePlacement value="panel"`, where it docks. Omit to keep the toggle.
19
+ * Needs a profile that records traces.
18
20
  */
19
21
  trace?: boolean;
20
- };
22
+ } & LiveCallOptions;
21
23
  /**
22
24
  * Live (voice / video) runner on Astryx. Before a call: a landing like the
23
25
  * chat's, offering a voice or video call. In a call: the agent tag and state
package/ui/LiveRunner.js CHANGED
@@ -1,11 +1,9 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { AspectRatio } from '@astryxdesign/core/AspectRatio';
3
3
  import { Banner } from '@astryxdesign/core/Banner';
4
4
  import { Button } from '@astryxdesign/core/Button';
5
5
  import { Center } from '@astryxdesign/core/Center';
6
- import { ChatComposer, ChatComposerInput, ChatLayout, ChatMessage, ChatMessageBubble, ChatMessageList, ChatSystemMessage, ChatSendButton, } from '@astryxdesign/core/Chat';
7
6
  import { Dialog } from '@astryxdesign/core/Dialog';
8
- import { EmptyState } from '@astryxdesign/core/EmptyState';
9
7
  import { HStack } from '@astryxdesign/core/HStack';
10
8
  import { Icon } from '@astryxdesign/core/Icon';
11
9
  import { IconButton } from '@astryxdesign/core/IconButton';
@@ -15,18 +13,18 @@ import { Text } from '@astryxdesign/core/Text';
15
13
  import { ToggleButton } from '@astryxdesign/core/ToggleButton';
16
14
  import { Toolbar } from '@astryxdesign/core/Toolbar';
17
15
  import { VStack } from '@astryxdesign/core/VStack';
18
- import { IconCameraRotate, IconMessage, IconMicrophone, IconMicrophoneOff, IconPhone, IconPhoneOff, IconSubtitles, IconVideo, IconVideoOff, } from '@tabler/icons-react';
19
- import { useEffect, useLayoutEffect, useRef } from 'react';
16
+ import { IconCameraRotate, IconMessage, IconMicrophone, IconMicrophoneOff, IconPhone, IconPhoneOff, IconVideo, IconVideoOff, } from '@tabler/icons-react';
17
+ import { Fragment, useEffect, useLayoutEffect, useRef, } from 'react';
20
18
  import { InkWaveform } from "../components/InkWaveform.js";
21
19
  import { useLiveRunnerModel } from "../components/live/use-live-runner-model.js";
22
- import { NO_FOCUS_RING } from "./ChatComposerBar.js";
20
+ import { LiveCaptionsPanel } from "./LiveCaptionsPanel.js";
23
21
  import { liveStateLabel } from "./labels.js";
24
22
  import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
25
- import { SidePanel, SidePanelHeader, SidePanelToggle, useSidePanel } from "./SidePanel.js";
26
- import { ApprovalCard, AuthChallengeCard } from "./ToolGateCard.js";
23
+ import { PANEL_BOTTOM, PANEL_EDGE, SidePanel, SidePanelHeader, SidePanelToggle, useSidePanel, } from "./SidePanel.js";
27
24
  import { DEFAULT_CHAT_MAX_WIDTH } from "./TheoremChat.js";
28
- import { TheoremThemeProvider } from "./theme.js";
25
+ import { ApprovalCard, AuthChallengeCard, GATE_CARD_WIDTH } from "./ToolGateCard.js";
29
26
  import { useTraceInspector, WithTrace } from "./TraceInspectorPanel.js";
27
+ import { TheoremThemeProvider } from "./theme.js";
30
28
  /** The waveform's box: it rests on the stage floor rather than filling the stage. */
31
29
  const WAVE_MAX_WIDTH = 720;
32
30
  const WAVE_HEIGHT = 320;
@@ -40,10 +38,10 @@ const WAVE_HEIGHT = 320;
40
38
  export function LiveRunner({ theme, mode, labels, ...props }) {
41
39
  return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(LiveRunnerBody, { ...props }) }) }));
42
40
  }
43
- function LiveRunnerBody({ iface, connection, trace }) {
41
+ function LiveRunnerBody({ iface, connection, trace, slots, context, pageTools, }) {
44
42
  const t = useLabels();
45
- const model = useLiveRunnerModel(iface, connection);
46
- // Both panels size against the whole live layout, so a third means the same for each.
43
+ const model = useLiveRunnerModel(iface, connection, { slots, context, pageTools });
44
+ // why: Both panels size against the whole live layout, so a third means the same for each.
47
45
  const layoutRef = useRef(null);
48
46
  const inspector = useTraceInspector(iface, model.traces, trace);
49
47
  const captions = useSidePanel(layoutRef, true);
@@ -57,7 +55,7 @@ function LiveRunnerBody({ iface, connection, trace }) {
57
55
  if (!model.callStarted) {
58
56
  return (_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { ref: layoutRef, height: "fill", header: _jsx(SidePanelHeader, { children: inspector.toggle }), content: _jsx(LayoutContent, { padding: 0, children: _jsx(LiveLanding, { model: model }) }) }) }));
59
57
  }
60
- return (_jsxs(_Fragment, { children: [_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { ref: layoutRef, height: "fill", header: _jsxs(SidePanelHeader, { children: [captionsToggle, inspector.toggle] }), content: _jsx(LayoutContent, { padding: 0, children: _jsx(Layout, { height: "fill", end: _jsx(SidePanel, { id: captions.id, labels: captionLabels, resizable: captions.resizable, open: captions.open, padding: 0, children: _jsx(LiveCaptions, { model: model }) }), content: _jsx(LayoutContent, { padding: 0, children: _jsx(VStack, { height: "100%", paddingInline: 3, paddingBlockEnd: 3, children: _jsx(LiveStage, { model: model }) }) }) }) }) }) }), _jsx(LiveToolGateDialog, { prompt: model.gatePrompt, onResolve: model.resolveGateDecision })] }));
58
+ return (_jsxs(_Fragment, { children: [_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { ref: layoutRef, height: "fill", header: _jsx(SidePanelHeader, { children: inspector.toggle }), content: _jsx(LayoutContent, { padding: 0, children: _jsx(Layout, { height: "fill", end: _jsx(SidePanel, { id: captions.id, labels: captionLabels, resizable: captions.resizable, open: captions.open, children: _jsx(LiveCaptions, { model: model }) }), content: _jsx(LayoutContent, { padding: 0, children: _jsx(VStack, { height: "100%", paddingInline: PANEL_EDGE, paddingBlockEnd: PANEL_BOTTOM, children: _jsx(LiveStage, { model: model, captionsToggle: captionsToggle }) }) }) }) }) }) }), _jsx(LiveToolGateDialog, { prompt: model.gatePrompt, agent: t('@theorem.agent.handle', { handle: model.handle }), onResolve: model.resolveGateDecision })] }));
61
59
  }
62
60
  /**
63
61
  * Before a call: the chat landing's greeting, with call buttons where the
@@ -65,16 +63,18 @@ function LiveRunnerBody({ iface, connection, trace }) {
65
63
  */
66
64
  function LiveLanding({ model }) {
67
65
  const t = useLabels();
68
- return (_jsx(VStack, { minHeight: "100%", vAlign: "center", gap: 8, paddingInline: 3, children: _jsx(Center, { axis: "horizontal", width: "100%", children: _jsxs(VStack, { width: "100%", maxWidth: DEFAULT_CHAT_MAX_WIDTH, gap: 8, children: [_jsxs(VStack, { gap: 1, children: [_jsx(Text, { type: "large", as: "h2", children: t('@theorem.agent.handle', { handle: model.handle }) }), _jsx(Text, { type: "display-2", as: "h1", children: t('@theorem.live.greeting') })] }), _jsxs(HStack, { gap: 2, wrap: "wrap", children: [_jsx(Button, { label: t(model.voiceAvailable ? '@theorem.live.start_voice_call' : '@theorem.live.start_call'), variant: "primary", icon: _jsx(Icon, { icon: IconPhone }), onClick: () => model.startCall({ video: false }) }), model.videoAvailable ? (_jsx(Button, { label: t('@theorem.live.start_video_call'), variant: "secondary", icon: _jsx(Icon, { icon: IconVideo }), onClick: () => model.startCall({ video: true }) })) : null] })] }) }) }));
66
+ return (_jsx(VStack, { minHeight: "100%", vAlign: "center", gap: 8, paddingInline: 3, children: _jsx(Center, { axis: "horizontal", width: "100%", children: _jsxs(VStack, { width: "100%", maxWidth: DEFAULT_CHAT_MAX_WIDTH, gap: 8, children: [_jsxs(VStack, { gap: 1, children: [_jsx(Text, { type: "large", as: "h2", children: t('@theorem.agent.handle', { handle: model.handle }) }), _jsx(Text, { type: "display-2", as: "h1", children: t('@theorem.live.greeting') })] }), _jsxs(HStack, { gap: 2, wrap: "wrap", children: [_jsx(Button, { label: t(model.voiceAvailable
67
+ ? '@theorem.live.start_voice_call'
68
+ : '@theorem.live.start_call'), variant: "primary", icon: _jsx(Icon, { icon: IconPhone }), onClick: () => model.startCall({ video: false }) }), model.videoAvailable ? (_jsx(Button, { label: t('@theorem.live.start_video_call'), variant: "secondary", icon: _jsx(Icon, { icon: IconVideo }), onClick: () => model.startCall({ video: true }) })) : null] })] }) }) }));
69
69
  }
70
70
  /**
71
71
  * The agent tag and state over the resting waveform, then the call controls,
72
72
  * all in one column the waveform's width.
73
73
  */
74
- function LiveStage({ model }) {
74
+ function LiveStage({ model, captionsToggle }) {
75
75
  const t = useLabels();
76
76
  const ref = useEnterMotion();
77
- return (_jsx(VStack, { ref: ref, height: "100%", hAlign: "center", children: _jsxs(VStack, { width: "100%", maxWidth: WAVE_MAX_WIDTH, height: "100%", gap: 3, children: [_jsxs(VStack, { gap: 0.5, children: [_jsx(Text, { type: "label", color: "secondary", children: t('@theorem.agent.handle', { handle: model.handle }) }), _jsx(Text, { size: "sm", color: "secondary", children: liveStateLabel(t, model.liveState, model.activeTool) })] }), model.failure ? _jsx(Banner, { status: "error", title: model.failure.error }) : null, model.sessionEnded ? _jsx(Banner, { status: "info", title: model.sessionEnded }) : null, _jsx(StackItem, { size: "fill", children: _jsx(VStack, { height: "100%", vAlign: "end", children: _jsx(VStack, { width: "100%", height: WAVE_HEIGHT, style: { color: 'var(--color-icon-primary)' }, "aria-hidden": "true", children: _jsx(InkWaveform, { status: model.status, inputLevel: model.inputLevel, outputLevel: model.outputLevel, toolActive: model.toolActive, variant: "hero" }) }) }) }), _jsx(LiveControls, { model: model })] }) }));
77
+ return (_jsx(VStack, { ref: ref, height: "100%", hAlign: "center", children: _jsxs(VStack, { width: "100%", maxWidth: WAVE_MAX_WIDTH, height: "100%", gap: 3, children: [_jsxs(VStack, { gap: 0.5, children: [_jsx(Text, { type: "label", color: "secondary", children: t('@theorem.agent.handle', { handle: model.handle }) }), _jsx(Text, { size: "sm", color: "secondary", children: liveStateLabel(t, model.liveState, model.activeTool) })] }), model.failure ? _jsx(Banner, { status: "error", role: "alert", title: model.failure.error }) : null, model.sessionEnded ? (_jsx(Banner, { status: "info", role: "status", title: model.sessionEnded })) : null, _jsx(StackItem, { size: "fill", children: _jsx(VStack, { height: "100%", vAlign: "end", children: _jsx(VStack, { width: "100%", height: WAVE_HEIGHT, style: { color: 'var(--color-icon-primary)' }, "aria-hidden": "true", children: _jsx(InkWaveform, { status: model.status, levelsRef: model.levelsRef, toolActive: model.toolActive, variant: "hero" }) }) }) }), _jsx(LiveControls, { model: model, captionsToggle: captionsToggle })] }) }));
78
78
  }
79
79
  /**
80
80
  * Eases the call view in from the landing: Astryx has no enter-transition
@@ -96,10 +96,10 @@ function useEnterMotion() {
96
96
  return ref;
97
97
  }
98
98
  /**
99
- * Mic, camera, text and call controls, centred under the stage. Icons show
99
+ * Mic, camera, captions, and call controls, centred under the stage. Icons show
100
100
  * what a click does, as End call does: a slash means "turn this off".
101
101
  */
102
- function LiveControls({ model }) {
102
+ function LiveControls({ model, captionsToggle }) {
103
103
  const t = useLabels();
104
104
  const inactive = !model.sessionActive;
105
105
  const controls = [];
@@ -116,57 +116,36 @@ function LiveControls({ model }) {
116
116
  } }, "flip"));
117
117
  }
118
118
  }
119
- controls.push(model.canRestart ? (_jsx(IconButton, { label: t('@theorem.live.start_call'), tooltip: t('@theorem.live.start_call'), variant: "primary", icon: _jsx(Icon, { icon: IconPhone }), onClick: () => {
119
+ controls.push(_jsx(Fragment, { children: captionsToggle }, "captions"), model.canRestart ? (_jsx(IconButton, { label: t('@theorem.live.start_call'), tooltip: t('@theorem.live.start_call'), variant: "primary", icon: _jsx(Icon, { icon: IconPhone }), onClick: () => {
120
120
  void model.handleRestart();
121
121
  } }, "call")) : (_jsx(IconButton, { label: t('@theorem.live.end_call'), tooltip: t('@theorem.live.end_call'), variant: "destructive", isDisabled: inactive, icon: _jsx(Icon, { icon: IconPhoneOff }), onClick: model.handleEnd }, "call")));
122
122
  return (_jsx(Toolbar, { label: t('@theorem.live.controls'), dividers: ['top'], centerContent: _jsx(HStack, { gap: 2, vAlign: "center", children: controls }) }));
123
123
  }
124
124
  /**
125
- * Captions as a chat column: committed turns, then the interim lines still
126
- * being heard, and the text composer in its dock when the profile takes text.
125
+ * Captions drawn by the chat's own transcript, with the text composer in its
126
+ * dock when the profile takes text.
127
127
  */
128
128
  function LiveCaptions({ model }) {
129
- const t = useLabels();
130
- const { interimAgent } = model.captions;
131
- const agentName = t('@theorem.agent.handle', { handle: model.handle });
132
- const composer = model.textAvailable ? (_jsx(ChatComposer, { style: NO_FOCUS_RING, value: model.textDraft, onChange: model.setTextDraft, onSubmit: model.handleSendText, isDisabled: !model.sessionActive, placeholder: t('@theorem.composer.placeholder', { handle: model.handle }), input: _jsx(ChatComposerInput, {}), sendButton: _jsx(ChatSendButton, {}) })) : null;
133
- const messages = [
134
- ...model.pastCalls.flatMap((turns, call) => [
135
- ...turns.map((turn) => captionMessage(`${String(call)}:${turn.id}`, turn.role, turn.text, agentName)),
136
- _jsx(ChatSystemMessage, { variant: "divider", children: t('@theorem.live.new_session') }, `session-${String(call + 1)}`),
137
- ]),
138
- ...captionMessages(model.captions, agentName),
139
- ];
140
- // The empty state goes in ChatLayout's slot, which centres it; the list's
141
- // own slot sits under its bottom-align spacer. ChatLayout flexes to fill the
142
- // stack below any video preview.
143
- return (_jsxs(VStack, { height: "100%", children: [model.videoPreview ? (_jsx(LiveVideoPreview, { video: model.videoPreview, facingMode: model.videoFacingMode })) : null, _jsx(ChatLayout, { composer: composer, emptyState: _jsx(EmptyState, { icon: _jsx(Icon, { icon: IconSubtitles, size: "lg", color: "secondary" }), title: t('@theorem.panel.captions.empty.title'), description: t('@theorem.panel.captions.empty.description'), isCompact: true }), children: messages.length > 0 ? (_jsx(ChatMessageList, { isStreaming: interimAgent !== '', children: messages })) : null })] }));
144
- }
145
- /** The current call's committed turns, then the lines still being heard. */
146
- function captionMessages({ turns, interimUser, interimAgent }, agentName) {
147
- const messages = turns.map((turn) => captionMessage(turn.id, turn.role, turn.text, agentName));
148
- if (interimUser)
149
- messages.push(captionMessage('interim-user', 'user', interimUser, agentName));
150
- if (interimAgent)
151
- messages.push(captionMessage('interim-agent', 'agent', interimAgent, agentName));
152
- return messages;
153
- }
154
- function captionMessage(key, role, text, agentName) {
155
- return role === 'user' ? (_jsx(ChatMessage, { sender: "user", children: _jsx(ChatMessageBubble, { children: text }) }, key)) : (_jsx(ChatMessage, { sender: "assistant", name: agentName, children: _jsx(Text, { children: text }) }, key));
129
+ return (_jsx(LiveCaptionsPanel, { handle: model.handle, captions: model.captions, pastCalls: model.pastCalls, draftText: model.textDraft, onDraftTextChange: model.setTextDraft, onSubmit: model.handleSendText, isDisabled: !model.sessionActive, showComposer: model.textAvailable, leading: model.videoPreview ? (_jsx(LiveVideoPreview, { video: model.videoPreview, facingMode: model.videoFacingMode })) : null }));
156
130
  }
157
131
  /**
158
132
  * The camera preview, at the top of the captions panel. The capture element comes
159
133
  * from the session client, so it is mounted imperatively; it sits one level
160
134
  * below AspectRatio, so it carries its own fill and mirror styles.
161
135
  */
162
- function LiveVideoPreview({ video, facingMode }) {
136
+ function LiveVideoPreview({ video, facingMode, }) {
163
137
  const t = useLabels();
164
138
  const hostRef = useRef(null);
165
139
  useEffect(() => {
166
140
  const host = hostRef.current;
167
141
  if (!host)
168
142
  return;
169
- Object.assign(video.style, { width: '100%', height: '100%', objectFit: 'cover', display: 'block' });
143
+ Object.assign(video.style, {
144
+ width: '100%',
145
+ height: '100%',
146
+ objectFit: 'cover',
147
+ display: 'block',
148
+ });
170
149
  host.replaceChildren(video);
171
150
  return () => {
172
151
  if (video.parentElement === host)
@@ -178,16 +157,24 @@ function LiveVideoPreview({ video, facingMode }) {
178
157
  }, [video, facingMode]);
179
158
  return (_jsx(AspectRatio, { ratio: 4 / 3, fit: "cover", children: _jsx("div", { ref: hostRef, "aria-label": t('@theorem.live.camera_preview'), role: "img" }) }));
180
159
  }
181
- /** Tool approvals and credential prompts, as a dialog that must be answered. */
182
- function LiveToolGateDialog({ prompt, onResolve, }) {
183
- return (_jsx(Dialog, { isOpen: prompt !== null, purpose: "required", width: 480,
184
- // "required" disables dismissal; should one slip through, it denies.
160
+ /** Room around the gate card for its shadow. */
161
+ const GATE_DIALOG_PADDING = 4;
162
+ const GATE_DIALOG_WIDTH = GATE_CARD_WIDTH + 2 * GATE_DIALOG_PADDING * 4;
163
+ /** The dialog draws no surface of its own: the card is the one chat shows, alone. */
164
+ const GATE_DIALOG_BARE = { background: 'transparent', boxShadow: 'none' };
165
+ /**
166
+ * Tool approvals and credential prompts, as a dialog that must be answered. It shows the card
167
+ * chat shows inline and nothing else, and closes on the answer.
168
+ */
169
+ function LiveToolGateDialog({ prompt, agent, onResolve, }) {
170
+ return (_jsx(Dialog, { isOpen: prompt !== null, purpose: "required", width: GATE_DIALOG_WIDTH, padding: GATE_DIALOG_PADDING, style: GATE_DIALOG_BARE,
171
+ // why: "required" disables dismissal; should one slip through, it denies.
185
172
  onOpenChange: (open) => {
186
173
  if (!open)
187
174
  onResolve({ action: 'deny' });
188
175
  }, children: prompt ? (prompt.gate.kind === 'auth' ? (_jsx(AuthChallengeCard, { gate: prompt.gate, toolName: prompt.gate.tool, onAuthenticated: (secret) => {
189
176
  onResolve({ action: 'auth', secret });
190
- } })) : (_jsx(ApprovalCard, { gate: prompt.gate, toolName: prompt.gate.tool, input: prompt.input, onDecision: (action) => {
177
+ } })) : (_jsx(ApprovalCard, { gate: prompt.gate, toolName: prompt.gate.tool, agent: agent, input: prompt.input, onDecision: (action) => {
191
178
  onResolve({ action });
192
179
  } }))) : null }));
193
180
  }
@@ -0,0 +1,33 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * What a pane says when it has nothing of its own to show: centred both ways
4
+ * in the room it is given. Loading, a failure, and an empty pane are this one
5
+ * component, so they sit in the same place.
6
+ */
7
+ export declare function PaneState({ icon, title, description, actions, children, }: {
8
+ /** Every pane state has one: it is what the eye finds first. */
9
+ icon: ReactNode;
10
+ title: string;
11
+ description?: string;
12
+ /** The buttons that lead out of the state. */
13
+ actions?: ReactNode;
14
+ /** What goes under the words: the detail of a failure, or the way out of it. */
15
+ children?: ReactNode;
16
+ }): import("react").JSX.Element;
17
+ /**
18
+ * A pane that is waiting: the spinner where an empty pane's icon would be,
19
+ * once the wait has gone on a moment; blank and busy before then. Given when
20
+ * the work began, it counts the seconds under the label.
21
+ */
22
+ export declare function PaneLoading({ label, since }: {
23
+ label: string;
24
+ since?: number | null;
25
+ }): import("react").JSX.Element;
26
+ /** A pane that could not show what it is for, and why. */
27
+ export declare function PaneFailure({ title, description, actions, children, }: {
28
+ title: string;
29
+ description?: string;
30
+ /** The buttons that lead out of the failure. */
31
+ actions?: ReactNode;
32
+ children?: ReactNode;
33
+ }): import("react").JSX.Element;
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { EmptyState } from '@astryxdesign/core/EmptyState';
3
+ import { Icon } from '@astryxdesign/core/Icon';
4
+ import { Spinner } from '@astryxdesign/core/Spinner';
5
+ import { VStack } from '@astryxdesign/core/VStack';
6
+ import { IconAlertTriangle } from '@tabler/icons-react';
7
+ import { useEffect, useState } from 'react';
8
+ import { useSecondTicker } from "../hooks/use-second-ticker.js";
9
+ import { liveDuration } from "./labels.js";
10
+ import { useLabels } from "./labels-provider.js";
11
+ /**
12
+ * What a pane says when it has nothing of its own to show: centred both ways
13
+ * in the room it is given. Loading, a failure, and an empty pane are this one
14
+ * component, so they sit in the same place.
15
+ */
16
+ export function PaneState({ icon, title, description, actions, children, }) {
17
+ return (_jsxs(VStack, { height: "100%", vAlign: "center", hAlign: "center", gap: 3, padding: 4, children: [_jsx(EmptyState, { icon: icon, title: title, description: description, actions: actions, isCompact: true }), children] }));
18
+ }
19
+ /**
20
+ * A pane that is waiting: the spinner where an empty pane's icon would be,
21
+ * once the wait has gone on a moment; blank and busy before then. Given when
22
+ * the work began, it counts the seconds under the label.
23
+ */
24
+ export function PaneLoading({ label, since }) {
25
+ const t = useLabels();
26
+ const now = useSecondTicker(since != null);
27
+ const long = useLongWait();
28
+ if (!long)
29
+ return _jsx("div", { "aria-busy": "true", style: { height: '100%' } });
30
+ return (_jsx(PaneState, { icon: _jsx(Spinner, { size: "lg" }), title: label, description: since == null ? undefined : liveDuration(t, now - since) }));
31
+ }
32
+ /**
33
+ * How long a wait stays blank before its spinner shows. Most waits (a profile
34
+ * describing itself, a switch to another agent) end sooner, and the pane
35
+ * changes straight to what it waited for instead of flashing a spinner.
36
+ */
37
+ const QUIET_WAIT_MS = 300;
38
+ /** Whether a wait has gone on long enough to say so. */
39
+ function useLongWait() {
40
+ const [long, setLong] = useState(false);
41
+ useEffect(() => {
42
+ const timer = globalThis.setTimeout(() => setLong(true), QUIET_WAIT_MS);
43
+ return () => globalThis.clearTimeout(timer);
44
+ }, []);
45
+ return long;
46
+ }
47
+ /** A pane that could not show what it is for, and why. */
48
+ export function PaneFailure({ title, description, actions, children, }) {
49
+ return (_jsx(PaneState, { icon: _jsx(Icon, { icon: IconAlertTriangle, size: "lg", color: "secondary" }), title: title, description: description, actions: actions, children: children }));
50
+ }
@@ -5,6 +5,6 @@ import { type Edit as StateEdit } from './StateFields.tsx';
5
5
  * input its type asks for, labelled by its title or key and described by its
6
6
  * description. Optional fields left empty aren't sent.
7
7
  */
8
- export declare function SchemaFields({ schema, value, onChange }: StateEdit<Record<string, unknown>> & {
8
+ export declare function SchemaFields({ schema, value, onChange, }: StateEdit<Record<string, unknown>> & {
9
9
  schema: JsonSchema;
10
10
  }): import("react").JSX.Element;