@kitn.ai/ui 0.17.0 → 0.18.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 (494) hide show
  1. package/README.md +28 -5
  2. package/dist/components/artifact.d.ts +39 -1
  3. package/dist/components/attachment-types.d.ts +12 -0
  4. package/dist/components/attachments.d.ts +2 -10
  5. package/dist/components/card.d.ts +27 -0
  6. package/dist/components/chain-of-thought.d.ts +78 -2
  7. package/dist/components/chat-scope-picker.d.ts +7 -0
  8. package/dist/components/chat-thread.d.ts +37 -12
  9. package/dist/components/choice-card.d.ts +26 -0
  10. package/dist/components/coachmark.d.ts +49 -0
  11. package/dist/components/composer-dom.d.ts +35 -0
  12. package/dist/components/composer-highlight.d.ts +48 -0
  13. package/dist/components/composer-history.d.ts +34 -0
  14. package/dist/components/composer.d.ts +86 -0
  15. package/dist/components/confirm-card.d.ts +17 -0
  16. package/dist/components/conversation-item.d.ts +9 -0
  17. package/dist/components/conversation-list.d.ts +36 -1
  18. package/dist/components/file-tree.d.ts +15 -0
  19. package/dist/components/form.d.ts +34 -0
  20. package/dist/components/markdown.d.ts +3 -0
  21. package/dist/components/message.d.ts +12 -0
  22. package/dist/components/model-switcher.d.ts +7 -0
  23. package/dist/components/prompt-input.d.ts +13 -4
  24. package/dist/components/prompt-suggestion.d.ts +9 -1
  25. package/dist/components/reasoning.d.ts +14 -1
  26. package/dist/components/response-compare.d.ts +14 -0
  27. package/dist/components/screen.d.ts +46 -0
  28. package/dist/components/tasks-card.d.ts +49 -1
  29. package/dist/components/toast.d.ts +12 -2
  30. package/dist/components/tool-types.d.ts +10 -0
  31. package/dist/components/tool.d.ts +8 -8
  32. package/dist/components/voice-input.d.ts +27 -0
  33. package/dist/components/voice-output.d.ts +33 -0
  34. package/dist/core-C8fzo39E.js +12 -0
  35. package/dist/custom-elements.json +8392 -1860
  36. package/dist/elements/agent-card.d.ts +1 -0
  37. package/dist/elements/agent-card.js +1 -0
  38. package/dist/elements/artifact.js +1 -1
  39. package/dist/elements/attachments.js +1 -1
  40. package/dist/elements/autoloader.js +1 -1
  41. package/dist/elements/avatar.d.ts +1 -0
  42. package/dist/elements/avatar.js +1 -0
  43. package/dist/elements/badge.d.ts +1 -0
  44. package/dist/elements/badge.js +1 -0
  45. package/dist/elements/button.d.ts +1 -0
  46. package/dist/elements/button.js +1 -0
  47. package/dist/elements/card.js +1 -1
  48. package/dist/elements/cards.js +1 -1
  49. package/dist/elements/chain-of-thought.js +1 -1
  50. package/dist/elements/chat-scope-picker.js +1 -1
  51. package/dist/elements/chat-types.d.ts +47 -0
  52. package/dist/elements/chat-workspace.js +1 -1
  53. package/dist/elements/chat.js +1 -1
  54. package/dist/elements/checkpoint.js +1 -1
  55. package/dist/elements/choice.js +1 -1
  56. package/dist/elements/chunks/Icon-DLLBeLG5.js +1 -0
  57. package/dist/elements/chunks/action-icons-UB4V5FNq.js +1 -0
  58. package/dist/elements/chunks/arrow-left-C-gwlUmx.js +1 -0
  59. package/dist/elements/chunks/attachments-CgbErf1r.js +1 -0
  60. package/dist/elements/chunks/badge-DTQpgB6N.js +1 -0
  61. package/dist/elements/chunks/button-XDUM6lXs.js +1 -0
  62. package/dist/elements/chunks/card-CBlFZ-7I.js +1 -0
  63. package/dist/elements/chunks/chat-thread-BIhmqwNL.js +1 -0
  64. package/dist/elements/chunks/check-hGUoVIKb.js +1 -0
  65. package/dist/elements/chunks/chevron-down-DDoaSUtO.js +1 -0
  66. package/dist/elements/chunks/chevron-right-CdGEUakB.js +1 -0
  67. package/dist/elements/chunks/circle-BKAZdmzb.js +1 -0
  68. package/dist/elements/chunks/circle-check-CmYG-y3I.js +1 -0
  69. package/dist/elements/chunks/circle-x-DewgAEym.js +1 -0
  70. package/dist/elements/chunks/cn-CU7UAWzk.js +1 -0
  71. package/dist/elements/chunks/code-block-DedwxwFI.js +1 -0
  72. package/dist/elements/chunks/collapsible-FfgmTHtM.js +1 -0
  73. package/dist/elements/chunks/composer-Cfbw5HKT.js +65 -0
  74. package/dist/elements/chunks/context-DwJmxlRE.js +1 -0
  75. package/dist/elements/chunks/conversation-list-DsCQMfPq.js +1 -0
  76. package/dist/elements/chunks/default-input-BuqsoENC.js +1 -0
  77. package/dist/elements/chunks/define-rGsaWsLL.js +1 -0
  78. package/dist/elements/chunks/disclosure-CwoAkpMH.js +1 -0
  79. package/dist/elements/chunks/download-DJ06G3Bz.js +1 -0
  80. package/dist/elements/chunks/dropdown-BFg8ri_R.js +1 -0
  81. package/dist/elements/chunks/ellipsis-dlN6YvGw.js +1 -0
  82. package/dist/elements/chunks/external-link-DBX2lc_R.js +1 -0
  83. package/dist/elements/chunks/file-text-HfwUx4F9.js +1 -0
  84. package/dist/elements/chunks/file-tree-C6Toj71u.js +1 -0
  85. package/dist/elements/chunks/folder-MYuqa9Xp.js +1 -0
  86. package/dist/elements/chunks/hover-card-gE7iyXdW.js +1 -0
  87. package/dist/elements/chunks/icon-Vqz4ivrY.js +1 -0
  88. package/dist/elements/chunks/{index-CTBEJeo6.js → index-BCRhi84a.js} +1 -1
  89. package/dist/elements/chunks/info-B_toFS0l.js +1 -0
  90. package/dist/elements/chunks/input-gzbHYZvQ.js +1 -0
  91. package/dist/elements/chunks/kbd-n49c4KZm.js +1 -0
  92. package/dist/elements/chunks/link-CKPs9yM0.js +1 -0
  93. package/dist/elements/chunks/loader-q6MamGus.js +1 -0
  94. package/dist/elements/chunks/{markdown-CMTGx9r8.js → markdown-DBrcqq4S.js} +9 -9
  95. package/dist/elements/chunks/message-CTqaYI0s.js +1 -0
  96. package/dist/elements/chunks/message-feedback-Cvzm03w0.js +1 -0
  97. package/dist/elements/chunks/message-square-CUg9eRCi.js +1 -0
  98. package/dist/elements/chunks/minimize-2-l2klxP6K.js +1 -0
  99. package/dist/elements/chunks/model-switcher-CmyjiHO1.js +1 -0
  100. package/dist/elements/chunks/overlay-C8sOYqjT.js +1 -0
  101. package/dist/elements/chunks/paperclip-42Ia7xHD.js +1 -0
  102. package/dist/elements/chunks/progress-bar-tzKMWAwO.js +1 -0
  103. package/dist/elements/chunks/prompt-suggestion-D3F6vP_A.js +1 -0
  104. package/dist/elements/chunks/reasoning-BJNmmyov.js +1 -0
  105. package/dist/elements/chunks/resizable-BRsk19qI.js +1 -0
  106. package/dist/elements/chunks/rotate-cw-BgcggLHC.js +1 -0
  107. package/dist/elements/chunks/scroll-area-CgGbFFbT.js +1 -0
  108. package/dist/elements/chunks/separator-8hM4GdHE.js +1 -0
  109. package/dist/elements/chunks/settings-CaUIYOg-.js +1 -0
  110. package/dist/elements/chunks/settings-group-CYjOVIZQ.js +1 -0
  111. package/dist/elements/chunks/share-BTYM3Ddt.js +1 -0
  112. package/dist/elements/chunks/skeleton-DsTYrPUe.js +1 -0
  113. package/dist/elements/chunks/slots-DkZ-Cmha.js +1 -0
  114. package/dist/elements/chunks/star-9R3LBQ9n.js +1 -0
  115. package/dist/elements/chunks/store-CYl5Y0_u.js +1 -0
  116. package/dist/elements/chunks/text-shimmer-CaYGv8Se.js +1 -0
  117. package/dist/elements/chunks/textarea-NEr2SLcD.js +1 -0
  118. package/dist/elements/chunks/thumbs-up-DZYZkFfc.js +1 -0
  119. package/dist/elements/chunks/toast-store-ClxVZRcc.js +1 -0
  120. package/dist/elements/chunks/tool-CHZOmoe9.js +1 -0
  121. package/dist/elements/chunks/tooltip-WpHElELC.js +1 -0
  122. package/dist/elements/chunks/triangle-alert-BlS80PUg.js +1 -0
  123. package/dist/elements/chunks/use-card-resolution-VSVoxDeJ.js +1 -0
  124. package/dist/elements/chunks/video-DxTPZqT8.js +1 -0
  125. package/dist/elements/chunks/x-C1qtndxn.js +1 -0
  126. package/dist/elements/coachmark.d.ts +1 -0
  127. package/dist/elements/coachmark.js +1 -0
  128. package/dist/elements/code-block.js +1 -1
  129. package/dist/elements/command.d.ts +1 -0
  130. package/dist/elements/command.js +1 -0
  131. package/dist/elements/compare.js +1 -1
  132. package/dist/elements/composer.d.ts +1 -0
  133. package/dist/elements/composer.js +1 -0
  134. package/dist/elements/confirm-card.js +1 -1
  135. package/dist/elements/context-meter.js +1 -1
  136. package/dist/elements/conversation-list.js +1 -1
  137. package/dist/elements/css.d.ts +1 -0
  138. package/dist/elements/default-input.d.ts +51 -0
  139. package/dist/elements/define.d.ts +53 -0
  140. package/dist/elements/dialog.d.ts +1 -0
  141. package/dist/elements/dialog.js +1 -0
  142. package/dist/elements/disclosure.d.ts +37 -0
  143. package/dist/elements/editable-label.d.ts +1 -0
  144. package/dist/elements/editable-label.js +1 -0
  145. package/dist/elements/embed.js +1 -1
  146. package/dist/elements/empty.js +1 -1
  147. package/dist/elements/feedback-bar.js +1 -1
  148. package/dist/elements/file-tree.js +1 -1
  149. package/dist/elements/file-upload.js +1 -1
  150. package/dist/elements/form.js +1 -1
  151. package/dist/elements/hover-card.d.ts +1 -0
  152. package/dist/elements/hover-card.js +1 -0
  153. package/dist/elements/icon.d.ts +1 -0
  154. package/dist/elements/icon.js +1 -0
  155. package/dist/elements/image.js +1 -1
  156. package/dist/elements/input.d.ts +1 -0
  157. package/dist/elements/input.js +1 -0
  158. package/dist/elements/kbd.d.ts +1 -0
  159. package/dist/elements/kbd.js +1 -0
  160. package/dist/elements/link-preview.js +1 -1
  161. package/dist/elements/loader.js +1 -1
  162. package/dist/elements/markdown.js +1 -1
  163. package/dist/elements/menu.d.ts +1 -0
  164. package/dist/elements/menu.js +1 -0
  165. package/dist/elements/message-skills.js +1 -1
  166. package/dist/elements/message.js +1 -1
  167. package/dist/elements/model-switcher.js +1 -1
  168. package/dist/elements/nav.d.ts +1 -0
  169. package/dist/elements/nav.js +1 -0
  170. package/dist/elements/notice.d.ts +1 -0
  171. package/dist/elements/notice.js +1 -0
  172. package/dist/elements/pane-group.d.ts +1 -0
  173. package/dist/elements/pane-group.js +1 -0
  174. package/dist/elements/pane.d.ts +1 -0
  175. package/dist/elements/pane.js +1 -0
  176. package/dist/elements/popover.js +1 -1
  177. package/dist/elements/progress-bar.d.ts +1 -0
  178. package/dist/elements/progress-bar.js +1 -0
  179. package/dist/elements/prompt-dock.d.ts +1 -0
  180. package/dist/elements/prompt-dock.js +1 -0
  181. package/dist/elements/prompt-input.js +1 -1
  182. package/dist/elements/prompt-suggestions.js +1 -1
  183. package/dist/elements/reasoning.js +1 -1
  184. package/dist/elements/register-impl.d.ts +0 -0
  185. package/dist/elements/register.d.ts +13 -0
  186. package/dist/elements/remote.d.ts +1 -0
  187. package/dist/elements/remote.js +1 -0
  188. package/dist/elements/resizable.js +1 -1
  189. package/dist/elements/response-stream.js +2 -2
  190. package/dist/elements/screen.d.ts +1 -0
  191. package/dist/elements/screen.js +1 -0
  192. package/dist/elements/scroll-area.d.ts +1 -0
  193. package/dist/elements/scroll-area.js +1 -0
  194. package/dist/elements/scroll-button.js +1 -1
  195. package/dist/elements/search.d.ts +1 -0
  196. package/dist/elements/search.js +1 -0
  197. package/dist/elements/segmented.d.ts +1 -0
  198. package/dist/elements/segmented.js +1 -0
  199. package/dist/elements/separator.d.ts +1 -0
  200. package/dist/elements/separator.js +1 -0
  201. package/dist/elements/setting-item.d.ts +1 -0
  202. package/dist/elements/setting-item.js +1 -0
  203. package/dist/elements/settings-group.d.ts +1 -0
  204. package/dist/elements/settings-group.js +1 -0
  205. package/dist/elements/skeleton.d.ts +1 -0
  206. package/dist/elements/skeleton.js +1 -0
  207. package/dist/elements/slots.d.ts +176 -0
  208. package/dist/elements/source.js +1 -1
  209. package/dist/elements/status.d.ts +1 -0
  210. package/dist/elements/status.js +1 -0
  211. package/dist/elements/switch.js +1 -1
  212. package/dist/elements/tabs.d.ts +1 -0
  213. package/dist/elements/tabs.js +1 -0
  214. package/dist/elements/tasks.js +1 -1
  215. package/dist/elements/text-shimmer.js +1 -1
  216. package/dist/elements/thinking-bar.js +1 -1
  217. package/dist/elements/toast.js +1 -1
  218. package/dist/elements/tool.js +1 -1
  219. package/dist/elements/tooltip.d.ts +1 -0
  220. package/dist/elements/tooltip.js +1 -0
  221. package/dist/elements/voice-input.js +1 -1
  222. package/dist/elements/voice-output.d.ts +1 -0
  223. package/dist/elements/voice-output.js +1 -0
  224. package/dist/elements.d.ts +733 -58
  225. package/dist/engine-javascript-C1x7zo1_.js +141 -0
  226. package/dist/index.d.ts +3 -2
  227. package/dist/index.js +8256 -5703
  228. package/dist/kai.es.js +1 -0
  229. package/dist/llms/llms-full.txt +1146 -64
  230. package/dist/llms/llms.txt +4 -4
  231. package/dist/mcp.es.js +62 -7
  232. package/dist/primitives/composer-model.d.ts +22 -0
  233. package/dist/primitives/composer-triggers.d.ts +19 -0
  234. package/dist/primitives/create-kai-chat.d.ts +38 -0
  235. package/dist/primitives/toast-store.d.ts +32 -2
  236. package/dist/primitives/use-speech-recognition.d.ts +26 -0
  237. package/dist/react/index.d.ts +1085 -97
  238. package/dist/react/runtime.d.ts +9 -2
  239. package/dist/react/use-kai-chat.d.ts +41 -0
  240. package/dist/react.js +572 -193
  241. package/dist/register-impl-CYWGkgbI.js +141 -0
  242. package/dist/schemas/tasks.schema.json +2 -2
  243. package/dist/state/index.d.ts +4 -0
  244. package/dist/state/messages.d.ts +11 -0
  245. package/dist/state/stream.d.ts +22 -0
  246. package/dist/state/suggestions.d.ts +4 -0
  247. package/dist/state.js +117 -0
  248. package/dist/theme.tokens.css +87 -0
  249. package/dist/types.d.ts +4 -0
  250. package/dist/ui/agent-card.d.ts +61 -0
  251. package/dist/ui/button.d.ts +5 -2
  252. package/dist/ui/card.d.ts +61 -0
  253. package/dist/ui/collapsible.d.ts +15 -1
  254. package/dist/ui/command.d.ts +33 -0
  255. package/dist/ui/dialog.d.ts +42 -0
  256. package/dist/ui/dropdown.d.ts +95 -1
  257. package/dist/ui/editable-label.d.ts +26 -0
  258. package/dist/ui/hover-card.d.ts +13 -1
  259. package/dist/ui/icon.d.ts +18 -0
  260. package/dist/ui/input.d.ts +33 -0
  261. package/dist/ui/kbd.d.ts +21 -0
  262. package/dist/ui/nav.d.ts +62 -0
  263. package/dist/ui/notice.d.ts +31 -0
  264. package/dist/ui/overlay.d.ts +12 -0
  265. package/dist/ui/pane-grid.d.ts +58 -0
  266. package/dist/ui/pane-group.d.ts +75 -0
  267. package/dist/ui/pane.d.ts +78 -0
  268. package/dist/ui/popover.d.ts +11 -1
  269. package/dist/ui/progress-bar.d.ts +23 -0
  270. package/dist/ui/prompt-dock.d.ts +96 -0
  271. package/dist/ui/resizable.d.ts +12 -0
  272. package/dist/ui/scroll-area.d.ts +4 -0
  273. package/dist/ui/segmented.d.ts +28 -0
  274. package/dist/ui/settings-group.d.ts +32 -0
  275. package/dist/ui/skeleton.d.ts +12 -0
  276. package/dist/ui/stat.d.ts +16 -0
  277. package/dist/ui/status.d.ts +13 -0
  278. package/dist/ui/switch.d.ts +8 -1
  279. package/dist/ui/tabs.d.ts +35 -0
  280. package/dist/ui/tooltip.d.ts +19 -1
  281. package/frameworks/react/index.tsx +1042 -157
  282. package/frameworks/react/runtime.tsx +30 -1
  283. package/frameworks/react/use-kai-chat.tsx +87 -0
  284. package/llms-full.txt +1146 -64
  285. package/llms.txt +4 -4
  286. package/package.json +23 -8
  287. package/src/agent-tooling/mcp/manifest.ts +14 -0
  288. package/src/agent-tooling/mcp/tools/debug.ts +10 -6
  289. package/src/agent-tooling/mcp/tools/reference.ts +32 -0
  290. package/src/agent-tooling/mcp/tools/scaffold.ts +30 -1
  291. package/src/components/artifact.tsx +98 -13
  292. package/src/components/attachment-types.ts +13 -0
  293. package/src/components/attachments.tsx +4 -14
  294. package/src/components/card.tsx +109 -15
  295. package/src/components/chain-of-thought.tsx +210 -1
  296. package/src/components/chat-scope-picker.tsx +13 -3
  297. package/src/components/chat-thread.tsx +225 -116
  298. package/src/components/choice-card.tsx +103 -11
  299. package/src/components/coachmark.tsx +245 -0
  300. package/src/components/component-meta.json +691 -102
  301. package/src/components/composer-dom.ts +159 -0
  302. package/src/components/composer-highlight.ts +242 -0
  303. package/src/components/composer-history.ts +82 -0
  304. package/src/components/composer.tsx +979 -0
  305. package/src/components/confirm-card.tsx +54 -1
  306. package/src/components/conversation-item.tsx +69 -7
  307. package/src/components/conversation-list.tsx +134 -37
  308. package/src/components/file-tree.tsx +197 -47
  309. package/src/components/form-widgets.tsx +9 -7
  310. package/src/components/form.tsx +140 -17
  311. package/src/components/image.tsx +3 -5
  312. package/src/components/link-preview.tsx +5 -4
  313. package/src/components/markdown.tsx +5 -2
  314. package/src/components/message.tsx +23 -4
  315. package/src/components/model-switcher.tsx +19 -4
  316. package/src/components/prompt-input.tsx +46 -75
  317. package/src/components/prompt-suggestion.tsx +48 -16
  318. package/src/components/reasoning.tsx +29 -6
  319. package/src/components/response-compare.tsx +31 -2
  320. package/src/components/screen.tsx +176 -0
  321. package/src/components/tasks-card.tsx +342 -21
  322. package/src/components/toast.tsx +119 -31
  323. package/src/components/tool-types.ts +10 -0
  324. package/src/components/tool.tsx +22 -16
  325. package/src/components/voice-input.tsx +102 -19
  326. package/src/components/voice-output.tsx +202 -0
  327. package/src/elements/agent-card.tsx +67 -0
  328. package/src/elements/artifact.tsx +53 -4
  329. package/src/elements/attachments.tsx +1 -0
  330. package/src/elements/avatar.tsx +33 -0
  331. package/src/elements/badge.tsx +32 -0
  332. package/src/elements/button.tsx +126 -0
  333. package/src/elements/card-media.jpg +0 -0
  334. package/src/elements/card.tsx +124 -31
  335. package/src/elements/cards.tsx +81 -7
  336. package/src/elements/chain-of-thought.tsx +77 -32
  337. package/src/elements/chat-scope-picker.tsx +35 -8
  338. package/src/elements/chat-types.ts +2 -2
  339. package/src/elements/chat-workspace.tsx +153 -41
  340. package/src/elements/chat.tsx +45 -23
  341. package/src/elements/choice.tsx +71 -19
  342. package/src/elements/coachmark.tsx +90 -0
  343. package/src/elements/command.tsx +209 -0
  344. package/src/elements/compare.tsx +24 -2
  345. package/src/elements/compiled.css +1 -1
  346. package/src/elements/composer.tsx +151 -0
  347. package/src/elements/confirm-card.tsx +36 -11
  348. package/src/elements/conversation-list.tsx +80 -11
  349. package/src/elements/default-input.tsx +75 -53
  350. package/src/elements/define.tsx +47 -6
  351. package/src/elements/dialog.tsx +98 -0
  352. package/src/elements/disclosure.ts +73 -0
  353. package/src/elements/editable-label.tsx +125 -0
  354. package/src/elements/element-manifest.json +129 -1
  355. package/src/elements/element-meta.json +5562 -1336
  356. package/src/elements/element-types.d.ts +732 -57
  357. package/src/elements/file-tree.tsx +12 -2
  358. package/src/elements/file-upload.tsx +1 -1
  359. package/src/elements/form.tsx +54 -11
  360. package/src/elements/hover-card.tsx +80 -0
  361. package/src/elements/icon-names.json +48 -0
  362. package/src/elements/icon.tsx +41 -0
  363. package/src/elements/input.tsx +203 -0
  364. package/src/elements/kbd.tsx +45 -0
  365. package/src/elements/menu.tsx +217 -0
  366. package/src/elements/message.tsx +58 -11
  367. package/src/elements/model-switcher.tsx +27 -1
  368. package/src/elements/nav.tsx +87 -0
  369. package/src/elements/notice.tsx +53 -0
  370. package/src/elements/pane-group.tsx +119 -0
  371. package/src/elements/pane.tsx +129 -0
  372. package/src/elements/popover.tsx +39 -17
  373. package/src/elements/progress-bar.tsx +39 -0
  374. package/src/elements/prompt-dock.tsx +81 -0
  375. package/src/elements/prompt-input.tsx +112 -63
  376. package/src/elements/prompt-suggestions.tsx +25 -9
  377. package/src/elements/reasoning.tsx +30 -9
  378. package/src/elements/register-impl.ts +38 -3
  379. package/src/elements/register.ts +11 -3
  380. package/src/elements/resizable.tsx +48 -11
  381. package/src/elements/screen.tsx +90 -0
  382. package/src/elements/scroll-area.tsx +33 -0
  383. package/src/elements/search.tsx +180 -0
  384. package/src/elements/segmented.tsx +135 -0
  385. package/src/elements/separator.tsx +36 -0
  386. package/src/elements/setting-item.tsx +50 -0
  387. package/src/elements/settings-group.tsx +37 -0
  388. package/src/elements/skeleton.tsx +45 -0
  389. package/src/elements/slots.ts +607 -0
  390. package/src/elements/source.tsx +13 -6
  391. package/src/elements/status.tsx +42 -0
  392. package/src/elements/switch.tsx +89 -14
  393. package/src/elements/tabs.tsx +95 -0
  394. package/src/elements/tasks.tsx +70 -14
  395. package/src/elements/toast.tsx +17 -2
  396. package/src/elements/tool.tsx +43 -9
  397. package/src/elements/tooltip.tsx +85 -0
  398. package/src/elements/video-poster.jpg +0 -0
  399. package/src/elements/voice-input.tsx +66 -16
  400. package/src/elements/voice-output.tsx +69 -0
  401. package/src/index.ts +5 -2
  402. package/src/primitives/card-schemas/tasks.schema.json +2 -2
  403. package/src/primitives/composer-model.ts +49 -0
  404. package/src/primitives/composer-triggers.ts +35 -0
  405. package/src/primitives/create-kai-chat.ts +66 -0
  406. package/src/primitives/toast-store.ts +43 -2
  407. package/src/primitives/use-speech-recognition.ts +135 -0
  408. package/src/state/index.ts +6 -0
  409. package/src/state/messages.ts +36 -0
  410. package/src/state/stream.ts +92 -0
  411. package/src/state/suggestions.ts +9 -0
  412. package/src/types.ts +4 -0
  413. package/src/ui/agent-card.tsx +212 -0
  414. package/src/ui/button.tsx +16 -5
  415. package/src/ui/card.tsx +245 -0
  416. package/src/ui/collapsible.tsx +30 -4
  417. package/src/ui/command.tsx +99 -0
  418. package/src/ui/dialog.tsx +217 -0
  419. package/src/ui/dropdown.tsx +375 -13
  420. package/src/ui/editable-label.tsx +112 -0
  421. package/src/ui/hover-card.tsx +28 -5
  422. package/src/ui/icon.tsx +110 -0
  423. package/src/ui/input.tsx +132 -0
  424. package/src/ui/kbd.tsx +118 -0
  425. package/src/ui/nav.tsx +282 -0
  426. package/src/ui/notice.tsx +84 -0
  427. package/src/ui/overlay.tsx +108 -2
  428. package/src/ui/pane-grid.tsx +116 -0
  429. package/src/ui/pane-group.tsx +280 -0
  430. package/src/ui/pane.tsx +193 -0
  431. package/src/ui/popover.tsx +16 -2
  432. package/src/ui/progress-bar.tsx +83 -0
  433. package/src/ui/prompt-dock.tsx +145 -0
  434. package/src/ui/resizable.tsx +69 -1
  435. package/src/ui/scroll-area.tsx +14 -3
  436. package/src/ui/segmented.tsx +109 -0
  437. package/src/ui/settings-group.tsx +67 -0
  438. package/src/ui/skeleton.tsx +62 -5
  439. package/src/ui/stat.tsx +41 -0
  440. package/src/ui/status.tsx +48 -0
  441. package/src/ui/switch.tsx +49 -23
  442. package/src/ui/tabs.tsx +168 -0
  443. package/src/ui/tooltip.tsx +52 -8
  444. package/theme.css +113 -0
  445. package/dist/components/slash-command.d.ts +0 -15
  446. package/dist/elements/chunks/Icon-D_Zmwmwh.js +0 -1
  447. package/dist/elements/chunks/action-icons-PVfIvcuG.js +0 -1
  448. package/dist/elements/chunks/attachments-Dp6vqtrc.js +0 -1
  449. package/dist/elements/chunks/button-CKyYD9G4.js +0 -1
  450. package/dist/elements/chunks/card-Cjho-PYk.js +0 -1
  451. package/dist/elements/chunks/chat-thread-RT808iPf.js +0 -1
  452. package/dist/elements/chunks/check-CDYaMYrM.js +0 -1
  453. package/dist/elements/chunks/chevron-down-3SBiq8HF.js +0 -1
  454. package/dist/elements/chunks/chevron-right-k2UFlIVK.js +0 -1
  455. package/dist/elements/chunks/cn-Dogu6PbV.js +0 -1
  456. package/dist/elements/chunks/code-block-BYUmfFSd.js +0 -1
  457. package/dist/elements/chunks/collapsible-BiNwdOn6.js +0 -1
  458. package/dist/elements/chunks/context-18g6oi4J.js +0 -1
  459. package/dist/elements/chunks/conversation-list-D9Y0NVdo.js +0 -1
  460. package/dist/elements/chunks/default-input-CSAoYYMd.js +0 -1
  461. package/dist/elements/chunks/download-D8K0Je8r.js +0 -1
  462. package/dist/elements/chunks/dropdown-bUs2iTia.js +0 -1
  463. package/dist/elements/chunks/external-link-6mA0wxab.js +0 -1
  464. package/dist/elements/chunks/file-text-DnaYk3Dm.js +0 -1
  465. package/dist/elements/chunks/file-tree-CgtRLlxK.js +0 -1
  466. package/dist/elements/chunks/hover-card-CIGw62gP.js +0 -1
  467. package/dist/elements/chunks/link-BlGSeoPy.js +0 -1
  468. package/dist/elements/chunks/loader-DvhR4qa0.js +0 -1
  469. package/dist/elements/chunks/message-CDZwXjhL.js +0 -1
  470. package/dist/elements/chunks/message-feedback-CYx81QiA.js +0 -1
  471. package/dist/elements/chunks/model-switcher-O_tUhoOT.js +0 -1
  472. package/dist/elements/chunks/overlay-DydXYMQW.js +0 -1
  473. package/dist/elements/chunks/prompt-suggestion-ldlWV5kd.js +0 -1
  474. package/dist/elements/chunks/reasoning-2PUnHlmr.js +0 -1
  475. package/dist/elements/chunks/resizable-B5WNbSeb.js +0 -1
  476. package/dist/elements/chunks/star-t9tI-P0q.js +0 -1
  477. package/dist/elements/chunks/store-C6rIooZz.js +0 -1
  478. package/dist/elements/chunks/text-shimmer-uh9V_KUh.js +0 -1
  479. package/dist/elements/chunks/textarea-3nm6Ia9U.js +0 -1
  480. package/dist/elements/chunks/thumbs-up-BBtguGld.js +0 -1
  481. package/dist/elements/chunks/toast-store-D8cL3fWl.js +0 -1
  482. package/dist/elements/chunks/tool-GJ3vwp1U.js +0 -1
  483. package/dist/elements/chunks/tooltip-DwKAz4XR.js +0 -1
  484. package/dist/elements/chunks/triangle-alert-Ct1zt2JL.js +0 -1
  485. package/dist/elements/chunks/use-card-resolution-D-_0Alzy.js +0 -1
  486. package/dist/elements/chunks/video-mVYiM99-.js +0 -1
  487. package/dist/elements/chunks/x-CzxL27Ch.js +0 -1
  488. package/dist/kitn-chat.es.js +0 -1
  489. package/dist/register-JyTglR6m.js +0 -1
  490. package/dist/register-impl-C6wlSsts.js +0 -77
  491. package/src/components/slash-command.tsx +0 -232
  492. package/src/elements/framework-usage.json +0 -588
  493. /package/dist/{kitn-chat-provider.d.ts → kai-provider.d.ts} +0 -0
  494. /package/dist/{kitn-chat-provider.es.js → kai-provider.es.js} +0 -0
@@ -1,5 +1,6 @@
1
- import { createSignal, For, Show } from 'solid-js';
1
+ import { createSignal, createEffect, For, Show, onMount } from 'solid-js';
2
2
  import { ChatConfig, useChatConfig } from '../primitives/chat-config';
3
+ import { type ComposerDoc, normalizeValue, serializeToText } from '../primitives/composer-model';
3
4
  import { ChatContainer, ChatContainerContent, ChatContainerScrollAnchor } from './chat-container';
4
5
  import { Message, MessageAvatar, MessageBody } from './message';
5
6
  import { type AttachmentData } from './attachments';
@@ -11,7 +12,7 @@ import {
11
12
  ContextContentBody, ContextContentFooter, ContextInputUsage, ContextOutputUsage,
12
13
  } from './context';
13
14
  import { DefaultPromptInput } from '../elements/default-input';
14
- import type { SlashCommandItem } from './slash-command';
15
+ import type { TriggerDef } from './composer';
15
16
  import type { ChatMessage } from '../elements/chat-types';
16
17
  import type { ProseSize } from '../primitives/chat-config';
17
18
  import type { ModelOption } from '../types';
@@ -31,9 +32,10 @@ export interface ChatThreadProps {
31
32
  * content, and optional reasoning/tools/attachments/actions. Set as a JS
32
33
  * property (`el.messages = [...]`). */
33
34
  messages: ChatMessage[];
34
- /** Controlled value of the input. When set, the host owns the input text and
35
- * must update it on `kai-value-change`; leave unset for uncontrolled behavior. */
36
- value?: string;
35
+ /** Value of the input. A **string** is controlled (the host owns the text and
36
+ * updates it on `kai-value-change`). A **ComposerDoc** is a one-time seed that
37
+ * pre-populates pills; the user then edits freely. Leave unset for uncontrolled. */
38
+ value?: string | ComposerDoc;
37
39
  /** Placeholder text shown in the empty input. */
38
40
  placeholder?: string;
39
41
  /** When true, shows the loading/streaming state and disables submit (use while
@@ -75,29 +77,63 @@ export interface ChatThreadProps {
75
77
  headerStart?: boolean;
76
78
  /** Whether the host has `slot="header-end"` content (right of the controls). */
77
79
  headerEnd?: boolean;
80
+ // ── Composition slots ─────────────────────────────────────────────────────
81
+ // Each flag below is set by the `<kai-chat>` facade when matching light-DOM
82
+ // `slot="…"` content is projected, and gates one composition slot. Two kinds:
83
+ // • INJECT — additive: project YOUR markup into a region (sidebar, footer,
84
+ // composer-actions, header-start/-end).
85
+ // • REPLACE — substitutive: your markup stands in for a whole region
86
+ // (header, empty, composer). A replaced region's projected
87
+ // content owns its own data/events — a slotted (light-DOM) node
88
+ // can't read this component's reactive state. That boundary is
89
+ // the whole reason `messages` stays a data prop, not a slot.
90
+ /** REPLACE — full custom header in place of the built-in title/model/context bar. */
91
+ headerFull?: boolean;
92
+ /** INJECT — left sidebar column (e.g. a conversation list / your own nav). */
93
+ sidebar?: boolean;
94
+ /** REPLACE — custom zero-state rendered in the message area while the thread is empty (replaces the empty message list only; the composer and its suggestions still render). */
95
+ empty?: boolean;
96
+ /** REPLACE — full custom composer in place of the built-in prompt input. The
97
+ * projected content wires its own submit (the data-flow boundary). */
98
+ composer?: boolean;
99
+ /** INJECT — accessory row just above the composer (e.g. extra actions). */
100
+ composerActions?: boolean;
101
+ /** INJECT — footer row below the composer (disclaimers, token meter, …). */
102
+ footer?: boolean;
78
103
  /** Show a Search (Globe) button in the input toolbar; fires a `search` event. */
79
104
  search?: boolean;
80
105
  /** Show a Voice (Mic) button in the input toolbar; fires a `voice` event. */
81
106
  voice?: boolean;
82
- /** Slash commandswhen set, typing `/` in the input opens the command
83
- * palette and fires `kai-slash-select`. Set as a JS property. */
84
- slashCommands?: SlashCommandItem[];
85
- /** Command ids to highlight as active in the palette. */
86
- slashActiveIds?: string[];
87
- /** Single-line palette rows. */
88
- slashCompact?: boolean;
107
+ /** Rich entity triggers each `{ char, kind, items }` opens a caret-anchored
108
+ * menu that inserts an atomic pill (`/` skills, `@` agents/plugins). Set as a
109
+ * JS property; forwarded to the input. */
110
+ triggers?: TriggerDef[];
111
+ /** Default icon per entity kind (kind → image src) for pills/menu items. */
112
+ kindIcons?: Record<string, string>;
89
113
  /** Whether each message's action bar is always visible (`'always'`, default)
90
114
  * or only revealed on hover of that message row (`'hover'`). */
91
115
  actionsReveal?: 'always' | 'hover';
92
116
  // callbacks (the facade maps these to dispatch())
93
117
  onValueChange?: (value: string) => void;
94
118
  onSubmit?: (detail: { value: string; attachments: AttachmentData[] }) => void;
119
+ onAttachmentsChange?: (attachments: AttachmentData[]) => void;
95
120
  onSuggestionClick?: (value: string) => void;
96
121
  onModelChange?: (modelId: string) => void;
97
122
  onMessageAction?: (detail: MessageActionDetail) => void;
98
123
  onSearch?: () => void;
99
124
  onVoice?: () => void;
100
- onSlashSelect?: (command: SlashCommandItem) => void;
125
+ /** Receive the imperative controller once mounted. The kai-chat facade forwards
126
+ * these as element methods (focus/clear/send/scrollToBottom). */
127
+ controllerRef?: (controller: ChatThreadController) => void;
128
+ }
129
+
130
+ /** Imperative handle exposed via `controllerRef` — the input half of the chat's
131
+ * interaction surface, forwarded onto `<kai-chat>` as instance methods. */
132
+ export interface ChatThreadController {
133
+ focus(options?: FocusOptions): void;
134
+ clear(): void;
135
+ send(): void;
136
+ scrollToBottom(behavior?: ScrollBehavior): void;
101
137
  }
102
138
 
103
139
  export function ChatThread(props: ChatThreadProps) {
@@ -112,14 +148,26 @@ export function ChatThread(props: ChatThreadProps) {
112
148
  emit: (detail) => props.onMessageAction?.(detail),
113
149
  target: () => rootEl,
114
150
  });
115
- const [internal, setInternal] = createSignal(props.value ?? '');
151
+ const [internal, setInternal] = createSignal<string | ComposerDoc>(props.value ?? '');
116
152
  const [attachments, setAttachments] = createSignal<AttachmentData[]>([]);
117
- const current = () => props.value ?? internal();
153
+ // A string `value` is controlled; a ComposerDoc `value` is a one-time seed that
154
+ // lives in `internal` so the user's (string) edits replace it without a fight.
155
+ const current = (): string | ComposerDoc =>
156
+ typeof props.value === 'string' ? props.value : internal();
157
+ createEffect(() => {
158
+ const v = props.value;
159
+ if (v != null && typeof v !== 'string') setInternal(v);
160
+ });
118
161
  const handleChange = (v: string) => { setInternal(v); props.onValueChange?.(v); };
119
- const handleSubmit = () => { props.onSubmit?.({ value: current(), attachments: attachments() }); setAttachments([]); };
162
+ // After a send, reset the composer. Clear the internal draft ONLY when the value is
163
+ // uncontrolled (props.value === undefined) — a controlled host owns its own value and
164
+ // clears it itself. This lets the batteries-included hooks (useKaiChat/createKaiChat),
165
+ // whose `bind` does not control `value`, get a clean composer after each submit.
166
+ const afterSubmit = () => { setAttachments([]); if (props.value === undefined) setInternal(''); };
167
+ const handleSubmit = () => { props.onSubmit?.({ value: serializeToText(normalizeValue(current())), attachments: attachments() }); afterSubmit(); };
120
168
  const handleSuggestionClick = (v: string) => {
121
169
  if ((props.suggestionMode ?? 'submit') === 'fill') { handleChange(v); props.onSuggestionClick?.(v); }
122
- else { props.onSubmit?.({ value: v, attachments: attachments() }); setAttachments([]); }
170
+ else { props.onSubmit?.({ value: v, attachments: attachments() }); afterSubmit(); }
123
171
  };
124
172
  const showHeader = () => !!(props.chatTitle || props.models || props.context || props.headerStart || props.headerEnd);
125
173
  // Suggestions are conversation starters: show only on an empty thread unless
@@ -128,112 +176,173 @@ export function ChatThread(props: ChatThreadProps) {
128
176
  props.persistSuggestions || props.messages.length === 0 ? props.suggestions : undefined;
129
177
  const showScrollButton = () => props.scrollButton !== false;
130
178
 
179
+ // Hand the imperative controller to the facade once mounted (rootEl is set).
180
+ onMount(() => {
181
+ props.controllerRef?.({
182
+ focus: (options) =>
183
+ rootEl
184
+ ?.querySelector<HTMLElement>('[contenteditable]:not([contenteditable="false"]), textarea')
185
+ ?.focus(options),
186
+ clear: () => { setInternal(''); setAttachments([]); props.onValueChange?.(''); },
187
+ send: () => handleSubmit(),
188
+ scrollToBottom: (behavior) => {
189
+ const vp = rootEl?.querySelector<HTMLElement>('.overflow-y-auto');
190
+ vp?.scrollTo({ top: vp.scrollHeight, behavior: behavior ?? 'smooth' });
191
+ },
192
+ });
193
+ });
194
+
131
195
  return (
132
196
  <ChatConfig proseSize={props.proseSize} codeTheme={props.codeTheme} codeHighlight={props.codeHighlight !== false} portalMount={outer.portalMount()}>
133
- <div ref={(e) => (rootEl = e as HTMLElement)} class={`flex h-full flex-col bg-background ${props.class ?? ''}`}>
134
- <Show when={showHeader()}>
135
- <header class="flex h-14 shrink-0 items-center justify-between border-b border-border px-5">
136
- <div class="flex items-center gap-2">
137
- {/* Consumer-injected leading controls (sidebar-toggle, compose, a
138
- popover title-button). Projects light-DOM `slot="header-start"`
139
- children of <kai-chat>; inert outside a shadow root. */}
140
- <slot name="header-start" />
141
- <Show when={props.chatTitle}>
142
- <div class="text-sm font-semibold text-foreground">{props.chatTitle}</div>
197
+ {/* The root is a ROW so a `sidebar` slot can sit beside the main column.
198
+ With no sidebar projected it collapses to the original column. */}
199
+ <div ref={(e) => (rootEl = e as HTMLElement)} class={`flex h-full bg-background ${props.class ?? ''}`}>
200
+ <Show when={props.sidebar}>
201
+ <aside part="sidebar" class="flex w-64 shrink-0 flex-col overflow-hidden border-r border-border">
202
+ <slot name="sidebar" />
203
+ </aside>
204
+ </Show>
205
+ <div class="flex h-full min-w-0 flex-1 flex-col">
206
+ {/* Header: a full `header` slot REPLACES the built-in bar; otherwise the
207
+ built-in header renders, itself carrying the header-start/header-end
208
+ INJECT slots. */}
209
+ <Show
210
+ when={props.headerFull}
211
+ fallback={
212
+ <Show when={showHeader()}>
213
+ <header part="header-bar" class="flex h-14 shrink-0 items-center justify-between border-b border-border px-5">
214
+ <div class="flex items-center gap-2">
215
+ {/* Consumer-injected leading controls (sidebar-toggle, compose, a
216
+ popover title-button). Projects light-DOM `slot="header-start"`
217
+ children of <kai-chat>; inert outside a shadow root. */}
218
+ <slot name="header-start" />
219
+ <Show when={props.chatTitle}>
220
+ <div class="text-sm font-semibold text-foreground">{props.chatTitle}</div>
221
+ </Show>
222
+ </div>
223
+ <div class="flex items-center gap-2">
224
+ <Show when={props.models}>
225
+ <ModelSwitcher
226
+ models={props.models!}
227
+ currentModelId={props.currentModel ?? props.models![0]?.id ?? ''}
228
+ onModelChange={(modelId) => props.onModelChange?.(modelId)}
229
+ />
230
+ </Show>
231
+ <Show when={props.context}>
232
+ <Context
233
+ usedTokens={props.context!.usedTokens} maxTokens={props.context!.maxTokens}
234
+ inputTokens={props.context!.inputTokens} outputTokens={props.context!.outputTokens}
235
+ estimatedCost={props.context!.estimatedCost}
236
+ >
237
+ <ContextTrigger />
238
+ <ContextContent>
239
+ <ContextContentHeader />
240
+ <ContextContentBody><div class="space-y-1.5"><ContextInputUsage /><ContextOutputUsage /></div></ContextContentBody>
241
+ <ContextContentFooter />
242
+ </ContextContent>
243
+ </Context>
244
+ </Show>
245
+ {/* Consumer-injected trailing controls (share, settings, …).
246
+ Projects light-DOM `slot="header-end"` children of <kai-chat>. */}
247
+ <slot name="header-end" />
248
+ </div>
249
+ </header>
143
250
  </Show>
144
- </div>
145
- <div class="flex items-center gap-2">
146
- <Show when={props.models}>
147
- <ModelSwitcher
148
- models={props.models!}
149
- currentModelId={props.currentModel ?? props.models![0]?.id ?? ''}
150
- onModelChange={(modelId) => props.onModelChange?.(modelId)}
151
- />
251
+ }
252
+ >
253
+ <header part="header" class="shrink-0"><slot name="header" /></header>
254
+ </Show>
255
+ <div class="relative flex-1 overflow-hidden">
256
+ <ChatContainer class="h-full px-4 py-3">
257
+ <ChatContainerContent class="mx-auto w-full max-w-3xl space-y-4">
258
+ {/* REPLACE — custom empty-state slot, shown only while the thread is
259
+ empty. The component still owns WHEN it shows (data state); the
260
+ consumer owns WHAT it looks like. */}
261
+ <Show when={props.empty && props.messages.length === 0}>
262
+ <slot name="empty" />
263
+ </Show>
264
+ <For each={props.messages}>
265
+ {(m) => {
266
+ const body = (
267
+ <MessageBody
268
+ content={m.content}
269
+ reasoning={m.reasoning}
270
+ tools={m.tools}
271
+ attachments={m.attachments}
272
+ isUser={m.role === 'user'}
273
+ markdown={m.role === 'assistant'}
274
+ actions={m.actions}
275
+ actionsReveal={reveal()}
276
+ activeFeedback={feedback.resolveFeedback(m)}
277
+ copied={feedback.isCopied(m.id)}
278
+ onAction={(action) => feedback.handleAction(m, action)}
279
+ />
280
+ );
281
+ const rowGroup = reveal() === 'hover' ? 'group ' : '';
282
+ return (
283
+ <Show
284
+ when={m.avatar}
285
+ fallback={
286
+ <Message class={`${rowGroup}${m.role === 'user' ? 'flex-col items-end' : 'flex-col items-start'}`}>
287
+ {body}
288
+ </Message>
289
+ }
290
+ >
291
+ {(av) => (
292
+ <Message class={rowGroup}>
293
+ <MessageAvatar src={av().src ?? ''} alt={av().alt ?? ''} fallback={av().fallback} />
294
+ <div class={`flex min-w-0 flex-1 flex-col ${m.role === 'user' ? 'items-end' : 'items-start'}`}>
295
+ {body}
296
+ </div>
297
+ </Message>
298
+ )}
299
+ </Show>
300
+ );
301
+ }}
302
+ </For>
303
+ <ChatContainerScrollAnchor />
304
+ </ChatContainerContent>
305
+ <Show when={showScrollButton()}>
306
+ <div class="absolute bottom-4 left-1/2 flex w-full max-w-3xl -translate-x-1/2 justify-center px-5">
307
+ <ScrollButton class="shadow-sm" />
308
+ </div>
152
309
  </Show>
153
- <Show when={props.context}>
154
- <Context
155
- usedTokens={props.context!.usedTokens} maxTokens={props.context!.maxTokens}
156
- inputTokens={props.context!.inputTokens} outputTokens={props.context!.outputTokens}
157
- estimatedCost={props.context!.estimatedCost}
158
- >
159
- <ContextTrigger />
160
- <ContextContent>
161
- <ContextContentHeader />
162
- <ContextContentBody><div class="space-y-1.5"><ContextInputUsage /><ContextOutputUsage /></div></ContextContentBody>
163
- <ContextContentFooter />
164
- </ContextContent>
165
- </Context>
310
+ </ChatContainer>
311
+ </div>
312
+ {/* INJECT — accessory row above the composer (extra actions/toolbar). */}
313
+ <Show when={props.composerActions}>
314
+ <div class="shrink-0 px-4">
315
+ <div class="mx-auto flex max-w-3xl items-center gap-2 pb-2"><slot name="composer-actions" /></div>
316
+ </div>
317
+ </Show>
318
+ <div class="shrink-0 px-4 pb-4">
319
+ <div class="mx-auto max-w-3xl">
320
+ {/* REPLACE — a full `composer` slot stands in for the built-in input.
321
+ The slotted content owns its own submit/loading wiring. */}
322
+ <Show
323
+ when={props.composer}
324
+ fallback={
325
+ <DefaultPromptInput
326
+ value={current()} placeholder={props.placeholder} loading={props.loading === true}
327
+ suggestions={visibleSuggestions()} attachments={attachments()}
328
+ search={props.search === true} voice={props.voice === true}
329
+ triggers={props.triggers} kindIcons={props.kindIcons}
330
+ onValueChange={handleChange} onSubmit={handleSubmit} onSuggestionClick={handleSuggestionClick}
331
+ onAttachmentsChange={(a) => { setAttachments(a); props.onAttachmentsChange?.(a); }}
332
+ onSearch={() => props.onSearch?.()} onVoice={() => props.onVoice?.()}
333
+ />
334
+ }
335
+ >
336
+ <slot name="composer" />
166
337
  </Show>
167
- {/* Consumer-injected trailing controls (share, settings, …).
168
- Projects light-DOM `slot="header-end"` children of <kai-chat>. */}
169
- <slot name="header-end" />
170
338
  </div>
171
- </header>
172
- </Show>
173
- <div class="relative flex-1 overflow-hidden">
174
- <ChatContainer class="h-full px-4 py-3">
175
- <ChatContainerContent class="mx-auto w-full max-w-3xl space-y-4">
176
- <For each={props.messages}>
177
- {(m) => {
178
- const body = (
179
- <MessageBody
180
- content={m.content}
181
- reasoning={m.reasoning}
182
- tools={m.tools}
183
- attachments={m.attachments}
184
- isUser={m.role === 'user'}
185
- markdown={m.role === 'assistant'}
186
- actions={m.actions}
187
- actionsReveal={reveal()}
188
- activeFeedback={feedback.resolveFeedback(m)}
189
- copied={feedback.isCopied(m.id)}
190
- onAction={(action) => feedback.handleAction(m, action)}
191
- />
192
- );
193
- const rowGroup = reveal() === 'hover' ? 'group ' : '';
194
- return (
195
- <Show
196
- when={m.avatar}
197
- fallback={
198
- <Message class={`${rowGroup}${m.role === 'user' ? 'flex-col items-end' : 'flex-col items-start'}`}>
199
- {body}
200
- </Message>
201
- }
202
- >
203
- {(av) => (
204
- <Message class={rowGroup}>
205
- <MessageAvatar src={av().src ?? ''} alt={av().alt ?? ''} fallback={av().fallback} />
206
- <div class={`flex min-w-0 flex-1 flex-col ${m.role === 'user' ? 'items-end' : 'items-start'}`}>
207
- {body}
208
- </div>
209
- </Message>
210
- )}
211
- </Show>
212
- );
213
- }}
214
- </For>
215
- <ChatContainerScrollAnchor />
216
- </ChatContainerContent>
217
- <Show when={showScrollButton()}>
218
- <div class="absolute bottom-4 left-1/2 flex w-full max-w-3xl -translate-x-1/2 justify-center px-5">
219
- <ScrollButton class="shadow-sm" />
220
- </div>
221
- </Show>
222
- </ChatContainer>
223
- </div>
224
- <div class="shrink-0 px-4 pb-4">
225
- <div class="mx-auto max-w-3xl">
226
- <DefaultPromptInput
227
- value={current()} placeholder={props.placeholder} loading={props.loading === true}
228
- suggestions={visibleSuggestions()} attachments={attachments()}
229
- search={props.search === true} voice={props.voice === true}
230
- slashCommands={props.slashCommands} slashActiveIds={props.slashActiveIds} slashCompact={props.slashCompact === true}
231
- onValueChange={handleChange} onSubmit={handleSubmit} onSuggestionClick={handleSuggestionClick}
232
- onAttachmentsChange={setAttachments}
233
- onSearch={() => props.onSearch?.()} onVoice={() => props.onVoice?.()}
234
- onSlashSelect={(command) => props.onSlashSelect?.(command)}
235
- />
236
339
  </div>
340
+ {/* INJECT — footer row below the composer. */}
341
+ <Show when={props.footer}>
342
+ <div part="footer" class="shrink-0 px-4 pb-3">
343
+ <div class="mx-auto max-w-3xl text-center text-xs text-muted-foreground"><slot name="footer" /></div>
344
+ </div>
345
+ </Show>
237
346
  </div>
238
347
  </div>
239
348
  </ChatConfig>
@@ -8,6 +8,7 @@ import {
8
8
  createMemo,
9
9
  createEffect,
10
10
  on,
11
+ onMount,
11
12
  ErrorBoundary,
12
13
  createUniqueId,
13
14
  } from 'solid-js';
@@ -151,6 +152,22 @@ export function firstEnabledIndex(options: ChoiceOption[]): number {
151
152
  // The <ChoiceCard> component.
152
153
  // ─────────────────────────────────────────────────────────────────────────────
153
154
 
155
+ /** Imperative handle exposed via `controllerRef` — surfaces the choice card's
156
+ * latent selection/submit/dismiss capabilities so the `<kai-choice>` facade can
157
+ * forward them as instance methods (focus/select/send/dismiss/reopen). */
158
+ export interface ChoiceController {
159
+ /** Focus the radiogroup roving tab stop (or the Other input when selected). */
160
+ focus(options?: FocusOptions): void;
161
+ /** Select an option by id locally (no emit) — same as a row click. */
162
+ select(optionId: string): void;
163
+ /** Submit the current selection — emits the `action` verb + resolves single-shot. */
164
+ send(): void;
165
+ /** Dismiss the card — emits `dismiss` + optimistically collapses to the stub. */
166
+ dismiss(): void;
167
+ /** Re-open a dismissed card from its stub — emits `reopen`. */
168
+ reopen(): void;
169
+ }
170
+
154
171
  export interface ChoiceCardProps {
155
172
  /** The choice definition (CardEnvelope.data). */
156
173
  data?: ChoiceCardData;
@@ -166,6 +183,17 @@ export interface ChoiceCardProps {
166
183
  class?: string;
167
184
  /** When set, render the chromed read-only view instead of the interactive radiogroup. */
168
185
  resolution?: CardResolution;
186
+ /** Controlled selection (option id). When set, the consumer owns the current pick. */
187
+ value?: string;
188
+ /** Option id to pre-select on mount (uncontrolled seed). */
189
+ defaultValue?: string;
190
+ /** Disable the whole radiogroup + Submit (e.g. while the agent is busy). */
191
+ disabled?: boolean;
192
+ /** Fires when the selection changes BEFORE submit (row click or select()). */
193
+ onValueChange?: (value: string) => void;
194
+ /** Receive the imperative controller once mounted. The `<kai-choice>` facade
195
+ * forwards these as element methods (focus/select/send/dismiss/reopen). */
196
+ controllerRef?: (controller: ChoiceController) => void;
169
197
  }
170
198
 
171
199
  /**
@@ -182,7 +210,20 @@ export interface ChoiceCardProps {
182
210
  */
183
211
  export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
184
212
  const merged = mergeProps({ cardId: 'kai-choice' }, props);
185
- const [local] = splitProps(merged, ['data', 'cardId', 'heading', 'host', 'hostElement', 'class', 'resolution']);
213
+ const [local] = splitProps(merged, [
214
+ 'data',
215
+ 'cardId',
216
+ 'heading',
217
+ 'host',
218
+ 'hostElement',
219
+ 'class',
220
+ 'resolution',
221
+ 'value',
222
+ 'defaultValue',
223
+ 'disabled',
224
+ 'onValueChange',
225
+ 'controllerRef',
226
+ ]);
186
227
 
187
228
  const ctxHost = useCardHost();
188
229
  const uid = createUniqueId();
@@ -214,20 +255,33 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
214
255
  });
215
256
 
216
257
  const [focusIndex, setFocusIndex] = createSignal(0);
217
- const [selectedId, setSelectedId] = createSignal<string | undefined>(undefined);
258
+ // Uncontrolled selection store, seeded from `defaultValue`. When the consumer
259
+ // sets the `value` prop the controlled path wins (same pattern as the composer):
260
+ // `selectedId()` reads value ?? internal, and `setSelection()` writes the
261
+ // internal store + fires onValueChange so a controlled host can mirror it.
262
+ const [internalId, setInternalId] = createSignal<string | undefined>(props.defaultValue);
263
+ const selectedId = createMemo<string | undefined>(() => local.value ?? internalId());
264
+ const setSelection = (id: string): void => {
265
+ if (selectedId() === id) return;
266
+ setInternalId(id);
267
+ local.onValueChange?.(id);
268
+ };
218
269
  const [otherText, setOtherText] = createSignal('');
219
270
 
271
+ const isDisabled = (): boolean => local.disabled === true;
272
+
220
273
  let groupRef: HTMLDivElement | undefined;
221
274
  let otherInputRef: HTMLInputElement | undefined;
222
275
 
223
276
  const otherSelected = createMemo(() => selectedId() === OTHER_ACTION);
224
277
 
225
- // Reset all transient state whenever a NEW definition arrives.
278
+ // Reset all transient state whenever a NEW definition arrives (re-seed the
279
+ // uncontrolled selection from `defaultValue`).
226
280
  createEffect(
227
281
  on(
228
282
  () => local.data,
229
283
  () => {
230
- setSelectedId(undefined);
284
+ setInternalId(local.defaultValue);
231
285
  setOtherText('');
232
286
  setFocusIndex(Math.max(0, firstEnabledIndex(options())));
233
287
  },
@@ -264,11 +318,12 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
264
318
 
265
319
  const isOther = (opt: ChoiceOption): boolean => opt.id === OTHER_ACTION;
266
320
 
267
- // Select (do NOT emit) the given option locally.
321
+ // Select (do NOT emit) the given option locally; fires onValueChange.
268
322
  const select = (opt: ChoiceOption): void => {
269
323
  if (res.isResolved()) return; // single-shot
324
+ if (isDisabled()) return; // group-level freeze
270
325
  if (opt.disabled) return;
271
- setSelectedId(opt.id);
326
+ setSelection(opt.id);
272
327
  if (isOther(opt)) {
273
328
  queueMicrotask(() => otherInputRef?.focus());
274
329
  }
@@ -277,6 +332,7 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
277
332
  // Whether the current selection is submittable.
278
333
  const canSubmit = createMemo(() => {
279
334
  if (res.isResolved()) return false;
335
+ if (isDisabled()) return false; // group-level freeze
280
336
  const id = selectedId();
281
337
  if (id === undefined) return false;
282
338
  if (id === OTHER_ACTION) return otherText().trim().length > 0;
@@ -288,6 +344,7 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
288
344
  // Submit the current selection: emit `action` then resolve (single-shot).
289
345
  const submit = (): void => {
290
346
  if (res.isResolved()) return;
347
+ if (isDisabled()) return; // group-level freeze
291
348
  const id = selectedId();
292
349
  if (id === undefined) return;
293
350
  if (id === OTHER_ACTION) {
@@ -321,13 +378,43 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
321
378
  };
322
379
  const onReopen = (): void => emit({ kind: 'reopen', cardId: local.cardId });
323
380
 
324
- const focusRadio = (index: number): void => {
381
+ const focusRadio = (index: number, options?: FocusOptions): void => {
325
382
  const radios = groupRef?.querySelectorAll<HTMLElement>('[role="radio"]');
326
- radios?.[index]?.focus();
383
+ radios?.[index]?.focus(options);
327
384
  };
328
385
 
386
+ // ── Imperative controller (Pattern C): hand the facade a handle over the
387
+ // card's latent selection/submit/dismiss capabilities. Every method drives
388
+ // the SAME internal path the buttons/keyboard drive, so the same kai-card
389
+ // events fire (and onValueChange for select()). ─────────────────────────────
390
+ onMount(() => {
391
+ local.controllerRef?.({
392
+ // Focus the roving radiogroup tab stop, or the Other input when it's selected.
393
+ focus: (options) => {
394
+ if (otherSelected() && otherInputRef) otherInputRef.focus(options);
395
+ else focusRadio(focusIndex(), options);
396
+ },
397
+ // Select an option by id locally (no emit) — same as a row click; respects
398
+ // disabled/resolved gating and moves the roving focus to the picked row.
399
+ select: (optionId) => {
400
+ const opts = options();
401
+ const idx = opts.findIndex((o) => o.id === optionId);
402
+ if (idx === -1) return;
403
+ setFocusIndex(idx);
404
+ select(opts[idx]);
405
+ },
406
+ // Submit the current selection (the Submit-button path).
407
+ send: () => submit(),
408
+ // Trigger the dismiss path (the X-button path).
409
+ dismiss: () => onDismiss(),
410
+ // Re-open a dismissed card (the stub's affordance path).
411
+ reopen: () => onReopen(),
412
+ });
413
+ });
414
+
329
415
  const onGroupKeyDown = (e: KeyboardEvent): void => {
330
416
  if (res.isResolved()) return;
417
+ if (isDisabled()) return; // group-level freeze
331
418
  const opts = options();
332
419
  const i = focusIndex();
333
420
  if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
@@ -385,14 +472,18 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
385
472
  role="radiogroup"
386
473
  aria-label={groupLabel()}
387
474
  aria-describedby={local.data?.prompt ? promptId : undefined}
388
- class="divide-y divide-border overflow-hidden rounded-lg border border-input"
475
+ aria-disabled={isDisabled() ? 'true' : undefined}
476
+ class={cn(
477
+ 'divide-y divide-border overflow-hidden rounded-lg border border-input',
478
+ isDisabled() && 'cursor-not-allowed opacity-60',
479
+ )}
389
480
  onKeyDown={onGroupKeyDown}
390
481
  >
391
482
  <For each={options()}>
392
483
  {(opt, index) => {
393
484
  const checked = () => selectedId() === opt.id;
394
485
  const tabStop = () =>
395
- !opt.disabled && index() === focusIndex() && !res.isResolved();
486
+ !opt.disabled && !isDisabled() && index() === focusIndex() && !res.isResolved();
396
487
  const descId = `kai-choice-desc-${uid}-${opt.id}`;
397
488
  const hasDesc = () => Boolean(opt.description);
398
489
  return (
@@ -422,7 +513,8 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
422
513
  id={otherInputId}
423
514
  ref={otherInputRef}
424
515
  type="text"
425
- class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
516
+ disabled={isDisabled()}
517
+ class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-60"
426
518
  placeholder={otherCfg()?.placeholder}
427
519
  value={otherText()}
428
520
  onInput={(e) => setOtherText(e.currentTarget.value)}