@kitn.ai/ui 0.15.1 → 0.17.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 (333) hide show
  1. package/README.md +37 -13
  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 +91 -0
  13. package/dist/components/checkpoint.d.ts +20 -0
  14. package/dist/components/choice-card.d.ts +78 -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/dismissed-stub.d.ts +22 -0
  21. package/dist/components/embed.d.ts +21 -0
  22. package/dist/components/empty.d.ts +43 -0
  23. package/dist/components/feedback-bar.d.ts +45 -0
  24. package/dist/components/file-tree.d.ts +57 -0
  25. package/dist/components/file-upload.d.ts +16 -0
  26. package/dist/components/form-widgets.d.ts +31 -0
  27. package/dist/components/form.d.ts +103 -0
  28. package/dist/components/image.d.ts +12 -0
  29. package/dist/components/link-preview.d.ts +23 -0
  30. package/dist/components/loader.d.ts +60 -0
  31. package/dist/components/markdown.d.ts +8 -0
  32. package/dist/components/message-skills.d.ts +15 -0
  33. package/dist/components/message.d.ts +111 -0
  34. package/dist/components/model-switcher.d.ts +8 -0
  35. package/dist/components/prompt-input.d.ts +38 -0
  36. package/dist/components/prompt-suggestion.d.ts +13 -0
  37. package/dist/components/reasoning.d.ts +20 -0
  38. package/dist/components/response-compare-types.d.ts +66 -0
  39. package/dist/components/response-compare.d.ts +51 -0
  40. package/dist/components/response-stream.d.ts +14 -0
  41. package/dist/components/scroll-button.d.ts +7 -0
  42. package/dist/components/slash-command.d.ts +15 -0
  43. package/dist/components/source.d.ts +23 -0
  44. package/dist/components/tasks-card.d.ts +72 -0
  45. package/dist/components/text-shimmer.d.ts +9 -0
  46. package/dist/components/thinking-bar.d.ts +10 -0
  47. package/dist/components/toast.d.ts +48 -0
  48. package/dist/components/tool.d.ts +16 -0
  49. package/dist/components/use-card-resolution.d.ts +16 -0
  50. package/dist/components/voice-input.d.ts +7 -0
  51. package/dist/core-AYMC6_lb.js +5874 -12
  52. package/dist/css-M7EaDHN_.js +6 -1
  53. package/dist/custom-elements.json +260 -11
  54. package/dist/elements/artifact.d.ts +1 -0
  55. package/dist/elements/artifact.js +1 -0
  56. package/dist/elements/attachments.d.ts +1 -0
  57. package/dist/elements/attachments.js +1 -0
  58. package/dist/elements/autoloader.d.ts +2 -0
  59. package/dist/elements/autoloader.js +1 -0
  60. package/dist/elements/card.d.ts +1 -0
  61. package/dist/elements/card.js +1 -0
  62. package/dist/elements/cards.d.ts +1 -0
  63. package/dist/elements/cards.js +1 -0
  64. package/dist/elements/chain-of-thought.d.ts +1 -0
  65. package/dist/elements/chain-of-thought.js +1 -0
  66. package/dist/elements/chat-scope-picker.d.ts +1 -0
  67. package/dist/elements/chat-scope-picker.js +1 -0
  68. package/dist/elements/chat-workspace.d.ts +1 -0
  69. package/dist/elements/chat-workspace.js +1 -0
  70. package/dist/elements/chat.d.ts +1 -0
  71. package/dist/elements/chat.js +1 -0
  72. package/dist/elements/checkpoint.d.ts +1 -0
  73. package/dist/elements/checkpoint.js +1 -0
  74. package/dist/elements/choice.d.ts +1 -0
  75. package/dist/elements/choice.js +1 -0
  76. package/dist/elements/chunks/Icon-D_Zmwmwh.js +1 -0
  77. package/dist/elements/chunks/action-icons-PVfIvcuG.js +1 -0
  78. package/dist/elements/chunks/attachments-Dp6vqtrc.js +1 -0
  79. package/dist/elements/chunks/bash-InADTalH.js +1 -0
  80. package/dist/elements/chunks/button-CKyYD9G4.js +1 -0
  81. package/dist/elements/chunks/card-Cjho-PYk.js +1 -0
  82. package/dist/elements/chunks/card-routing-C986KuUA.js +1 -0
  83. package/dist/elements/chunks/chat-thread-RT808iPf.js +1 -0
  84. package/dist/elements/chunks/check-CDYaMYrM.js +1 -0
  85. package/dist/elements/chunks/chevron-down-3SBiq8HF.js +1 -0
  86. package/dist/elements/chunks/chevron-right-k2UFlIVK.js +1 -0
  87. package/dist/elements/chunks/cn-Dogu6PbV.js +1 -0
  88. package/dist/elements/chunks/code-block-BYUmfFSd.js +1 -0
  89. package/dist/elements/chunks/collapsible-BiNwdOn6.js +1 -0
  90. package/dist/elements/chunks/context-18g6oi4J.js +1 -0
  91. package/dist/elements/chunks/conversation-list-D9Y0NVdo.js +1 -0
  92. package/dist/elements/chunks/core-AYMC6_lb.js +12 -0
  93. package/dist/elements/chunks/css-M7EaDHN_.js +1 -0
  94. package/dist/elements/chunks/default-input-CSAoYYMd.js +1 -0
  95. package/dist/elements/chunks/download-D8K0Je8r.js +1 -0
  96. package/dist/elements/chunks/dropdown-bUs2iTia.js +1 -0
  97. package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +141 -0
  98. package/dist/elements/chunks/external-link-6mA0wxab.js +1 -0
  99. package/dist/elements/chunks/file-text-DnaYk3Dm.js +1 -0
  100. package/dist/elements/chunks/file-tree-CgtRLlxK.js +1 -0
  101. package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +1 -0
  102. package/dist/elements/chunks/github-light-JYsPkUQd.js +1 -0
  103. package/dist/elements/chunks/hover-card-CIGw62gP.js +1 -0
  104. package/dist/elements/chunks/html-CPZ3oZQ7.js +1 -0
  105. package/dist/elements/chunks/index-CTBEJeo6.js +1 -0
  106. package/dist/elements/chunks/javascript-C25yR2R2.js +1 -0
  107. package/dist/elements/chunks/json-DxJze_jm.js +1 -0
  108. package/dist/elements/chunks/link-BlGSeoPy.js +1 -0
  109. package/dist/elements/chunks/loader-DvhR4qa0.js +1 -0
  110. package/dist/elements/chunks/markdown-CMTGx9r8.js +64 -0
  111. package/dist/elements/chunks/message-CDZwXjhL.js +1 -0
  112. package/dist/elements/chunks/message-feedback-CYx81QiA.js +1 -0
  113. package/dist/elements/chunks/model-switcher-O_tUhoOT.js +1 -0
  114. package/dist/elements/chunks/overlay-DydXYMQW.js +1 -0
  115. package/dist/elements/chunks/prompt-suggestion-ldlWV5kd.js +1 -0
  116. package/dist/elements/chunks/reasoning-2PUnHlmr.js +1 -0
  117. package/dist/elements/chunks/resizable-B5WNbSeb.js +1 -0
  118. package/dist/elements/chunks/star-t9tI-P0q.js +1 -0
  119. package/dist/elements/chunks/store-C6rIooZz.js +1 -0
  120. package/dist/elements/chunks/svelte--5p79yCD.js +1 -0
  121. package/dist/elements/chunks/text-shimmer-uh9V_KUh.js +1 -0
  122. package/dist/elements/chunks/textarea-3nm6Ia9U.js +1 -0
  123. package/dist/elements/chunks/thumbs-up-BBtguGld.js +1 -0
  124. package/dist/elements/chunks/toast-store-D8cL3fWl.js +1 -0
  125. package/dist/elements/chunks/tool-GJ3vwp1U.js +1 -0
  126. package/dist/elements/chunks/tooltip-DwKAz4XR.js +1 -0
  127. package/dist/elements/chunks/triangle-alert-Ct1zt2JL.js +1 -0
  128. package/dist/elements/chunks/tsx-B8rCNbgL.js +1 -0
  129. package/dist/elements/chunks/typescript-RycA9KXf.js +1 -0
  130. package/dist/elements/chunks/use-card-resolution-D-_0Alzy.js +1 -0
  131. package/dist/elements/chunks/video-mVYiM99-.js +1 -0
  132. package/dist/elements/chunks/vue-BmIZj4XD.js +1 -0
  133. package/dist/elements/chunks/x-CzxL27Ch.js +1 -0
  134. package/dist/elements/code-block.d.ts +1 -0
  135. package/dist/elements/code-block.js +1 -0
  136. package/dist/elements/compare.d.ts +1 -0
  137. package/dist/elements/compare.js +1 -0
  138. package/dist/elements/confirm-card.d.ts +1 -0
  139. package/dist/elements/confirm-card.js +1 -0
  140. package/dist/elements/context-meter.d.ts +1 -0
  141. package/dist/elements/context-meter.js +1 -0
  142. package/dist/elements/conversation-list.d.ts +1 -0
  143. package/dist/elements/conversation-list.js +1 -0
  144. package/dist/elements/embed.d.ts +1 -0
  145. package/dist/elements/embed.js +1 -0
  146. package/dist/elements/empty.d.ts +1 -0
  147. package/dist/elements/empty.js +1 -0
  148. package/dist/elements/feedback-bar.d.ts +1 -0
  149. package/dist/elements/feedback-bar.js +1 -0
  150. package/dist/elements/file-tree.d.ts +1 -0
  151. package/dist/elements/file-tree.js +1 -0
  152. package/dist/elements/file-upload.d.ts +1 -0
  153. package/dist/elements/file-upload.js +1 -0
  154. package/dist/elements/form.d.ts +1 -0
  155. package/dist/elements/form.js +1 -0
  156. package/dist/elements/image.d.ts +1 -0
  157. package/dist/elements/image.js +1 -0
  158. package/dist/elements/link-preview.d.ts +1 -0
  159. package/dist/elements/link-preview.js +1 -0
  160. package/dist/elements/loader.d.ts +1 -0
  161. package/dist/elements/loader.js +1 -0
  162. package/dist/elements/markdown.d.ts +1 -0
  163. package/dist/elements/markdown.js +1 -0
  164. package/dist/elements/message-skills.d.ts +1 -0
  165. package/dist/elements/message-skills.js +1 -0
  166. package/dist/elements/message.d.ts +1 -0
  167. package/dist/elements/message.js +1 -0
  168. package/dist/elements/model-switcher.d.ts +1 -0
  169. package/dist/elements/model-switcher.js +1 -0
  170. package/dist/elements/popover.d.ts +1 -0
  171. package/dist/elements/popover.js +1 -0
  172. package/dist/elements/prompt-input.d.ts +1 -0
  173. package/dist/elements/prompt-input.js +1 -0
  174. package/dist/elements/prompt-suggestions.d.ts +1 -0
  175. package/dist/elements/prompt-suggestions.js +1 -0
  176. package/dist/elements/reasoning.d.ts +1 -0
  177. package/dist/elements/reasoning.js +1 -0
  178. package/dist/elements/resizable.d.ts +1 -0
  179. package/dist/elements/resizable.js +1 -0
  180. package/dist/elements/response-stream.d.ts +1 -0
  181. package/dist/elements/response-stream.js +14 -0
  182. package/dist/elements/scroll-button.d.ts +1 -0
  183. package/dist/elements/scroll-button.js +1 -0
  184. package/dist/elements/source.d.ts +1 -0
  185. package/dist/elements/source.js +1 -0
  186. package/dist/elements/switch.d.ts +1 -0
  187. package/dist/elements/switch.js +1 -0
  188. package/dist/elements/tasks.d.ts +1 -0
  189. package/dist/elements/tasks.js +1 -0
  190. package/dist/elements/text-shimmer.d.ts +1 -0
  191. package/dist/elements/text-shimmer.js +1 -0
  192. package/dist/elements/thinking-bar.d.ts +1 -0
  193. package/dist/elements/thinking-bar.js +1 -0
  194. package/dist/elements/toast.d.ts +1 -0
  195. package/dist/elements/toast.js +1 -0
  196. package/dist/elements/tool.d.ts +1 -0
  197. package/dist/elements/tool.js +1 -0
  198. package/dist/elements/voice-input.d.ts +1 -0
  199. package/dist/elements/voice-input.js +1 -0
  200. package/dist/elements.d.ts +768 -0
  201. package/dist/engine-javascript-vq0WuIJl.js +2516 -14
  202. package/dist/github-dark-dimmed-DUshB20C.js +4 -1
  203. package/dist/github-light-JYsPkUQd.js +4 -1
  204. package/dist/html-CPZ3oZQ7.js +10 -1
  205. package/dist/index.d.ts +124 -2
  206. package/dist/index.js +15593 -0
  207. package/dist/javascript-C25yR2R2.js +6 -1
  208. package/dist/json-DxJze_jm.js +6 -1
  209. package/dist/kitn-chat.es.js +1 -77
  210. package/dist/llms/llms-full.txt +57 -12
  211. package/dist/llms/llms.txt +3 -3
  212. package/dist/mcp.es.js +2296 -0
  213. package/dist/primitives/card-contract.d.ts +119 -0
  214. package/dist/primitives/card-host.d.ts +12 -0
  215. package/dist/primitives/card-recovery.d.ts +55 -0
  216. package/dist/primitives/card-registry.d.ts +17 -0
  217. package/dist/primitives/card-resolution.d.ts +8 -0
  218. package/dist/primitives/card-routing.d.ts +12 -0
  219. package/dist/primitives/card-validate.d.ts +25 -0
  220. package/dist/primitives/chat-config.d.ts +31 -0
  221. package/dist/primitives/controllable.d.ts +13 -0
  222. package/dist/primitives/embed-providers.d.ts +65 -0
  223. package/dist/primitives/highlighter.d.ts +23 -0
  224. package/dist/primitives/link-preview.d.ts +47 -0
  225. package/dist/primitives/message-feedback.d.ts +35 -0
  226. package/dist/primitives/pdf-preview.d.ts +50 -0
  227. package/dist/primitives/toast-store.d.ts +88 -0
  228. package/dist/primitives/use-auto-resize.d.ts +8 -0
  229. package/dist/primitives/use-resize-observer.d.ts +1 -0
  230. package/dist/primitives/use-stick-to-bottom.d.ts +5 -0
  231. package/dist/primitives/use-text-stream.d.ts +19 -0
  232. package/dist/primitives/use-voice-recorder.d.ts +9 -0
  233. package/dist/react/index.d.ts +1198 -0
  234. package/dist/react/runtime.d.ts +15 -0
  235. package/dist/react.js +275 -0
  236. package/dist/register-JyTglR6m.js +1 -0
  237. package/dist/register-impl-C6wlSsts.js +77 -0
  238. package/dist/remote/host-embed.d.ts +33 -0
  239. package/dist/remote/index.d.ts +2 -0
  240. package/dist/remote/origin.d.ts +5 -0
  241. package/dist/remote/provider-runtime.d.ts +27 -0
  242. package/dist/remote/provider.d.ts +2 -0
  243. package/dist/remote/validate.d.ts +4 -0
  244. package/dist/remote/version.d.ts +4 -0
  245. package/dist/remote/wire.d.ts +53 -0
  246. package/dist/schemas/choice.schema.json +5 -0
  247. package/dist/schemas/tasks.schema.json +5 -0
  248. package/dist/svelte--5p79yCD.js +15 -1
  249. package/dist/tsx-B8rCNbgL.js +6 -1
  250. package/dist/types.d.ts +42 -0
  251. package/dist/typescript-RycA9KXf.js +6 -1
  252. package/dist/ui/action-icons.d.ts +11 -0
  253. package/dist/ui/avatar.d.ts +8 -0
  254. package/dist/ui/badge.d.ts +9 -0
  255. package/dist/ui/button.d.ts +10 -0
  256. package/dist/ui/collapsible.d.ts +27 -0
  257. package/dist/ui/dropdown.d.ts +20 -0
  258. package/dist/ui/hover-card.d.ts +28 -0
  259. package/dist/ui/overlay.d.ts +67 -0
  260. package/dist/ui/popover.d.ts +32 -0
  261. package/dist/ui/resizable.d.ts +76 -0
  262. package/dist/ui/scroll-area.d.ts +5 -0
  263. package/dist/ui/separator.d.ts +5 -0
  264. package/dist/ui/skeleton.d.ts +5 -0
  265. package/dist/ui/switch.d.ts +19 -0
  266. package/dist/ui/textarea.d.ts +6 -0
  267. package/dist/ui/tooltip.d.ts +9 -0
  268. package/dist/utils/cn.d.ts +2 -0
  269. package/dist/vue-BmIZj4XD.js +33 -1
  270. package/frameworks/react/index.tsx +67 -11
  271. package/frameworks/react/runtime.tsx +16 -2
  272. package/llms-full.txt +57 -12
  273. package/llms.txt +3 -3
  274. package/package.json +36 -14
  275. package/src/agent-tooling/archetypes.ts +46 -0
  276. package/src/agent-tooling/integrations/cloudflare.ts +88 -0
  277. package/src/agent-tooling/integrations/langgraph.ts +60 -0
  278. package/src/agent-tooling/integrations/mastra.ts +27 -0
  279. package/src/agent-tooling/integrations/mock.ts +34 -0
  280. package/src/agent-tooling/integrations/ollama.ts +47 -0
  281. package/src/agent-tooling/integrations/openrouter.ts +26 -0
  282. package/src/agent-tooling/integrations/pi.ts +40 -0
  283. package/src/agent-tooling/integrations/pydantic-ai.ts +52 -0
  284. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +44 -0
  285. package/src/agent-tooling/mcp/manifest.ts +128 -0
  286. package/src/agent-tooling/mcp/server.ts +59 -0
  287. package/src/agent-tooling/mcp/stdio.ts +18 -0
  288. package/src/agent-tooling/mcp/tools/debug.ts +489 -0
  289. package/src/agent-tooling/mcp/tools/reference.ts +132 -0
  290. package/src/agent-tooling/mcp/tools/scaffold.ts +1759 -0
  291. package/src/agent-tooling/mcp/tools/theme.ts +340 -0
  292. package/src/agent-tooling/mcp/tools/types.ts +14 -0
  293. package/src/agent-tooling/registry.ts +41 -0
  294. package/src/agent-tooling/types.ts +30 -0
  295. package/src/components/chat-thread.tsx +27 -35
  296. package/src/components/choice-card.tsx +33 -4
  297. package/src/components/component-meta.json +250 -26
  298. package/src/components/confirm-card.tsx +15 -1
  299. package/src/components/dismissed-stub.tsx +80 -0
  300. package/src/components/form.tsx +21 -3
  301. package/src/components/message.tsx +151 -10
  302. package/src/components/response-compare-types.ts +127 -0
  303. package/src/components/response-compare.tsx +431 -0
  304. package/src/components/tasks-card.tsx +33 -3
  305. package/src/components/toast.tsx +398 -0
  306. package/src/components/use-card-resolution.ts +24 -8
  307. package/src/elements/autoloader.ts +89 -0
  308. package/src/elements/chat-types.ts +7 -0
  309. package/src/elements/chat-workspace.tsx +4 -2
  310. package/src/elements/chat.tsx +4 -2
  311. package/src/elements/compare.tsx +88 -0
  312. package/src/elements/compiled.css +1 -1
  313. package/src/elements/define.tsx +7 -1
  314. package/src/elements/element-manifest.json +182 -0
  315. package/src/elements/element-meta.json +191 -68
  316. package/src/elements/element-types.d.ts +43 -6
  317. package/src/elements/framework-usage.json +25 -0
  318. package/src/elements/message.tsx +27 -45
  319. package/src/elements/register-impl.ts +58 -0
  320. package/src/elements/register.ts +26 -51
  321. package/src/elements/remote.tsx +4 -0
  322. package/src/elements/styles.css +19 -0
  323. package/src/elements/toast.tsx +74 -0
  324. package/src/index.ts +30 -0
  325. package/src/primitives/card-contract.ts +14 -5
  326. package/src/primitives/card-recovery.ts +149 -0
  327. package/src/primitives/card-routing.ts +3 -0
  328. package/src/primitives/card-schemas/choice.schema.json +5 -0
  329. package/src/primitives/card-schemas/tasks.schema.json +5 -0
  330. package/src/primitives/message-feedback.ts +123 -0
  331. package/src/primitives/toast-store.ts +235 -0
  332. package/src/ui/tooltip.tsx +8 -2
  333. package/theme.css +4 -2
@@ -12,6 +12,7 @@ import {
12
12
  import { cn } from '../utils/cn';
13
13
  import { Button } from '../ui/button';
14
14
  import { Card } from './card';
15
+ import { DismissedStub } from './dismissed-stub';
15
16
  import type { CardEnvelope, CardEvent, CardHost, CardResolution } from '../primitives/card-contract';
16
17
  import { useCardResolution } from './use-card-resolution';
17
18
  import { emitCardEvent } from '../primitives/card-routing';
@@ -208,7 +209,15 @@ export function ConfirmCard(props: ConfirmCardProps): JSX.Element {
208
209
  });
209
210
  };
210
211
 
211
- const onDismiss = (): void => emit({ kind: 'dismiss', cardId: local.cardId });
212
+ // Dismiss: emit the contract `dismiss` AND optimistically flip to a `dismissed`
213
+ // resolution so the card collapses to its re-openable stub immediately (the host
214
+ // confirms/overrides via the resolution prop on the next render).
215
+ const onDismiss = (): void => {
216
+ if (res.isResolved()) return;
217
+ emit({ kind: 'dismiss', cardId: local.cardId });
218
+ res.setLocal({ kind: 'dismissed' });
219
+ };
220
+ const onReopen = (): void => emit({ kind: 'reopen', cardId: local.cardId });
212
221
 
213
222
  let bodyRef: HTMLDivElement | undefined;
214
223
 
@@ -229,6 +238,10 @@ export function ConfirmCard(props: ConfirmCardProps): JSX.Element {
229
238
  return <Card heading={local.heading} errorMessage="The card failed to render." />;
230
239
  }}
231
240
  >
241
+ <Show
242
+ when={!res.isDeferred()}
243
+ fallback={<DismissedStub type={CONFIRM_CARD_TYPE} title={local.heading} onReopen={onReopen} />}
244
+ >
232
245
  <Card
233
246
  heading={local.heading}
234
247
  actions={
@@ -309,6 +322,7 @@ export function ConfirmCard(props: ConfirmCardProps): JSX.Element {
309
322
  </Show>
310
323
  </div>
311
324
  </Card>
325
+ </Show>
312
326
  </ErrorBoundary>
313
327
  </Show>
314
328
  );
@@ -0,0 +1,80 @@
1
+ import { type JSX, Show } from 'solid-js';
2
+ import { cn } from '../utils/cn';
3
+ import { Button } from '../ui/button';
4
+ import { Card } from './card';
5
+ import { RotateCcw } from 'lucide-solid';
6
+
7
+ // ─────────────────────────────────────────────────────────────────────────────
8
+ // DismissedStub — the collapsed, re-openable read-only view a card renders once
9
+ // the user has dismissed it (a DEFERRED resolution, not a terminal one). It's a
10
+ // dense `Card` showing "<intent>: <title> — dismissed" + a ghost Reopen button.
11
+ // Pure/presentational: the owning card wires `onReopen` to emit `{kind:'reopen'}`.
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+
14
+ /** The card types that render a dismissed stub, mapped to their intent verb. */
15
+ export type DismissedCardType = 'confirm' | 'choice' | 'form' | 'tasks';
16
+
17
+ const INTENT_LABELS: Record<DismissedCardType, string> = {
18
+ confirm: 'Proposed',
19
+ choice: 'Choose',
20
+ form: 'Form',
21
+ tasks: 'Tasks',
22
+ };
23
+
24
+ /** Map a card `type` to its dismissed-stub intent label ("Proposed:", "Choose:",
25
+ * "Form:", "Tasks:"). Falls back to a capitalized type for unknown card types. */
26
+ export function stubIntent(type: string): string {
27
+ const known = INTENT_LABELS[type as DismissedCardType];
28
+ if (known) return `${known}:`;
29
+ if (type.length === 0) return 'Card:';
30
+ return `${type.charAt(0).toUpperCase()}${type.slice(1)}:`;
31
+ }
32
+
33
+ export interface DismissedStubProps {
34
+ /** The card type, used to derive the intent label ("Proposed:" etc.). */
35
+ type: string;
36
+ /** The card's title/heading; shown after the intent label. Falls back gracefully. */
37
+ title?: string;
38
+ /** Re-open handler. The owning card emits `{ kind:'reopen', cardId }`. */
39
+ onReopen: () => void;
40
+ /** The Reopen button label. Default 'Reopen'. */
41
+ reopenLabel?: string;
42
+ class?: string;
43
+ }
44
+
45
+ /**
46
+ * `DismissedStub` — the compact stub a card collapses to after it's been dismissed.
47
+ * Group-labelled for a11y; the Reopen affordance is a ghost button.
48
+ */
49
+ export function DismissedStub(props: DismissedStubProps): JSX.Element {
50
+ const intent = (): string => stubIntent(props.type);
51
+ const title = (): string => props.title ?? 'this card';
52
+ const label = (): string => props.reopenLabel ?? 'Reopen';
53
+ const groupLabel = (): string => `${intent()} ${title()} — dismissed`;
54
+
55
+ return (
56
+ <Card dense class={props.class}>
57
+ <div
58
+ role="group"
59
+ aria-label={groupLabel()}
60
+ class="flex flex-wrap items-center gap-x-2 gap-y-1"
61
+ >
62
+ <span class="min-w-0 text-sm text-muted-foreground">
63
+ <span class="font-medium text-foreground">{intent()}</span>{' '}
64
+ <span class="text-foreground">{title()}</span>
65
+ <span class="text-muted-foreground"> — dismissed</span>
66
+ </span>
67
+ <Button
68
+ type="button"
69
+ variant="ghost"
70
+ size="sm"
71
+ class={cn('ml-auto gap-1.5')}
72
+ onClick={() => props.onReopen()}
73
+ >
74
+ <RotateCcw size={14} aria-hidden="true" />
75
+ <Show when={label()}>{label()}</Show>
76
+ </Button>
77
+ </div>
78
+ </Card>
79
+ );
80
+ }
@@ -14,6 +14,7 @@ import { createStore, produce, unwrap } from 'solid-js/store';
14
14
  import { cn } from '../utils/cn';
15
15
  import { Button } from '../ui/button';
16
16
  import { Card } from './card';
17
+ import { DismissedStub } from './dismissed-stub';
17
18
  import {
18
19
  validateAgainstSchema,
19
20
  type JsonSchema,
@@ -437,11 +438,12 @@ export function Form(props: FormProps): JSX.Element {
437
438
  else emit({ kind: 'error', cardId: local.cardId, message: state.message });
438
439
  }));
439
440
 
440
- // Surface the resolved state for host styling.
441
+ // Surface the resolved state for host styling. Only a `submit` resolution is the
442
+ // "submitted" state; a deferred `dismissed` (or `expired`) is not.
441
443
  createEffect(() => {
442
444
  const el = local.hostElement;
443
445
  if (!el) return;
444
- if (res.isResolved()) el.setAttribute('data-kai-resolved', 'submitted');
446
+ if (res.resolution()?.kind === 'submit') el.setAttribute('data-kai-resolved', 'submitted');
445
447
  else el.removeAttribute('data-kai-resolved');
446
448
  });
447
449
 
@@ -488,6 +490,15 @@ export function Form(props: FormProps): JSX.Element {
488
490
  res.setLocal({ kind: 'submit', data: out });
489
491
  };
490
492
 
493
+ // Dismiss: emit `dismiss` AND optimistically flip to a `dismissed` resolution so
494
+ // the form collapses to its re-openable stub immediately.
495
+ const onDismiss = (): void => {
496
+ if (res.isResolved()) return;
497
+ emit({ kind: 'dismiss', cardId: local.cardId });
498
+ res.setLocal({ kind: 'dismissed' });
499
+ };
500
+ const onReopen = (): void => emit({ kind: 'reopen', cardId: local.cardId });
501
+
491
502
  const actions = createMemo(() => def()['x-kai-actions'] ?? []);
492
503
  const submitLabel = () => def()['x-kai-submitLabel'] ?? 'Submit';
493
504
  const dismissible = () => def()['x-kai-dismissible'] === true;
@@ -509,6 +520,12 @@ export function Form(props: FormProps): JSX.Element {
509
520
  return <Card heading={local.heading} errorMessage="The form failed to render." />;
510
521
  }}
511
522
  >
523
+ <Show
524
+ when={!res.isDeferred()}
525
+ fallback={
526
+ <DismissedStub type="form" title={local.heading ?? def().title} onReopen={onReopen} />
527
+ }
528
+ >
512
529
  <Card
513
530
  heading={local.heading ?? def().title}
514
531
  description={def().description}
@@ -522,7 +539,7 @@ export function Form(props: FormProps): JSX.Element {
522
539
  <Button
523
540
  type="button"
524
541
  variant="ghost"
525
- onClick={() => emit({ kind: 'dismiss', cardId: local.cardId })}
542
+ onClick={onDismiss}
526
543
  >
527
544
  Dismiss
528
545
  </Button>
@@ -575,6 +592,7 @@ export function Form(props: FormProps): JSX.Element {
575
592
  </form>
576
593
  </Show>
577
594
  </Card>
595
+ </Show>
578
596
  </ErrorBoundary>
579
597
  </Show>
580
598
  );
@@ -5,8 +5,11 @@ import { cn } from "../utils/cn";
5
5
  import { Markdown } from "./markdown";
6
6
  import { Button } from "../ui/button";
7
7
  import { actionIcon, BUILTIN_ACTION_LABEL } from "../ui/action-icons";
8
- import type { ChatMessageAction, CustomAction } from "../elements/chat-types";
8
+ import type { ChatMessageAction, CustomAction, FeedbackVote } from "../elements/chat-types";
9
9
  import { useChatConfig, textClass } from "../primitives/chat-config";
10
+ import { Reasoning, ReasoningTrigger, ReasoningContent } from "./reasoning";
11
+ import { Tool, type ToolPart } from "./tool";
12
+ import { Attachments, Attachment, AttachmentPreview, AttachmentInfo, type AttachmentData } from "./attachments";
10
13
 
11
14
  // --- Message ---
12
15
 
@@ -119,6 +122,13 @@ export interface MessageActionBarProps {
119
122
  reveal?: 'always' | 'hover';
120
123
  /** Fired with the built-in name or the custom action id when a button is clicked. */
121
124
  onAction: (id: string) => void;
125
+ /** The active feedback vote. When `'like'`/`'dislike'` is set, that button is
126
+ * marked pressed (filled) and the OTHER vote button animates out. `undefined`
127
+ * shows both. Pure/prop-driven so the bar survives re-renders. */
128
+ activeFeedback?: FeedbackVote;
129
+ /** When true, the `copy` button shows its emerald check icon instead of the
130
+ * copy glyph (cleared by the owner after ~2s). */
131
+ copied?: boolean;
122
132
  class?: string;
123
133
  }
124
134
 
@@ -130,12 +140,25 @@ function normalizeAction(a: ChatMessageAction | CustomAction) {
130
140
  return { id: a.id, label: a.label, Icon: actionIcon(a.icon) };
131
141
  }
132
142
 
143
+ /** Is this entry one of the two feedback votes? */
144
+ function feedbackVoteOf(a: ChatMessageAction | CustomAction): FeedbackVote | undefined {
145
+ return a === 'like' || a === 'dislike' ? a : undefined;
146
+ }
147
+
133
148
  /**
134
149
  * The shared message action toolbar. Renders one ghost icon button per entry —
135
150
  * built-in names pull their label+icon from the curated registry; custom
136
151
  * descriptors use their `label` plus `actionIcon(icon)` (label-only when the
137
152
  * icon is unknown or absent). `reveal="hover"` makes the bar fade in on the
138
153
  * parent `.group`'s hover.
154
+ *
155
+ * Pure/prop-driven: feedback (`activeFeedback`) and copy (`copied`) state are
156
+ * owned by the parent facade and passed in — the bar holds no internal signals,
157
+ * so it survives the new-array-per-chunk re-renders of a streaming thread. With
158
+ * a vote active, the chosen `like`/`dislike` button is marked `aria-pressed` +
159
+ * filled and the other vote button collapses its width (sliding the active thumb
160
+ * into its place) via a 0fr↔1fr grid transition. The
161
+ * `copy` button swaps to an emerald check while `copied`.
139
162
  */
140
163
  function MessageActionBar(props: MessageActionBarProps) {
141
164
  return (
@@ -150,38 +173,156 @@ function MessageActionBar(props: MessageActionBarProps) {
150
173
  {(a) => {
151
174
  const item = normalizeAction(a);
152
175
  const tooltipText = () => (typeof a !== 'string' && a.tooltip) ? a.tooltip : item.label;
176
+ const vote = feedbackVoteOf(a);
177
+ // A vote button is the active one when it matches the resolved vote.
178
+ const isActiveVote = () => vote !== undefined && props.activeFeedback === vote;
179
+ // The COPY button reflects the copied check.
180
+ const isCopy = item.id === 'copy';
181
+ const showCheck = () => isCopy && props.copied === true;
182
+
153
183
  // Factory (not a shared node): each Show branch gets its own Button so
154
184
  // the eager DOM node is never referenced from two places.
155
185
  const button = () => (
156
186
  <Button
157
187
  variant="ghost"
158
188
  size="icon-sm"
159
- class="rounded-full"
189
+ class={cn('rounded-full', isActiveVote() && 'text-primary')}
160
190
  data-action={item.id}
161
- aria-label={item.label}
191
+ aria-label={showCheck() ? 'Copied' : item.label}
192
+ aria-pressed={vote !== undefined ? isActiveVote() : undefined}
162
193
  onClick={() => props.onAction(item.id)}
163
194
  >
164
- <Show when={item.Icon} fallback={<span class="px-1 text-xs">{item.label}</span>}>
165
- {(Icon) => {
166
- const I = Icon();
167
- return <I class="size-3.5" />;
168
- }}
195
+ <Show
196
+ when={showCheck()}
197
+ fallback={
198
+ <Show when={item.Icon} fallback={<span class="px-1 text-xs">{item.label}</span>}>
199
+ {(Icon) => {
200
+ const I = Icon();
201
+ return <I class={cn('size-3.5', isActiveVote() && 'fill-current')} />;
202
+ }}
203
+ </Show>
204
+ }
205
+ >
206
+ <Check class="size-3.5 text-emerald-400" />
169
207
  </Show>
170
208
  </Button>
171
209
  );
172
210
  // Icon-only buttons get a tooltip; label-only buttons (text already
173
211
  // visible) don't.
174
- return (
212
+ const rendered = () => (
175
213
  <Show when={item.Icon} fallback={button()}>
176
214
  <Tooltip content={tooltipText()}>{button()}</Tooltip>
177
215
  </Show>
178
216
  );
217
+
218
+ // When the OTHER vote is active, this vote button collapses — its WIDTH
219
+ // animates to zero via a 0fr↔1fr grid so the remaining thumb slides into
220
+ // its place, while it fades out; it slides + fades back on un-vote. (Kept
221
+ // mounted-but-collapsed, not unmounted, so the sibling can slide.) No
222
+ // vote active → both shown; non-vote entries always render.
223
+ return (
224
+ <Show when={vote !== undefined} fallback={rendered()}>
225
+ {(() => {
226
+ const show = () => props.activeFeedback === undefined || props.activeFeedback === vote;
227
+ return (
228
+ <span
229
+ data-feedback-collapsed={show() ? undefined : ''}
230
+ class={cn(
231
+ 'grid transition-[grid-template-columns,opacity] duration-300 ease-out',
232
+ show() ? 'grid-cols-[1fr] opacity-100' : 'grid-cols-[0fr] opacity-0',
233
+ )}
234
+ >
235
+ <span class="min-w-0 overflow-hidden">{rendered()}</span>
236
+ </span>
237
+ );
238
+ })()}
239
+ </Show>
240
+ );
179
241
  }}
180
242
  </For>
181
243
  </MessageActions>
182
244
  );
183
245
  }
184
246
 
247
+ // --- MessageBody ---
248
+
249
+ export interface MessageBodyProps {
250
+ /** The message text/markdown. */
251
+ content: string;
252
+ /** Optional collapsible reasoning block, rendered above the content. */
253
+ reasoning?: { text: string; label?: string };
254
+ /** Tool-call parts, rendered above the content. */
255
+ tools?: ToolPart[];
256
+ /** Inline attachment previews, rendered above the content. */
257
+ attachments?: AttachmentData[];
258
+ /** Whether this is a user message (right-aligned bubble) vs an assistant
259
+ * message (full-width transparent). */
260
+ isUser: boolean;
261
+ /** Whether the content renders as markdown. */
262
+ markdown: boolean;
263
+ /** Action-bar entries — built-in names and/or custom descriptors. When empty
264
+ * the bar is not rendered. */
265
+ actions?: (ChatMessageAction | CustomAction)[];
266
+ /** `'always'` (default) keeps the bar visible; `'hover'` reveals it on parent
267
+ * `.group` hover. */
268
+ actionsReveal?: 'always' | 'hover';
269
+ /** Fired with the built-in name or custom id when an action is clicked. */
270
+ onAction?: (id: string) => void;
271
+ /** The currently-active feedback vote, so the bar can mark like/dislike and
272
+ * hide the other. */
273
+ activeFeedback?: FeedbackVote;
274
+ /** When true, the copy button shows its "copied" check icon. */
275
+ copied?: boolean;
276
+ }
277
+
278
+ /**
279
+ * The shared message body: an optional reasoning block, tool calls, inline
280
+ * attachments, the content bubble, and the action bar — in that order. This is
281
+ * the single source of truth for how a message renders, consumed by `ChatThread`
282
+ * (the `<For>` over `messages`), the standalone `<kai-message>` facade, and (in
283
+ * future) `kai-compare` for each candidate. Pure/prop-driven: all interaction
284
+ * state (copied, feedback vote) is owned above and passed in.
285
+ */
286
+ function MessageBody(props: MessageBodyProps) {
287
+ return (
288
+ <>
289
+ <Show when={props.reasoning}>
290
+ {(r) => (
291
+ <Reasoning class="mb-2 w-full">
292
+ <ReasoningTrigger>{r().label ?? 'Reasoning'}</ReasoningTrigger>
293
+ <ReasoningContent markdown>{r().text}</ReasoningContent>
294
+ </Reasoning>
295
+ )}
296
+ </Show>
297
+ <For each={props.tools ?? []}>{(tp) => <Tool toolPart={tp} class="mb-2 w-full" />}</For>
298
+ <Show when={props.attachments?.length}>
299
+ <Attachments variant="inline" class={props.isUser ? 'mb-2 justify-end' : 'mb-2'}>
300
+ <For each={props.attachments!}>
301
+ {(att) => (<Attachment data={att}><AttachmentPreview /><AttachmentInfo /></Attachment>)}
302
+ </For>
303
+ </Attachments>
304
+ </Show>
305
+ <MessageContent
306
+ markdown={props.markdown}
307
+ class={props.isUser
308
+ ? 'bg-muted text-primary max-w-[85%] rounded-2xl px-4 py-2'
309
+ : 'bg-transparent p-0'}
310
+ >
311
+ {props.content}
312
+ </MessageContent>
313
+ <Show when={(props.actions?.length ?? 0) > 0}>
314
+ <MessageActionBar
315
+ actions={props.actions!}
316
+ reveal={props.actionsReveal === 'hover' ? 'hover' : 'always'}
317
+ activeFeedback={props.activeFeedback}
318
+ copied={props.copied}
319
+ onAction={(id) => props.onAction?.(id)}
320
+ />
321
+ </Show>
322
+ </>
323
+ );
324
+ }
325
+
185
326
  // --- MessageAction ---
186
327
 
187
328
  export interface MessageActionProps {
@@ -224,4 +365,4 @@ function MessageCopyButton(props: MessageCopyButtonProps) {
224
365
  );
225
366
  }
226
367
 
227
- export { Message, MessageAvatar, MessageContent, MessageActions, MessageActionBar, MessageAction, MessageCopyButton };
368
+ export { Message, MessageAvatar, MessageContent, MessageActions, MessageActionBar, MessageBody, MessageAction, MessageCopyButton };
@@ -0,0 +1,127 @@
1
+ // Types + pure helpers for the dual-response comparison card (`ResponseCompare` /
2
+ // `<kai-compare>`). A compare presents exactly TWO assistant candidates for the
3
+ // same prompt side-by-side; the user picks the better one (a COMMIT, not a
4
+ // Submit), which the consumer turns into a `(prompt, chosen, rejected)`
5
+ // preference pair. Kept framework-agnostic and DOM-free so it unit-tests in
6
+ // isolation.
7
+ import type { ToolPart } from './tool';
8
+ import type { AttachmentData } from './attachments';
9
+
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+ // Types
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+
14
+ /** One assistant response in a comparison. Rendered identically to an assistant
15
+ * message (reasoning + tools + attachments + markdown content) via `MessageBody`. */
16
+ export interface CompareCandidate {
17
+ /** Stable id, unique within the pair; echoed back in the selection. */
18
+ id: string;
19
+ /** The response text/markdown. */
20
+ content: string;
21
+ /** Optional collapsible reasoning block. */
22
+ reasoning?: { text: string; label?: string };
23
+ /** Tool-call parts rendered above the content. */
24
+ tools?: ToolPart[];
25
+ /** Inline attachment previews rendered above the content. */
26
+ attachments?: AttachmentData[];
27
+ /** Optional short column label (e.g. "A" / "B" / "Concise"). */
28
+ label?: string;
29
+ /** Optional model name shown as a sub-label. */
30
+ model?: string;
31
+ /** When true this candidate is still streaming — its pick control is disabled
32
+ * and a per-column shimmer shows until it (and its sibling) settle. */
33
+ streaming?: boolean;
34
+ }
35
+
36
+ /** The two-candidate tuple. Enforced at the type level; validated at runtime by
37
+ * `normalizeCandidates`. */
38
+ export type ComparePair = [CompareCandidate, CompareCandidate];
39
+
40
+ /** How the card collapses once the user picks. `'winner'` (default) shows just the
41
+ * chosen candidate's body; `'none'` keeps both columns but marks the choice. */
42
+ export type CompareCollapse = 'winner' | 'none';
43
+
44
+ /** The compare definition (set as the `data` JS property on `<kai-compare>`). */
45
+ export interface ResponseCompareData {
46
+ /** Optional prompt/question shown above the two columns. */
47
+ prompt?: string;
48
+ /** Exactly two candidates: [A, B]. */
49
+ candidates: ComparePair;
50
+ /** Collapse behaviour after a pick. Default `'winner'`. */
51
+ collapse?: CompareCollapse;
52
+ }
53
+
54
+ /** Emitted (and re-hydratable) when the user picks. The consumer pairs
55
+ * `(prompt, chosenId, rejectedIds)` for preference capture / RLHF. */
56
+ export interface CompareSelection {
57
+ /** The id of the chosen candidate. */
58
+ chosenId: string;
59
+ /** The id(s) of the rejected candidate(s) — for a pair, the other one. */
60
+ rejectedIds: string[];
61
+ /** Epoch ms when the pick was made. */
62
+ at?: number;
63
+ }
64
+
65
+ // ─────────────────────────────────────────────────────────────────────────────
66
+ // Pure helpers (unit-tested in isolation).
67
+ // ─────────────────────────────────────────────────────────────────────────────
68
+
69
+ /**
70
+ * Validate + normalize the candidate list. A compare requires EXACTLY two
71
+ * candidates with unique, non-empty string ids. Returns the usable pair, or an
72
+ * error message when the definition is unusable (rendered as an inline error /
73
+ * emitted as a `kai-error`). Modeled on `normalizeOptions`.
74
+ */
75
+ export function normalizeCandidates(candidates: unknown): {
76
+ candidates: ComparePair | null;
77
+ error?: string;
78
+ } {
79
+ if (!Array.isArray(candidates)) {
80
+ return { candidates: null, error: "This comparison couldn't be displayed." };
81
+ }
82
+ if (candidates.length !== 2) {
83
+ return {
84
+ candidates: null,
85
+ error: `A comparison needs exactly two candidates (got ${candidates.length}).`,
86
+ };
87
+ }
88
+ const out: CompareCandidate[] = [];
89
+ const seen = new Set<string>();
90
+ for (const c of candidates) {
91
+ if (!c || typeof c !== 'object') {
92
+ return { candidates: null, error: "This comparison couldn't be displayed." };
93
+ }
94
+ const cand = c as Partial<CompareCandidate>;
95
+ if (typeof cand.id !== 'string' || cand.id.length === 0) {
96
+ return { candidates: null, error: 'Each candidate needs a non-empty id.' };
97
+ }
98
+ if (seen.has(cand.id)) {
99
+ return { candidates: null, error: `Duplicate candidate id "${cand.id}".` };
100
+ }
101
+ seen.add(cand.id);
102
+ out.push({
103
+ id: cand.id,
104
+ content: typeof cand.content === 'string' ? cand.content : '',
105
+ reasoning: cand.reasoning,
106
+ tools: Array.isArray(cand.tools) ? cand.tools : undefined,
107
+ attachments: Array.isArray(cand.attachments) ? cand.attachments : undefined,
108
+ label: typeof cand.label === 'string' ? cand.label : undefined,
109
+ model: typeof cand.model === 'string' ? cand.model : undefined,
110
+ streaming: cand.streaming === true,
111
+ });
112
+ }
113
+ return { candidates: [out[0], out[1]] };
114
+ }
115
+
116
+ /** Build the selection payload for choosing `chosenId` out of the pair (the other
117
+ * candidate is the rejected one). Stamps `at` with the current time. */
118
+ export function buildSelection(pair: ComparePair, chosenId: string): CompareSelection {
119
+ const rejectedIds = pair.filter((c) => c.id !== chosenId).map((c) => c.id);
120
+ return { chosenId, rejectedIds, at: Date.now() };
121
+ }
122
+
123
+ /** Whether any candidate in the pair is still streaming (pick stays disabled). */
124
+ export function isAnyStreaming(pair: ComparePair | null): boolean {
125
+ if (!pair) return false;
126
+ return pair.some((c) => c.streaming === true);
127
+ }