@theoremjs/react 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +246 -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-transport.d.ts +48 -0
  16. package/client/decision-transport.js +102 -0
  17. package/client/decode-composer-draft.d.ts +11 -0
  18. package/client/decode-composer-draft.js +12 -0
  19. package/client/encode-composer-draft.d.ts +9 -0
  20. package/client/encode-composer-draft.js +34 -0
  21. package/client/encode-files.d.ts +18 -0
  22. package/client/encode-files.js +54 -0
  23. package/client/failure.d.ts +32 -0
  24. package/client/failure.js +34 -0
  25. package/client/generation-selection.d.ts +15 -0
  26. package/client/generation-selection.js +24 -0
  27. package/client/host-call.d.ts +51 -0
  28. package/client/host-call.js +42 -0
  29. package/client/host-transport.d.ts +68 -0
  30. package/client/host-transport.js +74 -0
  31. package/client/image-output.d.ts +6 -0
  32. package/client/image-output.js +13 -0
  33. package/client/index.d.ts +20 -0
  34. package/client/index.js +18 -0
  35. package/client/ink-waveform.d.ts +17 -0
  36. package/client/ink-waveform.js +69 -0
  37. package/client/live/apply-live-turn-tool-event.d.ts +14 -0
  38. package/client/live/apply-live-turn-tool-event.js +39 -0
  39. package/client/live/live-captions.d.ts +23 -0
  40. package/client/live/live-captions.js +70 -0
  41. package/client/live/live-mic-forward.d.ts +29 -0
  42. package/client/live/live-mic-forward.js +37 -0
  43. package/client/live/live-state.d.ts +11 -0
  44. package/client/live/live-state.js +20 -0
  45. package/client/live/live-tool.d.ts +11 -0
  46. package/client/live/live-tool.js +1 -0
  47. package/client/live/live-video.d.ts +10 -0
  48. package/client/live/live-video.js +71 -0
  49. package/client/live/run-live-tool-call.d.ts +17 -0
  50. package/client/live/run-live-tool-call.js +32 -0
  51. package/client/live-client.d.ts +141 -0
  52. package/client/live-client.js +638 -0
  53. package/client/live-errors.d.ts +2 -0
  54. package/client/live-errors.js +9 -0
  55. package/client/live-messages.d.ts +110 -0
  56. package/client/live-messages.js +67 -0
  57. package/client/mic-capture.d.ts +4 -0
  58. package/client/mic-capture.js +6 -0
  59. package/client/oauth-popup.d.ts +26 -0
  60. package/client/oauth-popup.js +30 -0
  61. package/client/pcm-downsample.d.ts +4 -0
  62. package/client/pcm-downsample.js +45 -0
  63. package/client/run-commit.d.ts +61 -0
  64. package/client/run-commit.js +124 -0
  65. package/client/run-session.d.ts +80 -0
  66. package/client/run-session.js +215 -0
  67. package/client/schema-fields.d.ts +63 -0
  68. package/client/schema-fields.js +178 -0
  69. package/client/shaped-data.d.ts +118 -0
  70. package/client/shaped-data.js +314 -0
  71. package/client/source-citations.d.ts +13 -0
  72. package/client/source-citations.js +56 -0
  73. package/client/state-fields.d.ts +16 -0
  74. package/client/state-fields.js +44 -0
  75. package/client/tool-result.d.ts +78 -0
  76. package/client/tool-result.js +275 -0
  77. package/client/tool-resume.d.ts +33 -0
  78. package/client/tool-resume.js +11 -0
  79. package/client/trace-feed.d.ts +13 -0
  80. package/client/trace-feed.js +18 -0
  81. package/client/trace-story.d.ts +163 -0
  82. package/client/trace-story.js +416 -0
  83. package/client/trace-view.d.ts +130 -0
  84. package/client/trace-view.js +293 -0
  85. package/client/transcript-groups.d.ts +114 -0
  86. package/client/transcript-groups.js +167 -0
  87. package/client/transcript-scroll.d.ts +7 -0
  88. package/client/transcript-scroll.js +6 -0
  89. package/client/transport.d.ts +176 -0
  90. package/client/transport.js +256 -0
  91. package/client/turn-client.d.ts +35 -0
  92. package/client/turn-client.js +93 -0
  93. package/client/voice-label.d.ts +2 -0
  94. package/client/voice-label.js +15 -0
  95. package/client/voice-recorder.d.ts +27 -0
  96. package/client/voice-recorder.js +165 -0
  97. package/client/walk-away.d.ts +34 -0
  98. package/client/walk-away.js +48 -0
  99. package/client/wire-line.d.ts +56 -0
  100. package/client/wire-line.js +90 -0
  101. package/client/worklets/mic-capture.js +16 -0
  102. package/components/InkWaveform.d.ts +10 -0
  103. package/components/InkWaveform.js +108 -0
  104. package/components/live/use-live-runner-controls.d.ts +48 -0
  105. package/components/live/use-live-runner-controls.js +100 -0
  106. package/components/live/use-live-runner-model.d.ts +45 -0
  107. package/components/live/use-live-runner-model.js +175 -0
  108. package/components/live/use-live-runner-ui.d.ts +65 -0
  109. package/components/live/use-live-runner-ui.js +159 -0
  110. package/components/live/use-live-session-client.d.ts +41 -0
  111. package/components/live/use-live-session-client.js +123 -0
  112. package/components/use-composer-voice.d.ts +28 -0
  113. package/components/use-composer-voice.js +90 -0
  114. package/components/use-editor-selection.d.ts +3 -0
  115. package/components/use-editor-selection.js +16 -0
  116. package/components/use-voice-playback.d.ts +16 -0
  117. package/components/use-voice-playback.js +68 -0
  118. package/hooks/use-described.d.ts +8 -0
  119. package/hooks/use-described.js +23 -0
  120. package/hooks/use-theorem-chat-actions.d.ts +75 -0
  121. package/hooks/use-theorem-chat-actions.js +265 -0
  122. package/hooks/use-theorem-chat-state.d.ts +66 -0
  123. package/hooks/use-theorem-chat-state.js +118 -0
  124. package/hooks/use-theorem-chat.d.ts +63 -0
  125. package/hooks/use-theorem-chat.js +340 -0
  126. package/hooks/use-theorem-decision.d.ts +23 -0
  127. package/hooks/use-theorem-decision.js +75 -0
  128. package/hooks/use-theorem-host.d.ts +44 -0
  129. package/hooks/use-theorem-host.js +117 -0
  130. package/hooks/use-theorem-interface.d.ts +19 -0
  131. package/hooks/use-theorem-interface.js +25 -0
  132. package/hooks/use-trace-records.d.ts +4 -0
  133. package/hooks/use-trace-records.js +7 -0
  134. package/index.d.ts +14 -0
  135. package/index.js +14 -0
  136. package/live.d.ts +8 -0
  137. package/live.js +10 -0
  138. package/package.json +73 -4
  139. package/server/credential-store.d.ts +24 -0
  140. package/server/credential-store.js +14 -0
  141. package/server/decision-handler.d.ts +34 -0
  142. package/server/decision-handler.js +55 -0
  143. package/server/handler.d.ts +114 -0
  144. package/server/handler.js +568 -0
  145. package/server/json-response.d.ts +2 -0
  146. package/server/json-response.js +7 -0
  147. package/server/mod.d.ts +16 -0
  148. package/server/mod.js +15 -0
  149. package/server/request-check.d.ts +16 -0
  150. package/server/request-check.js +34 -0
  151. package/server/session-store.d.ts +74 -0
  152. package/server/session-store.js +49 -0
  153. package/server/steer-inbox.d.ts +35 -0
  154. package/server/steer-inbox.js +52 -0
  155. package/server/walk-away.d.ts +22 -0
  156. package/server/walk-away.js +41 -0
  157. package/styles/ink-controls.css +134 -0
  158. package/ui/ChatComposerBar.d.ts +39 -0
  159. package/ui/ChatComposerBar.js +251 -0
  160. package/ui/ChatTranscript.d.ts +30 -0
  161. package/ui/ChatTranscript.js +474 -0
  162. package/ui/ConsoleFrame.d.ts +47 -0
  163. package/ui/ConsoleFrame.js +46 -0
  164. package/ui/DecisionAnswers.d.ts +15 -0
  165. package/ui/DecisionAnswers.js +102 -0
  166. package/ui/LiveRunner.d.ts +28 -0
  167. package/ui/LiveRunner.js +193 -0
  168. package/ui/SchemaFields.d.ts +10 -0
  169. package/ui/SchemaFields.js +122 -0
  170. package/ui/ShapedData.d.ts +11 -0
  171. package/ui/ShapedData.js +151 -0
  172. package/ui/SidePanel.d.ts +57 -0
  173. package/ui/SidePanel.js +50 -0
  174. package/ui/StateFields.d.ts +36 -0
  175. package/ui/StateFields.js +96 -0
  176. package/ui/TheoremChat.d.ts +71 -0
  177. package/ui/TheoremChat.js +146 -0
  178. package/ui/TheoremDecision.d.ts +38 -0
  179. package/ui/TheoremDecision.js +87 -0
  180. package/ui/TheoremHost.d.ts +38 -0
  181. package/ui/TheoremHost.js +150 -0
  182. package/ui/ToolGateCard.d.ts +28 -0
  183. package/ui/ToolGateCard.js +100 -0
  184. package/ui/ToolResult.d.ts +10 -0
  185. package/ui/ToolResult.js +60 -0
  186. package/ui/ToolResultCharts.d.ts +5 -0
  187. package/ui/ToolResultCharts.js +86 -0
  188. package/ui/TraceCharts.d.ts +13 -0
  189. package/ui/TraceCharts.js +235 -0
  190. package/ui/TraceGuardrails.d.ts +10 -0
  191. package/ui/TraceGuardrails.js +56 -0
  192. package/ui/TraceInspector.d.ts +6 -0
  193. package/ui/TraceInspector.js +189 -0
  194. package/ui/TraceInspectorPanel.d.ts +24 -0
  195. package/ui/TraceInspectorPanel.js +57 -0
  196. package/ui/TraceOverview.d.ts +34 -0
  197. package/ui/TraceOverview.js +96 -0
  198. package/ui/TraceSpanDetail.d.ts +10 -0
  199. package/ui/TraceSpanDetail.js +188 -0
  200. package/ui/TraceStory.d.ts +20 -0
  201. package/ui/TraceStory.js +107 -0
  202. package/ui/TraceValues.d.ts +36 -0
  203. package/ui/TraceValues.js +113 -0
  204. package/ui/TraceWaterfall.d.ts +13 -0
  205. package/ui/TraceWaterfall.js +132 -0
  206. package/ui/VoiceNote.d.ts +12 -0
  207. package/ui/VoiceNote.js +28 -0
  208. package/ui/built/theme.css +774 -0
  209. package/ui/built/theorem.d.ts +13 -0
  210. package/ui/built/theorem.js +631 -0
  211. package/ui/built/theorem.variants.d.ts +16 -0
  212. package/ui/disclosure-motion.d.ts +6 -0
  213. package/ui/disclosure-motion.js +163 -0
  214. package/ui/icons.d.ts +4 -0
  215. package/ui/icons.js +37 -0
  216. package/ui/index.d.ts +25 -0
  217. package/ui/index.js +29 -0
  218. package/ui/labels-provider.d.ts +16 -0
  219. package/ui/labels-provider.js +61 -0
  220. package/ui/labels.d.ts +1153 -0
  221. package/ui/labels.js +603 -0
  222. package/ui/theme.d.ts +10 -0
  223. package/ui/theme.js +13 -0
  224. package/ui/theorem-theme.d.ts +10 -0
  225. package/ui/theorem-theme.js +83 -0
  226. package/ui/trace-palette.d.ts +24 -0
  227. package/ui/trace-palette.js +24 -0
  228. package/ui/trace-patterns.d.ts +32 -0
  229. package/ui/trace-patterns.js +44 -0
  230. package/ui/transcript-copy-text.d.ts +4 -0
  231. package/ui/transcript-copy-text.js +39 -0
@@ -0,0 +1,57 @@
1
+ import { type ResizableRegion } from '@astryxdesign/core/Resizable';
2
+ import { type ReactNode, type RefObject } from 'react';
3
+ /** Whether a side panel is wide enough for two readable columns: twice its minimum. */
4
+ export declare function isSidePanelWide(size: number): boolean;
5
+ /** A side panel's open state and size. `containerRef` is the Layout its percentages resolve against. */
6
+ export declare function useSidePanel(containerRef: RefObject<HTMLDivElement | null>, initiallyOpen: boolean): {
7
+ id: string;
8
+ open: boolean;
9
+ toggle: () => void;
10
+ resizable: ResizableRegion;
11
+ };
12
+ /** The Layout header row: side panel toggles, at the end. */
13
+ export declare function SidePanelHeader({ children }: {
14
+ children?: ReactNode;
15
+ }): import("react").JSX.Element;
16
+ /** A side panel's words: its name, and its toggle and drag handle actions. */
17
+ export type SidePanelLabels = {
18
+ name: string;
19
+ show: string;
20
+ hide: string;
21
+ resize: string;
22
+ };
23
+ /** Shows or hides one side panel (`panelId` from useSidePanel). */
24
+ export declare function SidePanelToggle({ labels, icon, panelId, open, onToggle, }: {
25
+ labels: SidePanelLabels;
26
+ icon: ReactNode;
27
+ panelId: string;
28
+ open: boolean;
29
+ onToggle: () => void;
30
+ }): import("react").JSX.Element;
31
+ export type SidePanelProps = {
32
+ id?: string;
33
+ labels: SidePanelLabels;
34
+ /** From `useResizable`: the panel's width and its handle's drag state. */
35
+ resizable: ResizableRegion;
36
+ /**
37
+ * Closed panels stay mounted at zero width and inert, so the theme can ease
38
+ * their width; their content renders only while open, so it never reflows
39
+ * into the collapsing width.
40
+ */
41
+ open?: boolean;
42
+ /** Card padding; 0 for content that brings its own (e.g. ChatLayout). */
43
+ padding?: 0;
44
+ children?: ReactNode;
45
+ };
46
+ /** Page corners like the host's raised panels, no border or shadow; the card fill, so surface-filled cards read on it. */
47
+ export declare const RAISED: {
48
+ background: string;
49
+ borderRadius: string;
50
+ };
51
+ /**
52
+ * A Layout `end` panel as Astryx's IDE template builds one (a reversed
53
+ * `ResizeHandle` before a `LayoutPanel` sized by `useResizable`), holding a
54
+ * raised section inset from the layout edge and bottom instead of a flat pane.
55
+ * For more than one panel, nest Layouts, one panel each, as the template does.
56
+ */
57
+ export declare function SidePanel({ id, labels, resizable, open, padding, children }: SidePanelProps): import("react").JSX.Element;
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card } from '@astryxdesign/core/Card';
3
+ import { HStack } from '@astryxdesign/core/HStack';
4
+ import { IconButton } from '@astryxdesign/core/IconButton';
5
+ import { LayoutHeader, LayoutPanel } from '@astryxdesign/core/Layout';
6
+ import { ResizeHandle, useResizable, } from '@astryxdesign/core/Resizable';
7
+ import { VStack } from '@astryxdesign/core/VStack';
8
+ import { useCallback, useId, useState } from 'react';
9
+ /**
10
+ * `useResizable` sizing for side panels: a third of the layout (pass the
11
+ * Layout as `containerRef` so the percentages resolve), dragged between a
12
+ * readable minimum and half.
13
+ */
14
+ const SIDE_PANEL_SIZING = {
15
+ defaultSize: '33%',
16
+ minSize: 280,
17
+ maxSize: '50%',
18
+ };
19
+ /** Whether a side panel is wide enough for two readable columns: twice its minimum. */
20
+ export function isSidePanelWide(size) {
21
+ return size >= SIDE_PANEL_SIZING.minSize * 2;
22
+ }
23
+ /** A side panel's open state and size. `containerRef` is the Layout its percentages resolve against. */
24
+ export function useSidePanel(containerRef, initiallyOpen) {
25
+ const id = useId();
26
+ const [open, setOpen] = useState(initiallyOpen);
27
+ const toggle = useCallback(() => setOpen((value) => !value), []);
28
+ const resizable = useResizable({ ...SIDE_PANEL_SIZING, containerRef });
29
+ return { id, open, toggle, resizable };
30
+ }
31
+ /** The Layout header row: side panel toggles, at the end. */
32
+ export function SidePanelHeader({ children }) {
33
+ return (_jsx(LayoutHeader, { hasDivider: false, children: _jsx(HStack, { hAlign: "end", gap: 1, children: children }) }));
34
+ }
35
+ /** Shows or hides one side panel (`panelId` from useSidePanel). */
36
+ export function SidePanelToggle({ labels, icon, panelId, open, onToggle, }) {
37
+ const action = open ? labels.hide : labels.show;
38
+ return (_jsx(IconButton, { label: action, tooltip: action, variant: "ghost", "aria-expanded": open, "aria-controls": panelId, icon: icon, onClick: onToggle }));
39
+ }
40
+ /** Page corners like the host's raised panels, no border or shadow; the card fill, so surface-filled cards read on it. */
41
+ export const RAISED = { background: 'var(--color-background-card)', borderRadius: 'var(--radius-page)' };
42
+ /**
43
+ * A Layout `end` panel as Astryx's IDE template builds one (a reversed
44
+ * `ResizeHandle` before a `LayoutPanel` sized by `useResizable`), holding a
45
+ * raised section inset from the layout edge and bottom instead of a flat pane.
46
+ * For more than one panel, nest Layouts, one panel each, as the template does.
47
+ */
48
+ export function SidePanel({ id, labels, resizable, open = true, padding, children }) {
49
+ return (_jsxs(_Fragment, { children: [open ? (_jsx(ResizeHandle, { direction: "horizontal", isReversed: true, hasDivider: false, isAlwaysVisible: false, resizable: resizable.props, label: labels.resize })) : null, _jsx(LayoutPanel, { id: id, label: labels.name, role: "complementary", width: open ? resizable.size : 0, hasDivider: false, padding: 0, isScrollable: false, inert: !open, children: _jsx(VStack, { height: "100%", paddingInlineEnd: 3, paddingBlockEnd: 3, children: _jsx(Card, { variant: "transparent", height: "100%", padding: padding, style: RAISED, children: open ? children : null }) }) })] }));
50
+ }
@@ -0,0 +1,36 @@
1
+ import type { DecisionJson } from '@theoremjs/agents';
2
+ export type Edit<T = DecisionJson> = {
3
+ value: T;
4
+ onChange: (next: T) => void;
5
+ };
6
+ export type GroupLabel = {
7
+ label: string;
8
+ isRequired?: boolean;
9
+ description?: string;
10
+ };
11
+ /** Several fields under one name, in the same label a single field has. */
12
+ export declare function Labelled({ label, isRequired, description, children }: GroupLabel & {
13
+ children: React.ReactNode;
14
+ }): import("react").JSX.Element;
15
+ /** Nested fields sit under their name, set in by a rule. */
16
+ export declare function Group({ children, ...label }: GroupLabel & {
17
+ children: React.ReactNode;
18
+ }): import("react").JSX.Element;
19
+ /** One more row at the end of a list. */
20
+ export declare function AddRow({ field, onAdd }: {
21
+ field: string;
22
+ onAdd: () => void;
23
+ }): import("react").JSX.Element;
24
+ /** Removes a list's row, named by its place. */
25
+ export declare function RemoveRow({ index, onRemove }: {
26
+ index: number;
27
+ onRemove: () => void;
28
+ }): import("react").JSX.Element;
29
+ /**
30
+ * A decision's state edited field by field: objects as labelled fields, lists
31
+ * of words as tokens, other lists as rows. The shape itself (new keys, other
32
+ * types) is edited as JSON.
33
+ */
34
+ export declare function StateFields({ value, onChange, label }: Edit & {
35
+ label: string;
36
+ }): import("react").JSX.Element;
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from '@astryxdesign/core/Button';
3
+ import { FieldLabel } from '@astryxdesign/core/Field';
4
+ import { HStack } from '@astryxdesign/core/HStack';
5
+ import { IconButton } from '@astryxdesign/core/IconButton';
6
+ import { NumberInput } from '@astryxdesign/core/NumberInput';
7
+ import { StackItem } from '@astryxdesign/core/Stack';
8
+ import { Switch } from '@astryxdesign/core/Switch';
9
+ import { Text } from '@astryxdesign/core/Text';
10
+ import { TextArea } from '@astryxdesign/core/TextArea';
11
+ import { TextInput } from '@astryxdesign/core/TextInput';
12
+ import { Tokenizer } from '@astryxdesign/core/Tokenizer';
13
+ import { VStack } from '@astryxdesign/core/VStack';
14
+ import { IconPlus, IconX } from '@tabler/icons-react';
15
+ import { useId, useState } from 'react';
16
+ import { humanize, isPlain } from "../client/shaped-data.js";
17
+ import { blankLike, isWordList, tableColumns } from "../client/state-fields.js";
18
+ import { useLabels } from "./labels-provider.js";
19
+ const NO_SUGGESTIONS = { search: () => [], bootstrap: () => [] };
20
+ /** Text this long, or on several lines, gets a text area. */
21
+ const LONG_TEXT = 48;
22
+ /** A string, number, true/false, or null, as its input. */
23
+ function PlainField({ value, onChange, label, isLabelHidden }) {
24
+ // Decided once, so the field doesn't swap inputs (and lose focus) as the text grows.
25
+ const [isLong] = useState(() => typeof value === 'string' && (value.length > LONG_TEXT || value.includes('\n')));
26
+ if (typeof value === 'boolean')
27
+ return _jsx(Switch, { label: label, value: value, onChange: onChange });
28
+ if (typeof value === 'number') {
29
+ return _jsx(NumberInput, { label: label, isLabelHidden: isLabelHidden, size: "sm", value: value, isWheelEnabled: false, onChange: onChange });
30
+ }
31
+ if (value === null)
32
+ return _jsx(TextInput, { label: label, isLabelHidden: isLabelHidden, size: "sm", value: "", placeholder: "null", isDisabled: true, onChange: () => { } });
33
+ const text = String(value);
34
+ return isLong ? (_jsx(TextArea, { label: label, isLabelHidden: isLabelHidden, size: "sm", rows: 2, value: text, onChange: onChange })) : (_jsx(TextInput, { label: label, isLabelHidden: isLabelHidden, size: "sm", value: text, onChange: onChange }));
35
+ }
36
+ /** Several fields under one name, in the same label a single field has. */
37
+ export function Labelled({ label, isRequired, description, children }) {
38
+ const id = useId();
39
+ return (_jsxs(VStack, { gap: 2, role: "group", "aria-labelledby": id, children: [_jsx(FieldLabel, { label: label, inputID: "", labelID: id, isGroupLabel: true, isRequired: isRequired, description: description }), children] }));
40
+ }
41
+ /** Nested fields sit under their name, set in by a rule. */
42
+ export function Group({ children, ...label }) {
43
+ return (_jsx(Labelled, { ...label, children: _jsx("div", { style: { paddingInlineStart: 12, borderInlineStart: '1px solid var(--color-border)' }, children: children }) }));
44
+ }
45
+ /** One more row at the end of a list. */
46
+ export function AddRow({ field, onAdd }) {
47
+ const t = useLabels();
48
+ return (_jsx(HStack, { children: _jsx(Button, { label: t('@theorem.decision.add_item', { field }), size: "sm", variant: "ghost", icon: _jsx(IconPlus, { size: 14 }), onClick: onAdd }) }));
49
+ }
50
+ /** Removes a list's row, named by its place. */
51
+ export function RemoveRow({ index, onRemove }) {
52
+ const t = useLabels();
53
+ return (_jsx(IconButton, { label: t('@theorem.decision.remove_item', { item: t('@theorem.data.item', { index: String(index + 1) }) }), size: "sm", variant: "ghost", icon: _jsx(IconX, { size: 14 }), onClick: onRemove }));
54
+ }
55
+ function ObjectFields({ value, onChange }) {
56
+ return (_jsx(VStack, { gap: 3, children: Object.entries(value).map(([key, entry]) => (_jsx(Field, { label: humanize(key), value: entry, onChange: (next) => onChange({ ...value, [key]: next }) }, key))) }));
57
+ }
58
+ /** A list of words as tokens; any other list as rows, each removable, with one more shaped like the last. */
59
+ function ListField({ value, onChange, label }) {
60
+ const t = useLabels();
61
+ if (isWordList(value)) {
62
+ return (_jsx(Tokenizer, { label: label, size: "sm", value: value.map((word, index) => ({ id: `${String(index)}:${word}`, label: word })), searchSource: NO_SUGGESTIONS, hasCreate: true, debounceMs: 0, placeholder: t('@theorem.decision.add_value'), onChange: (items) => onChange(items.map((item) => item.label)) }));
63
+ }
64
+ const columns = tableColumns(value);
65
+ const add = _jsx(AddRow, { field: label, onAdd: () => onChange([...value, blankLike(value.at(-1))]) });
66
+ const remove = (index) => _jsx(RemoveRow, { index: index, onRemove: () => onChange(value.filter((_, at) => at !== index)) });
67
+ if (columns) {
68
+ // Rows alike and flat, as ShapedData draws them: a table, one input per cell.
69
+ const grid = { display: 'grid', gap: 8, alignItems: 'start', gridTemplateColumns: `${columns.map((column) => `minmax(0, ${String(column.weight)}fr)`).join(' ')} auto` };
70
+ const rows = value;
71
+ return (_jsxs(Labelled, { label: label, children: [_jsxs("div", { style: grid, "aria-hidden": true, children: [columns.map((column) => (_jsx(Text, { type: "supporting", color: "secondary", maxLines: 1, children: humanize(column.key) }, column.key))), _jsx("span", { style: { width: 28 } })] }), rows.map((row, index) => (_jsxs("div", { style: grid, children: [columns.map((column) => (_jsx(PlainField, { label: `${humanize(column.key)} ${String(index + 1)}`, isLabelHidden: true, value: row[column.key] ?? null, onChange: (next) => onChange(rows.map((old, at) => (at === index ? { ...old, [column.key]: next } : old))) }, column.key))), remove(index)] }, index))), add] }));
72
+ }
73
+ return (_jsx(Group, { label: label, children: _jsxs(VStack, { gap: 3, children: [value.map((entry, index) => {
74
+ const item = t('@theorem.data.item', { index: String(index + 1) });
75
+ const set = (next) => onChange(value.map((old, at) => (at === index ? next : old)));
76
+ return (_jsxs(HStack, { gap: 2, vAlign: "start", children: [_jsx(StackItem, { size: "fill", children: isPlain(entry) ? _jsx(PlainField, { label: item, isLabelHidden: true, value: entry, onChange: set }) : _jsx(Field, { label: item, value: entry, onChange: set }) }), remove(index)] }, index));
77
+ }), add] }) }));
78
+ }
79
+ function Field({ value, onChange, label }) {
80
+ if (Array.isArray(value))
81
+ return _jsx(ListField, { label: label, value: value, onChange: onChange });
82
+ if (value !== null && typeof value === 'object') {
83
+ return (_jsx(Group, { label: label, children: _jsx(ObjectFields, { value: value, onChange: onChange }) }));
84
+ }
85
+ return _jsx(PlainField, { label: label, value: value, onChange: onChange });
86
+ }
87
+ /**
88
+ * A decision's state edited field by field: objects as labelled fields, lists
89
+ * of words as tokens, other lists as rows. The shape itself (new keys, other
90
+ * types) is edited as JSON.
91
+ */
92
+ export function StateFields({ value, onChange, label }) {
93
+ if (value !== null && typeof value === 'object' && !Array.isArray(value))
94
+ return _jsx(ObjectFields, { value: value, onChange: onChange });
95
+ return _jsx(Field, { label: label, value: value, onChange: onChange });
96
+ }
@@ -0,0 +1,71 @@
1
+ import type { DefinedTheme } from '@astryxdesign/core/theme';
2
+ import { type ReactNode, type Ref } from 'react';
3
+ import { type HttpTransportOptions, type TheoremTransport } from '../client/transport.ts';
4
+ import { type ChatSnapshot, type SentTurn } from '../hooks/use-theorem-chat.ts';
5
+ import type { TheoremLabels } from './labels.ts';
6
+ export type TheoremChatProps = {
7
+ /** Where `createTheoremHandler` is mounted. Default `/api/theorem`. Ignored when `transport` is set. */
8
+ endpoint?: string;
9
+ /** Extra fetch options for the default HTTP transport (auth headers, custom fetch). */
10
+ http?: Omit<HttpTransportOptions, 'endpoint'>;
11
+ /** Bring your own transport (tests, playgrounds, non-HTTP hosts). */
12
+ transport?: TheoremTransport;
13
+ /** Astryx theme. Omit to inherit the host's `<Theme>` or use `theoremTheme`. */
14
+ theme?: DefinedTheme;
15
+ mode?: 'system' | 'light' | 'dark';
16
+ /**
17
+ * Replacement lines by locale, for any `@theorem.*` key in
18
+ * `THEOREM_UI_CATALOG` or any of Astryx's `@astryx.*` keys. The locale is
19
+ * the host's Astryx `InternationalizationProvider` locale (default `en`).
20
+ */
21
+ labels?: TheoremLabels;
22
+ placeholder?: string;
23
+ /** Shown above the centred composer before the first message. Default: the agent's handle and a prompt. */
24
+ emptyState?: ReactNode;
25
+ density?: 'compact' | 'balanced' | 'spacious';
26
+ /**
27
+ * Widest the transcript and composer may grow, as a CSS length. Default
28
+ * {@link DEFAULT_CHAT_MAX_WIDTH}: 50% of the chat, or full width on narrow screens.
29
+ */
30
+ maxWidth?: string;
31
+ /** Page scroller when the chat is not its own scroll container (e.g. `document.documentElement`). */
32
+ scrollRef?: React.RefObject<HTMLElement | null>;
33
+ /**
34
+ * Show the trace in place of the chat, from the host's own control; the
35
+ * built-in trace toggle then hides. Omit to keep the toggle. Needs a profile
36
+ * that records traces.
37
+ */
38
+ trace?: boolean;
39
+ className?: string;
40
+ style?: React.CSSProperties;
41
+ /** A conversation to resume, as `onChatChange` reported it. Read once, when the chat mounts. */
42
+ initialChat?: ChatSnapshot;
43
+ /**
44
+ * Called with the conversation each time it comes to rest: a turn finished, or a message was
45
+ * added or removed; never while a reply streams. Keep it to resume the chat later.
46
+ */
47
+ onChatChange?: (snapshot: ChatSnapshot) => void;
48
+ /** Lets the host send a message as the composer would; see {@link TheoremChatHandle}. */
49
+ chatRef?: Ref<TheoremChatHandle>;
50
+ };
51
+ /** What a host can do to a mounted {@link TheoremChat}. */
52
+ export type TheoremChatHandle = {
53
+ /**
54
+ * Sends `text` as the user, and resolves with the blocks the turn added once the reply is done;
55
+ * `null` when the chat can't take a message now (a reply is streaming, or waits on a gate).
56
+ */
57
+ send: (text: string) => Promise<SentTurn | null>;
58
+ };
59
+ /**
60
+ * 50% of the chat's width, but never narrower than 640px (or the full width,
61
+ * whichever is smaller) — so phones and narrow panes get 100%.
62
+ */
63
+ export declare const DEFAULT_CHAT_MAX_WIDTH = "max(50%, min(100%, 640px))";
64
+ /**
65
+ * Drop-in chat for a profile served by `createTheoremHandler`:
66
+ *
67
+ * ```tsx
68
+ * <TheoremChat endpoint="/api/theorem" />
69
+ * ```
70
+ */
71
+ export declare function TheoremChat({ endpoint, http, transport, theme, mode, labels, ...rest }: TheoremChatProps): import("react").JSX.Element;
@@ -0,0 +1,146 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Banner } from '@astryxdesign/core/Banner';
3
+ import { Center } from '@astryxdesign/core/Center';
4
+ import { ChatLayout } from '@astryxdesign/core/Chat';
5
+ import { Layout, LayoutContent } from '@astryxdesign/core/Layout';
6
+ import { Spinner } from '@astryxdesign/core/Spinner';
7
+ import { Text } from '@astryxdesign/core/Text';
8
+ import { VStack } from '@astryxdesign/core/VStack';
9
+ import { useImperativeHandle, useLayoutEffect, useMemo, useRef, } from 'react';
10
+ import { moveComposerPendingWithinKind, removeComposerPendingMessage, } from '@theoremjs/agents/interface';
11
+ import { createHttpTransport } from "../client/transport.js";
12
+ import { useTheoremChat } from "../hooks/use-theorem-chat.js";
13
+ import { useTheoremInterface } from "../hooks/use-theorem-interface.js";
14
+ import { ChatComposerBar } from "./ChatComposerBar.js";
15
+ import { parseAspectRatio } from "../client/image-output.js";
16
+ import { ChatTranscript } from "./ChatTranscript.js";
17
+ import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
18
+ import { TheoremThemeProvider } from "./theme.js";
19
+ import { SidePanelHeader } from "./SidePanel.js";
20
+ import { useTraceInspector, WithTrace } from "./TraceInspectorPanel.js";
21
+ /**
22
+ * 50% of the chat's width, but never narrower than 640px (or the full width,
23
+ * whichever is smaller) — so phones and narrow panes get 100%.
24
+ */
25
+ export const DEFAULT_CHAT_MAX_WIDTH = 'max(50%, min(100%, 640px))';
26
+ /** Centres content in a column no wider than `maxWidth`, using Astryx layout props. */
27
+ function ChatColumn({ maxWidth, ref, children }) {
28
+ return (_jsx(Center, { axis: "horizontal", width: "100%", children: _jsx(VStack, { ref: ref, width: "100%", maxWidth: maxWidth, children: children }) }));
29
+ }
30
+ /** Reads an Astryx duration token ("410ms" / "0.4s") as milliseconds. */
31
+ function tokenMs(value, fallback) {
32
+ const n = Number.parseFloat(value);
33
+ if (Number.isNaN(n))
34
+ return fallback;
35
+ return value.trim().endsWith('ms') ? n : n * 1_000;
36
+ }
37
+ /**
38
+ * The composer is centred until the first message, then lives in ChatLayout's
39
+ * dock. Astryx has no layout-transition helper, so this eases the jump (FLIP)
40
+ * with Astryx's `--duration-medium` / `--ease-standard` tokens and keeps focus.
41
+ */
42
+ function useComposerGlide(landing, inputRef) {
43
+ const columnRef = useRef(null);
44
+ const landingTop = useRef(null);
45
+ useLayoutEffect(() => {
46
+ const el = columnRef.current;
47
+ if (!el)
48
+ return;
49
+ if (landing) {
50
+ landingTop.current = el.getBoundingClientRect().top;
51
+ return;
52
+ }
53
+ const from = landingTop.current;
54
+ if (from === null)
55
+ return;
56
+ landingTop.current = null;
57
+ inputRef.current?.focus();
58
+ const delta = from - el.getBoundingClientRect().top;
59
+ if (delta === 0 || globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches)
60
+ return;
61
+ const tokens = getComputedStyle(el);
62
+ el.animate([{ transform: `translateY(${String(delta)}px)` }, { transform: 'none' }], {
63
+ duration: tokenMs(tokens.getPropertyValue('--duration-medium'), 410),
64
+ easing: tokens.getPropertyValue('--ease-standard').trim() || 'ease-out',
65
+ });
66
+ });
67
+ return columnRef;
68
+ }
69
+ /** The composer bar wired to a `useTheoremChat` model. */
70
+ function ChatComposerForChat({ chat, iface, placeholder, inputRef, }) {
71
+ return (_jsx(ChatComposerBar, { iface: iface, draftText: chat.draftText, pendingFiles: chat.pendingFiles, pendingVoice: chat.pendingVoice, pendingMessages: chat.pendingMessages, issues: chat.issues, phase: chat.phase, failure: chat.failure, selectedModel: chat.session.selectedModel, selectedEffort: chat.session.selectedEffort, placeholder: placeholder, inputRef: inputRef, onDraftTextChange: chat.setDraftText, onFilesSelected: (files) => {
72
+ chat.setPendingFiles((prev) => [...prev, ...files]);
73
+ chat.setIssues([]);
74
+ }, onAttachmentRemove: (index) => {
75
+ chat.setPendingFiles((prev) => prev.filter((_, i) => i !== index));
76
+ }, onVoiceStaged: (file) => {
77
+ chat.setPendingVoice([file]);
78
+ chat.setIssues([]);
79
+ }, onVoiceClear: () => chat.setPendingVoice([]), onSubmit: () => {
80
+ void chat.handleSubmit();
81
+ }, onStop: chat.handleStop, onMenuAction: chat.handleMenuAction, onGenerationChange: chat.handleGenerationChange, onPendingMove: (id, direction) => {
82
+ chat.setPendingMessages((prev) => moveComposerPendingWithinKind(prev, id, direction));
83
+ }, onPendingRemove: (id) => {
84
+ chat.setPendingMessages((prev) => removeComposerPendingMessage(prev, id));
85
+ }, onPendingQueue: chat.handlePendingQueue, onPendingRestore: (id) => {
86
+ void chat.handlePendingRestore(id);
87
+ }, onPendingSendNow: (id) => {
88
+ const message = chat.pendingMessages.find((m) => m.id === id);
89
+ if (message)
90
+ void chat.handleSendNow(message);
91
+ } }));
92
+ }
93
+ function ChatBody({ transport, iface, placeholder, emptyState, density, maxWidth = DEFAULT_CHAT_MAX_WIDTH, scrollRef, trace, className, style, initialChat, onChatChange, chatRef, }) {
94
+ const t = useLabels();
95
+ const chat = useTheoremChat({ transport, iface, initial: initialChat, onChange: onChatChange });
96
+ const sendText = chat.sendText;
97
+ useImperativeHandle(chatRef, () => ({ send: sendText }), [sendText]);
98
+ const blocks = useMemo(() => [...chat.blocks, ...chat.streamBlocks], [chat.blocks, chat.streamBlocks]);
99
+ const handle = t('@theorem.agent.handle', { handle: iface.identity.handle });
100
+ const landing = blocks.length === 0;
101
+ const inputRef = useRef(null);
102
+ const composerRef = useComposerGlide(landing, inputRef);
103
+ const inspector = useTraceInspector(iface, transport.traces, trace);
104
+ const header = _jsx(SidePanelHeader, { children: inspector.toggle });
105
+ const composer = (_jsx(ChatComposerForChat, { chat: chat, iface: iface, placeholder: placeholder, inputRef: inputRef }));
106
+ // Before the first message: the composer alone, centred (Astryx AI chat
107
+ // template landing). paddingInline={3} matches the dock's inset so the
108
+ // composer keeps its width when it moves.
109
+ if (landing) {
110
+ return (_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { height: "fill", className: className, style: style, header: header, content: _jsx(LayoutContent, { padding: 0, children: _jsxs(VStack, { minHeight: "100%", vAlign: "center", gap: 8, paddingInline: 3, children: [_jsx(ChatColumn, { maxWidth: maxWidth, children: emptyState ?? (_jsxs(VStack, { gap: 1, children: [_jsx(Text, { type: "large", as: "h2", children: handle }), _jsx(Text, { type: "display-2", as: "h1", children: t('@theorem.chat.greeting') })] })) }), _jsx(ChatColumn, { ref: composerRef, maxWidth: maxWidth, children: composer })] }) }) }) }));
111
+ }
112
+ // Structure from Astryx's AI chat template: Layout › LayoutContent › ChatLayout,
113
+ // with the composer in ChatLayout's own dock. The dock leaves 12px under the
114
+ // composer; paddingBlockEnd={3} adds 12px more for a 24px bottom margin.
115
+ return (_jsx(WithTrace, { inspector: inspector, children: _jsx(Layout, { height: "fill", className: className, style: style, header: header, content: _jsx(LayoutContent, { padding: 0, children: _jsx(VStack, { height: "100%", children: _jsx(ChatLayout, { density: density, scrollRef: scrollRef, composer: _jsx(VStack, { paddingBlockEnd: 3, children: _jsx(ChatColumn, { ref: composerRef, maxWidth: maxWidth, children: composer }) }), children: _jsx(ChatColumn, { maxWidth: maxWidth, children: _jsx(ChatTranscript, { blocks: blocks, handle: handle, streaming: chat.streaming, delivery: chat.delivery, answering: chat.answering, imageOutput: iface.type === 'image' ? { ratio: parseAspectRatio(iface.image.aspectRatio) } : undefined, onToolDecision: (index, action) => {
116
+ void chat.handleToolDecision(index, action);
117
+ }, onAuthenticated: (index, secret) => {
118
+ void chat.handleAuthenticated(index, secret);
119
+ } }) }) }) }) }) }) }));
120
+ }
121
+ function ChatForTransport(props) {
122
+ const t = useLabels();
123
+ const described = useTheoremInterface(props.transport);
124
+ if (described.status === 'loading')
125
+ return _jsx(Spinner, { size: "lg", label: t('@theorem.chat.loading') });
126
+ if (described.status === 'error') {
127
+ return _jsx(Banner, { status: "error", title: described.failure.error });
128
+ }
129
+ if (described.iface.type === 'live') {
130
+ return (_jsx(Banner, { status: "warning", title: t('@theorem.chat.live_unsupported.title'), description: t('@theorem.chat.live_unsupported.description') }));
131
+ }
132
+ return _jsx(ChatBody, { ...props, transport: props.transport, iface: described.iface });
133
+ }
134
+ /**
135
+ * Drop-in chat for a profile served by `createTheoremHandler`:
136
+ *
137
+ * ```tsx
138
+ * <TheoremChat endpoint="/api/theorem" />
139
+ * ```
140
+ */
141
+ export function TheoremChat({ endpoint, http, transport, theme, mode, labels, ...rest }) {
142
+ const resolved = useMemo(() => transport ?? createHttpTransport({ ...http, endpoint }),
143
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- `http` identity is caller-owned; endpoint drives reconnection.
144
+ [transport, endpoint]);
145
+ return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(ChatForTransport, { ...rest, transport: resolved }) }) }));
146
+ }
@@ -0,0 +1,38 @@
1
+ import type { DefinedTheme } from '@astryxdesign/core/theme';
2
+ import { type CSSProperties } from 'react';
3
+ import { type DecisionTransport } from '../client/decision-transport.ts';
4
+ import type { HttpOptions } from '../client/transport.ts';
5
+ import type { TheoremLabels } from './labels.ts';
6
+ export type TheoremDecisionProps = {
7
+ /** Where `createTheoremDecisionHandler` is mounted. Default `/api/decision`. Ignored when `transport` is set. */
8
+ endpoint?: string;
9
+ /** Extra fetch options for the default HTTP transport (auth headers, custom fetch). */
10
+ http?: HttpOptions;
11
+ /** Bring your own transport (tests, playgrounds, non-HTTP hosts). */
12
+ transport?: DecisionTransport;
13
+ /** The JSON the state field starts with. Default `{}`. */
14
+ defaultState?: string;
15
+ theme?: DefinedTheme;
16
+ mode?: 'system' | 'light' | 'dark';
17
+ /** Replacement lines by locale, for any `@theorem.*` or `@astryx.*` key. */
18
+ labels?: TheoremLabels;
19
+ /** Widest the column may grow, as a CSS length. Default `960px`. */
20
+ maxWidth?: string;
21
+ /**
22
+ * Show the trace in place of the decision, from the host's own control; the
23
+ * built-in trace toggle then hides. Omit to keep the toggle. Needs a profile
24
+ * that records traces.
25
+ */
26
+ trace?: boolean;
27
+ className?: string;
28
+ style?: CSSProperties;
29
+ };
30
+ /**
31
+ * Drop-in surface for a decision profile served by `createTheoremDecisionHandler`:
32
+ * the state as fields or JSON, a Decide button (⌘/Ctrl+Enter), and the answers.
33
+ *
34
+ * ```tsx
35
+ * <TheoremDecision endpoint="/api/decision" />
36
+ * ```
37
+ */
38
+ export declare function TheoremDecision({ endpoint, http, transport, theme, mode, labels, ...rest }: TheoremDecisionProps): import("react").JSX.Element;
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
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';
6
+ import { createDecisionTransport, } from "../client/decision-transport.js";
7
+ import { useTheoremDecision } from "../hooks/use-theorem-decision.js";
8
+ import { TheoremDecisionAnswers } from "./DecisionAnswers.js";
9
+ import { ConsoleFrame, JsonRequest, RequestCard, RunBar } from "./ConsoleFrame.js";
10
+ import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
11
+ import { StateFields } from "./StateFields.js";
12
+ import { TheoremThemeProvider } from "./theme.js";
13
+ import { useTraceInspector } from "./TraceInspectorPanel.js";
14
+ /** The field's text as the state a decision sends, measured the way the kernel measures it. */
15
+ function checkState(text, maxBytes) {
16
+ let state;
17
+ try {
18
+ state = JSON.parse(text);
19
+ }
20
+ catch {
21
+ return { ok: false, reason: 'invalid_json' };
22
+ }
23
+ if (state === null)
24
+ return { ok: false, reason: 'null_state', state };
25
+ const bytes = new TextEncoder().encode(JSON.stringify(state)).byteLength;
26
+ if (maxBytes !== undefined && bytes > maxBytes)
27
+ return { ok: false, reason: 'too_large', bytes, state };
28
+ return { ok: true, state, bytes };
29
+ }
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
+ function stateNote(t, check, maxBytes, bytes) {
39
+ if (!check.ok) {
40
+ if (check.reason === 'too_large')
41
+ 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');
43
+ }
44
+ return maxBytes === undefined ? bytes(check.bytes) : t('@theorem.decision.state_size', { size: bytes(check.bytes), limit: bytes(maxBytes) });
45
+ }
46
+ function DecisionBody({ transport, iface, decision, defaultState = '{}', maxWidth = '960px', trace, className, style }) {
47
+ const t = useLabels();
48
+ const bytes = useBytes();
49
+ 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'));
54
+ const shown = check.state === undefined ? 'json' : view;
55
+ const inspector = useTraceInspector(iface, transport.traces, trace);
56
+ const deciding = decision.status === 'deciding';
57
+ const decide = () => {
58
+ if (check.ok)
59
+ void decision.decide(check.state);
60
+ };
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] }));
64
+ }
65
+ function DecisionForTransport(props) {
66
+ const t = useLabels();
67
+ const decision = useTheoremDecision(props.transport);
68
+ if (decision.describeFailure)
69
+ return _jsx(Banner, { status: "error", title: decision.describeFailure.error });
70
+ if (!decision.iface)
71
+ return _jsx(Spinner, { size: "lg", label: t('@theorem.chat.loading') });
72
+ return _jsx(DecisionBody, { ...props, iface: decision.iface, decision: decision });
73
+ }
74
+ /**
75
+ * Drop-in surface for a decision profile served by `createTheoremDecisionHandler`:
76
+ * the state as fields or JSON, a Decide button (⌘/Ctrl+Enter), and the answers.
77
+ *
78
+ * ```tsx
79
+ * <TheoremDecision endpoint="/api/decision" />
80
+ * ```
81
+ */
82
+ 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]);
86
+ return (_jsx(TheoremThemeProvider, { theme: theme, mode: mode, children: _jsx(TheoremLabelsProvider, { labels: labels, children: _jsx(DecisionForTransport, { ...rest, transport: resolved }) }) }));
87
+ }
@@ -0,0 +1,38 @@
1
+ import type { DefinedTheme } from '@astryxdesign/core/theme';
2
+ import { type CSSProperties } from 'react';
3
+ import { type HostTransport } from '../client/host-transport.ts';
4
+ import type { HttpOptions } from '../client/transport.ts';
5
+ import { type TheoremLabels } from './labels.ts';
6
+ export type TheoremHostProps = {
7
+ /** Where `createTheoremHostHandler` is mounted. Default `/api/host`. Ignored when `transport` is set. */
8
+ endpoint?: string;
9
+ /** Extra fetch options for the default HTTP transport (auth headers, custom fetch). */
10
+ http?: HttpOptions;
11
+ /** Bring your own transport (tests, playgrounds, non-HTTP hosts). */
12
+ transport?: HostTransport;
13
+ theme?: DefinedTheme;
14
+ mode?: 'system' | 'light' | 'dark';
15
+ /** Replacement lines by locale, for any `@theorem.*` or `@astryx.*` key. */
16
+ labels?: TheoremLabels;
17
+ /** Widest the column may grow, as a CSS length. Default `960px`. */
18
+ maxWidth?: string;
19
+ /**
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.
23
+ */
24
+ trace?: boolean;
25
+ className?: string;
26
+ style?: CSSProperties;
27
+ };
28
+ /**
29
+ * Drop-in console for a host profile served by `createTheoremHostHandler`:
30
+ * pick a tool, fill in the request its input schema draws, Run (⌘/Ctrl+Enter),
31
+ * and read the response laid out from what it returned — figures, charts,
32
+ * images, and the data itself. Gates are answered in place.
33
+ *
34
+ * ```tsx
35
+ * <TheoremHost endpoint="/api/host" />
36
+ * ```
37
+ */
38
+ export declare function TheoremHost({ endpoint, http, transport, theme, mode, labels, ...rest }: TheoremHostProps): import("react").JSX.Element;