@kitn.ai/ui 0.28.0 → 0.30.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 (615) hide show
  1. package/README.md +11 -7
  2. package/dist/{Icon-DObhsRtm.js → Icon-BzBET3E-.js} +1 -1
  3. package/dist/{action-icons-CcgNN2oT.js → action-icons-DaW2pU6m.js} +1 -1
  4. package/dist/agent-tooling/construct/public.d.ts +15 -0
  5. package/dist/agent-tooling/construct/schema.d.ts +111 -0
  6. package/dist/arrow-down-D783iMJj.js +1 -0
  7. package/dist/arrow-left-0VLM5kyg.js +1 -0
  8. package/dist/artifact-DGjzHYUT.js +1 -0
  9. package/dist/{attachments-Qr4kg-2b.js → attachments-YvXHlg88.js} +1 -1
  10. package/dist/{audio-visualizer-DToYdfSc.js → audio-visualizer-9lz5IHsL.js} +1 -1
  11. package/dist/{badge-CdEu_jn2.js → badge-Dh_vp8A5.js} +1 -1
  12. package/dist/{bell-DQAAOpDz.js → bell-CT7_WUwS.js} +1 -1
  13. package/dist/{button-DkuOnGLy.js → button-C_3te6fm.js} +1 -1
  14. package/dist/{card-renderer-Jx70m6_l.js → card-renderer-DZVaGH2e.js} +1 -1
  15. package/dist/check-CiEOVnEN.js +1 -0
  16. package/dist/{checkbox-D7iRB3nL.js → checkbox-Bu2T2e0U.js} +1 -1
  17. package/dist/{checkbox-group-B2_rpoPM.js → checkbox-group-BiryEXPQ.js} +1 -1
  18. package/dist/chevron-down-CSobuZRG.js +1 -0
  19. package/dist/chevron-right-DR9ZAP-2.js +1 -0
  20. package/dist/{choice-card-m0BYqqOK.js → choice-card-CM6IjkmL.js} +1 -1
  21. package/dist/circle-BC66EYF7.js +1 -0
  22. package/dist/circle-check-BqmahW0O.js +1 -0
  23. package/dist/{circle-x-BPG0iPst.js → circle-x-Bk-dA5Wf.js} +1 -1
  24. package/dist/{code-block-ClMLKb7C.js → code-block-B26t-Q7q.js} +1 -1
  25. package/dist/{collapsible-B4DlWicL.js → collapsible-Bhc636Cf.js} +1 -1
  26. package/dist/components/builder-composer-triggers.d.ts +62 -0
  27. package/dist/components/builder-layout.d.ts +54 -0
  28. package/dist/components/builder-message-actions.d.ts +72 -0
  29. package/dist/components/builder-panel.d.ts +189 -0
  30. package/dist/components/builder-preview.d.ts +11 -0
  31. package/dist/components/builder-shell-controls.d.ts +69 -0
  32. package/dist/components/builder-skeleton.d.ts +58 -0
  33. package/dist/components/builder-start.d.ts +75 -0
  34. package/dist/components/builder-workspace-variants.d.ts +23 -0
  35. package/dist/components/captions.d.ts +66 -0
  36. package/dist/{composer-sQ0HcvyG.js → composer-DMI85B6j.js} +1 -1
  37. package/dist/{confirm-card-rQWDQsnC.js → confirm-card-B0YIAwXp.js} +1 -1
  38. package/dist/construct-cli.es.js +3 -3
  39. package/dist/construct.d.ts +5 -0
  40. package/dist/construct.js +327 -0
  41. package/dist/{context-0kg4ZX1J.js → context-DWgfhqWp.js} +1 -1
  42. package/dist/controllable-BagRpyHv.js +1 -0
  43. package/dist/{conversation-item-BxwQLaFJ.js → conversation-item-BsOsVGmT.js} +1 -1
  44. package/dist/{conversation-list-BCfGN1JU.js → conversation-list-aX3Vtc7L.js} +1 -1
  45. package/dist/{copy-n9u9rqpi.js → copy-BmEwsNhJ.js} +1 -1
  46. package/dist/core-C8fzo39E.js +1 -12
  47. package/dist/{create-tween-uPkArjIG.js → create-tween-CNOAU3Fx.js} +1 -1
  48. package/dist/{create-tween-C-q-JCM-.js → create-tween-WfqiENls.js} +1 -1
  49. package/dist/{create-tween-CRrJXxBd.js → create-tween-wlFtW9Kh.js} +1 -1
  50. package/dist/{create-tween-DZ3RRDHD.js → create-tween-zt79ba32.js} +1 -1
  51. package/dist/{default-input-CaMrJkgH.js → default-input-DMV5Eaze.js} +1 -1
  52. package/dist/define-CI0y3eE4.js +1 -0
  53. package/dist/define.js +40 -40
  54. package/dist/define.server.js +45 -45
  55. package/dist/{disclosure-WUImBbDv.js → disclosure-NnACBxsC.js} +1 -1
  56. package/dist/{dropdown-wZVeJOMd.js → dropdown-DT7G0RGR.js} +1 -1
  57. package/dist/elements/agent-card.js +1 -1
  58. package/dist/elements/artifact.js +1 -1
  59. package/dist/elements/attachments.js +1 -1
  60. package/dist/elements/audio-visualizer.js +1 -1
  61. package/dist/elements/avatar.js +1 -1
  62. package/dist/elements/badge.js +1 -1
  63. package/dist/elements/button.js +1 -1
  64. package/dist/elements/card.js +1 -1
  65. package/dist/elements/cards.js +1 -1
  66. package/dist/elements/chain-of-thought.js +1 -1
  67. package/dist/elements/chat-scope-picker.js +1 -1
  68. package/dist/elements/chat-workspace.js +1 -1
  69. package/dist/elements/chat.js +1 -1
  70. package/dist/elements/checkbox-group.js +1 -1
  71. package/dist/elements/checkbox.js +1 -1
  72. package/dist/elements/checkpoint.js +1 -1
  73. package/dist/elements/choice.js +1 -1
  74. package/dist/elements/coachmark.js +1 -1
  75. package/dist/elements/code-block.js +1 -1
  76. package/dist/elements/command.js +1 -1
  77. package/dist/elements/compare.js +1 -1
  78. package/dist/elements/composer.js +1 -1
  79. package/dist/elements/confirm-card.js +1 -1
  80. package/dist/elements/context-meter.js +1 -1
  81. package/dist/elements/conversation-item.js +1 -1
  82. package/dist/elements/conversation-list.js +1 -1
  83. package/dist/elements/dialog.js +1 -1
  84. package/dist/elements/dock.js +1 -1
  85. package/dist/elements/dropdown.js +1 -1
  86. package/dist/elements/editable-label.js +1 -1
  87. package/dist/elements/embed.js +1 -1
  88. package/dist/elements/empty.js +1 -1
  89. package/dist/elements/feedback-bar.js +1 -1
  90. package/dist/elements/file-tree.js +1 -1
  91. package/dist/elements/file-upload.js +1 -1
  92. package/dist/elements/form.js +1 -1
  93. package/dist/elements/hover-card.js +1 -1
  94. package/dist/elements/icon.js +1 -1
  95. package/dist/elements/image.js +1 -1
  96. package/dist/elements/input.js +1 -1
  97. package/dist/elements/kbd.js +1 -1
  98. package/dist/elements/link-preview.js +1 -1
  99. package/dist/elements/loader.js +1 -1
  100. package/dist/elements/markdown.js +1 -1
  101. package/dist/elements/menu.js +1 -1
  102. package/dist/elements/message-skills.js +1 -1
  103. package/dist/elements/message.js +1 -1
  104. package/dist/elements/model-switcher.js +1 -1
  105. package/dist/elements/nav.js +1 -1
  106. package/dist/elements/notice.js +1 -1
  107. package/dist/elements/pane-grid.js +1 -1
  108. package/dist/elements/pane-group.js +1 -1
  109. package/dist/elements/pane.js +1 -1
  110. package/dist/elements/popover.js +1 -1
  111. package/dist/elements/progress-bar.js +1 -1
  112. package/dist/elements/prompt-dock.js +1 -1
  113. package/dist/elements/prompt-input.js +1 -1
  114. package/dist/elements/prompt-suggestions.js +1 -1
  115. package/dist/elements/radio-group.js +1 -1
  116. package/dist/elements/reasoning.js +1 -1
  117. package/dist/elements/remote.js +1 -1
  118. package/dist/elements/resizable.js +1 -1
  119. package/dist/elements/response-stream.js +1 -1
  120. package/dist/elements/screen.js +1 -1
  121. package/dist/elements/scroll-area.js +1 -1
  122. package/dist/elements/scroll-button.js +1 -1
  123. package/dist/elements/search.js +1 -1
  124. package/dist/elements/segmented.js +1 -1
  125. package/dist/elements/select.js +1 -1
  126. package/dist/elements/separator.js +1 -1
  127. package/dist/elements/setting-item.js +1 -1
  128. package/dist/elements/settings-group.js +1 -1
  129. package/dist/elements/skeleton.js +1 -1
  130. package/dist/elements/slider.js +1 -1
  131. package/dist/elements/source.js +1 -1
  132. package/dist/elements/status.js +1 -1
  133. package/dist/elements/switch.js +1 -1
  134. package/dist/elements/tabs.js +1 -1
  135. package/dist/elements/tasks.js +1 -1
  136. package/dist/elements/text-shimmer.js +1 -1
  137. package/dist/elements/thinking-bar.js +1 -1
  138. package/dist/elements/thread.js +1 -1
  139. package/dist/elements/toast.js +1 -1
  140. package/dist/elements/tool.js +1 -1
  141. package/dist/elements/tooltip.js +1 -1
  142. package/dist/elements/voice-input.js +1 -1
  143. package/dist/elements/voice-output.js +1 -1
  144. package/dist/{ellipsis-CbOpJnU3.js → ellipsis-6pJ7SxYT.js} +1 -1
  145. package/dist/{embed-CrfjkbUG.js → embed-BMje-nxj.js} +1 -1
  146. package/dist/engine-javascript-C1x7zo1_.js +1 -141
  147. package/dist/{external-link-ByXAwBBQ.js → external-link-Beh9DKeR.js} +1 -1
  148. package/dist/{file-text-DMib2qwt.js → file-text-D1GPfZ8Q.js} +1 -1
  149. package/dist/{file-tree-CJqM2aP7.js → file-tree-Dwj2lqg-.js} +1 -1
  150. package/dist/{folder-_BAb929C.js → folder-b6D8x1DY.js} +1 -1
  151. package/dist/{form-DSZgZQZk.js → form-DWgdxDXu.js} +1 -1
  152. package/dist/{hover-card-BQbul9eq.js → hover-card-CWRkqm40.js} +1 -1
  153. package/dist/{icon-CdcfRhYI.js → icon-CL0jdhsf.js} +1 -1
  154. package/dist/index.js +1741 -1725
  155. package/dist/index.server.js +1582 -1566
  156. package/dist/{info-DsFjGuI_.js → info-0cOUherQ.js} +1 -1
  157. package/dist/{input-DKbmkMos.js → input-BH6Ru91Q.js} +1 -1
  158. package/dist/kai.es.js +1 -1
  159. package/dist/{kbd-PvdGhX4s.js → kbd-DRAnVtNX.js} +1 -1
  160. package/dist/{link-zF8PzWfF.js → link-Dlr0vssB.js} +1 -1
  161. package/dist/{link-preview-DbM1866N.js → link-preview-DODgRyV9.js} +1 -1
  162. package/dist/loader-KvJB6NrT.js +1 -0
  163. package/dist/{markdown-LMvmAyBk.js → markdown-DX9tuJVj.js} +1 -1
  164. package/dist/mcp.es.js +9 -5
  165. package/dist/{message-BYZECyek.js → message-BqQ7Ot5q.js} +1 -1
  166. package/dist/{message-BSsgvHvf.js → message-DkaGfBd8.js} +1 -1
  167. package/dist/message-circle-BPpp5QKv.js +1 -0
  168. package/dist/message-square-BLbsd9BJ.js +1 -0
  169. package/dist/{minimize-2-BQRoBV1V.js → minimize-2-CyOHJwcN.js} +1 -1
  170. package/dist/{model-switcher-CMpCOYcB.js → model-switcher-0hrKDiK2.js} +1 -1
  171. package/dist/{overlay-Cb-MkyDa.js → overlay-DBMFXie6.js} +1 -1
  172. package/dist/{panel-right-i8warFxT.js → panel-right-t8SWJL6e.js} +1 -1
  173. package/dist/{paperclip-ByY3UVO6.js → paperclip-CdzuOjbU.js} +1 -1
  174. package/dist/play-BXy1u7rW.js +1 -0
  175. package/dist/primitives/use-auto-resize.d.ts +11 -0
  176. package/dist/{progress-bar-jVAHvMUf.js → progress-bar-CiBtxMWT.js} +1 -1
  177. package/dist/{prompt-suggestion-CD-qtNgC.js → prompt-suggestion-I_DHP21i.js} +1 -1
  178. package/dist/{radio-BsoBMhkF.js → radio-C_p3n39l.js} +1 -1
  179. package/dist/{reasoning-4kBPtlTx.js → reasoning-BDwy_tIe.js} +1 -1
  180. package/dist/register-impl-DvEnhIxx.js +424 -0
  181. package/dist/{resizable-uPmY9VY-.js → resizable-SYC-fStM.js} +1 -1
  182. package/dist/{rotate-ccw-B2xn93py.js → rotate-ccw-B1nwmwuY.js} +1 -1
  183. package/dist/{rotate-cw-CMVLoCyH.js → rotate-cw-CLAnBcm1.js} +1 -1
  184. package/dist/{scroll-area-BqLUo8ZZ.js → scroll-area-zRyXoLhI.js} +1 -1
  185. package/dist/{scroll-button-DslXiJyL.js → scroll-button-jzrbYCG9.js} +1 -1
  186. package/dist/{select-2cLRtPGT.js → select-DM-0nEe0.js} +1 -1
  187. package/dist/{separator-BWQtNJcM.js → separator-BToOOoOt.js} +1 -1
  188. package/dist/{settings-BBR63jLg.js → settings-PaGo_S4U.js} +1 -1
  189. package/dist/{settings-group-B2ciy-Iw.js → settings-group-BFXGIfXq.js} +1 -1
  190. package/dist/{skeleton-CkHI-HgC.js → skeleton-DYpmFnnm.js} +1 -1
  191. package/dist/{slider-DDR9U-i0.js → slider-ebutAmkQ.js} +1 -1
  192. package/dist/{solid-DruVOXnf.js → solid-15v_3WVL.js} +3563 -3412
  193. package/dist/{solid-BzCFdL25.js → solid-5P-otgdi.js} +4502 -4296
  194. package/dist/solid.d.ts +6 -0
  195. package/dist/solid.js +259 -254
  196. package/dist/solid.server.js +259 -254
  197. package/dist/{source-GEnFF528.js → source-c1gpRl9h.js} +1 -1
  198. package/dist/{star-CilHq8GP.js → star-pcV_3LqF.js} +1 -1
  199. package/dist/{store-Ciyk6eZG.js → store-CSgbFFCg.js} +1 -1
  200. package/dist/{switch-KkeQIbuH.js → switch-Bh2OdLV2.js} +1 -1
  201. package/dist/{tasks-card-BrN4bggJ.js → tasks-card-CbqQAn2S.js} +1 -1
  202. package/dist/text-shimmer-BSzgEnDH.js +1 -0
  203. package/dist/textarea-DrYNt_0z.js +1 -0
  204. package/dist/theme.tokens.css +22 -21
  205. package/dist/{thumbs-up-CsT9Wzb9.js → thumbs-up-BL3-NenK.js} +1 -1
  206. package/dist/{toast-store-tgK6MuMv.js → toast-store-DkOlYczt.js} +1 -1
  207. package/dist/{tool-DzUjIBT8.js → tool-CeIWUVUe.js} +1 -1
  208. package/dist/{tooltip-Bkkg_4t0.js → tooltip-GA-nmg06.js} +1 -1
  209. package/dist/{trash-2-jBKxjGGQ.js → trash-2-CT66QWlm.js} +1 -1
  210. package/dist/{triangle-alert-DlQ68ppf.js → triangle-alert-CeHLWxhD.js} +1 -1
  211. package/dist/ui/color-field.d.ts +48 -0
  212. package/dist/ui/textarea.d.ts +4 -0
  213. package/dist/ui/toggle-chip.d.ts +40 -0
  214. package/dist/{upload-TcAHXv9W.js → upload-9Csb0F0J.js} +1 -1
  215. package/dist/{use-card-resolution-DaZqAdWf.js → use-card-resolution-Zg0a08Z-.js} +1 -1
  216. package/dist/{variant-aurora-DbW_OWL2.js → variant-aurora-4VBQHIKV.js} +1 -1
  217. package/dist/{variant-aurora-CNmfHhcx.js → variant-aurora-DBeCAQNk.js} +2 -2
  218. package/dist/{variant-aurora-D6GsdTPA.js → variant-aurora-FzHVXHby.js} +2 -2
  219. package/dist/{variant-aurora-BhLuYxJR.js → variant-aurora-LE3fqxgw.js} +1 -1
  220. package/dist/{variant-custom-BvYHR62b.js → variant-custom-AFRGcOBS.js} +1 -1
  221. package/dist/{variant-custom-DV2x254s.js → variant-custom-BQkNuPpE.js} +2 -2
  222. package/dist/{variant-custom-BRvaNHSi.js → variant-custom-DI5R0rln.js} +1 -1
  223. package/dist/{variant-custom-DMTrEO5w.js → variant-custom-WWnrrRHS.js} +2 -2
  224. package/dist/{variant-wave-CyBueXj9.js → variant-wave-DUCbH5oI.js} +2 -2
  225. package/dist/{variant-wave-BNMGrDfj.js → variant-wave-DuQV7DhQ.js} +2 -2
  226. package/dist/{variant-wave-BpDd8t3Z.js → variant-wave-nSI2sfII.js} +1 -1
  227. package/dist/{variant-wave-sp-qwsdI.js → variant-wave-wTP5v_jU.js} +1 -1
  228. package/dist/x-eiO9V6J6.js +1 -0
  229. package/{src/elements/styles.css → kit-base.css} +27 -44
  230. package/package.json +32 -17
  231. package/solid.css +33 -0
  232. package/theme.css +39 -23
  233. package/bin/route.test.js +0 -58
  234. package/dist/arrow-down-CjRU3JgV.js +0 -1
  235. package/dist/arrow-left-Bnpb1UEW.js +0 -1
  236. package/dist/artifact-Bwt7OgcR.js +0 -1
  237. package/dist/check-njkVdZu2.js +0 -1
  238. package/dist/chevron-down-B72s6PXR.js +0 -1
  239. package/dist/chevron-right-BatuHzUm.js +0 -1
  240. package/dist/circle-check-B62pODB3.js +0 -1
  241. package/dist/circle-zx0ByzY9.js +0 -1
  242. package/dist/controllable-CnGpMF6r.js +0 -1
  243. package/dist/define-By-uL588.js +0 -1
  244. package/dist/loader-BM4CEz4G.js +0 -1
  245. package/dist/message-circle-DruwObfX.js +0 -1
  246. package/dist/message-square-BG6q6mlm.js +0 -1
  247. package/dist/play-BvKKq3fX.js +0 -1
  248. package/dist/register-impl-B1hLkrI8.js +0 -424
  249. package/dist/text-shimmer-Dcj1MHuq.js +0 -1
  250. package/dist/textarea-Dpvbngbg.js +0 -1
  251. package/dist/x-Z5UntkIJ.js +0 -1
  252. package/src/agent-tooling/README.md +0 -192
  253. package/src/agent-tooling/archetypes.ts +0 -205
  254. package/src/agent-tooling/catalog/README.md +0 -676
  255. package/src/agent-tooling/catalog/catalog-types.ts +0 -231
  256. package/src/agent-tooling/catalog/fabrications.ts +0 -96
  257. package/src/agent-tooling/catalog/invariants.ts +0 -284
  258. package/src/agent-tooling/catalog/labs-titles.ts +0 -114
  259. package/src/agent-tooling/catalog/scenarios.ts +0 -87
  260. package/src/agent-tooling/catalog/surfaces.ts +0 -347
  261. package/src/agent-tooling/construct/cli-entry.ts +0 -8
  262. package/src/agent-tooling/construct/cli.ts +0 -147
  263. package/src/agent-tooling/construct/codegen.ts +0 -1635
  264. package/src/agent-tooling/construct/construct.v1.schema.json +0 -319
  265. package/src/agent-tooling/construct/dev.ts +0 -148
  266. package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +0 -7
  267. package/src/agent-tooling/construct/fixtures/ops-console.construct.json +0 -44
  268. package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +0 -32
  269. package/src/agent-tooling/construct/schema.ts +0 -452
  270. package/src/agent-tooling/integrations/anthropic.ts +0 -442
  271. package/src/agent-tooling/integrations/cloudflare.ts +0 -218
  272. package/src/agent-tooling/integrations/langgraph.ts +0 -133
  273. package/src/agent-tooling/integrations/mastra.ts +0 -182
  274. package/src/agent-tooling/integrations/mock.ts +0 -148
  275. package/src/agent-tooling/integrations/ollama.ts +0 -97
  276. package/src/agent-tooling/integrations/openai.ts +0 -95
  277. package/src/agent-tooling/integrations/openrouter.ts +0 -82
  278. package/src/agent-tooling/integrations/pi.ts +0 -176
  279. package/src/agent-tooling/integrations/pydantic-ai.ts +0 -108
  280. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +0 -398
  281. package/src/agent-tooling/mcp/css-raw.d.ts +0 -12
  282. package/src/agent-tooling/mcp/manifest.ts +0 -422
  283. package/src/agent-tooling/mcp/server.ts +0 -116
  284. package/src/agent-tooling/mcp/stdio.ts +0 -18
  285. package/src/agent-tooling/mcp/tools/construct.ts +0 -130
  286. package/src/agent-tooling/mcp/tools/debug.ts +0 -621
  287. package/src/agent-tooling/mcp/tools/reference.ts +0 -980
  288. package/src/agent-tooling/mcp/tools/scaffold.ts +0 -6708
  289. package/src/agent-tooling/mcp/tools/theme.ts +0 -456
  290. package/src/agent-tooling/mcp/tools/types.ts +0 -14
  291. package/src/agent-tooling/mcp/validate-args.ts +0 -141
  292. package/src/agent-tooling/recipes/composed-thread.ts +0 -714
  293. package/src/agent-tooling/recipes/index.ts +0 -22
  294. package/src/agent-tooling/recipes/types.ts +0 -32
  295. package/src/agent-tooling/registry.ts +0 -132
  296. package/src/agent-tooling/route-emit.ts +0 -305
  297. package/src/agent-tooling/types.ts +0 -390
  298. package/src/components/artifact-card.tsx +0 -120
  299. package/src/components/artifact.tsx +0 -914
  300. package/src/components/attachment-types.ts +0 -66
  301. package/src/components/attachments.tsx +0 -536
  302. package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +0 -257
  303. package/src/components/audio-visualizer/aurora.glsl.ts +0 -330
  304. package/src/components/audio-visualizer/fit-scale.ts +0 -112
  305. package/src/components/audio-visualizer/index.tsx +0 -528
  306. package/src/components/audio-visualizer/labs/lab-choreography.ts +0 -187
  307. package/src/components/audio-visualizer/labs/lab-shaders.ts +0 -363
  308. package/src/components/audio-visualizer/labs/lab-visualizer.tsx +0 -102
  309. package/src/components/audio-visualizer/shader-canvas.tsx +0 -876
  310. package/src/components/audio-visualizer/sizes.ts +0 -54
  311. package/src/components/audio-visualizer/variant-aurora.tsx +0 -305
  312. package/src/components/audio-visualizer/variant-bar.tsx +0 -175
  313. package/src/components/audio-visualizer/variant-custom.tsx +0 -344
  314. package/src/components/audio-visualizer/variant-grid.tsx +0 -175
  315. package/src/components/audio-visualizer/variant-radial.tsx +0 -163
  316. package/src/components/audio-visualizer/variant-wave.tsx +0 -130
  317. package/src/components/audio-visualizer/wave.glsl.ts +0 -130
  318. package/src/components/card-fallback.tsx +0 -60
  319. package/src/components/card-renderer.tsx +0 -220
  320. package/src/components/card.tsx +0 -212
  321. package/src/components/chain-of-thought.tsx +0 -348
  322. package/src/components/chat-container.tsx +0 -89
  323. package/src/components/chat-scope-picker.tsx +0 -57
  324. package/src/components/chat-thread.tsx +0 -1014
  325. package/src/components/checkpoint.tsx +0 -88
  326. package/src/components/choice-card.tsx +0 -743
  327. package/src/components/coachmark.tsx +0 -249
  328. package/src/components/code-block.tsx +0 -181
  329. package/src/components/composer-dom.ts +0 -159
  330. package/src/components/composer-highlight.ts +0 -242
  331. package/src/components/composer-history.ts +0 -82
  332. package/src/components/composer.tsx +0 -1075
  333. package/src/components/confirm-card.tsx +0 -402
  334. package/src/components/context.tsx +0 -377
  335. package/src/components/conversation-item.tsx +0 -241
  336. package/src/components/conversation-list.tsx +0 -429
  337. package/src/components/conversation-panel.tsx +0 -118
  338. package/src/components/dismissed-stub.tsx +0 -80
  339. package/src/components/embed.tsx +0 -200
  340. package/src/components/empty.tsx +0 -177
  341. package/src/components/feedback-bar.tsx +0 -166
  342. package/src/components/file-tree.tsx +0 -530
  343. package/src/components/file-upload.tsx +0 -167
  344. package/src/components/form-widgets.tsx +0 -525
  345. package/src/components/form.tsx +0 -1296
  346. package/src/components/home-panel.tsx +0 -159
  347. package/src/components/image.tsx +0 -65
  348. package/src/components/link-preview.tsx +0 -195
  349. package/src/components/loader.tsx +0 -367
  350. package/src/components/markdown.tsx +0 -162
  351. package/src/components/message-skills.tsx +0 -40
  352. package/src/components/message.tsx +0 -765
  353. package/src/components/model-switcher.tsx +0 -132
  354. package/src/components/prompt-input.tsx +0 -188
  355. package/src/components/prompt-suggestion.tsx +0 -150
  356. package/src/components/reasoning.tsx +0 -293
  357. package/src/components/response-compare-types.ts +0 -152
  358. package/src/components/response-compare.tsx +0 -455
  359. package/src/components/response-stream.tsx +0 -109
  360. package/src/components/screen.tsx +0 -181
  361. package/src/components/scroll-button.tsx +0 -93
  362. package/src/components/source.tsx +0 -192
  363. package/src/components/tasks-card.tsx +0 -801
  364. package/src/components/text-shimmer.tsx +0 -37
  365. package/src/components/thinking-bar.tsx +0 -50
  366. package/src/components/thread.tsx +0 -228
  367. package/src/components/toast.tsx +0 -503
  368. package/src/components/tool-classify.ts +0 -26
  369. package/src/components/tool-types.ts +0 -49
  370. package/src/components/tool.tsx +0 -190
  371. package/src/components/use-card-resolution.ts +0 -44
  372. package/src/components/voice-input.tsx +0 -222
  373. package/src/components/voice-output.tsx +0 -242
  374. package/src/components/widget-tab-bar.tsx +0 -73
  375. package/src/components/workspace-shell.tsx +0 -285
  376. package/src/diagnostics/hook.ts +0 -352
  377. package/src/diagnostics/index.ts +0 -82
  378. package/src/diagnostics/report-request.ts +0 -296
  379. package/src/elements/agent-card.tsx +0 -67
  380. package/src/elements/artifact.tsx +0 -194
  381. package/src/elements/attachments.tsx +0 -140
  382. package/src/elements/audio-visualizer.tsx +0 -227
  383. package/src/elements/autoloader.ts +0 -89
  384. package/src/elements/avatar.tsx +0 -33
  385. package/src/elements/badge.tsx +0 -32
  386. package/src/elements/button.tsx +0 -149
  387. package/src/elements/card.tsx +0 -133
  388. package/src/elements/cards.tsx +0 -266
  389. package/src/elements/chain-of-thought.tsx +0 -141
  390. package/src/elements/chat-scope-picker.tsx +0 -65
  391. package/src/elements/chat-types.ts +0 -102
  392. package/src/elements/chat-workspace.tsx +0 -188
  393. package/src/elements/chat.tsx +0 -304
  394. package/src/elements/checkbox-group.tsx +0 -164
  395. package/src/elements/checkbox.tsx +0 -134
  396. package/src/elements/checkpoint.tsx +0 -47
  397. package/src/elements/choice.tsx +0 -100
  398. package/src/elements/coachmark.tsx +0 -90
  399. package/src/elements/code-block.tsx +0 -60
  400. package/src/elements/command.tsx +0 -197
  401. package/src/elements/compare.tsx +0 -111
  402. package/src/elements/compiled.css +0 -2
  403. package/src/elements/composer.tsx +0 -162
  404. package/src/elements/confirm-card.tsx +0 -76
  405. package/src/elements/context-meter.tsx +0 -107
  406. package/src/elements/conversation-item.tsx +0 -120
  407. package/src/elements/conversation-list.tsx +0 -203
  408. package/src/elements/css.ts +0 -5
  409. package/src/elements/default-input.tsx +0 -392
  410. package/src/elements/define-entry.ts +0 -12
  411. package/src/elements/define.tsx +0 -563
  412. package/src/elements/diagnostic-events.ts +0 -114
  413. package/src/elements/dialog.tsx +0 -145
  414. package/src/elements/disclosure.ts +0 -95
  415. package/src/elements/dock.tsx +0 -215
  416. package/src/elements/dropdown.tsx +0 -123
  417. package/src/elements/editable-label.tsx +0 -140
  418. package/src/elements/element-data-types.ts +0 -152
  419. package/src/elements/element-diagnostics.ts +0 -392
  420. package/src/elements/element-manifest.json +0 -354
  421. package/src/elements/element-nonscalar.json +0 -187
  422. package/src/elements/element-types.d.ts +0 -4353
  423. package/src/elements/embed.tsx +0 -35
  424. package/src/elements/empty.tsx +0 -29
  425. package/src/elements/feedback-bar.tsx +0 -66
  426. package/src/elements/file-tree.tsx +0 -62
  427. package/src/elements/file-upload.tsx +0 -44
  428. package/src/elements/form.tsx +0 -94
  429. package/src/elements/hover-card.tsx +0 -80
  430. package/src/elements/icon.tsx +0 -41
  431. package/src/elements/image.tsx +0 -32
  432. package/src/elements/input.tsx +0 -492
  433. package/src/elements/kbd.tsx +0 -45
  434. package/src/elements/link-preview.tsx +0 -34
  435. package/src/elements/loader.tsx +0 -25
  436. package/src/elements/markdown.tsx +0 -38
  437. package/src/elements/menu.tsx +0 -241
  438. package/src/elements/message-skills.tsx +0 -83
  439. package/src/elements/message.tsx +0 -361
  440. package/src/elements/model-switcher.tsx +0 -121
  441. package/src/elements/nav.tsx +0 -87
  442. package/src/elements/notice.tsx +0 -53
  443. package/src/elements/pane-grid.tsx +0 -111
  444. package/src/elements/pane-group.tsx +0 -119
  445. package/src/elements/pane.tsx +0 -129
  446. package/src/elements/popover.tsx +0 -81
  447. package/src/elements/progress-bar.tsx +0 -39
  448. package/src/elements/prompt-dock.tsx +0 -82
  449. package/src/elements/prompt-input.tsx +0 -240
  450. package/src/elements/prompt-suggestions.tsx +0 -113
  451. package/src/elements/radio-group.tsx +0 -129
  452. package/src/elements/reasoning.tsx +0 -71
  453. package/src/elements/register-impl.ts +0 -148
  454. package/src/elements/register.ts +0 -76
  455. package/src/elements/remote.tsx +0 -225
  456. package/src/elements/resizable.globals.d.ts +0 -36
  457. package/src/elements/resizable.tsx +0 -704
  458. package/src/elements/response-stream.tsx +0 -40
  459. package/src/elements/screen.tsx +0 -90
  460. package/src/elements/scroll-area.tsx +0 -33
  461. package/src/elements/scroll-button.tsx +0 -179
  462. package/src/elements/search.tsx +0 -185
  463. package/src/elements/segmented.tsx +0 -129
  464. package/src/elements/select.tsx +0 -165
  465. package/src/elements/separator.tsx +0 -36
  466. package/src/elements/setting-item.tsx +0 -50
  467. package/src/elements/settings-group.tsx +0 -37
  468. package/src/elements/skeleton.tsx +0 -45
  469. package/src/elements/slider.tsx +0 -171
  470. package/src/elements/slot-text.ts +0 -73
  471. package/src/elements/slots.ts +0 -787
  472. package/src/elements/source.tsx +0 -126
  473. package/src/elements/status.tsx +0 -42
  474. package/src/elements/switch.tsx +0 -118
  475. package/src/elements/tabs.tsx +0 -95
  476. package/src/elements/tasks.tsx +0 -103
  477. package/src/elements/text-shimmer.tsx +0 -28
  478. package/src/elements/thinking-bar.tsx +0 -43
  479. package/src/elements/thread.tsx +0 -139
  480. package/src/elements/toast.tsx +0 -106
  481. package/src/elements/tool.tsx +0 -78
  482. package/src/elements/tooltip.tsx +0 -85
  483. package/src/elements/validate-messages.ts +0 -76
  484. package/src/elements/voice-input.tsx +0 -100
  485. package/src/elements/voice-output.tsx +0 -82
  486. package/src/index.ts +0 -430
  487. package/src/primitives/audio-bands.ts +0 -174
  488. package/src/primitives/card-component-types.ts +0 -63
  489. package/src/primitives/card-contract.ts +0 -80
  490. package/src/primitives/card-data-types.ts +0 -361
  491. package/src/primitives/card-host.tsx +0 -37
  492. package/src/primitives/card-recovery.ts +0 -160
  493. package/src/primitives/card-registry.tsx +0 -78
  494. package/src/primitives/card-resolution.ts +0 -36
  495. package/src/primitives/card-routing.ts +0 -85
  496. package/src/primitives/card-schemas/artifact.schema.json +0 -92
  497. package/src/primitives/card-schemas/card-envelope.schema.json +0 -14
  498. package/src/primitives/card-schemas/card-event.schema.json +0 -12
  499. package/src/primitives/card-schemas/choice.schema.json +0 -75
  500. package/src/primitives/card-schemas/confirm.schema.json +0 -65
  501. package/src/primitives/card-schemas/embed.schema.json +0 -63
  502. package/src/primitives/card-schemas/form.result.schema.json +0 -7
  503. package/src/primitives/card-schemas/form.schema.json +0 -53
  504. package/src/primitives/card-schemas/link.schema.json +0 -54
  505. package/src/primitives/card-schemas/tasks.result.schema.json +0 -16
  506. package/src/primitives/card-schemas/tasks.schema.json +0 -83
  507. package/src/primitives/card-tags.ts +0 -61
  508. package/src/primitives/card-validate-cards.ts +0 -255
  509. package/src/primitives/card-validate-schemas.ts +0 -58
  510. package/src/primitives/card-validate.ts +0 -131
  511. package/src/primitives/chat-config.tsx +0 -103
  512. package/src/primitives/composer-model.ts +0 -49
  513. package/src/primitives/composer-triggers.ts +0 -35
  514. package/src/primitives/controllable.ts +0 -21
  515. package/src/primitives/conversation-store.ts +0 -248
  516. package/src/primitives/create-kai-chat.ts +0 -66
  517. package/src/primitives/create-tween.ts +0 -266
  518. package/src/primitives/embed-providers.ts +0 -254
  519. package/src/primitives/field-mask.ts +0 -256
  520. package/src/primitives/field-semantics.ts +0 -115
  521. package/src/primitives/highlighter.ts +0 -157
  522. package/src/primitives/input-mask.ts +0 -853
  523. package/src/primitives/link-preview.ts +0 -87
  524. package/src/primitives/message-feedback.ts +0 -124
  525. package/src/primitives/pdf-preview.ts +0 -121
  526. package/src/primitives/toast-store.ts +0 -338
  527. package/src/primitives/url-scheme-policy.ts +0 -70
  528. package/src/primitives/use-audio-analysis.ts +0 -340
  529. package/src/primitives/use-auto-resize.ts +0 -31
  530. package/src/primitives/use-resize-observer.ts +0 -45
  531. package/src/primitives/use-sequencer.ts +0 -72
  532. package/src/primitives/use-speech-recognition.ts +0 -146
  533. package/src/primitives/use-stick-to-bottom.ts +0 -75
  534. package/src/primitives/use-text-stream.ts +0 -143
  535. package/src/primitives/use-voice-recorder.ts +0 -62
  536. package/src/primitives/visualizer-sequences.ts +0 -350
  537. package/src/remote/host-embed.ts +0 -345
  538. package/src/remote/index.ts +0 -2
  539. package/src/remote/origin.ts +0 -30
  540. package/src/remote/provider-runtime.ts +0 -262
  541. package/src/remote/provider.ts +0 -2
  542. package/src/remote/validate.ts +0 -22
  543. package/src/remote/version.ts +0 -12
  544. package/src/remote/wire.ts +0 -48
  545. package/src/schemas/from-tool-call.ts +0 -151
  546. package/src/schemas/index.ts +0 -341
  547. package/src/schemas/provider-subsets.ts +0 -538
  548. package/src/schemas/registry.ts +0 -355
  549. package/src/schemas/tool-defs.ts +0 -793
  550. package/src/solid.ts +0 -173
  551. package/src/state/index.ts +0 -44
  552. package/src/state/messages.ts +0 -53
  553. package/src/state/mock.ts +0 -297
  554. package/src/state/parts.ts +0 -295
  555. package/src/state/persistence.ts +0 -180
  556. package/src/state/stream.ts +0 -246
  557. package/src/state/suggestions.ts +0 -9
  558. package/src/state/threads.ts +0 -142
  559. package/src/test-utils/fake-clock.ts +0 -88
  560. package/src/types.ts +0 -95
  561. package/src/ui/action-icons.ts +0 -53
  562. package/src/ui/agent-card.tsx +0 -218
  563. package/src/ui/avatar.tsx +0 -23
  564. package/src/ui/badge.tsx +0 -21
  565. package/src/ui/button.tsx +0 -49
  566. package/src/ui/card.tsx +0 -245
  567. package/src/ui/checkbox-group.tsx +0 -153
  568. package/src/ui/checkbox.tsx +0 -63
  569. package/src/ui/collapsible.tsx +0 -163
  570. package/src/ui/command.tsx +0 -103
  571. package/src/ui/dialog.tsx +0 -223
  572. package/src/ui/dock.tsx +0 -630
  573. package/src/ui/dropdown.tsx +0 -636
  574. package/src/ui/editable-label.tsx +0 -112
  575. package/src/ui/hover-card.tsx +0 -345
  576. package/src/ui/icon.tsx +0 -167
  577. package/src/ui/input.tsx +0 -391
  578. package/src/ui/kbd.tsx +0 -118
  579. package/src/ui/nav.tsx +0 -282
  580. package/src/ui/notice.tsx +0 -84
  581. package/src/ui/overlay.tsx +0 -277
  582. package/src/ui/pane-grid.tsx +0 -117
  583. package/src/ui/pane-group.tsx +0 -291
  584. package/src/ui/pane.tsx +0 -193
  585. package/src/ui/popover.tsx +0 -106
  586. package/src/ui/progress-bar.tsx +0 -83
  587. package/src/ui/prompt-dock.tsx +0 -145
  588. package/src/ui/radio.tsx +0 -150
  589. package/src/ui/resizable.tsx +0 -693
  590. package/src/ui/scroll-area.tsx +0 -26
  591. package/src/ui/segmented.tsx +0 -109
  592. package/src/ui/select.tsx +0 -168
  593. package/src/ui/separator.tsx +0 -10
  594. package/src/ui/settings-group.tsx +0 -67
  595. package/src/ui/skeleton.tsx +0 -73
  596. package/src/ui/slider.tsx +0 -178
  597. package/src/ui/status.tsx +0 -48
  598. package/src/ui/switch.tsx +0 -119
  599. package/src/ui/tabs.tsx +0 -168
  600. package/src/ui/textarea.tsx +0 -21
  601. package/src/ui/tooltip.tsx +0 -118
  602. package/src/utils/cn.ts +0 -30
  603. package/src/wire/chunk.ts +0 -248
  604. package/src/wire/consume.ts +0 -599
  605. package/src/wire/diagnostics.ts +0 -727
  606. package/src/wire/encode-probe.ts +0 -214
  607. package/src/wire/encode.ts +0 -947
  608. package/src/wire/files.ts +0 -342
  609. package/src/wire/formats/anthropic.ts +0 -274
  610. package/src/wire/formats/openai.ts +0 -228
  611. package/src/wire/index.ts +0 -95
  612. package/src/wire/media-types.ts +0 -344
  613. package/src/wire/read.ts +0 -396
  614. package/src/wire/sink-helpers.ts +0 -50
  615. package/src/wire/sse.ts +0 -153
@@ -1,914 +0,0 @@
1
- import {
2
- type JSX,
3
- splitProps,
4
- mergeProps,
5
- createSignal,
6
- createEffect,
7
- createMemo,
8
- on,
9
- onMount,
10
- onCleanup,
11
- Show,
12
- } from 'solid-js';
13
- import { cn } from '../utils/cn';
14
- import { Button } from '../ui/button';
15
- import { CodeBlock, CodeBlockCode } from './code-block';
16
- import { FileTree, type FileTreeFile } from './file-tree';
17
- import { Loader } from './loader';
18
- import { isPdfPreviewEnabled, renderPdfInto } from '../primitives/pdf-preview';
19
- import { isSafeUrl, isScriptUrl } from '../primitives/card-routing';
20
- import {
21
- ArrowLeft,
22
- ArrowRight,
23
- RotateCw,
24
- House,
25
- Eye,
26
- Code as CodeIcon,
27
- FileText,
28
- ExternalLink,
29
- Download,
30
- Maximize2,
31
- Minimize2,
32
- } from 'lucide-solid';
33
-
34
- export type ArtifactTab = 'preview' | 'code';
35
-
36
- /** A file the artifact can preview + show source for. */
37
- export type ArtifactFile = FileTreeFile;
38
-
39
- /** Imperative handle exposed via `controllerRef` — surfaces the artifact's latent
40
- * toolbar capabilities (history back/forward/reload/home, programmatic navigate,
41
- * file selection, open-in-new-tab, maximize/restore) so the `<kai-artifact>`
42
- * facade can forward them as instance methods. Each delegates to the SAME internal
43
- * handler the toolbar buttons use, so every existing event still fires. */
44
- export interface ArtifactController {
45
- /** Go back in the artifact's own history stack (no-op when there's no prior entry). */
46
- back(): void;
47
- /** Go forward in the history stack (no-op when there's no forward entry). */
48
- forward(): void;
49
- /** Force-reload the current preview url (also re-renders an inline PDF). */
50
- reload(): void;
51
- /** Navigate to the `src` home url (no-op when there's no `src`). */
52
- home(): void;
53
- /** Push + load a url in the preview (the path-field submit path). */
54
- navigate(url: string): void;
55
- /** Select a file by path: highlights the tree, shows its source, navigates the preview. */
56
- selectFile(path: string): void;
57
- /** Open the current url in a new browser tab (no-op when there's no concrete url). */
58
- openExternal(): void;
59
- /** Enter the maximized view-state (fires onMaximizeChange/kai-maximize-change). */
60
- maximize(): void;
61
- /** Exit the maximized view-state. */
62
- restore(): void;
63
- }
64
-
65
- export interface ArtifactProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'onSelect'> {
66
- /** URL the preview iframe frames. */
67
- src?: string;
68
- /** Files for the Code tab's tree (+ each file's preview `url`). */
69
- files?: ArtifactFile[];
70
- /** Controlled active tab — when set, the artifact follows it (re-asserted on
71
- * every change). When undefined the tab is uncontrolled (see `defaultTab`). */
72
- tab?: ArtifactTab;
73
- /** Uncontrolled INITIAL tab (used only when `tab` is undefined). The user can
74
- * then freely switch tabs; defaults to `preview`. */
75
- defaultTab?: ArtifactTab;
76
- /** Selected file path (syncs tree highlight + Code source + preview). */
77
- activeFile?: string;
78
- /** iframe `sandbox` override. Default `allow-scripts allow-forms`. */
79
- sandbox?: string;
80
- /** Accessible iframe title. */
81
- iframeTitle?: string;
82
- /** Fired when the preview navigates (back/forward/reload/path-edit/file-click). */
83
- onNavigate?: (url: string) => void;
84
- /** Fired when the Preview|Code tab changes. */
85
- onTabChange?: (tab: ArtifactTab) => void;
86
- /** Fired when a file is selected in the tree. */
87
- onFileSelect?: (path: string) => void;
88
- // view-state
89
- /** Controlled maximize view-state (drives the expand/restore button). */
90
- maximized?: boolean;
91
- /** Fired when the expand/restore button toggles the maximize view-state. */
92
- onMaximizeChange?: (maximized: boolean) => void;
93
- // toolbar composition — existing five default SHOWN (no-* flags invert in the facade)
94
- /** Show the back/forward nav buttons. Default `true`. */
95
- showNav?: boolean;
96
- /** Show the reload button. Default `true`. */
97
- showReload?: boolean;
98
- /** Show the home button. Default `true`. */
99
- showHome?: boolean;
100
- /** Show the editable path/address field. Default `true`. */
101
- showPathField?: boolean;
102
- /** Show the Preview|Code tab toggle. Default `true`. */
103
- showTabs?: boolean;
104
- // new affordances — OPT-IN (default hidden; see resolved decision #2)
105
- /** Show the expand-to-fill button. Default `false` (opt-in). */
106
- expandable?: boolean;
107
- /** Show the open-in-new-tab button. Default `false` (opt-in). */
108
- openInTab?: boolean;
109
- // chrome
110
- /** Standalone chrome: rounded + bordered (default in-panel = square, borderless). */
111
- standalone?: boolean;
112
- /** Make the path field read-only (visible, nav-tracking, non-editable). */
113
- readonlyPath?: boolean;
114
- /** Friendly address shown in the path field INSTEAD of the real current url
115
- * (read-only, non-navigable). Use when the framed url is not consumer-facing
116
- * (e.g. a `data:` blob) so a clean address is shown instead of leaking it.
117
- * Unset = show the real url (editable per `readonlyPath`). */
118
- displayUrl?: string;
119
- /** Receive the imperative controller once mounted. The `<kai-artifact>` facade
120
- * forwards these as element methods (back/forward/reload/home/navigate/
121
- * selectFile/openExternal/maximize/restore). */
122
- controllerRef?: (controller: ArtifactController) => void;
123
- }
124
-
125
- const DEFAULT_SANDBOX = 'allow-scripts allow-forms';
126
-
127
- /** Refuse loudly. `src` and `files[].url` are MODEL-REACHABLE -- they arrive on
128
- * an `artifact` card envelope produced by a tool call, and the card schema
129
- * validates them only as `format: "uri"`, which constrains no scheme -- so
130
- * every url this component puts in an href, hands to `window.open`, or frames
131
- * is filtered. A refusal without a trace would be a decision made while hiding
132
- * that it happened; the component has no error channel of its own, so it warns
133
- * the way `card-routing` does. */
134
- function warnBlockedUrl(sink: string, url: string): void {
135
- // eslint-disable-next-line no-console
136
- console.warn(`[kai-artifact] blocked unsafe url in ${sink}: ${url}`);
137
- }
138
-
139
- /** Resolve a file's preview URL: explicit `url`, else `<src-origin> + /path`. */
140
- function resolveFileUrl(file: ArtifactFile, src: string | undefined): string {
141
- if (file.url) return file.url;
142
- if (!src) return file.path;
143
- try {
144
- return new URL(file.path, src).href;
145
- } catch {
146
- return file.path;
147
- }
148
- }
149
-
150
- /** True when `url` should render as a PDF: a matching `files` entry is typed
151
- * `'pdf'`, or the URL path (query/hash stripped) ends in `.pdf`. */
152
- export function isPdfUrl(url: string, files: ArtifactFile[]): boolean {
153
- if (!url) return false;
154
- const match = files.find((f) => f.url === url || f.path === url);
155
- if (match?.type === 'pdf') return true;
156
- const path = url.split(/[?#]/)[0];
157
- return /\.pdf$/i.test(path);
158
- }
159
-
160
- /**
161
- * `Artifact` — a framed, switchable generated-artifact viewer. A functional nav
162
- * toolbar (back · forward · reload · home + editable path field + Preview|Code
163
- * toggle) over a sandboxed `<iframe>` (Preview) or a file-tree + `<kai-code-block>`
164
- * (Code). The component self-navigates the iframe and emits `kai-navigate` /
165
- * `kai-tab-change` / `kai-file-select` so a consumer can observe/sync.
166
- */
167
- export function Artifact(props: ArtifactProps): JSX.Element {
168
- const merged = mergeProps(
169
- {
170
- files: [] as ArtifactFile[],
171
- // No `tab` default: a present `tab` means controlled. The uncontrolled
172
- // initial tab comes from `defaultTab` (seeded into the internal signal below).
173
- defaultTab: 'preview' as ArtifactTab,
174
- sandbox: DEFAULT_SANDBOX,
175
- showNav: true,
176
- showReload: true,
177
- showHome: true,
178
- showPathField: true,
179
- showTabs: true,
180
- expandable: false,
181
- openInTab: false,
182
- standalone: false,
183
- readonlyPath: false,
184
- maximized: false,
185
- },
186
- props,
187
- );
188
- const [local, rest] = splitProps(merged, [
189
- 'src',
190
- 'files',
191
- 'tab',
192
- 'defaultTab',
193
- 'activeFile',
194
- 'sandbox',
195
- 'iframeTitle',
196
- 'onNavigate',
197
- 'onTabChange',
198
- 'onFileSelect',
199
- 'maximized',
200
- 'onMaximizeChange',
201
- 'showNav',
202
- 'showReload',
203
- 'showHome',
204
- 'showPathField',
205
- 'showTabs',
206
- 'expandable',
207
- 'openInTab',
208
- 'standalone',
209
- 'readonlyPath',
210
- 'displayUrl',
211
- 'controllerRef',
212
- 'class',
213
- ]);
214
-
215
- // A sandboxed iframe WITHOUT `allow-same-origin` makes the framed document's
216
- // `contentWindow.history`/`location` opaque (a deliberate security property —
217
- // see the spec). So the component keeps its OWN history stack for the
218
- // navigations it initiates (path-edit, file-click, home, src changes) and
219
- // back/forward/reload operate on that. (In-frame relative-link clicks navigate
220
- // the iframe itself but can't be observed cross-origin — a known, accepted
221
- // sandbox limitation.)
222
- //
223
- // DO NOT add `allow-same-origin` to get those clicks tracked. This comment
224
- // used to suggest exactly that "if the consumer trusts the artifact", and it
225
- // was the worst advice in the file: `allow-same-origin` gives the framed
226
- // document the HOST's origin, so a hostile `src` runs with full access to the
227
- // embedding page, zero clicks -- confirmed executing in Chromium. And "the
228
- // artifact is trusted" is not a premise you get to hold when the url can come
229
- // from tool output: on the card path `src` and `files[].url` are
230
- // model-supplied. The scheme filter below is a backstop, not a licence --
231
- // `allow-same-origin` still hands the framed document the host's cookies,
232
- // storage and DOM for everything else it does. Losing in-frame click tracking
233
- // is the correct trade.
234
- const [history, setHistory] = createSignal<string[]>(local.src ? [local.src] : []);
235
- const [cursor, setCursor] = createSignal(local.src ? 0 : -1);
236
- const currentUrl = () => history()[cursor()] ?? '';
237
- const canBack = () => cursor() > 0;
238
- const canForward = () => cursor() < history().length - 1;
239
-
240
- /** What the iframe is actually pointed at.
241
- *
242
- * Separate from `currentUrl()` on purpose: the history stack and the address
243
- * field keep reporting what ARRIVED (a reader looking at a refused artifact
244
- * should see the address that was refused, not a blank), while this is the
245
- * only value that reaches the `src` sink.
246
- *
247
- * `isScriptUrl` and not `isSafeUrl`: a `data:`/`blob:` src is a legitimate
248
- * artifact -- see the note on `displayUrl` -- and gets an opaque origin, so
249
- * it cannot touch the host page. `javascript:`/`vbscript:` run in the
250
- * EMBEDDER's origin, which the default sandbox already refuses but a
251
- * consumer-set `allow-same-origin` re-enables (see the note on the history
252
- * stack above for why nothing here recommends that setting). */
253
- const framedUrl = createMemo(() => {
254
- const u = currentUrl();
255
- if (u && isScriptUrl(u)) {
256
- warnBlockedUrl('preview', u);
257
- return '';
258
- }
259
- return u;
260
- });
261
-
262
- // Seed the internal tab: a present `tab` (controlled) wins; otherwise the
263
- // uncontrolled `defaultTab` (which merges to 'preview'). The user can then
264
- // switch freely unless `tab` is set (the effect below re-asserts the controlled
265
- // value on each change).
266
- const [tab, setTab] = createSignal<ArtifactTab>(local.tab ?? local.defaultTab);
267
- const [activeFile, setActiveFile] = createSignal<string | undefined>(local.activeFile);
268
- const [reloadKey, setReloadKey] = createSignal(0);
269
-
270
- // Maximize view-state — controlled by `local.maximized`, toggled by the button.
271
- const [maximized, setMaximized] = createSignal<boolean>(local.maximized ?? false);
272
- createEffect(() => setMaximized(local.maximized ?? false));
273
- const toggleMaximize = () => {
274
- const next = !maximized();
275
- setMaximized(next);
276
- local.onMaximizeChange?.(next);
277
- };
278
-
279
- // Open-in-tab — only enabled when there's a concrete SAFE url to open. The
280
- // scheme filter is part of `canOpenInTab` rather than only of the handler so
281
- // the button renders DISABLED for a url that would be refused: a control that
282
- // looks live and does nothing is the worse failure.
283
- const canOpenInTab = createMemo(() => {
284
- const u = currentUrl();
285
- return !!u && u !== 'about:blank' && isSafeUrl(u);
286
- });
287
- const openInNewTab = () => {
288
- const u = currentUrl();
289
- if (!canOpenInTab()) {
290
- // `openExternal()` on the controller reaches this without any button, so
291
- // the refusal has to be stated here and not only in the disabled state.
292
- if (u && u !== 'about:blank') warnBlockedUrl('openExternal', u);
293
- return;
294
- }
295
- window.open(u, '_blank', 'noopener,noreferrer');
296
- };
297
-
298
- // The expand button is suppressed in standalone (no enclosing resizable).
299
- const showExpand = createMemo(() => local.expandable && !local.standalone);
300
- // Omit the whole toolbar when nothing is shown.
301
- const showAnyToolbar = createMemo(
302
- () =>
303
- local.showNav ||
304
- local.showReload ||
305
- local.showHome ||
306
- local.showPathField ||
307
- local.showTabs ||
308
- showExpand() ||
309
- local.openInTab,
310
- );
311
-
312
- let iframeEl: HTMLIFrameElement | undefined;
313
-
314
- // Controlled syncing: when the consumer changes the props, follow them. `tab` is
315
- // followed ONLY when it's actually set — an undefined `tab` means uncontrolled,
316
- // so the internal signal (seeded from defaultTab) is left for the user to drive.
317
- createEffect(() => {
318
- if (local.tab !== undefined) setTab(local.tab);
319
- });
320
- createEffect(() => setActiveFile(local.activeFile));
321
- // `src` change → navigate. Use `on(local.src, …)` so the effect tracks ONLY
322
- // the `src` prop — NOT `currentUrl()` — otherwise navigating away (file click,
323
- // path edit) would re-trigger it and snap the iframe back to `src`. Skip the
324
- // initial run: history is already seeded with `src` and the iframe binds it.
325
- createEffect(
326
- on(
327
- () => local.src,
328
- (next) => {
329
- if (next && next !== currentUrl()) navigate(next);
330
- },
331
- { defer: true },
332
- ),
333
- );
334
-
335
- const fileFor = (path: string | undefined): ArtifactFile | undefined =>
336
- path === undefined ? undefined : local.files.find((f) => f.path === path);
337
-
338
- const activeFileObj = createMemo(() => fileFor(activeFile()));
339
- // Code applies only to text-ish files that carry source.
340
- const hasSource = createMemo(() => {
341
- const f = activeFileObj();
342
- return !!f && f.type !== 'image' && f.type !== 'pdf' && typeof f.code === 'string';
343
- });
344
-
345
- /** Push a new entry (truncating any forward history) and load it. */
346
- function navigate(url: string) {
347
- if (url === currentUrl()) {
348
- reload();
349
- return;
350
- }
351
- setHistory((h) => [...h.slice(0, cursor() + 1), url]);
352
- setCursor((c) => c + 1);
353
- loadCurrent();
354
- }
355
-
356
- /** Point the iframe at the current cursor entry + emit `navigate`. */
357
- function loadCurrent() {
358
- if (iframeEl) iframeEl.src = framedUrl() || 'about:blank';
359
- // Reports the REAL url, not the framed one: a consumer observing navigation
360
- // (or a card patching `src` back into its envelope) must not be told the
361
- // model sent something it did not.
362
- local.onNavigate?.(currentUrl());
363
- }
364
-
365
- function selectTab(next: ArtifactTab) {
366
- if (next === tab()) return;
367
- setTab(next);
368
- local.onTabChange?.(next);
369
- }
370
-
371
- function selectFile(path: string, file: ArtifactFile) {
372
- setActiveFile(path);
373
- local.onFileSelect?.(path);
374
- navigate(resolveFileUrl(file, local.src));
375
- }
376
-
377
- // Toolbar actions — operate on the component's own history stack (see above).
378
- const goBack = () => {
379
- if (!canBack()) return;
380
- setCursor((c) => c - 1);
381
- loadCurrent();
382
- };
383
- const goForward = () => {
384
- if (!canForward()) return;
385
- setCursor((c) => c + 1);
386
- loadCurrent();
387
- };
388
- function reload() {
389
- if (iframeEl) {
390
- // Force a real reload even when the src is unchanged.
391
- iframeEl.src = 'about:blank';
392
- iframeEl.src = framedUrl() || 'about:blank';
393
- }
394
- setReloadKey((k) => k + 1); // re-render the inline PDF viewer too
395
- local.onNavigate?.(currentUrl());
396
- }
397
- const goHome = () => {
398
- if (local.src) navigate(local.src);
399
- };
400
-
401
- // Explicitly drive the maximize view-state to a target (the imperative twin of
402
- // the toggle button). Fires onMaximizeChange only when the value actually flips,
403
- // matching toggleMaximize's "report on change" contract.
404
- const setMaximizeState = (next: boolean) => {
405
- if (maximized() === next) return;
406
- setMaximized(next);
407
- local.onMaximizeChange?.(next);
408
- };
409
-
410
- // --- Imperative controller (Pattern C): hand the facade a handle over the
411
- // artifact's latent toolbar capabilities. Every method delegates to the
412
- // SAME internal handler the toolbar buttons use, so navigate/tab-change/
413
- // file-select/maximize-change all still fire. ---
414
- onMount(() => {
415
- local.controllerRef?.({
416
- back: () => goBack(),
417
- forward: () => goForward(),
418
- reload: () => reload(),
419
- home: () => goHome(),
420
- navigate: (url) => navigate(url),
421
- // Resolve the file for the path so highlight + source + preview all sync;
422
- // a path with no matching file entry is a no-op (nothing to show).
423
- selectFile: (path) => {
424
- const file = fileFor(path);
425
- if (file) selectFile(path, file);
426
- },
427
- openExternal: () => openInNewTab(),
428
- maximize: () => setMaximizeState(true),
429
- restore: () => setMaximizeState(false),
430
- });
431
- });
432
-
433
- // Best-effort: if the consumer opted into `allow-same-origin`, keep the path
434
- // field truthful as the framed doc navigates itself. Cross-origin (the secure
435
- // default) throws and we keep our own stack.
436
- const onIframeLoad = () => {
437
- try {
438
- const href = iframeEl?.contentWindow?.location.href;
439
- if (href && href !== 'about:blank' && href !== currentUrl()) {
440
- setHistory((h) => [...h.slice(0, cursor() + 1), href]);
441
- setCursor((c) => c + 1);
442
- local.onNavigate?.(href);
443
- }
444
- } catch {
445
- /* cross-origin (sandboxed without allow-same-origin): keep our own url */
446
- }
447
- };
448
-
449
- const submitPath = (e: Event) => {
450
- e.preventDefault();
451
- const input = (e.currentTarget as HTMLFormElement).elements.namedItem(
452
- 'kai-artifact-path',
453
- ) as HTMLInputElement | null;
454
- if (local.readonlyPath || local.displayUrl != null) {
455
- // Submit is a no-op while read-only or showing a friendly displayUrl; keep
456
- // the field reflecting the shown value (never push the real, possibly data:, url).
457
- if (input) input.value = local.displayUrl ?? currentUrl();
458
- return;
459
- }
460
- if (input && input.value) navigate(input.value);
461
- };
462
-
463
- return (
464
- <div
465
- class={cn(
466
- 'flex h-full w-full flex-col overflow-hidden bg-card text-card-foreground',
467
- local.standalone && 'rounded-xl border border-border',
468
- local.class,
469
- )}
470
- {...rest}
471
- >
472
- <Show when={showAnyToolbar()}>
473
- <ArtifactToolbar
474
- url={() => local.displayUrl ?? currentUrl()}
475
- tab={tab}
476
- canBack={canBack}
477
- canForward={canForward}
478
- canHome={() => !!local.src}
479
- onBack={goBack}
480
- onForward={goForward}
481
- onReload={reload}
482
- onHome={goHome}
483
- onSubmitPath={submitPath}
484
- onTab={selectTab}
485
- showNav={() => local.showNav}
486
- showReload={() => local.showReload}
487
- showHome={() => local.showHome}
488
- showPathField={() => local.showPathField}
489
- showTabs={() => local.showTabs}
490
- showExpand={showExpand}
491
- showOpenInTab={() => local.openInTab}
492
- maximized={maximized}
493
- onToggleMaximize={toggleMaximize}
494
- canOpenInTab={canOpenInTab}
495
- onOpenInTab={openInNewTab}
496
- readonlyPath={() => local.readonlyPath || local.displayUrl != null}
497
- />
498
- </Show>
499
- <div class="relative min-h-0 flex-1">
500
- <Show
501
- when={tab() === 'preview'}
502
- fallback={
503
- <ArtifactCode
504
- files={local.files}
505
- activeFile={activeFile}
506
- activeFileObj={activeFileObj}
507
- hasSource={hasSource}
508
- onSelect={selectFile}
509
- />
510
- }
511
- >
512
- <Show
513
- when={isPdfUrl(currentUrl(), local.files)}
514
- fallback={
515
- <ArtifactPreview
516
- ref={(el) => (iframeEl = el)}
517
- src={framedUrl}
518
- sandbox={local.sandbox}
519
- title={local.iframeTitle ?? 'Artifact preview'}
520
- onLoad={onIframeLoad}
521
- />
522
- }
523
- >
524
- <ArtifactPdfPreview url={currentUrl()} reloadKey={reloadKey()} />
525
- </Show>
526
- </Show>
527
- </div>
528
- </div>
529
- );
530
- }
531
-
532
- // --- ArtifactToolbar (internal) -------------------------------------------
533
-
534
- interface ToolbarProps {
535
- url: () => string;
536
- tab: () => ArtifactTab;
537
- canBack: () => boolean;
538
- canForward: () => boolean;
539
- canHome: () => boolean;
540
- onBack: () => void;
541
- onForward: () => void;
542
- onReload: () => void;
543
- onHome: () => void;
544
- onSubmitPath: (e: Event) => void;
545
- onTab: (tab: ArtifactTab) => void;
546
- showNav: () => boolean;
547
- showReload: () => boolean;
548
- showHome: () => boolean;
549
- showPathField: () => boolean;
550
- showTabs: () => boolean;
551
- showExpand: () => boolean;
552
- showOpenInTab: () => boolean;
553
- maximized: () => boolean;
554
- onToggleMaximize: () => void;
555
- canOpenInTab: () => boolean;
556
- onOpenInTab: () => void;
557
- readonlyPath: () => boolean;
558
- }
559
-
560
- function ArtifactToolbar(props: ToolbarProps): JSX.Element {
561
- return (
562
- <div
563
- data-artifact-toolbar
564
- class="flex shrink-0 items-center gap-1.5 border-b border-border bg-surface px-2 py-1.5"
565
- >
566
- <Show when={props.showNav()}>
567
- <Button
568
- variant="ghost"
569
- size="icon-sm"
570
- aria-label="Back"
571
- disabled={!props.canBack()}
572
- onClick={() => props.onBack()}
573
- >
574
- <ArrowLeft size={16} aria-hidden="true" />
575
- </Button>
576
- <Button
577
- variant="ghost"
578
- size="icon-sm"
579
- aria-label="Forward"
580
- disabled={!props.canForward()}
581
- onClick={() => props.onForward()}
582
- >
583
- <ArrowRight size={16} aria-hidden="true" />
584
- </Button>
585
- </Show>
586
- <Show when={props.showReload()}>
587
- <Button variant="ghost" size="icon-sm" aria-label="Reload" onClick={() => props.onReload()}>
588
- <RotateCw size={15} aria-hidden="true" />
589
- </Button>
590
- </Show>
591
- <Show when={props.showHome()}>
592
- <Button
593
- variant="ghost"
594
- size="icon-sm"
595
- aria-label="Home"
596
- disabled={!props.canHome()}
597
- onClick={() => props.onHome()}
598
- >
599
- <House size={15} aria-hidden="true" />
600
- </Button>
601
- </Show>
602
- <Show when={props.showPathField()}>
603
- <form class="min-w-0 flex-1" onSubmit={(e) => props.onSubmitPath(e)}>
604
- <label class="sr-only" for="kai-artifact-path">
605
- Address
606
- </label>
607
- <input
608
- id="kai-artifact-path"
609
- name="kai-artifact-path"
610
- type="text"
611
- spellcheck={false}
612
- autocomplete="off"
613
- readonly={props.readonlyPath() || undefined}
614
- aria-readonly={props.readonlyPath() ? 'true' : undefined}
615
- value={props.url()}
616
- class={cn(
617
- 'h-7 w-full rounded-md border border-border px-2.5 text-xs text-foreground font-mono outline-none',
618
- props.readonlyPath()
619
- ? 'bg-muted/40 cursor-default'
620
- : 'bg-background focus-visible:ring-2 focus-visible:ring-ring',
621
- )}
622
- placeholder="Enter a path or URL…"
623
- />
624
- </form>
625
- </Show>
626
- <Show when={!props.showPathField()}>
627
- <div class="flex-1" aria-hidden="true" />
628
- </Show>
629
- <Show when={props.showExpand()}>
630
- <Button
631
- variant="ghost"
632
- size="icon-sm"
633
- aria-label={props.maximized() ? 'Collapse' : 'Expand'}
634
- aria-expanded={props.maximized()}
635
- onClick={() => props.onToggleMaximize()}
636
- >
637
- <Show when={props.maximized()} fallback={<Maximize2 size={15} aria-hidden="true" />}>
638
- <Minimize2 size={15} aria-hidden="true" />
639
- </Show>
640
- </Button>
641
- </Show>
642
- <Show when={props.showOpenInTab()}>
643
- <Button
644
- variant="ghost"
645
- size="icon-sm"
646
- aria-label="Open in new tab"
647
- disabled={!props.canOpenInTab()}
648
- onClick={() => props.onOpenInTab()}
649
- >
650
- <ExternalLink size={15} aria-hidden="true" />
651
- </Button>
652
- </Show>
653
- <Show when={props.showTabs()}>
654
- <div
655
- role="tablist"
656
- aria-label="View"
657
- class="flex shrink-0 items-center gap-0.5 rounded-md bg-muted p-0.5"
658
- >
659
- <SegmentButton
660
- label="Preview"
661
- icon={<Eye size={14} aria-hidden="true" />}
662
- selected={props.tab() === 'preview'}
663
- onClick={() => props.onTab('preview')}
664
- />
665
- <SegmentButton
666
- label="Code"
667
- icon={<CodeIcon size={14} aria-hidden="true" />}
668
- selected={props.tab() === 'code'}
669
- onClick={() => props.onTab('code')}
670
- />
671
- </div>
672
- </Show>
673
- </div>
674
- );
675
- }
676
-
677
- function SegmentButton(props: {
678
- label: string;
679
- icon: JSX.Element;
680
- selected: boolean;
681
- onClick: () => void;
682
- }): JSX.Element {
683
- return (
684
- <button
685
- type="button"
686
- role="tab"
687
- aria-selected={props.selected}
688
- class={cn(
689
- 'inline-flex h-6 items-center gap-1.5 rounded px-2 text-xs font-medium transition-colors outline-none',
690
- 'focus-visible:ring-2 focus-visible:ring-ring',
691
- props.selected
692
- ? 'bg-background text-foreground shadow-sm'
693
- : 'text-muted-foreground hover:text-foreground',
694
- )}
695
- onClick={() => props.onClick()}
696
- >
697
- {props.icon}
698
- {props.label}
699
- </button>
700
- );
701
- }
702
-
703
- // --- ArtifactPreview (internal) -------------------------------------------
704
-
705
- interface PreviewProps {
706
- ref: (el: HTMLIFrameElement) => void;
707
- src: () => string;
708
- sandbox: string;
709
- title: string;
710
- onLoad: () => void;
711
- }
712
-
713
- function ArtifactPreview(props: PreviewProps): JSX.Element {
714
- return (
715
- <iframe
716
- ref={props.ref}
717
- src={props.src() || 'about:blank'}
718
- sandbox={props.sandbox}
719
- title={props.title}
720
- class="absolute inset-0 h-full w-full border-0 bg-white"
721
- onLoad={() => props.onLoad()}
722
- />
723
- );
724
- }
725
-
726
- // --- ArtifactCode (internal) ----------------------------------------------
727
-
728
- interface CodeProps {
729
- files: ArtifactFile[];
730
- activeFile: () => string | undefined;
731
- activeFileObj: () => ArtifactFile | undefined;
732
- hasSource: () => boolean;
733
- onSelect: (path: string, file: ArtifactFile) => void;
734
- }
735
-
736
- function ArtifactCode(props: CodeProps): JSX.Element {
737
- return (
738
- <div class="absolute inset-0 flex">
739
- <div class="w-56 shrink-0 overflow-auto border-r border-border bg-surface-sunken py-1.5 scrollbar-thin">
740
- <FileTree
741
- files={props.files}
742
- activeFile={props.activeFile()}
743
- onSelect={(path, file) => props.onSelect(path, file)}
744
- />
745
- </div>
746
- <div class="min-w-0 flex-1 overflow-auto scrollbar-thin">
747
- <Show
748
- when={props.activeFileObj()}
749
- fallback={
750
- <div class="flex h-full items-center justify-center p-6 text-sm text-muted-foreground">
751
- Select a file to view its source.
752
- </div>
753
- }
754
- >
755
- <Show
756
- when={props.hasSource()}
757
- fallback={
758
- <div class="flex h-full items-center justify-center p-6 text-sm text-muted-foreground">
759
- No source — this file ({props.activeFileObj()!.type ?? 'binary'}) has no code view.
760
- </div>
761
- }
762
- >
763
- <CodeBlock class="h-full rounded-none border-0">
764
- <CodeBlockCode
765
- code={props.activeFileObj()!.code ?? ''}
766
- language={props.activeFileObj()!.language}
767
- />
768
- </CodeBlock>
769
- </Show>
770
- </Show>
771
- </div>
772
- </div>
773
- );
774
- }
775
-
776
- // --- ArtifactPdfFallback (internal) ---------------------------------------
777
-
778
- /** Shown when inline PDF rendering is disabled or fails (CORS / load / parse).
779
- *
780
- * THIS IS THE DEFAULT VIEW FOR A HOSTILE URL, not an edge case: pdf.js has to
781
- * FETCH the document to render it inline, and it cannot fetch a `javascript:`
782
- * or `data:` one, so every such url fails straight through to here -- where two
783
- * anchors used to put it in an `href`, one click from executing in the host
784
- * page's origin. The url is model-reachable (`artifact` card `src` /
785
- * `files[].url`, schema-validated only as `format: "uri"`), so it is filtered
786
- * with the same `isSafeUrl` the markdown renderer and the card `open` verb use.
787
- * Relative paths pass, which matters here: `resolveFileUrl` returns a bare
788
- * `path` when the artifact has no `src`. */
789
- function ArtifactPdfFallback(props: { url: string }): JSX.Element {
790
- const name = () => {
791
- const path = props.url.split(/[?#]/)[0];
792
- return path.slice(path.lastIndexOf('/') + 1) || 'document.pdf';
793
- };
794
- /** The url actually put in `href`, or `undefined` -- which OMITS the attribute.
795
- * Never `''`: an empty href navigates to the current page. */
796
- const href = createMemo(() => {
797
- if (!props.url) return undefined;
798
- if (isSafeUrl(props.url)) return props.url;
799
- warnBlockedUrl('pdf fallback', props.url);
800
- return undefined;
801
- });
802
- const linkClass =
803
- 'inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';
804
- return (
805
- <div
806
- role="region"
807
- aria-label="PDF preview unavailable"
808
- class="absolute inset-0 flex flex-col items-center justify-center gap-3 bg-card p-6 text-center"
809
- >
810
- <FileText size={40} class="text-muted-foreground" aria-hidden="true" />
811
- <div class="text-sm font-medium text-foreground">{name()}</div>
812
- <div class="text-xs text-muted-foreground">Can't preview this PDF inline.</div>
813
- {/* No safe address, no controls. Two buttons that silently do nothing
814
- would be worse than saying what happened -- and a bare `<a>` with the
815
- href dropped is exactly that. */}
816
- <Show
817
- when={href()}
818
- fallback={
819
- <div class="text-xs text-muted-foreground">
820
- Blocked: that address uses a scheme this viewer won't open.
821
- </div>
822
- }
823
- >
824
- <div class="flex flex-wrap items-center justify-center gap-2">
825
- <a class={linkClass} href={href()} target="_blank" rel="noopener noreferrer">
826
- Open in new tab
827
- <ExternalLink size={13} aria-hidden="true" />
828
- </a>
829
- <a class={linkClass} href={href()} download="">
830
- <Download size={13} aria-hidden="true" />
831
- Download
832
- </a>
833
- </div>
834
- </Show>
835
- </div>
836
- );
837
- }
838
-
839
- // --- ArtifactPdfPreview (internal) ----------------------------------------
840
-
841
- /**
842
- * Renders a PDF inline via pdf.js (loaded on demand). Four states:
843
- * disabled → fallback (no network); loading → spinner; success → stacked
844
- * fit-width canvases; error (load/CORS/parse) → fallback card. Re-renders when
845
- * the url or `reloadKey` changes, and (debounced) when the panel resizes.
846
- */
847
- function ArtifactPdfPreview(props: { url: string; reloadKey: number }): JSX.Element {
848
- const [state, setState] = createSignal<'loading' | 'success' | 'error'>('loading');
849
- let container: HTMLDivElement | undefined;
850
- let token = 0;
851
- let resizeTimer: ReturnType<typeof setTimeout> | undefined;
852
-
853
- const renderNow = async () => {
854
- if (!isPdfPreviewEnabled() || !container) {
855
- setState('error');
856
- return;
857
- }
858
- const mine = ++token;
859
- setState('loading');
860
- try {
861
- const width = container.clientWidth || 600;
862
- await renderPdfInto(props.url, container, width);
863
- if (mine === token) setState('success');
864
- } catch {
865
- if (mine === token) setState('error');
866
- }
867
- };
868
-
869
- createEffect(
870
- on(
871
- () => [props.url, props.reloadKey] as const,
872
- () => {
873
- void renderNow();
874
- },
875
- ),
876
- );
877
-
878
- onMount(() => {
879
- if (!container || typeof ResizeObserver === 'undefined') return;
880
- const ro = new ResizeObserver(() => {
881
- if (state() !== 'success') return;
882
- clearTimeout(resizeTimer);
883
- resizeTimer = setTimeout(() => void renderNow(), 200);
884
- });
885
- ro.observe(container);
886
- onCleanup(() => {
887
- ro.disconnect();
888
- clearTimeout(resizeTimer);
889
- token++; // cancel any in-flight render
890
- });
891
- });
892
-
893
- return (
894
- <div class="absolute inset-0">
895
- {/* Always present so clientWidth is the real panel width. */}
896
- <div
897
- ref={(el) => (container = el)}
898
- role="region"
899
- aria-label="PDF preview"
900
- aria-busy={state() === 'loading'}
901
- tabindex="0"
902
- class="absolute inset-0 flex flex-col items-center gap-3 overflow-auto bg-surface-sunken p-3 scrollbar-thin focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset"
903
- />
904
- <Show when={isPdfPreviewEnabled() && state() === 'loading'}>
905
- <div class="pointer-events-none absolute inset-0 flex items-center justify-center">
906
- <Loader variant="circular" size="md" />
907
- </div>
908
- </Show>
909
- <Show when={!isPdfPreviewEnabled() || state() === 'error'}>
910
- <ArtifactPdfFallback url={props.url} />
911
- </Show>
912
- </div>
913
- );
914
- }