@elabs-ai/components-ai 4.2.0 → 5.1.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 (462) hide show
  1. package/README.md +1 -1
  2. package/dist/{_audio-player-media-chrome-T3XVXWRZ.js → _audio-player-media-chrome.js} +1 -1
  3. package/dist/_code-block-theme.js +12 -0
  4. package/dist/_code-block-theme.js.map +1 -0
  5. package/dist/{_flow-boundary-SHNWLQG5.js → _flow-boundary.js} +1 -1
  6. package/dist/_flow-boundary.js.map +1 -0
  7. package/dist/_flow-lazy.js +8 -0
  8. package/dist/_flow-lazy.js.map +1 -0
  9. package/dist/_lazy-boundary-conformance.js +2 -0
  10. package/dist/_lazy-boundary-conformance.js.map +1 -0
  11. package/dist/_lazy-cjk.js +13 -0
  12. package/dist/_lazy-cjk.js.map +1 -0
  13. package/dist/_lazy-engine-boundary.js +9 -0
  14. package/dist/_lazy-engine-boundary.js.map +1 -0
  15. package/dist/_lazy-math.js +13 -0
  16. package/dist/_lazy-math.js.map +1 -0
  17. package/dist/_lazy-mermaid.js +12 -0
  18. package/dist/_lazy-mermaid.js.map +1 -0
  19. package/dist/_mermaid-error-panel.js +9 -0
  20. package/dist/_mermaid-error-panel.js.map +1 -0
  21. package/dist/{_persona-rive-JEG44YHX.js → _persona-rive.js} +19 -24
  22. package/dist/_persona-rive.js.map +1 -0
  23. package/dist/_streamdown-i18n.js +21 -0
  24. package/dist/_streamdown-i18n.js.map +1 -0
  25. package/dist/_streamdown-safety.js +10 -0
  26. package/dist/_streamdown-safety.js.map +1 -0
  27. package/dist/_theme-scope-store.js +13 -0
  28. package/dist/_theme-scope-store.js.map +1 -0
  29. package/dist/a2ui/a2ui-surface.js +17 -0
  30. package/dist/a2ui/a2ui-surface.js.map +1 -0
  31. package/dist/a2ui/core/catalog.generated.js +10 -0
  32. package/dist/a2ui/core/catalog.generated.js.map +1 -0
  33. package/dist/a2ui/core/complete-json.js +10 -0
  34. package/dist/a2ui/core/complete-json.js.map +1 -0
  35. package/dist/a2ui/core/index.js +37 -0
  36. package/dist/a2ui/core/index.js.map +1 -0
  37. package/dist/a2ui/core/schema.js +11 -0
  38. package/dist/a2ui/core/schema.js.map +1 -0
  39. package/dist/a2ui/core/spec.js +10 -0
  40. package/dist/a2ui/core/spec.js.map +1 -0
  41. package/dist/a2ui/core/validate.js +16 -0
  42. package/dist/a2ui/core/validate.js.map +1 -0
  43. package/dist/a2ui/index.js +54 -0
  44. package/dist/a2ui/index.js.map +1 -0
  45. package/dist/a2ui/ui-catalog.js +18 -0
  46. package/dist/a2ui/ui-catalog.js.map +1 -0
  47. package/dist/agent-event.js +10 -0
  48. package/dist/agent-event.js.map +1 -0
  49. package/dist/agent-timeline.js +11 -0
  50. package/dist/agent-timeline.js.map +1 -0
  51. package/dist/agent.js +25 -0
  52. package/dist/agent.js.map +1 -0
  53. package/dist/artifact.js +23 -0
  54. package/dist/artifact.js.map +1 -0
  55. package/dist/asset-preview.js +22 -0
  56. package/dist/asset-preview.js.map +1 -0
  57. package/dist/attachments.js +33 -0
  58. package/dist/attachments.js.map +1 -0
  59. package/dist/audio-player.js +30 -0
  60. package/dist/audio-player.js.map +1 -0
  61. package/dist/audio-visualizer.js +9 -0
  62. package/dist/audio-visualizer.js.map +1 -0
  63. package/dist/canvas.js +10 -0
  64. package/dist/canvas.js.map +1 -0
  65. package/dist/chain-of-thought.js +22 -0
  66. package/dist/chain-of-thought.js.map +1 -0
  67. package/dist/chat-greeting.js +8 -0
  68. package/dist/chat-greeting.js.map +1 -0
  69. package/dist/chat-shell.js +9 -0
  70. package/dist/chat-shell.js.map +1 -0
  71. package/dist/checkpoint.js +13 -0
  72. package/dist/checkpoint.js.map +1 -0
  73. package/dist/chunk-2XOIG5TG.js +72 -0
  74. package/dist/chunk-2XOIG5TG.js.map +1 -0
  75. package/dist/chunk-34CQ2VHM.js +172 -0
  76. package/dist/chunk-34CQ2VHM.js.map +1 -0
  77. package/dist/chunk-3TDHPXZZ.js +60 -0
  78. package/dist/chunk-3TDHPXZZ.js.map +1 -0
  79. package/dist/chunk-3XNPMDZY.js +478 -0
  80. package/dist/chunk-3XNPMDZY.js.map +1 -0
  81. package/dist/chunk-3YPT3V7L.js +56 -0
  82. package/dist/chunk-3YPT3V7L.js.map +1 -0
  83. package/dist/chunk-475UEU2Q.js +87 -0
  84. package/dist/chunk-475UEU2Q.js.map +1 -0
  85. package/dist/chunk-4CBTII2I.js +95 -0
  86. package/dist/chunk-4CBTII2I.js.map +1 -0
  87. package/dist/chunk-4XRNIZMG.js +22 -0
  88. package/dist/chunk-4XRNIZMG.js.map +1 -0
  89. package/dist/chunk-5NNWUKK5.js +2 -0
  90. package/dist/chunk-5NNWUKK5.js.map +1 -0
  91. package/dist/chunk-5UOMU27Z.js +164 -0
  92. package/dist/chunk-5UOMU27Z.js.map +1 -0
  93. package/dist/chunk-5VGYZ62I.js +264 -0
  94. package/dist/chunk-5VGYZ62I.js.map +1 -0
  95. package/dist/chunk-6IQPH2OS.js +21 -0
  96. package/dist/chunk-6IQPH2OS.js.map +1 -0
  97. package/dist/chunk-6PB2MXCI.js +482 -0
  98. package/dist/chunk-6PB2MXCI.js.map +1 -0
  99. package/dist/chunk-7FD5CZEU.js +215 -0
  100. package/dist/chunk-7FD5CZEU.js.map +1 -0
  101. package/dist/chunk-7RL6KZDF.js +266 -0
  102. package/dist/chunk-7RL6KZDF.js.map +1 -0
  103. package/dist/chunk-ADOYAGGK.js +482 -0
  104. package/dist/chunk-ADOYAGGK.js.map +1 -0
  105. package/dist/chunk-BA3AEMOU.js +21 -0
  106. package/dist/chunk-BA3AEMOU.js.map +1 -0
  107. package/dist/chunk-BBHFOYML.js +336 -0
  108. package/dist/chunk-BBHFOYML.js.map +1 -0
  109. package/dist/chunk-BHUVISIY.js +221 -0
  110. package/dist/chunk-BHUVISIY.js.map +1 -0
  111. package/dist/chunk-BM2LWERA.js +100 -0
  112. package/dist/chunk-BM2LWERA.js.map +1 -0
  113. package/dist/chunk-CMOYXNPJ.js +211 -0
  114. package/dist/chunk-CMOYXNPJ.js.map +1 -0
  115. package/dist/chunk-CZP3RHC4.js +74 -0
  116. package/dist/chunk-CZP3RHC4.js.map +1 -0
  117. package/dist/chunk-DDG5JHQT.js +112 -0
  118. package/dist/chunk-DDG5JHQT.js.map +1 -0
  119. package/dist/chunk-DJODXO7I.js +38 -0
  120. package/dist/chunk-DJODXO7I.js.map +1 -0
  121. package/dist/chunk-DWJDJLPU.js +192 -0
  122. package/dist/chunk-DWJDJLPU.js.map +1 -0
  123. package/dist/chunk-DZW6LM22.js +45 -0
  124. package/dist/chunk-DZW6LM22.js.map +1 -0
  125. package/dist/chunk-ELGCZLZF.js +305 -0
  126. package/dist/chunk-ELGCZLZF.js.map +1 -0
  127. package/dist/chunk-EV3U6ZZB.js +209 -0
  128. package/dist/chunk-EV3U6ZZB.js.map +1 -0
  129. package/dist/chunk-EY7LVUKQ.js +87 -0
  130. package/dist/chunk-EY7LVUKQ.js.map +1 -0
  131. package/dist/chunk-F4C74F7M.js +230 -0
  132. package/dist/chunk-F4C74F7M.js.map +1 -0
  133. package/dist/chunk-FERYDFZA.js +108 -0
  134. package/dist/chunk-FERYDFZA.js.map +1 -0
  135. package/dist/chunk-FUIYG2DS.js +166 -0
  136. package/dist/chunk-FUIYG2DS.js.map +1 -0
  137. package/dist/chunk-GA7M34KU.js +236 -0
  138. package/dist/chunk-GA7M34KU.js.map +1 -0
  139. package/dist/chunk-GKYZT3LA.js +136 -0
  140. package/dist/chunk-GKYZT3LA.js.map +1 -0
  141. package/dist/chunk-H42LRUQ4.js +102 -0
  142. package/dist/chunk-H42LRUQ4.js.map +1 -0
  143. package/dist/chunk-HVXM2G5F.js +222 -0
  144. package/dist/chunk-HVXM2G5F.js.map +1 -0
  145. package/dist/chunk-I4RDAGVY.js +53 -0
  146. package/dist/chunk-I4RDAGVY.js.map +1 -0
  147. package/dist/chunk-IQJG4IDX.js +208 -0
  148. package/dist/chunk-IQJG4IDX.js.map +1 -0
  149. package/dist/chunk-IX2PXG4Y.js +144 -0
  150. package/dist/chunk-IX2PXG4Y.js.map +1 -0
  151. package/dist/chunk-IXICSOOP.js +287 -0
  152. package/dist/chunk-IXICSOOP.js.map +1 -0
  153. package/dist/chunk-JY5HSN4D.js +84 -0
  154. package/dist/chunk-JY5HSN4D.js.map +1 -0
  155. package/dist/chunk-L4SU3QJV.js +174 -0
  156. package/dist/chunk-L4SU3QJV.js.map +1 -0
  157. package/dist/chunk-LMNSBOBG.js +19 -0
  158. package/dist/chunk-LMNSBOBG.js.map +1 -0
  159. package/dist/chunk-MBI7D6IL.js +42 -0
  160. package/dist/chunk-MBI7D6IL.js.map +1 -0
  161. package/dist/chunk-MLFMYCPH.js +192 -0
  162. package/dist/chunk-MLFMYCPH.js.map +1 -0
  163. package/dist/chunk-MOK66734.js +33 -0
  164. package/dist/chunk-MOK66734.js.map +1 -0
  165. package/dist/chunk-MXTAWPYM.js +196 -0
  166. package/dist/chunk-MXTAWPYM.js.map +1 -0
  167. package/dist/chunk-NCL2UZKI.js +96 -0
  168. package/dist/chunk-NCL2UZKI.js.map +1 -0
  169. package/dist/chunk-NET4YLRY.js +107 -0
  170. package/dist/chunk-NET4YLRY.js.map +1 -0
  171. package/dist/chunk-NIG67CFM.js +329 -0
  172. package/dist/chunk-NIG67CFM.js.map +1 -0
  173. package/dist/chunk-NMVNNO3L.js +10 -0
  174. package/dist/chunk-NMVNNO3L.js.map +1 -0
  175. package/dist/chunk-NP7VID4K.js +2 -0
  176. package/dist/chunk-NP7VID4K.js.map +1 -0
  177. package/dist/chunk-NPUCMAG2.js +382 -0
  178. package/dist/chunk-NPUCMAG2.js.map +1 -0
  179. package/dist/chunk-NSCLRHES.js +102 -0
  180. package/dist/chunk-NSCLRHES.js.map +1 -0
  181. package/dist/chunk-OEUXHVFW.js +86 -0
  182. package/dist/chunk-OEUXHVFW.js.map +1 -0
  183. package/dist/chunk-ONBHFIMZ.js +137 -0
  184. package/dist/chunk-ONBHFIMZ.js.map +1 -0
  185. package/dist/chunk-OPH2HHL2.js +228 -0
  186. package/dist/chunk-OPH2HHL2.js.map +1 -0
  187. package/dist/chunk-OQERHIQP.js +287 -0
  188. package/dist/chunk-OQERHIQP.js.map +1 -0
  189. package/dist/chunk-P6YJAZFG.js +103 -0
  190. package/dist/chunk-P6YJAZFG.js.map +1 -0
  191. package/dist/chunk-PG7ERDOD.js +65 -0
  192. package/dist/chunk-PG7ERDOD.js.map +1 -0
  193. package/dist/chunk-PIB5JBMA.js +90 -0
  194. package/dist/chunk-PIB5JBMA.js.map +1 -0
  195. package/dist/chunk-PQ5BPIWQ.js +22 -0
  196. package/dist/chunk-PQ5BPIWQ.js.map +1 -0
  197. package/dist/chunk-PQBF6ZIQ.js +55 -0
  198. package/dist/chunk-PQBF6ZIQ.js.map +1 -0
  199. package/dist/chunk-PTWCTSZL.js +173 -0
  200. package/dist/chunk-PTWCTSZL.js.map +1 -0
  201. package/dist/chunk-QJUNPVOS.js +101 -0
  202. package/dist/chunk-QJUNPVOS.js.map +1 -0
  203. package/dist/chunk-QOBSEGCG.js +996 -0
  204. package/dist/chunk-QOBSEGCG.js.map +1 -0
  205. package/dist/chunk-QUDZOTRV.js +43 -0
  206. package/dist/chunk-QUDZOTRV.js.map +1 -0
  207. package/dist/chunk-RB7G65JC.js +213 -0
  208. package/dist/chunk-RB7G65JC.js.map +1 -0
  209. package/dist/chunk-RPHAOR7P.js +45 -0
  210. package/dist/chunk-RPHAOR7P.js.map +1 -0
  211. package/dist/chunk-RTCSH2AN.js +331 -0
  212. package/dist/chunk-RTCSH2AN.js.map +1 -0
  213. package/dist/chunk-S3ISPW3A.js +28 -0
  214. package/dist/chunk-S3ISPW3A.js.map +1 -0
  215. package/dist/chunk-SEVN656N.js +36 -0
  216. package/dist/chunk-SEVN656N.js.map +1 -0
  217. package/dist/chunk-SKVG672B.js +64 -0
  218. package/dist/chunk-SKVG672B.js.map +1 -0
  219. package/dist/chunk-SSGJ6MEM.js +1076 -0
  220. package/dist/chunk-SSGJ6MEM.js.map +1 -0
  221. package/dist/chunk-SSKZ2AED.js +118 -0
  222. package/dist/chunk-SSKZ2AED.js.map +1 -0
  223. package/dist/chunk-T56JFGXI.js +561 -0
  224. package/dist/chunk-T56JFGXI.js.map +1 -0
  225. package/dist/chunk-TCB6O74A.js +137 -0
  226. package/dist/chunk-TCB6O74A.js.map +1 -0
  227. package/dist/chunk-TTEYPO6X.js +57 -0
  228. package/dist/chunk-TTEYPO6X.js.map +1 -0
  229. package/dist/chunk-TWPHAJNM.js +462 -0
  230. package/dist/chunk-TWPHAJNM.js.map +1 -0
  231. package/dist/chunk-TXJJLP4O.js +336 -0
  232. package/dist/chunk-TXJJLP4O.js.map +1 -0
  233. package/dist/chunk-TZ6TB7VM.js +245 -0
  234. package/dist/chunk-TZ6TB7VM.js.map +1 -0
  235. package/dist/chunk-UBLKJJ7K.js +278 -0
  236. package/dist/chunk-UBLKJJ7K.js.map +1 -0
  237. package/dist/chunk-UCJ6IUFS.js +52 -0
  238. package/dist/chunk-UCJ6IUFS.js.map +1 -0
  239. package/dist/chunk-UCKFJ3QF.js +197 -0
  240. package/dist/chunk-UCKFJ3QF.js.map +1 -0
  241. package/dist/chunk-UPXSLILN.js +20 -0
  242. package/dist/chunk-UPXSLILN.js.map +1 -0
  243. package/dist/chunk-V4DORGWJ.js +92 -0
  244. package/dist/chunk-V4DORGWJ.js.map +1 -0
  245. package/dist/chunk-V6E5DYZD.js +80 -0
  246. package/dist/chunk-V6E5DYZD.js.map +1 -0
  247. package/dist/chunk-VLLYKF3P.js +96 -0
  248. package/dist/chunk-VLLYKF3P.js.map +1 -0
  249. package/dist/chunk-VLNBTOH6.js +43 -0
  250. package/dist/chunk-VLNBTOH6.js.map +1 -0
  251. package/dist/chunk-WMDSMSPL.js +303 -0
  252. package/dist/chunk-WMDSMSPL.js.map +1 -0
  253. package/dist/chunk-WU5BLGTF.js +149 -0
  254. package/dist/chunk-WU5BLGTF.js.map +1 -0
  255. package/dist/chunk-WYTZVSGK.js +245 -0
  256. package/dist/chunk-WYTZVSGK.js.map +1 -0
  257. package/dist/chunk-X47QMM6K.js +187 -0
  258. package/dist/chunk-X47QMM6K.js.map +1 -0
  259. package/dist/chunk-YHUSW7D2.js +40 -0
  260. package/dist/chunk-YHUSW7D2.js.map +1 -0
  261. package/dist/chunk-YSKD7UXX.js +126 -0
  262. package/dist/chunk-YSKD7UXX.js.map +1 -0
  263. package/dist/chunk-YYRDGWOU.js +72 -0
  264. package/dist/chunk-YYRDGWOU.js.map +1 -0
  265. package/dist/chunk-ZIGZMWM7.js +19 -0
  266. package/dist/chunk-ZIGZMWM7.js.map +1 -0
  267. package/dist/chunk-ZKRTUKNP.js +40 -0
  268. package/dist/chunk-ZKRTUKNP.js.map +1 -0
  269. package/dist/code-block.js +38 -0
  270. package/dist/code-block.js.map +1 -0
  271. package/dist/commit.js +51 -0
  272. package/dist/commit.js.map +1 -0
  273. package/dist/composer.js +15 -0
  274. package/dist/composer.js.map +1 -0
  275. package/dist/confirmation.js +53 -0
  276. package/dist/confirmation.js.map +1 -0
  277. package/dist/connection.js +8 -0
  278. package/dist/connection.js.map +1 -0
  279. package/dist/context-panel.js +25 -0
  280. package/dist/context-panel.js.map +1 -0
  281. package/dist/controls.js +10 -0
  282. package/dist/controls.js.map +1 -0
  283. package/dist/conversation.js +19 -0
  284. package/dist/conversation.js.map +1 -0
  285. package/dist/diff-view.js +15 -0
  286. package/dist/diff-view.js.map +1 -0
  287. package/dist/edge.js +10 -0
  288. package/dist/edge.js.map +1 -0
  289. package/dist/environment-variables.js +29 -0
  290. package/dist/environment-variables.js.map +1 -0
  291. package/dist/file-tree.js +23 -0
  292. package/dist/file-tree.js.map +1 -0
  293. package/dist/gallery.js +9 -0
  294. package/dist/gallery.js.map +1 -0
  295. package/dist/grouped-parts.js +21 -0
  296. package/dist/grouped-parts.js.map +1 -0
  297. package/dist/image.js +9 -0
  298. package/dist/image.js.map +1 -0
  299. package/dist/index.d.ts +366 -98
  300. package/dist/index.js +672 -13577
  301. package/dist/index.js.map +1 -1
  302. package/dist/inline-citation.js +39 -0
  303. package/dist/inline-citation.js.map +1 -0
  304. package/dist/jsx-preview.js +17 -0
  305. package/dist/jsx-preview.js.map +1 -0
  306. package/dist/markdown-view.js +17 -0
  307. package/dist/markdown-view.js.map +1 -0
  308. package/dist/message-compare.js +12 -0
  309. package/dist/message-compare.js.map +1 -0
  310. package/dist/message-edit.js +19 -0
  311. package/dist/message-edit.js.map +1 -0
  312. package/dist/message-feedback.js +11 -0
  313. package/dist/message-feedback.js.map +1 -0
  314. package/dist/message-form-spec.js +38 -0
  315. package/dist/message-form-spec.js.map +1 -0
  316. package/dist/message-form.js +26 -0
  317. package/dist/message-form.js.map +1 -0
  318. package/dist/message-table-spec.js +26 -0
  319. package/dist/message-table-spec.js.map +1 -0
  320. package/dist/message-table.js +12 -0
  321. package/dist/message-table.js.map +1 -0
  322. package/dist/message.js +51 -0
  323. package/dist/message.js.map +1 -0
  324. package/dist/mic-selector.js +27 -0
  325. package/dist/mic-selector.js.map +1 -0
  326. package/dist/model-provider-logo.js +13 -0
  327. package/dist/model-provider-logo.js.map +1 -0
  328. package/dist/motion-config.js +9 -0
  329. package/dist/motion-config.js.map +1 -0
  330. package/dist/node.js +22 -0
  331. package/dist/node.js.map +1 -0
  332. package/dist/open-in-chat.js +31 -0
  333. package/dist/open-in-chat.js.map +1 -0
  334. package/dist/package-info.js +25 -0
  335. package/dist/package-info.js.map +1 -0
  336. package/dist/panel.js +10 -0
  337. package/dist/panel.js.map +1 -0
  338. package/dist/part-groups.js +18 -0
  339. package/dist/part-groups.js.map +1 -0
  340. package/dist/permission-mode-select.js +9 -0
  341. package/dist/permission-mode-select.js.map +1 -0
  342. package/dist/persona-sources.js +8 -0
  343. package/dist/persona-sources.js.map +1 -0
  344. package/dist/persona.js +14 -0
  345. package/dist/persona.js.map +1 -0
  346. package/dist/plan.js +32 -0
  347. package/dist/plan.js.map +1 -0
  348. package/dist/prompt-input-effort.js +9 -0
  349. package/dist/prompt-input-effort.js.map +1 -0
  350. package/dist/prompt-input-mode.js +10 -0
  351. package/dist/prompt-input-mode.js.map +1 -0
  352. package/dist/prompt-input-slash.js +12 -0
  353. package/dist/prompt-input-slash.js.map +1 -0
  354. package/dist/prompt-input.js +89 -0
  355. package/dist/prompt-input.js.map +1 -0
  356. package/dist/queue.js +37 -0
  357. package/dist/queue.js.map +1 -0
  358. package/dist/reasoning.js +23 -0
  359. package/dist/reasoning.js.map +1 -0
  360. package/dist/sandbox.js +28 -0
  361. package/dist/sandbox.js.map +1 -0
  362. package/dist/schema-display.js +33 -0
  363. package/dist/schema-display.js.map +1 -0
  364. package/dist/selection-toolbar.js +11 -0
  365. package/dist/selection-toolbar.js.map +1 -0
  366. package/dist/session-header.js +9 -0
  367. package/dist/session-header.js.map +1 -0
  368. package/dist/session-status-bar.js +9 -0
  369. package/dist/session-status-bar.js.map +1 -0
  370. package/dist/shimmer.js +9 -0
  371. package/dist/shimmer.js.map +1 -0
  372. package/dist/snippet.js +17 -0
  373. package/dist/snippet.js.map +1 -0
  374. package/dist/sources.js +17 -0
  375. package/dist/sources.js.map +1 -0
  376. package/dist/speech-input.js +9 -0
  377. package/dist/speech-input.js.map +1 -0
  378. package/dist/stack-trace.js +27 -0
  379. package/dist/stack-trace.js.map +1 -0
  380. package/dist/suggestion.js +16 -0
  381. package/dist/suggestion.js.map +1 -0
  382. package/dist/task.js +18 -0
  383. package/dist/task.js.map +1 -0
  384. package/dist/test-results.js +43 -0
  385. package/dist/test-results.js.map +1 -0
  386. package/dist/token-usage.js +27 -0
  387. package/dist/token-usage.js.map +1 -0
  388. package/dist/tool-result-card.js +9 -0
  389. package/dist/tool-result-card.js.map +1 -0
  390. package/dist/tool.js +27 -0
  391. package/dist/tool.js.map +1 -0
  392. package/dist/toolbar.js +10 -0
  393. package/dist/toolbar.js.map +1 -0
  394. package/dist/transcription.js +11 -0
  395. package/dist/transcription.js.map +1 -0
  396. package/dist/turn-status.js +9 -0
  397. package/dist/turn-status.js.map +1 -0
  398. package/dist/use-audio-level.js +9 -0
  399. package/dist/use-audio-level.js.map +1 -0
  400. package/dist/voice-selector.js +47 -0
  401. package/dist/voice-selector.js.map +1 -0
  402. package/dist/web-preview.js +19 -0
  403. package/dist/web-preview.js.map +1 -0
  404. package/package.json +11 -9
  405. package/schemas/a2ui-surface.v1.schema.json +5434 -0
  406. package/src/_chat-shell-rail.tsx +3 -1
  407. package/src/_flow-boundary.tsx +2 -2
  408. package/src/_persona-rive.tsx +27 -41
  409. package/src/a2ui/a2ui-surface.stories.tsx +662 -0
  410. package/src/a2ui/a2ui-surface.test.tsx +201 -0
  411. package/src/a2ui/a2ui-surface.tsx +354 -0
  412. package/src/a2ui/catalog.source.json +772 -0
  413. package/src/a2ui/core/a2ui-core.test.ts +178 -0
  414. package/src/a2ui/core/catalog.generated.ts +1019 -0
  415. package/src/a2ui/core/complete-json.ts +81 -0
  416. package/src/a2ui/core/index.ts +10 -0
  417. package/src/a2ui/core/schema.ts +111 -0
  418. package/src/a2ui/core/spec.ts +148 -0
  419. package/src/a2ui/core/validate.ts +266 -0
  420. package/src/a2ui/index.ts +7 -0
  421. package/src/a2ui/ui-catalog.tsx +225 -0
  422. package/src/a2ui/wave1-chart-spec.test.ts +87 -0
  423. package/src/a2ui/wave2-chart-spec.test.ts +108 -0
  424. package/src/agentic-workspace.stories.tsx +6 -45
  425. package/src/chat-shell.test.tsx +40 -10
  426. package/src/chat-shell.tsx +90 -44
  427. package/src/chat.stories.tsx +47 -42
  428. package/src/composer.stories.tsx +4 -5
  429. package/src/composer.test.tsx +45 -0
  430. package/src/composer.tsx +104 -21
  431. package/src/context-panel.stories.tsx +1 -1
  432. package/src/context-panel.tsx +1 -1
  433. package/src/conversation.stories.tsx +1 -0
  434. package/src/conversation.test.tsx +16 -0
  435. package/src/conversation.tsx +16 -2
  436. package/src/diff-view.stories.tsx +79 -0
  437. package/src/diff-view.tsx +3 -2
  438. package/src/file-tree.tsx +24 -17
  439. package/src/gallery.stories.tsx +42 -0
  440. package/src/gallery.tsx +2 -2
  441. package/src/grouped-parts.stories.tsx +23 -0
  442. package/src/grouped-parts.tsx +1 -1
  443. package/src/index.ts +1 -0
  444. package/src/message-compare.stories.tsx +40 -0
  445. package/src/message-compare.tsx +2 -1
  446. package/src/message-form.tsx +1 -1
  447. package/src/message-table.stories.tsx +24 -0
  448. package/src/message-table.tsx +1 -1
  449. package/src/package-info.tsx +12 -9
  450. package/src/reasoning.stories.tsx +27 -0
  451. package/src/reasoning.tsx +1 -1
  452. package/src/session-header.stories.tsx +35 -1
  453. package/src/session-header.tsx +1 -1
  454. package/src/sources.stories.tsx +30 -0
  455. package/src/sources.tsx +4 -1
  456. package/src/templates-ai-assistant.stories.tsx +151 -87
  457. package/src/tool.stories.tsx +39 -0
  458. package/src/tool.tsx +3 -3
  459. package/src/toolbar.tsx +0 -10
  460. package/dist/_flow-boundary-SHNWLQG5.js.map +0 -1
  461. package/dist/_persona-rive-JEG44YHX.js.map +0 -1
  462. /package/dist/{_audio-player-media-chrome-T3XVXWRZ.js.map → _audio-player-media-chrome.js.map} +0 -0
@@ -1,25 +1,31 @@
1
1
  "use client";
2
2
 
3
3
  import type { ReactNode } from "react";
4
+ import { useLayoutEffect, useRef } from "react";
4
5
  import { cn } from "@elabs-ai/components-ui/lib/cn";
5
6
 
6
7
  export interface ChatShellProps {
7
8
  /** Scrollable transcript — typically a <Conversation>. */
8
9
  children: ReactNode;
9
- /** Sticky composer — typically a <Composer> (the standard chat input). */
10
+ /**
11
+ * Floating composer — typically a <Composer> (the standard chat input). It
12
+ * floats, padded, over the bottom of the transcript, centred at
13
+ * `--chat-composer-column` (default `--container-3xl`) under a transcript
14
+ * column of `--chat-column` (default `--container-4xl`, read by
15
+ * `ConversationContent`); turns scroll up behind it and fade out.
16
+ */
10
17
  composer?: ReactNode;
11
18
  /** Optional header row (title, model picker, actions). */
12
19
  header?: ReactNode;
13
20
  /** Optional right-side context rail (sources, files, memory). */
14
21
  aside?: ReactNode;
15
22
  /**
16
- * Layout treatment:
17
- * - `"card"` (default) — a self-contained rounded, bordered chat card. Use
18
- * when the shell sits on a page among other content and needs its own frame.
19
- * - `"bare"` — an immersive pane that fills its container with NO frame: the
20
- * transcript fades under the header and above the composer (a token-driven
21
- * top/bottom scrim) and the composer floats on the pane background instead
22
- * of a hard bordered bar. Use when the shell already lives inside a bounded
23
+ * Frame treatment (the transcript/composer layout is the same in both):
24
+ * - `"card"` (default) — a self-contained rounded, bordered chat card with a
25
+ * divider under the header. Use when the shell sits on a page among other
26
+ * content and needs its own frame.
27
+ * - `"bare"` an immersive pane that fills its container with NO frame and
28
+ * no header divider. Use when the shell already lives inside a bounded
23
29
  * region (an AppShell `SidebarInset`, a full-page workspace) so it doesn't
24
30
  * draw a redundant second frame.
25
31
  */
@@ -28,14 +34,15 @@ export interface ChatShellProps {
28
34
  }
29
35
 
30
36
  /**
31
- * Layout shell for an AI chat built from the AI Elements: a header, a scrollable
32
- * transcript (give it a <Conversation>, which is `flex-1`), a sticky composer
33
- * (a <Composer>, the standard chat input), and an optional context rail. Fills
37
+ * Layout shell for an AI chat built from the AI Elements: a header, a
38
+ * full-height scrollable transcript (give it a <Conversation>, which is
39
+ * `flex-1`), a composer floating over the transcript's bottom edge (a
40
+ * <Composer>, the standard chat input), and an optional context rail. Fills
34
41
  * its parent's height.
35
42
  *
36
- * Two layout modes (`variant`): a framed `"card"` for in-page use, and a
37
- * frameless `"bare"` immersive pane for when the shell already sits inside a
38
- * bounded region and a second frame would just read as boxy.
43
+ * Two frames (`variant`): a framed `"card"` for in-page use, and a frameless
44
+ * `"bare"` immersive pane for when the shell already sits inside a bounded
45
+ * region and a second frame would just read as boxy.
39
46
  */
40
47
  export function ChatShell({
41
48
  children,
@@ -46,50 +53,89 @@ export function ChatShell({
46
53
  className,
47
54
  }: ChatShellProps) {
48
55
  const bare = variant === "bare";
56
+ const rootRef = useRef<HTMLDivElement>(null);
57
+ const composerRef = useRef<HTMLDivElement>(null);
58
+ const hasComposer = composer != null;
59
+
60
+ // The composer floats OVER the transcript, so the transcript has to
61
+ // know how tall it is — `ConversationContent` pads its bottom by
62
+ // `--chat-composer-inset` (so the last turn scrolls clear of the composer)
63
+ // and `ConversationScrollButton` sits just above it. Written straight onto
64
+ // the root's style: no React state, no re-render per resize.
65
+ useLayoutEffect(() => {
66
+ const root = rootRef.current;
67
+ const node = composerRef.current;
68
+ if (!root) return;
69
+ if (!node) {
70
+ root.style.removeProperty("--chat-composer-inset");
71
+ return;
72
+ }
73
+ const sync = () =>
74
+ root.style.setProperty("--chat-composer-inset", `${node.getBoundingClientRect().height}px`);
75
+ sync();
76
+ if (typeof ResizeObserver === "undefined") return;
77
+ const observer = new ResizeObserver(sync);
78
+ observer.observe(node);
79
+ return () => observer.disconnect();
80
+ }, [hasComposer]);
81
+
49
82
  return (
50
83
  <div
84
+ ref={rootRef}
85
+ data-slot="chat-shell"
86
+ data-variant={variant}
51
87
  className={cn(
52
- "flex h-full min-h-0 w-full overflow-hidden bg-background",
88
+ // Reading columns: the transcript (`ConversationContent`) reads a
89
+ // little wider than the composer beneath it. Override per app, e.g.
90
+ // `className="[--chat-column:var(--container-5xl)]"`.
91
+ "flex h-full min-h-0 w-full overflow-hidden bg-background [--chat-column:var(--container-4xl)] [--chat-composer-column:var(--container-3xl)]",
53
92
  bare ? null : "rounded-xl border",
54
93
  className,
55
94
  )}
56
95
  >
57
96
  <div className="flex min-h-0 min-w-0 flex-1 flex-col">
58
97
  {header ? (
59
- <div className={cn("flex h-12 shrink-0 items-center px-4", bare ? null : "border-b")}>
98
+ <div className={cn("flex h-header shrink-0 items-center px-4", bare ? null : "border-b")}>
60
99
  {header}
61
100
  </div>
62
101
  ) : null}
63
102
 
64
- {bare ? (
65
- // Immersive transcript: the scroll area fades at both edges so content
66
- // dissolves under the header and above the composer instead of hitting
67
- // a hard bordered bar. The overlays are token-driven (`from-background`)
68
- // so they fade to the shell's own ground in every theme, and inert
69
- // (`pointer-events-none` + `aria-hidden`) so they never block scroll,
70
- // steal a tap, or get announced.
71
- <div className="relative flex min-h-0 flex-1 flex-col">
72
- {children}
73
- <div
74
- aria-hidden
75
- className="pointer-events-none absolute inset-x-0 top-0 z-10 h-8 bg-gradient-to-b from-background to-transparent"
76
- />
103
+ {/* The transcript runs the FULL height of the pane and the composer
104
+ floats over its bottom edge, so turns scroll up behind the composer
105
+ and dissolve under the top edge instead of stopping at a hard bar.
106
+ The scrims are token-driven (`from-background`) so they fade to the
107
+ shell's own ground in every theme, and inert (`pointer-events-none`
108
+ + `aria-hidden`) so they never block scroll, steal a tap, or get
109
+ announced. */}
110
+ <div className="relative flex min-h-0 flex-1 flex-col">
111
+ {children}
112
+ <div
113
+ aria-hidden
114
+ className="pointer-events-none absolute inset-x-0 top-0 z-10 h-8 bg-gradient-to-b from-background to-transparent"
115
+ />
116
+ <div
117
+ aria-hidden
118
+ data-slot="chat-shell-scrim"
119
+ // Solid under the composer, fading out a little above it.
120
+ className="pointer-events-none absolute inset-x-0 bottom-0 z-10 bg-gradient-to-t from-background from-50% to-transparent"
121
+ style={{ height: "calc(var(--chat-composer-inset, 0px) + 3rem)" }}
122
+ />
123
+ {composer ? (
124
+ // The float layer passes pointer events through, so the
125
+ // transcript's scrollbar and the gutters beside the composer stay
126
+ // live; only the composer itself takes input. DOM order still puts
127
+ // it after the transcript (tab order follows reading order).
77
128
  <div
78
- aria-hidden
79
- className="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-12 bg-gradient-to-t from-background to-transparent"
80
- />
81
- </div>
82
- ) : (
83
- <div className="flex min-h-0 flex-1 flex-col">{children}</div>
84
- )}
85
-
86
- {composer ? (
87
- // Bare: the composer floats on the pane ground (transparent, no
88
- // divider) above the fade; card: the classic bordered footer.
89
- <div className={cn("shrink-0", bare ? "relative z-20 px-4 pb-4" : "border-t p-3")}>
90
- {composer}
91
- </div>
92
- ) : null}
129
+ ref={composerRef}
130
+ data-slot="chat-shell-composer"
131
+ className="pointer-events-none absolute inset-x-0 bottom-0 z-20 px-4 pb-4"
132
+ >
133
+ <div className="pointer-events-auto mx-auto w-full max-w-(--chat-composer-column)">
134
+ {composer}
135
+ </div>
136
+ </div>
137
+ ) : null}
138
+ </div>
93
139
  </div>
94
140
  {aside ? <div className="hidden w-80 shrink-0 border-s lg:block">{aside}</div> : null}
95
141
  </div>
@@ -1,5 +1,6 @@
1
1
  import { useState } from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
+ import { ChatShell } from "./chat-shell";
3
4
  import { Conversation, ConversationContent, ConversationScrollButton } from "./conversation";
4
5
  import { Message, MessageContent, MessageResponse } from "./message";
5
6
  import { Composer } from "./composer";
@@ -45,45 +46,49 @@ function ChatExample() {
45
46
  };
46
47
 
47
48
  return (
48
- <div className="mx-auto flex h-[640px] max-w-2xl flex-col overflow-hidden rounded-xl border">
49
- <Conversation className="flex-1">
50
- <ConversationContent>
51
- {messages.map((m) => (
52
- <Message from={m.role} key={m.id}>
53
- <MessageContent>
54
- {m.reasoning ? (
55
- <Reasoning>
56
- <ReasoningTrigger />
57
- <ReasoningContent>{m.reasoning}</ReasoningContent>
58
- </Reasoning>
59
- ) : null}
60
- <MessageResponse>{m.text}</MessageResponse>
61
- {m.role === "assistant" ? (
62
- <Sources>
63
- <SourcesTrigger count={2} />
64
- <SourcesContent>
65
- <Source href="https://example.com" title="Deploy log — wk 23" />
66
- <Source href="https://example.com" title="Billing runbook" />
67
- </SourcesContent>
68
- </Sources>
69
- ) : null}
70
- </MessageContent>
71
- </Message>
72
- ))}
73
- </ConversationContent>
74
- <ConversationScrollButton />
75
- </Conversation>
76
-
77
- <Composer
78
- className="m-3 mt-0"
79
- placeholder="Ask about deploys…"
80
- status={status === "submitted" ? "Generating…" : "Awaiting your input"}
81
- sendStatus={status}
82
- onSubmit={(message) => {
83
- const text = message.text?.trim();
84
- if (text) send(text);
85
- }}
86
- />
49
+ <div className="mx-auto h-[640px] max-w-4xl overflow-hidden rounded-xl border bg-background">
50
+ <ChatShell
51
+ variant="bare"
52
+ composer={
53
+ <Composer
54
+ placeholder="Ask about deploys…"
55
+ status={status === "submitted" ? "Generating…" : "Awaiting your input"}
56
+ sendStatus={status}
57
+ onSubmit={(message) => {
58
+ const text = message.text?.trim();
59
+ if (text) send(text);
60
+ }}
61
+ />
62
+ }
63
+ >
64
+ <Conversation className="flex-1">
65
+ <ConversationContent>
66
+ {messages.map((m) => (
67
+ <Message from={m.role} key={m.id}>
68
+ <MessageContent>
69
+ {m.reasoning ? (
70
+ <Reasoning>
71
+ <ReasoningTrigger />
72
+ <ReasoningContent>{m.reasoning}</ReasoningContent>
73
+ </Reasoning>
74
+ ) : null}
75
+ <MessageResponse>{m.text}</MessageResponse>
76
+ {m.role === "assistant" ? (
77
+ <Sources>
78
+ <SourcesTrigger count={2} />
79
+ <SourcesContent>
80
+ <Source href="https://example.com" title="Deploy log — wk 23" />
81
+ <Source href="https://example.com" title="Billing runbook" />
82
+ </SourcesContent>
83
+ </Sources>
84
+ ) : null}
85
+ </MessageContent>
86
+ </Message>
87
+ ))}
88
+ </ConversationContent>
89
+ <ConversationScrollButton />
90
+ </Conversation>
91
+ </ChatShell>
87
92
  </div>
88
93
  );
89
94
  }
@@ -100,9 +105,9 @@ const meta = {
100
105
  "composed from the @elabs-ai/components-ai grammar: a scrolling " +
101
106
  "[Conversation](?path=/story/ai-conversation--default) of " +
102
107
  "[Messages](?path=/story/ai-message--presets) with reasoning and sources, plus a " +
103
- "[Composer](?path=/story/ai-composer--default). Reach for " +
104
- "[ChatShell](?path=/story/ai-chatshell--default) when you want that header + " +
105
- "transcript + composer layout as one component instead of assembling it yourself.",
108
+ "[Composer](?path=/story/ai-composer--default), laid out by " +
109
+ "[ChatShell](?path=/story/ai-chatshell--default): the transcript scrolls behind the " +
110
+ "floating composer and both sit in centred reading columns.",
106
111
  },
107
112
  },
108
113
  },
@@ -24,9 +24,8 @@
24
24
  * the three controls the `mode`, `effort` and `slashCommands` slots render.
25
25
  * The fourth slot, `modelPicker`, takes a `ModelPicker` from
26
26
  * `@elabs-ai/components-ui`, which is why it has no page of its own here.
27
- * - **`WithMentionInput`** — the one documented bespoke shell: a mention
28
- * roster has to WRAP the textarea, which is the seam `Composer` owns, so
29
- * that page drops to `PromptInput` on purpose.
27
+ * - **`WithMentionInput`** — the `mentions` slot: an `@`-mention roster
28
+ * (`MentionInput` from `@elabs-ai/components-ui`) on the composer's field.
30
29
  *
31
30
  * `Terminal/TerminalComposer` is the console skin of this same family — same
32
31
  * footer order, different surface.
@@ -147,8 +146,8 @@ const meta = {
147
146
  "[PromptInputMode](?path=/docs/ai-composer-promptinputmode--docs), " +
148
147
  "[PromptInputEffort](?path=/docs/ai-composer-promptinputeffort--docs) and " +
149
148
  "[PromptInputSlash](?path=/docs/ai-composer-promptinputslash--docs) are the three controls the `mode`, `effort` and `slashCommands` slots render (the fourth slot, `modelPicker`, takes a `ModelPicker` from @elabs-ai/components-ui, which is why it has no page of its own); and " +
150
- "[WithMentionInput](?path=/docs/ai-composer-withmentioninput--docs) is the one documented bespoke shell a mention roster has to WRAP the textarea, the seam Composer owns, so that page drops to PromptInput on purpose. " +
151
- "The scaffold that puts this composer under a transcript is [Patterns/Blocks/AI Chat Shell](?path=/docs/patterns-blocks-ai-chat-shell--docs); the console skin of the same family is [Terminal/TerminalComposer](?path=/docs/terminal-terminalcomposer--docs).",
149
+ "[WithMentionInput](?path=/docs/ai-composer-withmentioninput--docs) shows the `mentions` slotan @-mention roster on the composer's own field. " +
150
+ "The scaffold that puts this composer under a transcript is [Patterns/Blocks/AI and Terminal/AI Chat Shell](?path=/docs/patterns-blocks-ai-and-terminal-ai-chat-shell--docs); the console skin of the same family is [Terminal/TerminalComposer](?path=/docs/terminal-terminalcomposer--docs).",
152
151
  },
153
152
  },
154
153
  },
@@ -531,3 +531,48 @@ describe("Composer — shortcut hints change with a busy state (#107)", () => {
531
531
  expect(shortcutRow).not.toContainElement(stopButton);
532
532
  });
533
533
  });
534
+
535
+ describe("Composer — mentions slot", () => {
536
+ const ROSTER = [
537
+ { id: "u-ada", label: "Ada Lovelace" },
538
+ { id: "u-grace", label: "Grace Hopper" },
539
+ ];
540
+
541
+ it("opens the roster on @, inserts on Enter without submitting, then submits and resets", async () => {
542
+ const user = userEvent.setup();
543
+ const onSubmit = vi.fn();
544
+ const onValueChange = vi.fn();
545
+ render(
546
+ <Composer
547
+ onSubmit={onSubmit}
548
+ showAttach={false}
549
+ showVoice={false}
550
+ mentions={{ options: ROSTER, onValueChange }}
551
+ />,
552
+ );
553
+ const field = screen.getByRole("textbox") as HTMLTextAreaElement;
554
+ // The composer's own field — what PromptInput's submit reads.
555
+ expect(field).toHaveAttribute("name", "message");
556
+
557
+ await user.click(field);
558
+ await user.keyboard("hi @ada");
559
+ expect(field).toHaveAttribute("data-state", "open");
560
+ await user.keyboard("{Enter}");
561
+ expect(field.value).toBe("hi @Ada Lovelace ");
562
+ expect(onSubmit).not.toHaveBeenCalled();
563
+ expect(onValueChange).toHaveBeenLastCalledWith(
564
+ expect.objectContaining({ mentions: [expect.objectContaining({ id: "u-ada" })] }),
565
+ );
566
+
567
+ await user.keyboard("{Enter}");
568
+ expect(onSubmit).toHaveBeenCalledTimes(1);
569
+ expect(onSubmit.mock.calls[0]![0].text.trim()).toBe("hi @Ada Lovelace");
570
+ expect(field.value).toBe("");
571
+ expect(onValueChange).toHaveBeenLastCalledWith({ text: "", mentions: [] });
572
+ });
573
+
574
+ it("refuses slashCommands and mentions together at the type level", () => {
575
+ // @ts-expect-error — the field takes a slash palette OR a mention roster.
576
+ render(<Composer slashCommands={[]} mentions={{ options: ROSTER }} />);
577
+ });
578
+ });
package/src/composer.tsx CHANGED
@@ -2,7 +2,20 @@
2
2
 
3
3
  import { useCallback, useRef, useState, type ComponentProps, type ReactNode } from "react";
4
4
  import { ArrowUp, Mic, Paperclip, Sparkles } from "lucide-react";
5
- import { cn, Kbd, useLocale } from "@elabs-ai/components-ui";
5
+ import { useControllableState } from "@radix-ui/react-use-controllable-state";
6
+ import {
7
+ cn,
8
+ Kbd,
9
+ MentionInput,
10
+ MentionInputContent,
11
+ MentionInputEmpty,
12
+ MentionInputItem,
13
+ MentionInputList,
14
+ MentionInputTextarea,
15
+ useLocale,
16
+ type MentionOption,
17
+ type MentionValue,
18
+ } from "@elabs-ai/components-ui";
6
19
 
7
20
  import {
8
21
  PromptInput,
@@ -62,7 +75,29 @@ export interface ComposerShortcut {
62
75
  label: string;
63
76
  }
64
77
 
65
- export interface ComposerProps {
78
+ /**
79
+ * An `@`-mention roster for the composer field (`MentionInput` from
80
+ * `@elabs-ai/components-ui`). The field keeps its own submit contract; the
81
+ * mentions ride alongside the text in a `MentionValue`.
82
+ *
83
+ * Read the mentions on submit from `value` (controlled) or from
84
+ * `onValueChange`, and resolve them with `serializeMentions(value)`
85
+ * (`{ text, mentionedIds }`). `Composer` resets the value to empty after a
86
+ * submit it accepts — through `onValueChange`, so a controlled caller's state
87
+ * clears too.
88
+ */
89
+ export interface ComposerMentions {
90
+ /** Who (or what) can be mentioned. */
91
+ options: MentionOption[];
92
+ /** Controlled value. Omit for an uncontrolled roster. */
93
+ value?: MentionValue;
94
+ /** Fires on every edit, pick and post-submit reset. */
95
+ onValueChange?: (value: MentionValue) => void;
96
+ }
97
+
98
+ const EMPTY_MENTION_VALUE: MentionValue = { text: "", mentions: [] };
99
+
100
+ export interface ComposerBaseProps {
66
101
  /** Submit handler — receives the assembled message (text + attachments). */
67
102
  onSubmit?: PromptInputProps["onSubmit"];
68
103
  /** Textarea placeholder. Default `"Ask me anything…"`. */
@@ -96,20 +131,6 @@ export interface ComposerProps {
96
131
  * and is entirely yours; `aria-label` names the scale.
97
132
  */
98
133
  effort?: ComposerEffortProps;
99
- /**
100
- * Slash-command palette. When set (and non-empty) the textarea becomes a
101
- * `PromptInputSlashTextarea` wrapped in a `PromptInputSlash`, so typing `/`
102
- * at the start of a line opens a filtered command list — nothing else about
103
- * the composer changes.
104
- *
105
- * Note this makes the textarea REACT-CONTROLLED (the palette needs to read
106
- * and splice the text), which is why `Composer` clears its own copy of the
107
- * text on submit: `PromptInput`'s `form.reset()` reaches the DOM node, not
108
- * React state.
109
- */
110
- slashCommands?: PromptInputSlashProps["commands"];
111
- /** Fires with the chosen slash command (alongside the text splice). */
112
- onSlashCommand?: PromptInputSlashProps["onSelect"];
113
134
  /**
114
135
  * Override the DEFAULT tool buttons (today: the attach button) — e.g. to add
115
136
  * a web-search or connect-data control. Render `PromptInputButton`s /
@@ -185,6 +206,38 @@ export interface ComposerProps {
185
206
  className?: string;
186
207
  }
187
208
 
209
+ /**
210
+ * The field enhancements that each take over the textarea — a slash palette OR
211
+ * a mention roster, never both. The union makes the impossible combination a
212
+ * type error instead of a silent precedence rule.
213
+ */
214
+ export type ComposerFieldProps =
215
+ | {
216
+ /**
217
+ * Slash-command palette. When set (and non-empty) the textarea becomes a
218
+ * `PromptInputSlashTextarea` wrapped in a `PromptInputSlash`, so typing `/`
219
+ * at the start of a line opens a filtered command list — nothing else about
220
+ * the composer changes.
221
+ *
222
+ * Note this makes the textarea REACT-CONTROLLED (the palette needs to read
223
+ * and splice the text), which is why `Composer` clears its own copy of the
224
+ * text on submit: `PromptInput`'s `form.reset()` reaches the DOM node, not
225
+ * React state.
226
+ */
227
+ slashCommands?: PromptInputSlashProps["commands"];
228
+ /** Fires with the chosen slash command (alongside the text splice). */
229
+ onSlashCommand?: PromptInputSlashProps["onSelect"];
230
+ mentions?: never;
231
+ }
232
+ | {
233
+ /** `@`-mention roster on the field — see `ComposerMentions`. */
234
+ mentions?: ComposerMentions;
235
+ slashCommands?: never;
236
+ onSlashCommand?: never;
237
+ };
238
+
239
+ export type ComposerProps = ComposerBaseProps & ComposerFieldProps;
240
+
188
241
  /**
189
242
  * Composer — the standard brand-ui AI chat input.
190
243
  *
@@ -202,9 +255,10 @@ export interface ComposerProps {
202
255
  *
203
256
  * **Composer is the chat input. Every control the `PromptInput` family ships
204
257
  * is reachable from a `Composer` prop; drop to `PromptInput` only for a
205
- * bespoke shell.** `modelPicker`, `mode`, `effort` and `slashCommands` are the
206
- * four slots that make that true — they render `ModelPicker`,
207
- * `PromptInputMode`, `PromptInputEffort` and `PromptInputSlash` respectively,
258
+ * bespoke shell.** `modelPicker`, `mode`, `effort`, `slashCommands` and
259
+ * `mentions` are the slots that make that true — they render `ModelPicker`,
260
+ * `PromptInputMode`, `PromptInputEffort`, `PromptInputSlash` and
261
+ * `MentionInput` respectively,
208
262
  * in the footer order `attach · modelPicker · mode · effort │ voice · send`
209
263
  * (the same left-to-right arrangement `TerminalComposer` uses, so the chat and
210
264
  * console skins agree).
@@ -218,6 +272,7 @@ export function Composer({
218
272
  effort,
219
273
  slashCommands,
220
274
  onSlashCommand,
275
+ mentions,
221
276
  tools,
222
277
  sendStatus,
223
278
  onStop,
@@ -239,6 +294,12 @@ export function Composer({
239
294
  const textareaRef = useRef<HTMLTextAreaElement>(null);
240
295
  const [text, setText] = useState("");
241
296
  const hasSlash = slashCommands !== undefined && slashCommands.length > 0;
297
+ // Always called (hook order); only read when `mentions` is set.
298
+ const [mentionValue, setMentionValue] = useControllableState<MentionValue>({
299
+ prop: mentions?.value,
300
+ defaultProp: EMPTY_MENTION_VALUE,
301
+ onChange: mentions?.onValueChange,
302
+ });
242
303
 
243
304
  // #107: "Composer shortcut hints change with a busy state" — derived from
244
305
  // the existing canonical `sendStatus`, never a second boolean prop. The
@@ -258,9 +319,12 @@ export function Composer({
258
319
  // keep rendering the sent text straight back. Clearing here is a no-op in
259
320
  // the uncontrolled (non-slash) arrangement.
260
321
  setText("");
261
- return onSubmit?.(message, event);
322
+ const result = onSubmit?.(message, event);
323
+ // `form.reset()` cannot clear a value MentionInput controls either.
324
+ if (mentions) setMentionValue(EMPTY_MENTION_VALUE);
325
+ return result;
262
326
  },
263
- [onSubmit],
327
+ [onSubmit, mentions, setMentionValue],
264
328
  );
265
329
 
266
330
  const resolvedPlaceholder = placeholder ?? t("ai.composer.placeholder");
@@ -305,6 +369,25 @@ export function Composer({
305
369
  placeholder={resolvedPlaceholder}
306
370
  />
307
371
  </PromptInputSlash>
372
+ ) : mentions ? (
373
+ <MentionInput
374
+ options={mentions.options}
375
+ value={mentionValue}
376
+ onValueChange={setMentionValue}
377
+ >
378
+ {/* `asChild` lends the mention behaviour to the composer's own
379
+ textarea, so `name="message"` — what PromptInput's submit
380
+ reads — survives. */}
381
+ <MentionInputTextarea asChild>
382
+ <PromptInputTextarea placeholder={resolvedPlaceholder} />
383
+ </MentionInputTextarea>
384
+ <MentionInputContent>
385
+ <MentionInputList>
386
+ {(option) => <MentionInputItem key={option.id} option={option} />}
387
+ </MentionInputList>
388
+ <MentionInputEmpty />
389
+ </MentionInputContent>
390
+ </MentionInput>
308
391
  ) : (
309
392
  <PromptInputTextarea placeholder={resolvedPlaceholder} />
310
393
  )}
@@ -102,7 +102,7 @@ function WorkspaceDemo(providerProps: Omit<ContextPanelProviderProps, "children"
102
102
  <ContextPanelProvider {...providerProps}>
103
103
  <div className="flex h-[520px] w-full overflow-hidden rounded-xl border bg-background">
104
104
  <div className="flex min-w-0 flex-1 flex-col">
105
- <header className="flex h-12 shrink-0 items-center justify-between border-b px-4">
105
+ <header className="flex h-header shrink-0 items-center justify-between border-b px-4">
106
106
  <span className="text-subtitle">Agentic workspace</span>
107
107
  <ContextPanelTrigger />
108
108
  </header>
@@ -469,7 +469,7 @@ export const ContextPanelHeader = forwardRef<HTMLDivElement, ContextPanelHeaderP
469
469
  <div
470
470
  ref={ref}
471
471
  data-slot="context-panel-header"
472
- className={cn("flex h-12 shrink-0 items-center gap-2 border-b px-3", className)}
472
+ className={cn("flex h-header shrink-0 items-center gap-2 border-b px-3", className)}
473
473
  {...props}
474
474
  >
475
475
  {isDetail ? (
@@ -5,6 +5,7 @@ import { Message, MessageContent } from "./message";
5
5
  const meta = {
6
6
  title: "AI/Conversation",
7
7
  component: Conversation,
8
+ tags: ["autodocs"],
8
9
  parameters: {
9
10
  layout: "padded",
10
11
  docs: {
@@ -45,6 +45,22 @@ describe("Conversation — transcript region", () => {
45
45
  expect(screen.getByRole("log")).toBeInTheDocument();
46
46
  });
47
47
 
48
+ it("lets the content grow past the viewport so its composer inset stays scrollable", () => {
49
+ render(
50
+ <Conversation>
51
+ <ConversationContent>
52
+ <p>turn</p>
53
+ </ConversationContent>
54
+ </Conversation>,
55
+ );
56
+ const content = screen.getByText("turn").parentElement!;
57
+ // `min-h-0` clamps the content to the viewport height: the turns then
58
+ // overflow it and the bottom padding (the floating-composer inset) is
59
+ // left behind, so the last turn scrolls under the composer.
60
+ expect(content).not.toHaveClass("min-h-0");
61
+ expect(content).toHaveClass("shrink-0", "flex-1");
62
+ });
63
+
48
64
  it("renders its children inside the log, in order", () => {
49
65
  render(
50
66
  <Conversation>
@@ -48,7 +48,19 @@ export const ConversationContent = ({
48
48
  // can't resolve against a block-level parent whose own height is
49
49
  // `min-height`-only (the #72 empty-state-pinned-to-the-top bug).
50
50
  scrollClassName={cn("flex flex-col", scrollClassName)}
51
- className={cn("flex flex-1 min-h-0 flex-col gap-8 p-4", className)}
51
+ // Inside a `ChatShell variant="bare"` the composer floats over the
52
+ // transcript: the shell publishes `--chat-composer-inset` (its measured
53
+ // height) so the last turn scrolls clear of it, and `--chat-column` so
54
+ // turns share the composer's reading column. Outside such a shell both
55
+ // variables are unset: the inset falls back to 0 and `max-width` to none.
56
+ // `shrink-0`, never `min-h-0`: the content div must grow with its turns.
57
+ // Clamped to the viewport height, the turns overflow it and its bottom
58
+ // padding stays behind at the viewport's edge, so the last turn ends up
59
+ // under the floating composer instead of above it.
60
+ className={cn(
61
+ "mx-auto flex w-full max-w-(--chat-column) flex-1 shrink-0 flex-col gap-8 p-4 pb-[calc(var(--chat-composer-inset,0px)+--spacing(4))]",
62
+ className,
63
+ )}
52
64
  {...props}
53
65
  />
54
66
  );
@@ -113,7 +125,9 @@ export const ConversationScrollButton = ({
113
125
  // the quieter `hover:bg-muted` (in place of `outline`'s default
114
126
  // `hover:bg-accent`) now applies in every theme, not only the two
115
127
  // shipped ones.
116
- "absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full bg-background hover:bg-muted",
128
+ // `--chat-composer-inset`: lift the button clear of a floating
129
+ // `ChatShell variant="bare"` composer (0 anywhere else).
130
+ "absolute bottom-[calc(var(--chat-composer-inset,0px)+--spacing(4))] left-[50%] z-30 translate-x-[-50%] rounded-full bg-background hover:bg-muted",
117
131
  className,
118
132
  )}
119
133
  aria-label={t("ai.turnStatus.scrollToBottom")}