@kitn.ai/ui 0.15.0 → 0.16.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 (366) hide show
  1. package/README.md +9 -0
  2. package/bin/mcp.js +22 -0
  3. package/dist/bash-InADTalH.js +6 -1
  4. package/dist/components/artifact.d.ts +58 -0
  5. package/dist/components/attachments.d.ts +67 -0
  6. package/dist/components/card-fallback.d.ts +10 -0
  7. package/dist/components/card-renderer.d.ts +13 -0
  8. package/dist/components/card.d.ts +28 -0
  9. package/dist/components/chain-of-thought.d.ts +29 -0
  10. package/dist/components/chat-container.d.ts +19 -0
  11. package/dist/components/chat-scope-picker.d.ts +9 -0
  12. package/dist/components/chat-thread.d.ts +93 -0
  13. package/dist/components/checkpoint.d.ts +20 -0
  14. package/dist/components/choice-card.d.ts +77 -0
  15. package/dist/components/code-block.d.ts +16 -0
  16. package/dist/components/confirm-card.d.ts +57 -0
  17. package/dist/components/context.d.ts +65 -0
  18. package/dist/components/conversation-item.d.ts +8 -0
  19. package/dist/components/conversation-list.d.ts +11 -0
  20. package/dist/components/embed.d.ts +21 -0
  21. package/dist/components/empty.d.ts +43 -0
  22. package/dist/components/feedback-bar.d.ts +45 -0
  23. package/dist/components/file-tree.d.ts +57 -0
  24. package/dist/components/file-upload.d.ts +16 -0
  25. package/dist/components/form-widgets.d.ts +31 -0
  26. package/dist/components/form.d.ts +103 -0
  27. package/dist/components/image.d.ts +12 -0
  28. package/dist/components/link-preview.d.ts +23 -0
  29. package/dist/components/loader.d.ts +60 -0
  30. package/dist/components/markdown.d.ts +8 -0
  31. package/dist/components/message-skills.d.ts +15 -0
  32. package/dist/components/message.d.ts +54 -0
  33. package/dist/components/model-switcher.d.ts +8 -0
  34. package/dist/components/prompt-input.d.ts +38 -0
  35. package/dist/components/prompt-suggestion.d.ts +13 -0
  36. package/dist/components/reasoning.d.ts +20 -0
  37. package/dist/components/response-stream.d.ts +14 -0
  38. package/dist/components/scroll-button.d.ts +7 -0
  39. package/dist/components/slash-command.d.ts +15 -0
  40. package/dist/components/source.d.ts +23 -0
  41. package/dist/components/tasks-card.d.ts +71 -0
  42. package/dist/components/text-shimmer.d.ts +9 -0
  43. package/dist/components/thinking-bar.d.ts +10 -0
  44. package/dist/components/tool.d.ts +16 -0
  45. package/dist/components/use-card-resolution.d.ts +12 -0
  46. package/dist/components/voice-input.d.ts +7 -0
  47. package/dist/core-AYMC6_lb.js +5874 -12
  48. package/dist/css-M7EaDHN_.js +6 -1
  49. package/dist/elements/artifact.d.ts +1 -0
  50. package/dist/elements/attachments.d.ts +1 -0
  51. package/dist/elements/card.d.ts +1 -0
  52. package/dist/elements/cards.d.ts +0 -0
  53. package/dist/elements/chain-of-thought.d.ts +13 -0
  54. package/dist/elements/chat-scope-picker.d.ts +1 -0
  55. package/dist/elements/chat-types.d.ts +41 -0
  56. package/dist/elements/chat-workspace.d.ts +1 -0
  57. package/dist/elements/chat.d.ts +1 -0
  58. package/dist/elements/checkpoint.d.ts +1 -0
  59. package/dist/elements/choice.d.ts +1 -0
  60. package/dist/elements/code-block.d.ts +1 -0
  61. package/dist/elements/confirm-card.d.ts +1 -0
  62. package/dist/elements/context-meter.d.ts +1 -0
  63. package/dist/elements/conversation-list.d.ts +11 -0
  64. package/dist/elements/css.d.ts +1 -0
  65. package/dist/elements/default-input.d.ts +40 -0
  66. package/dist/elements/define.d.ts +42 -0
  67. package/dist/elements/embed.d.ts +1 -0
  68. package/dist/elements/empty.d.ts +1 -0
  69. package/dist/elements/feedback-bar.d.ts +1 -0
  70. package/dist/elements/file-tree.d.ts +1 -0
  71. package/dist/elements/file-upload.d.ts +1 -0
  72. package/dist/elements/form.d.ts +1 -0
  73. package/dist/elements/image.d.ts +1 -0
  74. package/dist/elements/link-preview.d.ts +1 -0
  75. package/dist/elements/loader.d.ts +1 -0
  76. package/dist/elements/markdown.d.ts +1 -0
  77. package/dist/elements/message-skills.d.ts +17 -0
  78. package/dist/elements/message.d.ts +1 -0
  79. package/dist/elements/model-switcher.d.ts +11 -0
  80. package/dist/elements/popover.d.ts +1 -0
  81. package/dist/elements/prompt-input.d.ts +9 -0
  82. package/dist/elements/prompt-suggestions.d.ts +7 -0
  83. package/dist/elements/reasoning.d.ts +1 -0
  84. package/dist/elements/register-impl.d.ts +0 -0
  85. package/dist/elements/register.d.ts +3 -0
  86. package/dist/elements/remote.d.ts +1 -0
  87. package/dist/elements/resizable.d.ts +17 -0
  88. package/dist/elements/response-stream.d.ts +1 -0
  89. package/dist/elements/scroll-button.d.ts +1 -0
  90. package/dist/elements/source.d.ts +18 -0
  91. package/dist/elements/switch.d.ts +1 -0
  92. package/dist/elements/tasks.d.ts +1 -0
  93. package/dist/elements/text-shimmer.d.ts +1 -0
  94. package/dist/elements/thinking-bar.d.ts +1 -0
  95. package/dist/elements/tool.d.ts +1 -0
  96. package/dist/elements/voice-input.d.ts +1 -0
  97. package/dist/elements.d.ts +732 -0
  98. package/dist/engine-javascript-vq0WuIJl.js +2516 -14
  99. package/dist/github-dark-dimmed-DUshB20C.js +4 -1
  100. package/dist/github-light-JYsPkUQd.js +4 -1
  101. package/dist/html-CPZ3oZQ7.js +10 -1
  102. package/dist/index.d.ts +112 -2
  103. package/dist/index.js +13551 -0
  104. package/dist/javascript-C25yR2R2.js +6 -1
  105. package/dist/json-DxJze_jm.js +6 -1
  106. package/dist/kitn-chat.es.js +1 -77
  107. package/dist/llms/llms-full.txt +1 -1
  108. package/dist/llms/llms.txt +1 -1
  109. package/dist/mcp.es.js +2140 -0
  110. package/dist/primitives/card-contract.d.ts +103 -0
  111. package/dist/primitives/card-host.d.ts +12 -0
  112. package/dist/primitives/card-registry.d.ts +17 -0
  113. package/dist/primitives/card-resolution.d.ts +8 -0
  114. package/dist/primitives/card-routing.d.ts +12 -0
  115. package/dist/primitives/card-validate.d.ts +25 -0
  116. package/dist/primitives/chat-config.d.ts +31 -0
  117. package/dist/primitives/controllable.d.ts +13 -0
  118. package/dist/primitives/embed-providers.d.ts +65 -0
  119. package/dist/primitives/highlighter.d.ts +23 -0
  120. package/dist/primitives/link-preview.d.ts +47 -0
  121. package/dist/primitives/pdf-preview.d.ts +50 -0
  122. package/dist/primitives/use-auto-resize.d.ts +8 -0
  123. package/dist/primitives/use-resize-observer.d.ts +1 -0
  124. package/dist/primitives/use-stick-to-bottom.d.ts +5 -0
  125. package/dist/primitives/use-text-stream.d.ts +19 -0
  126. package/dist/primitives/use-voice-recorder.d.ts +9 -0
  127. package/dist/react/index.d.ts +1118 -0
  128. package/dist/react/runtime.d.ts +15 -0
  129. package/dist/react.js +265 -0
  130. package/dist/register-impl-wCsFPoh9.js +77 -0
  131. package/dist/register-ujhr1nBS.js +1 -0
  132. package/dist/remote/host-embed.d.ts +33 -0
  133. package/dist/remote/index.d.ts +2 -0
  134. package/dist/remote/origin.d.ts +5 -0
  135. package/dist/remote/provider-runtime.d.ts +27 -0
  136. package/dist/remote/provider.d.ts +2 -0
  137. package/dist/remote/validate.d.ts +4 -0
  138. package/dist/remote/version.d.ts +4 -0
  139. package/dist/remote/wire.d.ts +53 -0
  140. package/dist/svelte--5p79yCD.js +15 -1
  141. package/dist/tsx-B8rCNbgL.js +6 -1
  142. package/dist/types.d.ts +42 -0
  143. package/dist/typescript-RycA9KXf.js +6 -1
  144. package/dist/ui/action-icons.d.ts +11 -0
  145. package/dist/ui/avatar.d.ts +8 -0
  146. package/dist/ui/badge.d.ts +9 -0
  147. package/dist/ui/button.d.ts +10 -0
  148. package/dist/ui/collapsible.d.ts +27 -0
  149. package/dist/ui/dropdown.d.ts +20 -0
  150. package/dist/ui/hover-card.d.ts +28 -0
  151. package/dist/ui/overlay.d.ts +67 -0
  152. package/dist/ui/popover.d.ts +32 -0
  153. package/dist/ui/resizable.d.ts +76 -0
  154. package/dist/ui/scroll-area.d.ts +5 -0
  155. package/dist/ui/separator.d.ts +5 -0
  156. package/dist/ui/skeleton.d.ts +5 -0
  157. package/dist/ui/switch.d.ts +19 -0
  158. package/dist/ui/textarea.d.ts +6 -0
  159. package/dist/ui/tooltip.d.ts +8 -0
  160. package/dist/utils/cn.d.ts +2 -0
  161. package/dist/vue-BmIZj4XD.js +33 -1
  162. package/frameworks/react/index.tsx +4 -0
  163. package/frameworks/react/runtime.tsx +16 -2
  164. package/llms-full.txt +1 -1
  165. package/llms.txt +1 -1
  166. package/package.json +29 -11
  167. package/src/agent-tooling/archetypes.ts +46 -0
  168. package/src/agent-tooling/integrations/cloudflare.ts +88 -0
  169. package/src/agent-tooling/integrations/langgraph.ts +60 -0
  170. package/src/agent-tooling/integrations/mastra.ts +27 -0
  171. package/src/agent-tooling/integrations/mock.ts +34 -0
  172. package/src/agent-tooling/integrations/ollama.ts +47 -0
  173. package/src/agent-tooling/integrations/openrouter.ts +26 -0
  174. package/src/agent-tooling/integrations/pi.ts +40 -0
  175. package/src/agent-tooling/integrations/pydantic-ai.ts +52 -0
  176. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +44 -0
  177. package/src/agent-tooling/mcp/manifest.ts +128 -0
  178. package/src/agent-tooling/mcp/server.ts +59 -0
  179. package/src/agent-tooling/mcp/stdio.ts +18 -0
  180. package/src/agent-tooling/mcp/tools/debug.ts +305 -0
  181. package/src/agent-tooling/mcp/tools/reference.ts +132 -0
  182. package/src/agent-tooling/mcp/tools/scaffold.ts +1627 -0
  183. package/src/agent-tooling/mcp/tools/theme.ts +340 -0
  184. package/src/agent-tooling/mcp/tools/types.ts +14 -0
  185. package/src/agent-tooling/registry.ts +41 -0
  186. package/src/agent-tooling/types.ts +30 -0
  187. package/src/elements/compiled.css +1 -1
  188. package/src/elements/define.tsx +7 -1
  189. package/src/elements/element-types.d.ts +2 -1
  190. package/src/elements/register-impl.ts +55 -0
  191. package/src/elements/register.ts +17 -51
  192. package/theme.css +4 -2
  193. package/src/components/artifact.stories.tsx +0 -138
  194. package/src/components/attachments.stories.tsx +0 -303
  195. package/src/components/attachments.test.tsx +0 -64
  196. package/src/components/chain-of-thought.stories.tsx +0 -211
  197. package/src/components/chat-container.stories.tsx +0 -187
  198. package/src/components/chat-thread.test.tsx +0 -58
  199. package/src/components/checkpoint.stories.tsx +0 -102
  200. package/src/components/code-block.stories.tsx +0 -150
  201. package/src/components/confirm-card.stories.tsx +0 -74
  202. package/src/components/context.stories.tsx +0 -229
  203. package/src/components/context.test.tsx +0 -133
  204. package/src/components/conversation-item.stories.tsx +0 -125
  205. package/src/components/conversation-list.stories.tsx +0 -133
  206. package/src/components/default-input.test.tsx +0 -111
  207. package/src/components/empty.stories.tsx +0 -434
  208. package/src/components/feedback-bar.stories.tsx +0 -138
  209. package/src/components/feedback-bar.test.tsx +0 -103
  210. package/src/components/file-tree.stories.tsx +0 -73
  211. package/src/components/file-upload.stories.tsx +0 -156
  212. package/src/components/image.stories.tsx +0 -89
  213. package/src/components/loader.stories.tsx +0 -181
  214. package/src/components/markdown.stories.tsx +0 -180
  215. package/src/components/message-action-bar.test.tsx +0 -128
  216. package/src/components/message-narrow.stories.tsx +0 -329
  217. package/src/components/message-skills.stories.tsx +0 -211
  218. package/src/components/message.stories.tsx +0 -281
  219. package/src/components/model-switcher.stories.tsx +0 -123
  220. package/src/components/model-switcher.test.tsx +0 -65
  221. package/src/components/prompt-input.stories.tsx +0 -222
  222. package/src/components/prompt-suggestion.stories.tsx +0 -142
  223. package/src/components/reasoning.stories.tsx +0 -140
  224. package/src/components/resizable.test.tsx +0 -226
  225. package/src/components/scroll-button.stories.tsx +0 -100
  226. package/src/components/slash-command.stories.tsx +0 -163
  227. package/src/components/source-list.test.tsx +0 -152
  228. package/src/components/source.stories.tsx +0 -124
  229. package/src/components/tasks-card.stories.tsx +0 -78
  230. package/src/components/text-shimmer.stories.tsx +0 -87
  231. package/src/components/thinking-bar.stories.tsx +0 -87
  232. package/src/components/tool.stories.tsx +0 -153
  233. package/src/components/voice-input.stories.tsx +0 -83
  234. package/src/elements/artifact.stories.tsx +0 -506
  235. package/src/elements/attachments.stories.tsx +0 -190
  236. package/src/elements/card.stories.tsx +0 -113
  237. package/src/elements/cards.stories.tsx +0 -59
  238. package/src/elements/catalog.stories.tsx +0 -491
  239. package/src/elements/chain-of-thought.declarative.test.tsx +0 -153
  240. package/src/elements/chain-of-thought.stories.tsx +0 -113
  241. package/src/elements/chat-scope-picker.stories.tsx +0 -73
  242. package/src/elements/chat-workspace.stories.tsx +0 -288
  243. package/src/elements/chat.stories.tsx +0 -151
  244. package/src/elements/checkpoint.stories.tsx +0 -72
  245. package/src/elements/choice.stories.tsx +0 -240
  246. package/src/elements/code-block.stories.tsx +0 -83
  247. package/src/elements/composed-shell.stories.tsx +0 -316
  248. package/src/elements/confirm-card.stories.tsx +0 -227
  249. package/src/elements/context-meter.stories.tsx +0 -151
  250. package/src/elements/conversation-list.declarative.test.tsx +0 -137
  251. package/src/elements/conversation-list.stories.tsx +0 -243
  252. package/src/elements/embed.stories.tsx +0 -213
  253. package/src/elements/empty.stories.tsx +0 -111
  254. package/src/elements/feedback-bar.stories.tsx +0 -114
  255. package/src/elements/file-tree.stories.tsx +0 -134
  256. package/src/elements/file-upload.stories.tsx +0 -82
  257. package/src/elements/form.stories.tsx +0 -248
  258. package/src/elements/image.stories.tsx +0 -71
  259. package/src/elements/link-preview.stories.tsx +0 -209
  260. package/src/elements/loader.stories.tsx +0 -88
  261. package/src/elements/markdown.stories.tsx +0 -76
  262. package/src/elements/message-skills.declarative.test.tsx +0 -104
  263. package/src/elements/message-skills.stories.tsx +0 -114
  264. package/src/elements/message.stories.tsx +0 -218
  265. package/src/elements/model-switcher.declarative.test.tsx +0 -130
  266. package/src/elements/model-switcher.stories.tsx +0 -198
  267. package/src/elements/popover.stories.tsx +0 -80
  268. package/src/elements/prompt-input-slash-command.test.tsx +0 -128
  269. package/src/elements/prompt-input-stoppable.test.tsx +0 -88
  270. package/src/elements/prompt-input-toolbar-actions.test.tsx +0 -120
  271. package/src/elements/prompt-input.stories.tsx +0 -331
  272. package/src/elements/prompt-suggestions.declarative.test.tsx +0 -164
  273. package/src/elements/prompt-suggestions.stories.tsx +0 -213
  274. package/src/elements/reasoning.stories.tsx +0 -77
  275. package/src/elements/remote.stories.tsx +0 -314
  276. package/src/elements/resizable.stories.tsx +0 -426
  277. package/src/elements/response-stream.stories.tsx +0 -79
  278. package/src/elements/scroll-button.stories.tsx +0 -202
  279. package/src/elements/scroll-button.test.tsx +0 -148
  280. package/src/elements/source-list.stories.tsx +0 -143
  281. package/src/elements/source.stories.tsx +0 -88
  282. package/src/elements/switch.stories.tsx +0 -56
  283. package/src/elements/tasks.stories.tsx +0 -237
  284. package/src/elements/text-shimmer.stories.tsx +0 -64
  285. package/src/elements/thinking-bar.stories.tsx +0 -72
  286. package/src/elements/tool.stories.tsx +0 -89
  287. package/src/elements/voice-input.stories.tsx +0 -88
  288. package/src/primitives/controllable.test.ts +0 -47
  289. package/src/stories/chat-panel-layout.stories.tsx +0 -145
  290. package/src/stories/chat-scene.tsx +0 -571
  291. package/src/stories/checkpoint-restore.stories.tsx +0 -256
  292. package/src/stories/context-usage.stories.tsx +0 -212
  293. package/src/stories/conversation-with-reasoning.stories.tsx +0 -182
  294. package/src/stories/conversation-with-sources.stories.tsx +0 -471
  295. package/src/stories/docs/Accessibility.mdx +0 -119
  296. package/src/stories/docs/ChildElements.mdx +0 -269
  297. package/src/stories/docs/ForAIAgents.mdx +0 -162
  298. package/src/stories/docs/GettingStarted.mdx +0 -65
  299. package/src/stories/docs/Installation.mdx +0 -83
  300. package/src/stories/docs/Introduction.mdx +0 -50
  301. package/src/stories/docs/SolidJsAdvanced.mdx +0 -17
  302. package/src/stories/docs/Theming.mdx +0 -85
  303. package/src/stories/docs/element-controls.ts +0 -121
  304. package/src/stories/docs/frameworks/Angular.mdx +0 -245
  305. package/src/stories/docs/frameworks/Html.mdx +0 -227
  306. package/src/stories/docs/frameworks/Overview.mdx +0 -47
  307. package/src/stories/docs/frameworks/React.mdx +0 -227
  308. package/src/stories/docs/frameworks/Solid.mdx +0 -173
  309. package/src/stories/docs/frameworks/Svelte.mdx +0 -194
  310. package/src/stories/docs/frameworks/Vue.mdx +0 -238
  311. package/src/stories/docs/generative-ui-overview.mdx +0 -186
  312. package/src/stories/docs/recipes/SpeechToText.mdx +0 -7
  313. package/src/stories/docs/recipes/Streaming.mdx +0 -68
  314. package/src/stories/docs/recipes/TextToSpeech.mdx +0 -35
  315. package/src/stories/docs/theme-editor/canvas.tsx +0 -32
  316. package/src/stories/docs/theme-editor/inspector.tsx +0 -66
  317. package/src/stories/docs/theme-editor/presets.test.ts +0 -32
  318. package/src/stories/docs/theme-editor/presets.ts +0 -64
  319. package/src/stories/docs/theme-editor/theme-css.test.ts +0 -19
  320. package/src/stories/docs/theme-editor/theme-css.ts +0 -15
  321. package/src/stories/docs/theme-editor/theme-editor.tsx +0 -146
  322. package/src/stories/docs/theme-tokens.tsx +0 -174
  323. package/src/stories/examples/ChoosingComponents.mdx +0 -106
  324. package/src/stories/examples/sample-data.ts +0 -79
  325. package/src/stories/examples/usage/checkpoint-restore.ts +0 -156
  326. package/src/stories/examples/usage/context-usage.ts +0 -591
  327. package/src/stories/examples/usage/conversation-with-reasoning.ts +0 -224
  328. package/src/stories/examples/usage/conversation-with-sources.ts +0 -589
  329. package/src/stories/examples/usage/empty-state.ts +0 -144
  330. package/src/stories/examples/usage/full-chat-app.ts +0 -277
  331. package/src/stories/examples/usage/index.ts +0 -63
  332. package/src/stories/examples/usage/message-actions.ts +0 -704
  333. package/src/stories/examples/usage/pattern-centered-conversation.ts +0 -446
  334. package/src/stories/examples/usage/pattern-chat-panel-layout.ts +0 -442
  335. package/src/stories/examples/usage/pattern-docked-widget.ts +0 -479
  336. package/src/stories/examples/usage/prompt-input-variants.ts +0 -1356
  337. package/src/stories/examples/usage/streaming-response.ts +0 -687
  338. package/src/stories/examples/usage/types.ts +0 -33
  339. package/src/stories/full-chat.stories.tsx +0 -18
  340. package/src/stories/message-actions.stories.tsx +0 -230
  341. package/src/stories/pattern-centered-conversation.stories.tsx +0 -93
  342. package/src/stories/pattern-docked-widget.stories.tsx +0 -93
  343. package/src/stories/pattern-empty-state.stories.tsx +0 -76
  344. package/src/stories/prompt-input-variants.stories.tsx +0 -456
  345. package/src/stories/streaming-response.stories.tsx +0 -438
  346. package/src/stories/theme-editor.stories.tsx +0 -16
  347. package/src/stories/token-reference.stories.tsx +0 -18
  348. package/src/stories/typography.stories.tsx +0 -78
  349. package/src/ui/avatar.stories.tsx +0 -103
  350. package/src/ui/badge.stories.tsx +0 -86
  351. package/src/ui/button.stories.tsx +0 -145
  352. package/src/ui/collapsible.stories.tsx +0 -69
  353. package/src/ui/dropdown.stories.tsx +0 -59
  354. package/src/ui/hover-card.stories.tsx +0 -77
  355. package/src/ui/hover-card.test.tsx +0 -30
  356. package/src/ui/overlay.stories.tsx +0 -115
  357. package/src/ui/popover.stories.tsx +0 -81
  358. package/src/ui/popover.test.tsx +0 -99
  359. package/src/ui/resizable.stories.tsx +0 -236
  360. package/src/ui/scroll-area.stories.tsx +0 -50
  361. package/src/ui/separator.stories.tsx +0 -81
  362. package/src/ui/skeleton.stories.tsx +0 -337
  363. package/src/ui/switch.stories.tsx +0 -51
  364. package/src/ui/switch.test.tsx +0 -59
  365. package/src/ui/textarea.stories.tsx +0 -76
  366. package/src/ui/tooltip.stories.tsx +0 -74
@@ -1,76 +0,0 @@
1
- import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
- import { onMount } from 'solid-js';
3
- import './register'; // side effect: registers the custom elements
4
- import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
-
6
- // The web components are custom DOM elements, so declare the tags for JSX.
7
- declare module 'solid-js' {
8
- // eslint-disable-next-line @typescript-eslint/no-namespace
9
- namespace JSX {
10
- interface IntrinsicElements {
11
- 'kai-markdown': JSX.HTMLAttributes<HTMLElement>;
12
- }
13
- }
14
- }
15
-
16
- const sampleMarkdown = `### Markdown
17
- Renders **bold**, _italic_, \`code\`, and lists:
18
- - one
19
- - two
20
-
21
- > and blockquotes.
22
-
23
- \`\`\`ts
24
- export function add(a: number, b: number): number {
25
- return a + b;
26
- }
27
- \`\`\``;
28
-
29
- /** Render the actual `<kai-markdown>` custom element with a `content` property. */
30
- function MarkdownElement(props: { content: string }) {
31
- let el: (HTMLElement & { content?: string }) | undefined;
32
- onMount(() => {
33
- if (el) el.content = props.content;
34
- });
35
- return (
36
- <kai-markdown ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px', 'max-width': '720px' }} />
37
- );
38
- }
39
-
40
- const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
41
- <kai-markdown id="md" code-theme="github-dark-dimmed"></kai-markdown>
42
-
43
- <script type="module">
44
- import '@kitn.ai/ui/elements'; // registers the custom elements
45
-
46
- const md = document.getElementById('md');
47
- md.content = '### Hello\\nRenders **bold**, _italic_, and \\\`code\\\`.';
48
- // md.setAttribute('code-highlight', 'false'); // skip Shiki entirely
49
- </script>`;
50
-
51
- const meta = {
52
- title: 'Components/Markdown',
53
- tags: ['autodocs'],
54
- argTypes: argTypesFor('kai-markdown'),
55
- parameters: {
56
- layout: 'fullscreen',
57
- docs: {
58
- description: specDescription('kai-markdown', [
59
- '`<kai-markdown>` is the framework-agnostic **web component** that renders a markdown string (with fenced-code syntax highlighting via Shiki) as a standalone element, isolated in **Shadow DOM**.',
60
- '**When to use:** showing model output or any markdown in a non-Solid app without pulling in a markdown stack. In SolidJS, use the `Markdown` primitive directly.',
61
- '**Placement:** as a block inside a message row, doc viewer, or standalone content pane; it is `display: block` and expands to fill its container width, so constrain width on the parent.',
62
- "**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the source via the `content` **property** (`el.content = '...'`), and tune rendering with the `prose-size`, `code-theme`, and `code-highlight` attributes.",
63
- 'See the **Code** tab for HTML usage.',
64
- ]),
65
- },
66
- },
67
- } satisfies Meta;
68
-
69
- export default meta;
70
- type Story = StoryObj;
71
-
72
- /** Headings, emphasis, lists, a blockquote, and a highlighted code fence. */
73
- export const Default: Story = {
74
- render: () => <MarkdownElement content={sampleMarkdown} />,
75
- parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
76
- };
@@ -1,104 +0,0 @@
1
- /**
2
- * Unit tests for the declarative `<kai-skill>` light-DOM API of `<kai-skills>`.
3
- *
4
- * Strategy: the `defineWebComponent` call registers a real Shadow-DOM custom
5
- * element which requires a full browser environment (Constructable Stylesheets,
6
- * shadow roots, etc.) and is therefore not suitable for jsdom unit tests.
7
- * Instead we:
8
- * 1. Test the exported `parseKaiSkillElement` helper in isolation — it has no
9
- * DOM dependencies beyond `Element`, which jsdom provides perfectly.
10
- * 2. Test that the merged list of prop + slotted items renders badge spans
11
- * correctly via `MessageSkills` directly, mirroring the pattern used in
12
- * `prompt-suggestions.declarative.test.tsx`.
13
- */
14
- import { describe, it, expect, afterEach } from 'vitest';
15
- import '@testing-library/jest-dom/vitest';
16
- import { render, cleanup } from '@solidjs/testing-library';
17
- import { For } from 'solid-js';
18
- import { parseKaiSkillElement } from './message-skills';
19
- import { MessageSkills } from '../components/message-skills';
20
-
21
- afterEach(cleanup);
22
-
23
- // ---------------------------------------------------------------------------
24
- // parseKaiSkillElement — pure helper
25
- // ---------------------------------------------------------------------------
26
-
27
- describe('parseKaiSkillElement', () => {
28
- function makeNode(textContent: string, id?: string): Element {
29
- const el = document.createElement('kai-skill');
30
- el.textContent = textContent;
31
- if (id !== undefined) el.setAttribute('id', id);
32
- return el;
33
- }
34
-
35
- it('uses textContent as name', () => {
36
- const item = parseKaiSkillElement(makeNode('Web Search'));
37
- expect(item).toMatchObject({ name: 'Web Search' });
38
- });
39
-
40
- it('uses id attribute as id when present', () => {
41
- const item = parseKaiSkillElement(makeNode('Web Search', 'web-search'));
42
- expect(item).toMatchObject({ id: 'web-search', name: 'Web Search' });
43
- });
44
-
45
- it('falls back to textContent as id when id attribute is absent', () => {
46
- const item = parseKaiSkillElement(makeNode('Web Search'));
47
- expect(item).toMatchObject({ id: 'Web Search', name: 'Web Search' });
48
- });
49
-
50
- it('trims leading/trailing whitespace from textContent', () => {
51
- const item = parseKaiSkillElement(makeNode(' Code Interpreter '));
52
- expect(item).toMatchObject({ name: 'Code Interpreter' });
53
- });
54
-
55
- it('handles empty textContent with an id attribute', () => {
56
- const item = parseKaiSkillElement(makeNode('', 'code'));
57
- expect(item).toMatchObject({ id: 'code', name: '' });
58
- });
59
- });
60
-
61
- // ---------------------------------------------------------------------------
62
- // Merge + render: prop skills + slotted skills combine correctly
63
- // ---------------------------------------------------------------------------
64
-
65
- interface Skill {
66
- id: string;
67
- name: string;
68
- }
69
-
70
- describe('MessageSkills rendering with merged skills', () => {
71
- it('renders one badge span per skill', () => {
72
- const { getByText } = render(() => (
73
- <MessageSkills skills={[{ id: 'web-search', name: 'Web Search' }, { id: 'code', name: 'Code' }]} />
74
- ));
75
- expect(getByText('Web Search')).toBeInTheDocument();
76
- expect(getByText('Code')).toBeInTheDocument();
77
- });
78
-
79
- it('renders nothing when skills is empty', () => {
80
- const { container } = render(() => <MessageSkills skills={[]} />);
81
- expect(container.firstChild).toBeNull();
82
- });
83
-
84
- it('renders prop items before declarative (slotted) items', () => {
85
- // Simulate the merge: prop first, slotted second.
86
- const propSkills: Skill[] = [{ id: 'prop', name: 'Prop Skill' }];
87
- const slottedSkills: Skill[] = [{ id: 'slotted', name: 'Slotted Skill' }];
88
- const merged = [...propSkills, ...slottedSkills];
89
-
90
- const { getAllByText } = render(() => <MessageSkills skills={merged} />);
91
- // Both must be present; order is enforced by DOM order
92
- expect(getAllByText(/Prop Skill|Slotted Skill/)).toHaveLength(2);
93
- });
94
-
95
- it('parseKaiSkillElement produces items that render correctly', () => {
96
- const el = document.createElement('kai-skill');
97
- el.textContent = 'Memory';
98
- el.setAttribute('id', 'memory');
99
-
100
- const parsed = parseKaiSkillElement(el);
101
- const { getByText } = render(() => <MessageSkills skills={[parsed]} />);
102
- expect(getByText('Memory')).toBeInTheDocument();
103
- });
104
- });
@@ -1,114 +0,0 @@
1
- import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
- import { onMount } from 'solid-js';
3
- import './register'; // side effect: registers the custom elements
4
- import { argTypesFor, specDescription } from '../stories/docs/element-controls';
5
-
6
- // The web components are custom DOM elements, so declare the tags for JSX.
7
- declare module 'solid-js' {
8
- // eslint-disable-next-line @typescript-eslint/no-namespace
9
- namespace JSX {
10
- interface IntrinsicElements {
11
- 'kai-skills': JSX.HTMLAttributes<HTMLElement>;
12
- /** Light-DOM data carrier for declarative skill badges inside `<kai-skills>`. */
13
- 'kai-skill': JSX.HTMLAttributes<HTMLElement> & {
14
- id?: string;
15
- };
16
- }
17
- }
18
- }
19
-
20
- interface Skill {
21
- id: string;
22
- name: string;
23
- }
24
-
25
- const sampleSkills: Skill[] = [
26
- { id: 's1', name: 'web-search' },
27
- { id: 's2', name: 'code' },
28
- ];
29
-
30
- /** Render the actual `<kai-skills>` custom element with a `skills` property. */
31
- function MessageSkillsElement(props: { skills: Skill[] }) {
32
- let el: (HTMLElement & { skills?: Skill[] }) | undefined;
33
- onMount(() => {
34
- if (el) el.skills = props.skills;
35
- });
36
- return (
37
- <kai-skills ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px' }} />
38
- );
39
- }
40
-
41
- const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
42
- <kai-skills id="skills"></kai-skills>
43
-
44
- <script type="module">
45
- import '@kitn.ai/ui/elements'; // registers the custom elements
46
-
47
- const skills = document.getElementById('skills');
48
- skills.skills = [
49
- { id: 's1', name: 'web-search' },
50
- { id: 's2', name: 'code' },
51
- ];
52
- </script>`;
53
-
54
- const meta = {
55
- title: 'Components/Skills',
56
- tags: ['autodocs'],
57
- argTypes: argTypesFor('kai-skills'),
58
- parameters: {
59
- layout: 'fullscreen',
60
- docs: {
61
- description: specDescription('kai-skills', [
62
- '`<kai-skills>` is the framework-agnostic **web component** that badges which skills were active for a message, isolated in **Shadow DOM**.',
63
- '**When to use:** annotating a message row with the skills/tools it used, in a non-Solid app. In SolidJS, use the `MessageSkills` primitive directly.',
64
- '**Placement:** beneath or inline with the message content, typically in the footer of a message row alongside action buttons; it is `display: block` and wraps badges horizontally.',
65
- "**How to use:** register once with `import '@kitn.ai/ui/elements'`. Then either (a) set the `skills` **property** to an array of `{ id, name }` objects, or (b) compose `<kai-skill>` child elements — light-DOM data carriers hidden by the Shadow DOM — where the tag's text content is the badge label and the optional `id` attribute is the stable identifier. When both are provided, prop items render first and declarative children are appended after.",
66
- 'See the **Code** tab for HTML usage.',
67
- ]),
68
- },
69
- },
70
- } satisfies Meta;
71
-
72
- export default meta;
73
- type Story = StoryObj;
74
-
75
- /** Two active-skill badges set via the `skills` JS property. */
76
- export const Default: Story = {
77
- render: () => <MessageSkillsElement skills={sampleSkills} />,
78
- parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
79
- };
80
-
81
- const DECLARATIVE_HTML_SNIPPET = `<!-- Works in any framework or plain HTML — no JS property assignment needed -->
82
- <kai-skills>
83
- <kai-skill id="web-search">Web Search</kai-skill>
84
- <kai-skill id="code">Code</kai-skill>
85
- <kai-skill id="memory">Memory</kai-skill>
86
- </kai-skills>
87
-
88
- <script type="module">
89
- import '@kitn.ai/ui/elements'; // registers the custom elements
90
- </script>`;
91
-
92
- /**
93
- * Declare each skill badge as a `<kai-skill>` child element — no `skills`
94
- * property or JS array wiring needed. The tag's text content is the badge
95
- * label; the optional `id` attribute is the stable identifier (falls back to
96
- * the label when absent). Children are light-DOM data carriers hidden by the
97
- * Shadow DOM — pure data, no visible output of their own.
98
- * Mix with the `skills` prop: prop items render first, declarative children after.
99
- */
100
- export const DeclarativeSkills: Story = {
101
- name: 'Declarative Skills (kai-skill)',
102
- render: () => (
103
- <kai-skills style={{ display: 'block', padding: '16px' }}>
104
- <kai-skill id="web-search">Web Search</kai-skill>
105
- <kai-skill id="code">Code</kai-skill>
106
- <kai-skill id="memory">Memory</kai-skill>
107
- </kai-skills>
108
- ),
109
- parameters: {
110
- docs: {
111
- source: { code: DECLARATIVE_HTML_SNIPPET, language: 'html' },
112
- },
113
- },
114
- };
@@ -1,218 +0,0 @@
1
- import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
- import { onMount } from 'solid-js';
3
- import './register'; // side effect: registers the custom elements
4
- import type { ChatMessage } from './chat-types';
5
- import { argTypesFor, specDescription } from '../stories/docs/element-controls';
6
-
7
- // The web components are custom DOM elements, so declare the tags for JSX.
8
- declare module 'solid-js' {
9
- // eslint-disable-next-line @typescript-eslint/no-namespace
10
- namespace JSX {
11
- interface IntrinsicElements {
12
- 'kai-message': JSX.HTMLAttributes<HTMLElement>;
13
- 'kai-action': JSX.HTMLAttributes<HTMLElement> & { icon?: string; tooltip?: string };
14
- }
15
- }
16
- }
17
-
18
- const assistantMessage: ChatMessage = {
19
- id: 'm-a',
20
- role: 'assistant',
21
- content:
22
- "Here's the plan, with a quick code sample:\n\n```js\nconst kit = useChat();\n```\n\nThat wires the kit into your component.",
23
- reasoning: { text: 'The user wants X, so I should do Y then Z.', label: 'Reasoning' },
24
- tools: [{ type: 'search', state: 'output-available', input: { query: 'kitn docs' }, output: { hits: 3 } }],
25
- attachments: [
26
- {
27
- id: '1',
28
- type: 'file',
29
- filename: 'architecture.png',
30
- mediaType: 'image/png',
31
- url:
32
- 'data:image/svg+xml;utf8,' +
33
- encodeURIComponent(
34
- '<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96"><rect width="96" height="96" rx="12" fill="#7c3aed"/><text x="48" y="60" font-size="42" text-anchor="middle" fill="white">◆</text></svg>',
35
- ),
36
- },
37
- ],
38
- actions: ['copy', 'like', 'dislike', 'regenerate'],
39
- };
40
-
41
- const userMessage: ChatMessage = {
42
- id: 'm-u',
43
- role: 'user',
44
- content: 'How do I compose these myself?',
45
- };
46
-
47
- const AVATAR_SVG =
48
- 'data:image/svg+xml;utf8,' +
49
- encodeURIComponent(
50
- '<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><rect width="64" height="64" fill="#10b981"/><text x="32" y="42" font-size="28" text-anchor="middle" fill="white">K</text></svg>',
51
- );
52
-
53
- /** An assistant message with an avatar (from `message.avatar`) plus custom + built-in actions. */
54
- const avatarMessage: ChatMessage = {
55
- id: 'm-av',
56
- role: 'assistant',
57
- content: 'I have an avatar, a custom **Share** action, and the built-in copy/like actions.',
58
- avatar: { src: AVATAR_SVG, alt: 'Kitn', fallback: 'K' },
59
- actions: ['copy', 'like', { id: 'share', label: 'Share', icon: 'share' }],
60
- };
61
-
62
- /** Render the actual `<kai-message>` custom element with a `message` property and optional attrs. */
63
- function MessageElement(props: { message: ChatMessage; actionsReveal?: 'always' | 'hover' }) {
64
- let el: (HTMLElement & { message?: ChatMessage }) | undefined;
65
- onMount(() => {
66
- if (!el) return;
67
- if (props.actionsReveal) el.setAttribute('actions-reveal', props.actionsReveal);
68
- el.message = props.message;
69
- el.addEventListener('kai-message-action', (e) =>
70
- console.log('kai-message-action', (e as CustomEvent).detail.action),
71
- );
72
- });
73
- return (
74
- <kai-message ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px', 'max-width': '720px' }} />
75
- );
76
- }
77
-
78
- const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
79
- <kai-message id="msg" style="display:block;"></kai-message>
80
-
81
- <script type="module">
82
- import '@kitn.ai/ui/elements'; // registers the custom elements
83
-
84
- const msg = document.getElementById('msg');
85
- msg.message = {
86
- id: 'm-a', role: 'assistant',
87
- content: "Here's the plan:\\n\\n\\\`\\\`\\\`js\\nconst kit = useChat();\\n\\\`\\\`\\\`",
88
- reasoning: { text: 'The user wants X, so I should do Y.' },
89
- tools: [{ type: 'search', state: 'output-available', output: { hits: 3 } }],
90
- actions: ['copy', 'like', 'dislike', 'regenerate'],
91
- };
92
-
93
- // events are CustomEvents on the element (they do not bubble)
94
- msg.addEventListener('kai-message-action', (e) => console.log(e.detail.action, e.detail.messageId));
95
- </script>`;
96
-
97
- const meta = {
98
- title: 'Components/Message',
99
- tags: ['autodocs'],
100
- argTypes: argTypesFor('kai-message'),
101
- parameters: {
102
- layout: 'fullscreen',
103
- docs: {
104
- description: specDescription('kai-message', [
105
- '`<kai-message>` is the framework-agnostic **web component** for a single message row — markdown/plain content, an optional reasoning block, tool calls, attachments, and action buttons — all rendered from one `message` object (the same shape `<kai-chat>` uses per message). It is the keystone of the "compose your own message list" pattern, isolated in **Shadow DOM**.',
106
- "**When to use:** building a custom message thread in a non-Solid app, or anywhere you want to lay out the list yourself but keep the kit's rich message rendering. In SolidJS, compose the `Message` primitives for finer control.",
107
- "**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the whole row via the `message` **property** (`el.message = {...}`), and listen for the `kai-message-action` **CustomEvent** for action-button clicks. For simple cases, set `role` + `content` attributes instead of a full object.",
108
- 'See the **Code** tab for HTML usage.',
109
- ]),
110
- },
111
- },
112
- } satisfies Meta;
113
-
114
- export default meta;
115
- type Story = StoryObj;
116
-
117
- /** A rich assistant message: markdown, reasoning, a tool call, an attachment, and actions. */
118
- export const Assistant: Story = {
119
- render: () => <MessageElement message={assistantMessage} />,
120
- parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
121
- };
122
-
123
- /** A plain user message (markdown defaults off for the user role). */
124
- export const User: Story = {
125
- render: () => <MessageElement message={userMessage} />,
126
- };
127
-
128
- /** An avatar (from `message.avatar`) laid out beside the content column, with a
129
- * custom **Share** action (curated `icon`) alongside the built-in copy/like. */
130
- export const WithAvatarAndCustomAction: Story = {
131
- name: 'Avatar + Custom Action',
132
- render: () => <MessageElement message={avatarMessage} />,
133
- parameters: {
134
- docs: {
135
- source: {
136
- code: `<kai-message id="msg" style="display:block;"></kai-message>
137
- <script type="module">
138
- import '@kitn.ai/ui/elements';
139
- const msg = document.getElementById('msg');
140
- msg.message = {
141
- id: 'm-av', role: 'assistant', content: 'I have an avatar and a custom Share action.',
142
- avatar: { src: '/k.png', alt: 'Kitn', fallback: 'K' },
143
- // built-in names AND custom { id, label, icon } descriptors:
144
- actions: ['copy', 'like', { id: 'share', label: 'Share', icon: 'share' }],
145
- };
146
- msg.addEventListener('kai-message-action', (e) => console.log(e.detail.action)); // 'copy' | 'like' | 'share'
147
- </script>`,
148
- language: 'html',
149
- },
150
- },
151
- },
152
- };
153
-
154
- /** Declarative actions — `<kai-action>` light-DOM children instead of a
155
- * `message.actions` array. Each carries `id`, a curated `icon`, an optional
156
- * `tooltip`, and optional text (the accessible label). Great for plain HTML. */
157
- export const DeclarativeActions: Story = {
158
- name: 'Declarative Actions (kai-action)',
159
- render: () => {
160
- let el: HTMLElement | undefined;
161
- onMount(() => {
162
- if (!el) return;
163
- el.setAttribute('avatar-fallback', 'AI');
164
- el.setAttribute(
165
- 'content',
166
- 'Declare each button as a `<kai-action>` child — no `message` object or JS wiring needed.',
167
- );
168
- el.addEventListener('kai-message-action', (e) =>
169
- console.log('kai-message-action', (e as CustomEvent).detail.action),
170
- );
171
- });
172
- return (
173
- <kai-message
174
- ref={(e) => (el = e as HTMLElement)}
175
- style={{ display: 'block', padding: '16px', 'max-width': '720px' }}
176
- >
177
- <kai-action id="copy" icon="copy" tooltip="Copy"></kai-action>
178
- <kai-action id="regenerate" icon="regenerate" tooltip="Regenerate"></kai-action>
179
- <kai-action id="share" icon="share" tooltip="Share">Share</kai-action>
180
- <kai-action id="bookmark" icon="bookmark" tooltip="Bookmark">Bookmark</kai-action>
181
- </kai-message>
182
- );
183
- },
184
- parameters: {
185
- docs: {
186
- source: {
187
- code: `<kai-message role="assistant" avatar-fallback="AI" content="..." style="display:block;">
188
- <kai-action id="copy" icon="copy" tooltip="Copy"></kai-action>
189
- <kai-action id="regenerate" icon="regenerate" tooltip="Regenerate"></kai-action>
190
- <kai-action id="share" icon="share" tooltip="Share">Share</kai-action>
191
- <kai-action id="bookmark" icon="bookmark" tooltip="Bookmark">Bookmark</kai-action>
192
- </kai-message>
193
-
194
- <script type="module">
195
- import '@kitn.ai/ui/elements';
196
- document.querySelector('kai-message')
197
- .addEventListener('kai-message-action', (e) => console.log(e.detail.action)); // 'copy' | 'share' | …
198
- </script>`,
199
- language: 'html',
200
- },
201
- },
202
- },
203
- };
204
-
205
- /** `actions-reveal="hover"` — the action bar is hidden until you hover the row. */
206
- export const ActionsRevealOnHover: Story = {
207
- name: 'Actions Reveal on Hover',
208
- render: () => <MessageElement message={avatarMessage} actionsReveal="hover" />,
209
- parameters: {
210
- docs: {
211
- source: {
212
- code: `<!-- actions-reveal: 'always' (default) | 'hover' -->
213
- <kai-message actions-reveal="hover" id="msg" style="display:block;"></kai-message>`,
214
- language: 'html',
215
- },
216
- },
217
- },
218
- };
@@ -1,130 +0,0 @@
1
- /**
2
- * Unit tests for the declarative `<kai-model>` light-DOM API of
3
- * `<kai-model-switcher>`.
4
- *
5
- * Strategy: the `defineWebComponent` call registers a real Shadow-DOM custom
6
- * element which requires a full browser environment (Constructable Stylesheets,
7
- * shadow roots, etc.) and is therefore not suitable for jsdom unit tests.
8
- * Instead we:
9
- * 1. Test the exported `parseKaiModelElement` helper in isolation — it has no
10
- * DOM dependencies beyond `Element`, which jsdom provides perfectly.
11
- * 2. Test that the merged list of prop + slotted models combines correctly,
12
- * mirroring the pattern used in `prompt-suggestions.declarative.test.tsx`.
13
- */
14
- import { describe, it, expect } from 'vitest';
15
- import { parseKaiModelElement } from './model-switcher';
16
- import type { ModelOption } from '../types';
17
-
18
- // ---------------------------------------------------------------------------
19
- // parseKaiModelElement — pure helper
20
- // ---------------------------------------------------------------------------
21
-
22
- describe('parseKaiModelElement', () => {
23
- function makeNode(textContent: string, id?: string, provider?: string): Element {
24
- const el = document.createElement('kai-model');
25
- el.textContent = textContent;
26
- if (id !== undefined) el.setAttribute('id', id);
27
- if (provider !== undefined) el.setAttribute('provider', provider);
28
- return el;
29
- }
30
-
31
- it('maps textContent to name', () => {
32
- const item = parseKaiModelElement(makeNode('GPT-4o', 'gpt-4o'));
33
- expect(item.name).toBe('GPT-4o');
34
- });
35
-
36
- it('maps the id attribute to id', () => {
37
- const item = parseKaiModelElement(makeNode('GPT-4o', 'gpt-4o'));
38
- expect(item.id).toBe('gpt-4o');
39
- });
40
-
41
- it('maps the provider attribute to provider', () => {
42
- const item = parseKaiModelElement(makeNode('GPT-4o', 'gpt-4o', 'OpenAI'));
43
- expect(item.provider).toBe('OpenAI');
44
- });
45
-
46
- it('sets provider to undefined when the attribute is absent', () => {
47
- const item = parseKaiModelElement(makeNode('Claude Sonnet', 'sonnet'));
48
- expect(item.provider).toBeUndefined();
49
- });
50
-
51
- it('trims leading/trailing whitespace from textContent (name)', () => {
52
- const item = parseKaiModelElement(makeNode(' Claude Sonnet ', 'sonnet'));
53
- expect(item.name).toBe('Claude Sonnet');
54
- });
55
-
56
- it('returns empty string for id when id attribute is absent', () => {
57
- const item = parseKaiModelElement(makeNode('GPT-4o'));
58
- expect(item.id).toBe('');
59
- });
60
-
61
- it('produces the full ModelOption shape', () => {
62
- const item = parseKaiModelElement(makeNode('GPT-4o', 'gpt-4o', 'OpenAI'));
63
- expect(item).toEqual<ModelOption>({ id: 'gpt-4o', name: 'GPT-4o', provider: 'OpenAI', description: undefined, group: undefined });
64
- });
65
-
66
- it('maps the description and group attributes', () => {
67
- const el = document.createElement('kai-model');
68
- el.textContent = 'GPT-4o';
69
- el.setAttribute('id', 'gpt-4o');
70
- el.setAttribute('description', 'Flagship model');
71
- el.setAttribute('group', 'Legacy models');
72
- const item = parseKaiModelElement(el);
73
- expect(item.description).toBe('Flagship model');
74
- expect(item.group).toBe('Legacy models');
75
- });
76
- });
77
-
78
- // ---------------------------------------------------------------------------
79
- // Merge logic: prop models + slotted models combine correctly
80
- // ---------------------------------------------------------------------------
81
-
82
- describe('prop + declarative merge', () => {
83
- function makeNode(textContent: string, id: string, provider?: string): Element {
84
- const el = document.createElement('kai-model');
85
- el.textContent = textContent;
86
- el.setAttribute('id', id);
87
- if (provider !== undefined) el.setAttribute('provider', provider);
88
- return el;
89
- }
90
-
91
- it('renders prop items before declarative (slotted) items', () => {
92
- const propModels: ModelOption[] = [{ id: 'opus', name: 'Claude Opus', provider: 'Anthropic' }];
93
- const slottedModels = [makeNode('GPT-4o', 'gpt-4o', 'OpenAI')].map(parseKaiModelElement);
94
- const merged = [...propModels, ...slottedModels];
95
- expect(merged).toHaveLength(2);
96
- expect(merged[0].id).toBe('opus');
97
- expect(merged[1].id).toBe('gpt-4o');
98
- });
99
-
100
- it('works with only declarative children (empty prop array)', () => {
101
- const slottedModels = [
102
- makeNode('GPT-4o', 'gpt-4o', 'OpenAI'),
103
- makeNode('GPT-4o mini', 'gpt-4o-mini', 'OpenAI'),
104
- ].map(parseKaiModelElement);
105
- const merged = [...[], ...slottedModels];
106
- expect(merged).toHaveLength(2);
107
- expect(merged[0]).toMatchObject({ id: 'gpt-4o', name: 'GPT-4o', provider: 'OpenAI' });
108
- expect(merged[1]).toMatchObject({ id: 'gpt-4o-mini', name: 'GPT-4o mini', provider: 'OpenAI' });
109
- });
110
-
111
- it('works with only prop models (no declarative children)', () => {
112
- const propModels: ModelOption[] = [
113
- { id: 'sonnet', name: 'Claude Sonnet', provider: 'Anthropic' },
114
- { id: 'haiku', name: 'Claude Haiku', provider: 'Anthropic' },
115
- ];
116
- const merged = [...propModels, ...([] as ModelOption[])];
117
- expect(merged).toHaveLength(2);
118
- expect(merged[0].id).toBe('sonnet');
119
- expect(merged[1].id).toBe('haiku');
120
- });
121
-
122
- it('parseKaiModelElement produces items with the correct event payload key (id → modelId)', () => {
123
- // Confirm the parsed `id` field is what gets dispatched as `modelId` in kai-model-change.
124
- // The element fires: dispatch('kai-model-change', { modelId }) where modelId = item.id.
125
- const node = makeNode('GPT-4o', 'gpt-4o', 'OpenAI');
126
- const parsed = parseKaiModelElement(node);
127
- // The model-switcher component passes item.id as the modelId in the event.
128
- expect(parsed.id).toBe('gpt-4o'); // this becomes e.detail.modelId
129
- });
130
- });