@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.
- package/README.md +10 -2
- package/dist/{Icon-BnFKqqtY.js → Icon-DObhsRtm.js} +1 -1
- package/dist/{action-icons-CBskfBMz.js → action-icons-CcgNN2oT.js} +1 -1
- package/dist/agent-tooling/construct/public.d.ts +15 -0
- package/dist/agent-tooling/construct/schema.d.ts +111 -0
- package/dist/arrow-down-CjRU3JgV.js +1 -0
- package/dist/arrow-left-Bnpb1UEW.js +1 -0
- package/dist/{artifact-CvXEm1lS.js → artifact-Bwt7OgcR.js} +1 -1
- package/dist/{attachments-C43ZegEq.js → attachments-Qr4kg-2b.js} +1 -1
- package/dist/{audio-visualizer-B-79y1Tj.js → audio-visualizer-DToYdfSc.js} +1 -1
- package/dist/{badge-BpUbq8c0.js → badge-CdEu_jn2.js} +1 -1
- package/dist/{bell-sn17Ip9v.js → bell-DQAAOpDz.js} +1 -1
- package/dist/{button-BH6rN0U-.js → button-DkuOnGLy.js} +1 -1
- package/dist/{card-renderer-Cb1gQmth.js → card-renderer-Jx70m6_l.js} +1 -1
- package/dist/{card-routing-DifQgS7n.js → card-routing-0YidER_3.js} +1 -1
- package/dist/check-njkVdZu2.js +1 -0
- package/dist/{checkbox-Dc9Yov_B.js → checkbox-D7iRB3nL.js} +1 -1
- package/dist/{checkbox-group-B1kDLLYk.js → checkbox-group-B2_rpoPM.js} +1 -1
- package/dist/chevron-down-B72s6PXR.js +1 -0
- package/dist/chevron-right-BatuHzUm.js +1 -0
- package/dist/{choice-card-BT3c1S8g.js → choice-card-m0BYqqOK.js} +1 -1
- package/dist/circle-check-B62pODB3.js +1 -0
- package/dist/{circle-x-wobWkoSx.js → circle-x-BPG0iPst.js} +1 -1
- package/dist/circle-zx0ByzY9.js +1 -0
- package/dist/code-block-ClMLKb7C.js +1 -0
- package/dist/{collapsible-CPJbf6FM.js → collapsible-B4DlWicL.js} +1 -1
- package/dist/components/chat-thread.d.ts +89 -1
- package/dist/components/conversation-item.d.ts +16 -0
- package/dist/components/conversation-panel.d.ts +29 -0
- package/dist/components/home-panel.d.ts +34 -0
- package/dist/components/widget-tab-bar.d.ts +16 -0
- package/dist/{composer-CjngLzal.js → composer-sQ0HcvyG.js} +3 -3
- package/dist/{confirm-card-DCx_A4bU.js → confirm-card-rQWDQsnC.js} +1 -1
- package/dist/construct-cli.es.js +185 -17
- package/dist/construct.d.ts +5 -0
- package/dist/construct.js +327 -0
- package/dist/{context-BY8V-Nlp.js → context-0kg4ZX1J.js} +1 -1
- package/dist/controllable-CnGpMF6r.js +1 -0
- package/dist/conversation-item-BxwQLaFJ.js +1 -0
- package/dist/conversation-list-BCfGN1JU.js +1 -0
- package/dist/{copy-B6DO4PqB.js → copy-n9u9rqpi.js} +1 -1
- package/dist/core-C8fzo39E.js +1 -12
- package/dist/{create-tween-CnMHviKB.js → create-tween-C-q-JCM-.js} +1 -1
- package/dist/{create-tween-D1aiGZZh.js → create-tween-CRrJXxBd.js} +1 -1
- package/dist/{create-tween-BPzaefTp.js → create-tween-DZ3RRDHD.js} +1 -1
- package/dist/{create-tween-DUSQUKpr.js → create-tween-uPkArjIG.js} +1 -1
- package/dist/custom-elements.json +51 -2
- package/dist/{default-input-CucQEOqH.js → default-input-CaMrJkgH.js} +1 -1
- package/dist/define-By-uL588.js +1 -0
- package/dist/define.js +2 -2
- package/dist/define.server.js +2 -2
- package/dist/{disclosure-CbLtajUQ.js → disclosure-WUImBbDv.js} +1 -1
- package/dist/{dropdown-BwhViJFd.js → dropdown-wZVeJOMd.js} +1 -1
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.js +1 -1
- package/dist/elements/avatar.js +1 -1
- package/dist/elements/badge.js +1 -1
- package/dist/elements/button.js +1 -1
- package/dist/elements/card.js +1 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkbox-group.js +1 -1
- package/dist/elements/checkbox.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-item.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/dock.js +3 -3
- package/dist/elements/dropdown.js +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/embed.js +1 -1
- package/dist/elements/empty.js +1 -1
- package/dist/elements/feedback-bar.js +1 -1
- package/dist/elements/file-tree.js +1 -1
- package/dist/elements/file-upload.js +1 -1
- package/dist/elements/form.js +1 -1
- package/dist/elements/hover-card.js +1 -1
- package/dist/elements/icon.js +1 -1
- package/dist/elements/image.js +1 -1
- package/dist/elements/input.js +1 -1
- package/dist/elements/kbd.js +1 -1
- package/dist/elements/link-preview.js +1 -1
- package/dist/elements/loader.js +1 -1
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/menu.js +1 -1
- package/dist/elements/message-skills.js +1 -1
- package/dist/elements/message.js +1 -1
- package/dist/elements/model-switcher.js +1 -1
- package/dist/elements/nav.js +1 -1
- package/dist/elements/notice.js +1 -1
- package/dist/elements/pane-grid.js +1 -1
- package/dist/elements/pane-group.js +1 -1
- package/dist/elements/pane.js +1 -1
- package/dist/elements/popover.js +1 -1
- package/dist/elements/progress-bar.js +1 -1
- package/dist/elements/prompt-dock.js +1 -1
- package/dist/elements/prompt-input.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/radio-group.js +1 -1
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +1 -1
- package/dist/elements/screen.js +1 -1
- package/dist/elements/scroll-area.js +1 -1
- package/dist/elements/scroll-button.js +1 -1
- package/dist/elements/search.js +1 -1
- package/dist/elements/segmented.js +1 -1
- package/dist/elements/select.js +1 -1
- package/dist/elements/separator.js +1 -1
- package/dist/elements/setting-item.js +1 -1
- package/dist/elements/settings-group.js +1 -1
- package/dist/elements/skeleton.js +1 -1
- package/dist/elements/slider.js +1 -1
- package/dist/elements/source.js +1 -1
- package/dist/elements/status.js +1 -1
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.js +1 -1
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/thread.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.js +1 -1
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +20 -4
- package/dist/{ellipsis-DFr4Vqr3.js → ellipsis-CbOpJnU3.js} +1 -1
- package/dist/{embed-5s_vU4J0.js → embed-CrfjkbUG.js} +1 -1
- package/dist/engine-javascript-C1x7zo1_.js +1 -141
- package/dist/{external-link-u9d85ORQ.js → external-link-ByXAwBBQ.js} +1 -1
- package/dist/{file-text-CX7_x42o.js → file-text-DMib2qwt.js} +1 -1
- package/dist/{file-tree-D3kBjXI5.js → file-tree-CJqM2aP7.js} +1 -1
- package/dist/{folder-V-YdIL8f.js → folder-_BAb929C.js} +1 -1
- package/dist/{form-BRNUMCN2.js → form-DSZgZQZk.js} +1 -1
- package/dist/{hover-card-DaeiYrTA.js → hover-card-BQbul9eq.js} +1 -1
- package/dist/{icon-BpixdZNq.js → icon-CdcfRhYI.js} +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2842 -2703
- package/dist/index.server.js +2820 -2681
- package/dist/{info-8mFsIjS3.js → info-DsFjGuI_.js} +1 -1
- package/dist/{input-d1Yuu-hz.js → input-DKbmkMos.js} +1 -1
- package/dist/kai.es.js +1 -1
- package/dist/{kbd-961zMPFB.js → kbd-PvdGhX4s.js} +1 -1
- package/dist/{link-preview-DIIbV2Sd.js → link-preview-DbM1866N.js} +1 -1
- package/dist/{link-DlZZBGaR.js → link-zF8PzWfF.js} +1 -1
- package/dist/{loader-YQszHFaW.js → loader-BM4CEz4G.js} +1 -1
- package/dist/{markdown-CVk1MPsj.js → markdown-LMvmAyBk.js} +7 -7
- package/dist/mcp.es.js +79 -5
- package/dist/{message-CwKVEdZg.js → message-BSsgvHvf.js} +1 -1
- package/dist/{message-DS8lzHc1.js → message-BYZECyek.js} +1 -1
- package/dist/message-circle-DruwObfX.js +1 -0
- package/dist/message-square-BG6q6mlm.js +1 -0
- package/dist/{minimize-2-UEZmP2QT.js → minimize-2-BQRoBV1V.js} +1 -1
- package/dist/{model-switcher-Kk1jEQ5t.js → model-switcher-CMpCOYcB.js} +1 -1
- package/dist/{overlay-C1od6LOz.js → overlay-Cb-MkyDa.js} +1 -1
- package/dist/{panel-right-ryET0yyn.js → panel-right-i8warFxT.js} +1 -1
- package/dist/{paperclip-BDNoZFJP.js → paperclip-ByY3UVO6.js} +1 -1
- package/dist/play-BvKKq3fX.js +1 -0
- package/dist/primitives/conversation-store.d.ts +54 -0
- package/dist/{progress-bar-BvYX0aoy.js → progress-bar-jVAHvMUf.js} +1 -1
- package/dist/{prompt-suggestion-BRFu2lcu.js → prompt-suggestion-CD-qtNgC.js} +1 -1
- package/dist/{radio-DcL23cNJ.js → radio-BsoBMhkF.js} +1 -1
- package/dist/react/index.d.ts +393 -0
- package/dist/react.js +20 -20
- package/dist/{reasoning-Cackq4Rh.js → reasoning-4kBPtlTx.js} +1 -1
- package/dist/register-impl-B1hLkrI8.js +424 -0
- package/dist/{resizable-Cp9HUYih.js → resizable-uPmY9VY-.js} +1 -1
- package/dist/{rotate-ccw-0DLRDHH5.js → rotate-ccw-B2xn93py.js} +1 -1
- package/dist/{rotate-cw-DOB9IGcU.js → rotate-cw-CMVLoCyH.js} +1 -1
- package/dist/{scroll-area-emVmWP4K.js → scroll-area-BqLUo8ZZ.js} +1 -1
- package/dist/{scroll-button-CeP_gv1x.js → scroll-button-DslXiJyL.js} +1 -1
- package/dist/{select-CWmu00tS.js → select-2cLRtPGT.js} +1 -1
- package/dist/{separator-CE6vly6z.js → separator-BWQtNJcM.js} +1 -1
- package/dist/{settings-CE-78SvZ.js → settings-BBR63jLg.js} +1 -1
- package/dist/{settings-group-ZFWNxFQR.js → settings-group-B2ciy-Iw.js} +1 -1
- package/dist/{skeleton-BebdP4mf.js → skeleton-CkHI-HgC.js} +1 -1
- package/dist/{slider-BAKRYUlz.js → slider-DDR9U-i0.js} +1 -1
- package/dist/{solid-BE5ui6sy.js → solid-BzCFdL25.js} +7720 -7058
- package/dist/{solid-DFhsce3O.js → solid-DruVOXnf.js} +5864 -5283
- package/dist/solid.d.ts +6 -2
- package/dist/solid.js +217 -210
- package/dist/solid.server.js +217 -210
- package/dist/{source-B0FlguSx.js → source-GEnFF528.js} +1 -1
- package/dist/{star-CBMZvLuN.js → star-CilHq8GP.js} +1 -1
- package/dist/store-Ciyk6eZG.js +1 -0
- package/dist/{switch-CViARZCy.js → switch-KkeQIbuH.js} +1 -1
- package/dist/{tasks-card-MjCcOhR_.js → tasks-card-BrN4bggJ.js} +1 -1
- package/dist/{text-shimmer-DKlwRF2G.js → text-shimmer-Dcj1MHuq.js} +1 -1
- package/dist/{textarea-3ay2Me2-.js → textarea-Dpvbngbg.js} +1 -1
- package/dist/theme.tokens.css +28 -0
- package/dist/{thumbs-up-DQcu2HMO.js → thumbs-up-CsT9Wzb9.js} +1 -1
- package/dist/{toast-store-C6yKOhbk.js → toast-store-tgK6MuMv.js} +1 -1
- package/dist/{tool-BCYNlfKy.js → tool-DzUjIBT8.js} +1 -1
- package/dist/{tooltip-05zI4hjs.js → tooltip-Bkkg_4t0.js} +1 -1
- package/dist/{trash-2-BR1RxGky.js → trash-2-jBKxjGGQ.js} +1 -1
- package/dist/{triangle-alert-DHohDOCy.js → triangle-alert-DlQ68ppf.js} +1 -1
- package/dist/types.d.ts +48 -3
- package/dist/ui/dock.d.ts +29 -0
- package/dist/{upload-BbGW6A_u.js → upload-TcAHXv9W.js} +1 -1
- package/dist/{url-scheme-policy-DHpoTJwB.js → url-scheme-policy-DR5DgvkM.js} +1 -1
- package/dist/{use-card-resolution-CJW4grFa.js → use-card-resolution-DaZqAdWf.js} +1 -1
- package/dist/{variant-aurora-xiMPty8f.js → variant-aurora-BhLuYxJR.js} +1 -1
- package/dist/{variant-aurora-BUOVgPOB.js → variant-aurora-CNmfHhcx.js} +2 -2
- package/dist/{variant-aurora-CUsUJvFI.js → variant-aurora-D6GsdTPA.js} +2 -2
- package/dist/{variant-aurora-CW2i4bfr.js → variant-aurora-DbW_OWL2.js} +1 -1
- package/dist/{variant-custom-D91HjAKz.js → variant-custom-BRvaNHSi.js} +1 -1
- package/dist/{variant-custom-BQtOnmeO.js → variant-custom-BvYHR62b.js} +1 -1
- package/dist/{variant-custom-CAnzzppW.js → variant-custom-DMTrEO5w.js} +2 -2
- package/dist/{variant-custom-CAm9L-E5.js → variant-custom-DV2x254s.js} +2 -2
- package/dist/{variant-wave-Ca1Ls4_y.js → variant-wave-BNMGrDfj.js} +2 -2
- package/dist/{variant-wave-uY5crU_K.js → variant-wave-BpDd8t3Z.js} +1 -1
- package/dist/{variant-wave-CxX39DKd.js → variant-wave-CyBueXj9.js} +2 -2
- package/dist/{variant-wave-DYyzIIZv.js → variant-wave-sp-qwsdI.js} +1 -1
- package/dist/x-Z5UntkIJ.js +1 -0
- package/frameworks/react/index.tsx +14 -4
- package/llms-full.txt +7 -2
- package/package.json +16 -16
- package/src/elements/element-meta.json +49 -6
- package/theme.css +28 -0
- package/bin/route.test.js +0 -58
- package/dist/arrow-down-Br0qtpsz.js +0 -1
- package/dist/arrow-left-Dj7xgE12.js +0 -1
- package/dist/check-DPeeMUYx.js +0 -1
- package/dist/chevron-down-CHSWk7ZT.js +0 -1
- package/dist/chevron-right-B7l_fMgx.js +0 -1
- package/dist/circle-Bb0yOpqM.js +0 -1
- package/dist/circle-check-BGZpCiz7.js +0 -1
- package/dist/code-block-CUQ5mtv0.js +0 -1
- package/dist/controllable-D43BKmtX.js +0 -1
- package/dist/conversation-list-D4lLU_yS.js +0 -1
- package/dist/define-CGGX-7Vg.js +0 -1
- package/dist/message-circle-Xf8Q-vZp.js +0 -1
- package/dist/message-square-Cxnwgkns.js +0 -1
- package/dist/play-OFIvYXUQ.js +0 -1
- package/dist/register-impl-C7rENvPK.js +0 -424
- package/dist/store-7eoadydQ.js +0 -1
- package/dist/x-C5m1JFgi.js +0 -1
- package/src/agent-tooling/archetypes.ts +0 -205
- package/src/agent-tooling/catalog/README.md +0 -676
- package/src/agent-tooling/catalog/catalog-types.ts +0 -231
- package/src/agent-tooling/catalog/fabrications.ts +0 -96
- package/src/agent-tooling/catalog/invariants.ts +0 -284
- package/src/agent-tooling/catalog/labs-titles.ts +0 -114
- package/src/agent-tooling/catalog/scenarios.ts +0 -87
- package/src/agent-tooling/catalog/surfaces.ts +0 -347
- package/src/agent-tooling/construct/cli-entry.ts +0 -8
- package/src/agent-tooling/construct/cli.ts +0 -131
- package/src/agent-tooling/construct/codegen.ts +0 -1353
- package/src/agent-tooling/construct/construct.v1.schema.json +0 -250
- package/src/agent-tooling/construct/dev.ts +0 -148
- package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +0 -7
- package/src/agent-tooling/construct/fixtures/ops-console.construct.json +0 -44
- package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +0 -23
- package/src/agent-tooling/construct/schema.ts +0 -371
- package/src/agent-tooling/integrations/anthropic.ts +0 -442
- package/src/agent-tooling/integrations/cloudflare.ts +0 -218
- package/src/agent-tooling/integrations/langgraph.ts +0 -133
- package/src/agent-tooling/integrations/mastra.ts +0 -182
- package/src/agent-tooling/integrations/mock.ts +0 -148
- package/src/agent-tooling/integrations/ollama.ts +0 -97
- package/src/agent-tooling/integrations/openai.ts +0 -95
- package/src/agent-tooling/integrations/openrouter.ts +0 -82
- package/src/agent-tooling/integrations/pi.ts +0 -176
- package/src/agent-tooling/integrations/pydantic-ai.ts +0 -108
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +0 -398
- package/src/agent-tooling/mcp/css-raw.d.ts +0 -12
- package/src/agent-tooling/mcp/manifest.ts +0 -422
- package/src/agent-tooling/mcp/server.ts +0 -116
- package/src/agent-tooling/mcp/stdio.ts +0 -18
- package/src/agent-tooling/mcp/tools/construct.ts +0 -130
- package/src/agent-tooling/mcp/tools/debug.ts +0 -621
- package/src/agent-tooling/mcp/tools/reference.ts +0 -980
- package/src/agent-tooling/mcp/tools/scaffold.ts +0 -6708
- package/src/agent-tooling/mcp/tools/theme.ts +0 -456
- package/src/agent-tooling/mcp/tools/types.ts +0 -14
- package/src/agent-tooling/mcp/validate-args.ts +0 -141
- package/src/agent-tooling/recipes/composed-thread.ts +0 -714
- package/src/agent-tooling/recipes/index.ts +0 -22
- package/src/agent-tooling/recipes/types.ts +0 -32
- package/src/agent-tooling/registry.ts +0 -132
- package/src/agent-tooling/route-emit.ts +0 -305
- package/src/agent-tooling/types.ts +0 -390
- package/src/components/artifact-card.tsx +0 -120
- package/src/components/artifact.tsx +0 -914
- package/src/components/attachment-types.ts +0 -66
- package/src/components/attachments.tsx +0 -536
- package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +0 -257
- package/src/components/audio-visualizer/aurora.glsl.ts +0 -330
- package/src/components/audio-visualizer/fit-scale.ts +0 -112
- package/src/components/audio-visualizer/index.tsx +0 -528
- package/src/components/audio-visualizer/labs/lab-choreography.ts +0 -187
- package/src/components/audio-visualizer/labs/lab-shaders.ts +0 -363
- package/src/components/audio-visualizer/labs/lab-visualizer.tsx +0 -102
- package/src/components/audio-visualizer/shader-canvas.tsx +0 -876
- package/src/components/audio-visualizer/sizes.ts +0 -54
- package/src/components/audio-visualizer/variant-aurora.tsx +0 -305
- package/src/components/audio-visualizer/variant-bar.tsx +0 -175
- package/src/components/audio-visualizer/variant-custom.tsx +0 -344
- package/src/components/audio-visualizer/variant-grid.tsx +0 -175
- package/src/components/audio-visualizer/variant-radial.tsx +0 -163
- package/src/components/audio-visualizer/variant-wave.tsx +0 -130
- package/src/components/audio-visualizer/wave.glsl.ts +0 -130
- package/src/components/card-fallback.tsx +0 -60
- package/src/components/card-renderer.tsx +0 -220
- package/src/components/card.tsx +0 -212
- package/src/components/chain-of-thought.tsx +0 -348
- package/src/components/chat-container.tsx +0 -89
- package/src/components/chat-scope-picker.tsx +0 -57
- package/src/components/chat-thread.tsx +0 -518
- package/src/components/checkpoint.tsx +0 -88
- package/src/components/choice-card.tsx +0 -743
- package/src/components/coachmark.tsx +0 -249
- package/src/components/code-block.tsx +0 -181
- package/src/components/composer-dom.ts +0 -159
- package/src/components/composer-highlight.ts +0 -242
- package/src/components/composer-history.ts +0 -82
- package/src/components/composer.tsx +0 -1075
- package/src/components/confirm-card.tsx +0 -402
- package/src/components/context.tsx +0 -377
- package/src/components/conversation-item.tsx +0 -218
- package/src/components/conversation-list.tsx +0 -429
- package/src/components/dismissed-stub.tsx +0 -80
- package/src/components/embed.tsx +0 -200
- package/src/components/empty.tsx +0 -177
- package/src/components/feedback-bar.tsx +0 -166
- package/src/components/file-tree.tsx +0 -530
- package/src/components/file-upload.tsx +0 -167
- package/src/components/form-widgets.tsx +0 -525
- package/src/components/form.tsx +0 -1296
- package/src/components/image.tsx +0 -65
- package/src/components/link-preview.tsx +0 -195
- package/src/components/loader.tsx +0 -367
- package/src/components/markdown.tsx +0 -162
- package/src/components/message-skills.tsx +0 -40
- package/src/components/message.tsx +0 -765
- package/src/components/model-switcher.tsx +0 -132
- package/src/components/prompt-input.tsx +0 -188
- package/src/components/prompt-suggestion.tsx +0 -150
- package/src/components/reasoning.tsx +0 -293
- package/src/components/response-compare-types.ts +0 -152
- package/src/components/response-compare.tsx +0 -455
- package/src/components/response-stream.tsx +0 -109
- package/src/components/screen.tsx +0 -181
- package/src/components/scroll-button.tsx +0 -93
- package/src/components/source.tsx +0 -192
- package/src/components/tasks-card.tsx +0 -801
- package/src/components/text-shimmer.tsx +0 -37
- package/src/components/thinking-bar.tsx +0 -50
- package/src/components/thread.tsx +0 -228
- package/src/components/toast.tsx +0 -503
- package/src/components/tool-classify.ts +0 -26
- package/src/components/tool-types.ts +0 -49
- package/src/components/tool.tsx +0 -190
- package/src/components/use-card-resolution.ts +0 -44
- package/src/components/voice-input.tsx +0 -222
- package/src/components/voice-output.tsx +0 -242
- package/src/components/workspace-shell.tsx +0 -285
- package/src/diagnostics/hook.ts +0 -352
- package/src/diagnostics/index.ts +0 -82
- package/src/diagnostics/report-request.ts +0 -296
- package/src/elements/agent-card.tsx +0 -67
- package/src/elements/artifact.tsx +0 -194
- package/src/elements/attachments.tsx +0 -140
- package/src/elements/audio-visualizer.tsx +0 -227
- package/src/elements/autoloader.ts +0 -89
- package/src/elements/avatar.tsx +0 -33
- package/src/elements/badge.tsx +0 -32
- package/src/elements/button.tsx +0 -149
- package/src/elements/card.tsx +0 -133
- package/src/elements/cards.tsx +0 -266
- package/src/elements/chain-of-thought.tsx +0 -141
- package/src/elements/chat-scope-picker.tsx +0 -65
- package/src/elements/chat-types.ts +0 -102
- package/src/elements/chat-workspace.tsx +0 -188
- package/src/elements/chat.tsx +0 -212
- package/src/elements/checkbox-group.tsx +0 -164
- package/src/elements/checkbox.tsx +0 -134
- package/src/elements/checkpoint.tsx +0 -47
- package/src/elements/choice.tsx +0 -100
- package/src/elements/coachmark.tsx +0 -90
- package/src/elements/code-block.tsx +0 -60
- package/src/elements/command.tsx +0 -197
- package/src/elements/compare.tsx +0 -111
- package/src/elements/compiled.css +0 -2
- package/src/elements/composer.tsx +0 -162
- package/src/elements/confirm-card.tsx +0 -76
- package/src/elements/context-meter.tsx +0 -107
- package/src/elements/conversation-item.tsx +0 -120
- package/src/elements/conversation-list.tsx +0 -203
- package/src/elements/css.ts +0 -5
- package/src/elements/default-input.tsx +0 -392
- package/src/elements/define-entry.ts +0 -12
- package/src/elements/define.tsx +0 -563
- package/src/elements/diagnostic-events.ts +0 -114
- package/src/elements/dialog.tsx +0 -145
- package/src/elements/disclosure.ts +0 -95
- package/src/elements/dock.tsx +0 -215
- package/src/elements/dropdown.tsx +0 -123
- package/src/elements/editable-label.tsx +0 -140
- package/src/elements/element-data-types.ts +0 -152
- package/src/elements/element-diagnostics.ts +0 -392
- package/src/elements/element-manifest.json +0 -354
- package/src/elements/element-nonscalar.json +0 -185
- package/src/elements/element-types.d.ts +0 -4337
- package/src/elements/embed.tsx +0 -35
- package/src/elements/empty.tsx +0 -29
- package/src/elements/feedback-bar.tsx +0 -66
- package/src/elements/file-tree.tsx +0 -62
- package/src/elements/file-upload.tsx +0 -44
- package/src/elements/form.tsx +0 -94
- package/src/elements/hover-card.tsx +0 -80
- package/src/elements/icon.tsx +0 -41
- package/src/elements/image.tsx +0 -32
- package/src/elements/input.tsx +0 -492
- package/src/elements/kbd.tsx +0 -45
- package/src/elements/link-preview.tsx +0 -34
- package/src/elements/loader.tsx +0 -25
- package/src/elements/markdown.tsx +0 -38
- package/src/elements/menu.tsx +0 -241
- package/src/elements/message-skills.tsx +0 -83
- package/src/elements/message.tsx +0 -361
- package/src/elements/model-switcher.tsx +0 -121
- package/src/elements/nav.tsx +0 -87
- package/src/elements/notice.tsx +0 -53
- package/src/elements/pane-grid.tsx +0 -111
- package/src/elements/pane-group.tsx +0 -119
- package/src/elements/pane.tsx +0 -129
- package/src/elements/popover.tsx +0 -81
- package/src/elements/progress-bar.tsx +0 -39
- package/src/elements/prompt-dock.tsx +0 -82
- package/src/elements/prompt-input.tsx +0 -240
- package/src/elements/prompt-suggestions.tsx +0 -113
- package/src/elements/radio-group.tsx +0 -129
- package/src/elements/reasoning.tsx +0 -71
- package/src/elements/register-impl.ts +0 -148
- package/src/elements/register.ts +0 -76
- package/src/elements/remote.tsx +0 -225
- package/src/elements/resizable.globals.d.ts +0 -36
- package/src/elements/resizable.tsx +0 -704
- package/src/elements/response-stream.tsx +0 -40
- package/src/elements/screen.tsx +0 -90
- package/src/elements/scroll-area.tsx +0 -33
- package/src/elements/scroll-button.tsx +0 -179
- package/src/elements/search.tsx +0 -185
- package/src/elements/segmented.tsx +0 -129
- package/src/elements/select.tsx +0 -165
- package/src/elements/separator.tsx +0 -36
- package/src/elements/setting-item.tsx +0 -50
- package/src/elements/settings-group.tsx +0 -37
- package/src/elements/skeleton.tsx +0 -45
- package/src/elements/slider.tsx +0 -171
- package/src/elements/slot-text.ts +0 -73
- package/src/elements/slots.ts +0 -787
- package/src/elements/source.tsx +0 -126
- package/src/elements/status.tsx +0 -42
- package/src/elements/styles.css +0 -328
- package/src/elements/switch.tsx +0 -118
- package/src/elements/tabs.tsx +0 -95
- package/src/elements/tasks.tsx +0 -103
- package/src/elements/text-shimmer.tsx +0 -28
- package/src/elements/thinking-bar.tsx +0 -43
- package/src/elements/thread.tsx +0 -139
- package/src/elements/toast.tsx +0 -106
- package/src/elements/tool.tsx +0 -78
- package/src/elements/tooltip.tsx +0 -85
- package/src/elements/validate-messages.ts +0 -76
- package/src/elements/voice-input.tsx +0 -100
- package/src/elements/voice-output.tsx +0 -82
- package/src/index.ts +0 -428
- package/src/primitives/audio-bands.ts +0 -174
- package/src/primitives/card-component-types.ts +0 -63
- package/src/primitives/card-contract.ts +0 -80
- package/src/primitives/card-data-types.ts +0 -361
- package/src/primitives/card-host.tsx +0 -37
- package/src/primitives/card-recovery.ts +0 -160
- package/src/primitives/card-registry.tsx +0 -78
- package/src/primitives/card-resolution.ts +0 -36
- package/src/primitives/card-routing.ts +0 -85
- package/src/primitives/card-schemas/artifact.schema.json +0 -92
- package/src/primitives/card-schemas/card-envelope.schema.json +0 -14
- package/src/primitives/card-schemas/card-event.schema.json +0 -12
- package/src/primitives/card-schemas/choice.schema.json +0 -75
- package/src/primitives/card-schemas/confirm.schema.json +0 -65
- package/src/primitives/card-schemas/embed.schema.json +0 -63
- package/src/primitives/card-schemas/form.result.schema.json +0 -7
- package/src/primitives/card-schemas/form.schema.json +0 -53
- package/src/primitives/card-schemas/link.schema.json +0 -54
- package/src/primitives/card-schemas/tasks.result.schema.json +0 -16
- package/src/primitives/card-schemas/tasks.schema.json +0 -83
- package/src/primitives/card-tags.ts +0 -61
- package/src/primitives/card-validate-cards.ts +0 -255
- package/src/primitives/card-validate-schemas.ts +0 -58
- package/src/primitives/card-validate.ts +0 -131
- package/src/primitives/chat-config.tsx +0 -103
- package/src/primitives/composer-model.ts +0 -49
- package/src/primitives/composer-triggers.ts +0 -35
- package/src/primitives/controllable.ts +0 -21
- package/src/primitives/create-kai-chat.ts +0 -66
- package/src/primitives/create-tween.ts +0 -266
- package/src/primitives/embed-providers.ts +0 -254
- package/src/primitives/field-mask.ts +0 -256
- package/src/primitives/field-semantics.ts +0 -115
- package/src/primitives/highlighter.ts +0 -157
- package/src/primitives/input-mask.ts +0 -853
- package/src/primitives/link-preview.ts +0 -87
- package/src/primitives/message-feedback.ts +0 -124
- package/src/primitives/pdf-preview.ts +0 -121
- package/src/primitives/toast-store.ts +0 -338
- package/src/primitives/url-scheme-policy.ts +0 -70
- package/src/primitives/use-audio-analysis.ts +0 -340
- package/src/primitives/use-auto-resize.ts +0 -31
- package/src/primitives/use-resize-observer.ts +0 -45
- package/src/primitives/use-sequencer.ts +0 -72
- package/src/primitives/use-speech-recognition.ts +0 -146
- package/src/primitives/use-stick-to-bottom.ts +0 -75
- package/src/primitives/use-text-stream.ts +0 -143
- package/src/primitives/use-voice-recorder.ts +0 -62
- package/src/primitives/visualizer-sequences.ts +0 -350
- package/src/remote/host-embed.ts +0 -345
- package/src/remote/index.ts +0 -2
- package/src/remote/origin.ts +0 -30
- package/src/remote/provider-runtime.ts +0 -262
- package/src/remote/provider.ts +0 -2
- package/src/remote/validate.ts +0 -22
- package/src/remote/version.ts +0 -12
- package/src/remote/wire.ts +0 -48
- package/src/schemas/from-tool-call.ts +0 -151
- package/src/schemas/index.ts +0 -341
- package/src/schemas/provider-subsets.ts +0 -538
- package/src/schemas/registry.ts +0 -355
- package/src/schemas/tool-defs.ts +0 -793
- package/src/solid.ts +0 -165
- package/src/state/index.ts +0 -44
- package/src/state/messages.ts +0 -53
- package/src/state/mock.ts +0 -297
- package/src/state/parts.ts +0 -295
- package/src/state/persistence.ts +0 -180
- package/src/state/stream.ts +0 -246
- package/src/state/suggestions.ts +0 -9
- package/src/state/threads.ts +0 -142
- package/src/test-utils/fake-clock.ts +0 -88
- package/src/types.ts +0 -53
- package/src/ui/action-icons.ts +0 -53
- package/src/ui/agent-card.tsx +0 -218
- package/src/ui/avatar.tsx +0 -23
- package/src/ui/badge.tsx +0 -21
- package/src/ui/button.tsx +0 -49
- package/src/ui/card.tsx +0 -245
- package/src/ui/checkbox-group.tsx +0 -153
- package/src/ui/checkbox.tsx +0 -63
- package/src/ui/collapsible.tsx +0 -163
- package/src/ui/command.tsx +0 -103
- package/src/ui/dialog.tsx +0 -223
- package/src/ui/dock.tsx +0 -584
- package/src/ui/dropdown.tsx +0 -636
- package/src/ui/editable-label.tsx +0 -112
- package/src/ui/hover-card.tsx +0 -345
- package/src/ui/icon.tsx +0 -167
- package/src/ui/input.tsx +0 -391
- package/src/ui/kbd.tsx +0 -118
- package/src/ui/nav.tsx +0 -282
- package/src/ui/notice.tsx +0 -84
- package/src/ui/overlay.tsx +0 -277
- package/src/ui/pane-grid.tsx +0 -117
- package/src/ui/pane-group.tsx +0 -291
- package/src/ui/pane.tsx +0 -193
- package/src/ui/popover.tsx +0 -106
- package/src/ui/progress-bar.tsx +0 -83
- package/src/ui/prompt-dock.tsx +0 -145
- package/src/ui/radio.tsx +0 -150
- package/src/ui/resizable.tsx +0 -693
- package/src/ui/scroll-area.tsx +0 -26
- package/src/ui/segmented.tsx +0 -109
- package/src/ui/select.tsx +0 -168
- package/src/ui/separator.tsx +0 -10
- package/src/ui/settings-group.tsx +0 -67
- package/src/ui/skeleton.tsx +0 -73
- package/src/ui/slider.tsx +0 -178
- package/src/ui/status.tsx +0 -48
- package/src/ui/switch.tsx +0 -119
- package/src/ui/tabs.tsx +0 -168
- package/src/ui/textarea.tsx +0 -21
- package/src/ui/tooltip.tsx +0 -118
- package/src/utils/cn.ts +0 -30
- package/src/wire/chunk.ts +0 -248
- package/src/wire/consume.ts +0 -599
- package/src/wire/diagnostics.ts +0 -727
- package/src/wire/encode-probe.ts +0 -214
- package/src/wire/encode.ts +0 -947
- package/src/wire/files.ts +0 -342
- package/src/wire/formats/anthropic.ts +0 -274
- package/src/wire/formats/openai.ts +0 -228
- package/src/wire/index.ts +0 -95
- package/src/wire/media-types.ts +0 -344
- package/src/wire/read.ts +0 -396
- package/src/wire/sink-helpers.ts +0 -50
- 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
|
-
}
|