@agent-native/toolkit 0.5.1 → 0.7.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 (349) hide show
  1. package/README.md +17 -1
  2. package/dist/chat-history/ChatHistoryList.d.ts +103 -0
  3. package/dist/chat-history/ChatHistoryList.d.ts.map +1 -0
  4. package/dist/chat-history/ChatHistoryList.js +109 -0
  5. package/dist/chat-history/ChatHistoryList.js.map +1 -0
  6. package/dist/chat-history/ChatHistoryList.spec.d.ts +2 -0
  7. package/dist/chat-history/ChatHistoryList.spec.d.ts.map +1 -0
  8. package/dist/chat-history/ChatHistoryList.spec.js +283 -0
  9. package/dist/chat-history/ChatHistoryList.spec.js.map +1 -0
  10. package/dist/chat-history/index.d.ts +2 -0
  11. package/dist/chat-history/index.d.ts.map +1 -0
  12. package/dist/chat-history/index.js +2 -0
  13. package/dist/chat-history/index.js.map +1 -0
  14. package/dist/chat-history.css +314 -0
  15. package/dist/composer/AgentComposerFrame.d.ts +19 -0
  16. package/dist/composer/AgentComposerFrame.d.ts.map +1 -0
  17. package/dist/composer/AgentComposerFrame.js +14 -0
  18. package/dist/composer/AgentComposerFrame.js.map +1 -0
  19. package/dist/composer/ComposerPlusMenu.d.ts +17 -0
  20. package/dist/composer/ComposerPlusMenu.d.ts.map +1 -0
  21. package/dist/composer/ComposerPlusMenu.js +501 -0
  22. package/dist/composer/ComposerPlusMenu.js.map +1 -0
  23. package/dist/composer/MentionPopover.d.ts +29 -0
  24. package/dist/composer/MentionPopover.d.ts.map +1 -0
  25. package/dist/composer/MentionPopover.js +180 -0
  26. package/dist/composer/MentionPopover.js.map +1 -0
  27. package/dist/composer/PastedTextChip.d.ts +9 -0
  28. package/dist/composer/PastedTextChip.d.ts.map +1 -0
  29. package/dist/composer/PastedTextChip.js +47 -0
  30. package/dist/composer/PastedTextChip.js.map +1 -0
  31. package/dist/composer/PromptComposer.d.ts +114 -0
  32. package/dist/composer/PromptComposer.d.ts.map +1 -0
  33. package/dist/composer/PromptComposer.js +302 -0
  34. package/dist/composer/PromptComposer.js.map +1 -0
  35. package/dist/composer/PromptComposer.spec.d.ts +2 -0
  36. package/dist/composer/PromptComposer.spec.d.ts.map +1 -0
  37. package/dist/composer/PromptComposer.spec.js +64 -0
  38. package/dist/composer/PromptComposer.spec.js.map +1 -0
  39. package/dist/composer/RealtimeVoiceMode.d.ts +104 -0
  40. package/dist/composer/RealtimeVoiceMode.d.ts.map +1 -0
  41. package/dist/composer/RealtimeVoiceMode.js +245 -0
  42. package/dist/composer/RealtimeVoiceMode.js.map +1 -0
  43. package/dist/composer/RealtimeVoiceMode.spec.d.ts +2 -0
  44. package/dist/composer/RealtimeVoiceMode.spec.d.ts.map +1 -0
  45. package/dist/composer/RealtimeVoiceMode.spec.js +439 -0
  46. package/dist/composer/RealtimeVoiceMode.spec.js.map +1 -0
  47. package/dist/composer/TiptapComposer.d.ts +221 -0
  48. package/dist/composer/TiptapComposer.d.ts.map +1 -0
  49. package/dist/composer/TiptapComposer.js +1838 -0
  50. package/dist/composer/TiptapComposer.js.map +1 -0
  51. package/dist/composer/TiptapComposer.spec.d.ts +2 -0
  52. package/dist/composer/TiptapComposer.spec.d.ts.map +1 -0
  53. package/dist/composer/TiptapComposer.spec.js +277 -0
  54. package/dist/composer/TiptapComposer.spec.js.map +1 -0
  55. package/dist/composer/VoiceButton.d.ts +25 -0
  56. package/dist/composer/VoiceButton.d.ts.map +1 -0
  57. package/dist/composer/VoiceButton.js +100 -0
  58. package/dist/composer/VoiceButton.js.map +1 -0
  59. package/dist/composer/VoiceButton.spec.d.ts +2 -0
  60. package/dist/composer/VoiceButton.spec.d.ts.map +1 -0
  61. package/dist/composer/VoiceButton.spec.js +19 -0
  62. package/dist/composer/VoiceButton.spec.js.map +1 -0
  63. package/dist/composer/asset-picker-url.d.ts +16 -0
  64. package/dist/composer/asset-picker-url.d.ts.map +1 -0
  65. package/dist/composer/asset-picker-url.js +64 -0
  66. package/dist/composer/asset-picker-url.js.map +1 -0
  67. package/dist/composer/asset-picker-url.spec.d.ts +2 -0
  68. package/dist/composer/asset-picker-url.spec.d.ts.map +1 -0
  69. package/dist/composer/asset-picker-url.spec.js +23 -0
  70. package/dist/composer/asset-picker-url.spec.js.map +1 -0
  71. package/dist/composer/attachment-accept.d.ts +9 -0
  72. package/dist/composer/attachment-accept.d.ts.map +1 -0
  73. package/dist/composer/attachment-accept.js +65 -0
  74. package/dist/composer/attachment-accept.js.map +1 -0
  75. package/dist/composer/attachment-accept.spec.d.ts +2 -0
  76. package/dist/composer/attachment-accept.spec.d.ts.map +1 -0
  77. package/dist/composer/attachment-accept.spec.js +15 -0
  78. package/dist/composer/attachment-accept.spec.js.map +1 -0
  79. package/dist/composer/draft-key.d.ts +2 -0
  80. package/dist/composer/draft-key.d.ts.map +1 -0
  81. package/dist/composer/draft-key.js +8 -0
  82. package/dist/composer/draft-key.js.map +1 -0
  83. package/dist/composer/draft-key.spec.d.ts +2 -0
  84. package/dist/composer/draft-key.spec.d.ts.map +1 -0
  85. package/dist/composer/draft-key.spec.js +13 -0
  86. package/dist/composer/draft-key.spec.js.map +1 -0
  87. package/dist/composer/extensions/FileReference.d.ts +3 -0
  88. package/dist/composer/extensions/FileReference.d.ts.map +1 -0
  89. package/dist/composer/extensions/FileReference.js +36 -0
  90. package/dist/composer/extensions/FileReference.js.map +1 -0
  91. package/dist/composer/extensions/MentionReference.d.ts +3 -0
  92. package/dist/composer/extensions/MentionReference.d.ts.map +1 -0
  93. package/dist/composer/extensions/MentionReference.js +66 -0
  94. package/dist/composer/extensions/MentionReference.js.map +1 -0
  95. package/dist/composer/extensions/SkillReference.d.ts +3 -0
  96. package/dist/composer/extensions/SkillReference.d.ts.map +1 -0
  97. package/dist/composer/extensions/SkillReference.js +40 -0
  98. package/dist/composer/extensions/SkillReference.js.map +1 -0
  99. package/dist/composer/index.d.ts +14 -0
  100. package/dist/composer/index.d.ts.map +1 -0
  101. package/dist/composer/index.js +13 -0
  102. package/dist/composer/index.js.map +1 -0
  103. package/dist/composer/pasted-text.d.ts +31 -0
  104. package/dist/composer/pasted-text.d.ts.map +1 -0
  105. package/dist/composer/pasted-text.js +130 -0
  106. package/dist/composer/pasted-text.js.map +1 -0
  107. package/dist/composer/pasted-text.spec.d.ts +2 -0
  108. package/dist/composer/pasted-text.spec.d.ts.map +1 -0
  109. package/dist/composer/pasted-text.spec.js +107 -0
  110. package/dist/composer/pasted-text.spec.js.map +1 -0
  111. package/dist/composer/prompt-attachments.d.ts +18 -0
  112. package/dist/composer/prompt-attachments.d.ts.map +1 -0
  113. package/dist/composer/prompt-attachments.js +65 -0
  114. package/dist/composer/prompt-attachments.js.map +1 -0
  115. package/dist/composer/prompt-attachments.spec.d.ts +2 -0
  116. package/dist/composer/prompt-attachments.spec.d.ts.map +1 -0
  117. package/dist/composer/prompt-attachments.spec.js +63 -0
  118. package/dist/composer/prompt-attachments.spec.js.map +1 -0
  119. package/dist/composer/realtime-voice-audio-level.d.ts +14 -0
  120. package/dist/composer/realtime-voice-audio-level.d.ts.map +1 -0
  121. package/dist/composer/realtime-voice-audio-level.js +47 -0
  122. package/dist/composer/realtime-voice-audio-level.js.map +1 -0
  123. package/dist/composer/realtime-voice-audio-level.spec.d.ts +2 -0
  124. package/dist/composer/realtime-voice-audio-level.spec.d.ts.map +1 -0
  125. package/dist/composer/realtime-voice-audio-level.spec.js +25 -0
  126. package/dist/composer/realtime-voice-audio-level.spec.js.map +1 -0
  127. package/dist/composer/realtime-voice-transcript.d.ts +26 -0
  128. package/dist/composer/realtime-voice-transcript.d.ts.map +1 -0
  129. package/dist/composer/realtime-voice-transcript.js +103 -0
  130. package/dist/composer/realtime-voice-transcript.js.map +1 -0
  131. package/dist/composer/realtime-voice-transcript.spec.d.ts +2 -0
  132. package/dist/composer/realtime-voice-transcript.spec.d.ts.map +1 -0
  133. package/dist/composer/realtime-voice-transcript.spec.js +76 -0
  134. package/dist/composer/realtime-voice-transcript.spec.js.map +1 -0
  135. package/dist/composer/runtime-adapters.d.ts +163 -0
  136. package/dist/composer/runtime-adapters.d.ts.map +1 -0
  137. package/dist/composer/runtime-adapters.js +130 -0
  138. package/dist/composer/runtime-adapters.js.map +1 -0
  139. package/dist/composer/types.d.ts +60 -0
  140. package/dist/composer/types.d.ts.map +1 -0
  141. package/dist/composer/types.js +2 -0
  142. package/dist/composer/types.js.map +1 -0
  143. package/dist/composer/use-file-search.d.ts +6 -0
  144. package/dist/composer/use-file-search.d.ts.map +1 -0
  145. package/dist/composer/use-file-search.js +53 -0
  146. package/dist/composer/use-file-search.js.map +1 -0
  147. package/dist/composer/use-mention-search.d.ts +6 -0
  148. package/dist/composer/use-mention-search.d.ts.map +1 -0
  149. package/dist/composer/use-mention-search.js +75 -0
  150. package/dist/composer/use-mention-search.js.map +1 -0
  151. package/dist/composer/use-skills.d.ts +7 -0
  152. package/dist/composer/use-skills.d.ts.map +1 -0
  153. package/dist/composer/use-skills.js +40 -0
  154. package/dist/composer/use-skills.js.map +1 -0
  155. package/dist/composer/useRealtimeVoiceMode.d.ts +179 -0
  156. package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -0
  157. package/dist/composer/useRealtimeVoiceMode.js +1657 -0
  158. package/dist/composer/useRealtimeVoiceMode.js.map +1 -0
  159. package/dist/composer/useRealtimeVoiceMode.spec.d.ts +2 -0
  160. package/dist/composer/useRealtimeVoiceMode.spec.d.ts.map +1 -0
  161. package/dist/composer/useRealtimeVoiceMode.spec.js +618 -0
  162. package/dist/composer/useRealtimeVoiceMode.spec.js.map +1 -0
  163. package/dist/composer/useVoiceDictation.d.ts +57 -0
  164. package/dist/composer/useVoiceDictation.d.ts.map +1 -0
  165. package/dist/composer/useVoiceDictation.js +1004 -0
  166. package/dist/composer/useVoiceDictation.js.map +1 -0
  167. package/dist/composer/useVoiceDictation.spec.d.ts +2 -0
  168. package/dist/composer/useVoiceDictation.spec.d.ts.map +1 -0
  169. package/dist/composer/useVoiceDictation.spec.js +32 -0
  170. package/dist/composer/useVoiceDictation.spec.js.map +1 -0
  171. package/dist/editor/RegistryBlockContext.d.ts +68 -0
  172. package/dist/editor/RegistryBlockContext.d.ts.map +1 -0
  173. package/dist/editor/RegistryBlockContext.js +10 -0
  174. package/dist/editor/RegistryBlockContext.js.map +1 -0
  175. package/dist/editor/RegistryBlockNode.d.ts +66 -0
  176. package/dist/editor/RegistryBlockNode.d.ts.map +1 -0
  177. package/dist/editor/RegistryBlockNode.js +406 -0
  178. package/dist/editor/RegistryBlockNode.js.map +1 -0
  179. package/dist/editor/RegistryBlockNode.spec.d.ts +2 -0
  180. package/dist/editor/RegistryBlockNode.spec.d.ts.map +1 -0
  181. package/dist/editor/RegistryBlockNode.spec.js +215 -0
  182. package/dist/editor/RegistryBlockNode.spec.js.map +1 -0
  183. package/dist/editor/index.d.ts +2 -0
  184. package/dist/editor/index.d.ts.map +1 -1
  185. package/dist/editor/index.js +2 -0
  186. package/dist/editor/index.js.map +1 -1
  187. package/dist/editor/registrySlashCommands.d.ts +68 -0
  188. package/dist/editor/registrySlashCommands.d.ts.map +1 -0
  189. package/dist/editor/registrySlashCommands.js +51 -0
  190. package/dist/editor/registrySlashCommands.js.map +1 -0
  191. package/package.json +32 -1
  192. package/src/app-shell/header-actions.tsx +84 -0
  193. package/src/app-shell/index.ts +1 -0
  194. package/src/chat-history/ChatHistoryList.spec.tsx +453 -0
  195. package/src/chat-history/ChatHistoryList.tsx +469 -0
  196. package/src/chat-history/index.ts +7 -0
  197. package/src/chat-history.css +314 -0
  198. package/src/collab-ui/AgentPresenceChip.tsx +69 -0
  199. package/src/collab-ui/LiveCursorOverlay.tsx +241 -0
  200. package/src/collab-ui/PresenceBar.tsx +346 -0
  201. package/src/collab-ui/RecentEditHighlights.tsx +225 -0
  202. package/src/collab-ui/RemoteSelectionRings.tsx +227 -0
  203. package/src/collab-ui/agent-identity.ts +16 -0
  204. package/src/collab-ui/index.ts +39 -0
  205. package/src/collab-ui/lead-client.ts +32 -0
  206. package/src/collab-ui/types.spec.ts +18 -0
  207. package/src/collab-ui/types.ts +96 -0
  208. package/src/composer/AgentComposerFrame.tsx +59 -0
  209. package/src/composer/ComposerPlusMenu.tsx +983 -0
  210. package/src/composer/MentionPopover.tsx +412 -0
  211. package/src/composer/PastedTextChip.tsx +150 -0
  212. package/src/composer/PromptComposer.spec.ts +73 -0
  213. package/src/composer/PromptComposer.tsx +718 -0
  214. package/src/composer/RealtimeVoiceMode.spec.tsx +771 -0
  215. package/src/composer/RealtimeVoiceMode.tsx +856 -0
  216. package/src/composer/TiptapComposer.spec.ts +390 -0
  217. package/src/composer/TiptapComposer.tsx +2936 -0
  218. package/src/composer/VoiceButton.spec.ts +33 -0
  219. package/src/composer/VoiceButton.tsx +255 -0
  220. package/src/composer/asset-picker-url.spec.ts +56 -0
  221. package/src/composer/asset-picker-url.ts +80 -0
  222. package/src/composer/attachment-accept.spec.ts +22 -0
  223. package/src/composer/attachment-accept.ts +71 -0
  224. package/src/composer/draft-key.spec.ts +19 -0
  225. package/src/composer/draft-key.ts +7 -0
  226. package/src/composer/extensions/FileReference.tsx +55 -0
  227. package/src/composer/extensions/MentionReference.tsx +93 -0
  228. package/src/composer/extensions/SkillReference.tsx +56 -0
  229. package/src/composer/index.ts +79 -0
  230. package/src/composer/pasted-text.spec.ts +162 -0
  231. package/src/composer/pasted-text.ts +160 -0
  232. package/src/composer/prompt-attachments.spec.ts +80 -0
  233. package/src/composer/prompt-attachments.ts +93 -0
  234. package/src/composer/realtime-voice-audio-level.spec.ts +32 -0
  235. package/src/composer/realtime-voice-audio-level.ts +64 -0
  236. package/src/composer/realtime-voice-transcript.spec.ts +95 -0
  237. package/src/composer/realtime-voice-transcript.ts +134 -0
  238. package/src/composer/runtime-adapters.tsx +343 -0
  239. package/src/composer/types.ts +66 -0
  240. package/src/composer/use-file-search.ts +64 -0
  241. package/src/composer/use-mention-search.ts +90 -0
  242. package/src/composer/use-skills.ts +45 -0
  243. package/src/composer/useRealtimeVoiceMode.spec.ts +818 -0
  244. package/src/composer/useRealtimeVoiceMode.tsx +2117 -0
  245. package/src/composer/useVoiceDictation.spec.ts +45 -0
  246. package/src/composer/useVoiceDictation.ts +1191 -0
  247. package/src/context-ui/ContextMeter.tsx +101 -0
  248. package/src/context-ui/ContextSegmentRow.tsx +184 -0
  249. package/src/context-ui/ContextTreemap.tsx +125 -0
  250. package/src/context-ui/ContextXRayPanel.tsx +377 -0
  251. package/src/context-ui/format.ts +42 -0
  252. package/src/context-ui/index.ts +15 -0
  253. package/src/context-ui/types.ts +43 -0
  254. package/src/design-tweaks/index.ts +1 -0
  255. package/src/design-tweaks/visual-style-controls.tsx +1084 -0
  256. package/src/editor/BubbleToolbar.tsx +253 -0
  257. package/src/editor/CodeBlockNode.tsx +254 -0
  258. package/src/editor/DragHandle.spec.ts +588 -0
  259. package/src/editor/DragHandle.ts +1437 -0
  260. package/src/editor/ImageExtension.roundtrip.spec.ts +117 -0
  261. package/src/editor/ImageExtension.ts +306 -0
  262. package/src/editor/RegistryBlockContext.tsx +107 -0
  263. package/src/editor/RegistryBlockNode.spec.ts +279 -0
  264. package/src/editor/RegistryBlockNode.tsx +652 -0
  265. package/src/editor/RichMarkdownEditor.collab.spec.ts +79 -0
  266. package/src/editor/RichMarkdownEditor.roundtrip.spec.ts +226 -0
  267. package/src/editor/RichMarkdownEditor.tsx +81 -0
  268. package/src/editor/RunId.ts +61 -0
  269. package/src/editor/SharedRichEditor.tsx +321 -0
  270. package/src/editor/SlashCommandMenu.tsx +310 -0
  271. package/src/editor/disableHistory.spec.ts +85 -0
  272. package/src/editor/extensions.ts +402 -0
  273. package/src/editor/gfmDoc.spec.ts +230 -0
  274. package/src/editor/gfmDoc.ts +92 -0
  275. package/src/editor/index.ts +93 -0
  276. package/src/editor/registrySlashCommands.ts +131 -0
  277. package/src/editor/structuralReorderUndo.spec.ts +144 -0
  278. package/src/editor/surgical-apply.spec.ts +279 -0
  279. package/src/editor/surgical-apply.ts +192 -0
  280. package/src/editor/useCollabReconcile.concurrent.spec.ts +422 -0
  281. package/src/editor/useCollabReconcile.idempotent.spec.ts +324 -0
  282. package/src/editor/useCollabReconcile.ts +589 -0
  283. package/src/editor.css +510 -0
  284. package/src/hooks/index.ts +2 -0
  285. package/src/hooks/use-mobile.tsx +21 -0
  286. package/src/hooks/use-toast.ts +188 -0
  287. package/src/index.ts +8 -0
  288. package/src/onboarding/index.ts +1 -0
  289. package/src/provider.tsx +47 -0
  290. package/src/sharing/VisibilityBadge.spec.ts +96 -0
  291. package/src/sharing/VisibilityBadge.tsx +37 -0
  292. package/src/sharing/index.ts +4 -0
  293. package/src/styles.css +21 -0
  294. package/src/ui/accordion.tsx +56 -0
  295. package/src/ui/alert-dialog.tsx +141 -0
  296. package/src/ui/alert.tsx +59 -0
  297. package/src/ui/aspect-ratio.tsx +5 -0
  298. package/src/ui/avatar.tsx +48 -0
  299. package/src/ui/badge.tsx +37 -0
  300. package/src/ui/breadcrumb.tsx +115 -0
  301. package/src/ui/button.tsx +78 -0
  302. package/src/ui/calendar.tsx +214 -0
  303. package/src/ui/card.tsx +86 -0
  304. package/src/ui/carousel.tsx +260 -0
  305. package/src/ui/chart.tsx +375 -0
  306. package/src/ui/checkbox.tsx +28 -0
  307. package/src/ui/collapsible.tsx +9 -0
  308. package/src/ui/command.spec.tsx +51 -0
  309. package/src/ui/command.tsx +174 -0
  310. package/src/ui/context-menu.tsx +202 -0
  311. package/src/ui/date-picker.tsx +64 -0
  312. package/src/ui/dialog.spec.tsx +43 -0
  313. package/src/ui/dialog.tsx +146 -0
  314. package/src/ui/drawer.tsx +126 -0
  315. package/src/ui/dropdown-menu.tsx +218 -0
  316. package/src/ui/form.tsx +177 -0
  317. package/src/ui/hover-card.tsx +27 -0
  318. package/src/ui/index.ts +52 -0
  319. package/src/ui/input-otp.tsx +69 -0
  320. package/src/ui/input.tsx +22 -0
  321. package/src/ui/label.tsx +24 -0
  322. package/src/ui/logo.tsx +36 -0
  323. package/src/ui/menubar.tsx +235 -0
  324. package/src/ui/navigation-menu.tsx +128 -0
  325. package/src/ui/pagination.tsx +121 -0
  326. package/src/ui/popover.tsx +44 -0
  327. package/src/ui/progress.tsx +26 -0
  328. package/src/ui/radio-group.tsx +42 -0
  329. package/src/ui/resizable.tsx +43 -0
  330. package/src/ui/scroll-area.tsx +46 -0
  331. package/src/ui/select.tsx +158 -0
  332. package/src/ui/separator.tsx +29 -0
  333. package/src/ui/sheet.tsx +160 -0
  334. package/src/ui/sidebar.tsx +777 -0
  335. package/src/ui/skeleton.tsx +18 -0
  336. package/src/ui/slider.tsx +26 -0
  337. package/src/ui/sonner.tsx +58 -0
  338. package/src/ui/spinner.tsx +17 -0
  339. package/src/ui/switch.tsx +27 -0
  340. package/src/ui/table.tsx +117 -0
  341. package/src/ui/tabs.tsx +53 -0
  342. package/src/ui/textarea.tsx +23 -0
  343. package/src/ui/toast.tsx +127 -0
  344. package/src/ui/toaster.tsx +33 -0
  345. package/src/ui/toggle-group.tsx +59 -0
  346. package/src/ui/toggle.tsx +43 -0
  347. package/src/ui/tooltip.tsx +53 -0
  348. package/src/ui/use-toast.ts +3 -0
  349. package/src/utils.ts +8 -0
@@ -0,0 +1,718 @@
1
+ import {
2
+ AssistantRuntimeProvider,
3
+ ThreadPrimitive,
4
+ useAui,
5
+ useComposer,
6
+ useLocalRuntime,
7
+ } from "@assistant-ui/react";
8
+ import type {
9
+ Attachment,
10
+ AttachmentAdapter,
11
+ ChatModelAdapter,
12
+ CompleteAttachment,
13
+ PendingAttachment,
14
+ } from "@assistant-ui/react";
15
+ import {
16
+ CompositeAttachmentAdapter,
17
+ SimpleImageAttachmentAdapter,
18
+ } from "@assistant-ui/react";
19
+ import { IconX } from "@tabler/icons-react";
20
+ import {
21
+ useCallback,
22
+ useEffect,
23
+ useMemo,
24
+ useRef,
25
+ useState,
26
+ type CSSProperties,
27
+ type Ref,
28
+ type ReactNode,
29
+ } from "react";
30
+
31
+ import { TooltipProvider } from "../ui/tooltip.js";
32
+ import { cn } from "../utils.js";
33
+ import { AgentComposerFrame } from "./AgentComposerFrame.js";
34
+ import { IMAGE_ATTACHMENT_ACCEPT } from "./attachment-accept.js";
35
+ import {
36
+ PROMPT_DOCUMENT_ATTACHMENT_ACCEPT,
37
+ TextAttachmentAdapter,
38
+ } from "./attachment-accept.js";
39
+ import { isPastedTextAttachmentName } from "./pasted-text.js";
40
+ import { PastedTextChip } from "./PastedTextChip.js";
41
+ import { escapePromptAttachmentAttribute } from "./prompt-attachments.js";
42
+ import {
43
+ type EngineModelGroup,
44
+ type ReasoningEffort,
45
+ useComposerRuntimeAdapters,
46
+ } from "./runtime-adapters.js";
47
+ import {
48
+ DEFAULT_VOICE_DICTATION_ENABLED,
49
+ TiptapComposer,
50
+ type ComposerSubmitIntent,
51
+ type TiptapComposerHandle,
52
+ type TiptapComposerSubmitOptions,
53
+ } from "./TiptapComposer.js";
54
+ import type {
55
+ AgentComposerLayoutVariant,
56
+ Reference,
57
+ SkillResult,
58
+ SlashCommand,
59
+ } from "./types.js";
60
+
61
+ const MAX_INLINE_TEXT_FILE_CHARS = 60_000;
62
+ const SUBMIT_ENGINE_STATUS_TIMEOUT_MS = 1000;
63
+
64
+ /**
65
+ * Files the user attached via the "+" button in PromptComposer. The host owns
66
+ * what to do with them — typically POST to a per-app upload endpoint and pass
67
+ * the resulting URLs/paths into the prompt that gets sent to the agent.
68
+ */
69
+ export type PromptComposerFile = File;
70
+
71
+ export interface PromptComposerSubmitOptions {
72
+ intent?: ComposerSubmitIntent;
73
+ model?: string;
74
+ engine?: string;
75
+ effort?: ReasoningEffort;
76
+ }
77
+
78
+ export interface PromptComposerProps {
79
+ /** Called when the user submits the composer. */
80
+ onSubmit: (
81
+ text: string,
82
+ files: PromptComposerFile[],
83
+ references: Reference[],
84
+ options: PromptComposerSubmitOptions,
85
+ ) => void;
86
+ placeholder?: string;
87
+ disabled?: boolean;
88
+ autoFocus?: boolean;
89
+ className?: string;
90
+ style?: CSSProperties;
91
+ rootClassName?: string;
92
+ rootStyle?: CSSProperties;
93
+ /** Forwarded to TiptapComposer for draft persistence. */
94
+ draftScope?: string;
95
+ /** Keep the submitted prompt in the editor. Default: false. */
96
+ preserveDraftOnSubmit?: boolean;
97
+ /** Show the model selector (default: true). */
98
+ showModelSelector?: boolean;
99
+ /** Show the voice dictation button. Defaults to DEFAULT_VOICE_DICTATION_ENABLED. */
100
+ voiceEnabled?: boolean;
101
+ /** Show file upload controls and pass submitted files to onSubmit (default: true). */
102
+ attachmentsEnabled?: boolean;
103
+ /**
104
+ * Controls the shared "+" affordance. Defaults to upload-only for standalone
105
+ * prompt forms; chat surfaces can opt into the full sidebar menu.
106
+ */
107
+ plusMenuMode?: "full" | "upload-only" | "hidden";
108
+ /** Programmatically seed the composer with plain text. */
109
+ initialText?: string;
110
+ /** Stable key used to re-apply `initialText` when the host picks a preset. */
111
+ initialTextKey?: string | number;
112
+ /** Optional host-owned control rendered directly after the "+" button. */
113
+ modeControl?: ReactNode;
114
+ /** Explicit host-owned toolbar slot rendered directly after the "+" button. */
115
+ toolbarSlot?: ReactNode;
116
+ /** Custom attachment button to render instead of the default "+" affordance. */
117
+ attachButton?: ReactNode;
118
+ /** Custom action button to render instead of the default send button. */
119
+ actionButton?: ReactNode;
120
+ /** Extra button rendered alongside the default send button. */
121
+ extraActionButton?: ReactNode;
122
+ /** Shared sizing/layout variant for host surfaces. Default keeps sidebar behavior. */
123
+ layoutVariant?: AgentComposerLayoutVariant;
124
+ /** Additional slash commands surfaced in the shared / menu. */
125
+ slashCommands?: SlashCommand[];
126
+ /** Additional slash skills surfaced in the shared / menu. */
127
+ slashSkills?: SkillResult[];
128
+ /** Include built-in sidebar slash commands like /clear and /help. Default true. */
129
+ includeDefaultSlashCommands?: boolean;
130
+ /** Include app-discovered skills from the default agent endpoint. Default true. */
131
+ includeDefaultSlashSkills?: boolean;
132
+ /** Called when a slash command from the shared / menu is executed. */
133
+ onSlashCommand?: (command: string) => void;
134
+ /** External model list for hosts that already resolve models outside the app. */
135
+ availableModels?: EngineModelGroup[];
136
+ /** Whether the external model list is still being resolved. */
137
+ modelListLoading?: boolean;
138
+ selectedModel?: string;
139
+ selectedEngine?: string;
140
+ selectedEffort?: ReasoningEffort;
141
+ onModelChange?: (model: string, engine: string) => void;
142
+ onEffortChange?: (effort: ReasoningEffort) => void;
143
+ /**
144
+ * Enable server-backed model/provider status checks. Defaults off when the
145
+ * host supplies model state and callbacks, otherwise on.
146
+ */
147
+ modelStatusChecksEnabled?: boolean;
148
+ /** Called whenever the plain editor text changes. */
149
+ onTextChange?: (text: string) => void;
150
+ /**
151
+ * Override the Builder.io connect action in the model picker. When provided,
152
+ * clicking "Connect Builder.io" calls this instead of opening a browser popup.
153
+ * Used by the Electron desktop app to route through the native IPC handler.
154
+ */
155
+ onConnectProvider?: () => void;
156
+ /** Called when a local runtime needs its native sign-in/setup flow. */
157
+ onConnectLocalRuntime?: (engine: string) => void;
158
+ /** Imperative handle for focusing the composer. */
159
+ composerRef?: Ref<TiptapComposerHandle>;
160
+ }
161
+
162
+ // Minimal pass-through adapter. PromptComposer always submits through
163
+ // onSubmitOverride, so the runtime never actually calls this — but
164
+ // `useLocalRuntime` needs *something* shaped like a ChatModelAdapter.
165
+ const NOOP_ADAPTER: ChatModelAdapter = {
166
+ async *run() {
167
+ return;
168
+ },
169
+ };
170
+
171
+ /**
172
+ * Local binary document adapter so reference PDFs, decks, and docs can be
173
+ * attached without dragging the whole assistant chat module into bundles that
174
+ * just want a prompt popover.
175
+ */
176
+ class BinaryDocumentAttachmentAdapter implements AttachmentAdapter {
177
+ public accept = PROMPT_DOCUMENT_ATTACHMENT_ACCEPT;
178
+
179
+ public async add(state: { file: File }): Promise<PendingAttachment> {
180
+ return {
181
+ id: state.file.name,
182
+ type: "document",
183
+ name: state.file.name,
184
+ contentType: state.file.type || "application/octet-stream",
185
+ file: state.file,
186
+ status: { type: "requires-action", reason: "composer-send" },
187
+ };
188
+ }
189
+
190
+ public async send(
191
+ attachment: PendingAttachment,
192
+ ): Promise<CompleteAttachment> {
193
+ return {
194
+ ...attachment,
195
+ status: { type: "complete" },
196
+ content: [],
197
+ };
198
+ }
199
+
200
+ public async remove() {
201
+ /* noop */
202
+ }
203
+ }
204
+
205
+ class RasterImageAttachmentAdapter extends SimpleImageAttachmentAdapter {
206
+ public accept = IMAGE_ATTACHMENT_ACCEPT;
207
+ }
208
+
209
+ function isInlineableTextFile(file: File): boolean {
210
+ if (file.type.startsWith("text/")) return true;
211
+ if (file.type === "application/json") return true;
212
+ return /\.(txt|md|markdown|csv|json|yaml|yml|html?|css|xml)$/i.test(
213
+ file.name,
214
+ );
215
+ }
216
+
217
+ function formatInlineTextFile(name: string, text: string): string {
218
+ const truncated = text.length > MAX_INLINE_TEXT_FILE_CHARS;
219
+ const body = truncated ? text.slice(0, MAX_INLINE_TEXT_FILE_CHARS) : text;
220
+ return [
221
+ `<uploaded-text-file name="${escapePromptAttachmentAttribute(name)}">`,
222
+ body,
223
+ truncated
224
+ ? `[Truncated after ${MAX_INLINE_TEXT_FILE_CHARS} characters.]`
225
+ : "",
226
+ "</uploaded-text-file>",
227
+ ]
228
+ .filter(Boolean)
229
+ .join("\n");
230
+ }
231
+
232
+ export async function buildPromptComposerSubmission(options: {
233
+ text: string;
234
+ attachments?: ReadonlyArray<unknown>;
235
+ }): Promise<{ text: string; files: File[] }> {
236
+ const files: File[] = [];
237
+ const pastedTextBlocks: string[] = [];
238
+ const rawText = options.text;
239
+
240
+ for (const att of options.attachments ?? []) {
241
+ const a = att as Attachment;
242
+ if ("file" in a && a.file instanceof File) {
243
+ const file = a.file;
244
+ if (isPastedTextAttachmentName(file.name)) {
245
+ try {
246
+ pastedTextBlocks.push(await file.text());
247
+ } catch {
248
+ files.push(file);
249
+ }
250
+ } else {
251
+ if (isInlineableTextFile(file)) {
252
+ try {
253
+ pastedTextBlocks.push(
254
+ formatInlineTextFile(file.name, await file.text()),
255
+ );
256
+ } catch {
257
+ // Keep the upload path fallback below.
258
+ }
259
+ }
260
+ // Note: images are NOT inlined into the prompt text even when small.
261
+ // Inlining a base64 data-URL into a text string consumes an enormous
262
+ // number of tokens (≈ 700 K per MB) and most hosts handle images via
263
+ // proper attachment channels. The `files` array below carries the image
264
+ // for the host to process through a dedicated attachment pipeline.
265
+ files.push(file);
266
+ }
267
+ }
268
+ }
269
+
270
+ return {
271
+ text: pastedTextBlocks.length
272
+ ? [rawText.trim(), ...pastedTextBlocks].filter(Boolean).join("\n\n")
273
+ : rawText,
274
+ files,
275
+ };
276
+ }
277
+
278
+ function getImageSrc(attachment: Attachment): string | null {
279
+ if (attachment.type !== "image") return null;
280
+ if ("file" in attachment && attachment.file) {
281
+ return URL.createObjectURL(attachment.file);
282
+ }
283
+ const imagePart = attachment.content?.find((part) => part.type === "image");
284
+ return imagePart && "image" in imagePart ? imagePart.image : null;
285
+ }
286
+
287
+ function ImagePreviewLightbox({
288
+ src,
289
+ alt,
290
+ onClose,
291
+ }: {
292
+ src: string;
293
+ alt: string;
294
+ onClose: () => void;
295
+ }) {
296
+ useEffect(() => {
297
+ const handler = (e: KeyboardEvent) => {
298
+ if (e.key === "Escape") onClose();
299
+ };
300
+ document.addEventListener("keydown", handler);
301
+ document.body.style.overflow = "hidden";
302
+ return () => {
303
+ document.removeEventListener("keydown", handler);
304
+ document.body.style.overflow = "";
305
+ };
306
+ }, [onClose]);
307
+
308
+ return (
309
+ <div
310
+ role="dialog"
311
+ aria-label="Image preview"
312
+ onClick={onClose}
313
+ className="fixed inset-0 z-[300] flex items-center justify-center bg-black/80 p-6 cursor-zoom-out"
314
+ >
315
+ <img
316
+ src={src}
317
+ alt={alt}
318
+ onClick={(e) => e.stopPropagation()}
319
+ className="max-h-full max-w-full object-contain rounded-md shadow-2xl cursor-default"
320
+ />
321
+ <button
322
+ type="button"
323
+ onClick={onClose}
324
+ aria-label="Close preview"
325
+ className="absolute end-4 top-4 flex h-8 w-8 cursor-pointer items-center justify-center rounded-full border border-white/30 bg-black/40 text-white hover:bg-black/60"
326
+ >
327
+ <IconX className="h-4 w-4" />
328
+ </button>
329
+ </div>
330
+ );
331
+ }
332
+
333
+ function AttachmentChip({
334
+ attachment,
335
+ onRemove,
336
+ }: {
337
+ attachment: Attachment;
338
+ onRemove: (id: string) => void;
339
+ }) {
340
+ const src = useMemo(() => getImageSrc(attachment), [attachment]);
341
+ const [previewOpen, setPreviewOpen] = useState(false);
342
+ useEffect(
343
+ () => () => {
344
+ if (src?.startsWith("blob:")) URL.revokeObjectURL(src);
345
+ },
346
+ [src],
347
+ );
348
+
349
+ if (isPastedTextAttachmentName(attachment.name)) {
350
+ return <PastedTextChip attachment={attachment} onRemove={onRemove} />;
351
+ }
352
+
353
+ if (src) {
354
+ return (
355
+ <>
356
+ <button
357
+ type="button"
358
+ onClick={() => setPreviewOpen(true)}
359
+ aria-label={`Preview ${attachment.name}`}
360
+ className="agent-composer-attachment-image group relative flex h-16 min-w-16 max-w-28 cursor-zoom-in items-center justify-center overflow-hidden rounded-lg border border-border/70 bg-muted/50"
361
+ >
362
+ <img
363
+ src={src}
364
+ alt={attachment.name}
365
+ className="max-h-full max-w-full object-contain p-1"
366
+ />
367
+ <span
368
+ role="button"
369
+ tabIndex={0}
370
+ onClick={(e) => {
371
+ e.stopPropagation();
372
+ onRemove(attachment.id);
373
+ }}
374
+ onKeyDown={(e) => {
375
+ if (e.key === "Enter" || e.key === " ") {
376
+ e.preventDefault();
377
+ e.stopPropagation();
378
+ onRemove(attachment.id);
379
+ }
380
+ }}
381
+ aria-label={`Remove ${attachment.name}`}
382
+ className="absolute end-1 top-1 flex h-5 w-5 cursor-pointer items-center justify-center rounded-full border border-border/60 bg-background/90 text-muted-foreground hover:text-foreground"
383
+ >
384
+ <IconX className="h-3 w-3" />
385
+ </span>
386
+ </button>
387
+ {previewOpen ? (
388
+ <ImagePreviewLightbox
389
+ src={src}
390
+ alt={attachment.name}
391
+ onClose={() => setPreviewOpen(false)}
392
+ />
393
+ ) : null}
394
+ </>
395
+ );
396
+ }
397
+
398
+ return (
399
+ <div className="agent-composer-attachment-chip group relative inline-flex max-w-[200px] items-center gap-2 rounded-md border border-border/70 bg-muted/50 px-2 py-1.5 text-xs">
400
+ <div className="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-background text-[9px] font-semibold uppercase text-muted-foreground">
401
+ {attachment.name.split(".").pop() || "file"}
402
+ </div>
403
+ <span className="min-w-0 truncate font-medium">{attachment.name}</span>
404
+ <button
405
+ type="button"
406
+ onClick={() => onRemove(attachment.id)}
407
+ aria-label={`Remove ${attachment.name}`}
408
+ className="flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded text-muted-foreground hover:text-foreground"
409
+ >
410
+ <IconX className="h-3 w-3" />
411
+ </button>
412
+ </div>
413
+ );
414
+ }
415
+
416
+ function PromptAttachmentStrip() {
417
+ const attachments = useComposer((state) => state.attachments);
418
+ const aui = useAui();
419
+
420
+ const handleRemove = useCallback(
421
+ (id: string) => {
422
+ void aui.composer().attachment({ id }).remove();
423
+ },
424
+ [aui],
425
+ );
426
+
427
+ if (attachments.length === 0) return null;
428
+ return (
429
+ <div className="agent-composer-attachment-strip flex flex-wrap gap-2 px-2 pt-2">
430
+ {attachments.map((attachment) => (
431
+ <AttachmentChip
432
+ key={attachment.id}
433
+ attachment={attachment}
434
+ onRemove={handleRemove}
435
+ />
436
+ ))}
437
+ </div>
438
+ );
439
+ }
440
+
441
+ function PromptComposerInner({
442
+ onSubmit,
443
+ placeholder,
444
+ disabled,
445
+ autoFocus,
446
+ className,
447
+ style,
448
+ rootClassName,
449
+ rootStyle,
450
+ draftScope,
451
+ preserveDraftOnSubmit = false,
452
+ showModelSelector = true,
453
+ voiceEnabled = DEFAULT_VOICE_DICTATION_ENABLED,
454
+ attachmentsEnabled = true,
455
+ plusMenuMode,
456
+ initialText,
457
+ initialTextKey,
458
+ modeControl,
459
+ toolbarSlot,
460
+ attachButton,
461
+ actionButton,
462
+ extraActionButton,
463
+ layoutVariant,
464
+ slashCommands,
465
+ slashSkills,
466
+ includeDefaultSlashCommands,
467
+ includeDefaultSlashSkills,
468
+ onSlashCommand,
469
+ availableModels,
470
+ modelListLoading,
471
+ selectedModel,
472
+ selectedEngine,
473
+ selectedEffort,
474
+ onModelChange,
475
+ onEffortChange,
476
+ modelStatusChecksEnabled,
477
+ onTextChange,
478
+ onConnectProvider,
479
+ onConnectLocalRuntime,
480
+ composerRef,
481
+ }: PromptComposerProps) {
482
+ const adapters = useComposerRuntimeAdapters();
483
+ const modelsAdapter = adapters.models!;
484
+ const BuilderSetupCard = modelsAdapter.BuilderSetupCard;
485
+ const BuilderSetupContent = modelsAdapter.BuilderSetupContent;
486
+ const localRef = useRef<TiptapComposerHandle>(null);
487
+ const handleRef = composerRef ?? localRef;
488
+ const hostManagedModels = Boolean(
489
+ availableModels && selectedModel && onModelChange,
490
+ );
491
+ const resolvedModelStatusChecksEnabled =
492
+ modelStatusChecksEnabled ?? !hostManagedModels;
493
+ const models = modelsAdapter.useChatModels!({
494
+ enabled: showModelSelector && resolvedModelStatusChecksEnabled,
495
+ });
496
+ const composerModel = showModelSelector
497
+ ? (selectedModel ?? models.selectedModel)
498
+ : undefined;
499
+ const composerEngine = showModelSelector
500
+ ? (selectedEngine ?? models.selectedEngine)
501
+ : undefined;
502
+ const composerEffort = showModelSelector
503
+ ? (selectedEffort ?? models.selectedEffort)
504
+ : undefined;
505
+ const composerModelGroups = showModelSelector
506
+ ? (availableModels ?? models.availableModels)
507
+ : undefined;
508
+ const composerModelListLoading =
509
+ showModelSelector &&
510
+ (modelListLoading ??
511
+ (availableModels ? availableModels.length === 0 : models.isLoading));
512
+ const handleModelChange = showModelSelector
513
+ ? (onModelChange ?? models.onModelChange)
514
+ : undefined;
515
+ const handleEffortChange = showModelSelector
516
+ ? (onEffortChange ?? models.onEffortChange)
517
+ : undefined;
518
+ const agentEngineConfigured = modelsAdapter.useAgentEngineConfigured!(
519
+ resolvedModelStatusChecksEnabled,
520
+ );
521
+ const missingApiKey = agentEngineConfigured.missing;
522
+ const [missingKeyBouncePulse, setMissingKeyBouncePulse] = useState(0);
523
+ const bounceMissingKeySetup = useCallback(() => {
524
+ setMissingKeyBouncePulse((pulse) => pulse + 1);
525
+ if (typeof window !== "undefined") {
526
+ window.dispatchEvent(new Event("agent-chat:missing-api-key"));
527
+ }
528
+ }, []);
529
+ const handleBuilderConnected = useCallback(() => {
530
+ if (typeof window !== "undefined") {
531
+ window.dispatchEvent(new Event("agent-engine:configured-changed"));
532
+ }
533
+ }, []);
534
+ const ensureAgentEngineReadyForSubmit = useCallback(async () => {
535
+ if (!resolvedModelStatusChecksEnabled) return true;
536
+ const state =
537
+ agentEngineConfigured.state === "missing"
538
+ ? "missing"
539
+ : await modelsAdapter.fetchAgentEngineConfiguredState!(
540
+ resolvedModelStatusChecksEnabled,
541
+ {
542
+ timeoutMs: SUBMIT_ENGINE_STATUS_TIMEOUT_MS,
543
+ },
544
+ );
545
+ if (state !== "missing") return true;
546
+ bounceMissingKeySetup();
547
+ return false;
548
+ }, [
549
+ agentEngineConfigured.state,
550
+ bounceMissingKeySetup,
551
+ modelsAdapter,
552
+ resolvedModelStatusChecksEnabled,
553
+ ]);
554
+
555
+ useEffect(() => {
556
+ if (!autoFocus) return;
557
+ const id = window.setTimeout(() => {
558
+ const target =
559
+ typeof handleRef === "object" && handleRef && "current" in handleRef
560
+ ? handleRef.current
561
+ : null;
562
+ target?.focus();
563
+ }, 50);
564
+ return () => window.clearTimeout(id);
565
+ }, [autoFocus, handleRef]);
566
+
567
+ const handleSubmit = useCallback(
568
+ async (
569
+ text: string,
570
+ references: Reference[],
571
+ attachments?: ReadonlyArray<unknown>,
572
+ submitOptions?: TiptapComposerSubmitOptions,
573
+ ) => {
574
+ // PromptComposer hosts (NewWorkspaceAppFlow, create-extension, create-deck,
575
+ // …) submit a single string prompt — they don't run the assistant-ui
576
+ // attachment send pipeline. TiptapComposer auto-converts large pastes
577
+ // into a "Pasted text" chip, which would otherwise disappear into an
578
+ // unprocessed File. Inline the chip body back into the prompt text so
579
+ // newlines and full content survive the round-trip.
580
+ const { text: finalText, files } = await buildPromptComposerSubmission({
581
+ text,
582
+ attachments,
583
+ });
584
+ onSubmit(finalText, files, references, {
585
+ intent: submitOptions?.intent ?? "immediate",
586
+ model: composerModel,
587
+ engine: composerEngine,
588
+ effort: composerEffort,
589
+ });
590
+ },
591
+ [composerEffort, composerEngine, composerModel, onSubmit],
592
+ );
593
+ const useInlineMissingKeySetup = layoutVariant === "compact";
594
+
595
+ return (
596
+ <>
597
+ {missingApiKey && !useInlineMissingKeySetup && BuilderSetupCard ? (
598
+ <BuilderSetupCard
599
+ onConnected={handleBuilderConnected}
600
+ bouncePulse={missingKeyBouncePulse}
601
+ fullWidth
602
+ layout="sidebar"
603
+ />
604
+ ) : null}
605
+ {missingApiKey && useInlineMissingKeySetup && BuilderSetupContent ? (
606
+ <div className="mb-2 rounded-md border border-border/80 bg-background/80 p-2.5 text-start shadow-sm">
607
+ <BuilderSetupContent
608
+ onConnected={handleBuilderConnected}
609
+ layout="sidebar"
610
+ />
611
+ </div>
612
+ ) : null}
613
+ <AgentComposerFrame
614
+ className={cn(
615
+ "text-start",
616
+ missingApiKey && "cursor-pointer",
617
+ className,
618
+ )}
619
+ rootClassName={rootClassName}
620
+ style={style}
621
+ rootStyle={rootStyle}
622
+ layoutVariant={layoutVariant}
623
+ onClick={missingApiKey ? bounceMissingKeySetup : undefined}
624
+ >
625
+ <PromptAttachmentStrip />
626
+ <TiptapComposer
627
+ focusRef={handleRef}
628
+ disabled={disabled || missingApiKey}
629
+ placeholder={
630
+ missingApiKey ? "Connect AI above to continue..." : placeholder
631
+ }
632
+ initialText={initialText}
633
+ initialTextKey={initialTextKey}
634
+ onSubmit={handleSubmit}
635
+ onBeforeSubmit={ensureAgentEngineReadyForSubmit}
636
+ clearOnSubmit={!preserveDraftOnSubmit}
637
+ plusMenuMode={
638
+ plusMenuMode ?? (attachmentsEnabled ? "upload-only" : "hidden")
639
+ }
640
+ attachButton={attachButton}
641
+ modeControl={modeControl}
642
+ toolbarSlot={toolbarSlot}
643
+ actionButton={actionButton}
644
+ extraActionButton={extraActionButton}
645
+ layoutVariant={layoutVariant}
646
+ slashCommands={slashCommands}
647
+ slashSkills={slashSkills}
648
+ includeDefaultSlashCommands={includeDefaultSlashCommands}
649
+ includeDefaultSlashSkills={includeDefaultSlashSkills}
650
+ onSlashCommand={onSlashCommand}
651
+ voiceEnabled={voiceEnabled}
652
+ onTextChange={onTextChange}
653
+ draftScope={draftScope}
654
+ selectedModel={composerModel}
655
+ selectedEffort={composerEffort}
656
+ availableModels={composerModelGroups}
657
+ modelListLoading={composerModelListLoading}
658
+ onModelChange={handleModelChange}
659
+ onEffortChange={handleEffortChange}
660
+ providerConnectStatusEnabled={resolvedModelStatusChecksEnabled}
661
+ onConnectProvider={onConnectProvider}
662
+ onConnectLocalRuntime={onConnectLocalRuntime}
663
+ />
664
+ </AgentComposerFrame>
665
+ </>
666
+ );
667
+ }
668
+
669
+ /**
670
+ * Standalone composer that mirrors the agent sidebar's input experience —
671
+ * voice dictation, file upload, model selector, submit-on-Enter — for use in
672
+ * popovers and inline prompt forms (create tool, create deck, create dashboard,
673
+ * the Dispatch new-app flow, etc.).
674
+ *
675
+ * The host owns submission: when the user presses Enter or clicks submit,
676
+ * `onSubmit(text, files, references, options)` is called. PromptComposer runs
677
+ * its own minimal assistant-ui runtime so it can be dropped into any subtree
678
+ * without needing the outer chat to be mounted.
679
+ */
680
+ export function PromptComposer(props: PromptComposerProps) {
681
+ const StaleIndexBoundary =
682
+ useComposerRuntimeAdapters().agentChat!.StaleIndexBoundary!;
683
+ const attachmentAdapter = useMemo(
684
+ () =>
685
+ new CompositeAttachmentAdapter([
686
+ new RasterImageAttachmentAdapter(),
687
+ new BinaryDocumentAttachmentAdapter(),
688
+ new TextAttachmentAdapter(),
689
+ ]),
690
+ [],
691
+ );
692
+ const runtime = useLocalRuntime(NOOP_ADAPTER, {
693
+ adapters: { attachments: attachmentAdapter },
694
+ });
695
+ const resetKey = [
696
+ props.draftScope ?? "",
697
+ props.initialTextKey ?? "",
698
+ props.initialText ?? "",
699
+ ].join(":");
700
+
701
+ return (
702
+ <TooltipProvider delayDuration={200}>
703
+ <AssistantRuntimeProvider runtime={runtime}>
704
+ <ThreadPrimitive.Root
705
+ className="contents"
706
+ style={{ display: "contents" }}
707
+ >
708
+ <StaleIndexBoundary
709
+ resetKey={resetKey}
710
+ componentName="PromptComposer"
711
+ >
712
+ <PromptComposerInner {...props} />
713
+ </StaleIndexBoundary>
714
+ </ThreadPrimitive.Root>
715
+ </AssistantRuntimeProvider>
716
+ </TooltipProvider>
717
+ );
718
+ }