@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,134 +1,134 @@
1
1
  /* Shared ink waveform + borderless controls (live shell and turn voice composer). */
2
2
 
3
3
  :root {
4
- --ink-ease: cubic-bezier(0.22, 1, 0.36, 1);
5
- --ink-duration: 0.3s;
4
+ --ink-ease: cubic-bezier(0.22, 1, 0.36, 1);
5
+ --ink-duration: 0.3s;
6
6
  }
7
7
 
8
8
  .ink-state {
9
- margin: 0;
10
- font-size: 0.75rem;
11
- font-weight: 700;
12
- letter-spacing: 0.06em;
13
- text-transform: lowercase;
14
- color: var(--color-ink);
9
+ margin: 0;
10
+ font-size: 0.75rem;
11
+ font-weight: 700;
12
+ letter-spacing: 0.06em;
13
+ text-transform: lowercase;
14
+ color: var(--color-ink);
15
15
  }
16
16
 
17
17
  .ink-wave-slot {
18
- flex: 1 1 auto;
19
- display: flex;
20
- align-items: flex-end;
21
- justify-content: center;
22
- min-height: min(52vh, 22rem);
23
- margin-bottom: 1.5rem;
24
- padding: 0 0.5rem;
18
+ flex: 1 1 auto;
19
+ display: flex;
20
+ align-items: flex-end;
21
+ justify-content: center;
22
+ min-height: min(52vh, 22rem);
23
+ margin-bottom: 1.5rem;
24
+ padding: 0 0.5rem;
25
25
  }
26
26
 
27
27
  .ink-wave {
28
- display: block;
29
- width: min(100%, 36rem);
30
- height: min(52vh, 22rem);
31
- color: var(--color-ink);
28
+ display: block;
29
+ width: min(100%, 36rem);
30
+ height: min(52vh, 22rem);
31
+ color: var(--color-ink);
32
32
  }
33
33
 
34
34
  .ink-wave--hero {
35
- width: min(100%, 56rem);
36
- height: 100%;
37
- min-height: 40dvh;
38
- max-height: 80dvh;
35
+ width: min(100%, 56rem);
36
+ height: 100%;
37
+ min-height: 40dvh;
38
+ max-height: 80dvh;
39
39
  }
40
40
 
41
41
  .ink-wave__bar {
42
- stroke: currentcolor;
43
- stroke-linecap: square;
42
+ stroke: currentcolor;
43
+ stroke-linecap: square;
44
44
  }
45
45
 
46
46
  .ink-divider {
47
- flex-shrink: 0;
48
- width: 100%;
49
- height: 0;
50
- margin: 0 0 1.5rem;
51
- border: none;
52
- border-top: 1px solid rgba(0, 0, 0, 0.14);
47
+ flex-shrink: 0;
48
+ width: 100%;
49
+ height: 0;
50
+ margin: 0 0 1.5rem;
51
+ border: none;
52
+ border-top: 1px solid rgba(0, 0, 0, 0.14);
53
53
  }
54
54
 
55
55
  .ink-controls {
56
- display: flex;
57
- align-items: center;
58
- justify-content: space-between;
59
- gap: 1rem;
60
- flex-shrink: 0;
56
+ display: flex;
57
+ align-items: center;
58
+ justify-content: space-between;
59
+ gap: 1rem;
60
+ flex-shrink: 0;
61
61
  }
62
62
 
63
63
  .ink-controls__left,
64
64
  .ink-controls__right {
65
- display: flex;
66
- align-items: center;
67
- gap: 0.65rem;
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 0.65rem;
68
68
  }
69
69
 
70
70
  .ink-control {
71
- display: inline-flex;
72
- align-items: center;
73
- justify-content: center;
74
- width: 2.5rem;
75
- height: 2.5rem;
76
- padding: 0;
77
- color: var(--color-ink);
78
- background: transparent;
79
- border: none;
80
- cursor: pointer;
81
- opacity: 0.55;
82
- transition: opacity var(--ink-duration) var(--ink-ease);
71
+ display: inline-flex;
72
+ align-items: center;
73
+ justify-content: center;
74
+ width: 2.5rem;
75
+ height: 2.5rem;
76
+ padding: 0;
77
+ color: var(--color-ink);
78
+ background: transparent;
79
+ border: none;
80
+ cursor: pointer;
81
+ opacity: 0.55;
82
+ transition: opacity var(--ink-duration) var(--ink-ease);
83
83
  }
84
84
 
85
85
  .ink-control:not(:disabled):hover {
86
- opacity: 1;
86
+ opacity: 1;
87
87
  }
88
88
 
89
89
  .ink-control:disabled {
90
- opacity: 0.22;
91
- cursor: default;
90
+ opacity: 0.22;
91
+ cursor: default;
92
92
  }
93
93
 
94
94
  .ink-control--active {
95
- opacity: 0.9;
95
+ opacity: 0.9;
96
96
  }
97
97
 
98
98
  .ink-tooltip {
99
- position: relative;
100
- display: inline-flex;
99
+ position: relative;
100
+ display: inline-flex;
101
101
  }
102
102
 
103
103
  .ink-tooltip__label {
104
- position: absolute;
105
- left: 50%;
106
- z-index: 6;
107
- padding: 0.28rem 0.45rem;
108
- font-family: var(--font-mono, ui-monospace, monospace);
109
- font-size: 0.62rem;
110
- font-weight: 700;
111
- letter-spacing: 0.08em;
112
- line-height: 1.2;
113
- text-transform: lowercase;
114
- white-space: nowrap;
115
- color: var(--color-paper, #f4f1ea);
116
- background: var(--color-ink, #111);
117
- border-radius: 0;
118
- pointer-events: none;
119
- transform: translateX(-50%);
104
+ position: absolute;
105
+ left: 50%;
106
+ z-index: 6;
107
+ padding: 0.28rem 0.45rem;
108
+ font-family: var(--font-mono, ui-monospace, monospace);
109
+ font-size: 0.62rem;
110
+ font-weight: 700;
111
+ letter-spacing: 0.08em;
112
+ line-height: 1.2;
113
+ text-transform: lowercase;
114
+ white-space: nowrap;
115
+ color: var(--color-paper, #f4f1ea);
116
+ background: var(--color-ink, #111);
117
+ border-radius: 0;
118
+ pointer-events: none;
119
+ transform: translateX(-50%);
120
120
  }
121
121
 
122
122
  .ink-tooltip__label--top {
123
- bottom: calc(100% + 0.45rem);
123
+ bottom: calc(100% + 0.45rem);
124
124
  }
125
125
 
126
126
  .ink-tooltip__label--bottom {
127
- top: calc(100% + 0.45rem);
127
+ top: calc(100% + 0.45rem);
128
128
  }
129
129
 
130
130
  @media (prefers-reduced-motion: reduce) {
131
- .ink-control {
132
- transition: none;
133
- }
131
+ .ink-control {
132
+ transition: none;
133
+ }
134
134
  }
@@ -0,0 +1,10 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * Cards in as many columns as fit, each as tall as its content. Each card,
4
+ * in order, goes under the shortest column so far, so short cards never
5
+ * leave a hole beside a tall one.
6
+ */
7
+ export declare function CardWall({ minWidth, children }: {
8
+ minWidth: number;
9
+ children: ReactNode;
10
+ }): import("react").JSX.Element;
package/ui/CardWall.js ADDED
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { HStack } from '@astryxdesign/core/HStack';
3
+ import { StackItem } from '@astryxdesign/core/Stack';
4
+ import { VStack } from '@astryxdesign/core/VStack';
5
+ import { Children, useLayoutEffect, useRef, useState } from 'react';
6
+ import { columnCount, wallColumns } from "./wall-columns.js";
7
+ /** The wall's width and gap, and each card's height by its place in `children`. */
8
+ function measureWall(wall, stacks, placed, cards) {
9
+ const heights = Array.from({ length: cards });
10
+ placed.forEach((column, at) => {
11
+ const shown = stacks[at]?.children;
12
+ column.forEach((card, row) => {
13
+ const element = shown?.[row];
14
+ if (element instanceof HTMLElement)
15
+ heights[card] = element.offsetHeight;
16
+ });
17
+ });
18
+ const gap = Number.parseFloat(getComputedStyle(wall).columnGap) || 0;
19
+ return { width: wall.clientWidth, gap, heights };
20
+ }
21
+ function sameWall(a, b) {
22
+ return (a.width === b.width &&
23
+ a.gap === b.gap &&
24
+ a.heights.length === b.heights.length &&
25
+ a.heights.every((height, at) => height === b.heights[at]));
26
+ }
27
+ /**
28
+ * Cards in as many columns as fit, each as tall as its content. Each card,
29
+ * in order, goes under the shortest column so far, so short cards never
30
+ * leave a hole beside a tall one.
31
+ */
32
+ export function CardWall({ minWidth, children }) {
33
+ const cards = Children.toArray(children);
34
+ const wallRef = useRef(null);
35
+ const stacks = useRef([]);
36
+ const [wall, setWall] = useState({ width: 0, gap: 0, heights: [] });
37
+ const placed = wallColumns(cards.map((_, at) => wall.heights[at]), columnCount(wall.width, minWidth, wall.gap), wall.gap);
38
+ const placedRef = useRef(placed);
39
+ placedRef.current = placed;
40
+ const layout = placed.map((column) => column.join(' ')).join('|');
41
+ useLayoutEffect(() => {
42
+ const node = wallRef.current;
43
+ if (!node || !layout)
44
+ return;
45
+ const measure = () => {
46
+ const next = measureWall(node, stacks.current, placedRef.current, cards.length);
47
+ setWall((shown) => (sameWall(shown, next) ? shown : next));
48
+ };
49
+ measure();
50
+ const observer = new ResizeObserver(measure);
51
+ observer.observe(node);
52
+ for (const stack of stacks.current) {
53
+ for (const card of stack?.children ?? [])
54
+ observer.observe(card);
55
+ }
56
+ return () => observer.disconnect();
57
+ }, [layout, cards.length]);
58
+ return (_jsx(HStack, { ref: wallRef, gap: 3, vAlign: "start", children: placed.map((column, at) => (_jsx(StackItem, { size: "fill", children: _jsx(VStack, { ref: (node) => {
59
+ stacks.current[at] = node;
60
+ }, gap: 3, children: column.map((card) => cards[card]) }) }, String(at)))) }));
61
+ }
@@ -1,14 +1,21 @@
1
1
  import { type ChatComposerInputHandle } from '@astryxdesign/core/Chat';
2
- import { type AttachmentValidationIssue, type ComposerMenuAction, type ComposerPendingMessage, type ComposerProfileInterface, type ComposerRunPhase } from '@theoremjs/agents/interface';
2
+ import { type AttachmentValidationIssue, type ComposerMenuAction, type ComposerPendingMessage, type ComposerProfileInterface, type ComposerRunPhase, type LiveProfileInterface } from '@theoremjs/agents/interface';
3
3
  import type { ClientFailure } from '../client/failure.ts';
4
- export declare const NO_FOCUS_RING: React.CSSProperties;
4
+ /** A slot the profile declares: its allowed values, the one chosen, and how to choose another. */
5
+ /** The effort selector's icon. The studio's Agents list shows the same one. */
6
+ export declare const EFFORT_ICON: import("react").ForwardRefExoticComponent<import("@tabler/icons-react").IconProps & import("react").RefAttributes<SVGSVGElement>>;
7
+ export type SlotChoices = {
8
+ declared: Readonly<Record<string, readonly string[]>>;
9
+ values: Readonly<Record<string, string>>;
10
+ onChange: (name: string, value: string) => void;
11
+ };
5
12
  export type ChatComposerBarProps = {
6
13
  iface: ComposerProfileInterface;
7
14
  draftText: string;
8
15
  pendingFiles: readonly File[];
9
16
  pendingVoice: readonly File[];
10
17
  pendingMessages: readonly ComposerPendingMessage[];
11
- /** Files the last send refused, one per reason; worded with the profile's lexicon. */
18
+ /** Files the last send refused, one per reason; worded with the profile's labels. */
12
19
  issues: readonly AttachmentValidationIssue[];
13
20
  phase: ComposerRunPhase;
14
21
  failure: ClientFailure | null;
@@ -29,11 +36,27 @@ export type ChatComposerBarProps = {
29
36
  modelId: string;
30
37
  effort?: string;
31
38
  }) => void;
39
+ /** The profile's `inputs.slots` with the value chosen for each; one selector per slot, beside the model's. */
40
+ slotChoices?: SlotChoices;
32
41
  onPendingMove: (id: string, direction: 'up' | 'down') => void;
33
42
  onPendingRemove: (id: string) => void;
34
43
  onPendingQueue: (id: string) => void;
35
44
  onPendingRestore: (id: string) => void;
36
45
  onPendingSendNow: (id: string) => void;
37
46
  };
38
- /** Astryx composer wired to Theorem's send / stop / queue / steer / stash matrix. */
39
- export declare function ChatComposerBar(props: ChatComposerBarProps): import("react").JSX.Element;
47
+ /** A Live call's text line: it is sent as typed, so there is nothing to attach, queue, or stop. */
48
+ export type LiveComposerBarProps = Omit<Pick<ChatComposerBarProps, 'draftText' | 'placeholder' | 'inputRef' | 'onDraftTextChange' | 'onSubmit'>, 'onSubmit'> & {
49
+ /** The typed line. The button reads the field, so this is the text, not the click. */
50
+ onSubmit: (text: string) => void;
51
+ /** The live profile, when the host has one. The placeholder uses its handle. */
52
+ iface?: LiveProfileInterface;
53
+ /** Profile handle, when the host has no interface object. Wins over `iface`. */
54
+ handle?: string;
55
+ /** Off while no call is connected. */
56
+ isDisabled: boolean;
57
+ };
58
+ /**
59
+ * Astryx composer wired to Theorem's send / stop / queue / steer / stash matrix.
60
+ * For a Live profile it is the text field and send button alone.
61
+ */
62
+ export declare function ChatComposerBar(props: ChatComposerBarProps | LiveComposerBarProps): import("react").JSX.Element;
@@ -12,20 +12,23 @@ import { Text } from '@astryxdesign/core/Text';
12
12
  import { Thumbnail } from '@astryxdesign/core/Thumbnail';
13
13
  import { Token } from '@astryxdesign/core/Token';
14
14
  import { VStack } from '@astryxdesign/core/VStack';
15
- import { IconArrowDown, IconArrowUp, IconBrain, IconCornerDownLeft, IconInputAi, IconMicrophone, IconPaperclip, IconPencil, IconPlayerRecordFilled, IconSend, IconStack2, IconX, } from '@tabler/icons-react';
16
- import { useEffect, useMemo, useRef, useState } from 'react';
17
- import { attachmentIssueText } from '@theoremjs/agents';
15
+ import { IconAdjustmentsHorizontal, IconArrowDown, IconArrowUp, IconBrain, IconCornerDownLeft, IconInputAi, IconMicrophone, IconPaperclip, IconPencil, IconPlayerRecordFilled, IconSend, IconStack2, IconX, } from '@tabler/icons-react';
16
+ import { attachmentIssueText, labelText } from '@theoremjs/agents';
18
17
  import { composerPendingPreview, effortSelectEnabled, interfaceEffortOptions, interfaceModelOptions, modelSelectEnabled, } from '@theoremjs/agents/interface';
18
+ import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
19
19
  import { stageComposerFiles } from "../client/composer-attachments.js";
20
- import { composerActionState } from "../client/composer-primary.js";
21
20
  import { composerDrawerSummary } from "../client/composer-drawer.js";
22
21
  import { isStashShortcut, resolveComposerHint, STASH_SHORTCUT, } from "../client/composer-hints.js";
22
+ import { composerActionState } from "../client/composer-primary.js";
23
23
  import { useComposerVoice } from "../components/use-composer-voice.js";
24
+ import { useEditorSelection } from "../components/use-editor-selection.js";
24
25
  import { composerDrawerLabel } from "./labels.js";
25
26
  import { TheoremLabelsProvider, useLabels } from "./labels-provider.js";
26
27
  import { VoiceNote } from "./VoiceNote.js";
27
- import { useEditorSelection } from "../components/use-editor-selection.js";
28
- export const NO_FOCUS_RING = { '--focus-outline-width': '0px' };
28
+ const NO_FOCUS_RING = { '--focus-outline-width': '0px' };
29
+ /** A slot the profile declares: its allowed values, the one chosen, and how to choose another. */
30
+ /** The effort selector's icon. The studio's Agents list shows the same one. */
31
+ export const EFFORT_ICON = IconBrain;
29
32
  const isImage = (file) => file.type.startsWith('image/');
30
33
  const anyFile = () => true;
31
34
  /** Object URLs for the files `keep` (a module-level predicate) accepts, revoked when the files change. */
@@ -53,35 +56,46 @@ function PendingRow(props) {
53
56
  /** The model and effort choices this profile offers; efforts only for a chosen model. */
54
57
  function generationOptions(iface, selectedModel) {
55
58
  const models = modelSelectEnabled(iface) ? interfaceModelOptions(iface) : [];
56
- const efforts = selectedModel && effortSelectEnabled(iface, selectedModel) ? interfaceEffortOptions(iface, selectedModel) : [];
59
+ const efforts = selectedModel && effortSelectEnabled(iface, selectedModel)
60
+ ? interfaceEffortOptions(iface, selectedModel)
61
+ : [];
57
62
  return { models, efforts };
58
63
  }
59
64
  function ModelSelector(props) {
60
- const t = useLabels();
61
65
  if (props.models.length === 0)
62
66
  return null;
63
- return (_jsx(Selector, { label: t('@theorem.composer.model'), isLabelHidden: true, size: "sm", variant: "ghost", startIcon: _jsx(IconInputAi, { size: 14 }), placement: "above", isDisabled: props.isDisabled, value: props.value ?? '', options: props.models.map((m) => ({ value: m.id, label: m.id, description: m.label !== m.id ? m.label : undefined })), onChange: props.onChange }));
67
+ return (_jsx(Selector, { label: props.label, isLabelHidden: true, size: "sm", variant: "ghost", startIcon: _jsx(IconInputAi, { size: 14 }), placement: "above", isDisabled: props.isDisabled, value: props.value ?? '', options: props.models.map((m) => ({
68
+ value: m.id,
69
+ label: m.id,
70
+ description: m.label !== m.id ? m.label : undefined,
71
+ })), onChange: props.onChange }));
64
72
  }
65
73
  function EffortSelector(props) {
66
- const t = useLabels();
67
74
  if (props.efforts.length === 0)
68
75
  return null;
69
- return (_jsx(Selector, { label: t('@theorem.composer.effort'), isLabelHidden: true, size: "sm", variant: "ghost", startIcon: _jsx(IconBrain, { size: 14 }), placement: "above", isDisabled: props.isDisabled, value: props.value ?? '', options: props.efforts.map((e) => ({ value: e.alias, label: e.alias, description: e.level })), onChange: props.onChange }));
76
+ return (_jsx(Selector, { label: props.label, isLabelHidden: true, size: "sm", variant: "ghost", startIcon: _jsx(EFFORT_ICON, { size: 14 }), placement: "above", isDisabled: props.isDisabled, value: props.value ?? '', options: props.efforts.map((e) => ({ value: e.alias, label: e.alias, description: e.level })), onChange: props.onChange }));
77
+ }
78
+ function SlotSelectors(props) {
79
+ if (!props.choices)
80
+ return null;
81
+ const { declared, values, onChange } = props.choices;
82
+ return Object.entries(declared).map(([name, allowed]) => (_jsx(Selector, { label: name, isLabelHidden: true, size: "sm", variant: "ghost", startIcon: _jsx(IconAdjustmentsHorizontal, { size: 14 }), placement: "above", isDisabled: props.isDisabled, value: values[name] ?? '', options: allowed.map((value) => ({ value, label: value })), onChange: (value) => onChange(name, value) }, name)));
70
83
  }
71
84
  function GenerationSelect(props) {
72
85
  const { models, efforts } = generationOptions(props.iface, props.selectedModel);
73
- if (models.length + efforts.length === 0)
86
+ const slotCount = Object.keys(props.slotChoices?.declared ?? {}).length;
87
+ if (models.length + efforts.length + slotCount === 0)
74
88
  return null;
75
- return (_jsxs(HStack, { gap: 1, children: [_jsx(ModelSelector, { models: models, value: props.selectedModel, isDisabled: props.isDisabled, onChange: (modelId) => props.onChange({ modelId }) }), _jsx(EffortSelector, { efforts: efforts, value: props.selectedEffort, isDisabled: props.isDisabled, onChange: (effort) => {
89
+ return (_jsxs(HStack, { gap: 1, children: [_jsx(ModelSelector, { label: labelText('composer.model', {}, props.iface.labels), models: models, value: props.selectedModel, isDisabled: props.isDisabled, onChange: (modelId) => props.onChange({ modelId }) }), _jsx(EffortSelector, { label: labelText('composer.effort', {}, props.iface.labels), efforts: efforts, value: props.selectedEffort, isDisabled: props.isDisabled, onChange: (effort) => {
76
90
  if (props.selectedModel)
77
91
  props.onChange({ modelId: props.selectedModel, effort });
78
- } })] }));
92
+ } }), _jsx(SlotSelectors, { choices: props.slotChoices, isDisabled: props.isDisabled })] }));
79
93
  }
80
94
  function composerStatus(args) {
81
95
  if (args.failure)
82
96
  return { type: 'error', message: args.failure.error };
83
97
  const warning = [
84
- ...args.issues.map((issue) => attachmentIssueText(issue, args.lexicon)),
98
+ ...args.issues.map((issue) => attachmentIssueText(issue, args.labels)),
85
99
  args.voiceFailure?.error,
86
100
  ]
87
101
  .filter(Boolean)
@@ -94,7 +108,7 @@ function PendingDrawer(props) {
94
108
  return (_jsx(ChatComposerDrawer, { count: props.summary.count, label: composerDrawerLabel(t, props.summary), children: _jsxs(VStack, { gap: 2, width: "100%", children: [props.messages.map((message) => (_jsx(PendingRow, { message: message, ...props.pendingActions }, message.id))), props.imageFiles.length + props.voiceFiles.length > 0 ? (_jsxs(HStack, { gap: 2, wrap: "wrap", vAlign: "center", children: [props.imageFiles.map(({ file, index, preview }) => (_jsx(Thumbnail, { src: preview, alt: file.name, label: file.name, onRemove: () => props.onAttachmentRemove(index) }, `${file.name}:${String(index)}`))), props.voiceFiles.map((file, index) => (_jsx(VoiceNote, { src: props.voiceUrls[index] ?? '', mimeType: file.type || undefined, onRemove: props.onVoiceRemove }, `voice:${file.name}`)))] })) : null, props.otherFiles.length > 0 ? (_jsx(HStack, { gap: 2, wrap: "wrap", children: props.otherFiles.map(({ file, index }) => (_jsx(Token, { label: file.name, onRemove: () => props.onAttachmentRemove(index) }, `${file.name}:${String(index)}`))) })) : null] }) }));
95
109
  }
96
110
  /** A hidden file input behind the paperclip button. */
97
- function AttachFilesButton({ accept, onFiles }) {
111
+ function AttachFilesButton({ accept, onFiles, }) {
98
112
  const t = useLabels();
99
113
  const label = t('@theorem.composer.attach');
100
114
  const fileInputRef = useRef(null);
@@ -107,7 +121,7 @@ function AttachFilesButton({ accept, onFiles }) {
107
121
  }
108
122
  return (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, hidden: true, accept: accept, onChange: handleChange }), _jsx(IconButton, { label: label, tooltip: label, size: "sm", variant: "ghost", icon: _jsx(IconPaperclip, { size: 16 }), onClick: () => fileInputRef.current?.click() })] }));
109
123
  }
110
- function SendMenu({ actions, onAction }) {
124
+ function SendMenu({ actions, onAction, }) {
111
125
  const t = useLabels();
112
126
  if (actions.length === 0)
113
127
  return null;
@@ -138,7 +152,11 @@ function ComposerHint({ hint, onAction }) {
138
152
  function useStagedFiles(pendingFiles, pendingVoice) {
139
153
  const previews = useObjectUrls(pendingFiles, isImage);
140
154
  const voiceUrls = useObjectUrls(pendingVoice, anyFile);
141
- const staged = pendingFiles.map((file, index) => ({ file, index, preview: previews[index] }));
155
+ const staged = pendingFiles.map((file, index) => ({
156
+ file,
157
+ index,
158
+ preview: previews[index],
159
+ }));
142
160
  return {
143
161
  imageFiles: staged.filter((entry) => entry.preview !== undefined),
144
162
  otherFiles: staged.filter((entry) => entry.preview === undefined),
@@ -149,7 +167,12 @@ function useStagedFiles(pendingFiles, pendingVoice) {
149
167
  function useFileStaging(pendingFiles, maxFiles, props) {
150
168
  const [stagingIssues, setStagingIssues] = useState([]);
151
169
  function stageFiles(incoming) {
152
- const staged = stageComposerFiles({ existing: pendingFiles, incoming, maxFiles, voiceCount: props.pendingVoice.length });
170
+ const staged = stageComposerFiles({
171
+ existing: pendingFiles,
172
+ incoming,
173
+ maxFiles,
174
+ voiceCount: props.pendingVoice.length,
175
+ });
153
176
  const added = staged.files.slice(pendingFiles.length);
154
177
  if (added.length > 0)
155
178
  props.onFilesSelected(added);
@@ -158,7 +181,7 @@ function useFileStaging(pendingFiles, maxFiles, props) {
158
181
  return { stageFiles, stagingIssues };
159
182
  }
160
183
  /** The record button, when the profile takes voice. */
161
- function ComposerRecordButton({ hasVoice, voice }) {
184
+ function ComposerRecordButton({ hasVoice, voice, }) {
162
185
  if (!hasVoice)
163
186
  return null;
164
187
  return _jsx(RecordButton, { recording: voice.recording, onToggle: () => void voice.toggleRecording() });
@@ -188,9 +211,40 @@ function ComposerTextInput({ editorRef, handleRef, onStash, }) {
188
211
  onStash?.();
189
212
  } }));
190
213
  }
191
- /** Astryx composer wired to Theorem's send / stop / queue / steer / stash matrix. */
214
+ function isLiveComposer(props) {
215
+ return 'isDisabled' in props;
216
+ }
217
+ /**
218
+ * Astryx composer wired to Theorem's send / stop / queue / steer / stash matrix.
219
+ * For a Live profile it is the text field and send button alone.
220
+ */
192
221
  export function ChatComposerBar(props) {
193
- return (_jsx(TheoremLabelsProvider, { children: _jsx(ChatComposerBarBody, { ...props }) }));
222
+ return (_jsx(TheoremLabelsProvider, { children: isLiveComposer(props) ? (_jsx(LiveComposerBarBody, { ...props })) : (_jsx(ChatComposerBarBody, { ...props })) }));
223
+ }
224
+ function LiveComposerBarBody(props) {
225
+ const t = useLabels();
226
+ const editorRef = useRef(null);
227
+ const setEditor = useCallback((node) => {
228
+ editorRef.current = node;
229
+ const outer = props.inputRef;
230
+ if (typeof outer === 'function')
231
+ outer(node);
232
+ else if (outer)
233
+ outer.current = node;
234
+ }, [props.inputRef]);
235
+ // why: The button used to send only the React draft. Typing lives in the field first, so an
236
+ // empty draft left the button dead. Read the field.
237
+ const submitTyped = () => {
238
+ const text = (editorRef.current?.getValue() ?? props.draftText).trim();
239
+ if (!text || props.isDisabled)
240
+ return;
241
+ props.onSubmit(text);
242
+ props.onDraftTextChange('');
243
+ };
244
+ return (_jsx(ChatComposer, { style: NO_FOCUS_RING, value: props.draftText, onChange: props.onDraftTextChange, onSubmit: props.onSubmit, isDisabled: props.isDisabled, placeholder: props.placeholder ??
245
+ t('@theorem.composer.placeholder', {
246
+ handle: props.handle ?? props.iface?.identity.handle ?? '',
247
+ }), input: _jsx(ChatComposerInput, { handleRef: setEditor }), sendButton: _jsx(ChatSendButton, { isDisabled: props.isDisabled, onSend: submitTyped }) }));
194
248
  }
195
249
  function ChatComposerBarBody(props) {
196
250
  const { iface, phase } = props;
@@ -199,7 +253,7 @@ function ChatComposerBarBody(props) {
199
253
  const pendingFiles = useMemo(() => [...props.pendingFiles], [props.pendingFiles]);
200
254
  const voice = useComposerVoice({
201
255
  inputs,
202
- lexicon: iface.lexicon,
256
+ labels: iface.labels,
203
257
  pendingFiles,
204
258
  onVoiceStaged: props.onVoiceStaged,
205
259
  onVoiceClear: props.onVoiceClear,
@@ -231,21 +285,22 @@ function ChatComposerBarBody(props) {
231
285
  }
232
286
  const { stageFiles, stagingIssues } = useFileStaging(pendingFiles, inputs.maxFiles, props);
233
287
  return (_jsx(ChatComposer
234
- // No keyboard focus ring on the composer (product choice): zero Astryx's
288
+ // why: No keyboard focus ring on the composer (product choice): zero Astryx's
235
289
  // focus-outline token for this subtree. The editor still shows its caret.
236
290
  , {
237
- // No keyboard focus ring on the composer (product choice): zero Astryx's
291
+ // why: No keyboard focus ring on the composer (product choice): zero Astryx's
238
292
  // focus-outline token for this subtree. The editor still shows its caret.
239
293
  style: NO_FOCUS_RING, value: props.draftText, onChange: props.onDraftTextChange, onSubmit: runPrimary, onStop: props.onStop, isStopShown: primary === 'stop', placeholder: voice.recording
240
294
  ? t('@theorem.composer.listening')
241
- : (props.placeholder ?? t('@theorem.composer.placeholder', { handle: iface.identity.handle })),
242
- // A profile that takes no text has no text field (false, not null: null gets Astryx's default one).
243
- input: inputs.text && (_jsx(ComposerTextInput, { editorRef: editorRef, handleRef: props.inputRef, onStash: canStash ? () => props.onMenuAction('stash') : undefined })), drawer: composerDrawer(props, stagedFiles, () => voice.discardRecordingOrVoice()), headerActions: inputs.attachments ? _jsx(AttachFilesButton, { accept: inputs.attachments.acceptAttr, onFiles: stageFiles }) : undefined, headerContext: hint ? _jsx(ComposerHint, { hint: hint, onAction: () => props.onMenuAction('stash') }) : undefined, footerActions: _jsx(GenerationSelect, { iface: iface, selectedModel: props.selectedModel, selectedEffort: props.selectedEffort, isDisabled: phase === 'streaming', onChange: props.onGenerationChange }),
244
- // Astryx: sendActions render to the left of the send button, at size="md".
295
+ : (props.placeholder ??
296
+ t('@theorem.composer.placeholder', { handle: iface.identity.handle })),
297
+ // why: A profile that takes no text has no text field (false, not null: null gets Astryx's default one).
298
+ input: inputs.text && (_jsx(ComposerTextInput, { editorRef: editorRef, handleRef: props.inputRef, onStash: canStash ? () => props.onMenuAction('stash') : undefined })), drawer: composerDrawer(props, stagedFiles, () => voice.discardRecordingOrVoice()), headerActions: inputs.attachments ? (_jsx(AttachFilesButton, { accept: inputs.attachments.acceptAttr, onFiles: stageFiles })) : undefined, headerContext: hint ? _jsx(ComposerHint, { hint: hint, onAction: () => props.onMenuAction('stash') }) : undefined, footerActions: _jsx(GenerationSelect, { iface: iface, slotChoices: props.slotChoices, selectedModel: props.selectedModel, selectedEffort: props.selectedEffort, isDisabled: phase === 'streaming', onChange: props.onGenerationChange }),
299
+ // why: Astryx: sendActions render to the left of the send button, at size="md".
245
300
  sendActions: _jsxs(_Fragment, { children: [_jsx(SendMenu, { actions: menuActions, onAction: props.onMenuAction }), _jsx(ComposerRecordButton, { hasVoice: Boolean(inputs.voice), voice: voice })] }), sendButton: _jsx(ChatSendButton, { isStopShown: primary === 'stop', isDisabled: primaryDisabled, onSend: runPrimary, onStop: props.onStop }), status: composerStatus({
246
301
  failure: props.failure,
247
302
  issues: [...props.issues, ...stagingIssues],
248
303
  voiceFailure: voice.failure,
249
- lexicon: iface.lexicon,
304
+ labels: iface.labels,
250
305
  }) }));
251
306
  }
@@ -1,6 +1,7 @@
1
1
  import { type ChatMessageStatus } from '@astryxdesign/core/Chat';
2
+ import type { LabelOverrides } from '@theoremjs/agents';
3
+ import { type TranscriptBlock } from '@theoremjs/agents/interface';
2
4
  import { type ReactNode } from 'react';
3
- import type { TranscriptBlock } from '@theoremjs/agents/interface';
4
5
  import type { AnsweringGate } from '../client/tool-resume.ts';
5
6
  import { type ToolDecision } from './ToolGateCard.tsx';
6
7
  export type ChatTranscriptProps = {
@@ -16,11 +17,23 @@ export type ChatTranscriptProps = {
16
17
  /** Signed in at a gate: `secret` is a key the user typed; after an OAuth callback there is none. */
17
18
  onAuthenticated?: (index: number, secret?: string) => void;
18
19
  emptyState?: ReactNode;
20
+ /**
21
+ * Labelled dividers, keyed by block id: each is drawn above the turn that
22
+ * starts at its block, and that block always starts a turn of its own.
23
+ */
24
+ dividers?: Readonly<Record<string, string>>;
19
25
  /**
20
26
  * Set for image profiles: generated images show large, framed to their own
21
27
  * aspect ratio, with a matching skeleton while one generates.
22
28
  */
23
29
  imageOutput?: ImageOutput;
30
+ /**
31
+ * Show what was used, as tokens and cost: each reply's total under it, and
32
+ * on an agent tool's call row what the agent it ran used. Off by default.
33
+ */
34
+ usage?: boolean;
35
+ /** The profile's labels (`iface.labels`): a stopped turn says why in its words. */
36
+ labels?: LabelOverrides;
24
37
  };
25
38
  /** Image-profile display: `ratio` is the profile's pinned aspect ratio, if any. */
26
39
  export type ImageOutput = {