@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
@@ -1,16 +1,17 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Banner } from '@astryxdesign/core/Banner';
3
- import { Spinner } from '@astryxdesign/core/Spinner';
4
- import { useLocale } from '@astryxdesign/core/i18n';
5
- import { useMemo, useState } from 'react';
3
+ import { useMemo, useRef, useState } from 'react';
4
+ import { droppedImages, namedImages, stateFields, } from "../client/decision-images.js";
6
5
  import { createDecisionTransport, } from "../client/decision-transport.js";
7
6
  import { useTheoremDecision } from "../hooks/use-theorem-decision.js";
7
+ import { ConsoleFrame, JsonRequest, RequestCard, ResponseColumn, RunBar, useBytes, } from "./ConsoleFrame.js";
8
8
  import { TheoremDecisionAnswers } from "./DecisionAnswers.js";
9
- import { ConsoleFrame, JsonRequest, RequestCard, RunBar } from "./ConsoleFrame.js";
9
+ import { ImageDrop } from "./DecisionImages.js";
10
10
  import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
11
- import { StateFields } from "./StateFields.js";
12
- import { TheoremThemeProvider } from "./theme.js";
11
+ import { PaneFailure, PaneLoading } from "./PaneState.js";
12
+ import { StateFields, StateImages } from "./StateFields.js";
13
13
  import { useTraceInspector } from "./TraceInspectorPanel.js";
14
+ import { TheoremThemeProvider } from "./theme.js";
14
15
  /** The field's text as the state a decision sends, measured the way the kernel measures it. */
15
16
  function checkState(text, maxBytes) {
16
17
  let state;
@@ -27,48 +28,90 @@ function checkState(text, maxBytes) {
27
28
  return { ok: false, reason: 'too_large', bytes, state };
28
29
  return { ok: true, state, bytes };
29
30
  }
30
- function useBytes() {
31
- const locale = useLocale();
32
- return useMemo(() => {
33
- const b = new Intl.NumberFormat(locale, { style: 'unit', unit: 'byte', unitDisplay: 'narrow' });
34
- const kb = new Intl.NumberFormat(locale, { style: 'unit', unit: 'kilobyte', unitDisplay: 'narrow', maximumFractionDigits: 1 });
35
- return (bytes) => (bytes < 1024 ? b.format(bytes) : kb.format(bytes / 1024));
36
- }, [locale]);
37
- }
38
31
  function stateNote(t, check, maxBytes, bytes) {
39
32
  if (!check.ok) {
40
33
  if (check.reason === 'too_large')
41
34
  return t('@theorem.decision.too_large', { limit: bytes(maxBytes ?? 0) });
42
- return t(check.reason === 'invalid_json' ? '@theorem.decision.invalid_json' : '@theorem.decision.null_state');
35
+ return t(check.reason === 'invalid_json'
36
+ ? '@theorem.decision.invalid_json'
37
+ : '@theorem.decision.null_state');
43
38
  }
44
- return maxBytes === undefined ? bytes(check.bytes) : t('@theorem.decision.state_size', { size: bytes(check.bytes), limit: bytes(maxBytes) });
39
+ return maxBytes === undefined
40
+ ? null
41
+ : t('@theorem.decision.state_size', { size: bytes(check.bytes), limit: bytes(maxBytes) });
45
42
  }
46
- function DecisionBody({ transport, iface, decision, defaultState = '{}', maxWidth = '960px', trace, className, style }) {
47
- const t = useLabels();
48
- const bytes = useBytes();
43
+ /** Where a decision stands: nothing asked, asked with nothing back yet, or answered or failed. */
44
+ function decisionPhase(decision) {
45
+ if (decision.failure || decision.result)
46
+ return 'settled';
47
+ return decision.status === 'deciding' ? 'waiting' : 'idle';
48
+ }
49
+ /** The state a decision is asked about, held as text, and the view it is edited in. */
50
+ function useDecisionState(defaultState, maxStateBytes) {
49
51
  const [text, setText] = useState(defaultState);
50
- const check = checkState(text, iface.maxStateBytes);
51
- const note = stateNote(t, check, iface.maxStateBytes, bytes);
52
- // Fields while the text parses; JSON to change the shape, or to fix it.
53
- const [view, setView] = useState(() => (check.state === undefined ? 'json' : 'fields'));
52
+ const check = checkState(text, maxStateBytes);
53
+ const [view, setView] = useState(() => check.state === undefined ? 'json' : 'fields');
54
54
  const shown = check.state === undefined ? 'json' : view;
55
- const inspector = useTraceInspector(iface, transport.traces, trace);
55
+ return { text, setText, check, shown, setView };
56
+ }
57
+ /** The state, as fields or JSON, and Decide. */
58
+ function DecisionRequest({ form, iface, images, onImages, note, isRunning, onRun, onStop, }) {
59
+ const t = useLabels();
60
+ const { text, setText, check, shown, setView } = form;
61
+ const withImages = iface.questions.some((question) => question.type === 'box') || Object.keys(images).length > 0;
62
+ const fields = shown === 'fields' ? check.state : undefined;
63
+ const named = stateFields(fields);
64
+ return (_jsxs(RequestCard, { title: t('@theorem.decision.state'), view: shown, hasFields: check.state !== undefined, onView: setView, onRun: onRun, children: [fields !== undefined ? (_jsx(StateImages, { value: { images, onImages }, children: _jsx(StateFields, { label: t('@theorem.decision.state'), value: fields, onChange: (next) => {
65
+ setText(JSON.stringify(next, null, 2));
66
+ } }) })) : (_jsx(JsonRequest, { label: t('@theorem.decision.state'), text: text, isInvalid: !check.ok, onChange: setText })), withImages && named ? (_jsx(ImageDrop, { onImages: (dropped) => {
67
+ const next = droppedImages(dropped, { images, host: iface.images, state: named });
68
+ onImages(next.images);
69
+ setText(JSON.stringify(next.state, null, 2));
70
+ } })) : null, _jsx(RunBar, { note: note, isReady: check.ok, isRunning: isRunning, runLabel: t('@theorem.decision.decide'), runningLabel: t('@theorem.decision.deciding'), stopLabel: t('@theorem.decision.stop'), onRun: onRun, onStop: onStop })] }));
71
+ }
72
+ /** What came back: the failure, the answers, or a spinner until the first of either. */
73
+ function DecisionAnswered({ iface, decision, asked, onQuestion, questionText, }) {
74
+ const t = useLabels();
56
75
  const deciding = decision.status === 'deciding';
76
+ return (_jsxs(_Fragment, { children: [decision.failure ? (_jsx(Banner, { status: "error", role: "alert", title: decision.failure.error })) : null, deciding && !decision.result ? (_jsx(PaneLoading, { label: t('@theorem.decision.deciding'), since: decision.startedAt })) : null, decision.result ? (_jsx(TheoremDecisionAnswers, { iface: iface, result: decision.result, asked: asked, onQuestion: onQuestion, questionText: questionText, elapsedMs: decision.elapsedMs, isStale: deciding })) : null] }));
77
+ }
78
+ /**
79
+ * The frame's second child. A call, not a component: the frame counts its
80
+ * children to choose a layout, and no response yet has to count as none.
81
+ */
82
+ function decisionResponse(columns, phase, labels, since, answers) {
83
+ if (phase === 'idle')
84
+ return null;
85
+ if (!columns)
86
+ return answers;
87
+ const waiting = phase === 'waiting';
88
+ return (_jsx(ResponseColumn, { label: labels.answers, fill: waiting, children: waiting ? _jsx(PaneLoading, { label: labels.deciding, since: since }) : answers }));
89
+ }
90
+ function DecisionBody({ transport, iface, decision, defaultState = '{}', defaultImages, onQuestion, questionText, maxWidth = '960px', trace, flush, columns = false, className, style, }) {
91
+ const t = useLabels();
92
+ const bytes = useBytes();
93
+ const form = useDecisionState(defaultState, iface.maxStateBytes);
94
+ const { check } = form;
95
+ const [images, setImages] = useState(defaultImages ?? {});
96
+ /** The images and state the answers were asked about, kept with them: later edits don't redraw them. */
97
+ const [asked, setAsked] = useState();
98
+ const inspector = useTraceInspector(iface, transport.traces, trace);
57
99
  const decide = () => {
58
- if (check.ok)
59
- void decision.decide(check.state);
100
+ if (!check.ok)
101
+ return;
102
+ const sent = namedImages(images, check.state);
103
+ setAsked({ images: { ...iface.images, ...sent }, state: check.state });
104
+ void decision.decide(check.state, Object.keys(sent).length > 0 ? sent : undefined);
60
105
  };
61
- return (_jsxs(ConsoleFrame, { inspector: inspector, maxWidth: maxWidth, className: className, style: style, children: [_jsxs(RequestCard, { title: t('@theorem.decision.state'), view: shown, hasFields: check.state !== undefined, onView: setView, onRun: decide, children: [shown === 'fields' && check.state !== undefined ? (_jsx(StateFields, { label: t('@theorem.decision.state'), value: check.state, onChange: (next) => {
62
- setText(JSON.stringify(next, null, 2));
63
- } })) : (_jsx(JsonRequest, { label: t('@theorem.decision.state'), text: text, isInvalid: !check.ok, onChange: setText })), _jsx(RunBar, { note: note, isReady: check.ok, isRunning: deciding, runLabel: t('@theorem.decision.decide'), runningLabel: t('@theorem.decision.deciding'), stopLabel: t('@theorem.decision.stop'), onRun: decide, onStop: decision.cancel })] }), decision.failure ? _jsx(Banner, { status: "error", title: decision.failure.error }) : null, decision.result ? (_jsx(TheoremDecisionAnswers, { iface: iface, result: decision.result, elapsedMs: decision.elapsedMs, isStale: deciding })) : null] }));
106
+ return (_jsxs(ConsoleFrame, { inspector: inspector, maxWidth: maxWidth, flush: flush, columns: columns, requestLabel: t('@theorem.decision.state'), className: className, style: style, children: [_jsx(DecisionRequest, { form: form, iface: iface, images: images, onImages: setImages, note: stateNote(t, check, iface.maxStateBytes, bytes), isRunning: decision.status === 'deciding', onRun: decide, onStop: decision.cancel }), decisionResponse(columns, decisionPhase(decision), { answers: t('@theorem.decision.answers'), deciding: t('@theorem.decision.deciding') }, decision.startedAt, _jsx(DecisionAnswered, { iface: iface, decision: decision, asked: asked, onQuestion: onQuestion, questionText: questionText }))] }));
64
107
  }
65
108
  function DecisionForTransport(props) {
66
109
  const t = useLabels();
67
110
  const decision = useTheoremDecision(props.transport);
68
111
  if (decision.describeFailure)
69
- return _jsx(Banner, { status: "error", title: decision.describeFailure.error });
112
+ return _jsx(PaneFailure, { title: decision.describeFailure.error });
70
113
  if (!decision.iface)
71
- return _jsx(Spinner, { size: "lg", label: t('@theorem.chat.loading') });
114
+ return _jsx(PaneLoading, { label: t('@theorem.chat.loading') });
72
115
  return _jsx(DecisionBody, { ...props, iface: decision.iface, decision: decision });
73
116
  }
74
117
  /**
@@ -80,8 +123,8 @@ function DecisionForTransport(props) {
80
123
  * ```
81
124
  */
82
125
  export function TheoremDecision({ endpoint, http, transport, theme, mode, labels, ...rest }) {
83
- const resolved = useMemo(() => transport ?? createDecisionTransport({ ...http, endpoint }),
84
- // eslint-disable-next-line react-hooks/exhaustive-deps -- `http` identity is caller-owned; endpoint drives reconnection.
85
- [transport, endpoint]);
126
+ const httpRef = useRef(http);
127
+ httpRef.current = http;
128
+ const resolved = useMemo(() => transport ?? createDecisionTransport({ ...httpRef.current, endpoint }), [transport, endpoint]);
86
129
  return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(DecisionForTransport, { ...rest, transport: resolved }) }) }));
87
130
  }
@@ -0,0 +1,27 @@
1
+ import { type EmbeddingTransport } from '../client/embedding-transport.ts';
2
+ import { type ConsolePageProps } from './ConsoleFrame.tsx';
3
+ import { type EmbeddingDocument } from './EmbeddingDocuments.tsx';
4
+ export type TheoremEmbeddingProps = ConsolePageProps & {
5
+ /** Where `createTheoremEmbeddingHandler` is mounted. Default `/api/embedding`. Ignored when `transport` is set. */
6
+ endpoint?: string;
7
+ /** Bring your own transport (tests, studios, non-HTTP hosts). */
8
+ transport?: EmbeddingTransport;
9
+ /** The text the documents card starts with, split into paragraphs. Default none. */
10
+ defaultText?: string;
11
+ /** Documents the card starts with beside the text, each with the file it came from. Default none. */
12
+ defaultDocuments?: readonly EmbeddingDocument[];
13
+ /** The query the search starts with. Default none. */
14
+ defaultQuery?: string;
15
+ };
16
+ /**
17
+ * Drop-in search over an embedding profile served by `createTheoremEmbeddingHandler`:
18
+ * paste text and split it, or drop files, and Index embeds them as documents
19
+ * in as many requests as the profile's limits need. Then Search embeds a query
20
+ * and ranks the documents by cosine similarity. Every request is shown as
21
+ * JSON, and in the trace.
22
+ *
23
+ * ```tsx
24
+ * <TheoremEmbedding endpoint="/api/embedding" />
25
+ * ```
26
+ */
27
+ export declare function TheoremEmbedding({ endpoint, http, transport, theme, mode, labels, ...rest }: TheoremEmbeddingProps): import("react").JSX.Element;
@@ -0,0 +1,122 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { VStack } from '@astryxdesign/core/VStack';
3
+ import { useMemo, useRef, useState } from 'react';
4
+ import { rankMatches } from "../client/embedding-input.js";
5
+ import { createEmbeddingTransport, } from "../client/embedding-transport.js";
6
+ import { useEmbeddingIndex } from "../hooks/use-embedding-index.js";
7
+ import { useTheoremEmbedding } from "../hooks/use-theorem-embedding.js";
8
+ import { ConsoleFrame, ConsoleSection, ResponseColumn, } from "./ConsoleFrame.js";
9
+ import { EmbeddingDocuments, useEmbeddingDocuments, } from "./EmbeddingDocuments.js";
10
+ import { EmbeddingQuery, useEmbeddingQuery } from "./EmbeddingQuery.js";
11
+ import { EmbeddingResults, IndexCard } from "./EmbeddingResults.js";
12
+ import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
13
+ import { PaneFailure, PaneLoading } from "./PaneState.js";
14
+ import { useTraceInspector } from "./TraceInspectorPanel.js";
15
+ import { TheoremThemeProvider } from "./theme.js";
16
+ /** The same documents: the same texts, and the very same files. */
17
+ function sameInputs(a, b) {
18
+ return a.length === b.length && a.every((input, at) => input === b[at]);
19
+ }
20
+ /** Where the documents stand for the last query: embedded, changed since, or embedded by another model. */
21
+ function indexStateOf(indexed, queried, inputs) {
22
+ if (!indexed)
23
+ return 'fresh';
24
+ // why: A query from another model than the index's is in another space; no score between them means anything.
25
+ if (queried && queried.model !== indexed.model)
26
+ return 'model';
27
+ return sameInputs(indexed.inputs, inputs) ? 'fresh' : 'stale';
28
+ }
29
+ function indexPhase(index) {
30
+ if (index.status === 'indexing')
31
+ return 'indexing';
32
+ return index.result !== null || index.failure !== null ? 'indexed' : 'idle';
33
+ }
34
+ /**
35
+ * The frame's second child. A call, not a component: the frame counts its
36
+ * children to choose a layout, and nothing indexed yet has to count as none.
37
+ */
38
+ function embeddingResponse(columns, label, phase, results) {
39
+ if (phase === 'idle')
40
+ return null;
41
+ return columns ? (_jsx(ResponseColumn, { label: label, fill: phase === 'indexing', children: results })) : (results);
42
+ }
43
+ /** The spinner while the documents embed, counting the requests once there is more than one. */
44
+ function IndexingPane({ index }) {
45
+ const t = useLabels();
46
+ const { progress } = index;
47
+ return (_jsx(PaneLoading, { label: progress
48
+ ? t('@theorem.embedding.indexing_progress', {
49
+ done: Math.min(progress.done + 1, progress.total),
50
+ total: progress.total,
51
+ })
52
+ : t('@theorem.embedding.indexing'), since: index.startedAt }));
53
+ }
54
+ /** Where the last query stands against the index: whether it searched, and its vector while both share a model. */
55
+ function searchOf(index, search, inputs) {
56
+ const state = indexStateOf(index.result, search.result, inputs);
57
+ if (state === 'model')
58
+ return { state, searched: false, queried: null };
59
+ return {
60
+ state,
61
+ searched: search.status !== 'idle' || search.result !== null,
62
+ queried: search.result?.embeddings[0]?.values ?? null,
63
+ };
64
+ }
65
+ /** The query against the index, and once it ran, what it found. */
66
+ function IndexSearch({ iface, index, search, query, indexed, state, searched, matches, pointing, }) {
67
+ return (_jsxs(ConsoleSection, { children: [_jsx(EmbeddingQuery, { form: query, rules: iface.inputs, index: state, isRunning: search.status === 'embedding', onRun: (input) => {
68
+ void search.embed([input], { task: 'query' });
69
+ }, onStop: search.cancel }), searched ? (_jsx(VStack, { padding: 4, children: _jsx(EmbeddingResults, { index: index, search: search, documents: indexed, matches: matches, pointing: pointing }) })) : null] }));
70
+ }
71
+ /** The index, and once it holds vectors, the search over them. */
72
+ function EmbeddingIndexed({ inputs, ...props }) {
73
+ const { index, indexed } = props;
74
+ const { state, searched, queried } = searchOf(index, props.search, inputs);
75
+ const vectors = index.result?.vectors;
76
+ const matches = useMemo(() => (queried && vectors ? rankMatches(queried, vectors) : []), [queried, vectors]);
77
+ const [hovered, setHovered] = useState(null);
78
+ const pointing = useMemo(() => ({ hovered, onHover: setHovered }), [hovered]);
79
+ return (_jsxs(VStack, { gap: 3, children: [_jsx(IndexCard, { run: index, documents: indexed, query: queried, matches: matches, pointing: pointing }), index.result ? (_jsx(IndexSearch, { ...props, state: state, searched: searched, matches: matches, pointing: pointing })) : null] }));
80
+ }
81
+ function EmbeddingBody({ transport, iface, search, defaultText = '', defaultDocuments, defaultQuery = '', maxWidth = '960px', trace, flush, columns = false, className, style, }) {
82
+ const t = useLabels();
83
+ const documents = useEmbeddingDocuments(defaultText, iface.inputs, defaultDocuments);
84
+ const query = useEmbeddingQuery(defaultQuery, iface.inputs);
85
+ const index = useEmbeddingIndex(transport);
86
+ const [indexed, setIndexed] = useState([]);
87
+ const inspector = useTraceInspector(iface, transport.traces, trace);
88
+ const runIndex = () => {
89
+ if (documents.jsonInvalid || documents.issue || documents.inputs.length === 0)
90
+ return;
91
+ setIndexed(documents.documents);
92
+ void index.index(documents.inputs, iface.inputs);
93
+ };
94
+ const phase = indexPhase(index);
95
+ return (_jsxs(ConsoleFrame, { inspector: inspector, maxWidth: maxWidth, flush: flush, columns: columns, requestLabel: t('@theorem.embedding.documents'), className: className, style: style, children: [_jsx(EmbeddingDocuments, { form: documents, rules: iface.inputs, isRunning: phase === 'indexing', onRun: runIndex, onStop: index.cancel }), embeddingResponse(columns, t('@theorem.embedding.results'), phase, phase === 'indexing' ? (_jsx(IndexingPane, { index: index })) : (_jsx(EmbeddingIndexed, { iface: iface, index: index, search: search, query: query, indexed: indexed, inputs: documents.inputs })))] }));
96
+ }
97
+ function EmbeddingForTransport(props) {
98
+ const t = useLabels();
99
+ const search = useTheoremEmbedding(props.transport);
100
+ if (search.describeFailure)
101
+ return _jsx(PaneFailure, { title: search.describeFailure.error });
102
+ if (!search.iface)
103
+ return _jsx(PaneLoading, { label: t('@theorem.chat.loading') });
104
+ return _jsx(EmbeddingBody, { ...props, iface: search.iface, search: search });
105
+ }
106
+ /**
107
+ * Drop-in search over an embedding profile served by `createTheoremEmbeddingHandler`:
108
+ * paste text and split it, or drop files, and Index embeds them as documents
109
+ * in as many requests as the profile's limits need. Then Search embeds a query
110
+ * and ranks the documents by cosine similarity. Every request is shown as
111
+ * JSON, and in the trace.
112
+ *
113
+ * ```tsx
114
+ * <TheoremEmbedding endpoint="/api/embedding" />
115
+ * ```
116
+ */
117
+ export function TheoremEmbedding({ endpoint, http, transport, theme, mode, labels, ...rest }) {
118
+ const httpRef = useRef(http);
119
+ httpRef.current = http;
120
+ const resolved = useMemo(() => transport ?? createEmbeddingTransport({ ...httpRef.current, endpoint }), [transport, endpoint]);
121
+ return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(EmbeddingForTransport, { ...rest, transport: resolved }) }) }));
122
+ }
@@ -1,6 +1,7 @@
1
1
  import type { DefinedTheme } from '@astryxdesign/core/theme';
2
2
  import { type CSSProperties } from 'react';
3
3
  import { type HostTransport } from '../client/host-transport.ts';
4
+ import type { PageTools } from '../client/live/live-page-tool.ts';
4
5
  import type { HttpOptions } from '../client/transport.ts';
5
6
  import { type TheoremLabels } from './labels.ts';
6
7
  export type TheoremHostProps = {
@@ -8,20 +9,36 @@ export type TheoremHostProps = {
8
9
  endpoint?: string;
9
10
  /** Extra fetch options for the default HTTP transport (auth headers, custom fetch). */
10
11
  http?: HttpOptions;
11
- /** Bring your own transport (tests, playgrounds, non-HTTP hosts). */
12
+ /** Bring your own transport (tests, studios, non-HTTP hosts). */
12
13
  transport?: HostTransport;
14
+ /** why: Page callbacks run only after the server saves a dispatch grant. */
15
+ pageTools?: PageTools;
13
16
  theme?: DefinedTheme;
14
17
  mode?: 'system' | 'light' | 'dark';
15
18
  /** Replacement lines by locale, for any `@theorem.*` or `@astryx.*` key. */
16
19
  labels?: TheoremLabels;
20
+ /**
21
+ * Colour code the reply left untagged: a fence with no language is read and, when it is plainly
22
+ * one of JavaScript, TypeScript, Python, shell, JSON, CSS, HTML, XML, YAML, or Markdown, shown as it.
23
+ * A fence with a language is never changed. Off by default.
24
+ */
25
+ detectCodeLanguage?: boolean;
17
26
  /** Widest the column may grow, as a CSS length. Default `960px`. */
18
27
  maxWidth?: string;
19
28
  /**
20
- * Show the trace in place of the console, from the host's own control; the
21
- * built-in trace toggle then hides. Omit to keep the toggle. Needs a profile
22
- * that records traces.
29
+ * Drive the trace from the host's own control. The built-in toggle hides.
30
+ * The open trace takes the console's place, unless this page is inside
31
+ * `TracePlacement value="panel"`, where it docks. Omit to keep the toggle.
32
+ * Needs a profile that records traces.
23
33
  */
24
34
  trace?: boolean;
35
+ /**
36
+ * The page is already a raised panel. The console sits in it instead of
37
+ * painting the ground it uses on a flat page.
38
+ */
39
+ flush?: boolean;
40
+ /** The request on the left and the response on the right. */
41
+ columns?: boolean;
25
42
  className?: string;
26
43
  style?: CSSProperties;
27
44
  };
@@ -35,4 +52,4 @@ export type TheoremHostProps = {
35
52
  * <TheoremHost endpoint="/api/host" />
36
53
  * ```
37
54
  */
38
- export declare function TheoremHost({ endpoint, http, transport, theme, mode, labels, ...rest }: TheoremHostProps): import("react").JSX.Element;
55
+ export declare function TheoremHost({ endpoint, http, transport, theme, mode, labels, detectCodeLanguage, ...rest }: TheoremHostProps): import("react").JSX.Element;
package/ui/TheoremHost.js CHANGED
@@ -1,29 +1,35 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Banner } from '@astryxdesign/core/Banner';
3
3
  import { EmptyState } from '@astryxdesign/core/EmptyState';
4
4
  import { HStack } from '@astryxdesign/core/HStack';
5
+ import { useLocale } from '@astryxdesign/core/i18n';
5
6
  import { List, ListItem } from '@astryxdesign/core/List';
6
- import { Selector } from '@astryxdesign/core/Selector';
7
- import { Spinner } from '@astryxdesign/core/Spinner';
7
+ import { Selector, SelectorOption } from '@astryxdesign/core/Selector';
8
8
  import { StatusDot } from '@astryxdesign/core/StatusDot';
9
9
  import { Text } from '@astryxdesign/core/Text';
10
10
  import { Token } from '@astryxdesign/core/Token';
11
11
  import { VStack } from '@astryxdesign/core/VStack';
12
- import { useLocale } from '@astryxdesign/core/i18n';
13
- import { useMemo, useState } from 'react';
14
- import { hostCallFailure, hostCallStatus, hostConsoleView } from "../client/host-call.js";
15
- import { createHostTransport } from "../client/host-transport.js";
12
+ import { IconMathFunction, IconWorld } from '@tabler/icons-react';
13
+ import { useMemo, useRef, useState } from 'react';
14
+ import { hostCallFailure, hostCallStatus, hostConsoleView, } from "../client/host-call.js";
15
+ import { createHostTransport, } from "../client/host-transport.js";
16
16
  import { missingFields } from "../client/schema-fields.js";
17
17
  import { isRow, json } from "../client/shaped-data.js";
18
+ import { toolCallLabel } from "../client/transcript-groups.js";
19
+ import { useSecondTicker } from "../hooks/use-second-ticker.js";
18
20
  import { useTheoremHost } from "../hooks/use-theorem-host.js";
19
- import { workDuration } from "./labels.js";
20
- import { ConsoleFrame, JsonRequest, RequestCard, RunBar } from "./ConsoleFrame.js";
21
+ import { Arrive, Stream } from "./arrive.js";
22
+ import { ConsoleFrame, JsonRequest, RequestCard, ResponseColumn, RunBar, } from "./ConsoleFrame.js";
23
+ import { liveDuration, workDuration } from "./labels.js";
21
24
  import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
25
+ import { MarkdownPluginsProvider } from "./markdown-plugins.js";
26
+ import { IconMcp } from "./mcp-icon.js";
27
+ import { PaneFailure, PaneLoading } from "./PaneState.js";
22
28
  import { SchemaFields } from "./SchemaFields.js";
23
- import { TheoremThemeProvider } from "./theme.js";
24
29
  import { ApprovalCard, AuthChallengeCard } from "./ToolGateCard.js";
25
30
  import { ToolResult } from "./ToolResult.js";
26
31
  import { useTraceInspector } from "./TraceInspectorPanel.js";
32
+ import { TheoremThemeProvider } from "./theme.js";
27
33
  /** Calls listed under the console before older ones drop off the page. */
28
34
  const MAX_EARLIER = 8;
29
35
  const STATUS_DOT = {
@@ -49,13 +55,39 @@ function checkRequest(t, tool, text) {
49
55
  return { ok: false, note: t('@theorem.host.missing', { fields: missing.join(', ') }), input };
50
56
  return { ok: true, input };
51
57
  }
52
- /** A tool's kind, and its access when it can change things. */
58
+ /** The kind's icon. The same glyphs the studio uses for a tool's type. */
59
+ const KIND_ICON = {
60
+ function: IconMathFunction,
61
+ http: IconWorld,
62
+ mcp: IconMcp,
63
+ };
64
+ /** The most room a row of the tool menu takes, and the lines of description it shows. */
65
+ const TOOL_MENU_PX = 440;
66
+ const TOOL_MENU_LINES = 2;
67
+ /** Kind and description, shown on the dropdown row and nowhere else. */
68
+ function toolOptionDescription(t, tool) {
69
+ const kind = t(`@theorem.host.kind.${tool.kind}`);
70
+ return tool.description ? `${kind} · ${tool.description}` : kind;
71
+ }
72
+ /** A tool's access when it can change things. Its kind stays in the dropdown. */
53
73
  function ToolTokens({ tool }) {
54
74
  const t = useLabels();
55
- return (_jsxs(HStack, { gap: 1, wrap: "wrap", children: [_jsx(Token, { label: t(`@theorem.host.kind.${tool.kind}`), size: "sm", color: "gray" }), tool.access === 'read-only' ? null : (_jsx(Token, { label: t(`@theorem.host.access.${tool.access}`), size: "sm", color: tool.access === 'destructive' ? 'red' : 'orange' }))] }));
75
+ if (tool.access === 'read-only')
76
+ return null;
77
+ return (_jsx(HStack, { gap: 1, wrap: "wrap", children: _jsx(Token, { label: t(`@theorem.tool.access.${tool.access}`), size: "sm", color: tool.access === 'destructive' ? 'red' : 'orange' }) }));
78
+ }
79
+ /** The tool's activity while it runs, and its activityPast once it has finished, with the clock beside it. */
80
+ function callActivity(t, call, now) {
81
+ const label = call.call ? toolCallLabel(call.call) : call.name;
82
+ const duration = call.isRunning
83
+ ? liveDuration(t, now - call.startedAt)
84
+ : call.elapsedMs == null
85
+ ? null
86
+ : workDuration(t, call.elapsedMs);
87
+ return { label, duration };
56
88
  }
57
89
  /** The gate a call paused on, answered in place. */
58
- function CallGate({ call, onAnswer }) {
90
+ function CallGate({ call, onAnswer, }) {
59
91
  const state = call.call?.state;
60
92
  if (state?.phase !== 'gate')
61
93
  return null;
@@ -64,16 +96,34 @@ function CallGate({ call, onAnswer }) {
64
96
  }
65
97
  return (_jsx(ApprovalCard, { gate: state.gate, toolName: call.name, input: call.input, decided: call.answering === 'auth' ? null : call.answering, onDecision: (action) => void onAnswer(call.id, { action }) }));
66
98
  }
67
- /** One call's outcome: its gate, its failure, or its output, then a line for the tool, time, and status. */
68
- function CallOutcome({ call, onAnswer }) {
69
- const t = useLabels();
99
+ /** A call is settled once it failed, completed, or stopped running. */
100
+ function callSettled(call) {
101
+ const state = call.call?.state;
102
+ return Boolean(hostCallFailure(call)) || state?.phase === 'complete' || !call.isRunning;
103
+ }
104
+ /** A running call with nothing else in the column fills the card, the way an empty trace does. */
105
+ function responseFills(call) {
106
+ const state = call.call?.state;
107
+ return !callSettled(call) && state?.phase !== 'gate';
108
+ }
109
+ /** A settled call: its failure or its output, then the activity line once it has stopped. */
110
+ function CallSettled({ call, activity }) {
70
111
  const state = call.call?.state;
71
112
  const failure = hostCallFailure(call);
72
- const meta = [call.name, call.elapsedMs == null ? null : workDuration(t, call.elapsedMs), t(`@theorem.host.status.${hostCallStatus(call)}`)].filter(Boolean);
73
- return (_jsxs(VStack, { gap: 3, children: [_jsx(CallGate, { call: call, onAnswer: onAnswer }), failure ? _jsx(Banner, { status: "error", title: failure.title, description: failure.description }) : null, state?.phase === 'complete' ? _jsx(ToolResult, { output: state.output, parts: state.parts }) : null, call.isRunning ? null : (_jsx(Text, { type: "supporting", color: "secondary", hasTabularNumbers: true, children: meta.join(' · ') }))] }));
113
+ return (_jsx(Stream, { children: _jsxs(VStack, { gap: 3, children: [failure ? (_jsx(Arrive, { children: _jsx(Banner, { status: "error", role: "alert", title: failure.title, description: failure.description }) })) : null, state?.phase === 'complete' ? (_jsx(ToolResult, { output: state.output, parts: state.parts })) : null, call.isRunning ? null : _jsx(Arrive, { children: activity })] }) }));
114
+ }
115
+ /** One call's outcome: its gate, its failure, or its output, then the tool's activity and the time. */
116
+ function CallOutcome({ call, onAnswer, }) {
117
+ const t = useLabels();
118
+ const now = useSecondTicker(call.isRunning);
119
+ const { label, duration } = callActivity(t, call, now);
120
+ if (responseFills(call))
121
+ return _jsx(PaneLoading, { label: label, since: call.startedAt });
122
+ const activity = (_jsx(Text, { size: "sm", color: "secondary", children: duration ? t('@theorem.host.finished', { activity: label, duration }) : label }));
123
+ return (_jsxs(VStack, { gap: 3, children: [_jsx(CallGate, { call: call, onAnswer: onAnswer }), callSettled(call) ? _jsx(CallSettled, { call: call, activity: activity }) : null] }));
74
124
  }
75
125
  /** This page's earlier calls, newest first; picking one shows it again with its request. */
76
- function EarlierCalls({ calls, onPick }) {
126
+ function EarlierCalls({ calls, onPick, }) {
77
127
  const t = useLabels();
78
128
  const locale = useLocale();
79
129
  const time = useMemo(() => new Intl.DateTimeFormat(locale, { timeStyle: 'medium' }), [locale]);
@@ -85,12 +135,17 @@ function EarlierCalls({ calls, onPick }) {
85
135
  }) })] }));
86
136
  }
87
137
  /** The picked tool's request, as fields or JSON, and Run. */
88
- function ToolRequest({ tools, tool, check, text, view, shown, onText, onPick, onView, onRun, onStop }) {
138
+ function ToolRequest({ tools, tool, check, text, view, shown, onText, onPick, onView, onRun, onStop, }) {
89
139
  const t = useLabels();
90
140
  const fieldsInput = 'input' in check ? check.input : undefined;
91
141
  const showFields = view === 'fields' && fieldsInput !== undefined;
92
- return (_jsxs(RequestCard, { title: t('@theorem.host.request'), view: showFields ? 'fields' : 'json', hasFields: fieldsInput !== undefined, onView: onView, onRun: onRun, children: [_jsxs(VStack, { gap: 2, children: [_jsx(Selector, { label: t('@theorem.host.tool'), size: "sm", options: tools.map((option) => ({ value: option.name, label: option.name, description: option.description })), value: tool.name, hasSearch: tools.length > 8, description: tool.description, onChange: onPick }), _jsx(ToolTokens, { tool: tool })] }), showFields ? (_jsx(SchemaFields
93
- // A new tool starts a new form, so inputs don't carry state across schemas.
142
+ return (_jsxs(RequestCard, { title: t('@theorem.host.request'), view: showFields ? 'fields' : 'json', hasFields: fieldsInput !== undefined, onView: onView, onRun: onRun, children: [_jsxs(VStack, { gap: 2, children: [_jsx(Selector, { label: t('@theorem.host.tool'), size: "sm", options: tools.map((option) => ({
143
+ value: option.name,
144
+ label: option.name,
145
+ icon: KIND_ICON[option.kind],
146
+ description: toolOptionDescription(t, option),
147
+ })), value: tool.name, hasSearch: tools.length > 8, renderOption: (option) => (_jsx("div", { style: { width: TOOL_MENU_PX, maxWidth: '100%', minWidth: 0 }, children: _jsx(SelectorOption, { icon: option.icon, label: option.label, description: option.description && (_jsx(Text, { type: "supporting", color: "secondary", maxLines: TOOL_MENU_LINES, children: option.description })) }) })), onChange: onPick }), _jsx(ToolTokens, { tool: tool })] }), showFields ? (_jsx(SchemaFields
148
+ // why: A new tool starts a new form, so inputs don't carry state across schemas.
94
149
  , { schema: tool.inputSchema, value: fieldsInput, onChange: (next) => {
95
150
  onText(json(next));
96
151
  } }, tool.name)) : (_jsx(JsonRequest, { label: t('@theorem.host.request'), text: text, isInvalid: fieldsInput === undefined, onChange: onText })), _jsx(RunBar, { note: check.ok ? t('@theorem.host.shortcut') : check.note, isReady: check.ok, isRunning: shown?.isRunning === true, runLabel: t('@theorem.host.run'), runningLabel: t('@theorem.host.running'), stopLabel: t('@theorem.host.stop'), onRun: onRun, onStop: () => {
@@ -98,38 +153,73 @@ function ToolRequest({ tools, tool, check, text, view, shown, onText, onPick, on
98
153
  onStop(shown.id);
99
154
  } })] }));
100
155
  }
101
- function HostBody({ transport, iface, host, maxWidth = '960px', trace, className, style }) {
156
+ /** Starts the request as a call, and returns its id. Nothing starts while one is on screen running. */
157
+ function startCall(host, tool, check, shown) {
158
+ if (!tool || !check?.ok || shown?.isRunning)
159
+ return null;
160
+ return host.run(tool.name, check.input);
161
+ }
162
+ /** What the console is showing: the picked tool, its request, and the call on screen. */
163
+ function useHostConsole(iface, host) {
102
164
  const t = useLabels();
103
165
  const [picked, setPicked] = useState(iface.tools[0]?.name);
104
- // Each tool keeps its own request while you move between them.
105
166
  const [drafts, setDrafts] = useState({});
106
167
  const [shownId, setShownId] = useState(null);
107
168
  const [view, setView] = useState('fields');
108
- const inspector = useTraceInspector(iface, transport.traces, trace);
109
- const { tool, text, shown, earlier } = hostConsoleView({ tools: iface.tools, picked, drafts, calls: host.calls, shownId });
169
+ const { tool, text, shown, earlier } = hostConsoleView({
170
+ tools: iface.tools,
171
+ picked,
172
+ drafts,
173
+ calls: host.calls,
174
+ shownId,
175
+ });
110
176
  const check = tool ? checkRequest(t, tool, text) : null;
111
177
  const setText = (next) => {
112
178
  if (tool)
113
179
  setDrafts((previous) => ({ ...previous, [tool.name]: next }));
114
180
  };
115
181
  const run = () => {
116
- if (tool && check?.ok && !shown?.isRunning)
117
- setShownId(host.run(tool.name, check.input));
182
+ const id = startCall(host, tool, check, shown);
183
+ if (id !== null)
184
+ setShownId(id);
118
185
  };
119
186
  const pick = (call) => {
120
187
  setPicked(call.name);
121
188
  setDrafts((previous) => ({ ...previous, [call.name]: json(call.input) }));
122
189
  setShownId(call.id);
123
190
  };
124
- return (_jsxs(ConsoleFrame, { inspector: inspector, maxWidth: maxWidth, className: className, style: style, children: [tool && check ? (_jsx(ToolRequest, { tools: iface.tools, tool: tool, check: check, text: text, view: view, shown: shown, onText: setText, onPick: setPicked, onView: setView, onRun: run, onStop: host.cancel })) : (_jsx(EmptyState, { title: t('@theorem.host.no_tools') })), shown ? _jsx(CallOutcome, { call: shown, onAnswer: host.answer }) : null, _jsx(EarlierCalls, { calls: earlier, onPick: pick })] }));
191
+ return { tool, text, shown, earlier, check, view, setView, setPicked, setText, run, pick };
192
+ }
193
+ /** The call on screen, then this page's earlier ones. */
194
+ function HostCalls({ shown, earlier, onAnswer, onPick, }) {
195
+ return (_jsxs(_Fragment, { children: [shown ? _jsx(CallOutcome, { call: shown, onAnswer: onAnswer }) : null, _jsx(EarlierCalls, { calls: earlier, onPick: onPick })] }));
196
+ }
197
+ /**
198
+ * The frame's second child. A call, not a component: the frame counts its
199
+ * children to choose a layout, and no calls yet has to count as none.
200
+ */
201
+ function hostResponse(columns, label, shown, earlier, calls) {
202
+ if (!columns)
203
+ return calls;
204
+ const alone = earlier.length === 0;
205
+ if (!shown && alone)
206
+ return null;
207
+ return (_jsx(ResponseColumn, { label: label, fill: shown != null && responseFills(shown) && alone, children: calls }));
208
+ }
209
+ function HostBody({ transport, iface, host, maxWidth = '960px', trace, flush, columns = false, className, style, }) {
210
+ const t = useLabels();
211
+ const inspector = useTraceInspector(iface, transport.traces, trace);
212
+ const desk = useHostConsole(iface, host);
213
+ const { tool, check, shown, earlier } = desk;
214
+ return (_jsxs(ConsoleFrame, { inspector: inspector, maxWidth: maxWidth, flush: flush, columns: columns, requestLabel: t('@theorem.host.request'), className: className, style: style, children: [tool && check ? (_jsx(ToolRequest, { tools: iface.tools, tool: tool, check: check, text: desk.text, view: desk.view, shown: shown, onText: desk.setText, onPick: desk.setPicked, onView: desk.setView, onRun: desk.run, onStop: host.cancel })) : (_jsx(EmptyState, { title: t('@theorem.host.no_tools') })), hostResponse(columns, t('@theorem.host.response'), shown, earlier, _jsx(HostCalls, { shown: shown, earlier: earlier, onAnswer: host.answer, onPick: desk.pick }))] }));
125
215
  }
126
216
  function HostForTransport(props) {
127
217
  const t = useLabels();
128
- const host = useTheoremHost(props.transport);
218
+ const host = useTheoremHost(props.transport, props.pageTools);
129
219
  if (host.describeFailure)
130
- return _jsx(Banner, { status: "error", title: host.describeFailure.error });
220
+ return _jsx(PaneFailure, { title: host.describeFailure.error });
131
221
  if (!host.iface)
132
- return _jsx(Spinner, { size: "lg", label: t('@theorem.chat.loading') });
222
+ return _jsx(PaneLoading, { label: t('@theorem.chat.loading') });
133
223
  return _jsx(HostBody, { ...props, iface: host.iface, host: host });
134
224
  }
135
225
  /**
@@ -142,9 +232,9 @@ function HostForTransport(props) {
142
232
  * <TheoremHost endpoint="/api/host" />
143
233
  * ```
144
234
  */
145
- export function TheoremHost({ endpoint, http, transport, theme, mode, labels, ...rest }) {
146
- const resolved = useMemo(() => transport ?? createHostTransport({ ...http, endpoint }),
147
- // eslint-disable-next-line react-hooks/exhaustive-deps -- `http` identity is caller-owned; endpoint drives reconnection.
148
- [transport, endpoint]);
149
- return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(HostForTransport, { ...rest, transport: resolved }) }) }));
235
+ export function TheoremHost({ endpoint, http, transport, theme, mode, labels, detectCodeLanguage, ...rest }) {
236
+ const httpRef = useRef(http);
237
+ httpRef.current = http;
238
+ const resolved = useMemo(() => transport ?? createHostTransport({ ...httpRef.current, endpoint }), [transport, endpoint]);
239
+ return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(MarkdownPluginsProvider, { detectCodeLanguage: detectCodeLanguage, children: _jsx(HostForTransport, { ...rest, transport: resolved }) }) }) }));
150
240
  }
@@ -1,11 +1,15 @@
1
1
  import type { ToolGate } from '@theoremjs/agents/kernel';
2
2
  import type { ToolDecisionAction } from '../client/tool-resume.ts';
3
3
  export type ToolDecision = ToolDecisionAction;
4
+ /** The widest a gate card gets, in pixels. */
5
+ export declare const GATE_CARD_WIDTH = 400;
4
6
  export type ApprovalCardProps = {
5
7
  gate: ToolGate;
6
8
  toolName: string;
9
+ /** The agent asking, as its name is shown (`@concierge`); the heading names it. */
10
+ agent?: string;
7
11
  input?: unknown;
8
- /** The decision on its way; it shows in place of the buttons until the gate settles, or they return. */
12
+ /** The decision on its way; its button shows loading and the other is disabled until the gate settles. */
9
13
  decided?: ToolDecision | null;
10
14
  onDecision?: (action: ToolDecision) => void;
11
15
  };