@kitn.ai/ui 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +394 -0
  3. package/dist/bash-InADTalH.js +1 -0
  4. package/dist/core-AYMC6_lb.js +12 -0
  5. package/dist/css-M7EaDHN_.js +1 -0
  6. package/dist/custom-elements.json +4739 -0
  7. package/dist/engine-javascript-vq0WuIJl.js +141 -0
  8. package/dist/github-dark-dimmed-DUshB20C.js +1 -0
  9. package/dist/github-light-JYsPkUQd.js +1 -0
  10. package/dist/host-embed.d.ts +33 -0
  11. package/dist/html-CPZ3oZQ7.js +1 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/javascript-C25yR2R2.js +1 -0
  14. package/dist/json-DxJze_jm.js +1 -0
  15. package/dist/kitn-chat-provider.d.ts +2 -0
  16. package/dist/kitn-chat-provider.es.js +240 -0
  17. package/dist/kitn-chat.es.js +77 -0
  18. package/dist/llms/llms-full.txt +1018 -0
  19. package/dist/llms/llms.txt +104 -0
  20. package/dist/origin.d.ts +5 -0
  21. package/dist/provider-runtime.d.ts +27 -0
  22. package/dist/schemas/card-envelope.schema.json +14 -0
  23. package/dist/schemas/card-event.schema.json +12 -0
  24. package/dist/schemas/choice.schema.json +70 -0
  25. package/dist/schemas/confirm.schema.json +65 -0
  26. package/dist/schemas/embed.schema.json +65 -0
  27. package/dist/schemas/form.result.schema.json +7 -0
  28. package/dist/schemas/form.schema.json +33 -0
  29. package/dist/schemas/link.schema.json +56 -0
  30. package/dist/schemas/tasks.result.schema.json +16 -0
  31. package/dist/schemas/tasks.schema.json +78 -0
  32. package/dist/svelte--5p79yCD.js +1 -0
  33. package/dist/theme.tokens.css +176 -0
  34. package/dist/tsx-B8rCNbgL.js +1 -0
  35. package/dist/typescript-RycA9KXf.js +1 -0
  36. package/dist/validate.d.ts +4 -0
  37. package/dist/version.d.ts +4 -0
  38. package/dist/vue-BmIZj4XD.js +1 -0
  39. package/dist/wire.d.ts +53 -0
  40. package/frameworks/react/index.tsx +948 -0
  41. package/frameworks/react/runtime.tsx +94 -0
  42. package/llms-full.txt +1018 -0
  43. package/llms.txt +104 -0
  44. package/package.json +141 -0
  45. package/src/components/artifact.stories.tsx +138 -0
  46. package/src/components/artifact.tsx +743 -0
  47. package/src/components/attachments.stories.tsx +303 -0
  48. package/src/components/attachments.test.tsx +64 -0
  49. package/src/components/attachments.tsx +399 -0
  50. package/src/components/card-fallback.tsx +28 -0
  51. package/src/components/card-renderer.tsx +52 -0
  52. package/src/components/card.tsx +110 -0
  53. package/src/components/chain-of-thought.stories.tsx +211 -0
  54. package/src/components/chain-of-thought.tsx +139 -0
  55. package/src/components/chat-container.stories.tsx +187 -0
  56. package/src/components/chat-container.tsx +82 -0
  57. package/src/components/chat-scope-picker.tsx +47 -0
  58. package/src/components/chat-thread.test.tsx +58 -0
  59. package/src/components/chat-thread.tsx +249 -0
  60. package/src/components/checkpoint.stories.tsx +102 -0
  61. package/src/components/checkpoint.tsx +88 -0
  62. package/src/components/choice-card.tsx +569 -0
  63. package/src/components/code-block.stories.tsx +150 -0
  64. package/src/components/code-block.tsx +105 -0
  65. package/src/components/component-meta.json +3735 -0
  66. package/src/components/confirm-card.stories.tsx +74 -0
  67. package/src/components/confirm-card.tsx +315 -0
  68. package/src/components/context.stories.tsx +229 -0
  69. package/src/components/context.test.tsx +133 -0
  70. package/src/components/context.tsx +363 -0
  71. package/src/components/conversation-item.stories.tsx +125 -0
  72. package/src/components/conversation-item.tsx +16 -0
  73. package/src/components/conversation-list.stories.tsx +133 -0
  74. package/src/components/conversation-list.tsx +101 -0
  75. package/src/components/default-input.test.tsx +111 -0
  76. package/src/components/embed.tsx +196 -0
  77. package/src/components/empty.stories.tsx +434 -0
  78. package/src/components/empty.tsx +166 -0
  79. package/src/components/feedback-bar.stories.tsx +138 -0
  80. package/src/components/feedback-bar.test.tsx +103 -0
  81. package/src/components/feedback-bar.tsx +166 -0
  82. package/src/components/file-tree.stories.tsx +73 -0
  83. package/src/components/file-tree.tsx +383 -0
  84. package/src/components/file-upload.stories.tsx +156 -0
  85. package/src/components/file-upload.tsx +161 -0
  86. package/src/components/form-widgets.tsx +461 -0
  87. package/src/components/form.tsx +873 -0
  88. package/src/components/image.stories.tsx +89 -0
  89. package/src/components/image.tsx +67 -0
  90. package/src/components/link-preview.tsx +194 -0
  91. package/src/components/loader.stories.tsx +181 -0
  92. package/src/components/loader.tsx +333 -0
  93. package/src/components/markdown.stories.tsx +180 -0
  94. package/src/components/markdown.tsx +81 -0
  95. package/src/components/message-action-bar.test.tsx +128 -0
  96. package/src/components/message-narrow.stories.tsx +329 -0
  97. package/src/components/message-skills.stories.tsx +211 -0
  98. package/src/components/message-skills.tsx +36 -0
  99. package/src/components/message.stories.tsx +281 -0
  100. package/src/components/message.tsx +227 -0
  101. package/src/components/model-switcher.stories.tsx +123 -0
  102. package/src/components/model-switcher.test.tsx +65 -0
  103. package/src/components/model-switcher.tsx +87 -0
  104. package/src/components/prompt-input.stories.tsx +222 -0
  105. package/src/components/prompt-input.tsx +211 -0
  106. package/src/components/prompt-suggestion.stories.tsx +142 -0
  107. package/src/components/prompt-suggestion.tsx +115 -0
  108. package/src/components/reasoning.stories.tsx +140 -0
  109. package/src/components/reasoning.tsx +156 -0
  110. package/src/components/resizable.test.tsx +226 -0
  111. package/src/components/response-stream.tsx +103 -0
  112. package/src/components/scroll-button.stories.tsx +100 -0
  113. package/src/components/scroll-button.tsx +34 -0
  114. package/src/components/slash-command.stories.tsx +163 -0
  115. package/src/components/slash-command.tsx +232 -0
  116. package/src/components/source-list.test.tsx +152 -0
  117. package/src/components/source.stories.tsx +124 -0
  118. package/src/components/source.tsx +129 -0
  119. package/src/components/tasks-card.stories.tsx +78 -0
  120. package/src/components/tasks-card.tsx +432 -0
  121. package/src/components/text-shimmer.stories.tsx +87 -0
  122. package/src/components/text-shimmer.tsx +37 -0
  123. package/src/components/thinking-bar.stories.tsx +87 -0
  124. package/src/components/thinking-bar.tsx +50 -0
  125. package/src/components/tool.stories.tsx +153 -0
  126. package/src/components/tool.tsx +184 -0
  127. package/src/components/use-card-resolution.ts +28 -0
  128. package/src/components/voice-input.stories.tsx +83 -0
  129. package/src/components/voice-input.tsx +107 -0
  130. package/src/elements/artifact.stories.tsx +506 -0
  131. package/src/elements/artifact.tsx +137 -0
  132. package/src/elements/attachments.stories.tsx +190 -0
  133. package/src/elements/attachments.tsx +136 -0
  134. package/src/elements/card.stories.tsx +113 -0
  135. package/src/elements/card.tsx +40 -0
  136. package/src/elements/cards.stories.tsx +59 -0
  137. package/src/elements/cards.tsx +97 -0
  138. package/src/elements/catalog.stories.tsx +491 -0
  139. package/src/elements/chain-of-thought.declarative.test.tsx +153 -0
  140. package/src/elements/chain-of-thought.stories.tsx +113 -0
  141. package/src/elements/chain-of-thought.tsx +92 -0
  142. package/src/elements/chat-scope-picker.stories.tsx +73 -0
  143. package/src/elements/chat-scope-picker.tsx +36 -0
  144. package/src/elements/chat-types.ts +42 -0
  145. package/src/elements/chat-workspace.stories.tsx +288 -0
  146. package/src/elements/chat-workspace.tsx +161 -0
  147. package/src/elements/chat.stories.tsx +151 -0
  148. package/src/elements/chat.tsx +80 -0
  149. package/src/elements/checkpoint.stories.tsx +72 -0
  150. package/src/elements/checkpoint.tsx +47 -0
  151. package/src/elements/choice.stories.tsx +240 -0
  152. package/src/elements/choice.tsx +48 -0
  153. package/src/elements/code-block.stories.tsx +83 -0
  154. package/src/elements/code-block.tsx +42 -0
  155. package/src/elements/compiled.css +2 -0
  156. package/src/elements/composed-shell.stories.tsx +316 -0
  157. package/src/elements/confirm-card.stories.tsx +227 -0
  158. package/src/elements/confirm-card.tsx +51 -0
  159. package/src/elements/context-meter.stories.tsx +151 -0
  160. package/src/elements/context-meter.tsx +121 -0
  161. package/src/elements/conversation-list.declarative.test.tsx +137 -0
  162. package/src/elements/conversation-list.stories.tsx +243 -0
  163. package/src/elements/conversation-list.tsx +80 -0
  164. package/src/elements/css.ts +5 -0
  165. package/src/elements/default-input.tsx +247 -0
  166. package/src/elements/define.tsx +171 -0
  167. package/src/elements/element-meta.json +3314 -0
  168. package/src/elements/element-types.d.ts +710 -0
  169. package/src/elements/embed.stories.tsx +213 -0
  170. package/src/elements/embed.tsx +35 -0
  171. package/src/elements/empty.stories.tsx +111 -0
  172. package/src/elements/empty.tsx +29 -0
  173. package/src/elements/feedback-bar.stories.tsx +114 -0
  174. package/src/elements/feedback-bar.tsx +61 -0
  175. package/src/elements/file-tree.stories.tsx +134 -0
  176. package/src/elements/file-tree.tsx +52 -0
  177. package/src/elements/file-upload.stories.tsx +82 -0
  178. package/src/elements/file-upload.tsx +44 -0
  179. package/src/elements/form.stories.tsx +248 -0
  180. package/src/elements/form.tsx +43 -0
  181. package/src/elements/framework-usage.json +563 -0
  182. package/src/elements/image.stories.tsx +71 -0
  183. package/src/elements/image.tsx +32 -0
  184. package/src/elements/link-preview.stories.tsx +209 -0
  185. package/src/elements/link-preview.tsx +34 -0
  186. package/src/elements/loader.stories.tsx +88 -0
  187. package/src/elements/loader.tsx +25 -0
  188. package/src/elements/markdown.stories.tsx +76 -0
  189. package/src/elements/markdown.tsx +38 -0
  190. package/src/elements/message-skills.declarative.test.tsx +104 -0
  191. package/src/elements/message-skills.stories.tsx +114 -0
  192. package/src/elements/message-skills.tsx +85 -0
  193. package/src/elements/message.stories.tsx +218 -0
  194. package/src/elements/message.tsx +162 -0
  195. package/src/elements/model-switcher.declarative.test.tsx +130 -0
  196. package/src/elements/model-switcher.stories.tsx +198 -0
  197. package/src/elements/model-switcher.tsx +93 -0
  198. package/src/elements/popover.stories.tsx +80 -0
  199. package/src/elements/popover.tsx +59 -0
  200. package/src/elements/prompt-input-slash-command.test.tsx +128 -0
  201. package/src/elements/prompt-input-stoppable.test.tsx +88 -0
  202. package/src/elements/prompt-input-toolbar-actions.test.tsx +120 -0
  203. package/src/elements/prompt-input.stories.tsx +331 -0
  204. package/src/elements/prompt-input.tsx +185 -0
  205. package/src/elements/prompt-suggestions.declarative.test.tsx +164 -0
  206. package/src/elements/prompt-suggestions.stories.tsx +213 -0
  207. package/src/elements/prompt-suggestions.tsx +94 -0
  208. package/src/elements/reasoning.stories.tsx +77 -0
  209. package/src/elements/reasoning.tsx +50 -0
  210. package/src/elements/register.ts +56 -0
  211. package/src/elements/remote.stories.tsx +314 -0
  212. package/src/elements/remote.tsx +221 -0
  213. package/src/elements/resizable.d.ts +27 -0
  214. package/src/elements/resizable.stories.tsx +426 -0
  215. package/src/elements/resizable.tsx +553 -0
  216. package/src/elements/response-stream.stories.tsx +79 -0
  217. package/src/elements/response-stream.tsx +40 -0
  218. package/src/elements/scroll-button.stories.tsx +202 -0
  219. package/src/elements/scroll-button.test.tsx +148 -0
  220. package/src/elements/scroll-button.tsx +126 -0
  221. package/src/elements/source-list.stories.tsx +143 -0
  222. package/src/elements/source.stories.tsx +88 -0
  223. package/src/elements/source.tsx +120 -0
  224. package/src/elements/styles.css +199 -0
  225. package/src/elements/switch.stories.tsx +56 -0
  226. package/src/elements/switch.tsx +43 -0
  227. package/src/elements/tasks.stories.tsx +237 -0
  228. package/src/elements/tasks.tsx +46 -0
  229. package/src/elements/text-shimmer.stories.tsx +64 -0
  230. package/src/elements/text-shimmer.tsx +28 -0
  231. package/src/elements/thinking-bar.stories.tsx +72 -0
  232. package/src/elements/thinking-bar.tsx +34 -0
  233. package/src/elements/tool.stories.tsx +89 -0
  234. package/src/elements/tool.tsx +23 -0
  235. package/src/elements/voice-input.stories.tsx +88 -0
  236. package/src/elements/voice-input.tsx +41 -0
  237. package/src/index.ts +253 -0
  238. package/src/primitives/card-contract.ts +71 -0
  239. package/src/primitives/card-host.tsx +35 -0
  240. package/src/primitives/card-registry.tsx +67 -0
  241. package/src/primitives/card-resolution.ts +36 -0
  242. package/src/primitives/card-routing.ts +79 -0
  243. package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
  244. package/src/primitives/card-schemas/card-event.schema.json +12 -0
  245. package/src/primitives/card-schemas/choice.schema.json +70 -0
  246. package/src/primitives/card-schemas/confirm.schema.json +65 -0
  247. package/src/primitives/card-schemas/embed.schema.json +65 -0
  248. package/src/primitives/card-schemas/form.result.schema.json +7 -0
  249. package/src/primitives/card-schemas/form.schema.json +33 -0
  250. package/src/primitives/card-schemas/link.schema.json +56 -0
  251. package/src/primitives/card-schemas/tasks.result.schema.json +16 -0
  252. package/src/primitives/card-schemas/tasks.schema.json +78 -0
  253. package/src/primitives/card-validate.ts +95 -0
  254. package/src/primitives/chat-config.tsx +76 -0
  255. package/src/primitives/controllable.test.ts +47 -0
  256. package/src/primitives/controllable.ts +21 -0
  257. package/src/primitives/embed-providers.ts +254 -0
  258. package/src/primitives/highlighter.ts +157 -0
  259. package/src/primitives/link-preview.ts +87 -0
  260. package/src/primitives/pdf-preview.ts +121 -0
  261. package/src/primitives/use-auto-resize.ts +31 -0
  262. package/src/primitives/use-resize-observer.ts +12 -0
  263. package/src/primitives/use-stick-to-bottom.ts +43 -0
  264. package/src/primitives/use-text-stream.ts +112 -0
  265. package/src/primitives/use-voice-recorder.ts +50 -0
  266. package/src/remote/host-embed.ts +345 -0
  267. package/src/remote/index.ts +2 -0
  268. package/src/remote/origin.ts +30 -0
  269. package/src/remote/provider-runtime.ts +259 -0
  270. package/src/remote/provider.ts +2 -0
  271. package/src/remote/validate.ts +22 -0
  272. package/src/remote/version.ts +12 -0
  273. package/src/remote/wire.ts +48 -0
  274. package/src/stories/chat-panel-layout.stories.tsx +145 -0
  275. package/src/stories/chat-scene.tsx +571 -0
  276. package/src/stories/checkpoint-restore.stories.tsx +256 -0
  277. package/src/stories/context-usage.stories.tsx +212 -0
  278. package/src/stories/conversation-with-reasoning.stories.tsx +182 -0
  279. package/src/stories/conversation-with-sources.stories.tsx +471 -0
  280. package/src/stories/docs/Accessibility.mdx +119 -0
  281. package/src/stories/docs/ChildElements.mdx +269 -0
  282. package/src/stories/docs/ForAIAgents.mdx +162 -0
  283. package/src/stories/docs/GettingStarted.mdx +65 -0
  284. package/src/stories/docs/Installation.mdx +83 -0
  285. package/src/stories/docs/Introduction.mdx +50 -0
  286. package/src/stories/docs/SolidJsAdvanced.mdx +17 -0
  287. package/src/stories/docs/Theming.mdx +85 -0
  288. package/src/stories/docs/element-controls.ts +121 -0
  289. package/src/stories/docs/frameworks/Angular.mdx +245 -0
  290. package/src/stories/docs/frameworks/Html.mdx +227 -0
  291. package/src/stories/docs/frameworks/Overview.mdx +47 -0
  292. package/src/stories/docs/frameworks/React.mdx +227 -0
  293. package/src/stories/docs/frameworks/Solid.mdx +173 -0
  294. package/src/stories/docs/frameworks/Svelte.mdx +194 -0
  295. package/src/stories/docs/frameworks/Vue.mdx +238 -0
  296. package/src/stories/docs/generative-ui-overview.mdx +186 -0
  297. package/src/stories/docs/recipes/SpeechToText.mdx +7 -0
  298. package/src/stories/docs/recipes/Streaming.mdx +68 -0
  299. package/src/stories/docs/recipes/TextToSpeech.mdx +35 -0
  300. package/src/stories/docs/theme-editor/canvas.tsx +32 -0
  301. package/src/stories/docs/theme-editor/inspector.tsx +66 -0
  302. package/src/stories/docs/theme-editor/presets.test.ts +32 -0
  303. package/src/stories/docs/theme-editor/presets.ts +64 -0
  304. package/src/stories/docs/theme-editor/theme-css.test.ts +19 -0
  305. package/src/stories/docs/theme-editor/theme-css.ts +15 -0
  306. package/src/stories/docs/theme-editor/theme-editor.tsx +146 -0
  307. package/src/stories/docs/theme-tokens.tsx +174 -0
  308. package/src/stories/examples/ChoosingComponents.mdx +106 -0
  309. package/src/stories/examples/sample-data.ts +79 -0
  310. package/src/stories/examples/usage/checkpoint-restore.ts +156 -0
  311. package/src/stories/examples/usage/context-usage.ts +591 -0
  312. package/src/stories/examples/usage/conversation-with-reasoning.ts +224 -0
  313. package/src/stories/examples/usage/conversation-with-sources.ts +589 -0
  314. package/src/stories/examples/usage/empty-state.ts +144 -0
  315. package/src/stories/examples/usage/full-chat-app.ts +277 -0
  316. package/src/stories/examples/usage/index.ts +63 -0
  317. package/src/stories/examples/usage/message-actions.ts +704 -0
  318. package/src/stories/examples/usage/pattern-centered-conversation.ts +446 -0
  319. package/src/stories/examples/usage/pattern-chat-panel-layout.ts +442 -0
  320. package/src/stories/examples/usage/pattern-docked-widget.ts +479 -0
  321. package/src/stories/examples/usage/prompt-input-variants.ts +1356 -0
  322. package/src/stories/examples/usage/streaming-response.ts +687 -0
  323. package/src/stories/examples/usage/types.ts +33 -0
  324. package/src/stories/full-chat.stories.tsx +18 -0
  325. package/src/stories/message-actions.stories.tsx +230 -0
  326. package/src/stories/pattern-centered-conversation.stories.tsx +93 -0
  327. package/src/stories/pattern-docked-widget.stories.tsx +93 -0
  328. package/src/stories/pattern-empty-state.stories.tsx +76 -0
  329. package/src/stories/prompt-input-variants.stories.tsx +456 -0
  330. package/src/stories/streaming-response.stories.tsx +438 -0
  331. package/src/stories/theme-editor.stories.tsx +16 -0
  332. package/src/stories/token-reference.stories.tsx +18 -0
  333. package/src/stories/typography.stories.tsx +78 -0
  334. package/src/types.ts +47 -0
  335. package/src/ui/action-icons.ts +52 -0
  336. package/src/ui/avatar.stories.tsx +103 -0
  337. package/src/ui/avatar.tsx +23 -0
  338. package/src/ui/badge.stories.tsx +86 -0
  339. package/src/ui/badge.tsx +21 -0
  340. package/src/ui/button.stories.tsx +145 -0
  341. package/src/ui/button.tsx +38 -0
  342. package/src/ui/collapsible.stories.tsx +69 -0
  343. package/src/ui/collapsible.tsx +125 -0
  344. package/src/ui/dropdown.stories.tsx +59 -0
  345. package/src/ui/dropdown.tsx +196 -0
  346. package/src/ui/hover-card.stories.tsx +77 -0
  347. package/src/ui/hover-card.test.tsx +30 -0
  348. package/src/ui/hover-card.tsx +172 -0
  349. package/src/ui/overlay.stories.tsx +115 -0
  350. package/src/ui/overlay.tsx +158 -0
  351. package/src/ui/popover.stories.tsx +81 -0
  352. package/src/ui/popover.test.tsx +99 -0
  353. package/src/ui/popover.tsx +92 -0
  354. package/src/ui/resizable.stories.tsx +236 -0
  355. package/src/ui/resizable.tsx +576 -0
  356. package/src/ui/scroll-area.stories.tsx +50 -0
  357. package/src/ui/scroll-area.tsx +15 -0
  358. package/src/ui/separator.stories.tsx +81 -0
  359. package/src/ui/separator.tsx +10 -0
  360. package/src/ui/skeleton.stories.tsx +337 -0
  361. package/src/ui/skeleton.tsx +16 -0
  362. package/src/ui/switch.stories.tsx +51 -0
  363. package/src/ui/switch.test.tsx +59 -0
  364. package/src/ui/switch.tsx +62 -0
  365. package/src/ui/textarea.stories.tsx +76 -0
  366. package/src/ui/textarea.tsx +21 -0
  367. package/src/ui/tooltip.stories.tsx +74 -0
  368. package/src/ui/tooltip.tsx +68 -0
  369. package/src/utils/cn.ts +24 -0
  370. package/theme.css +187 -0
@@ -0,0 +1,288 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { onMount, createSignal } from 'solid-js';
3
+ import './register'; // side effect: registers all kitn custom elements including <kai-workspace>
4
+ import type { ConversationGroup, ConversationSummary, ModelOption } from '../types';
5
+ import type { ChatMessage } from './chat-types';
6
+ import { argTypesFor, specDescription } from '../stories/docs/element-controls';
7
+
8
+ // The web components are custom DOM elements, so declare the tags for JSX.
9
+ declare module 'solid-js' {
10
+ // eslint-disable-next-line @typescript-eslint/no-namespace
11
+ namespace JSX {
12
+ interface IntrinsicElements {
13
+ 'kai-workspace': JSX.HTMLAttributes<HTMLElement>;
14
+ }
15
+ }
16
+ }
17
+
18
+ const sampleGroups: ConversationGroup[] = [
19
+ { id: 'today', name: 'Today', sortOrder: 0, createdAt: '2026-06-13T00:00:00.000Z' },
20
+ ];
21
+
22
+ const sampleConversations: ConversationSummary[] = [
23
+ {
24
+ id: '1',
25
+ title: 'Web component architecture',
26
+ groupId: 'today',
27
+ scope: { type: 'document' },
28
+ messageCount: 12,
29
+ lastMessageAt: '2026-06-13T15:30:00.000Z',
30
+ updatedAt: '2026-06-13T15:30:00.000Z',
31
+ },
32
+ {
33
+ id: '2',
34
+ title: 'Theming & tokens',
35
+ groupId: 'today',
36
+ scope: { type: 'document' },
37
+ messageCount: 5,
38
+ lastMessageAt: '2026-06-13T11:20:00.000Z',
39
+ updatedAt: '2026-06-13T11:20:00.000Z',
40
+ },
41
+ ];
42
+
43
+ const sampleModels: ModelOption[] = [
44
+ { id: 'claude-4', name: 'Claude 4 Opus', provider: 'Anthropic' },
45
+ { id: 'gpt-4o', name: 'GPT-4o', provider: 'OpenAI' },
46
+ ];
47
+
48
+ const sampleMessages: ChatMessage[] = [
49
+ { id: 'm1', role: 'user', content: 'How do I drop the whole chat app in with one tag?' },
50
+ {
51
+ id: 'm2',
52
+ role: 'assistant',
53
+ content:
54
+ 'Use `<kai-workspace>` — set `conversations`, `messages`, and `models` as properties and listen for `kai-conversation-select` + `kai-submit`.',
55
+ actions: ['copy', 'like'],
56
+ },
57
+ ];
58
+
59
+ type WorkspaceEl = HTMLElement & {
60
+ groups?: ConversationGroup[];
61
+ conversations?: ConversationSummary[];
62
+ activeId?: string;
63
+ messages?: ChatMessage[];
64
+ models?: ModelOption[];
65
+ currentModel?: string;
66
+ chatTitle?: string;
67
+ placeholder?: string;
68
+ loading?: boolean;
69
+ suggestionMode?: string;
70
+ proseSize?: string;
71
+ codeTheme?: string;
72
+ codeHighlight?: boolean;
73
+ scrollButton?: boolean;
74
+ search?: boolean;
75
+ voice?: boolean;
76
+ slashCompact?: boolean;
77
+ sidebarWidth?: number;
78
+ sidebarMinWidth?: number;
79
+ sidebarMaxWidth?: number;
80
+ sidebarCollapsed?: boolean;
81
+ defaultSidebarCollapsed?: boolean;
82
+ value?: string;
83
+ };
84
+
85
+ /** Live demo of the actual `<kai-workspace>` custom element (Shadow DOM and all). */
86
+ function WorkspaceElement(props: { args?: Record<string, unknown> }) {
87
+ let el: WorkspaceEl | undefined;
88
+ onMount(() => {
89
+ if (el) {
90
+ // Fixed array/object data
91
+ el.groups = sampleGroups;
92
+ el.conversations = sampleConversations;
93
+ el.activeId = '1';
94
+ el.messages = sampleMessages;
95
+ el.models = sampleModels;
96
+ el.currentModel = 'claude-4';
97
+ el.chatTitle = 'Web component architecture';
98
+ // Scalar args from Controls (override defaults above for scalar props)
99
+ const args = props.args;
100
+ if (args) {
101
+ const scalarNames = [
102
+ 'placeholder', 'loading', 'suggestionMode', 'proseSize', 'codeTheme',
103
+ 'codeHighlight', 'scrollButton', 'search', 'voice', 'slashCompact',
104
+ 'sidebarWidth', 'sidebarMinWidth', 'sidebarMaxWidth', 'defaultSidebarCollapsed', 'value',
105
+ ];
106
+ for (const name of scalarNames) {
107
+ if (name in args) (el as unknown as Record<string, unknown>)[name] = args[name];
108
+ }
109
+ }
110
+ el.addEventListener('kai-conversation-select', (e) => console.log('select', (e as CustomEvent).detail));
111
+ el.addEventListener('kai-submit', (e) => console.log('submit', (e as unknown as CustomEvent).detail));
112
+ el.addEventListener('kai-sidebar-toggle', (e) => console.log('kai-sidebar-toggle', (e as CustomEvent).detail));
113
+ }
114
+ });
115
+ return (
116
+ <div style={{ height: '720px', width: '100%' }}>
117
+ <kai-workspace
118
+ ref={(e) => (el = e as WorkspaceEl)}
119
+ style={{ display: 'block', height: '100%' }}
120
+ />
121
+ </div>
122
+ );
123
+ }
124
+
125
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
126
+ <kai-workspace id="workspace" style="display:block; height:100vh;"></kai-workspace>
127
+
128
+ <script type="module">
129
+ import '@kitn.ai/ui/elements'; // registers the custom elements
130
+
131
+ const workspace = document.getElementById('workspace');
132
+ workspace.conversations = [
133
+ {
134
+ id: '1', title: 'Web component architecture',
135
+ scope: { type: 'document' }, messageCount: 12,
136
+ lastMessageAt: '2026-06-13T15:30:00Z', updatedAt: '2026-06-13T15:30:00Z',
137
+ },
138
+ ];
139
+ workspace.messages = [
140
+ { id: 'm1', role: 'user', content: 'How do I drop the whole chat app in with one tag?' },
141
+ { id: 'm2', role: 'assistant', content: 'Use <kai-workspace> — set conversations, messages, and models as properties.' },
142
+ ];
143
+ workspace.models = [
144
+ { id: 'claude-4', name: 'Claude 4 Opus', provider: 'Anthropic' },
145
+ ];
146
+
147
+ // events are CustomEvents on the element (they do not bubble)
148
+ workspace.addEventListener('kai-conversation-select', (e) => console.log('selected conversation:', e.detail.id));
149
+ workspace.addEventListener('kai-submit', (e) => console.log('user sent:', e.detail.value));
150
+ workspace.addEventListener('kai-sidebar-toggle', (e) => console.log('sidebar collapsed:', e.detail.collapsed));
151
+ </script>`;
152
+
153
+ const SOLID_SNIPPET = `import '@kitn.ai/ui/elements'; // registers the custom elements
154
+ import { onMount } from 'solid-js';
155
+ import type { ConversationSummary, ModelOption } from '@kitn.ai/ui';
156
+ import type { ChatMessage } from '@kitn.ai/ui/elements';
157
+
158
+ function Workspace() {
159
+ let el: HTMLElement & {
160
+ conversations?: ConversationSummary[];
161
+ messages?: ChatMessage[];
162
+ models?: ModelOption[];
163
+ activeId?: string;
164
+ };
165
+ const conversations: ConversationSummary[] = [
166
+ {
167
+ id: '1', title: 'Web component architecture',
168
+ scope: { type: 'document' }, messageCount: 12,
169
+ lastMessageAt: '2026-06-13T15:30:00Z', updatedAt: '2026-06-13T15:30:00Z',
170
+ },
171
+ ];
172
+ const messages: ChatMessage[] = [
173
+ { id: 'm1', role: 'user', content: 'How do I drop the whole chat app in with one tag?' },
174
+ { id: 'm2', role: 'assistant', content: 'Use <kai-workspace> — set conversations, messages, and models as properties.' },
175
+ ];
176
+ onMount(() => {
177
+ el.conversations = conversations;
178
+ el.messages = messages;
179
+ el.activeId = '1';
180
+ el.addEventListener('kai-conversation-select', (e) => console.log('selected:', e.detail.id));
181
+ el.addEventListener('kai-submit', (e) => console.log('user sent:', e.detail.value));
182
+ el.addEventListener('kai-sidebar-toggle', (e) => console.log('sidebar collapsed:', e.detail.collapsed));
183
+ });
184
+ return (
185
+ <kai-workspace
186
+ ref={el}
187
+ style={{ display: 'block', height: '100vh' }}
188
+ />
189
+ );
190
+ }`;
191
+
192
+ const meta = {
193
+ title: 'Components/Workspace',
194
+ tags: ['autodocs'],
195
+ argTypes: argTypesFor('kai-workspace'),
196
+ parameters: {
197
+ layout: 'fullscreen',
198
+ docs: {
199
+ description: specDescription('kai-workspace', [
200
+ '`<kai-workspace>` is the full chat shell as a single **web component** — a resizable split layout with a collapsible conversation list on the left and a full message thread on the right, all isolated in **Shadow DOM**. SolidJS is bundled in, so the host needs nothing.',
201
+ '**When to use:** dropping an entire chat application shell into a non-Solid app (React, Vue, Svelte, plain HTML), or anywhere you want zero style conflicts and a ready-made list+chat layout. If you *are* in SolidJS and want fine-grained control, compose the `ConversationList` and `ChatThread` primitives directly.',
202
+ '**How to use:** register once with `import \'@kitn.ai/ui/elements\'`, set rich data as JS **properties** (`el.conversations = [...]`, `el.messages = [...]`, `el.models = [...]`), and listen for **CustomEvents** (`kai-conversation-select`, `kai-submit`, `kai-sidebar-toggle`, `kai-new-chat`) directly on the element.',
203
+ '**Anatomy:** **sidebar panel** (a `<kai-conversations>` list with new-chat + collapse toggle, drag-resizable via an inner divider handle, collapses to a ghost reveal button) | **divider handle** (drag or click to resize; disappears when collapsed) | **chat panel** (the full `<ChatThread>`: header + message list + prompt composer; the thread node is stable across collapse/expand).',
204
+ '**Placement:** as a full-page surface or large panel. Give it an explicit height (e.g. `height: 100vh`). The sidebar is drag-resizable and can be collapsed via the toggle button in its header.',
205
+ 'See the **Code** tab below for the HTML usage; the *SolidJS* story shows the same element inside a Solid component.',
206
+ ]),
207
+ },
208
+ },
209
+ } satisfies Meta;
210
+
211
+ export default meta;
212
+ type Story = StoryObj;
213
+
214
+ /** The element used the plain-HTML / any-framework way. */
215
+ export const Default: Story = {
216
+ args: {
217
+ placeholder: 'Send a message...',
218
+ loading: false,
219
+ suggestionMode: 'submit',
220
+ proseSize: 'sm',
221
+ codeTheme: 'github-dark-dimmed',
222
+ codeHighlight: true,
223
+ scrollButton: true,
224
+ search: false,
225
+ voice: false,
226
+ slashCompact: false,
227
+ },
228
+ render: (args: Record<string, unknown>) => <WorkspaceElement args={args} />,
229
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
230
+ };
231
+
232
+ /** The same element used inside a SolidJS component (properties via `ref` + `onMount`, events via `addEventListener`). */
233
+ export const InSolidJS: Story = {
234
+ name: 'In SolidJS',
235
+ render: () => <WorkspaceElement />,
236
+ parameters: { docs: { source: { code: SOLID_SNIPPET, language: 'tsx' } } },
237
+ };
238
+
239
+ const CONTROLLED_SNIPPET = `const ws = document.getElementById('workspace');
240
+ let collapsed = false;
241
+ ws.sidebarCollapsed = collapsed; // app owns the state
242
+
243
+ // react to the in-component toggle AND drive it from your own UI
244
+ ws.addEventListener('kai-sidebar-toggle', (e) => {
245
+ collapsed = e.detail.collapsed;
246
+ ws.sidebarCollapsed = collapsed; // persist / restore as you like
247
+ });
248
+ document.getElementById('toggle').onclick = () => {
249
+ collapsed = !collapsed;
250
+ ws.sidebarCollapsed = collapsed; // collapse from outside the header
251
+ };`;
252
+
253
+ /**
254
+ * Controlled collapse: the app owns `sidebarCollapsed`, drives it as a property,
255
+ * and updates it from `kai-sidebar-toggle`. An external button collapses the
256
+ * sidebar from outside the workspace — only possible because the state is
257
+ * controlled (uncontrolled mode keeps collapse internal to the element).
258
+ */
259
+ export const ControlledSidebar: Story = {
260
+ name: 'Controlled sidebar',
261
+ render: () => {
262
+ let el: WorkspaceEl | undefined;
263
+ const [collapsed, setCollapsed] = createSignal(false);
264
+ const drive = (next: boolean) => { setCollapsed(next); if (el) el.sidebarCollapsed = next; };
265
+ onMount(() => {
266
+ if (!el) return;
267
+ el.groups = sampleGroups;
268
+ el.conversations = sampleConversations;
269
+ el.activeId = '1';
270
+ el.messages = sampleMessages;
271
+ el.models = sampleModels;
272
+ el.currentModel = 'claude-4';
273
+ el.chatTitle = 'Web component architecture';
274
+ el.sidebarCollapsed = collapsed();
275
+ el.addEventListener('kai-sidebar-toggle', (e) => drive((e as CustomEvent<{ collapsed: boolean }>).detail.collapsed));
276
+ });
277
+ return (
278
+ <div style={{ height: '720px', width: '100%', display: 'flex', 'flex-direction': 'column', gap: '8px' }}>
279
+ <button type="button" onClick={() => drive(!collapsed())} style={{ 'align-self': 'flex-start', padding: '6px 12px', 'border-radius': '8px', border: '1px solid var(--color-border)', cursor: 'pointer' }}>
280
+ {collapsed() ? 'Expand' : 'Collapse'} sidebar (from the app)
281
+ </button>
282
+ <kai-workspace ref={(e) => (el = e as WorkspaceEl)} style={{ display: 'block', flex: '1', 'min-height': '0' }} />
283
+ </div>
284
+ );
285
+ },
286
+ parameters: { docs: { source: { code: CONTROLLED_SNIPPET, language: 'js' } } },
287
+ };
288
+
@@ -0,0 +1,161 @@
1
+ import { Show } from 'solid-js';
2
+ import { defineWebComponent } from './define';
3
+ import { createControllableSignal } from '../primitives/controllable';
4
+ import { ChatThread, type ChatThreadContextUsage } from '../components/chat-thread';
5
+ import { ConversationList } from '../components/conversation-list';
6
+ import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '../ui/resizable';
7
+ import { Button } from '../ui/button';
8
+ import { PanelLeftOpen } from 'lucide-solid';
9
+ import type { AttachmentData } from '../components/attachments';
10
+ import type { SlashCommandItem } from '../components/slash-command';
11
+ import type { ChatMessage } from './chat-types';
12
+ import type { ProseSize } from '../primitives/chat-config';
13
+ import type { ModelOption, ConversationGroup, ConversationSummary } from '../types';
14
+
15
+ interface Props extends Record<string, unknown> {
16
+ /** Pre-bucketed conversation groups for the sidebar. Set as a JS property. */
17
+ groups: ConversationGroup[];
18
+ /** Flat conversation list (auto-bucketed if `groups` is empty). Set as a JS property. */
19
+ conversations: ConversationSummary[];
20
+ /** Id of the open conversation, highlighted in the sidebar. */
21
+ activeId?: string;
22
+ /** The active conversation's message thread, newest last. Set as a JS property. */
23
+ messages: ChatMessage[];
24
+ value?: string;
25
+ placeholder?: string;
26
+ loading?: boolean;
27
+ suggestions?: string[];
28
+ suggestionMode?: 'submit' | 'fill';
29
+ proseSize?: ProseSize;
30
+ codeTheme?: string;
31
+ codeHighlight?: boolean;
32
+ chatTitle?: string;
33
+ models?: ModelOption[];
34
+ currentModel?: string;
35
+ context?: ChatThreadContextUsage;
36
+ scrollButton?: boolean;
37
+ search?: boolean;
38
+ voice?: boolean;
39
+ slashCommands?: SlashCommandItem[];
40
+ slashActiveIds?: string[];
41
+ slashCompact?: boolean;
42
+ /** Sidebar default width as a percent of the workspace (default 22). */
43
+ sidebarWidth?: number;
44
+ /** Sidebar min width in px (default 200). */
45
+ sidebarMinWidth?: number;
46
+ /** Sidebar max width in px (default 420). */
47
+ sidebarMaxWidth?: number;
48
+ /** Controlled collapsed state. Set this as a JS property (`el.sidebarCollapsed
49
+ * = true`) to drive the sidebar from your app, updating it in response to the
50
+ * `kai-sidebar-toggle` event. Omit for uncontrolled (the element manages it). */
51
+ sidebarCollapsed?: boolean;
52
+ /** Initial collapsed state when uncontrolled (default false). Use the
53
+ * `default-sidebar-collapsed` attribute to start collapsed in plain HTML. */
54
+ defaultSidebarCollapsed?: boolean;
55
+ }
56
+
57
+ interface Events {
58
+ /** A conversation was selected in the sidebar. */
59
+ 'kai-conversation-select': { id: string };
60
+ /** The "New chat" button was clicked. */
61
+ 'kai-new-chat': Record<string, never>;
62
+ /** The sidebar was collapsed or expanded. */
63
+ 'kai-sidebar-toggle': { collapsed: boolean };
64
+ /** User submitted a message. */
65
+ 'kai-submit': { value: string; attachments: AttachmentData[] };
66
+ /** Fired on every input change. */
67
+ 'kai-value-change': { value: string };
68
+ /** The header model switcher changed. */
69
+ 'kai-model-change': { modelId: string };
70
+ /** An action button on a message was clicked. */
71
+ 'kai-message-action': { messageId: string; action: string };
72
+ /** The Search button was clicked. */
73
+ 'kai-search': Record<string, never>;
74
+ /** The Mic / voice button was clicked. */
75
+ 'kai-voice': Record<string, never>;
76
+ /** A slash command was chosen from the palette. */
77
+ 'kai-slash-select': { command: SlashCommandItem };
78
+ /** A suggestion chip was clicked (only in `suggestion-mode="fill"`). */
79
+ 'kai-suggestion-click': { value: string };
80
+ }
81
+
82
+ defineWebComponent<Props, Events>('kai-workspace', {
83
+ groups: [], conversations: [], activeId: undefined, messages: [],
84
+ value: undefined, placeholder: 'Send a message...', loading: false,
85
+ suggestions: undefined, suggestionMode: 'submit', proseSize: 'sm',
86
+ codeTheme: 'github-dark-dimmed', codeHighlight: true, chatTitle: undefined,
87
+ models: undefined, currentModel: undefined, context: undefined, scrollButton: true,
88
+ search: false, voice: false, slashCommands: undefined, slashActiveIds: undefined, slashCompact: false,
89
+ sidebarWidth: 22, sidebarMinWidth: 200, sidebarMaxWidth: 420,
90
+ sidebarCollapsed: undefined, defaultSidebarCollapsed: undefined,
91
+ }, (props, { dispatch, flag }) => {
92
+ // Controlled/uncontrolled collapse: `sidebarCollapsed` (when set) wins;
93
+ // otherwise the element manages its own state, seeded from
94
+ // `defaultSidebarCollapsed`. `toggle` always writes the internal value (a no-op
95
+ // visually while controlled) and emits `kai-sidebar-toggle` so a controlling app
96
+ // can update its own state.
97
+ const [collapsed, setCollapsed] = createControllableSignal(
98
+ () => props.sidebarCollapsed as boolean | undefined,
99
+ flag('defaultSidebarCollapsed'),
100
+ );
101
+ const toggle = () => { const next = !collapsed(); setCollapsed(next); dispatch('kai-sidebar-toggle', { collapsed: next }); };
102
+
103
+ // Create the thread ONCE and reference the same node in both <Show> branches.
104
+ // It's owned by this component root (not by a Show branch), so toggling the
105
+ // sidebar moves the node between branches without disposing it — the thread's
106
+ // own state (e.g. an uncontrolled draft) survives the collapse/expand.
107
+ const threadEl = (
108
+ <ChatThread
109
+ messages={props.messages} value={props.value as string | undefined} placeholder={props.placeholder as string}
110
+ loading={flag('loading')} suggestions={props.suggestions as string[] | undefined}
111
+ suggestionMode={props.suggestionMode as 'submit' | 'fill'} proseSize={props.proseSize as ProseSize}
112
+ codeTheme={props.codeTheme as string} codeHighlight={flag('codeHighlight')}
113
+ chatTitle={props.chatTitle as string | undefined} models={props.models as ModelOption[] | undefined}
114
+ currentModel={props.currentModel as string | undefined} context={props.context as ChatThreadContextUsage | undefined}
115
+ scrollButton={props.scrollButton !== false} search={flag('search')} voice={flag('voice')}
116
+ slashCommands={props.slashCommands as SlashCommandItem[] | undefined}
117
+ slashActiveIds={props.slashActiveIds as string[] | undefined} slashCompact={flag('slashCompact')}
118
+ onValueChange={(value) => dispatch('kai-value-change', { value })}
119
+ onSubmit={(detail) => dispatch('kai-submit', detail)}
120
+ onSuggestionClick={(value) => dispatch('kai-suggestion-click', { value })}
121
+ onModelChange={(modelId) => dispatch('kai-model-change', { modelId })}
122
+ onMessageAction={(detail) => dispatch('kai-message-action', detail)}
123
+ onSearch={() => dispatch('kai-search', {})}
124
+ onVoice={() => dispatch('kai-voice', {})}
125
+ onSlashSelect={(command) => dispatch('kai-slash-select', { command })}
126
+ />
127
+ );
128
+
129
+ return (
130
+ <div class="h-full w-full overflow-hidden bg-background">
131
+ <Show
132
+ when={!collapsed()}
133
+ fallback={
134
+ <div class="relative h-full">
135
+ <Button
136
+ variant="ghost" size="icon-sm" aria-label="Open sidebar"
137
+ class="absolute left-2 top-2 z-10 rounded-full bg-card/80 shadow-sm backdrop-blur"
138
+ onClick={toggle}
139
+ >
140
+ <PanelLeftOpen class="size-4" />
141
+ </Button>
142
+ {threadEl}
143
+ </div>
144
+ }
145
+ >
146
+ <ResizablePanelGroup orientation="horizontal">
147
+ <ResizablePanel defaultSize={props.sidebarWidth as number} data-min-size={String(props.sidebarMinWidth)} data-max-size={String(props.sidebarMaxWidth)}>
148
+ <ConversationList
149
+ groups={props.groups} conversations={props.conversations} activeId={props.activeId as string | undefined}
150
+ onSelect={(id) => dispatch('kai-conversation-select', { id })}
151
+ onNewChat={() => dispatch('kai-new-chat', {})}
152
+ onToggleSidebar={toggle}
153
+ />
154
+ </ResizablePanel>
155
+ <ResizableHandle withHandle />
156
+ <ResizablePanel>{threadEl}</ResizablePanel>
157
+ </ResizablePanelGroup>
158
+ </Show>
159
+ </div>
160
+ );
161
+ });
@@ -0,0 +1,151 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { onMount } from 'solid-js';
3
+ import './register'; // side effect: registers <kai-chat>, <kai-conversations>, <kai-prompt-input>
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-chat': JSX.HTMLAttributes<HTMLElement>;
13
+ }
14
+ }
15
+ }
16
+
17
+ const sampleMessages: ChatMessage[] = [
18
+ { id: '1', role: 'user', content: 'How do I center a div?' },
19
+ {
20
+ id: '2',
21
+ role: 'assistant',
22
+ content:
23
+ 'The modern way is a grid:\n\n```css\n.box {\n display: grid;\n place-items: center;\n}\n```\n\nThat centers the child on both axes.',
24
+ actions: ['copy', 'like', 'dislike'],
25
+ },
26
+ ];
27
+
28
+ type ChatEl = HTMLElement & {
29
+ messages?: ChatMessage[];
30
+ value?: string;
31
+ placeholder?: string;
32
+ loading?: boolean;
33
+ suggestionMode?: string;
34
+ proseSize?: string;
35
+ codeTheme?: string;
36
+ codeHighlight?: boolean;
37
+ chatTitle?: string;
38
+ currentModel?: string;
39
+ scrollButton?: boolean;
40
+ search?: boolean;
41
+ voice?: boolean;
42
+ slashCompact?: boolean;
43
+ };
44
+
45
+ /** Live demo of the actual `<kai-chat>` custom element (Shadow DOM and all). */
46
+ function ChatElement(props: { args?: Record<string, unknown> }) {
47
+ let el: ChatEl | undefined;
48
+ onMount(() => {
49
+ if (el) {
50
+ // Fixed array data
51
+ el.messages = sampleMessages;
52
+ // Scalar args from Controls
53
+ const args = props.args;
54
+ if (args) {
55
+ const scalarNames = [
56
+ 'value', 'placeholder', 'loading', 'suggestionMode', 'proseSize',
57
+ 'codeTheme', 'codeHighlight', 'chatTitle', 'currentModel',
58
+ 'scrollButton', 'search', 'voice', 'slashCompact',
59
+ ];
60
+ for (const name of scalarNames) {
61
+ if (name in args) (el as unknown as Record<string, unknown>)[name] = args[name];
62
+ }
63
+ }
64
+ }
65
+ });
66
+ return <kai-chat ref={(e) => (el = e as ChatEl)} style={{ display: 'block', height: '560px' }} />;
67
+ }
68
+
69
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
70
+ <kai-chat id="chat" style="display:block; height:100vh;"></kai-chat>
71
+
72
+ <script type="module">
73
+ import '@kitn.ai/ui/elements'; // registers the custom elements
74
+
75
+ const chat = document.getElementById('chat');
76
+ chat.messages = [
77
+ { id: '1', role: 'user', content: 'How do I center a div?' },
78
+ { id: '2', role: 'assistant', content: 'Use \`display: grid; place-items: center;\`' },
79
+ ];
80
+
81
+ // events are CustomEvents on the element (they do not bubble)
82
+ chat.addEventListener('kai-submit', (e) => console.log('user sent:', e.detail.value));
83
+ </script>`;
84
+
85
+ const SOLID_SNIPPET = `import '@kitn.ai/ui/elements'; // registers the custom elements
86
+ import { onMount } from 'solid-js';
87
+ import type { ChatMessage } from '@kitn.ai/ui/elements';
88
+
89
+ function Chat() {
90
+ let el: HTMLElement & { messages?: ChatMessage[] };
91
+ const messages: ChatMessage[] = [
92
+ { id: '1', role: 'user', content: 'How do I center a div?' },
93
+ { id: '2', role: 'assistant', content: 'Use \`display: grid; place-items: center;\`' },
94
+ ];
95
+ onMount(() => { el.messages = messages; });
96
+ return (
97
+ <kai-chat
98
+ ref={el}
99
+ style={{ display: 'block', height: '100vh' }}
100
+ on:kai-submit={(e) => console.log('user sent:', e.detail.value)}
101
+ />
102
+ );
103
+ }`;
104
+
105
+ const meta = {
106
+ title: 'Components/Chat',
107
+ tags: ['autodocs'],
108
+ argTypes: argTypesFor('kai-chat'),
109
+ parameters: {
110
+ layout: 'fullscreen',
111
+ docs: {
112
+ description: specDescription('kai-chat', [
113
+ '`<kai-chat>` is the framework-agnostic **web component** version of the chat UI — a complete message thread plus prompt input, isolated in **Shadow DOM** so the host page\'s CSS can\'t leak in and the kit\'s styles can\'t leak out. SolidJS is bundled in, so the host needs nothing.',
114
+ '**When to use:** dropping a full chat into a non-Solid app (React, Vue, Svelte, plain HTML), or anywhere you want zero style conflicts. If you *are* in SolidJS and want fine-grained control, compose the primitives (`ChatContainer`, `Message`, `PromptInput`) instead.',
115
+ '**How to use:** register once with `import \'@kitn.ai/ui/elements\'`, set rich data as JS **properties** (`el.messages = [...]`), and listen for **CustomEvents** (`kai-submit`, `kai-message-action`, `kai-value-change`) directly on the element.',
116
+ '**Anatomy:** **header** (model switcher + context-meter, shown when `models`/`context` props are set) → **message list** (scrollable thread of `<Message>` rows, each rendered per `role`; a scroll-to-bottom button appears when scrolled up) → **prompt composer** (`<PromptInput>`: textarea + toolbar with suggestions, slash-command palette, voice/search buttons, and optional attachment area).',
117
+ '**Placement:** as a top-level panel or full-page surface. Give it an explicit height (e.g. `height: 100vh`).',
118
+ 'See the **Code** tab below for the HTML usage; the *SolidJS* story shows the same element inside a Solid component.',
119
+ ]),
120
+ },
121
+ },
122
+ } satisfies Meta;
123
+
124
+ export default meta;
125
+ type Story = StoryObj;
126
+
127
+ /** The element used the plain-HTML / any-framework way. */
128
+ export const Default: Story = {
129
+ args: {
130
+ placeholder: 'Send a message...',
131
+ loading: false,
132
+ suggestionMode: 'submit',
133
+ proseSize: 'sm',
134
+ codeTheme: 'github-dark-dimmed',
135
+ codeHighlight: true,
136
+ scrollButton: true,
137
+ search: false,
138
+ voice: false,
139
+ slashCompact: false,
140
+ },
141
+ render: (args: Record<string, unknown>) => <ChatElement args={args} />,
142
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
143
+ };
144
+
145
+ /** The same element used inside a SolidJS component (properties via `ref`, events via `on:`). */
146
+ export const InSolidJS: Story = {
147
+ name: 'In SolidJS',
148
+ render: () => <ChatElement />,
149
+ parameters: { docs: { source: { code: SOLID_SNIPPET, language: 'tsx' } } },
150
+ };
151
+