@parallelworks/ui 0.40.1 → 0.42.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 (289) hide show
  1. package/README.md +25 -0
  2. package/dist/ai/adapter/memoryStore.js +7 -0
  3. package/dist/ai/adapter/mock/mockAdapter.js +52 -17
  4. package/dist/ai/adapter/openai/createOpenAIStream.js +1 -1
  5. package/dist/ai/adapter/openai/index.d.ts +1 -1
  6. package/dist/ai/adapter/openai/sse.d.ts +8 -2
  7. package/dist/ai/adapter/openai/sse.js +40 -4
  8. package/dist/ai/adapter/openai/wire.d.ts +6 -0
  9. package/dist/ai/components/AllocationSelector.d.ts +3 -4
  10. package/dist/ai/components/AllocationSelector.js +37 -33
  11. package/dist/ai/components/AttachmentManager.js +7 -7
  12. package/dist/ai/components/AttachmentUpload.js +5 -5
  13. package/dist/ai/components/BranchNavigator.js +1 -1
  14. package/dist/ai/components/ChatEmptyState.js +2 -2
  15. package/dist/ai/components/ChatInput.js +10 -8
  16. package/dist/ai/components/ChatLayout.js +4 -5
  17. package/dist/ai/components/ChatMessage.js +12 -12
  18. package/dist/ai/components/ChatMessageList.js +9 -9
  19. package/dist/ai/components/ChatSidebar.js +8 -9
  20. package/dist/ai/components/ChatThread.js +4 -4
  21. package/dist/ai/components/ComposerChrome.d.ts +2 -2
  22. package/dist/ai/components/ComposerChrome.js +3 -6
  23. package/dist/ai/components/DragOverlay.js +2 -2
  24. package/dist/ai/components/ModelSelector.d.ts +5 -3
  25. package/dist/ai/components/ModelSelector.js +102 -339
  26. package/dist/ai/components/PastedText.js +11 -11
  27. package/dist/ai/components/ProviderIssueBanner.d.ts +2 -0
  28. package/dist/ai/components/ProviderIssueBanner.js +34 -16
  29. package/dist/ai/components/Reasoning.js +4 -4
  30. package/dist/ai/components/ShareDialog.js +43 -35
  31. package/dist/ai/components/SlashMenu.d.ts +12 -2
  32. package/dist/ai/components/SlashMenu.js +29 -13
  33. package/dist/ai/components/agent/ActivityLine.js +3 -3
  34. package/dist/ai/components/agent/AgentMessageParts.js +20 -20
  35. package/dist/ai/components/agent/PermissionPicker.js +13 -11
  36. package/dist/ai/components/conversationActions.d.ts +4 -6
  37. package/dist/ai/components/conversationActions.js +9 -13
  38. package/dist/ai/core/ChatProvider.js +4 -4
  39. package/dist/ai/core/config.js +1 -1
  40. package/dist/ai/core/parts.d.ts +5 -0
  41. package/dist/ai/core/parts.js +28 -1
  42. package/dist/ai/core/providerIssues.d.ts +7 -0
  43. package/dist/ai/core/providerIssues.js +9 -1
  44. package/dist/ai/core/useKeyboardShortcuts.d.ts +4 -0
  45. package/dist/ai/core/useKeyboardShortcuts.js +8 -2
  46. package/dist/ai/index.d.ts +0 -1
  47. package/dist/ai/index.js +1 -2
  48. package/dist/ai/strings.d.ts +19 -0
  49. package/dist/ai/strings.js +18 -1
  50. package/dist/ai/ui/Avatar.js +5 -5
  51. package/dist/ai/ui/ConversationSidebar.d.ts +0 -1
  52. package/dist/ai/ui/ConversationSidebar.js +6 -7
  53. package/dist/ai/ui/Markdown.js +3 -3
  54. package/dist/ai/ui/Notice.js +6 -6
  55. package/dist/ai/ui/chip.d.ts +4 -0
  56. package/dist/ai/ui/chip.js +8 -0
  57. package/dist/ai/ui/sidebar.d.ts +6 -5
  58. package/dist/ai/ui/sidebar.js +39 -43
  59. package/dist/components/ActionMenu.d.ts +13 -0
  60. package/dist/components/ActionMenu.js +30 -0
  61. package/dist/components/Avatar.js +6 -6
  62. package/dist/components/Breadcrumbs.js +1 -1
  63. package/dist/components/Button.js +10 -10
  64. package/dist/components/Callout.js +2 -2
  65. package/dist/components/Card.js +3 -3
  66. package/dist/components/Combobox.d.ts +66 -0
  67. package/dist/components/Combobox.js +393 -0
  68. package/dist/components/ConfirmModal.js +11 -11
  69. package/dist/components/ContextMenu.d.ts +23 -0
  70. package/dist/components/ContextMenu.js +89 -0
  71. package/dist/components/CopyToClipboard.js +2 -2
  72. package/dist/components/CreateModal.d.ts +17 -36
  73. package/dist/components/CreateModal.js +97 -259
  74. package/dist/components/DescriptionList.js +8 -8
  75. package/dist/components/Filter.js +2 -2
  76. package/dist/components/FilterPill.js +2 -2
  77. package/dist/components/HoverCard.d.ts +14 -0
  78. package/dist/components/HoverCard.js +33 -0
  79. package/dist/components/IconButton.d.ts +5 -5
  80. package/dist/components/IconButton.js +18 -22
  81. package/dist/components/InfoTip.d.ts +15 -0
  82. package/dist/components/InfoTip.js +60 -0
  83. package/dist/components/Input.js +5 -5
  84. package/dist/components/LetterBadge.js +2 -2
  85. package/dist/components/Loader.js +3 -3
  86. package/dist/components/Markdown.js +1 -1
  87. package/dist/components/MarkdownImpl.js +2 -2
  88. package/dist/components/Menu.d.ts +33 -0
  89. package/dist/components/Menu.js +86 -0
  90. package/dist/components/MenuEntries.d.ts +56 -0
  91. package/dist/components/MenuEntries.js +123 -0
  92. package/dist/components/Modal.d.ts +29 -0
  93. package/dist/components/Modal.js +47 -0
  94. package/dist/components/Overview.js +11 -10
  95. package/dist/components/PageHeader.d.ts +10 -16
  96. package/dist/components/PageHeader.js +69 -95
  97. package/dist/components/Pagination.js +5 -5
  98. package/dist/components/Provider.d.ts +5 -11
  99. package/dist/components/Provider.js +11 -14
  100. package/dist/components/Radio.d.ts +8 -0
  101. package/dist/components/Radio.js +16 -0
  102. package/dist/components/SectionHeader.js +2 -2
  103. package/dist/components/SegmentedControl.d.ts +6 -5
  104. package/dist/components/SegmentedControl.js +34 -30
  105. package/dist/components/Select.d.ts +27 -0
  106. package/dist/components/Select.js +78 -0
  107. package/dist/components/SettingModeOption.d.ts +5 -8
  108. package/dist/components/SettingModeOption.js +10 -12
  109. package/dist/components/SettingRow.js +4 -4
  110. package/dist/components/SettingsCard.js +4 -4
  111. package/dist/components/SettingsGroup.js +2 -2
  112. package/dist/components/Sort.js +1 -1
  113. package/dist/components/StatusBadge.js +7 -7
  114. package/dist/components/StatusIcons.js +4 -4
  115. package/dist/components/Switch.d.ts +22 -0
  116. package/dist/components/Switch.js +28 -0
  117. package/dist/components/Table.js +11 -11
  118. package/dist/components/Tooltip.d.ts +24 -20
  119. package/dist/components/Tooltip.js +58 -76
  120. package/dist/components/floating.d.ts +14 -0
  121. package/dist/components/floating.js +21 -0
  122. package/dist/components/search.d.ts +2 -0
  123. package/dist/components/search.js +14 -0
  124. package/dist/editor/Editor.js +1 -1
  125. package/dist/editor/EditorField.js +16 -21
  126. package/dist/editor/FormCodePanel.js +19 -19
  127. package/dist/editor/Monaco.js +3 -3
  128. package/dist/file-explorer/FileExplorer.js +77 -81
  129. package/dist/file-explorer/FileExplorerContext.js +2 -2
  130. package/dist/file-explorer/FilePreview/CodePreviewEditor.js +1 -1
  131. package/dist/file-explorer/FilePreview/Inline.js +1 -1
  132. package/dist/file-explorer/FilePreview/StreamedBadge.js +5 -6
  133. package/dist/file-explorer/FilePreview/Viewer.js +8 -8
  134. package/dist/file-explorer/FilePreview/index.d.ts +1 -1
  135. package/dist/file-explorer/FilePreview/index.js +49 -83
  136. package/dist/file-explorer/FileRow.d.ts +4 -3
  137. package/dist/file-explorer/FileRow.js +32 -38
  138. package/dist/file-explorer/FileRowsWindow.d.ts +3 -2
  139. package/dist/file-explorer/FileRowsWindow.js +4 -4
  140. package/dist/file-explorer/FileTable.d.ts +3 -2
  141. package/dist/file-explorer/FileTable.js +3 -3
  142. package/dist/file-explorer/LoadMoreRow.js +1 -1
  143. package/dist/file-explorer/TreeView.js +11 -16
  144. package/dist/file-explorer/helpers.js +11 -12
  145. package/dist/file-explorer/toasts.js +11 -11
  146. package/dist/form/FieldGroup.d.ts +11 -0
  147. package/dist/form/FieldGroup.js +36 -0
  148. package/dist/form/FieldWrapper.js +4 -4
  149. package/dist/form/Fields/BooleanField.js +23 -33
  150. package/dist/form/Fields/CheckboxGroupField.d.ts +1 -1
  151. package/dist/form/Fields/CheckboxGroupField.js +9 -13
  152. package/dist/form/Fields/ColorField.d.ts +1 -2
  153. package/dist/form/Fields/ColorField.js +6 -11
  154. package/dist/form/Fields/DropdownField.d.ts +3 -13
  155. package/dist/form/Fields/DropdownField.js +40 -27
  156. package/dist/form/Fields/DurationField.d.ts +1 -2
  157. package/dist/form/Fields/DurationField.js +25 -32
  158. package/dist/form/Fields/LabelField.js +2 -2
  159. package/dist/form/Fields/MultiDropdownField.d.ts +6 -12
  160. package/dist/form/Fields/MultiDropdownField.js +31 -22
  161. package/dist/form/Fields/NumberField.d.ts +1 -1
  162. package/dist/form/Fields/NumberField.js +65 -35
  163. package/dist/form/Fields/ObjectField.d.ts +1 -1
  164. package/dist/form/Fields/ObjectField.js +12 -43
  165. package/dist/form/Fields/RadioField.d.ts +1 -1
  166. package/dist/form/Fields/RadioField.js +30 -41
  167. package/dist/form/Fields/RangeField.d.ts +1 -2
  168. package/dist/form/Fields/RangeField.js +23 -16
  169. package/dist/form/Fields/StringField.d.ts +1 -2
  170. package/dist/form/Fields/StringField.js +58 -26
  171. package/dist/form/Fields/TextAreaField.js +12 -10
  172. package/dist/form/Form.d.ts +30 -48
  173. package/dist/form/Form.js +306 -412
  174. package/dist/form/FormLayout.js +9 -9
  175. package/dist/form/Wizard/WizardContainer.d.ts +1 -4
  176. package/dist/form/Wizard/WizardContainer.js +40 -56
  177. package/dist/form/Wizard/WizardNavigation.js +1 -1
  178. package/dist/form/Wizard/WizardStepContent.d.ts +1 -5
  179. package/dist/form/Wizard/WizardStepContent.js +11 -16
  180. package/dist/form/Wizard/WizardStepIndicator.js +6 -6
  181. package/dist/form/Wizard/types.d.ts +0 -14
  182. package/dist/form/fieldRegistry.d.ts +1 -1
  183. package/dist/form/fieldRegistry.js +6 -6
  184. package/dist/form/formEditing.js +1 -1
  185. package/dist/form/index.d.ts +5 -4
  186. package/dist/form/index.js +6 -7
  187. package/dist/form/types/fieldComponentTypes.d.ts +8 -10
  188. package/dist/form/useChoiceField.d.ts +20 -0
  189. package/dist/form/useChoiceField.js +49 -0
  190. package/dist/form/useFieldValue.d.ts +7 -0
  191. package/dist/form/useFieldValue.js +20 -0
  192. package/dist/form/useParsedOpts.d.ts +3 -1
  193. package/dist/form/useParsedOpts.js +12 -17
  194. package/dist/form/utils/dependentOptions.d.ts +8 -4
  195. package/dist/form/utils/dependentOptions.js +25 -17
  196. package/dist/form/utils/fieldItems.d.ts +10 -0
  197. package/dist/form/utils/fieldItems.js +11 -0
  198. package/dist/form/utils/fieldPath.d.ts +10 -0
  199. package/dist/form/utils/fieldPath.js +20 -0
  200. package/dist/form/utils/shareEqual.d.ts +6 -0
  201. package/dist/form/utils/shareEqual.js +38 -0
  202. package/dist/graph/AnnotationBanner.js +8 -8
  203. package/dist/graph/Collapse.d.ts +1 -0
  204. package/dist/graph/Collapse.js +6 -27
  205. package/dist/graph/Connectors.js +5 -5
  206. package/dist/graph/DependencyGraph.js +20 -19
  207. package/dist/graph/GraphEditor.js +30 -41
  208. package/dist/graph/GraphEditorDialogs.js +17 -17
  209. package/dist/graph/GraphNode.js +1 -1
  210. package/dist/graph/InputDialog.js +19 -22
  211. package/dist/graph/InputsEditor.js +58 -66
  212. package/dist/graph/JobSummary.js +42 -39
  213. package/dist/graph/MatrixGroup.js +1 -1
  214. package/dist/graph/ProblemsButton.js +18 -19
  215. package/dist/graph/Reveal.js +1 -1
  216. package/dist/graph/ShortcutsButton.js +15 -18
  217. package/dist/graph/SuggestionInput.d.ts +5 -10
  218. package/dist/graph/SuggestionInput.js +14 -18
  219. package/dist/graph/ToolbarPopover.d.ts +11 -6
  220. package/dist/graph/ToolbarPopover.js +35 -67
  221. package/dist/graph/TreeView.js +24 -37
  222. package/dist/graph/WorkflowSettingsDialog.js +4 -4
  223. package/dist/graph/editorChrome.js +13 -12
  224. package/dist/graph/editorFields.js +29 -29
  225. package/dist/graph/editorPrimitives.d.ts +1 -1
  226. package/dist/graph/editorPrimitives.js +10 -7
  227. package/dist/graph/editorRows.js +103 -97
  228. package/dist/graph/fieldProblems.js +2 -2
  229. package/dist/graph/graphEditorState.d.ts +4 -3
  230. package/dist/graph/inputRefs.js +9 -9
  231. package/dist/graph/settingsViews.js +6 -7
  232. package/dist/graph/stepWith.js +6 -6
  233. package/dist/index.d.ts +14 -15
  234. package/dist/index.js +15 -14
  235. package/dist/list/ActionToolbar.d.ts +6 -14
  236. package/dist/list/ActionToolbar.js +48 -96
  237. package/dist/list/ListPage.d.ts +6 -12
  238. package/dist/list/ListPage.js +46 -29
  239. package/dist/list/ListPager.js +4 -4
  240. package/dist/list/ListTable.js +5 -5
  241. package/dist/list/ListViewControls.d.ts +5 -4
  242. package/dist/list/ListViewControls.js +177 -267
  243. package/dist/list/UserHoverCard.d.ts +3 -17
  244. package/dist/list/UserHoverCard.js +21 -45
  245. package/dist/list/index.d.ts +3 -4
  246. package/dist/list/index.js +3 -4
  247. package/dist/list/listSearch.js +12 -11
  248. package/dist/list/rowMenu.d.ts +63 -0
  249. package/dist/list/rowMenu.js +134 -0
  250. package/dist/logviewer/LogViewer.js +26 -26
  251. package/dist/styles.css +1 -1
  252. package/package.json +7 -13
  253. package/dist/ai/ui/Dropdown.d.ts +0 -31
  254. package/dist/ai/ui/Dropdown.js +0 -110
  255. package/dist/components/AddMenu.d.ts +0 -18
  256. package/dist/components/AddMenu.js +0 -46
  257. package/dist/components/BareModal.d.ts +0 -17
  258. package/dist/components/BareModal.js +0 -77
  259. package/dist/components/CollapsiblePanel.d.ts +0 -27
  260. package/dist/components/CollapsiblePanel.js +0 -77
  261. package/dist/components/Dropdown.d.ts +0 -28
  262. package/dist/components/Dropdown.js +0 -355
  263. package/dist/components/Listbox.d.ts +0 -21
  264. package/dist/components/Listbox.js +0 -119
  265. package/dist/components/MultiSelect.d.ts +0 -22
  266. package/dist/components/MultiSelect.js +0 -168
  267. package/dist/components/SwitchToggle.d.ts +0 -15
  268. package/dist/components/SwitchToggle.js +0 -57
  269. package/dist/components/SwitchToggleSmall.d.ts +0 -6
  270. package/dist/components/SwitchToggleSmall.js +0 -14
  271. package/dist/components/Toggle.d.ts +0 -6
  272. package/dist/components/Toggle.js +0 -20
  273. package/dist/components/dropdownUtils.d.ts +0 -37
  274. package/dist/components/dropdownUtils.js +0 -99
  275. package/dist/form/SpecialTypes/DropdownWrapper.d.ts +0 -25
  276. package/dist/form/SpecialTypes/DropdownWrapper.js +0 -28
  277. package/dist/form/SpecialTypes/FormikCustomDropdown.d.ts +0 -51
  278. package/dist/form/SpecialTypes/FormikCustomDropdown.js +0 -119
  279. package/dist/form/SpecialTypes/FormikCustomInput.d.ts +0 -19
  280. package/dist/form/SpecialTypes/FormikCustomInput.js +0 -121
  281. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.d.ts +0 -13
  282. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +0 -31
  283. package/dist/form/SpecialTypes/FormikNumericInput.d.ts +0 -9
  284. package/dist/form/SpecialTypes/FormikNumericInput.js +0 -60
  285. package/dist/form/SpecialTypes/index.d.ts +0 -3
  286. package/dist/list/HoverCard.d.ts +0 -24
  287. package/dist/list/HoverCard.js +0 -73
  288. package/dist/list/RowContextMenu.d.ts +0 -127
  289. package/dist/list/RowContextMenu.js +0 -515
package/README.md CHANGED
@@ -176,6 +176,31 @@ animations) and streamdown's classes next to the theme contract:
176
176
 
177
177
  The prebuilt `@parallelworks/ui/styles.css` already includes both.
178
178
 
179
+ ## Form state
180
+
181
+ `DynamicForm` keeps its values in react-hook-form, a peer dependency, so the
182
+ host and the library share one instance. A host that reads or drives the form
183
+ creates it and passes it in:
184
+
185
+ ```tsx
186
+ const form = useForm()
187
+ const { isDirty } = useFormState({ control: form.control })
188
+
189
+ <DynamicForm form={form} formJSONs={inputs} initialValues={saved} />
190
+ ```
191
+
192
+ `form.getValues()`, `form.reset()` and `formState.isDirty` then work as
193
+ react-hook-form documents them. Without `form`, the form keeps its own;
194
+ `onValuesChange` reports each change either way. `missingFields` marks fields
195
+ as `required` errors.
196
+
197
+ Each field subscribes only to what it reads, so typing renders the edited
198
+ field and whatever reads it, not the whole form. A host field component,
199
+ passed through `fields` or `slots.formFields`, binds the same way: its own
200
+ value with `useFieldValue(field.name)` (or `useController`), another input's
201
+ with `useFieldAt(path)` or `useWatch`, and a pick from a list with
202
+ `useChoiceField`, which applies `autoselect` and `secondaryField`.
203
+
179
204
  ## Form layouts
180
205
 
181
206
  `DynamicForm` accepts an optional `$meta.layout` in `formJSONs`. A layout arranges
@@ -1,4 +1,5 @@
1
1
  import { ChatAdapterError } from "./types.js";
2
+ import { applyPartDelta, finalizeParts } from "../core/parts.js";
2
3
  //#region src/ai/adapter/memoryStore.ts
3
4
  function newId() {
4
5
  return crypto.randomUUID();
@@ -103,6 +104,7 @@ function createMemoryConversationStore(options) {
103
104
  return async (req, handlers, signal) => {
104
105
  let partialContent = "";
105
106
  let partialReasoning = "";
107
+ let parts = [];
106
108
  const wrappedHandlers = {
107
109
  ...handlers,
108
110
  onContent: (chunk) => {
@@ -112,6 +114,10 @@ function createMemoryConversationStore(options) {
112
114
  onReasoning: (chunk) => {
113
115
  partialReasoning += chunk;
114
116
  handlers.onReasoning?.(chunk);
117
+ },
118
+ onPart: (delta) => {
119
+ parts = applyPartDelta(parts, delta);
120
+ handlers.onPart?.(delta);
115
121
  }
116
122
  };
117
123
  recordUser(req);
@@ -126,6 +132,7 @@ function createMemoryConversationStore(options) {
126
132
  ...result.reasoning ? { reasoning: result.reasoning } : {},
127
133
  ...result.responsesOutput.length ? { responsesOutput: result.responsesOutput } : {},
128
134
  ...result.toolCalls.length ? { toolCalls: result.toolCalls } : {},
135
+ ...parts.length ? { parts: finalizeParts(parts) } : {},
129
136
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
130
137
  });
131
138
  return {
@@ -5,6 +5,7 @@ var CANNED_REPLIES = [
5
5
  "Everything you see is generated locally. The mock adapter implements the same `ChatAdapter` interface as the OpenAI-compatible adapter and any backend's own, so the UI can't tell the difference.\n\n```ts\nconst adapter = createMockChatAdapter()\n```\n\nMarkdown, `inline code`, and code blocks render through the same pipeline.",
6
6
  "Branching works too: edit one of your earlier messages or hit regenerate on a reply, then use the arrows to flip between branches."
7
7
  ];
8
+ var TOOL_REPLY = "Two clusters are running: **gpu-a** with 4 nodes, and **cpu-batch**, idle and scaled to zero.";
8
9
  var THINKING_PREAMBLE = "Thinking about the question, weighing a few canned options, and picking the next reply in the rotation...";
9
10
  function sleep(ms, signal) {
10
11
  return new Promise((resolve, reject) => {
@@ -30,21 +31,32 @@ function createMockChatAdapter(options) {
30
31
  conversations: store.conversations,
31
32
  models: { async list() {
32
33
  return {
33
- models: [{
34
- id: "mock:local/canned-small",
35
- object: "model",
36
- created: 0,
37
- owned_by: "mock",
38
- provider: "Mock Provider",
39
- tool_calling_mode: "none"
40
- }, {
41
- id: "mock:local/canned-thinking",
42
- object: "model",
43
- created: 0,
44
- owned_by: "mock",
45
- provider: "Mock Provider",
46
- tool_calling_mode: "none"
47
- }],
34
+ models: [
35
+ {
36
+ id: "mock:local/canned-small",
37
+ object: "model",
38
+ created: 0,
39
+ owned_by: "mock",
40
+ provider: "Mock Provider",
41
+ tool_calling_mode: "none"
42
+ },
43
+ {
44
+ id: "mock:local/canned-thinking",
45
+ object: "model",
46
+ created: 0,
47
+ owned_by: "mock",
48
+ provider: "Mock Provider",
49
+ tool_calling_mode: "none"
50
+ },
51
+ {
52
+ id: "mock:local/canned-tools",
53
+ object: "model",
54
+ created: 0,
55
+ owned_by: "mock",
56
+ provider: "Mock Provider",
57
+ tool_calling_mode: "native"
58
+ }
59
+ ],
48
60
  unreachableSessions: [],
49
61
  providerIssues: []
50
62
  };
@@ -57,8 +69,31 @@ function createMockChatAdapter(options) {
57
69
  reasoning += word;
58
70
  handlers.onReasoning?.(word);
59
71
  }
60
- const reply = CANNED_REPLIES[replyIndex % CANNED_REPLIES.length] ?? "";
61
- replyIndex++;
72
+ const usesTools = req.model.includes("tools");
73
+ if (usesTools) {
74
+ const id = `call-${crypto.randomUUID()}`;
75
+ handlers.onPart?.({
76
+ type: "tool_start",
77
+ id,
78
+ name: "list_clusters",
79
+ args: "{}"
80
+ });
81
+ await sleep(900, signal);
82
+ handlers.onPart?.({
83
+ type: "tool_end",
84
+ id,
85
+ result: JSON.stringify([{
86
+ name: "gpu-a",
87
+ status: "on",
88
+ nodes: 4
89
+ }, {
90
+ name: "cpu-batch",
91
+ status: "on",
92
+ nodes: 0
93
+ }])
94
+ });
95
+ }
96
+ const reply = usesTools ? TOOL_REPLY : CANNED_REPLIES[replyIndex++ % CANNED_REPLIES.length] ?? "";
62
97
  await sleep(250, signal);
63
98
  let content = "";
64
99
  for (const word of reply.split(/(?<= )/)) {
@@ -27,7 +27,7 @@ function createOpenAICompatibleStream(config) {
27
27
  if (!res.ok) throw new ChatAdapterError(await errorMessageFromResponse(res), { status: res.status });
28
28
  const reader = res.body?.getReader();
29
29
  if (!reader) throw new ChatAdapterError("No response body");
30
- return processSSEStream(reader, handlers.onContent, void 0, void 0, handlers.onReasoning, handlers.onMessageId, useResponses ? parseResponsesSSELine : void 0);
30
+ return processSSEStream(reader, handlers.onContent, void 0, void 0, handlers.onReasoning, handlers.onMessageId, useResponses ? parseResponsesSSELine : void 0, handlers.onPart);
31
31
  };
32
32
  }
33
33
  //#endregion
@@ -3,4 +3,4 @@ export { apiErrorMessage, errorMessageFromResponse, statusFallbackMessage, } fro
3
3
  export { createOpenAIChatAdapter, type OpenAIChatAdapterOptions, } from './openaiAdapter.js';
4
4
  export { buildResponsesRequestBody, parseResponsesSSELine, } from './responses.js';
5
5
  export { type ParsedSSELine, parseSSELine, processSSEStream, type SSELineParser, } from './sse.js';
6
- export type { CompletionChunk, CompletionChunkChoice, CompletionMessage, CompletionRequest, ModelsWireResponse, } from './wire.js';
6
+ export type { CompletionChunk, CompletionChunkChoice, CompletionMessage, CompletionRequest, ModelsWireResponse, ToolResultChunk, } from './wire.js';
@@ -1,10 +1,13 @@
1
1
  import type { ToolCall, ToolCallDelta } from '../../types.js';
2
+ import type { PartDelta } from '../types.js';
3
+ import type { ToolResultChunk } from './wire.js';
2
4
  export type ParsedSSELine = {
3
5
  id?: string | undefined;
4
6
  messageId?: string | undefined;
5
7
  content?: string | undefined;
6
8
  reasoningContent?: string | undefined;
7
9
  toolCalls?: ToolCallDelta[] | undefined;
10
+ toolResult?: ToolResultChunk | undefined;
8
11
  finishReason?: string | null | undefined;
9
12
  model?: string | undefined;
10
13
  error?: string | undefined;
@@ -12,8 +15,11 @@ export type ParsedSSELine = {
12
15
  } | null;
13
16
  export type SSELineParser = (line: string) => ParsedSSELine;
14
17
  export declare function parseSSELine(line: string): ParsedSSELine;
15
- /** Process an SSE stream from a ReadableStream reader */
16
- export declare function processSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>, onContent: (content: string) => void, onToolCall?: (toolCalls: ToolCall[]) => void, onError?: (error: string) => void, onReasoning?: (reasoning: string) => void, onMessageId?: (messageId: string) => void, parseLine?: SSELineParser): Promise<{
18
+ /** Process an SSE stream from a ReadableStream reader. With `onPart`, tool
19
+ * calls stream as parts; a call the gateway answered with a result is left
20
+ * off the returned `toolCalls`, which then hold only calls the caller must
21
+ * run. */
22
+ export declare function processSSEStream(reader: ReadableStreamDefaultReader<Uint8Array>, onContent: (content: string) => void, onToolCall?: (toolCalls: ToolCall[]) => void, onError?: (error: string) => void, onReasoning?: (reasoning: string) => void, onMessageId?: (messageId: string) => void, parseLine?: SSELineParser, onPart?: (delta: PartDelta) => void): Promise<{
17
23
  content: string;
18
24
  messageId: string;
19
25
  toolCalls: ToolCall[];
@@ -14,6 +14,7 @@ function parseSSELine(line) {
14
14
  content: choice?.delta?.content,
15
15
  reasoningContent: choice?.delta?.reasoning_content,
16
16
  toolCalls: choice?.delta?.tool_calls ?? void 0,
17
+ toolResult: chunk.toolResult,
17
18
  finishReason: choice?.finish_reason,
18
19
  model: chunk.model
19
20
  };
@@ -21,8 +22,11 @@ function parseSSELine(line) {
21
22
  return null;
22
23
  }
23
24
  }
24
- /** Process an SSE stream from a ReadableStream reader */
25
- async function processSSEStream(reader, onContent, onToolCall, onError, onReasoning, onMessageId, parseLine = parseSSELine) {
25
+ /** Process an SSE stream from a ReadableStream reader. With `onPart`, tool
26
+ * calls stream as parts; a call the gateway answered with a result is left
27
+ * off the returned `toolCalls`, which then hold only calls the caller must
28
+ * run. */
29
+ async function processSSEStream(reader, onContent, onToolCall, onError, onReasoning, onMessageId, parseLine = parseSSELine, onPart) {
26
30
  const decoder = new TextDecoder();
27
31
  let assistantContent = "";
28
32
  let assistantReasoning = "";
@@ -31,6 +35,19 @@ async function processSSEStream(reader, onContent, onToolCall, onError, onReason
31
35
  let resolvedModel = null;
32
36
  let responsesOutput = [];
33
37
  const toolCallsMap = /* @__PURE__ */ new Map();
38
+ const startedToolCalls = /* @__PURE__ */ new Set();
39
+ const answeredToolCalls = /* @__PURE__ */ new Set();
40
+ const startToolCall = (index) => {
41
+ const tc = toolCallsMap.get(index);
42
+ if (!onPart || !tc?.id || !tc.name || startedToolCalls.has(index)) return;
43
+ startedToolCalls.add(index);
44
+ onPart({
45
+ type: "tool_start",
46
+ id: tc.id,
47
+ name: tc.name,
48
+ args: tc.arguments
49
+ });
50
+ };
34
51
  let lineBuffer = "";
35
52
  while (true) {
36
53
  const result = await reader.read();
@@ -64,13 +81,32 @@ async function processSSEStream(reader, onContent, onToolCall, onError, onReason
64
81
  if (tc.index === void 0) continue;
65
82
  const existing = toolCallsMap.get(tc.index);
66
83
  if (existing) {
67
- if (tc.function?.arguments) existing.arguments += tc.function.arguments;
84
+ existing.id ||= tc.id || "";
85
+ existing.name ||= tc.function?.name || "";
86
+ if (tc.function?.arguments) {
87
+ existing.arguments += tc.function.arguments;
88
+ if (startedToolCalls.has(tc.index)) onPart?.({
89
+ type: "tool_update",
90
+ id: existing.id,
91
+ args: tc.function.arguments
92
+ });
93
+ }
68
94
  } else toolCallsMap.set(tc.index, {
69
95
  id: tc.id || "",
70
96
  type: tc.type || "function",
71
97
  name: tc.function?.name || "",
72
98
  arguments: tc.function?.arguments || ""
73
99
  });
100
+ startToolCall(tc.index);
101
+ }
102
+ if (parsed.toolResult) {
103
+ answeredToolCalls.add(parsed.toolResult.toolCallId);
104
+ onPart?.({
105
+ type: "tool_end",
106
+ id: parsed.toolResult.toolCallId,
107
+ result: parsed.toolResult.content,
108
+ ...parsed.toolResult.isError ? { isError: true } : {}
109
+ });
74
110
  }
75
111
  }
76
112
  }
@@ -89,7 +125,7 @@ async function processSSEStream(reader, onContent, onToolCall, onError, onReason
89
125
  if (parsed.finishReason) finishReason = parsed.finishReason;
90
126
  }
91
127
  }
92
- const toolCalls = Array.from(toolCallsMap.values()).map((tc) => ({
128
+ const toolCalls = Array.from(toolCallsMap.values()).filter((tc) => !answeredToolCalls.has(tc.id)).map((tc) => ({
93
129
  id: tc.id,
94
130
  type: tc.type,
95
131
  function: {
@@ -30,9 +30,15 @@ export interface CompletionChunk {
30
30
  object?: string;
31
31
  created?: number;
32
32
  messageId?: string;
33
+ toolResult?: ToolResultChunk;
33
34
  choices?: CompletionChunkChoice[] | null;
34
35
  usage?: TokenUsage;
35
36
  }
37
+ export interface ToolResultChunk {
38
+ toolCallId: string;
39
+ content: string;
40
+ isError?: boolean;
41
+ }
36
42
  export interface ModelsWireResponse {
37
43
  object: string;
38
44
  data: ChatModel[] | null;
@@ -1,4 +1,3 @@
1
- export default function AllocationSelector({ variant, }?: {
2
- /** 'bare' drops the border, for a toolbar that reads as text. */
3
- variant?: 'default' | 'bare';
4
- }): import("react").JSX.Element | null;
1
+ /** Which allocation an organization model bills, with its spend so far. Shows
2
+ * only for an organization model, and picks the first allocation for it. */
3
+ export default function AllocationSelector(): import("react").JSX.Element | null;
@@ -1,55 +1,59 @@
1
+ import { FaAngleDown } from "../../icons/fa/FaAngleDown.js";
2
+ import { Select } from "../../components/Select.js";
3
+ import { useChatConfig } from "../core/config.js";
1
4
  import { useChat } from "../core/ChatProvider.js";
2
- import Dropdown from "../ui/Dropdown.js";
5
+ import { chipClassName } from "../ui/chip.js";
3
6
  import { isOrgModel } from "../utils.js";
4
- import { useEffect } from "react";
5
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { useEffect } from "react";
6
9
  import useSWR from "swr";
7
10
  //#region src/ai/components/AllocationSelector.tsx
8
- function AllocationSelector({ variant = "default" } = {}) {
9
- const bare = variant === "bare";
11
+ var money = (amount) => `$${amount.toFixed(2)}`;
12
+ /** Which allocation an organization model bills, with its spend so far. Shows
13
+ * only for an organization model, and picks the first allocation for it. */
14
+ function AllocationSelector() {
10
15
  const { adapter, selectedProvider, selectedAllocation, setSelectedAllocation } = useChat();
16
+ const t = useChatConfig().strings.allocationPicker;
11
17
  const isOrgProvider = isOrgModel(selectedProvider);
12
18
  const allocationsAdapter = adapter.allocations;
13
19
  const { data: allocations } = useSWR(isOrgProvider && allocationsAdapter ? ["ai-chat-allocations"] : null, () => allocationsAdapter?.list());
20
+ const first = allocations?.[0];
14
21
  useEffect(() => {
15
- const first = allocations?.[0];
16
- if (isOrgProvider && first && !selectedAllocation) setSelectedAllocation(first.name);
17
- }, [
18
- isOrgProvider,
19
- allocations,
20
- selectedAllocation,
21
- setSelectedAllocation
22
- ]);
23
- useEffect(() => {
24
- if (!isOrgProvider && selectedAllocation) setSelectedAllocation(null);
22
+ if (!isOrgProvider) {
23
+ if (selectedAllocation) setSelectedAllocation(null);
24
+ } else if (first && !selectedAllocation) setSelectedAllocation(first.name);
25
25
  }, [
26
26
  isOrgProvider,
27
+ first,
27
28
  selectedAllocation,
28
29
  setSelectedAllocation
29
30
  ]);
30
31
  if (!isOrgProvider || !allocations?.length) return null;
31
- const options = allocations.map((alloc) => ({
32
- label: alloc.name,
33
- value: alloc.name
34
- }));
35
32
  const selected = allocations.find((a) => a.name === selectedAllocation);
36
33
  return /* @__PURE__ */ jsxs("div", {
37
- className: "flex items-center gap-2",
38
- children: [/* @__PURE__ */ jsx(Dropdown, {
39
- options,
40
- value: selectedAllocation ?? "",
41
- onChange: (v) => setSelectedAllocation(v || null),
42
- variant,
43
- textBoxClassName: bare ? "rounded-full h-8 px-2.5 text-[13px] hover:chat-tint" : "h-8 rounded-lg border theme-border text-xs px-2",
44
- placeholder: "Select allocation"
45
- }), selected && /* @__PURE__ */ jsxs("span", {
34
+ className: "flex min-w-0 items-center gap-2",
35
+ children: [/* @__PURE__ */ jsx(Select, {
36
+ "aria-label": t.choose,
37
+ items: allocations.map((a) => ({
38
+ value: a.name,
39
+ label: a.name
40
+ })),
41
+ value: selectedAllocation,
42
+ onValueChange: setSelectedAllocation,
43
+ trigger: /* @__PURE__ */ jsxs("button", {
44
+ type: "button",
45
+ className: chipClassName,
46
+ children: [/* @__PURE__ */ jsx("span", {
47
+ className: "truncate",
48
+ children: selected?.name ?? t.placeholder
49
+ }), /* @__PURE__ */ jsx(FaAngleDown, {
50
+ className: "h-3 w-3 shrink-0",
51
+ "aria-hidden": "true"
52
+ })]
53
+ })
54
+ }), selected && /* @__PURE__ */ jsx("span", {
46
55
  className: "text-xs tabular-nums theme-muted-text whitespace-nowrap",
47
- children: [
48
- "$",
49
- (selected.used ?? 0).toFixed(2),
50
- " / $",
51
- selected.total.toFixed(2)
52
- ]
56
+ children: t.usage(money(selected.used ?? 0), money(selected.total))
53
57
  })]
54
58
  });
55
59
  }
@@ -1,14 +1,14 @@
1
- import { safeUrl } from "../../safeUrl.js";
2
1
  import { BiSolidTrashAlt } from "../../icons/bi/BiSolidTrashAlt.js";
3
2
  import { FiDownload } from "../../icons/fi/FiDownload.js";
4
3
  import { FiFile } from "../../icons/fi/FiFile.js";
4
+ import { safeUrl } from "../../safeUrl.js";
5
5
  import { useChatConfig } from "../core/config.js";
6
6
  import { useChat } from "../core/ChatProvider.js";
7
7
  import { formatFileSize } from "../utils.js";
8
8
  import { attachmentsPageKey } from "./attachmentKeys.js";
9
- import cx from "classnames";
9
+ import { clsx } from "clsx";
10
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
11
  import { useCallback, useMemo, useState } from "react";
11
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
12
  import { DateTime } from "luxon";
13
13
  import useSWRInfinite from "swr/infinite";
14
14
  //#region src/ai/components/AttachmentManager.tsx
@@ -74,7 +74,7 @@ function AttachmentManager() {
74
74
  }) : attachments.length === 0 ? /* @__PURE__ */ jsx("p", {
75
75
  className: "text-center theme-muted-text py-12 text-sm",
76
76
  children: t.empty
77
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
77
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
78
78
  className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4",
79
79
  children: attachments.map((att) => {
80
80
  const downloadUrl = attachmentsAdapter.downloadUrl(att.id);
@@ -87,14 +87,14 @@ function AttachmentManager() {
87
87
  style: { cursor: "pointer" }
88
88
  } : {};
89
89
  return /* @__PURE__ */ jsxs("div", {
90
- className: cx("rounded-lg border theme-border overflow-hidden", isDeleting && "opacity-50 pointer-events-none"),
90
+ className: clsx("rounded-lg border theme-border overflow-hidden", isDeleting && "opacity-50 pointer-events-none"),
91
91
  children: [/* @__PURE__ */ jsxs("div", {
92
92
  className: "relative aspect-square bg-(--theme-muted-panel-bg) flex items-center justify-center",
93
93
  ...openProps,
94
94
  children: [/* @__PURE__ */ jsx(FiFile, { className: "w-8 h-8 theme-muted-text" }), showThumbnail && /* @__PURE__ */ jsx("img", {
95
95
  src: downloadUrl,
96
96
  alt: att.filename || "Image",
97
- className: cx("absolute inset-0 w-full h-full object-cover", thumbnail !== "loaded" && "opacity-0"),
97
+ className: clsx("absolute inset-0 w-full h-full object-cover", thumbnail !== "loaded" && "opacity-0"),
98
98
  loading: "lazy",
99
99
  onLoad: () => markThumbnail(att.id, "loaded"),
100
100
  onError: () => markThumbnail(att.id, "broken")
@@ -108,7 +108,7 @@ function AttachmentManager() {
108
108
  children: att.filename || "File"
109
109
  }), /* @__PURE__ */ jsxs("p", {
110
110
  className: "truncate theme-muted-text",
111
- children: [att.size ? formatFileSize(att.size) : "", att.uploadedAt && /* @__PURE__ */ jsxs(Fragment$1, { children: [att.size ? " · " : "", DateTime.fromISO(att.uploadedAt).toRelative()] })]
111
+ children: [att.size ? formatFileSize(att.size) : "", att.uploadedAt && /* @__PURE__ */ jsxs(Fragment, { children: [att.size ? " · " : "", DateTime.fromISO(att.uploadedAt).toRelative()] })]
112
112
  })]
113
113
  }), /* @__PURE__ */ jsxs("div", {
114
114
  className: "flex items-center gap-0.5 flex-shrink-0",
@@ -7,9 +7,9 @@ import { useChatConfig } from "../core/config.js";
7
7
  import { useChat } from "../core/ChatProvider.js";
8
8
  import { formatFileSize } from "../utils.js";
9
9
  import { ATTACHMENTS_INFINITE_KEY } from "./attachmentKeys.js";
10
- import cx from "classnames";
10
+ import { clsx } from "clsx";
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
12
  import { forwardRef, useCallback, useImperativeHandle, useRef, useState } from "react";
12
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
13
13
  import { mutate } from "swr";
14
14
  //#region src/ai/components/AttachmentUpload.tsx
15
15
  var DEFAULT_MAX_FILE_SIZE = 26214400;
@@ -112,7 +112,7 @@ var AttachmentUpload = forwardRef(function AttachmentUpload({ conversationId, at
112
112
  const handleClick = () => {
113
113
  fileInputRef.current?.click();
114
114
  };
115
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("input", {
115
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
116
116
  ref: fileInputRef,
117
117
  type: "file",
118
118
  multiple: true,
@@ -127,7 +127,7 @@ var AttachmentUpload = forwardRef(function AttachmentUpload({ conversationId, at
127
127
  children: attachments.map((attachment, index) => {
128
128
  const Icon = getFileIcon(attachment.mimeType);
129
129
  return /* @__PURE__ */ jsxs("div", {
130
- className: cx("flex items-center gap-2 px-2 py-1 rounded border theme-border theme-muted-panel", attachment.error && "border-red-500"),
130
+ className: clsx("flex items-center gap-2 px-2 py-1 rounded border theme-border theme-muted-panel", attachment.error && "border-red-500"),
131
131
  children: [
132
132
  /* @__PURE__ */ jsx(Icon, { className: "w-4 h-4 theme-muted-text" }),
133
133
  /* @__PURE__ */ jsx("span", {
@@ -163,7 +163,7 @@ var AttachmentUpload = forwardRef(function AttachmentUpload({ conversationId, at
163
163
  onDragOver: handleDragOver,
164
164
  onDragLeave: handleDragLeave,
165
165
  onDrop: handleDrop,
166
- className: cx("relative block w-full border-2 border-dashed rounded-lg p-3 text-center transition-colors", isDragging ? "border-blue-500 bg-blue-500/10" : "theme-border hover:theme-border", disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"),
166
+ className: clsx("relative block w-full border-2 border-dashed rounded-lg p-3 text-center transition-colors", isDragging ? "border-blue-500 bg-blue-500/10" : "theme-border hover:theme-border", disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"),
167
167
  onClick: disabled ? void 0 : handleClick,
168
168
  children: [/* @__PURE__ */ jsxs("span", {
169
169
  className: "flex items-center justify-center gap-2 text-sm theme-muted-text",
@@ -1,8 +1,8 @@
1
1
  import { FaAngleLeft } from "../../icons/fa/FaAngleLeft.js";
2
2
  import { FaAngleRight } from "../../icons/fa/FaAngleRight.js";
3
3
  import { useChatConfig } from "../core/config.js";
4
- import { useMemo } from "react";
5
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { useMemo } from "react";
6
6
  //#region src/ai/components/BranchNavigator.tsx
7
7
  function findSiblings(messages, messageId) {
8
8
  const message = messages.find((m) => m.id === messageId);
@@ -12,8 +12,8 @@ import DragOverlay from "./DragOverlay.js";
12
12
  import ChatInput from "./ChatInput.js";
13
13
  import ChatStage from "./ChatStage.js";
14
14
  import ProviderIssueBanner from "./ProviderIssueBanner.js";
15
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
16
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
16
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
17
17
  //#region src/ai/components/ChatEmptyState.tsx
18
18
  function pickRandom(arr, count) {
19
19
  return [...arr].sort(() => Math.random() - .5).slice(0, count);
@@ -138,7 +138,7 @@ function ChatEmptyState({ targetSession }) {
138
138
  disabled: isLoading || isLoadingModels || models.length === 0,
139
139
  placeholder: isLoadingModels ? tInput.placeholderLoading : hasLoadedModels && models.length === 0 ? tInput.placeholderNoProviders : tInput.placeholder,
140
140
  settingsLeft: /* @__PURE__ */ jsx(ConnectToolsLink, {}),
141
- settingsRight: /* @__PURE__ */ jsxs(Fragment$1, { children: [slots.composerUsage, /* @__PURE__ */ jsx(ComposerControls, { targetSession })] })
141
+ settingsRight: /* @__PURE__ */ jsxs(Fragment, { children: [slots.composerUsage, /* @__PURE__ */ jsx(ComposerControls, { targetSession })] })
142
142
  }),
143
143
  prompts.length > 0 && !(hasLoadedModels && models.length === 0) && /* @__PURE__ */ jsx("ul", {
144
144
  className: "mx-auto max-w-[var(--chat-column,50rem)] px-8",
@@ -11,9 +11,9 @@ import DragOverlay from "./DragOverlay.js";
11
11
  import { PasteFileCard } from "./PastedText.js";
12
12
  import { SlashMenu, useSlashMenu } from "./SlashMenu.js";
13
13
  import { usePasteCards } from "./usePasteCards.js";
14
- import cx from "classnames";
14
+ import { clsx } from "clsx";
15
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
16
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
16
- import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
17
17
  //#region src/ai/components/ChatInput.tsx
18
18
  /** An optional box that is still empty should not wear the filled look of
19
19
  * one waiting to send something. */
@@ -35,10 +35,10 @@ function AttachButton({ open, disabled, label, onClick }) {
35
35
  onClick,
36
36
  disabled,
37
37
  "aria-expanded": open,
38
- className: cx("flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full transition-colors", open ? "theme-text chat-tint-strong" : "theme-muted-text hover:theme-text hover:chat-tint", "disabled:opacity-50 disabled:cursor-not-allowed"),
38
+ className: clsx("flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full transition-colors", open ? "theme-text chat-tint-strong" : "theme-muted-text hover:theme-text hover:chat-tint", "disabled:opacity-50 disabled:cursor-not-allowed"),
39
39
  "aria-label": label,
40
40
  title: label,
41
- children: /* @__PURE__ */ jsx(GoPlus, { className: cx("h-5 w-5 transition-transform duration-200", open && "rotate-45") })
41
+ children: /* @__PURE__ */ jsx(GoPlus, { className: clsx("h-5 w-5 transition-transform duration-200", open && "rotate-45") })
42
42
  });
43
43
  }
44
44
  function SendButton({ onClick, enabled, emphasised, label, testId }) {
@@ -47,7 +47,7 @@ function SendButton({ onClick, enabled, emphasised, label, testId }) {
47
47
  onClick,
48
48
  disabled: !enabled,
49
49
  "data-testid": testId,
50
- className: cx("flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full transition-[background-color,color,transform] duration-150 active:scale-95", emphasised ? "theme-element hover:opacity-90" : "chat-send-idle", !enabled && "cursor-not-allowed active:scale-100"),
50
+ className: clsx("flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full transition-[background-color,color,transform] duration-150 active:scale-95", emphasised ? "theme-element hover:opacity-90" : "chat-send-idle", !enabled && "cursor-not-allowed active:scale-100"),
51
51
  "aria-label": label,
52
52
  title: label,
53
53
  children: /* @__PURE__ */ jsx(FaArrowUp, { className: "h-4 w-4" })
@@ -228,6 +228,7 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
228
228
  className: "relative",
229
229
  children: [slash && slashMenu.open && /* @__PURE__ */ jsx(SlashMenu, {
230
230
  label: slash.label,
231
+ listId: slashMenu.listId,
231
232
  matches: slashMenu.matches,
232
233
  active: slashMenu.active,
233
234
  onChoose: (name) => {
@@ -248,7 +249,7 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
248
249
  })
249
250
  }),
250
251
  /* @__PURE__ */ jsxs("div", {
251
- className: cx(!toolbar && "flex items-end gap-2 pr-2 pb-2"),
252
+ className: clsx(!toolbar && "flex items-end gap-2 pr-2 pb-2"),
252
253
  children: [/* @__PURE__ */ jsx("div", {
253
254
  className: "max-h-[200px] min-w-0 flex-1 overflow-y-auto",
254
255
  children: /* @__PURE__ */ jsx("textarea", {
@@ -257,11 +258,12 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
257
258
  onChange: (e) => setInputAndPersist(e.target.value),
258
259
  onKeyDown: handleKeyDown,
259
260
  onPaste: handlePaste,
261
+ ...slashMenu.inputProps,
260
262
  placeholder: effectivePlaceholder,
261
263
  "aria-label": inputLabel ?? effectivePlaceholder,
262
264
  disabled,
263
265
  rows: 1,
264
- className: cx("theme-text block w-full resize-none overflow-hidden", "bg-transparent border-0 px-4", toolbar ? "pt-3.5 pb-1" : "pt-3 pb-1", "placeholder:theme-muted-text", "focus:outline-none focus:ring-0", "disabled:opacity-50 disabled:cursor-not-allowed", "text-[1rem] leading-relaxed")
266
+ className: clsx("theme-text block w-full resize-none overflow-hidden", "bg-transparent border-0 px-4", toolbar ? "pt-3.5 pb-1" : "pt-3 pb-1", "placeholder:theme-muted-text", "focus:outline-none focus:ring-0", "disabled:opacity-50 disabled:cursor-not-allowed", "text-[1rem] leading-relaxed")
265
267
  })
266
268
  }), !toolbar && action]
267
269
  }),
@@ -286,7 +288,7 @@ var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeh
286
288
  ]
287
289
  })]
288
290
  }),
289
- (settingsLeft || uploadedCount > 0) && /* @__PURE__ */ jsx(ComposerSettings, { left: /* @__PURE__ */ jsxs(Fragment$1, { children: [settingsLeft, uploadedCount > 0 && /* @__PURE__ */ jsx("span", {
291
+ (settingsLeft || uploadedCount > 0) && /* @__PURE__ */ jsx(ComposerSettings, { left: /* @__PURE__ */ jsxs(Fragment, { children: [settingsLeft, uploadedCount > 0 && /* @__PURE__ */ jsx("span", {
290
292
  className: "text-[11px] theme-muted-text",
291
293
  children: t.filesAttached(uploadedCount)
292
294
  })] }) }),
@@ -4,9 +4,9 @@ import { useChat } from "../core/ChatProvider.js";
4
4
  import { KeyboardShortcutsProvider } from "../core/KeyboardShortcutsProvider.js";
5
5
  import { SidebarToggle } from "../ui/sidebar.js";
6
6
  import ChatSidebar from "./ChatSidebar.js";
7
- import cx from "classnames";
8
- import { useLayoutEffect, useRef, useState } from "react";
7
+ import { clsx } from "clsx";
9
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
+ import { useLayoutEffect, useRef, useState } from "react";
10
10
  //#region src/ai/components/ChatLayout.tsx
11
11
  /** The list at its default width beside the narrowest thread that still
12
12
  * reads comfortably; narrower than that, 'auto' moves the list to a drawer. */
@@ -51,10 +51,9 @@ function ConversationLayout({ sidebar, children, sidebarMode = "inline", drawerB
51
51
  const { strings } = useChatConfig();
52
52
  return /* @__PURE__ */ jsxs("div", {
53
53
  ref,
54
- className: cx("flex h-full", drawer && "relative overflow-hidden"),
54
+ className: clsx("flex h-full", drawer && "relative overflow-hidden"),
55
55
  children: [sidebar, /* @__PURE__ */ jsxs("main", {
56
- inert: drawer && drawerOpen,
57
- className: cx("flex min-w-0 flex-1 flex-col", CHAT_COLUMN_SCOPE),
56
+ className: clsx("flex min-w-0 flex-1 flex-col", CHAT_COLUMN_SCOPE),
58
57
  children: [drawer && (drawerToggle || drawerBar) && /* @__PURE__ */ jsxs("div", {
59
58
  className: "flex shrink-0 items-center gap-2 bg-(--theme-panel-bg) px-2 pt-2",
60
59
  children: [drawerToggle && /* @__PURE__ */ jsx(SidebarToggle, {