@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,14 +1,14 @@
1
1
  import {
2
2
  createSignal, createEffect, onCleanup, on, For, Show, createMemo,
3
3
  } from 'solid-js';
4
- import { X, Check } from 'lucide-solid';
4
+ import { X, Check, AlertTriangle, XCircle, Info } from 'lucide-solid';
5
5
  import { cn } from '../utils/cn';
6
6
  import { createPresence } from '../ui/overlay';
7
7
  import {
8
- type ToastItem, type ToastVariant, resolveDuration,
8
+ type ToastItem, type ToastVariant, type ToastAppearance, resolveDuration,
9
9
  } from '../primitives/toast-store';
10
10
 
11
- export type { ToastItem, ToastVariant };
11
+ export type { ToastItem, ToastVariant, ToastAppearance };
12
12
 
13
13
  /** Why a toast went away — surfaced to `onDismiss` (and the facade event). */
14
14
  export type ToastDismissReason = 'timeout' | 'close' | 'action';
@@ -27,6 +27,12 @@ export interface ToastRegionProps {
27
27
  /** Stacking: 'expanded' (default, full column) | 'collapsed' (Sonner-style
28
28
  * pile that expands on hover/focus). Attribute: stack. */
29
29
  stack?: 'expanded' | 'collapsed';
30
+ /** Region-level default appearance for toasts that don't set their own. A
31
+ * per-toast `appearance` always wins. Defaults to `'pill'`. */
32
+ appearance?: ToastAppearance;
33
+ /** Region-level default inverse treatment for toasts that don't set their own.
34
+ * A per-toast `inverse` always wins. Defaults to `false`. */
35
+ inverse?: boolean;
30
36
  /** When set, this region renders only the toasts scoped to that element and
31
37
  * anchors over its bounds (top-center), following it on scroll/resize. Unset =
32
38
  * the global, viewport-anchored region. */
@@ -60,6 +66,10 @@ interface ToastProps {
60
66
  /** Hold the auto-dismiss timer (the region sets this true while the whole stack
61
67
  * is hovered/expanded, so peers don't dismiss out from under the cursor). */
62
68
  paused?: boolean;
69
+ /** Region-level default appearance, used when the item doesn't set its own. */
70
+ appearance?: ToastAppearance;
71
+ /** Region-level default inverse, used when the item doesn't set its own. */
72
+ inverse?: boolean;
63
73
  }
64
74
 
65
75
  /**
@@ -73,6 +83,18 @@ export function Toast(props: ToastProps) {
73
83
  const presence = createPresence(open);
74
84
  const variant = () => props.item.variant ?? 'neutral';
75
85
  const dismissible = () => props.item.dismissible !== false;
86
+ // Item-level value wins, then the region-level default, then the built-in default.
87
+ const appearance = (): ToastAppearance => props.item.appearance ?? props.appearance ?? 'pill';
88
+ const inverse = (): boolean => props.item.inverse ?? props.inverse ?? false;
89
+
90
+ // Surface + accent colors, swapped for the high-contrast inverse treatment so the
91
+ // toast pops on EITHER appearance, in light AND dark. The inverse surface is the
92
+ // foreground color, so action/close/muted text switch to the background color.
93
+ const surfaceClass = () =>
94
+ inverse() ? 'bg-foreground text-background border-transparent' : 'bg-popover text-popover-foreground border-border';
95
+ const mutedClass = () => (inverse() ? 'text-background/70' : 'text-muted-foreground');
96
+ const actionClass = () => (inverse() ? 'text-background hover:text-background/80' : 'text-primary hover:text-primary/80');
97
+ const closeClass = () => (inverse() ? 'text-background/70 hover:text-background' : 'text-muted-foreground hover:text-foreground');
76
98
 
77
99
  let remaining = resolveDuration(props.item);
78
100
  let startedAt = 0;
@@ -128,45 +150,107 @@ export function Toast(props: ToastProps) {
128
150
  if (!keepOpen) { clear(); reason = 'action'; setOpen(false); }
129
151
  };
130
152
 
153
+ // Leading variant icon — shared by both appearances. In the card it sits at the
154
+ // top, so nudge it down to align with the title's cap height.
155
+ const iconAlign = () => (appearance() === 'card' ? 'mt-0.5' : undefined);
156
+ const VariantIcon = () => (
157
+ <>
158
+ <Show when={variant() === 'success'}>
159
+ <Check class={cn('size-4 shrink-0 text-emerald-500', iconAlign())} />
160
+ </Show>
161
+ <Show when={variant() === 'warning'}>
162
+ <AlertTriangle class={cn('size-4 shrink-0 text-warning', iconAlign())} />
163
+ </Show>
164
+ <Show when={variant() === 'error'}>
165
+ <XCircle class={cn('size-4 shrink-0 text-destructive', iconAlign())} />
166
+ </Show>
167
+ <Show when={variant() === 'info'}>
168
+ <Info class={cn('size-4 shrink-0 text-info', iconAlign())} />
169
+ </Show>
170
+ </>
171
+ );
172
+
173
+ const ActionButton = (p: { class?: string }) => (
174
+ <Show when={props.item.action}>
175
+ {(action) => (
176
+ <button
177
+ type="button"
178
+ onClick={act}
179
+ class={cn(
180
+ appearance() === 'card'
181
+ ? cn(
182
+ 'inline-flex shrink-0 items-center rounded-md border px-2.5 py-1 text-xs font-medium transition-colors',
183
+ inverse()
184
+ ? 'border-background/30 text-background hover:bg-background/10'
185
+ : 'border-border text-foreground hover:bg-accent',
186
+ )
187
+ : cn('shrink-0 rounded-md text-sm font-medium underline-offset-2 hover:underline', actionClass()),
188
+ p.class,
189
+ )}
190
+ >
191
+ {action().label}
192
+ </button>
193
+ )}
194
+ </Show>
195
+ );
196
+
197
+ const CloseButton = () => (
198
+ <Show when={dismissible()}>
199
+ <button
200
+ type="button"
201
+ onClick={close}
202
+ aria-label="Dismiss"
203
+ class={cn(
204
+ '-mr-1 flex size-5 shrink-0 items-center justify-center rounded-md transition-colors',
205
+ closeClass(),
206
+ )}
207
+ >
208
+ <X class="size-3.5" />
209
+ </button>
210
+ </Show>
211
+ );
212
+
131
213
  return (
132
214
  <Show when={presence.present()}>
133
215
  <div
134
216
  ref={presence.setRef}
135
217
  role="status"
218
+ data-appearance={appearance()}
219
+ data-inverse={inverse() ? '' : undefined}
136
220
  data-closed={presence.state() === 'closed' ? '' : undefined}
137
221
  onPointerEnter={() => setHovered(true)}
138
222
  onPointerLeave={() => setHovered(false)}
139
223
  class={cn(
140
- 'pointer-events-auto flex items-center gap-3 rounded-full border px-4 py-2.5 text-sm shadow-lg',
141
- 'bg-popover text-popover-foreground border-border',
224
+ 'pointer-events-auto border text-sm shadow-lg',
225
+ surfaceClass(),
142
226
  'animate-in fade-in-0 slide-in-from-top-4',
143
227
  'data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:slide-out-to-top-2',
228
+ appearance() === 'card'
229
+ ? 'flex min-w-[24rem] items-start gap-3 rounded-xl p-4 shadow-xl'
230
+ : 'flex items-center gap-3 rounded-full px-4 py-2.5',
144
231
  )}
145
232
  >
146
- <Show when={variant() === 'success'}>
147
- <Check class="size-4 shrink-0 text-emerald-500" />
148
- </Show>
149
- <span class="min-w-0 flex-1 truncate">{props.item.message}</span>
150
- <Show when={props.item.action}>
151
- {(action) => (
152
- <button
153
- type="button"
154
- onClick={act}
155
- class="text-primary hover:text-primary/80 shrink-0 rounded-md text-sm font-medium underline-offset-2 hover:underline"
156
- >
157
- {action().label}
158
- </button>
159
- )}
160
- </Show>
161
- <Show when={dismissible()}>
162
- <button
163
- type="button"
164
- onClick={close}
165
- aria-label="Dismiss"
166
- class="text-muted-foreground hover:text-foreground -mr-1 flex size-5 shrink-0 items-center justify-center rounded-md transition-colors"
167
- >
168
- <X class="size-3.5" />
169
- </button>
233
+ <VariantIcon />
234
+ <Show
235
+ when={appearance() === 'card'}
236
+ fallback={
237
+ <>
238
+ <span class="min-w-0 flex-1 truncate">{props.item.message}</span>
239
+ <ActionButton />
240
+ <CloseButton />
241
+ </>
242
+ }
243
+ >
244
+ <div class="min-w-0 flex-1">
245
+ <div class="font-medium leading-snug">{props.item.message}</div>
246
+ <Show when={props.item.description}>
247
+ <div class={cn('mt-1 line-clamp-2 text-xs leading-snug', mutedClass())}>
248
+ {props.item.description}
249
+ </div>
250
+ </Show>
251
+ <ActionButton class="mt-2" />
252
+ </div>
253
+ <CloseButton />
170
254
  </Show>
171
255
  </div>
172
256
  </Show>
@@ -333,7 +417,7 @@ export function ToastRegion(props: ToastRegionProps) {
333
417
  'pointer-events-none fixed z-[100] flex flex-col gap-2',
334
418
  anchor()
335
419
  ? ANCHOR_FLEX[position()]
336
- : cn('max-w-[min(28rem,calc(100vw-2rem))]', POSITION_CLASSES[position()]),
420
+ : cn('max-w-[min(30rem,calc(100vw-2rem))]', POSITION_CLASSES[position()]),
337
421
  )}
338
422
  style={anchor() ? anchorStyle(position(), anchor()!) : undefined}
339
423
  >
@@ -341,6 +425,8 @@ export function ToastRegion(props: ToastRegionProps) {
341
425
  {(item) => (
342
426
  <Toast
343
427
  item={item}
428
+ appearance={props.appearance}
429
+ inverse={props.inverse}
344
430
  onDismiss={(reason) => props.onDismiss?.(item.id, reason)}
345
431
  onAction={(label) => props.onAction?.(item.id, label)}
346
432
  />
@@ -368,7 +454,7 @@ export function ToastRegion(props: ToastRegionProps) {
368
454
  anchored edge. */}
369
455
  <div
370
456
  class="pointer-events-auto relative transition-[height] duration-200 ease-out"
371
- style={{ 'min-width': '16rem', 'max-width': 'min(28rem, calc(100vw - 2rem))', height: `${stageHeight()}px` }}
457
+ style={{ 'min-width': '16rem', 'max-width': 'min(30rem, calc(100vw - 2rem))', height: `${stageHeight()}px` }}
372
458
  >
373
459
  <For each={visible()}>
374
460
  {(item, i) => (
@@ -385,6 +471,8 @@ export function ToastRegion(props: ToastRegionProps) {
385
471
  <Toast
386
472
  item={item}
387
473
  paused={open()}
474
+ appearance={props.appearance}
475
+ inverse={props.inverse}
388
476
  onDismiss={(reason) => props.onDismiss?.(item.id, reason)}
389
477
  onAction={(label) => props.onAction?.(item.id, label)}
390
478
  />
@@ -0,0 +1,10 @@
1
+ /** A tool-call part rendered by <Tool>. Pure type — kept JSX-free so it can be
2
+ * imported by the framework-neutral state core and the React typecheck pass. */
3
+ export interface ToolPart {
4
+ type: string;
5
+ state: 'input-streaming' | 'input-available' | 'output-available' | 'output-error';
6
+ input?: Record<string, unknown>;
7
+ output?: Record<string, unknown>;
8
+ toolCallId?: string;
9
+ errorText?: string;
10
+ }
@@ -1,21 +1,19 @@
1
1
  import { type JSX, splitProps, createSignal, Show, For } from 'solid-js';
2
2
  import { cn } from '../utils/cn';
3
- import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '../ui/collapsible';
3
+ import { Collapsible, CollapsibleTrigger, CollapsibleContent, type CollapsibleController } from '../ui/collapsible';
4
4
  import { Button } from '../ui/button';
5
5
  import { CheckCircle, ChevronDown, Loader2, Settings, XCircle } from 'lucide-solid';
6
-
7
- export interface ToolPart {
8
- type: string;
9
- state: 'input-streaming' | 'input-available' | 'output-available' | 'output-error';
10
- input?: Record<string, unknown>;
11
- output?: Record<string, unknown>;
12
- toolCallId?: string;
13
- errorText?: string;
14
- }
6
+ import type { ToolPart } from './tool-types';
7
+ export type { ToolPart } from './tool-types';
15
8
 
16
9
  export interface ToolProps {
17
10
  toolPart: ToolPart;
18
11
  defaultOpen?: boolean;
12
+ /** Gate the disclosure trigger (programmatic control still works). */
13
+ disabled?: boolean;
14
+ /** Receive the open controller once mounted (forwarded from the Collapsible),
15
+ * so a parent (the kai-tool facade) can drive/observe open state. */
16
+ controllerRef?: (api: CollapsibleController) => void;
19
17
  class?: string;
20
18
  }
21
19
 
@@ -101,16 +99,24 @@ function ToolStateBadge(props: { state: ToolPart['state'] }) {
101
99
  }
102
100
 
103
101
  function Tool(props: ToolProps) {
104
- const [local, rest] = splitProps(props, ['toolPart', 'defaultOpen', 'class']);
105
- const [isOpen, setIsOpen] = createSignal(local.defaultOpen ?? false);
102
+ const [local, rest] = splitProps(props, ['toolPart', 'defaultOpen', 'disabled', 'controllerRef', 'class']);
103
+ // Drive the Collapsible UNCONTROLLED (seed from defaultOpen) and read its open
104
+ // state via the controller it hands up — that same controller is forwarded to
105
+ // the facade so wireDisclosure can drive show/hide/toggle.
106
+ const [api, setApi] = createSignal<CollapsibleController>();
107
+ const isOpen = () => api()?.open() ?? (local.defaultOpen ?? false);
106
108
 
107
109
  const state = () => local.toolPart.state;
108
110
  const input = () => local.toolPart.input;
109
111
  const output = () => local.toolPart.output;
110
112
 
111
113
  return (
112
- <div class={cn('mt-3 overflow-hidden rounded-lg bg-muted/30', local.class)}>
113
- <Collapsible open={isOpen()} onOpenChange={setIsOpen}>
114
+ <div class={cn('mt-3 overflow-hidden rounded-lg bg-surface', local.class)}>
115
+ <Collapsible
116
+ defaultOpen={local.defaultOpen}
117
+ disabled={local.disabled}
118
+ controllerRef={(a) => { setApi(a); local.controllerRef?.(a); }}
119
+ >
114
120
  <CollapsibleTrigger
115
121
  as={(triggerProps: JSX.HTMLAttributes<HTMLButtonElement>) => (
116
122
  <Button
@@ -134,7 +140,7 @@ function Tool(props: ToolProps) {
134
140
  <Show when={input() && Object.keys(input()!).length > 0}>
135
141
  <div>
136
142
  <h4 class="text-muted-foreground mb-2 text-sm font-medium">Input</h4>
137
- <div class="rounded bg-muted/50 p-2 font-mono text-sm">
143
+ <div class="rounded bg-surface-strong p-2 font-mono text-sm">
138
144
  <For each={Object.entries(input()!)}>
139
145
  {([key, value]) => (
140
146
  <div class="mb-1">
@@ -150,7 +156,7 @@ function Tool(props: ToolProps) {
150
156
  <Show when={output()}>
151
157
  <div>
152
158
  <h4 class="text-muted-foreground mb-2 text-sm font-medium">Output</h4>
153
- <div tabindex={0} class="max-h-60 overflow-auto rounded bg-muted/50 p-2 font-mono text-sm">
159
+ <div tabindex={0} class="max-h-60 overflow-auto rounded bg-surface-strong p-2 font-mono text-sm">
154
160
  <pre class="whitespace-pre-wrap">{formatValue(output())}</pre>
155
161
  </div>
156
162
  </div>
@@ -1,43 +1,126 @@
1
- import { splitProps, Show, For, createSignal } from 'solid-js';
1
+ import { splitProps, Show, For, createSignal, createEffect, on } from 'solid-js';
2
2
  import { cn } from '../utils/cn';
3
3
  import { Button } from '../ui/button';
4
4
  import { Tooltip } from '../ui/tooltip';
5
5
  import { useVoiceRecorder } from '../primitives/use-voice-recorder';
6
+ import { useSpeechRecognition } from '../primitives/use-speech-recognition';
7
+
8
+ /** Imperative handle exposed via `controllerRef` — surfaces the recorder's latent
9
+ * start/stop so the `<kai-voice-input>` facade can forward them as instance
10
+ * methods (push-to-talk). Both run the SAME getUserMedia → blob → transcription
11
+ * path as clicking the mic, so manual + programmatic emit identically. */
12
+ export interface VoiceInputController {
13
+ /** Begin recording programmatically (same path as clicking the mic). */
14
+ start(): void;
15
+ /** Stop the in-progress recording (produces the blob → onTranscribe). */
16
+ stop(): void;
17
+ }
6
18
 
7
19
  export interface VoiceInputProps {
8
20
  onTranscribe: (audio: Blob) => Promise<string>;
9
21
  onTranscription: (text: string) => void;
10
22
  disabled?: boolean;
11
23
  class?: string;
24
+ /** Host supplied a `transcribe` callback. When true the MediaRecorder →
25
+ * onTranscribe path runs. When false the component prefers native
26
+ * SpeechRecognition (capable browsers), falling back to record-only. */
27
+ hasTranscribe?: boolean;
28
+ /** BCP-47 language tag for native recognition (e.g. `en-US`). */
29
+ lang?: string;
30
+ /** Emit live partial transcripts via `onInterim` (native path only). */
31
+ interim?: boolean;
32
+ /** Live partial transcript from native recognition (when `interim`). */
33
+ onInterim?: (text: string) => void;
34
+ /** Fires whenever recording starts or stops. Guarded against the spurious
35
+ * initial `false` — only true transitions emit (the facade maps this to
36
+ * kai-recording-change). */
37
+ onRecordingChange?: (recording: boolean) => void;
38
+ /** Receive the imperative controller once mounted. The `<kai-voice-input>`
39
+ * facade forwards these as element methods (start/stop). */
40
+ controllerRef?: (controller: VoiceInputController) => void;
12
41
  }
13
42
 
14
43
  export function VoiceInput(props: VoiceInputProps) {
15
44
  const [local] = splitProps(props, ['onTranscribe', 'onTranscription', 'disabled', 'class']);
16
45
  const { isRecording, start, stop } = useVoiceRecorder();
46
+ const speech = useSpeechRecognition({ interim: props.interim });
17
47
  const [isProcessing, setIsProcessing] = createSignal(false);
18
48
 
49
+ // Path selection (§6): a `transcribe` callback always wins (MediaRecorder →
50
+ // host transcriber). Otherwise prefer native SpeechRecognition when the browser
51
+ // supports it. The unsupported-no-callback case falls through to recording the
52
+ // blob (→ onTranscribe → kai-audio-captured) with no text.
53
+ const useNative = () => !props.hasTranscribe && speech.isSupported;
54
+
19
55
  const label = () =>
20
- isProcessing() ? 'Transcribing...' : isRecording() ? 'Stop recording' : 'Voice input';
56
+ isProcessing() ? 'Transcribing...' : isActive() ? 'Stop recording' : 'Voice input';
21
57
 
22
- async function handleClick() {
23
- if (isRecording()) {
24
- stop();
25
- } else {
58
+ const isActive = () => (useNative() ? speech.isListening() : isRecording());
59
+
60
+ // Native path: SpeechRecognition resolves with the final transcript when it
61
+ // ends (click again or controller.stop()). Final text → onTranscription.
62
+ async function beginRecognition() {
63
+ try {
64
+ const text = await speech.start({
65
+ lang: props.lang,
66
+ onInterim: props.interim ? (t) => props.onInterim?.(t) : undefined,
67
+ });
68
+ if (text.trim()) local.onTranscription(text.trim());
69
+ } catch {
70
+ /* surfaced via speech.error; the mic returns to idle */
71
+ }
72
+ }
73
+
74
+ // Begin recording, then run transcription once the blob resolves. Shared by the
75
+ // mic click and the imperative start() so both flows emit identically. start()
76
+ // resolves its Blob when the recorder stops (click again or controller.stop()).
77
+ async function beginRecording() {
78
+ try {
79
+ const blob = await start();
80
+ setIsProcessing(true);
26
81
  try {
27
- const blob = await start();
28
- setIsProcessing(true);
29
- try {
30
- const text = await local.onTranscribe(blob);
31
- if (text.trim()) local.onTranscription(text.trim());
32
- } finally {
33
- setIsProcessing(false);
34
- }
35
- } catch {
82
+ const text = await local.onTranscribe(blob);
83
+ if (text.trim()) local.onTranscription(text.trim());
84
+ } finally {
36
85
  setIsProcessing(false);
37
86
  }
87
+ } catch {
88
+ setIsProcessing(false);
38
89
  }
39
90
  }
40
91
 
92
+ // Drive whichever path is active (§6: start/stop must follow the live path).
93
+ function begin() {
94
+ if (useNative()) void beginRecognition();
95
+ else void beginRecording();
96
+ }
97
+ function end() {
98
+ if (useNative()) speech.stop();
99
+ else stop();
100
+ }
101
+
102
+ async function handleClick() {
103
+ if (isActive()) end();
104
+ else begin();
105
+ }
106
+
107
+ // Emit recording transitions to the host. `on(..., { defer: true })` skips the
108
+ // initial mount value (a spurious {recording:false}) — only real start/stop
109
+ // transitions fire. Drives BOTH manual (click) and programmatic (start/stop)
110
+ // since they all flip the active path's signal (MediaRecorder or recognition).
111
+ createEffect(on(isActive, (recording) => {
112
+ props.onRecordingChange?.(recording);
113
+ }, { defer: true }));
114
+
115
+ // Imperative controller (Pattern C): hand the facade a start/stop handle.
116
+ // start() runs the active path; stop() ends the in-progress session (resolving
117
+ // the active path's promise). Guard start() so a double-start while already
118
+ // active is a no-op (matches the click toggle).
119
+ props.controllerRef?.({
120
+ start: () => { if (!isActive()) begin(); },
121
+ stop: () => end(),
122
+ });
123
+
41
124
  // Mic icon (not recording)
42
125
  const MicIcon = () => (
43
126
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -65,7 +148,7 @@ export function VoiceInput(props: VoiceInputProps) {
65
148
  return (
66
149
  <div class={cn('relative inline-flex items-center justify-center', local.class)}>
67
150
  {/* Animated pulse rings when recording */}
68
- <Show when={isRecording()}>
151
+ <Show when={isActive()}>
69
152
  <For each={[0, 1, 2]}>
70
153
  {(index) => (
71
154
  <div
@@ -88,16 +171,16 @@ export function VoiceInput(props: VoiceInputProps) {
88
171
  disabled={local.disabled || isProcessing()}
89
172
  class={cn(
90
173
  'relative z-10 rounded-full transition-all duration-300',
91
- isRecording() && 'bg-destructive text-white hover:bg-destructive/80',
174
+ isActive() && 'bg-destructive text-white hover:bg-destructive/80',
92
175
  )}
93
176
  >
94
177
  <Show when={isProcessing()}>
95
178
  <Spinner />
96
179
  </Show>
97
- <Show when={!isProcessing() && isRecording()}>
180
+ <Show when={!isProcessing() && isActive()}>
98
181
  <StopIcon />
99
182
  </Show>
100
- <Show when={!isProcessing() && !isRecording()}>
183
+ <Show when={!isProcessing() && !isActive()}>
101
184
  <MicIcon />
102
185
  </Show>
103
186
  </Button>