@kitn.ai/ui 0.17.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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-C2LTALdM.js +1 -0
  57. package/dist/elements/chunks/action-icons-Dtl8Ox5D.js +1 -0
  58. package/dist/elements/chunks/arrow-left-GbBceGBE.js +1 -0
  59. package/dist/elements/chunks/attachments-DLJcWCDV.js +1 -0
  60. package/dist/elements/chunks/badge-BTNZTODL.js +1 -0
  61. package/dist/elements/chunks/button-DBDZebGr.js +1 -0
  62. package/dist/elements/chunks/card-YfCyI4fH.js +1 -0
  63. package/dist/elements/chunks/chat-thread-Dz2qbzvI.js +1 -0
  64. package/dist/elements/chunks/check-fhmRksq_.js +1 -0
  65. package/dist/elements/chunks/chevron-down-DcRg1aRU.js +1 -0
  66. package/dist/elements/chunks/chevron-right-ChzBcNJ-.js +1 -0
  67. package/dist/elements/chunks/circle-B4fTe9v6.js +1 -0
  68. package/dist/elements/chunks/circle-check-CJV-3Rem.js +1 -0
  69. package/dist/elements/chunks/circle-x-pDtzlghQ.js +1 -0
  70. package/dist/elements/chunks/cn-CU7UAWzk.js +1 -0
  71. package/dist/elements/chunks/code-block-DZ6mz6vs.js +1 -0
  72. package/dist/elements/chunks/collapsible-CWUAShMC.js +1 -0
  73. package/dist/elements/chunks/composer-BZyPU6gD.js +65 -0
  74. package/dist/elements/chunks/context-Cx6eQA_G.js +1 -0
  75. package/dist/elements/chunks/conversation-list-C4xTQSpT.js +1 -0
  76. package/dist/elements/chunks/default-input-BZcwyKcP.js +1 -0
  77. package/dist/elements/chunks/define-CNSKFZCd.js +1 -0
  78. package/dist/elements/chunks/disclosure-BcBs--Jf.js +1 -0
  79. package/dist/elements/chunks/download-BDQwcFav.js +1 -0
  80. package/dist/elements/chunks/dropdown-wgs8xCJZ.js +1 -0
  81. package/dist/elements/chunks/ellipsis-CUiEJ6R1.js +1 -0
  82. package/dist/elements/chunks/external-link-DJUmC7g2.js +1 -0
  83. package/dist/elements/chunks/file-text-DOYfYvrh.js +1 -0
  84. package/dist/elements/chunks/file-tree-DQzNQrbr.js +1 -0
  85. package/dist/elements/chunks/folder-DBDUBANz.js +1 -0
  86. package/dist/elements/chunks/hover-card-BQGdGAQ3.js +1 -0
  87. package/dist/elements/chunks/icon-CAyRqVrg.js +1 -0
  88. package/dist/elements/chunks/{index-CTBEJeo6.js → index-BCRhi84a.js} +1 -1
  89. package/dist/elements/chunks/info-DBQl-m6e.js +1 -0
  90. package/dist/elements/chunks/input-DZD67EvW.js +1 -0
  91. package/dist/elements/chunks/kbd-DbyVrlnU.js +1 -0
  92. package/dist/elements/chunks/link-vko7AttA.js +1 -0
  93. package/dist/elements/chunks/loader-Cg6rO8tQ.js +1 -0
  94. package/dist/elements/chunks/{markdown-CMTGx9r8.js → markdown-n3Xbj3PR.js} +9 -9
  95. package/dist/elements/chunks/message-CgKytiSB.js +1 -0
  96. package/dist/elements/chunks/message-feedback-Ds3-US0e.js +1 -0
  97. package/dist/elements/chunks/message-square-MmRkvc-M.js +1 -0
  98. package/dist/elements/chunks/minimize-2-3kCdE0q3.js +1 -0
  99. package/dist/elements/chunks/model-switcher-BMtXGwLU.js +1 -0
  100. package/dist/elements/chunks/overlay-B1-MBI6h.js +1 -0
  101. package/dist/elements/chunks/paperclip-DDEfmkHc.js +1 -0
  102. package/dist/elements/chunks/progress-bar-Dwbiyv3s.js +1 -0
  103. package/dist/elements/chunks/prompt-suggestion-9lvgFcEa.js +1 -0
  104. package/dist/elements/chunks/reasoning-DzHWsdAJ.js +1 -0
  105. package/dist/elements/chunks/resizable-Bq_eHxUm.js +1 -0
  106. package/dist/elements/chunks/rotate-cw-jpisOdOV.js +1 -0
  107. package/dist/elements/chunks/scroll-area-g5Ze3K6f.js +1 -0
  108. package/dist/elements/chunks/separator-Bmjc5XyX.js +1 -0
  109. package/dist/elements/chunks/settings-Cz1bMJP9.js +1 -0
  110. package/dist/elements/chunks/settings-group-9imY3kRA.js +1 -0
  111. package/dist/elements/chunks/share-BdenF0wQ.js +1 -0
  112. package/dist/elements/chunks/skeleton-Pjc5jiaY.js +1 -0
  113. package/dist/elements/chunks/slots-DkZ-Cmha.js +1 -0
  114. package/dist/elements/chunks/star-DY6z1UUH.js +1 -0
  115. package/dist/elements/chunks/store-th4VyJXM.js +1 -0
  116. package/dist/elements/chunks/text-shimmer-CEi-tlXr.js +1 -0
  117. package/dist/elements/chunks/textarea-CboigarR.js +1 -0
  118. package/dist/elements/chunks/thumbs-up-BR33KLfA.js +1 -0
  119. package/dist/elements/chunks/toast-store-BEMda0fv.js +1 -0
  120. package/dist/elements/chunks/tool-TMued3WO.js +1 -0
  121. package/dist/elements/chunks/tooltip-Cft_Rxiw.js +1 -0
  122. package/dist/elements/chunks/triangle-alert-B07fX3Q8.js +1 -0
  123. package/dist/elements/chunks/use-card-resolution-M36zVciB.js +1 -0
  124. package/dist/elements/chunks/video-MKp3LmNT.js +1 -0
  125. package/dist/elements/chunks/x-Ckwicf6g.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 +8255 -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-cKscku5m.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 +77 -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 +31 -10
  287. package/src/agent-tooling/mcp/manifest.ts +14 -0
  288. package/src/agent-tooling/mcp/tools/debug.ts +11 -7
  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 +249 -0
  300. package/src/components/component-meta.json +699 -110
  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 +341 -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 +2 -2
  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 +87 -7
  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 +218 -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 +117 -0
  429. package/src/ui/pane-group.tsx +291 -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
@@ -20,7 +20,7 @@ export interface MessageProps extends JSX.HTMLAttributes<HTMLDivElement> {
20
20
  function Message(props: MessageProps) {
21
21
  const [local, rest] = splitProps(props, ["children", "class"]);
22
22
  return (
23
- <div class={cn("flex items-start gap-3", local.class)} {...rest}>
23
+ <div part="row" class={cn("flex items-start gap-3", local.class)} {...rest}>
24
24
  {local.children}
25
25
  </div>
26
26
  );
@@ -38,6 +38,7 @@ export interface MessageAvatarProps {
38
38
  function MessageAvatar(props: MessageAvatarProps) {
39
39
  return (
40
40
  <div
41
+ part="avatar"
41
42
  class={cn("h-8 w-8 shrink-0 overflow-hidden rounded-full", props.class)}
42
43
  >
43
44
  <Show
@@ -65,10 +66,14 @@ function MessageAvatar(props: MessageAvatarProps) {
65
66
  export interface MessageContentProps extends JSX.HTMLAttributes<HTMLDivElement> {
66
67
  children: JSX.Element | string;
67
68
  markdown?: boolean;
69
+ /** `::part` name(s) exposed on the content node. The body passes
70
+ * `"bubble content"` so consumers can target either the rounded bubble or the
71
+ * text region from outside the shadow boundary. */
72
+ part?: string;
68
73
  }
69
74
 
70
75
  function MessageContent(props: MessageContentProps) {
71
- const [local, rest] = splitProps(props, ["children", "markdown", "class"]);
76
+ const [local, rest] = splitProps(props, ["children", "markdown", "class", "part"]);
72
77
  const config = useChatConfig();
73
78
  const classNames = () =>
74
79
  cn(
@@ -81,12 +86,12 @@ function MessageContent(props: MessageContentProps) {
81
86
  <Show
82
87
  when={local.markdown}
83
88
  fallback={
84
- <div class={classNames()} {...rest}>
89
+ <div part={local.part} class={classNames()} {...rest}>
85
90
  {local.children}
86
91
  </div>
87
92
  }
88
93
  >
89
- <Markdown content={local.children as string} class={classNames()} />
94
+ <Markdown part={local.part} content={local.children as string} class={classNames()} />
90
95
  </Show>
91
96
  );
92
97
  }
@@ -163,6 +168,7 @@ function feedbackVoteOf(a: ChatMessageAction | CustomAction): FeedbackVote | und
163
168
  function MessageActionBar(props: MessageActionBarProps) {
164
169
  return (
165
170
  <MessageActions
171
+ part="actions"
166
172
  class={cn(
167
173
  'mt-1 flex gap-0',
168
174
  props.reveal === 'hover' && 'opacity-0 transition-opacity group-hover:opacity-100',
@@ -273,6 +279,14 @@ export interface MessageBodyProps {
273
279
  activeFeedback?: FeedbackVote;
274
280
  /** When true, the copy button shows its "copied" check icon. */
275
281
  copied?: boolean;
282
+ /** INJECT slot projected at the TOP of the body — above the reasoning / tools /
283
+ * content. A per-message header: a model-name label, a role + timestamp line.
284
+ * In the `<kai-message>` shadow this is `<slot name="before-body" />`. */
285
+ beforeBody?: JSX.Element;
286
+ /** INJECT slot projected at the BOTTOM of the body — below the action bar. A
287
+ * citation / sources row, a token-cost / latency line. In the `<kai-message>`
288
+ * shadow this is `<slot name="after-body" />`. */
289
+ afterBody?: JSX.Element;
276
290
  }
277
291
 
278
292
  /**
@@ -286,6 +300,8 @@ export interface MessageBodyProps {
286
300
  function MessageBody(props: MessageBodyProps) {
287
301
  return (
288
302
  <>
303
+ {/* before-body (inject): a per-message header above everything else. */}
304
+ <Show when={props.beforeBody}>{props.beforeBody}</Show>
289
305
  <Show when={props.reasoning}>
290
306
  {(r) => (
291
307
  <Reasoning class="mb-2 w-full">
@@ -303,6 +319,7 @@ function MessageBody(props: MessageBodyProps) {
303
319
  </Attachments>
304
320
  </Show>
305
321
  <MessageContent
322
+ part="bubble content"
306
323
  markdown={props.markdown}
307
324
  class={props.isUser
308
325
  ? 'bg-muted text-primary max-w-[85%] rounded-2xl px-4 py-2'
@@ -319,6 +336,8 @@ function MessageBody(props: MessageBodyProps) {
319
336
  onAction={(id) => props.onAction?.(id)}
320
337
  />
321
338
  </Show>
339
+ {/* after-body (inject): a citation row / cost line below the action bar. */}
340
+ <Show when={props.afterBody}>{props.afterBody}</Show>
322
341
  </>
323
342
  );
324
343
  }
@@ -1,10 +1,21 @@
1
1
  import { splitProps, For, Show, createSignal } from 'solid-js';
2
2
  import { cn } from '../utils/cn';
3
- import { Dropdown, DropdownTrigger, DropdownContent, DropdownItem } from '../ui/dropdown';
3
+ import { Dropdown, DropdownTrigger, DropdownContent, DropdownItem, type DropdownController } from '../ui/dropdown';
4
4
  import { Button } from '../ui/button';
5
5
  import type { ModelOption } from '../types';
6
6
 
7
- export interface ModelSwitcherProps { models: ModelOption[]; currentModelId: string; onModelChange: (modelId: string) => void; class?: string; }
7
+ export interface ModelSwitcherProps {
8
+ models: ModelOption[];
9
+ currentModelId: string;
10
+ onModelChange: (modelId: string) => void;
11
+ class?: string;
12
+ /** Initial open state of the dropdown (uncontrolled seed). */
13
+ defaultOpen?: boolean;
14
+ /** Disable the trigger — click/keyboard no longer open the dropdown. */
15
+ disabled?: boolean;
16
+ /** Receive the dropdown's open controller (forwarded from the inner Dropdown). */
17
+ controllerRef?: (api: DropdownController) => void;
18
+ }
8
19
 
9
20
  const Chevron = (props: { class?: string }) => (
10
21
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class={props.class}>
@@ -25,7 +36,7 @@ function ModelRow(props: { model: ModelOption; currentModelId: string; onModelCh
25
36
  }
26
37
 
27
38
  export function ModelSwitcher(props: ModelSwitcherProps) {
28
- const [local] = splitProps(props, ['models', 'currentModelId', 'onModelChange', 'class']);
39
+ const [local] = splitProps(props, ['models', 'currentModelId', 'onModelChange', 'class', 'defaultOpen', 'disabled', 'controllerRef']);
29
40
  const currentModel = () => local.models.find((m) => m.id === local.currentModelId);
30
41
 
31
42
  // Ungrouped models list first; grouped models collect under their group name,
@@ -44,7 +55,11 @@ export function ModelSwitcher(props: ModelSwitcherProps) {
44
55
 
45
56
  return (
46
57
  <Show when={local.models.length > 1}>
47
- <Dropdown>
58
+ <Dropdown
59
+ defaultOpen={local.defaultOpen}
60
+ disabled={local.disabled}
61
+ controllerRef={local.controllerRef}
62
+ >
48
63
  <DropdownTrigger as={(triggerProps: any) => (
49
64
  <Button variant="ghost" size="sm" class={cn('gap-1 text-meta text-muted-foreground', local.class)} {...triggerProps}>
50
65
  {currentModel()?.name ?? local.currentModelId}
@@ -1,18 +1,21 @@
1
- import { type JSX, splitProps, createSignal, createContext, useContext, createEffect, on } from 'solid-js';
1
+ import { type JSX, splitProps, createSignal, createContext, useContext } from 'solid-js';
2
2
  import { cn } from '../utils/cn';
3
3
  import { useChatConfig, textClass } from '../primitives/chat-config';
4
+ import { Composer, type TriggerDef, type ComposerChange } from './composer';
5
+ import type { ComposerDoc } from '../primitives/composer-model';
4
6
 
5
7
  // --- Context ---
6
8
 
7
9
  interface PromptInputContextType {
8
10
  isLoading: boolean;
9
- value: () => string;
11
+ // A string (controlled text) or a ComposerDoc (a seed that pre-populates pills).
12
+ value: () => string | ComposerDoc;
10
13
  setValue: (value: string) => void;
11
14
  maxHeight: number | string;
12
15
  onSubmit?: () => void;
13
16
  disabled?: boolean;
14
- textareaRef: HTMLTextAreaElement | undefined;
15
- setTextareaRef: (el: HTMLTextAreaElement) => void;
17
+ textareaRef: HTMLElement | undefined;
18
+ setTextareaRef: (el: HTMLElement) => void;
16
19
  }
17
20
 
18
21
  const PromptInputContext = createContext<PromptInputContextType>();
@@ -27,7 +30,8 @@ function usePromptInput() {
27
30
 
28
31
  export interface PromptInputProps extends JSX.HTMLAttributes<HTMLDivElement> {
29
32
  isLoading?: boolean;
30
- value?: string;
33
+ /** String = controlled text; ComposerDoc = a seed that pre-populates pills. */
34
+ value?: string | ComposerDoc;
31
35
  onValueChange?: (value: string) => void;
32
36
  maxHeight?: number | string;
33
37
  onSubmit?: () => void;
@@ -41,8 +45,8 @@ function PromptInput(props: PromptInputProps) {
41
45
  'children', 'disabled', 'class', 'onClick',
42
46
  ]);
43
47
 
44
- const [internalValue, setInternalValue] = createSignal(local.value ?? '');
45
- let textareaRef: HTMLTextAreaElement | undefined;
48
+ const [internalValue, setInternalValue] = createSignal<string | ComposerDoc>(local.value ?? '');
49
+ let textareaRef: HTMLElement | undefined;
46
50
 
47
51
  const handleChange = (newValue: string) => {
48
52
  setInternalValue(newValue);
@@ -70,13 +74,14 @@ function PromptInput(props: PromptInputProps) {
70
74
  }}
71
75
  >
72
76
  <div
77
+ data-prompt-input
73
78
  onClick={handleClick}
74
79
  class={cn(
75
80
  // The inner textarea neutralizes its own ring (focus-visible:ring-0),
76
81
  // so the FRAME owns the focus affordance: a blue ring whenever a
77
82
  // control inside it is focused. Without this the composer had no
78
83
  // visible keyboard-focus state.
79
- 'bg-muted/40 cursor-text rounded-xl p-2 shadow-xs',
84
+ 'bg-surface cursor-text rounded-xl p-2 shadow-xs',
80
85
  'focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-0',
81
86
  local.disabled && 'cursor-not-allowed opacity-60',
82
87
  local.class
@@ -93,83 +98,49 @@ function PromptInput(props: PromptInputProps) {
93
98
 
94
99
  export interface PromptInputTextareaProps extends JSX.TextareaHTMLAttributes<HTMLTextAreaElement> {
95
100
  disableAutosize?: boolean;
101
+ /** Rich entity triggers (`/`, `@`) forwarded to the composer. */
102
+ triggers?: TriggerDef[];
103
+ /** Default icon per entity kind (kind → image src), forwarded to the composer. */
104
+ kindIcons?: Record<string, string>;
105
+ /** Structured change (doc + entities) from the composer, on every edit. */
106
+ onComposerChange?: (change: ComposerChange) => void;
96
107
  }
97
108
 
98
109
  function PromptInputTextarea(props: PromptInputTextareaProps) {
99
- const [local, rest] = splitProps(props, ['class', 'onKeyDown', 'disableAutosize']);
110
+ const [local] = splitProps(props, ['class', 'placeholder', 'aria-label', 'triggers', 'kindIcons', 'onComposerChange']);
100
111
  const ctx = usePromptInput();
101
112
  const config = useChatConfig();
102
113
 
103
- function adjustHeight(el: HTMLTextAreaElement | undefined) {
104
- if (!el || local.disableAutosize) return;
105
- el.style.height = 'auto';
106
- const maxH = ctx.maxHeight;
107
- if (typeof maxH === 'number') {
108
- el.style.height = `${Math.min(el.scrollHeight, maxH)}px`;
109
- } else {
110
- el.style.height = `min(${el.scrollHeight}px, ${maxH})`;
111
- }
112
- }
113
-
114
- function handleRef(el: HTMLTextAreaElement) {
115
- ctx.setTextareaRef(el);
116
- adjustHeight(el);
117
- }
118
-
119
- createEffect(on(
120
- () => [ctx.value(), ctx.maxHeight, local.disableAutosize],
121
- () => {
122
- if (ctx.textareaRef && !local.disableAutosize) {
123
- adjustHeight(ctx.textareaRef);
124
- }
125
- }
126
- ));
127
-
128
- function handleInput(e: InputEvent & { currentTarget: HTMLTextAreaElement }) {
129
- const el = e.currentTarget;
130
- let value = el.value;
131
- // Disallow leading whitespace — a prompt can't start with a space or blank
132
- // line. Strip it (covers typing a space at the start AND pasting) and keep
133
- // the caret in the right place.
134
- if (/^\s/.test(value)) {
135
- const stripped = value.replace(/^\s+/, '');
136
- const removed = value.length - stripped.length;
137
- const caret = Math.max(0, (el.selectionStart ?? 0) - removed);
138
- el.value = stripped;
139
- el.setSelectionRange(caret, caret);
140
- value = stripped;
141
- }
142
- adjustHeight(el);
143
- ctx.setValue(value);
144
- }
145
-
146
- function handleKeyDown(e: KeyboardEvent & { currentTarget: HTMLTextAreaElement }) {
147
- if (e.key === 'Enter' && !e.shiftKey) {
148
- e.preventDefault();
149
- // A disabled composer is non-interactive: Enter must not submit.
150
- if (!ctx.disabled) ctx.onSubmit?.();
151
- }
152
- if (typeof local.onKeyDown === 'function') {
153
- (local.onKeyDown as (e: KeyboardEvent & { currentTarget: HTMLTextAreaElement }) => void)(e);
154
- }
155
- }
114
+ // The editable mirrors the original <textarea>'s classes EXACTLY so swapping in
115
+ // the contenteditable composer is visually identical. The frame (PromptInput
116
+ // root) still owns radius/bg/padding/focus-ring; auto-grow is native to a
117
+ // contenteditable block (capped via max-height on the editable).
118
+ const editableClass = () =>
119
+ cn(
120
+ 'text-foreground min-h-[44px] w-full bg-transparent shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0 overflow-y-auto whitespace-pre-wrap break-words',
121
+ textClass(config.proseSize()),
122
+ local.class,
123
+ );
156
124
 
157
125
  return (
158
- <textarea
159
- ref={handleRef}
126
+ <Composer
127
+ bare
160
128
  value={ctx.value()}
161
- onInput={handleInput}
162
- onKeyDown={handleKeyDown}
163
- class={cn(
164
- // Typed text uses the normal foreground (light/dark), NOT the brand/accent
165
- // color — the composer should read as plain text, not tinted.
166
- 'text-foreground min-h-[44px] w-full resize-none border-none bg-transparent shadow-none outline-none focus-visible:ring-0 focus-visible:ring-offset-0',
167
- textClass(config.proseSize()),
168
- local.class
169
- )}
170
- rows={1}
129
+ placeholder={local.placeholder as string | undefined}
130
+ ariaLabel={local['aria-label'] as string | undefined}
171
131
  disabled={ctx.disabled}
172
- {...rest}
132
+ maxHeight={ctx.maxHeight}
133
+ editableClass={editableClass()}
134
+ editableRef={(el) => ctx.setTextareaRef(el)}
135
+ triggers={local.triggers}
136
+ kindIcons={local.kindIcons}
137
+ // Surface the structured change (doc/entities) BEFORE the string value, so a
138
+ // consumer that enriches its events has the latest doc when value-change fires.
139
+ // A prompt can't start with whitespace — strip leading whitespace from the
140
+ // string value (parity with the old textarea); the controlled round-trip
141
+ // re-renders the editable so the stripped value shows too.
142
+ onChange={(c) => { local.onComposerChange?.(c); ctx.setValue(c.text.replace(/^\s+/, '')); }}
143
+ onSubmit={() => { if (!ctx.disabled) ctx.onSubmit?.(); }}
173
144
  />
174
145
  );
175
146
  }
@@ -1,20 +1,30 @@
1
1
  import { type JSX, Show, splitProps } from 'solid-js';
2
2
  import { cn } from '../utils/cn';
3
3
  import { Button } from '../ui/button';
4
+ import { renderIcon } from '../ui/icon';
4
5
 
5
6
  export interface PromptSuggestionProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
6
7
  children: JSX.Element | string;
7
8
  variant?: 'outline' | 'ghost' | 'default';
8
- size?: 'sm' | 'md' | 'lg' | 'icon' | 'icon-sm';
9
+ /** Row height for the `list` layout: `'md'` (default) or `'lg'` for taller
10
+ * rows (more vertical padding). Chips/block/highlight are unaffected. */
11
+ size?: 'md' | 'lg';
12
+ /** Optional leading icon (named icon, image URL/data-URI, or text). */
13
+ icon?: string;
9
14
  highlight?: string;
10
15
  /** Render as a full-width, left-aligned list row (the "suggested questions"
11
16
  * idiom) instead of a rounded pill. Wraps long text. Ignored in highlight
12
17
  * mode, which is always a list row. */
13
18
  block?: boolean;
19
+ /** Render as a full-width "Ideas for you" list row: a leading icon, a
20
+ * left-aligned label, and a hover background. Like `block`, but keeps the
21
+ * leading icon. Ignored in highlight mode. */
22
+ list?: boolean;
14
23
  }
15
24
 
16
25
  function PromptSuggestion(props: PromptSuggestionProps) {
17
- const [local, rest] = splitProps(props, ['children', 'variant', 'size', 'class', 'highlight', 'block']);
26
+ const [local, rest] = splitProps(props, ['children', 'variant', 'size', 'class', 'icon', 'highlight', 'block', 'list']);
27
+ const Icon = () => <Show when={local.icon}>{renderIcon(local.icon, { class: 'size-3.5 shrink-0' })}</Show>;
18
28
 
19
29
  const isHighlightMode = () => local.highlight !== undefined && local.highlight.trim() !== '';
20
30
  const content = () => typeof local.children === 'string' ? local.children : '';
@@ -24,29 +34,51 @@ function PromptSuggestion(props: PromptSuggestionProps) {
24
34
  when={isHighlightMode()}
25
35
  fallback={
26
36
  <Show
27
- when={local.block}
37
+ when={local.list}
28
38
  fallback={
29
- <Button
30
- variant={local.variant ?? 'outline'}
31
- size={local.size ?? 'lg'}
32
- class={cn('rounded-full', local.class)}
33
- {...rest}
39
+ <Show
40
+ when={local.block}
41
+ fallback={
42
+ <Button
43
+ variant={local.variant ?? 'outline'}
44
+ size="lg"
45
+ class={cn('rounded-full', local.class)}
46
+ {...rest}
47
+ >
48
+ <Icon />
49
+ {local.children}
50
+ </Button>
51
+ }
34
52
  >
35
- {local.children}
36
- </Button>
53
+ <Button
54
+ variant={local.variant ?? 'outline'}
55
+ size="md"
56
+ class={cn(
57
+ 'h-auto w-full cursor-pointer justify-start rounded-xl px-4 py-2.5',
58
+ 'text-left text-sm leading-snug whitespace-normal text-pretty',
59
+ local.class,
60
+ )}
61
+ {...rest}
62
+ >
63
+ {local.children}
64
+ </Button>
65
+ </Show>
37
66
  }
38
67
  >
39
68
  <Button
40
- variant={local.variant ?? 'outline'}
41
- size={local.size ?? 'md'}
69
+ variant={local.variant ?? 'ghost'}
70
+ size="md"
42
71
  class={cn(
43
- 'h-auto w-full cursor-pointer justify-start rounded-xl px-4 py-2.5',
72
+ 'group h-auto w-full cursor-pointer items-center justify-start gap-2.5 rounded-xl px-3',
73
+ local.size === 'lg' ? 'py-3.5' : 'py-2.5',
44
74
  'text-left text-sm leading-snug whitespace-normal text-pretty',
75
+ 'hover:bg-accent',
45
76
  local.class,
46
77
  )}
47
78
  {...rest}
48
79
  >
49
- {local.children}
80
+ <Icon />
81
+ <span class="min-w-0 flex-1">{local.children}</span>
50
82
  </Button>
51
83
  </Show>
52
84
  }
@@ -56,7 +88,7 @@ function PromptSuggestion(props: PromptSuggestionProps) {
56
88
  fallback={
57
89
  <Button
58
90
  variant={local.variant ?? 'ghost'}
59
- size={local.size ?? 'sm'}
91
+ size="sm"
60
92
  class={cn(
61
93
  'w-full cursor-pointer justify-start rounded-xl py-2',
62
94
  'hover:bg-accent',
@@ -70,7 +102,7 @@ function PromptSuggestion(props: PromptSuggestionProps) {
70
102
  >
71
103
  <Button
72
104
  variant={local.variant ?? 'ghost'}
73
- size={local.size ?? 'sm'}
105
+ size="sm"
74
106
  class={cn(
75
107
  'w-full cursor-pointer justify-start gap-0 rounded-xl py-2',
76
108
  'hover:bg-accent',
@@ -1,4 +1,4 @@
1
- import { type JSX, splitProps, createSignal, createContext, useContext, createEffect, onCleanup, Show } from 'solid-js';
1
+ import { type JSX, type Accessor, splitProps, createSignal, createContext, useContext, createEffect, onCleanup, Show } from 'solid-js';
2
2
  import { cn } from '../utils/cn';
3
3
  import { ChevronDown } from 'lucide-solid';
4
4
  import { Markdown } from './markdown';
@@ -7,8 +7,14 @@ import { observeContentHeight } from '../primitives/use-resize-observer';
7
7
  interface ReasoningContextValue {
8
8
  isOpen: () => boolean;
9
9
  onOpenChange: (open: boolean) => void;
10
+ disabled: () => boolean;
10
11
  }
11
12
 
13
+ /** Imperative open controller, handed to a parent (the kai-reasoning facade) via
14
+ * `controllerRef` so it can drive/observe open state — mirrors
15
+ * CollapsibleController/HoverCardController. */
16
+ export interface ReasoningController { open: Accessor<boolean>; setOpen: (v: boolean) => void; }
17
+
12
18
  const ReasoningContext = createContext<ReasoningContextValue>();
13
19
 
14
20
  function useReasoningContext() {
@@ -25,19 +31,30 @@ export interface ReasoningProps {
25
31
  children: JSX.Element;
26
32
  class?: string;
27
33
  open?: boolean;
34
+ /** Initial open state (uncontrolled seed). */
35
+ defaultOpen?: boolean;
28
36
  onOpenChange?: (open: boolean) => void;
29
37
  isStreaming?: boolean;
38
+ /** Gate the trigger — programmatic control via the controller still works. */
39
+ disabled?: boolean;
40
+ /** Receive the open controller (open accessor + setOpen) once mounted. */
41
+ controllerRef?: (api: ReasoningController) => void;
30
42
  }
31
43
 
32
44
  function Reasoning(props: ReasoningProps) {
33
- const [local] = splitProps(props, ['children', 'class', 'open', 'onOpenChange', 'isStreaming']);
34
- const [internalOpen, setInternalOpen] = createSignal(false);
45
+ const [local] = splitProps(props, ['children', 'class', 'open', 'defaultOpen', 'onOpenChange', 'isStreaming', 'disabled', 'controllerRef']);
46
+ const [internalOpen, setInternalOpen] = createSignal(local.defaultOpen ?? false);
35
47
  const [wasAutoOpened, setWasAutoOpened] = createSignal(false);
36
48
 
37
49
  const isControlled = () => local.open !== undefined;
38
50
  const isOpen = () => (isControlled() ? local.open! : internalOpen());
51
+ const disabled = () => !!local.disabled;
39
52
 
53
+ // The single open-mutate path, shared by the trigger and the imperative
54
+ // controller. In controlled mode we don't touch internal state; the consumer
55
+ // reflects via the `open` prop and observes via onOpenChange.
40
56
  const handleOpenChange = (newOpen: boolean) => {
57
+ if (isOpen() === newOpen) return;
41
58
  if (!isControlled()) {
42
59
  setInternalOpen(newOpen);
43
60
  }
@@ -56,8 +73,13 @@ function Reasoning(props: ReasoningProps) {
56
73
  }
57
74
  });
58
75
 
76
+ // Hand the controller up once. setOpen routes through handleOpenChange so it
77
+ // notifies onOpenChange and respects controlled/uncontrolled mode. Not gated
78
+ // by disabled — the facade's wireDisclosure applies disabled-gating itself.
79
+ local.controllerRef?.({ open: isOpen, setOpen: handleOpenChange });
80
+
59
81
  return (
60
- <ReasoningContext.Provider value={{ isOpen, onOpenChange: handleOpenChange }}>
82
+ <ReasoningContext.Provider value={{ isOpen, onOpenChange: handleOpenChange, disabled }}>
61
83
  <div class={local.class}>{local.children}</div>
62
84
  </ReasoningContext.Provider>
63
85
  );
@@ -71,12 +93,13 @@ export interface ReasoningTriggerProps extends JSX.ButtonHTMLAttributes<HTMLButt
71
93
 
72
94
  function ReasoningTrigger(props: ReasoningTriggerProps) {
73
95
  const [local, rest] = splitProps(props, ['children', 'class']);
74
- const { isOpen, onOpenChange } = useReasoningContext();
96
+ const { isOpen, onOpenChange, disabled } = useReasoningContext();
75
97
 
76
98
  return (
77
99
  <button
78
100
  class={cn('flex cursor-pointer items-center gap-2 text-meta', local.class)}
79
- onClick={() => onOpenChange(!isOpen())}
101
+ disabled={disabled() || undefined}
102
+ onClick={() => { if (!disabled()) onOpenChange(!isOpen()); }}
80
103
  {...rest}
81
104
  >
82
105
  <span class="text-primary">{local.children}</span>
@@ -41,6 +41,18 @@ export { normalizeCandidates, buildSelection, isAnyStreaming } from './response-
41
41
 
42
42
  export type CompareLayout = 'auto' | 'columns' | 'tabs';
43
43
 
44
+ /** Imperative handle exposed via `controllerRef` — surfaces the compare's latent
45
+ * capabilities (commit a pick by candidate id, focus the roving tab stop) so the
46
+ * `<kai-compare>` facade can forward them as instance methods (Pattern C). */
47
+ export interface ResponseCompareController {
48
+ /** Commit a pick by candidate id — same path as the "Pick this" button: emits
49
+ * onSelect + optimistically collapses (single-shot; inert while streaming or
50
+ * already resolved). No-op for an unknown id. */
51
+ select(candidateId: string): void;
52
+ /** Focus the current roving tab stop (the focused candidate's "Pick this" radio). */
53
+ focus(options?: FocusOptions): void;
54
+ }
55
+
44
56
  // ─────────────────────────────────────────────────────────────────────────────
45
57
  // Resolution controller — a sibling of `useCardResolution` for `CompareSelection`,
46
58
  // which has no `kind` field (so it can't satisfy `R extends CardResolution`). Same
@@ -88,6 +100,9 @@ export interface ResponseCompareProps {
88
100
  * component is ≥640px wide, tabs when narrower (e.g. on a phone). */
89
101
  layout?: CompareLayout;
90
102
  class?: string;
103
+ /** Receive the imperative controller once mounted. The `<kai-compare>` facade
104
+ * forwards these as element methods (select/focus). */
105
+ controllerRef?: (controller: ResponseCompareController) => void;
91
106
  /** Fired when the user commits a pick. */
92
107
  onSelect?: (sel: CompareSelection) => void;
93
108
  /** Fired once both candidates have settled (stopped streaming) and a valid
@@ -119,6 +134,7 @@ export function ResponseCompare(props: ResponseCompareProps): JSX.Element {
119
134
  'selection',
120
135
  'layout',
121
136
  'class',
137
+ 'controllerRef',
122
138
  'onSelect',
123
139
  'onReady',
124
140
  'onError',
@@ -178,11 +194,24 @@ export function ResponseCompare(props: ResponseCompareProps): JSX.Element {
178
194
  res.setLocal(sel);
179
195
  };
180
196
 
181
- const focusColumn = (index: number): void => {
197
+ const focusColumn = (index: number, options?: FocusOptions): void => {
182
198
  const cols = groupRef?.querySelectorAll<HTMLElement>('[role="radio"]');
183
- cols?.[index]?.focus();
199
+ cols?.[index]?.focus(options);
184
200
  };
185
201
 
202
+ // Imperative controller (Pattern C): hand the facade a handle over the latent
203
+ // pick + roving-focus capabilities. `pick`/`focusColumn` are already wired
204
+ // internally — this just surfaces them. `select` looks the candidate up by id
205
+ // (the public surface is id-based; `pick` is candidate-based internally) and
206
+ // is a no-op for an unknown id, while streaming, or once resolved (via pick()).
207
+ local.controllerRef?.({
208
+ select: (candidateId: string) => {
209
+ const cand = pair()?.find((c) => c.id === candidateId);
210
+ if (cand) pick(cand);
211
+ },
212
+ focus: (options?: FocusOptions) => focusColumn(focusIndex(), options),
213
+ });
214
+
186
215
  // Two-item roving tabindex (A↔B). Arrows toggle the focused column; Enter/Space
187
216
  // picks it. No-op once resolved.
188
217
  const onGroupKeyDown = (e: KeyboardEvent): void => {