@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.
Files changed (320) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +772 -2
  3. package/client/attachment-hover-preview.d.ts +11 -0
  4. package/client/attachment-hover-preview.js +36 -0
  5. package/client/audio-level.d.ts +5 -0
  6. package/client/audio-level.js +24 -0
  7. package/client/composer-attachments.d.ts +30 -0
  8. package/client/composer-attachments.js +33 -0
  9. package/client/composer-drawer.d.ts +19 -0
  10. package/client/composer-drawer.js +15 -0
  11. package/client/composer-hints.d.ts +25 -0
  12. package/client/composer-hints.js +20 -0
  13. package/client/composer-primary.d.ts +16 -0
  14. package/client/composer-primary.js +25 -0
  15. package/client/decision-images.d.ts +76 -0
  16. package/client/decision-images.js +139 -0
  17. package/client/decision-transport.d.ts +70 -0
  18. package/client/decision-transport.js +175 -0
  19. package/client/decode-composer-draft.d.ts +11 -0
  20. package/client/decode-composer-draft.js +12 -0
  21. package/client/embedding-input.d.ts +84 -0
  22. package/client/embedding-input.js +194 -0
  23. package/client/embedding-space.d.ts +82 -0
  24. package/client/embedding-space.js +389 -0
  25. package/client/embedding-transport.d.ts +40 -0
  26. package/client/embedding-transport.js +71 -0
  27. package/client/encode-composer-draft.d.ts +9 -0
  28. package/client/encode-composer-draft.js +34 -0
  29. package/client/encode-files.d.ts +18 -0
  30. package/client/encode-files.js +54 -0
  31. package/client/failure.d.ts +29 -0
  32. package/client/failure.js +34 -0
  33. package/client/generation-selection.d.ts +15 -0
  34. package/client/generation-selection.js +24 -0
  35. package/client/host-call.d.ts +51 -0
  36. package/client/host-call.js +47 -0
  37. package/client/host-transport.d.ts +70 -0
  38. package/client/host-transport.js +75 -0
  39. package/client/image-output.d.ts +6 -0
  40. package/client/image-output.js +13 -0
  41. package/client/index.d.ts +28 -0
  42. package/client/index.js +22 -0
  43. package/client/ink-waveform.d.ts +17 -0
  44. package/client/ink-waveform.js +68 -0
  45. package/client/live/apply-live-turn-tool-event.d.ts +14 -0
  46. package/client/live/apply-live-turn-tool-event.js +39 -0
  47. package/client/live/live-captions.d.ts +53 -0
  48. package/client/live/live-captions.js +174 -0
  49. package/client/live/live-mic-forward.d.ts +30 -0
  50. package/client/live/live-mic-forward.js +46 -0
  51. package/client/live/live-page-tool.d.ts +34 -0
  52. package/client/live/live-page-tool.js +11 -0
  53. package/client/live/live-state.d.ts +13 -0
  54. package/client/live/live-state.js +22 -0
  55. package/client/live/live-tool.d.ts +11 -0
  56. package/client/live/live-tool.js +1 -0
  57. package/client/live/live-video.d.ts +10 -0
  58. package/client/live/live-video.js +71 -0
  59. package/client/live/run-live-tool-call.d.ts +22 -0
  60. package/client/live/run-live-tool-call.js +70 -0
  61. package/client/live/use-live-caption-log.d.ts +17 -0
  62. package/client/live/use-live-caption-log.js +51 -0
  63. package/client/live-client.d.ts +189 -0
  64. package/client/live-client.js +829 -0
  65. package/client/live-errors.d.ts +2 -0
  66. package/client/live-errors.js +9 -0
  67. package/client/live-messages.d.ts +148 -0
  68. package/client/live-messages.js +100 -0
  69. package/client/mic-capture.d.ts +4 -0
  70. package/client/mic-capture.js +6 -0
  71. package/client/oauth-popup.d.ts +26 -0
  72. package/client/oauth-popup.js +30 -0
  73. package/client/page-dispatch.d.ts +15 -0
  74. package/client/page-dispatch.js +63 -0
  75. package/client/pcm-downsample.d.ts +4 -0
  76. package/client/pcm-downsample.js +45 -0
  77. package/client/run-commit.d.ts +34 -0
  78. package/client/run-commit.js +49 -0
  79. package/client/run-session.d.ts +79 -0
  80. package/client/run-session.js +207 -0
  81. package/client/schema-fields.d.ts +63 -0
  82. package/client/schema-fields.js +199 -0
  83. package/client/shaped-data.d.ts +118 -0
  84. package/client/shaped-data.js +343 -0
  85. package/client/source-citations.d.ts +13 -0
  86. package/client/source-citations.js +56 -0
  87. package/client/space-geometry.d.ts +39 -0
  88. package/client/space-geometry.js +183 -0
  89. package/client/state-fields.d.ts +16 -0
  90. package/client/state-fields.js +44 -0
  91. package/client/tool-result.d.ts +78 -0
  92. package/client/tool-result.js +293 -0
  93. package/client/tool-resume.d.ts +50 -0
  94. package/client/tool-resume.js +14 -0
  95. package/client/trace-feed.d.ts +13 -0
  96. package/client/trace-feed.js +18 -0
  97. package/client/trace-story.d.ts +163 -0
  98. package/client/trace-story.js +440 -0
  99. package/client/trace-view.d.ts +130 -0
  100. package/client/trace-view.js +333 -0
  101. package/client/transcript-groups.d.ts +140 -0
  102. package/client/transcript-groups.js +217 -0
  103. package/client/transcript-schema.d.ts +10 -0
  104. package/client/transcript-schema.js +120 -0
  105. package/client/transcript-scroll.d.ts +7 -0
  106. package/client/transcript-scroll.js +6 -0
  107. package/client/transport.d.ts +264 -0
  108. package/client/transport.js +330 -0
  109. package/client/turn-client.d.ts +44 -0
  110. package/client/turn-client.js +88 -0
  111. package/client/voice-label.d.ts +2 -0
  112. package/client/voice-label.js +15 -0
  113. package/client/voice-recorder.d.ts +27 -0
  114. package/client/voice-recorder.js +162 -0
  115. package/client/wire-line.d.ts +56 -0
  116. package/client/wire-line.js +90 -0
  117. package/client/worklets/mic-capture.js +16 -0
  118. package/components/InkWaveform.d.ts +21 -0
  119. package/components/InkWaveform.js +131 -0
  120. package/components/live/use-live-runner-controls.d.ts +47 -0
  121. package/components/live/use-live-runner-controls.js +96 -0
  122. package/components/live/use-live-runner-model.d.ts +49 -0
  123. package/components/live/use-live-runner-model.js +190 -0
  124. package/components/live/use-live-runner-ui.d.ts +67 -0
  125. package/components/live/use-live-runner-ui.js +167 -0
  126. package/components/live/use-live-session-client.d.ts +41 -0
  127. package/components/live/use-live-session-client.js +126 -0
  128. package/components/use-composer-voice.d.ts +28 -0
  129. package/components/use-composer-voice.js +90 -0
  130. package/components/use-editor-selection.d.ts +3 -0
  131. package/components/use-editor-selection.js +19 -0
  132. package/components/use-voice-playback.d.ts +17 -0
  133. package/components/use-voice-playback.js +68 -0
  134. package/hooks/use-described.d.ts +12 -0
  135. package/hooks/use-described.js +25 -0
  136. package/hooks/use-embedding-index.d.ts +27 -0
  137. package/hooks/use-embedding-index.js +43 -0
  138. package/hooks/use-one-call.d.ts +31 -0
  139. package/hooks/use-one-call.js +76 -0
  140. package/hooks/use-page-tool-warnings.d.ts +6 -0
  141. package/hooks/use-page-tool-warnings.js +25 -0
  142. package/hooks/use-second-ticker.d.ts +2 -0
  143. package/hooks/use-second-ticker.js +13 -0
  144. package/hooks/use-theorem-chat-actions.d.ts +79 -0
  145. package/hooks/use-theorem-chat-actions.js +287 -0
  146. package/hooks/use-theorem-chat-state.d.ts +66 -0
  147. package/hooks/use-theorem-chat-state.js +122 -0
  148. package/hooks/use-theorem-chat.d.ts +97 -0
  149. package/hooks/use-theorem-chat.js +528 -0
  150. package/hooks/use-theorem-decision.d.ts +18 -0
  151. package/hooks/use-theorem-decision.js +12 -0
  152. package/hooks/use-theorem-embedding.d.ts +18 -0
  153. package/hooks/use-theorem-embedding.js +12 -0
  154. package/hooks/use-theorem-host.d.ts +45 -0
  155. package/hooks/use-theorem-host.js +135 -0
  156. package/hooks/use-theorem-interface.d.ts +22 -0
  157. package/hooks/use-theorem-interface.js +15 -0
  158. package/hooks/use-trace-records.d.ts +4 -0
  159. package/hooks/use-trace-records.js +7 -0
  160. package/index.d.ts +17 -0
  161. package/index.js +17 -0
  162. package/live.d.ts +9 -0
  163. package/live.js +10 -0
  164. package/package.json +74 -4
  165. package/server/checkpoint-ownership.d.ts +6 -0
  166. package/server/checkpoint-ownership.js +42 -0
  167. package/server/conversation-store.d.ts +92 -0
  168. package/server/conversation-store.js +131 -0
  169. package/server/conversation.d.ts +193 -0
  170. package/server/conversation.js +552 -0
  171. package/server/credential-store.d.ts +24 -0
  172. package/server/credential-store.js +14 -0
  173. package/server/decision-handler.d.ts +42 -0
  174. package/server/decision-handler.js +46 -0
  175. package/server/embedding-handler.d.ts +33 -0
  176. package/server/embedding-handler.js +45 -0
  177. package/server/handler.d.ts +152 -0
  178. package/server/handler.js +771 -0
  179. package/server/json-response.d.ts +2 -0
  180. package/server/json-response.js +7 -0
  181. package/server/mod.d.ts +25 -0
  182. package/server/mod.js +23 -0
  183. package/server/page-dispatch.d.ts +22 -0
  184. package/server/page-dispatch.js +127 -0
  185. package/server/request-body.d.ts +2 -0
  186. package/server/request-body.js +42 -0
  187. package/server/request-check.d.ts +21 -0
  188. package/server/request-check.js +49 -0
  189. package/server/session-authority.d.ts +8 -0
  190. package/server/session-authority.js +78 -0
  191. package/server/session-boundary.d.ts +26 -0
  192. package/server/session-boundary.js +64 -0
  193. package/server/session-identity.d.ts +16 -0
  194. package/server/session-identity.js +41 -0
  195. package/server/session-limits.d.ts +12 -0
  196. package/server/session-limits.js +59 -0
  197. package/server/session-store.d.ts +123 -0
  198. package/server/session-store.js +264 -0
  199. package/server/single-action.d.ts +21 -0
  200. package/server/single-action.js +30 -0
  201. package/server/steer-inbox.d.ts +35 -0
  202. package/server/steer-inbox.js +52 -0
  203. package/server/tool-operations.d.ts +6 -0
  204. package/server/tool-operations.js +132 -0
  205. package/server/turn-input.d.ts +10 -0
  206. package/server/turn-input.js +29 -0
  207. package/styles/ink-controls.css +134 -0
  208. package/ui/CardWall.d.ts +10 -0
  209. package/ui/CardWall.js +61 -0
  210. package/ui/ChatComposerBar.d.ts +62 -0
  211. package/ui/ChatComposerBar.js +306 -0
  212. package/ui/ChatTranscript.d.ts +43 -0
  213. package/ui/ChatTranscript.js +527 -0
  214. package/ui/ConsoleFrame.d.ts +104 -0
  215. package/ui/ConsoleFrame.js +146 -0
  216. package/ui/DecisionAnswers.d.ts +53 -0
  217. package/ui/DecisionAnswers.js +233 -0
  218. package/ui/DecisionImages.d.ts +16 -0
  219. package/ui/DecisionImages.js +46 -0
  220. package/ui/EmbeddingDocuments.d.ts +78 -0
  221. package/ui/EmbeddingDocuments.js +278 -0
  222. package/ui/EmbeddingQuery.d.ts +27 -0
  223. package/ui/EmbeddingQuery.js +95 -0
  224. package/ui/EmbeddingResults.d.ts +41 -0
  225. package/ui/EmbeddingResults.js +165 -0
  226. package/ui/EmbeddingSpace.d.ts +26 -0
  227. package/ui/EmbeddingSpace.js +106 -0
  228. package/ui/LiveCaptionsPanel.d.ts +22 -0
  229. package/ui/LiveCaptionsPanel.js +27 -0
  230. package/ui/LiveRunner.d.ts +30 -0
  231. package/ui/LiveRunner.js +180 -0
  232. package/ui/PaneState.d.ts +33 -0
  233. package/ui/PaneState.js +50 -0
  234. package/ui/SchemaFields.d.ts +10 -0
  235. package/ui/SchemaFields.js +150 -0
  236. package/ui/ShapedData.d.ts +11 -0
  237. package/ui/ShapedData.js +196 -0
  238. package/ui/SidePanel.d.ts +126 -0
  239. package/ui/SidePanel.js +123 -0
  240. package/ui/StateFields.d.ts +44 -0
  241. package/ui/StateFields.js +139 -0
  242. package/ui/TheoremChat.d.ts +122 -0
  243. package/ui/TheoremChat.js +134 -0
  244. package/ui/TheoremDecision.d.ts +30 -0
  245. package/ui/TheoremDecision.js +130 -0
  246. package/ui/TheoremEmbedding.d.ts +27 -0
  247. package/ui/TheoremEmbedding.js +122 -0
  248. package/ui/TheoremHost.d.ts +55 -0
  249. package/ui/TheoremHost.js +240 -0
  250. package/ui/ToolGateCard.d.ts +32 -0
  251. package/ui/ToolGateCard.js +107 -0
  252. package/ui/ToolResult.d.ts +10 -0
  253. package/ui/ToolResult.js +62 -0
  254. package/ui/ToolResultCharts.d.ts +5 -0
  255. package/ui/ToolResultCharts.js +103 -0
  256. package/ui/TraceCharts.d.ts +13 -0
  257. package/ui/TraceCharts.js +266 -0
  258. package/ui/TraceGuardrails.d.ts +11 -0
  259. package/ui/TraceGuardrails.js +74 -0
  260. package/ui/TraceInspector.d.ts +16 -0
  261. package/ui/TraceInspector.js +206 -0
  262. package/ui/TraceInspectorPanel.d.ts +46 -0
  263. package/ui/TraceInspectorPanel.js +95 -0
  264. package/ui/TraceOverview.d.ts +38 -0
  265. package/ui/TraceOverview.js +126 -0
  266. package/ui/TraceSpanDetail.d.ts +10 -0
  267. package/ui/TraceSpanDetail.js +190 -0
  268. package/ui/TraceStory.d.ts +20 -0
  269. package/ui/TraceStory.js +108 -0
  270. package/ui/TraceValues.d.ts +36 -0
  271. package/ui/TraceValues.js +123 -0
  272. package/ui/TraceWaterfall.d.ts +13 -0
  273. package/ui/TraceWaterfall.js +135 -0
  274. package/ui/VoiceNote.d.ts +12 -0
  275. package/ui/VoiceNote.js +28 -0
  276. package/ui/approval-heading.d.ts +4 -0
  277. package/ui/approval-heading.js +9 -0
  278. package/ui/arrive.d.ts +18 -0
  279. package/ui/arrive.js +46 -0
  280. package/ui/built/theme.css +792 -0
  281. package/ui/built/theme.css.d.ts +9 -0
  282. package/ui/built/theorem.d.ts +13 -0
  283. package/ui/built/theorem.js +660 -0
  284. package/ui/built/theorem.variants.d.ts +22 -0
  285. package/ui/code-language.d.ts +12 -0
  286. package/ui/code-language.js +50 -0
  287. package/ui/disclosure-motion.d.ts +6 -0
  288. package/ui/disclosure-motion.js +207 -0
  289. package/ui/embedding-space-gl.d.ts +37 -0
  290. package/ui/embedding-space-gl.js +663 -0
  291. package/ui/embedding-space-view.d.ts +8 -0
  292. package/ui/embedding-space-view.js +8 -0
  293. package/ui/glide.d.ts +7 -0
  294. package/ui/glide.js +44 -0
  295. package/ui/icons.d.ts +4 -0
  296. package/ui/icons.js +38 -0
  297. package/ui/index.d.ts +36 -0
  298. package/ui/index.js +39 -0
  299. package/ui/labels-provider.d.ts +16 -0
  300. package/ui/labels-provider.js +64 -0
  301. package/ui/labels.d.ts +1442 -0
  302. package/ui/labels.js +1443 -0
  303. package/ui/markdown-plugins.d.ts +9 -0
  304. package/ui/markdown-plugins.js +13 -0
  305. package/ui/mcp-icon.d.ts +6 -0
  306. package/ui/mcp-icon.js +8 -0
  307. package/ui/row-keys.d.ts +19 -0
  308. package/ui/row-keys.js +33 -0
  309. package/ui/theme.d.ts +10 -0
  310. package/ui/theme.js +13 -0
  311. package/ui/theorem-theme.d.ts +10 -0
  312. package/ui/theorem-theme.js +105 -0
  313. package/ui/trace-palette.d.ts +10 -0
  314. package/ui/trace-palette.js +10 -0
  315. package/ui/trace-patterns.d.ts +40 -0
  316. package/ui/trace-patterns.js +57 -0
  317. package/ui/transcript-copy-text.d.ts +4 -0
  318. package/ui/transcript-copy-text.js +40 -0
  319. package/ui/wall-columns.d.ts +8 -0
  320. package/ui/wall-columns.js +19 -0
@@ -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
+ }
@@ -0,0 +1,30 @@
1
+ import type { DefinedTheme } from '@astryxdesign/core/theme';
2
+ import type { LiveProfileInterface } from '@theoremjs/agents/interface';
3
+ import type { LiveCallOptions } from '../client/live/live-page-tool.ts';
4
+ import type { LiveConnection } from '../client/live-client.ts';
5
+ import { type TheoremLabels } from './labels.ts';
6
+ export type LiveRunnerProps = {
7
+ iface: LiveProfileInterface;
8
+ /** What the call opens: a profile the host registered, or an open message for the relay. */
9
+ connection: () => LiveConnection | Promise<LiveConnection>;
10
+ /** Astryx theme, as on `TheoremChat`. Omit to inherit the host's `<Theme>` or use `theoremTheme`. */
11
+ theme?: DefinedTheme;
12
+ mode?: 'system' | 'light' | 'dark';
13
+ /** Replacement lines by locale, as on `TheoremChat`. */
14
+ labels?: TheoremLabels;
15
+ /**
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.
20
+ */
21
+ trace?: boolean;
22
+ } & LiveCallOptions;
23
+ /**
24
+ * Live (voice / video) runner on Astryx. Before a call: a landing like the
25
+ * chat's, offering a voice or video call. In a call: the agent tag and state
26
+ * over the ink waveform, captions in a right-hand chat column, call controls
27
+ * in a toolbar, and tool gates in a dialog. Offers the trace inspector when
28
+ * the profile records traces.
29
+ */
30
+ export declare function LiveRunner({ theme, mode, labels, ...props }: LiveRunnerProps): import("react").JSX.Element;
@@ -0,0 +1,180 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { AspectRatio } from '@astryxdesign/core/AspectRatio';
3
+ import { Banner } from '@astryxdesign/core/Banner';
4
+ import { Button } from '@astryxdesign/core/Button';
5
+ import { Center } from '@astryxdesign/core/Center';
6
+ import { Dialog } from '@astryxdesign/core/Dialog';
7
+ import { HStack } from '@astryxdesign/core/HStack';
8
+ import { Icon } from '@astryxdesign/core/Icon';
9
+ import { IconButton } from '@astryxdesign/core/IconButton';
10
+ import { Layout, LayoutContent } from '@astryxdesign/core/Layout';
11
+ import { StackItem } from '@astryxdesign/core/Stack';
12
+ import { Text } from '@astryxdesign/core/Text';
13
+ import { ToggleButton } from '@astryxdesign/core/ToggleButton';
14
+ import { Toolbar } from '@astryxdesign/core/Toolbar';
15
+ import { VStack } from '@astryxdesign/core/VStack';
16
+ import { IconCameraRotate, IconMessage, IconMicrophone, IconMicrophoneOff, IconPhone, IconPhoneOff, IconVideo, IconVideoOff, } from '@tabler/icons-react';
17
+ import { Fragment, useEffect, useLayoutEffect, useRef, } from 'react';
18
+ import { InkWaveform } from "../components/InkWaveform.js";
19
+ import { useLiveRunnerModel } from "../components/live/use-live-runner-model.js";
20
+ import { LiveCaptionsPanel } from "./LiveCaptionsPanel.js";
21
+ import { liveStateLabel } from "./labels.js";
22
+ import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
23
+ import { PANEL_BOTTOM, PANEL_EDGE, SidePanel, SidePanelHeader, SidePanelToggle, useSidePanel, } from "./SidePanel.js";
24
+ import { DEFAULT_CHAT_MAX_WIDTH } from "./TheoremChat.js";
25
+ import { ApprovalCard, AuthChallengeCard, GATE_CARD_WIDTH } from "./ToolGateCard.js";
26
+ import { useTraceInspector, WithTrace } from "./TraceInspectorPanel.js";
27
+ import { TheoremThemeProvider } from "./theme.js";
28
+ /** The waveform's box: it rests on the stage floor rather than filling the stage. */
29
+ const WAVE_MAX_WIDTH = 720;
30
+ const WAVE_HEIGHT = 320;
31
+ /**
32
+ * Live (voice / video) runner on Astryx. Before a call: a landing like the
33
+ * chat's, offering a voice or video call. In a call: the agent tag and state
34
+ * over the ink waveform, captions in a right-hand chat column, call controls
35
+ * in a toolbar, and tool gates in a dialog. Offers the trace inspector when
36
+ * the profile records traces.
37
+ */
38
+ export function LiveRunner({ theme, mode, labels, ...props }) {
39
+ return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(LiveRunnerBody, { ...props }) }) }));
40
+ }
41
+ function LiveRunnerBody({ iface, connection, trace, slots, context, pageTools, }) {
42
+ const t = useLabels();
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.
45
+ const layoutRef = useRef(null);
46
+ const inspector = useTraceInspector(iface, model.traces, trace);
47
+ const captions = useSidePanel(layoutRef, true);
48
+ const captionLabels = {
49
+ name: t('@theorem.panel.captions.name'),
50
+ show: t('@theorem.panel.captions.show'),
51
+ hide: t('@theorem.panel.captions.hide'),
52
+ resize: t('@theorem.panel.captions.resize'),
53
+ };
54
+ const captionsToggle = (_jsx(SidePanelToggle, { labels: captionLabels, icon: _jsx(Icon, { icon: IconMessage }), panelId: captions.id, open: captions.open, onToggle: captions.toggle }));
55
+ if (!model.callStarted) {
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 }) }) }) }));
57
+ }
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 })] }));
59
+ }
60
+ /**
61
+ * Before a call: the chat landing's greeting, with call buttons where the
62
+ * composer would be. Video starts camera and mic together.
63
+ */
64
+ function LiveLanding({ model }) {
65
+ const t = useLabels();
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
+ }
70
+ /**
71
+ * The agent tag and state over the resting waveform, then the call controls,
72
+ * all in one column the waveform's width.
73
+ */
74
+ function LiveStage({ model, captionsToggle }) {
75
+ const t = useLabels();
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", 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
+ }
79
+ /**
80
+ * Eases the call view in from the landing: Astryx has no enter-transition
81
+ * helper, so this animates with its `--duration-medium` / `--ease-standard`.
82
+ */
83
+ function useEnterMotion() {
84
+ const ref = useRef(null);
85
+ useLayoutEffect(() => {
86
+ const el = ref.current;
87
+ if (!el || globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches)
88
+ return;
89
+ const tokens = getComputedStyle(el);
90
+ const duration = Number.parseFloat(tokens.getPropertyValue('--duration-medium')) || 410;
91
+ el.animate([
92
+ { opacity: 0, transform: 'translateY(8px)' },
93
+ { opacity: 1, transform: 'none' },
94
+ ], { duration, easing: tokens.getPropertyValue('--ease-standard').trim() || 'ease-out' });
95
+ }, []);
96
+ return ref;
97
+ }
98
+ /**
99
+ * Mic, camera, captions, and call controls, centred under the stage. Icons show
100
+ * what a click does, as End call does: a slash means "turn this off".
101
+ */
102
+ function LiveControls({ model, captionsToggle }) {
103
+ const t = useLabels();
104
+ const inactive = !model.sessionActive;
105
+ const controls = [];
106
+ if (model.voiceAvailable) {
107
+ controls.push(_jsx(ToggleButton, { label: t('@theorem.live.microphone'), tooltip: t(model.isMuted ? '@theorem.live.unmute' : '@theorem.live.mute'), isIconOnly: true, isPressed: !model.isMuted, isDisabled: inactive, icon: _jsx(Icon, { icon: IconMicrophone }), pressedIcon: _jsx(Icon, { icon: IconMicrophoneOff }), onPressedChange: model.handleToggleMic }, "mic"));
108
+ }
109
+ if (model.videoAvailable) {
110
+ controls.push(_jsx(ToggleButton, { label: t('@theorem.live.camera'), tooltip: t(model.isVideoOn ? '@theorem.live.camera_off' : '@theorem.live.camera_on'), isIconOnly: true, isPressed: model.isVideoOn, isDisabled: inactive, icon: _jsx(Icon, { icon: IconVideo }), pressedIcon: _jsx(Icon, { icon: IconVideoOff }), onPressedChange: () => {
111
+ void model.handleToggleVideo();
112
+ } }, "video"));
113
+ if (model.isVideoOn) {
114
+ controls.push(_jsx(IconButton, { label: t('@theorem.live.flip_camera'), tooltip: t('@theorem.live.flip_camera'), variant: "ghost", isDisabled: inactive, icon: _jsx(Icon, { icon: IconCameraRotate }), onClick: () => {
115
+ void model.handleFlipCamera();
116
+ } }, "flip"));
117
+ }
118
+ }
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
+ void model.handleRestart();
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
+ return (_jsx(Toolbar, { label: t('@theorem.live.controls'), dividers: ['top'], centerContent: _jsx(HStack, { gap: 2, vAlign: "center", children: controls }) }));
123
+ }
124
+ /**
125
+ * Captions drawn by the chat's own transcript, with the text composer in its
126
+ * dock when the profile takes text.
127
+ */
128
+ function LiveCaptions({ model }) {
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 }));
130
+ }
131
+ /**
132
+ * The camera preview, at the top of the captions panel. The capture element comes
133
+ * from the session client, so it is mounted imperatively; it sits one level
134
+ * below AspectRatio, so it carries its own fill and mirror styles.
135
+ */
136
+ function LiveVideoPreview({ video, facingMode, }) {
137
+ const t = useLabels();
138
+ const hostRef = useRef(null);
139
+ useEffect(() => {
140
+ const host = hostRef.current;
141
+ if (!host)
142
+ return;
143
+ Object.assign(video.style, {
144
+ width: '100%',
145
+ height: '100%',
146
+ objectFit: 'cover',
147
+ display: 'block',
148
+ });
149
+ host.replaceChildren(video);
150
+ return () => {
151
+ if (video.parentElement === host)
152
+ host.removeChild(video);
153
+ };
154
+ }, [video]);
155
+ useEffect(() => {
156
+ video.style.transform = facingMode === 'user' ? 'scaleX(-1)' : '';
157
+ }, [video, facingMode]);
158
+ return (_jsx(AspectRatio, { ratio: 4 / 3, fit: "cover", children: _jsx("div", { ref: hostRef, "aria-label": t('@theorem.live.camera_preview'), role: "img" }) }));
159
+ }
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.
172
+ onOpenChange: (open) => {
173
+ if (!open)
174
+ onResolve({ action: 'deny' });
175
+ }, children: prompt ? (prompt.gate.kind === 'auth' ? (_jsx(AuthChallengeCard, { gate: prompt.gate, toolName: prompt.gate.tool, onAuthenticated: (secret) => {
176
+ onResolve({ action: 'auth', secret });
177
+ } })) : (_jsx(ApprovalCard, { gate: prompt.gate, toolName: prompt.gate.tool, agent: agent, input: prompt.input, onDecision: (action) => {
178
+ onResolve({ action });
179
+ } }))) : null }));
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
+ }
@@ -0,0 +1,10 @@
1
+ import { type JsonSchema } from '../client/schema-fields.ts';
2
+ import { type Edit as StateEdit } from './StateFields.tsx';
3
+ /**
4
+ * A tool's request as a form drawn from its input schema: each field the
5
+ * input its type asks for, labelled by its title or key and described by its
6
+ * description. Optional fields left empty aren't sent.
7
+ */
8
+ export declare function SchemaFields({ schema, value, onChange, }: StateEdit<Record<string, unknown>> & {
9
+ schema: JsonSchema;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,150 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CheckboxList, CheckboxListItem } from '@astryxdesign/core/CheckboxList';
3
+ import { DateInput } from '@astryxdesign/core/DateInput';
4
+ import { Grid } from '@astryxdesign/core/Grid';
5
+ import { HStack } from '@astryxdesign/core/HStack';
6
+ import { NumberInput } from '@astryxdesign/core/NumberInput';
7
+ import { SegmentedControl, SegmentedControlItem } from '@astryxdesign/core/SegmentedControl';
8
+ import { Selector } from '@astryxdesign/core/Selector';
9
+ import { Slider } from '@astryxdesign/core/Slider';
10
+ import { StackItem } from '@astryxdesign/core/Stack';
11
+ import { Switch } from '@astryxdesign/core/Switch';
12
+ import { Text } from '@astryxdesign/core/Text';
13
+ import { TextArea } from '@astryxdesign/core/TextArea';
14
+ import { TextInput } from '@astryxdesign/core/TextInput';
15
+ import { Tokenizer } from '@astryxdesign/core/Tokenizer';
16
+ import { VStack } from '@astryxdesign/core/VStack';
17
+ import { useState } from 'react';
18
+ import { blankRow, schemaFields, withField, } from "../client/schema-fields.js";
19
+ import { isRow, json } from "../client/shaped-data.js";
20
+ import { useLabels } from "./labels-provider.js";
21
+ import { useKeyedRows } from "./row-keys.js";
22
+ import { AddRow, Group, Labelled, RemoveRow } from "./StateFields.js";
23
+ const NO_SUGGESTIONS = { search: () => [], bootstrap: () => [] };
24
+ const CODE_FONT = { '--font-family-body': 'var(--font-family-code)' };
25
+ /** Lines a description shows before it is cut short. */
26
+ const DESCRIPTION_LINES = 2;
27
+ /** What a field is for, cut to a couple of lines. Hovering a cut one shows all of it. */
28
+ function Description({ text }) {
29
+ return (_jsx(Text, { type: "supporting", color: "secondary", maxLines: DESCRIPTION_LINES, children: text }));
30
+ }
31
+ /** A field whose input carries no label of its own, labelled as one. */
32
+ function Described({ field, children }) {
33
+ return (_jsx(Labelled, { label: field.label, isRequired: field.isRequired, children: children }));
34
+ }
35
+ /** A field the schema doesn't shape (no type, a union, a free object), as its JSON. */
36
+ function JsonField({ field, value, onChange }) {
37
+ const t = useLabels();
38
+ const [text, setText] = useState(() => (value === undefined ? '' : json(value)));
39
+ const [isValid, setValid] = useState(true);
40
+ return (_jsx(TextArea, { label: field.label, isRequired: field.isRequired, size: "sm", rows: 3, value: text, hasSpellCheck: false, status: isValid ? undefined : { type: 'error', message: t('@theorem.decision.invalid_json') }, controlStyle: CODE_FONT, onChange: (next) => {
41
+ setText(next);
42
+ if (next.trim() === '') {
43
+ setValid(true);
44
+ onChange(undefined);
45
+ return;
46
+ }
47
+ try {
48
+ onChange(JSON.parse(next));
49
+ setValid(true);
50
+ }
51
+ catch {
52
+ setValid(false);
53
+ }
54
+ } }));
55
+ }
56
+ /** A list of objects: each row its own fields, removable, and one more at the end. */
57
+ function RowsField({ field, item, value, onChange, }) {
58
+ const rows = Array.isArray(value) ? value : [];
59
+ const keyed = useKeyedRows(rows);
60
+ return (_jsx(Group, { label: field.label, isRequired: field.isRequired, description: field.description && _jsx(Description, { text: field.description }), children: _jsxs(VStack, { gap: 3, children: [keyed.rows.map(({ item: row, index, key }) => (_jsxs(HStack, { gap: 2, vAlign: "start", children: [_jsx(StackItem, { size: "fill", children: _jsx(ObjectFields, { schema: item, value: isRow(row) ? row : {}, onChange: (next) => onChange(rows.map((old, at) => (at === index ? next : old))) }) }), _jsx(RemoveRow, { index: index, onRemove: () => {
61
+ keyed.drop(index);
62
+ onChange(rows.filter((_, at) => at !== index));
63
+ } })] }, key))), _jsx(AddRow, { field: field.label, onAdd: () => onChange([...rows, blankRow(item)]) })] }) }));
64
+ }
65
+ function common(field) {
66
+ return {
67
+ label: field.label,
68
+ isRequired: field.isRequired,
69
+ size: 'sm',
70
+ };
71
+ }
72
+ /** One of a few options as segments; more as a menu. */
73
+ function ChoiceField({ field, options, isMenu, value, onChange, }) {
74
+ if (isMenu) {
75
+ return (_jsx(Selector, { ...common(field), options: options, value: typeof value === 'string' ? value : undefined, hasSearch: options.length > 10, onChange: onChange }));
76
+ }
77
+ return (_jsx(Described, { field: field, children: _jsx(HStack, { children: _jsx(SegmentedControl, { label: field.label, size: "sm", value: typeof value === 'string' ? value : '', onChange: onChange, children: options.map((option) => (_jsx(SegmentedControlItem, { value: option, label: option }, option))) }) }) }));
78
+ }
79
+ function TextField({ field, isLong, type, value, onChange, }) {
80
+ const text = typeof value === 'string' ? value : '';
81
+ return isLong ? (_jsx(TextArea, { ...common(field), rows: 3, value: text, onChange: onChange })) : (_jsx(TextInput, { ...common(field), type: type, value: text, onChange: onChange }));
82
+ }
83
+ /** A list of words or numbers as tokens. */
84
+ function WordsField({ field, value, onChange }) {
85
+ const t = useLabels();
86
+ const words = Array.isArray(value) ? value : [];
87
+ return (_jsx(Tokenizer, { ...common(field), value: words.map((word, index) => ({
88
+ id: `${String(index)}:${String(word)}`,
89
+ label: String(word),
90
+ })), searchSource: NO_SUGGESTIONS, hasCreate: true, debounceMs: 0, placeholder: t('@theorem.decision.add_value'), onChange: (items) => onChange(items.map((item) => typeof words[0] === 'number' && Number.isFinite(Number(item.label))
91
+ ? Number(item.label)
92
+ : item.label)) }));
93
+ }
94
+ /** Each control's input, as the schema asks for it. */
95
+ const CONTROLS = {
96
+ choice: ({ field, control, value, onChange }) => (_jsx(ChoiceField, { field: field, options: control.options, isMenu: control.isMenu, value: value, onChange: onChange })),
97
+ range: ({ field, control, value, onChange }) => (_jsx(Slider, { label: field.label, isRequired: field.isRequired, min: control.min, max: control.max, step: 1, value: typeof value === 'number' ? value : control.min, valueDisplay: "text", onChange: onChange })),
98
+ number: ({ field, control, value, onChange }) => (_jsx(NumberInput, { ...common(field), value: typeof value === 'number' ? value : undefined, min: control.min, max: control.max, isIntegerOnly: control.isInteger, isWheelEnabled: false, onChange: (next) => onChange(next ?? undefined) })),
99
+ switch: ({ field, value, onChange }) => (_jsx(Switch, { label: field.label, value: value === true, onChange: onChange })),
100
+ date: ({ field, value, onChange }) => (_jsx(DateInput, { ...common(field), value: typeof value === 'string' ? value : undefined, onChange: onChange })),
101
+ text: ({ field, control, value, onChange }) => (_jsx(TextField, { field: field, isLong: control.isLong, type: control.type, value: value, onChange: onChange })),
102
+ choices: ({ field, control, value, onChange }) => (_jsx(CheckboxList, { label: field.label, value: Array.isArray(value)
103
+ ? value.filter((entry) => typeof entry === 'string')
104
+ : [], onChange: onChange, children: control.options.map((option) => (_jsx(CheckboxListItem, { label: option, value: option }, option))) })),
105
+ words: ({ field, value, onChange }) => (_jsx(WordsField, { field: field, value: value, onChange: onChange })),
106
+ rows: ({ field, control, value, onChange }) => (_jsx(RowsField, { field: field, item: control.item, value: value, onChange: onChange })),
107
+ group: ({ field, control, value, onChange }) => (_jsx(Group, { label: field.label, isRequired: field.isRequired, description: field.description && _jsx(Description, { text: field.description }), children: _jsx(ObjectFields, { schema: control.schema, value: isRow(value) ? value : {}, onChange: onChange }) })),
108
+ json: ({ field, value, onChange }) => (_jsx(JsonField, { field: field, value: value, onChange: onChange })),
109
+ };
110
+ function Field({ field, value, onChange }) {
111
+ const control = CONTROLS[field.control.kind];
112
+ const input = control({ field, control: field.control, value, onChange });
113
+ // why: A group already says its description under its name.
114
+ const isGroup = field.control.kind === 'rows' || field.control.kind === 'group';
115
+ if (!field.description || isGroup)
116
+ return input;
117
+ return (_jsxs(VStack, { gap: 1, children: [input, _jsx(Description, { text: field.description })] }));
118
+ }
119
+ /** Room a short field takes: numbers, dates, menus, and one-line text sit side by side. */
120
+ const SHORT_FIELD_PX = 200;
121
+ /** Segments whose labels run this long in all still fit a short field's width. */
122
+ const SHORT_SEGMENT_CHARS = 18;
123
+ /** A field that reads in a column's width; the rest take the whole row. */
124
+ function isShort({ control }) {
125
+ switch (control.kind) {
126
+ case 'number':
127
+ case 'switch':
128
+ case 'date':
129
+ return true;
130
+ case 'choice':
131
+ return control.isMenu || control.options.join('').length <= SHORT_SEGMENT_CHARS;
132
+ case 'text':
133
+ return !control.isLong;
134
+ default:
135
+ return false;
136
+ }
137
+ }
138
+ function ObjectFields({ schema, value, onChange, }) {
139
+ return (_jsx(Grid, { columns: { minWidth: SHORT_FIELD_PX, repeat: 'fill' }, gap: 3, children: schemaFields(schema).map((field) => (_jsx("div", { style: isShort(field)
140
+ ? { minWidth: 0, alignSelf: 'start' }
141
+ : { gridColumn: '1 / -1', minWidth: 0 }, children: _jsx(Field, { field: field, value: value[field.key], onChange: (next) => onChange(withField(value, field.key, next)) }) }, field.key))) }));
142
+ }
143
+ /**
144
+ * A tool's request as a form drawn from its input schema: each field the
145
+ * input its type asks for, labelled by its title or key and described by its
146
+ * description. Optional fields left empty aren't sent.
147
+ */
148
+ export function SchemaFields({ schema, value, onChange, }) {
149
+ return _jsx(ObjectFields, { schema: schema, value: value, onChange: onChange });
150
+ }
@@ -0,0 +1,11 @@
1
+ import { type ReactNode } from 'react';
2
+ export type ShapedDataProps = {
3
+ value: unknown;
4
+ title?: string;
5
+ /** Drawn above the data in its data view (a tool result's figures and charts); a render that throws falls back to the JSON with it. */
6
+ lead?: ReactNode;
7
+ /** Top-level fields the lead already shows: the data view leaves them out, the JSON keeps them. */
8
+ ledKeys?: readonly string[];
9
+ };
10
+ /** A tool call's input, output or failure, or a structured reply, with a switch to its JSON. */
11
+ export declare function ShapedData({ value: raw, title, lead, ledKeys }: ShapedDataProps): import("react").JSX.Element;