@kitn.ai/ui 0.27.0 → 0.29.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 (612) hide show
  1. package/README.md +10 -2
  2. package/dist/{Icon-BnFKqqtY.js → Icon-DObhsRtm.js} +1 -1
  3. package/dist/{action-icons-CBskfBMz.js → action-icons-CcgNN2oT.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-CjRU3JgV.js +1 -0
  7. package/dist/arrow-left-Bnpb1UEW.js +1 -0
  8. package/dist/{artifact-CvXEm1lS.js → artifact-Bwt7OgcR.js} +1 -1
  9. package/dist/{attachments-C43ZegEq.js → attachments-Qr4kg-2b.js} +1 -1
  10. package/dist/{audio-visualizer-B-79y1Tj.js → audio-visualizer-DToYdfSc.js} +1 -1
  11. package/dist/{badge-BpUbq8c0.js → badge-CdEu_jn2.js} +1 -1
  12. package/dist/{bell-sn17Ip9v.js → bell-DQAAOpDz.js} +1 -1
  13. package/dist/{button-BH6rN0U-.js → button-DkuOnGLy.js} +1 -1
  14. package/dist/{card-renderer-Cb1gQmth.js → card-renderer-Jx70m6_l.js} +1 -1
  15. package/dist/{card-routing-DifQgS7n.js → card-routing-0YidER_3.js} +1 -1
  16. package/dist/check-njkVdZu2.js +1 -0
  17. package/dist/{checkbox-Dc9Yov_B.js → checkbox-D7iRB3nL.js} +1 -1
  18. package/dist/{checkbox-group-B1kDLLYk.js → checkbox-group-B2_rpoPM.js} +1 -1
  19. package/dist/chevron-down-B72s6PXR.js +1 -0
  20. package/dist/chevron-right-BatuHzUm.js +1 -0
  21. package/dist/{choice-card-BT3c1S8g.js → choice-card-m0BYqqOK.js} +1 -1
  22. package/dist/circle-check-B62pODB3.js +1 -0
  23. package/dist/{circle-x-wobWkoSx.js → circle-x-BPG0iPst.js} +1 -1
  24. package/dist/circle-zx0ByzY9.js +1 -0
  25. package/dist/code-block-ClMLKb7C.js +1 -0
  26. package/dist/{collapsible-CPJbf6FM.js → collapsible-B4DlWicL.js} +1 -1
  27. package/dist/components/chat-thread.d.ts +89 -1
  28. package/dist/components/conversation-item.d.ts +16 -0
  29. package/dist/components/conversation-panel.d.ts +29 -0
  30. package/dist/components/home-panel.d.ts +34 -0
  31. package/dist/components/widget-tab-bar.d.ts +16 -0
  32. package/dist/{composer-CjngLzal.js → composer-sQ0HcvyG.js} +3 -3
  33. package/dist/{confirm-card-DCx_A4bU.js → confirm-card-rQWDQsnC.js} +1 -1
  34. package/dist/construct-cli.es.js +185 -17
  35. package/dist/construct.d.ts +5 -0
  36. package/dist/construct.js +327 -0
  37. package/dist/{context-BY8V-Nlp.js → context-0kg4ZX1J.js} +1 -1
  38. package/dist/controllable-CnGpMF6r.js +1 -0
  39. package/dist/conversation-item-BxwQLaFJ.js +1 -0
  40. package/dist/conversation-list-BCfGN1JU.js +1 -0
  41. package/dist/{copy-B6DO4PqB.js → copy-n9u9rqpi.js} +1 -1
  42. package/dist/core-C8fzo39E.js +1 -12
  43. package/dist/{create-tween-CnMHviKB.js → create-tween-C-q-JCM-.js} +1 -1
  44. package/dist/{create-tween-D1aiGZZh.js → create-tween-CRrJXxBd.js} +1 -1
  45. package/dist/{create-tween-BPzaefTp.js → create-tween-DZ3RRDHD.js} +1 -1
  46. package/dist/{create-tween-DUSQUKpr.js → create-tween-uPkArjIG.js} +1 -1
  47. package/dist/custom-elements.json +51 -2
  48. package/dist/{default-input-CucQEOqH.js → default-input-CaMrJkgH.js} +1 -1
  49. package/dist/define-By-uL588.js +1 -0
  50. package/dist/define.js +2 -2
  51. package/dist/define.server.js +2 -2
  52. package/dist/{disclosure-CbLtajUQ.js → disclosure-WUImBbDv.js} +1 -1
  53. package/dist/{dropdown-BwhViJFd.js → dropdown-wZVeJOMd.js} +1 -1
  54. package/dist/elements/agent-card.js +1 -1
  55. package/dist/elements/artifact.js +1 -1
  56. package/dist/elements/attachments.js +1 -1
  57. package/dist/elements/audio-visualizer.js +1 -1
  58. package/dist/elements/avatar.js +1 -1
  59. package/dist/elements/badge.js +1 -1
  60. package/dist/elements/button.js +1 -1
  61. package/dist/elements/card.js +1 -1
  62. package/dist/elements/cards.js +1 -1
  63. package/dist/elements/chain-of-thought.js +1 -1
  64. package/dist/elements/chat-scope-picker.js +1 -1
  65. package/dist/elements/chat-workspace.js +1 -1
  66. package/dist/elements/chat.js +1 -1
  67. package/dist/elements/checkbox-group.js +1 -1
  68. package/dist/elements/checkbox.js +1 -1
  69. package/dist/elements/checkpoint.js +1 -1
  70. package/dist/elements/choice.js +1 -1
  71. package/dist/elements/coachmark.js +1 -1
  72. package/dist/elements/code-block.js +1 -1
  73. package/dist/elements/command.js +1 -1
  74. package/dist/elements/compare.js +1 -1
  75. package/dist/elements/composer.js +1 -1
  76. package/dist/elements/confirm-card.js +1 -1
  77. package/dist/elements/context-meter.js +1 -1
  78. package/dist/elements/conversation-item.js +1 -1
  79. package/dist/elements/conversation-list.js +1 -1
  80. package/dist/elements/dialog.js +1 -1
  81. package/dist/elements/dock.js +3 -3
  82. package/dist/elements/dropdown.js +1 -1
  83. package/dist/elements/editable-label.js +1 -1
  84. package/dist/elements/embed.js +1 -1
  85. package/dist/elements/empty.js +1 -1
  86. package/dist/elements/feedback-bar.js +1 -1
  87. package/dist/elements/file-tree.js +1 -1
  88. package/dist/elements/file-upload.js +1 -1
  89. package/dist/elements/form.js +1 -1
  90. package/dist/elements/hover-card.js +1 -1
  91. package/dist/elements/icon.js +1 -1
  92. package/dist/elements/image.js +1 -1
  93. package/dist/elements/input.js +1 -1
  94. package/dist/elements/kbd.js +1 -1
  95. package/dist/elements/link-preview.js +1 -1
  96. package/dist/elements/loader.js +1 -1
  97. package/dist/elements/markdown.js +1 -1
  98. package/dist/elements/menu.js +1 -1
  99. package/dist/elements/message-skills.js +1 -1
  100. package/dist/elements/message.js +1 -1
  101. package/dist/elements/model-switcher.js +1 -1
  102. package/dist/elements/nav.js +1 -1
  103. package/dist/elements/notice.js +1 -1
  104. package/dist/elements/pane-grid.js +1 -1
  105. package/dist/elements/pane-group.js +1 -1
  106. package/dist/elements/pane.js +1 -1
  107. package/dist/elements/popover.js +1 -1
  108. package/dist/elements/progress-bar.js +1 -1
  109. package/dist/elements/prompt-dock.js +1 -1
  110. package/dist/elements/prompt-input.js +1 -1
  111. package/dist/elements/prompt-suggestions.js +1 -1
  112. package/dist/elements/radio-group.js +1 -1
  113. package/dist/elements/reasoning.js +1 -1
  114. package/dist/elements/remote.js +1 -1
  115. package/dist/elements/resizable.js +1 -1
  116. package/dist/elements/response-stream.js +1 -1
  117. package/dist/elements/screen.js +1 -1
  118. package/dist/elements/scroll-area.js +1 -1
  119. package/dist/elements/scroll-button.js +1 -1
  120. package/dist/elements/search.js +1 -1
  121. package/dist/elements/segmented.js +1 -1
  122. package/dist/elements/select.js +1 -1
  123. package/dist/elements/separator.js +1 -1
  124. package/dist/elements/setting-item.js +1 -1
  125. package/dist/elements/settings-group.js +1 -1
  126. package/dist/elements/skeleton.js +1 -1
  127. package/dist/elements/slider.js +1 -1
  128. package/dist/elements/source.js +1 -1
  129. package/dist/elements/status.js +1 -1
  130. package/dist/elements/switch.js +1 -1
  131. package/dist/elements/tabs.js +1 -1
  132. package/dist/elements/tasks.js +1 -1
  133. package/dist/elements/text-shimmer.js +1 -1
  134. package/dist/elements/thinking-bar.js +1 -1
  135. package/dist/elements/thread.js +1 -1
  136. package/dist/elements/toast.js +1 -1
  137. package/dist/elements/tool.js +1 -1
  138. package/dist/elements/tooltip.js +1 -1
  139. package/dist/elements/voice-input.js +1 -1
  140. package/dist/elements/voice-output.js +1 -1
  141. package/dist/elements.d.ts +20 -4
  142. package/dist/{ellipsis-DFr4Vqr3.js → ellipsis-CbOpJnU3.js} +1 -1
  143. package/dist/{embed-5s_vU4J0.js → embed-CrfjkbUG.js} +1 -1
  144. package/dist/engine-javascript-C1x7zo1_.js +1 -141
  145. package/dist/{external-link-u9d85ORQ.js → external-link-ByXAwBBQ.js} +1 -1
  146. package/dist/{file-text-CX7_x42o.js → file-text-DMib2qwt.js} +1 -1
  147. package/dist/{file-tree-D3kBjXI5.js → file-tree-CJqM2aP7.js} +1 -1
  148. package/dist/{folder-V-YdIL8f.js → folder-_BAb929C.js} +1 -1
  149. package/dist/{form-BRNUMCN2.js → form-DSZgZQZk.js} +1 -1
  150. package/dist/{hover-card-DaeiYrTA.js → hover-card-BQbul9eq.js} +1 -1
  151. package/dist/{icon-BpixdZNq.js → icon-CdcfRhYI.js} +1 -1
  152. package/dist/index.d.ts +3 -1
  153. package/dist/index.js +2842 -2703
  154. package/dist/index.server.js +2820 -2681
  155. package/dist/{info-8mFsIjS3.js → info-DsFjGuI_.js} +1 -1
  156. package/dist/{input-d1Yuu-hz.js → input-DKbmkMos.js} +1 -1
  157. package/dist/kai.es.js +1 -1
  158. package/dist/{kbd-961zMPFB.js → kbd-PvdGhX4s.js} +1 -1
  159. package/dist/{link-preview-DIIbV2Sd.js → link-preview-DbM1866N.js} +1 -1
  160. package/dist/{link-DlZZBGaR.js → link-zF8PzWfF.js} +1 -1
  161. package/dist/{loader-YQszHFaW.js → loader-BM4CEz4G.js} +1 -1
  162. package/dist/{markdown-CVk1MPsj.js → markdown-LMvmAyBk.js} +7 -7
  163. package/dist/mcp.es.js +79 -5
  164. package/dist/{message-CwKVEdZg.js → message-BSsgvHvf.js} +1 -1
  165. package/dist/{message-DS8lzHc1.js → message-BYZECyek.js} +1 -1
  166. package/dist/message-circle-DruwObfX.js +1 -0
  167. package/dist/message-square-BG6q6mlm.js +1 -0
  168. package/dist/{minimize-2-UEZmP2QT.js → minimize-2-BQRoBV1V.js} +1 -1
  169. package/dist/{model-switcher-Kk1jEQ5t.js → model-switcher-CMpCOYcB.js} +1 -1
  170. package/dist/{overlay-C1od6LOz.js → overlay-Cb-MkyDa.js} +1 -1
  171. package/dist/{panel-right-ryET0yyn.js → panel-right-i8warFxT.js} +1 -1
  172. package/dist/{paperclip-BDNoZFJP.js → paperclip-ByY3UVO6.js} +1 -1
  173. package/dist/play-BvKKq3fX.js +1 -0
  174. package/dist/primitives/conversation-store.d.ts +54 -0
  175. package/dist/{progress-bar-BvYX0aoy.js → progress-bar-jVAHvMUf.js} +1 -1
  176. package/dist/{prompt-suggestion-BRFu2lcu.js → prompt-suggestion-CD-qtNgC.js} +1 -1
  177. package/dist/{radio-DcL23cNJ.js → radio-BsoBMhkF.js} +1 -1
  178. package/dist/react/index.d.ts +393 -0
  179. package/dist/react.js +20 -20
  180. package/dist/{reasoning-Cackq4Rh.js → reasoning-4kBPtlTx.js} +1 -1
  181. package/dist/register-impl-B1hLkrI8.js +424 -0
  182. package/dist/{resizable-Cp9HUYih.js → resizable-uPmY9VY-.js} +1 -1
  183. package/dist/{rotate-ccw-0DLRDHH5.js → rotate-ccw-B2xn93py.js} +1 -1
  184. package/dist/{rotate-cw-DOB9IGcU.js → rotate-cw-CMVLoCyH.js} +1 -1
  185. package/dist/{scroll-area-emVmWP4K.js → scroll-area-BqLUo8ZZ.js} +1 -1
  186. package/dist/{scroll-button-CeP_gv1x.js → scroll-button-DslXiJyL.js} +1 -1
  187. package/dist/{select-CWmu00tS.js → select-2cLRtPGT.js} +1 -1
  188. package/dist/{separator-CE6vly6z.js → separator-BWQtNJcM.js} +1 -1
  189. package/dist/{settings-CE-78SvZ.js → settings-BBR63jLg.js} +1 -1
  190. package/dist/{settings-group-ZFWNxFQR.js → settings-group-B2ciy-Iw.js} +1 -1
  191. package/dist/{skeleton-BebdP4mf.js → skeleton-CkHI-HgC.js} +1 -1
  192. package/dist/{slider-BAKRYUlz.js → slider-DDR9U-i0.js} +1 -1
  193. package/dist/{solid-BE5ui6sy.js → solid-BzCFdL25.js} +7720 -7058
  194. package/dist/{solid-DFhsce3O.js → solid-DruVOXnf.js} +5864 -5283
  195. package/dist/solid.d.ts +6 -2
  196. package/dist/solid.js +217 -210
  197. package/dist/solid.server.js +217 -210
  198. package/dist/{source-B0FlguSx.js → source-GEnFF528.js} +1 -1
  199. package/dist/{star-CBMZvLuN.js → star-CilHq8GP.js} +1 -1
  200. package/dist/store-Ciyk6eZG.js +1 -0
  201. package/dist/{switch-CViARZCy.js → switch-KkeQIbuH.js} +1 -1
  202. package/dist/{tasks-card-MjCcOhR_.js → tasks-card-BrN4bggJ.js} +1 -1
  203. package/dist/{text-shimmer-DKlwRF2G.js → text-shimmer-Dcj1MHuq.js} +1 -1
  204. package/dist/{textarea-3ay2Me2-.js → textarea-Dpvbngbg.js} +1 -1
  205. package/dist/theme.tokens.css +28 -0
  206. package/dist/{thumbs-up-DQcu2HMO.js → thumbs-up-CsT9Wzb9.js} +1 -1
  207. package/dist/{toast-store-C6yKOhbk.js → toast-store-tgK6MuMv.js} +1 -1
  208. package/dist/{tool-BCYNlfKy.js → tool-DzUjIBT8.js} +1 -1
  209. package/dist/{tooltip-05zI4hjs.js → tooltip-Bkkg_4t0.js} +1 -1
  210. package/dist/{trash-2-BR1RxGky.js → trash-2-jBKxjGGQ.js} +1 -1
  211. package/dist/{triangle-alert-DHohDOCy.js → triangle-alert-DlQ68ppf.js} +1 -1
  212. package/dist/types.d.ts +48 -3
  213. package/dist/ui/dock.d.ts +29 -0
  214. package/dist/{upload-BbGW6A_u.js → upload-TcAHXv9W.js} +1 -1
  215. package/dist/{url-scheme-policy-DHpoTJwB.js → url-scheme-policy-DR5DgvkM.js} +1 -1
  216. package/dist/{use-card-resolution-CJW4grFa.js → use-card-resolution-DaZqAdWf.js} +1 -1
  217. package/dist/{variant-aurora-xiMPty8f.js → variant-aurora-BhLuYxJR.js} +1 -1
  218. package/dist/{variant-aurora-BUOVgPOB.js → variant-aurora-CNmfHhcx.js} +2 -2
  219. package/dist/{variant-aurora-CUsUJvFI.js → variant-aurora-D6GsdTPA.js} +2 -2
  220. package/dist/{variant-aurora-CW2i4bfr.js → variant-aurora-DbW_OWL2.js} +1 -1
  221. package/dist/{variant-custom-D91HjAKz.js → variant-custom-BRvaNHSi.js} +1 -1
  222. package/dist/{variant-custom-BQtOnmeO.js → variant-custom-BvYHR62b.js} +1 -1
  223. package/dist/{variant-custom-CAnzzppW.js → variant-custom-DMTrEO5w.js} +2 -2
  224. package/dist/{variant-custom-CAm9L-E5.js → variant-custom-DV2x254s.js} +2 -2
  225. package/dist/{variant-wave-Ca1Ls4_y.js → variant-wave-BNMGrDfj.js} +2 -2
  226. package/dist/{variant-wave-uY5crU_K.js → variant-wave-BpDd8t3Z.js} +1 -1
  227. package/dist/{variant-wave-CxX39DKd.js → variant-wave-CyBueXj9.js} +2 -2
  228. package/dist/{variant-wave-DYyzIIZv.js → variant-wave-sp-qwsdI.js} +1 -1
  229. package/dist/x-Z5UntkIJ.js +1 -0
  230. package/frameworks/react/index.tsx +14 -4
  231. package/llms-full.txt +7 -2
  232. package/package.json +16 -16
  233. package/src/elements/element-meta.json +49 -6
  234. package/theme.css +28 -0
  235. package/bin/route.test.js +0 -58
  236. package/dist/arrow-down-Br0qtpsz.js +0 -1
  237. package/dist/arrow-left-Dj7xgE12.js +0 -1
  238. package/dist/check-DPeeMUYx.js +0 -1
  239. package/dist/chevron-down-CHSWk7ZT.js +0 -1
  240. package/dist/chevron-right-B7l_fMgx.js +0 -1
  241. package/dist/circle-Bb0yOpqM.js +0 -1
  242. package/dist/circle-check-BGZpCiz7.js +0 -1
  243. package/dist/code-block-CUQ5mtv0.js +0 -1
  244. package/dist/controllable-D43BKmtX.js +0 -1
  245. package/dist/conversation-list-D4lLU_yS.js +0 -1
  246. package/dist/define-CGGX-7Vg.js +0 -1
  247. package/dist/message-circle-Xf8Q-vZp.js +0 -1
  248. package/dist/message-square-Cxnwgkns.js +0 -1
  249. package/dist/play-OFIvYXUQ.js +0 -1
  250. package/dist/register-impl-C7rENvPK.js +0 -424
  251. package/dist/store-7eoadydQ.js +0 -1
  252. package/dist/x-C5m1JFgi.js +0 -1
  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 -131
  263. package/src/agent-tooling/construct/codegen.ts +0 -1353
  264. package/src/agent-tooling/construct/construct.v1.schema.json +0 -250
  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 -23
  269. package/src/agent-tooling/construct/schema.ts +0 -371
  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 -518
  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 -218
  336. package/src/components/conversation-list.tsx +0 -429
  337. package/src/components/dismissed-stub.tsx +0 -80
  338. package/src/components/embed.tsx +0 -200
  339. package/src/components/empty.tsx +0 -177
  340. package/src/components/feedback-bar.tsx +0 -166
  341. package/src/components/file-tree.tsx +0 -530
  342. package/src/components/file-upload.tsx +0 -167
  343. package/src/components/form-widgets.tsx +0 -525
  344. package/src/components/form.tsx +0 -1296
  345. package/src/components/image.tsx +0 -65
  346. package/src/components/link-preview.tsx +0 -195
  347. package/src/components/loader.tsx +0 -367
  348. package/src/components/markdown.tsx +0 -162
  349. package/src/components/message-skills.tsx +0 -40
  350. package/src/components/message.tsx +0 -765
  351. package/src/components/model-switcher.tsx +0 -132
  352. package/src/components/prompt-input.tsx +0 -188
  353. package/src/components/prompt-suggestion.tsx +0 -150
  354. package/src/components/reasoning.tsx +0 -293
  355. package/src/components/response-compare-types.ts +0 -152
  356. package/src/components/response-compare.tsx +0 -455
  357. package/src/components/response-stream.tsx +0 -109
  358. package/src/components/screen.tsx +0 -181
  359. package/src/components/scroll-button.tsx +0 -93
  360. package/src/components/source.tsx +0 -192
  361. package/src/components/tasks-card.tsx +0 -801
  362. package/src/components/text-shimmer.tsx +0 -37
  363. package/src/components/thinking-bar.tsx +0 -50
  364. package/src/components/thread.tsx +0 -228
  365. package/src/components/toast.tsx +0 -503
  366. package/src/components/tool-classify.ts +0 -26
  367. package/src/components/tool-types.ts +0 -49
  368. package/src/components/tool.tsx +0 -190
  369. package/src/components/use-card-resolution.ts +0 -44
  370. package/src/components/voice-input.tsx +0 -222
  371. package/src/components/voice-output.tsx +0 -242
  372. package/src/components/workspace-shell.tsx +0 -285
  373. package/src/diagnostics/hook.ts +0 -352
  374. package/src/diagnostics/index.ts +0 -82
  375. package/src/diagnostics/report-request.ts +0 -296
  376. package/src/elements/agent-card.tsx +0 -67
  377. package/src/elements/artifact.tsx +0 -194
  378. package/src/elements/attachments.tsx +0 -140
  379. package/src/elements/audio-visualizer.tsx +0 -227
  380. package/src/elements/autoloader.ts +0 -89
  381. package/src/elements/avatar.tsx +0 -33
  382. package/src/elements/badge.tsx +0 -32
  383. package/src/elements/button.tsx +0 -149
  384. package/src/elements/card.tsx +0 -133
  385. package/src/elements/cards.tsx +0 -266
  386. package/src/elements/chain-of-thought.tsx +0 -141
  387. package/src/elements/chat-scope-picker.tsx +0 -65
  388. package/src/elements/chat-types.ts +0 -102
  389. package/src/elements/chat-workspace.tsx +0 -188
  390. package/src/elements/chat.tsx +0 -212
  391. package/src/elements/checkbox-group.tsx +0 -164
  392. package/src/elements/checkbox.tsx +0 -134
  393. package/src/elements/checkpoint.tsx +0 -47
  394. package/src/elements/choice.tsx +0 -100
  395. package/src/elements/coachmark.tsx +0 -90
  396. package/src/elements/code-block.tsx +0 -60
  397. package/src/elements/command.tsx +0 -197
  398. package/src/elements/compare.tsx +0 -111
  399. package/src/elements/compiled.css +0 -2
  400. package/src/elements/composer.tsx +0 -162
  401. package/src/elements/confirm-card.tsx +0 -76
  402. package/src/elements/context-meter.tsx +0 -107
  403. package/src/elements/conversation-item.tsx +0 -120
  404. package/src/elements/conversation-list.tsx +0 -203
  405. package/src/elements/css.ts +0 -5
  406. package/src/elements/default-input.tsx +0 -392
  407. package/src/elements/define-entry.ts +0 -12
  408. package/src/elements/define.tsx +0 -563
  409. package/src/elements/diagnostic-events.ts +0 -114
  410. package/src/elements/dialog.tsx +0 -145
  411. package/src/elements/disclosure.ts +0 -95
  412. package/src/elements/dock.tsx +0 -215
  413. package/src/elements/dropdown.tsx +0 -123
  414. package/src/elements/editable-label.tsx +0 -140
  415. package/src/elements/element-data-types.ts +0 -152
  416. package/src/elements/element-diagnostics.ts +0 -392
  417. package/src/elements/element-manifest.json +0 -354
  418. package/src/elements/element-nonscalar.json +0 -185
  419. package/src/elements/element-types.d.ts +0 -4337
  420. package/src/elements/embed.tsx +0 -35
  421. package/src/elements/empty.tsx +0 -29
  422. package/src/elements/feedback-bar.tsx +0 -66
  423. package/src/elements/file-tree.tsx +0 -62
  424. package/src/elements/file-upload.tsx +0 -44
  425. package/src/elements/form.tsx +0 -94
  426. package/src/elements/hover-card.tsx +0 -80
  427. package/src/elements/icon.tsx +0 -41
  428. package/src/elements/image.tsx +0 -32
  429. package/src/elements/input.tsx +0 -492
  430. package/src/elements/kbd.tsx +0 -45
  431. package/src/elements/link-preview.tsx +0 -34
  432. package/src/elements/loader.tsx +0 -25
  433. package/src/elements/markdown.tsx +0 -38
  434. package/src/elements/menu.tsx +0 -241
  435. package/src/elements/message-skills.tsx +0 -83
  436. package/src/elements/message.tsx +0 -361
  437. package/src/elements/model-switcher.tsx +0 -121
  438. package/src/elements/nav.tsx +0 -87
  439. package/src/elements/notice.tsx +0 -53
  440. package/src/elements/pane-grid.tsx +0 -111
  441. package/src/elements/pane-group.tsx +0 -119
  442. package/src/elements/pane.tsx +0 -129
  443. package/src/elements/popover.tsx +0 -81
  444. package/src/elements/progress-bar.tsx +0 -39
  445. package/src/elements/prompt-dock.tsx +0 -82
  446. package/src/elements/prompt-input.tsx +0 -240
  447. package/src/elements/prompt-suggestions.tsx +0 -113
  448. package/src/elements/radio-group.tsx +0 -129
  449. package/src/elements/reasoning.tsx +0 -71
  450. package/src/elements/register-impl.ts +0 -148
  451. package/src/elements/register.ts +0 -76
  452. package/src/elements/remote.tsx +0 -225
  453. package/src/elements/resizable.globals.d.ts +0 -36
  454. package/src/elements/resizable.tsx +0 -704
  455. package/src/elements/response-stream.tsx +0 -40
  456. package/src/elements/screen.tsx +0 -90
  457. package/src/elements/scroll-area.tsx +0 -33
  458. package/src/elements/scroll-button.tsx +0 -179
  459. package/src/elements/search.tsx +0 -185
  460. package/src/elements/segmented.tsx +0 -129
  461. package/src/elements/select.tsx +0 -165
  462. package/src/elements/separator.tsx +0 -36
  463. package/src/elements/setting-item.tsx +0 -50
  464. package/src/elements/settings-group.tsx +0 -37
  465. package/src/elements/skeleton.tsx +0 -45
  466. package/src/elements/slider.tsx +0 -171
  467. package/src/elements/slot-text.ts +0 -73
  468. package/src/elements/slots.ts +0 -787
  469. package/src/elements/source.tsx +0 -126
  470. package/src/elements/status.tsx +0 -42
  471. package/src/elements/styles.css +0 -328
  472. package/src/elements/switch.tsx +0 -118
  473. package/src/elements/tabs.tsx +0 -95
  474. package/src/elements/tasks.tsx +0 -103
  475. package/src/elements/text-shimmer.tsx +0 -28
  476. package/src/elements/thinking-bar.tsx +0 -43
  477. package/src/elements/thread.tsx +0 -139
  478. package/src/elements/toast.tsx +0 -106
  479. package/src/elements/tool.tsx +0 -78
  480. package/src/elements/tooltip.tsx +0 -85
  481. package/src/elements/validate-messages.ts +0 -76
  482. package/src/elements/voice-input.tsx +0 -100
  483. package/src/elements/voice-output.tsx +0 -82
  484. package/src/index.ts +0 -428
  485. package/src/primitives/audio-bands.ts +0 -174
  486. package/src/primitives/card-component-types.ts +0 -63
  487. package/src/primitives/card-contract.ts +0 -80
  488. package/src/primitives/card-data-types.ts +0 -361
  489. package/src/primitives/card-host.tsx +0 -37
  490. package/src/primitives/card-recovery.ts +0 -160
  491. package/src/primitives/card-registry.tsx +0 -78
  492. package/src/primitives/card-resolution.ts +0 -36
  493. package/src/primitives/card-routing.ts +0 -85
  494. package/src/primitives/card-schemas/artifact.schema.json +0 -92
  495. package/src/primitives/card-schemas/card-envelope.schema.json +0 -14
  496. package/src/primitives/card-schemas/card-event.schema.json +0 -12
  497. package/src/primitives/card-schemas/choice.schema.json +0 -75
  498. package/src/primitives/card-schemas/confirm.schema.json +0 -65
  499. package/src/primitives/card-schemas/embed.schema.json +0 -63
  500. package/src/primitives/card-schemas/form.result.schema.json +0 -7
  501. package/src/primitives/card-schemas/form.schema.json +0 -53
  502. package/src/primitives/card-schemas/link.schema.json +0 -54
  503. package/src/primitives/card-schemas/tasks.result.schema.json +0 -16
  504. package/src/primitives/card-schemas/tasks.schema.json +0 -83
  505. package/src/primitives/card-tags.ts +0 -61
  506. package/src/primitives/card-validate-cards.ts +0 -255
  507. package/src/primitives/card-validate-schemas.ts +0 -58
  508. package/src/primitives/card-validate.ts +0 -131
  509. package/src/primitives/chat-config.tsx +0 -103
  510. package/src/primitives/composer-model.ts +0 -49
  511. package/src/primitives/composer-triggers.ts +0 -35
  512. package/src/primitives/controllable.ts +0 -21
  513. package/src/primitives/create-kai-chat.ts +0 -66
  514. package/src/primitives/create-tween.ts +0 -266
  515. package/src/primitives/embed-providers.ts +0 -254
  516. package/src/primitives/field-mask.ts +0 -256
  517. package/src/primitives/field-semantics.ts +0 -115
  518. package/src/primitives/highlighter.ts +0 -157
  519. package/src/primitives/input-mask.ts +0 -853
  520. package/src/primitives/link-preview.ts +0 -87
  521. package/src/primitives/message-feedback.ts +0 -124
  522. package/src/primitives/pdf-preview.ts +0 -121
  523. package/src/primitives/toast-store.ts +0 -338
  524. package/src/primitives/url-scheme-policy.ts +0 -70
  525. package/src/primitives/use-audio-analysis.ts +0 -340
  526. package/src/primitives/use-auto-resize.ts +0 -31
  527. package/src/primitives/use-resize-observer.ts +0 -45
  528. package/src/primitives/use-sequencer.ts +0 -72
  529. package/src/primitives/use-speech-recognition.ts +0 -146
  530. package/src/primitives/use-stick-to-bottom.ts +0 -75
  531. package/src/primitives/use-text-stream.ts +0 -143
  532. package/src/primitives/use-voice-recorder.ts +0 -62
  533. package/src/primitives/visualizer-sequences.ts +0 -350
  534. package/src/remote/host-embed.ts +0 -345
  535. package/src/remote/index.ts +0 -2
  536. package/src/remote/origin.ts +0 -30
  537. package/src/remote/provider-runtime.ts +0 -262
  538. package/src/remote/provider.ts +0 -2
  539. package/src/remote/validate.ts +0 -22
  540. package/src/remote/version.ts +0 -12
  541. package/src/remote/wire.ts +0 -48
  542. package/src/schemas/from-tool-call.ts +0 -151
  543. package/src/schemas/index.ts +0 -341
  544. package/src/schemas/provider-subsets.ts +0 -538
  545. package/src/schemas/registry.ts +0 -355
  546. package/src/schemas/tool-defs.ts +0 -793
  547. package/src/solid.ts +0 -165
  548. package/src/state/index.ts +0 -44
  549. package/src/state/messages.ts +0 -53
  550. package/src/state/mock.ts +0 -297
  551. package/src/state/parts.ts +0 -295
  552. package/src/state/persistence.ts +0 -180
  553. package/src/state/stream.ts +0 -246
  554. package/src/state/suggestions.ts +0 -9
  555. package/src/state/threads.ts +0 -142
  556. package/src/test-utils/fake-clock.ts +0 -88
  557. package/src/types.ts +0 -53
  558. package/src/ui/action-icons.ts +0 -53
  559. package/src/ui/agent-card.tsx +0 -218
  560. package/src/ui/avatar.tsx +0 -23
  561. package/src/ui/badge.tsx +0 -21
  562. package/src/ui/button.tsx +0 -49
  563. package/src/ui/card.tsx +0 -245
  564. package/src/ui/checkbox-group.tsx +0 -153
  565. package/src/ui/checkbox.tsx +0 -63
  566. package/src/ui/collapsible.tsx +0 -163
  567. package/src/ui/command.tsx +0 -103
  568. package/src/ui/dialog.tsx +0 -223
  569. package/src/ui/dock.tsx +0 -584
  570. package/src/ui/dropdown.tsx +0 -636
  571. package/src/ui/editable-label.tsx +0 -112
  572. package/src/ui/hover-card.tsx +0 -345
  573. package/src/ui/icon.tsx +0 -167
  574. package/src/ui/input.tsx +0 -391
  575. package/src/ui/kbd.tsx +0 -118
  576. package/src/ui/nav.tsx +0 -282
  577. package/src/ui/notice.tsx +0 -84
  578. package/src/ui/overlay.tsx +0 -277
  579. package/src/ui/pane-grid.tsx +0 -117
  580. package/src/ui/pane-group.tsx +0 -291
  581. package/src/ui/pane.tsx +0 -193
  582. package/src/ui/popover.tsx +0 -106
  583. package/src/ui/progress-bar.tsx +0 -83
  584. package/src/ui/prompt-dock.tsx +0 -145
  585. package/src/ui/radio.tsx +0 -150
  586. package/src/ui/resizable.tsx +0 -693
  587. package/src/ui/scroll-area.tsx +0 -26
  588. package/src/ui/segmented.tsx +0 -109
  589. package/src/ui/select.tsx +0 -168
  590. package/src/ui/separator.tsx +0 -10
  591. package/src/ui/settings-group.tsx +0 -67
  592. package/src/ui/skeleton.tsx +0 -73
  593. package/src/ui/slider.tsx +0 -178
  594. package/src/ui/status.tsx +0 -48
  595. package/src/ui/switch.tsx +0 -119
  596. package/src/ui/tabs.tsx +0 -168
  597. package/src/ui/textarea.tsx +0 -21
  598. package/src/ui/tooltip.tsx +0 -118
  599. package/src/utils/cn.ts +0 -30
  600. package/src/wire/chunk.ts +0 -248
  601. package/src/wire/consume.ts +0 -599
  602. package/src/wire/diagnostics.ts +0 -727
  603. package/src/wire/encode-probe.ts +0 -214
  604. package/src/wire/encode.ts +0 -947
  605. package/src/wire/files.ts +0 -342
  606. package/src/wire/formats/anthropic.ts +0 -274
  607. package/src/wire/formats/openai.ts +0 -228
  608. package/src/wire/index.ts +0 -95
  609. package/src/wire/media-types.ts +0 -344
  610. package/src/wire/read.ts +0 -396
  611. package/src/wire/sink-helpers.ts +0 -50
  612. 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
- }