@kitn.ai/ui 0.25.2 → 0.27.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 +17 -6
- package/bin/mcp.js +16 -3
- package/bin/route.js +23 -0
- package/bin/route.test.js +58 -0
- package/dist/{elements/chunks/Icon-b5A8hlHs.js → Icon-BnFKqqtY.js} +1 -1
- package/dist/action-icons-CBskfBMz.js +1 -0
- package/dist/arrow-down-Br0qtpsz.js +1 -0
- package/dist/arrow-left-Dj7xgE12.js +1 -0
- package/dist/artifact-CvXEm1lS.js +1 -0
- package/dist/assets/dev-5XzXYR21.js +91 -0
- package/dist/attachments-C43ZegEq.js +1 -0
- package/dist/audio-visualizer-B-79y1Tj.js +1 -0
- package/dist/badge-BpUbq8c0.js +1 -0
- package/dist/bash-InADTalH.js +1 -6
- package/dist/bell-sn17Ip9v.js +1 -0
- package/dist/{elements/chunks/button-B9okqG73.js → button-BH6rN0U-.js} +1 -1
- package/dist/card-renderer-Cb1gQmth.js +1 -0
- package/dist/card-routing-DifQgS7n.js +1 -0
- package/dist/check-DPeeMUYx.js +1 -0
- package/dist/checkbox-Dc9Yov_B.js +1 -0
- package/dist/checkbox-group-B1kDLLYk.js +1 -0
- package/dist/chevron-down-CHSWk7ZT.js +1 -0
- package/dist/chevron-right-B7l_fMgx.js +1 -0
- package/dist/choice-card-BT3c1S8g.js +1 -0
- package/dist/circle-Bb0yOpqM.js +1 -0
- package/dist/circle-check-BGZpCiz7.js +1 -0
- package/dist/circle-x-wobWkoSx.js +1 -0
- package/dist/{elements/chunks/cn-CU7UAWzk.js → cn-DN5AWfiS.js} +1 -1
- package/dist/code-block-CUQ5mtv0.js +1 -0
- package/dist/collapsible-CPJbf6FM.js +1 -0
- package/dist/components/attachment-types.d.ts +53 -1
- package/dist/components/attachments.d.ts +31 -0
- package/dist/components/audio-visualizer/fit-scale.d.ts +50 -0
- package/dist/components/audio-visualizer/index.d.ts +15 -1
- package/dist/components/audio-visualizer/variant-bar.d.ts +24 -1
- package/dist/components/card-renderer.d.ts +13 -0
- package/dist/components/card.d.ts +4 -0
- package/dist/components/chat-thread.d.ts +49 -42
- package/dist/components/code-block.d.ts +22 -0
- package/dist/components/conversation-item.d.ts +66 -1
- package/dist/components/conversation-list.d.ts +77 -0
- package/dist/components/form-widgets.d.ts +67 -2
- package/dist/components/form.d.ts +89 -0
- package/dist/components/message.d.ts +29 -0
- package/dist/components/reasoning.d.ts +6 -0
- package/dist/components/scroll-button.d.ts +11 -0
- package/dist/components/thread.d.ts +5 -0
- package/dist/components/toast.d.ts +2 -1
- package/dist/components/tool-types.d.ts +9 -0
- package/dist/components/voice-input.d.ts +8 -0
- package/dist/components/voice-output.d.ts +12 -1
- package/dist/components/workspace-shell.d.ts +87 -0
- package/dist/composer-CjngLzal.js +106 -0
- package/dist/confirm-card-DCx_A4bU.js +1 -0
- package/dist/construct-cli.es.js +1169 -0
- package/dist/context-BY8V-Nlp.js +1 -0
- package/dist/controllable-D43BKmtX.js +1 -0
- package/dist/conversation-list-D4lLU_yS.js +1 -0
- package/dist/copy-B6DO4PqB.js +1 -0
- package/dist/core-AYMC6_lb.js +12 -5874
- package/dist/{create-tween-CUzOlaMS.js → create-tween-BPzaefTp.js} +1 -1
- package/dist/{create-tween-B5Y4Im8G.js → create-tween-CnMHviKB.js} +1 -1
- package/dist/{create-tween-4z1XYLkR.js → create-tween-D1aiGZZh.js} +1 -1
- package/dist/{elements/chunks/create-tween-CUrM6D_o.js → create-tween-DUSQUKpr.js} +2 -2
- package/dist/css-M7EaDHN_.js +1 -6
- package/dist/custom-elements.json +3468 -1927
- package/dist/default-input-CucQEOqH.js +1 -0
- package/dist/define-CGGX-7Vg.js +1 -0
- package/dist/define.d.ts +4 -0
- package/dist/define.js +510 -0
- package/dist/define.server.d.ts +5 -0
- package/dist/define.server.js +506 -0
- package/dist/diagnostics/hook.d.ts +81 -0
- package/dist/diagnostics/index.d.ts +7 -0
- package/dist/diagnostics/report-request.d.ts +61 -0
- package/dist/diagnostics.d.ts +5 -0
- package/dist/diagnostics.js +209 -0
- package/dist/disclosure-CbLtajUQ.js +1 -0
- package/dist/dropdown-BwhViJFd.js +1 -0
- 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/autoloader.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.d.ts +1 -0
- package/dist/elements/checkbox-group.js +1 -0
- package/dist/elements/checkbox.d.ts +1 -0
- package/dist/elements/checkbox.js +1 -0
- 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.d.ts +1 -0
- package/dist/elements/conversation-item.js +1 -0
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/default-input.d.ts +3 -3
- package/dist/elements/define-entry.d.ts +12 -0
- package/dist/elements/define.d.ts +27 -0
- package/dist/elements/diagnostic-events.d.ts +74 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/dock.d.ts +1 -0
- package/dist/elements/dock.js +243 -0
- package/dist/elements/dropdown.d.ts +1 -0
- package/dist/elements/dropdown.js +1 -0
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-data-types.d.ts +37 -0
- package/dist/elements/element-diagnostics.d.ts +81 -0
- 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.d.ts +1 -0
- package/dist/elements/pane-grid.js +1 -0
- 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.d.ts +1 -0
- package/dist/elements/radio-group.js +1 -0
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/register.d.ts +2 -0
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +3 -3
- 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.d.ts +1 -0
- package/dist/elements/select.js +1 -0
- 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.d.ts +1 -0
- package/dist/elements/slider.js +1 -0
- package/dist/elements/slots.d.ts +36 -4
- 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 +589 -167
- package/dist/{elements/chunks/ellipsis-CVxqfsDZ.js → ellipsis-DFr4Vqr3.js} +1 -1
- package/dist/embed-5s_vU4J0.js +1 -0
- package/dist/engine-javascript-vq0WuIJl.js +14 -2516
- package/dist/{elements/chunks/external-link-DG2sRdQL.js → external-link-u9d85ORQ.js} +1 -1
- package/dist/file-text-CX7_x42o.js +1 -0
- package/dist/{elements/chunks/file-tree-yGWNK0Js.js → file-tree-D3kBjXI5.js} +1 -1
- package/dist/folder-V-YdIL8f.js +1 -0
- package/dist/form-BRNUMCN2.js +1 -0
- package/dist/github-dark-dimmed-DUshB20C.js +1 -4
- package/dist/github-light-JYsPkUQd.js +1 -4
- package/dist/hover-card-DaeiYrTA.js +1 -0
- package/dist/html-CPZ3oZQ7.js +1 -10
- package/dist/icon-BpixdZNq.js +1 -0
- package/dist/{elements/chunks/index-BCRhi84a.js → index-xu_pdcPi.js} +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +8473 -6470
- package/dist/index.server.js +6495 -4958
- package/dist/info-8mFsIjS3.js +1 -0
- package/dist/input-d1Yuu-hz.js +1 -0
- package/dist/javascript-C25yR2R2.js +1 -6
- package/dist/json-DxJze_jm.js +1 -6
- package/dist/kai-provider.es.js +40 -40
- package/dist/kai.es.js +1 -1
- package/dist/kbd-961zMPFB.js +1 -0
- package/dist/link-DlZZBGaR.js +1 -0
- package/dist/{elements/chunks/link-preview-Cf5KOP-i.js → link-preview-DIIbV2Sd.js} +1 -1
- package/dist/loader-YQszHFaW.js +1 -0
- package/dist/{elements/chunks/markdown-DbozyYqa.js → markdown-CVk1MPsj.js} +1 -1
- package/dist/mcp.es.js +3873 -544
- package/dist/message-CwKVEdZg.js +1 -0
- package/dist/message-DS8lzHc1.js +1 -0
- package/dist/message-circle-Xf8Q-vZp.js +1 -0
- package/dist/message-square-Cxnwgkns.js +1 -0
- package/dist/{elements/chunks/minimize-2-SegJ_oku.js → minimize-2-UEZmP2QT.js} +1 -1
- package/dist/{elements/chunks/model-switcher-D7JrFFHp.js → model-switcher-Kk1jEQ5t.js} +1 -1
- package/dist/{elements/chunks/overlay-CV7z2YuX.js → overlay-C1od6LOz.js} +1 -1
- package/dist/panel-right-ryET0yyn.js +1 -0
- package/dist/paperclip-BDNoZFJP.js +1 -0
- package/dist/play-OFIvYXUQ.js +1 -0
- package/dist/primitives/card-data-types.d.ts +49 -0
- package/dist/primitives/card-routing.d.ts +1 -33
- package/dist/primitives/card-validate.d.ts +2 -2
- package/dist/primitives/chat-config.d.ts +29 -2
- package/dist/primitives/field-mask.d.ts +96 -0
- package/dist/primitives/field-semantics.d.ts +36 -0
- package/dist/primitives/input-mask.d.ts +46 -0
- package/dist/primitives/url-scheme-policy.d.ts +34 -0
- package/dist/primitives/use-resize-observer.d.ts +23 -0
- package/dist/primitives/use-speech-recognition.d.ts +8 -0
- package/dist/primitives/use-text-stream.d.ts +5 -0
- package/dist/progress-bar-BvYX0aoy.js +1 -0
- package/dist/prompt-suggestion-BRFu2lcu.js +1 -0
- package/dist/radio-DcL23cNJ.js +1 -0
- package/dist/react/index.d.ts +306 -283
- package/dist/react.js +309 -247
- package/dist/reasoning-Cackq4Rh.js +1 -0
- package/dist/register-impl-C7rENvPK.js +424 -0
- package/dist/{elements/chunks/resizable-Dpzjlvp4.js → resizable-Cp9HUYih.js} +1 -1
- package/dist/rotate-ccw-0DLRDHH5.js +1 -0
- package/dist/rotate-cw-DOB9IGcU.js +1 -0
- package/dist/schemas/form.schema.json +21 -1
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/tool-defs.d.ts +45 -0
- package/dist/schemas.js +398 -305
- package/dist/scroll-area-emVmWP4K.js +1 -0
- package/dist/scroll-button-CeP_gv1x.js +1 -0
- package/dist/select-CWmu00tS.js +1 -0
- package/dist/{elements/chunks/separator-DH2r_ZSZ.js → separator-CE6vly6z.js} +1 -1
- package/dist/{elements/chunks/settings-BFMOd6yB.js → settings-CE-78SvZ.js} +1 -1
- package/dist/{elements/chunks/settings-group-B3VZKIcz.js → settings-group-ZFWNxFQR.js} +1 -1
- package/dist/{elements/chunks/skeleton-N8BuWYmf.js → skeleton-BebdP4mf.js} +1 -1
- package/dist/slider-BAKRYUlz.js +1 -0
- package/dist/slots-DaNjX0rc.js +1 -0
- package/dist/{solid-BMuDmFo_.js → solid-BE5ui6sy.js} +10672 -8116
- package/dist/{solid-CGYNdLRR.js → solid-DFhsce3O.js} +8137 -6056
- package/dist/solid.d.ts +15 -0
- package/dist/solid.js +248 -235
- package/dist/solid.server.js +248 -235
- package/dist/source-B0FlguSx.js +1 -0
- package/dist/star-CBMZvLuN.js +1 -0
- package/dist/state/index.d.ts +5 -1
- package/dist/state/mock.d.ts +32 -2
- package/dist/state/persistence.d.ts +51 -0
- package/dist/state/stream.d.ts +50 -0
- package/dist/state/threads.d.ts +71 -0
- package/dist/state.js +297 -172
- package/dist/store-7eoadydQ.js +1 -0
- package/dist/svelte--5p79yCD.js +1 -15
- package/dist/switch-CViARZCy.js +1 -0
- package/dist/tasks-card-MjCcOhR_.js +1 -0
- package/dist/text-shimmer-DKlwRF2G.js +1 -0
- package/dist/textarea-3ay2Me2-.js +1 -0
- package/dist/theme.tokens.css +82 -13
- package/dist/{elements/chunks/thumbs-up-DQe0hvkZ.js → thumbs-up-DQcu2HMO.js} +1 -1
- package/dist/toast-store-C6yKOhbk.js +1 -0
- package/dist/{elements/chunks/tool-C1YtxwPu.js → tool-BCYNlfKy.js} +1 -1
- package/dist/{elements/chunks/tooltip-BqwnFWhn.js → tooltip-05zI4hjs.js} +1 -1
- package/dist/trash-2-BR1RxGky.js +1 -0
- package/dist/{elements/chunks/triangle-alert-B6or7F5u.js → triangle-alert-DHohDOCy.js} +1 -1
- package/dist/tsx-B8rCNbgL.js +1 -6
- package/dist/types.d.ts +4 -2
- package/dist/typescript-RycA9KXf.js +1 -6
- package/dist/ui/checkbox-group.d.ts +83 -0
- package/dist/ui/checkbox.d.ts +40 -0
- package/dist/ui/dock.d.ts +131 -0
- package/dist/ui/hover-card.d.ts +44 -0
- package/dist/ui/icon.d.ts +3 -0
- package/dist/ui/input.d.ts +48 -2
- package/dist/ui/radio.d.ts +85 -0
- package/dist/ui/select.d.ts +71 -0
- package/dist/ui/slider.d.ts +65 -0
- package/dist/ui/switch.d.ts +9 -2
- package/dist/upload-BbGW6A_u.js +1 -0
- package/dist/url-scheme-policy-DHpoTJwB.js +1 -0
- package/dist/use-card-resolution-CJW4grFa.js +1 -0
- package/dist/{variant-aurora-DwSOfraQ.js → variant-aurora-BUOVgPOB.js} +27 -27
- package/dist/{variant-aurora-wKvAQ5wp.js → variant-aurora-CUsUJvFI.js} +28 -28
- package/dist/{variant-aurora-XEkPT0_z.js → variant-aurora-CW2i4bfr.js} +2 -2
- package/dist/{elements/chunks/variant-aurora-QXTZPyST.js → variant-aurora-xiMPty8f.js} +2 -2
- package/dist/variant-custom-BQtOnmeO.js +1 -0
- package/dist/{variant-custom-BhS9wIpU.js → variant-custom-CAm9L-E5.js} +65 -65
- package/dist/{variant-custom-eFIg2XWQ.js → variant-custom-CAnzzppW.js} +40 -40
- package/dist/variant-custom-D91HjAKz.js +1 -0
- package/dist/{variant-wave-CUWGx9Bn.js → variant-wave-Ca1Ls4_y.js} +21 -21
- package/dist/{variant-wave-CbVsmgwT.js → variant-wave-CxX39DKd.js} +25 -25
- package/dist/{variant-wave-BwzrHD1s.js → variant-wave-DYyzIIZv.js} +2 -2
- package/dist/{elements/chunks/variant-wave-DDCwhLas.js → variant-wave-uY5crU_K.js} +2 -2
- package/dist/vue-BmIZj4XD.js +1 -33
- package/dist/wire/chunk.d.ts +47 -0
- package/dist/wire/diagnostics.d.ts +575 -0
- package/dist/wire/encode-probe.d.ts +70 -0
- package/dist/wire/encode.d.ts +24 -0
- package/dist/wire/index.d.ts +2 -0
- package/dist/wire/sse.d.ts +10 -2
- package/dist/wire.js +917 -480
- package/dist/x-C5m1JFgi.js +1 -0
- package/frameworks/react/index.tsx +325 -108
- package/llms-full.txt +1354 -82
- package/llms.txt +6 -3
- package/package.json +39 -6
- package/src/agent-tooling/archetypes.ts +41 -9
- package/src/agent-tooling/catalog/README.md +676 -0
- package/src/agent-tooling/catalog/catalog-types.ts +231 -0
- package/src/agent-tooling/catalog/fabrications.ts +96 -0
- package/src/agent-tooling/catalog/invariants.ts +284 -0
- package/src/agent-tooling/catalog/labs-titles.ts +114 -0
- package/src/agent-tooling/catalog/scenarios.ts +87 -0
- package/src/agent-tooling/catalog/surfaces.ts +347 -0
- package/src/agent-tooling/construct/cli-entry.ts +8 -0
- package/src/agent-tooling/construct/cli.ts +131 -0
- package/src/agent-tooling/construct/codegen.ts +1353 -0
- package/src/agent-tooling/construct/construct.v1.schema.json +250 -0
- package/src/agent-tooling/construct/dev.ts +148 -0
- package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +7 -0
- package/src/agent-tooling/construct/fixtures/ops-console.construct.json +44 -0
- package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +23 -0
- package/src/agent-tooling/construct/schema.ts +371 -0
- package/src/agent-tooling/integrations/anthropic.ts +7 -1
- package/src/agent-tooling/integrations/cloudflare.ts +43 -2
- package/src/agent-tooling/integrations/langgraph.ts +7 -1
- package/src/agent-tooling/integrations/mastra.ts +7 -1
- package/src/agent-tooling/integrations/mock.ts +92 -17
- package/src/agent-tooling/integrations/ollama.ts +7 -1
- package/src/agent-tooling/integrations/openai.ts +7 -1
- package/src/agent-tooling/integrations/openrouter.ts +7 -1
- package/src/agent-tooling/integrations/pi.ts +56 -1
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +7 -1
- package/src/agent-tooling/mcp/css-raw.d.ts +12 -0
- package/src/agent-tooling/mcp/manifest.ts +100 -1
- package/src/agent-tooling/mcp/server.ts +63 -6
- package/src/agent-tooling/mcp/tools/construct.ts +130 -0
- package/src/agent-tooling/mcp/tools/debug.ts +126 -4
- package/src/agent-tooling/mcp/tools/reference.ts +542 -5
- package/src/agent-tooling/mcp/tools/scaffold.ts +1834 -307
- package/src/agent-tooling/mcp/tools/theme.ts +146 -30
- package/src/agent-tooling/mcp/validate-args.ts +141 -0
- package/src/agent-tooling/recipes/composed-thread.ts +714 -0
- package/src/agent-tooling/recipes/index.ts +22 -0
- package/src/agent-tooling/recipes/types.ts +32 -0
- package/src/agent-tooling/registry.ts +24 -7
- package/src/agent-tooling/route-emit.ts +26 -2
- package/src/components/attachment-types.ts +54 -1
- package/src/components/attachments.tsx +169 -22
- package/src/components/audio-visualizer/fit-scale.ts +112 -0
- package/src/components/audio-visualizer/index.tsx +80 -3
- package/src/components/audio-visualizer/variant-aurora.tsx +10 -4
- package/src/components/audio-visualizer/variant-bar.tsx +38 -6
- package/src/components/audio-visualizer/variant-custom.tsx +8 -2
- package/src/components/audio-visualizer/variant-grid.tsx +16 -11
- package/src/components/audio-visualizer/variant-radial.tsx +15 -10
- package/src/components/audio-visualizer/variant-wave.tsx +7 -2
- package/src/components/card-renderer.tsx +39 -8
- package/src/components/card.tsx +10 -2
- package/src/components/chat-container.tsx +4 -1
- package/src/components/chat-scope-picker.tsx +2 -2
- package/src/components/chat-thread.tsx +105 -13
- package/src/components/choice-card.tsx +102 -37
- package/src/components/coachmark.tsx +1 -1
- package/src/components/code-block.tsx +81 -5
- package/src/components/composer.tsx +89 -8
- package/src/components/confirm-card.tsx +20 -4
- package/src/components/conversation-item.tsx +138 -4
- package/src/components/conversation-list.tsx +230 -15
- package/src/components/embed.tsx +4 -0
- package/src/components/empty.tsx +14 -3
- package/src/components/file-tree.tsx +5 -1
- package/src/components/form-widgets.tsx +257 -195
- package/src/components/form.tsx +336 -21
- package/src/components/loader.tsx +12 -5
- package/src/components/message-skills.tsx +1 -1
- package/src/components/message.tsx +142 -12
- package/src/components/prompt-input.tsx +7 -1
- package/src/components/prompt-suggestion.tsx +4 -1
- package/src/components/reasoning.tsx +30 -3
- package/src/components/response-compare.tsx +2 -2
- package/src/components/response-stream.tsx +13 -7
- package/src/components/screen.tsx +5 -0
- package/src/components/scroll-button.tsx +63 -4
- package/src/components/source.tsx +6 -1
- package/src/components/tasks-card.tsx +58 -16
- package/src/components/thread.tsx +10 -1
- package/src/components/toast.tsx +11 -5
- package/src/components/tool-types.ts +9 -0
- package/src/components/voice-input.tsx +35 -3
- package/src/components/voice-output.tsx +46 -6
- package/src/components/workspace-shell.tsx +285 -0
- package/src/diagnostics/hook.ts +352 -0
- package/src/diagnostics/index.ts +82 -0
- package/src/diagnostics/report-request.ts +296 -0
- package/src/elements/attachments.tsx +9 -8
- package/src/elements/audio-visualizer.tsx +48 -4
- package/src/elements/chat-workspace.tsx +159 -287
- package/src/elements/chat.tsx +31 -10
- package/src/elements/checkbox-group.tsx +164 -0
- package/src/elements/checkbox.tsx +134 -0
- package/src/elements/code-block.tsx +19 -1
- package/src/elements/command.tsx +4 -1
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +12 -1
- package/src/elements/conversation-item.tsx +120 -0
- package/src/elements/conversation-list.tsx +60 -11
- package/src/elements/default-input.tsx +5 -5
- package/src/elements/define-entry.ts +12 -0
- package/src/elements/define.tsx +168 -8
- package/src/elements/diagnostic-events.ts +114 -0
- package/src/elements/dialog.tsx +47 -0
- package/src/elements/disclosure.ts +34 -12
- package/src/elements/dock.tsx +215 -0
- package/src/elements/dropdown.tsx +123 -0
- package/src/elements/element-data-types.ts +40 -0
- package/src/elements/element-diagnostics.ts +392 -0
- package/src/elements/element-manifest.json +36 -0
- package/src/elements/element-meta.json +1483 -560
- package/src/elements/element-nonscalar.json +185 -0
- package/src/elements/element-types.d.ts +589 -167
- package/src/elements/feedback-bar.tsx +5 -0
- package/src/elements/icon-names.json +29 -0
- package/src/elements/input.tsx +312 -23
- package/src/elements/menu.tsx +13 -0
- package/src/elements/message.tsx +2 -0
- package/src/elements/pane-grid.tsx +111 -0
- package/src/elements/prompt-dock.tsx +2 -1
- package/src/elements/prompt-input.tsx +16 -10
- package/src/elements/radio-group.tsx +129 -0
- package/src/elements/register-impl.ts +52 -0
- package/src/elements/register.ts +28 -0
- package/src/elements/resizable.tsx +52 -25
- package/src/elements/scroll-button.tsx +59 -6
- package/src/elements/select.tsx +165 -0
- package/src/elements/slider.tsx +171 -0
- package/src/elements/slots.ts +108 -13
- package/src/elements/styles.css +119 -9
- package/src/elements/thinking-bar.tsx +16 -7
- package/src/elements/thread.tsx +2 -0
- package/src/elements/toast.tsx +17 -1
- package/src/elements/tool.tsx +26 -5
- package/src/elements/voice-input.tsx +11 -2
- package/src/elements/voice-output.tsx +16 -3
- package/src/index.ts +4 -1
- package/src/primitives/card-data-types.ts +49 -0
- package/src/primitives/card-host.tsx +4 -2
- package/src/primitives/card-routing.ts +7 -56
- package/src/primitives/card-schemas/form.schema.json +21 -1
- package/src/primitives/card-validate.ts +10 -2
- package/src/primitives/chat-config.tsx +33 -6
- package/src/primitives/field-mask.ts +256 -0
- package/src/primitives/field-semantics.ts +115 -0
- package/src/primitives/input-mask.ts +853 -0
- package/src/primitives/toast-store.ts +66 -4
- package/src/primitives/url-scheme-policy.ts +70 -0
- package/src/primitives/use-resize-observer.ts +33 -0
- package/src/primitives/use-speech-recognition.ts +12 -1
- package/src/primitives/use-stick-to-bottom.ts +34 -2
- package/src/primitives/use-text-stream.ts +33 -2
- package/src/remote/provider-runtime.ts +6 -3
- package/src/schemas/index.ts +1 -0
- package/src/schemas/tool-defs.ts +307 -6
- package/src/solid.ts +15 -0
- package/src/state/index.ts +13 -1
- package/src/state/mock.ts +92 -6
- package/src/state/persistence.ts +180 -0
- package/src/state/stream.ts +71 -4
- package/src/state/threads.ts +142 -0
- package/src/types.ts +4 -2
- package/src/ui/agent-card.tsx +2 -2
- package/src/ui/avatar.tsx +1 -1
- package/src/ui/checkbox-group.tsx +153 -0
- package/src/ui/checkbox.tsx +63 -0
- package/src/ui/dialog.tsx +6 -0
- package/src/ui/dock.tsx +584 -0
- package/src/ui/dropdown.tsx +67 -23
- package/src/ui/hover-card.tsx +156 -6
- package/src/ui/icon.tsx +53 -0
- package/src/ui/input.tsx +275 -16
- package/src/ui/kbd.tsx +1 -1
- package/src/ui/nav.tsx +1 -1
- package/src/ui/pane-group.tsx +2 -2
- package/src/ui/radio.tsx +150 -0
- package/src/ui/resizable.tsx +11 -0
- package/src/ui/select.tsx +168 -0
- package/src/ui/slider.tsx +178 -0
- package/src/ui/switch.tsx +49 -18
- package/src/utils/cn.ts +11 -5
- package/src/wire/chunk.ts +47 -0
- package/src/wire/consume.ts +185 -26
- package/src/wire/diagnostics.ts +727 -0
- package/src/wire/encode-probe.ts +214 -0
- package/src/wire/encode.ts +267 -14
- package/src/wire/formats/anthropic.ts +8 -1
- package/src/wire/formats/openai.ts +5 -0
- package/src/wire/index.ts +22 -0
- package/src/wire/read.ts +250 -6
- package/src/wire/sse.ts +16 -3
- package/theme.css +84 -13
- package/dist/elements/chunks/action-icons-DW9muWrY.js +0 -1
- package/dist/elements/chunks/arrow-left-iJVnQmxA.js +0 -1
- package/dist/elements/chunks/artifact-BSuPYFHY.js +0 -1
- package/dist/elements/chunks/attachments-CL6TaqIb.js +0 -1
- package/dist/elements/chunks/audio-visualizer-BuUqQmaO.js +0 -1
- package/dist/elements/chunks/badge-TIMiJFKc.js +0 -1
- package/dist/elements/chunks/bash-InADTalH.js +0 -1
- package/dist/elements/chunks/card-renderer-CSoTL_e1.js +0 -1
- package/dist/elements/chunks/card-routing-BK09BdmL.js +0 -1
- package/dist/elements/chunks/chat-thread-DxoKaStk.js +0 -1
- package/dist/elements/chunks/check-MdLzbxrm.js +0 -1
- package/dist/elements/chunks/chevron-down-MK7IzDqe.js +0 -1
- package/dist/elements/chunks/chevron-right-C5jXhmtV.js +0 -1
- package/dist/elements/chunks/choice-card-CGMc3kek.js +0 -1
- package/dist/elements/chunks/circle-BG7VkPep.js +0 -1
- package/dist/elements/chunks/circle-check-GqvO2UBs.js +0 -1
- package/dist/elements/chunks/circle-x-mhH7pOK2.js +0 -1
- package/dist/elements/chunks/code-block-ClhQO9RW.js +0 -1
- package/dist/elements/chunks/collapsible-DBcBkFPh.js +0 -1
- package/dist/elements/chunks/composer-CZZmtYOT.js +0 -69
- package/dist/elements/chunks/confirm-card-BWjf2-xo.js +0 -1
- package/dist/elements/chunks/context-hKjNqiyQ.js +0 -1
- package/dist/elements/chunks/conversation-list-CsGJjSKG.js +0 -1
- package/dist/elements/chunks/core-AYMC6_lb.js +0 -12
- package/dist/elements/chunks/css-M7EaDHN_.js +0 -1
- package/dist/elements/chunks/default-input-B1Mem5wu.js +0 -1
- package/dist/elements/chunks/define-B_tXMYRm.js +0 -1
- package/dist/elements/chunks/disclosure-DAoYOKew.js +0 -1
- package/dist/elements/chunks/download-DnCyWFJE.js +0 -1
- package/dist/elements/chunks/dropdown-CmNna5lT.js +0 -1
- package/dist/elements/chunks/embed-CjAD2jSi.js +0 -1
- package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +0 -141
- package/dist/elements/chunks/file-text-D0J9F6M7.js +0 -1
- package/dist/elements/chunks/folder-NV1pi_ud.js +0 -1
- package/dist/elements/chunks/form-Cp8Y00Wn.js +0 -1
- package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +0 -1
- package/dist/elements/chunks/github-light-JYsPkUQd.js +0 -1
- package/dist/elements/chunks/hover-card-6KtXpLwT.js +0 -1
- package/dist/elements/chunks/html-CPZ3oZQ7.js +0 -1
- package/dist/elements/chunks/icon-97R8SR2p.js +0 -1
- package/dist/elements/chunks/info-C8r5Q6cc.js +0 -1
- package/dist/elements/chunks/input-DrseEjhp.js +0 -1
- package/dist/elements/chunks/javascript-C25yR2R2.js +0 -1
- package/dist/elements/chunks/json-DxJze_jm.js +0 -1
- package/dist/elements/chunks/kbd-OGJDUT-O.js +0 -1
- package/dist/elements/chunks/link-BdsfLB7b.js +0 -1
- package/dist/elements/chunks/loader-B5QzK_8_.js +0 -1
- package/dist/elements/chunks/message-CE4k-qaj.js +0 -1
- package/dist/elements/chunks/message-DtXKPqiR.js +0 -1
- package/dist/elements/chunks/message-square-C9ovmtOo.js +0 -1
- package/dist/elements/chunks/paperclip-Bg0ITU3b.js +0 -1
- package/dist/elements/chunks/progress-bar-D1_rzt6N.js +0 -1
- package/dist/elements/chunks/prompt-suggestion-D5Sm8xF1.js +0 -1
- package/dist/elements/chunks/reasoning-BrxQfpfI.js +0 -1
- package/dist/elements/chunks/rotate-cw-DzVDJOA1.js +0 -1
- package/dist/elements/chunks/scroll-area-93A1bbeG.js +0 -1
- package/dist/elements/chunks/scroll-button-CrTo7wm4.js +0 -1
- package/dist/elements/chunks/share-DgNbkJKY.js +0 -1
- package/dist/elements/chunks/slots-CIw9RlAe.js +0 -1
- package/dist/elements/chunks/source-w9V0NnBn.js +0 -1
- package/dist/elements/chunks/star-DHvACxtm.js +0 -1
- package/dist/elements/chunks/store-HIPGhNz9.js +0 -1
- package/dist/elements/chunks/svelte--5p79yCD.js +0 -1
- package/dist/elements/chunks/tasks-card-lPxoodlA.js +0 -1
- package/dist/elements/chunks/text-shimmer-BY_rI3s5.js +0 -1
- package/dist/elements/chunks/textarea-DPfuhswM.js +0 -1
- package/dist/elements/chunks/toast-store-VUZZBhzH.js +0 -1
- package/dist/elements/chunks/tsx-B8rCNbgL.js +0 -1
- package/dist/elements/chunks/typescript-RycA9KXf.js +0 -1
- package/dist/elements/chunks/use-card-resolution-DJg32jGH.js +0 -1
- package/dist/elements/chunks/variant-custom-D5ZlmziF.js +0 -1
- package/dist/elements/chunks/video-Bb86fVYI.js +0 -1
- package/dist/elements/chunks/vue-BmIZj4XD.js +0 -1
- package/dist/elements/chunks/x-BOhjBeOd.js +0 -1
- package/dist/llms/llms-full.txt +0 -2834
- package/dist/llms/llms.txt +0 -156
- package/dist/primitives/card-validate-generator.testlib.d.ts +0 -20
- package/dist/register-impl-DGmGzrEM.js +0 -145
- package/dist/ui/stat.d.ts +0 -16
- package/dist/variant-custom-MkTNTycd.js +0 -1
- package/src/primitives/card-validate-generator.testlib.ts +0 -38
- package/src/ui/stat.tsx +0 -41
- /package/dist/{elements/chunks/card-tags-D8lZ-C_U.js → card-tags-D8lZ-C_U.js} +0 -0
- /package/dist/{elements/chunks/link-preview-DNILK391.js → link-preview-DNILK391.js} +0 -0
|
@@ -19,6 +19,7 @@ import type { ProseSize } from '../primitives/chat-config';
|
|
|
19
19
|
import type { ModelOption } from '../types';
|
|
20
20
|
import type { CardComponentMap } from '../primitives/card-registry';
|
|
21
21
|
import type { CardSchemaMap } from './card-renderer';
|
|
22
|
+
import type { JSX } from 'solid-js';
|
|
22
23
|
|
|
23
24
|
export interface ChatThreadContextUsage {
|
|
24
25
|
usedTokens: number;
|
|
@@ -45,6 +46,11 @@ export interface ChatThreadProps {
|
|
|
45
46
|
* the kit checks its own seven built-ins and leaves your own card type
|
|
46
47
|
* unvalidated. A schema here WINS over a built-in of the same name. */
|
|
47
48
|
cardSchemas?: CardSchemaMap;
|
|
49
|
+
/** The custom-element host node to emit card events off when no `CardProvider`
|
|
50
|
+
* is present, forwarded through `MessageBody` to `CardRenderer`. The
|
|
51
|
+
* element facades pass their own host element so card events leave as the
|
|
52
|
+
* bubbling `kai-card` CustomEvent. */
|
|
53
|
+
cardHostElement?: HTMLElement;
|
|
48
54
|
/** Value of the input. A **string** is controlled (the host owns the text and
|
|
49
55
|
* updates it on `kai-value-change`). A **ComposerDoc** is a one-time seed that
|
|
50
56
|
* pre-populates pills; the user then edits freely. Leave unset for uncontrolled. */
|
|
@@ -73,6 +79,19 @@ export interface ChatThreadProps {
|
|
|
73
79
|
/** Enable Shiki syntax highlighting in code blocks. Turn off to render plain
|
|
74
80
|
* `<pre>` blocks (lighter, no highlighter load). Default true. */
|
|
75
81
|
codeHighlight?: boolean;
|
|
82
|
+
/** How `reasoning` parts render across the thread. `'full'` (default) is the
|
|
83
|
+
* current collapsible-disclosure behavior; `'compact'` shows only a shimmer
|
|
84
|
+
* loader while a reasoning part streams and nothing once it settles (no
|
|
85
|
+
* expandable detail); `'off'` renders reasoning parts not at all. Forwarded
|
|
86
|
+
* to every `MessageBody` as `reasoningMode`. */
|
|
87
|
+
reasoning?: 'full' | 'compact' | 'off';
|
|
88
|
+
/** Seeds the reasoning disclosure open AND keeps it tracking the stream
|
|
89
|
+
* (open while streaming, closes when it settles): the pre-Task-19f `full`
|
|
90
|
+
* behavior. Default false/absent: the panel starts closed (just the
|
|
91
|
+
* "Thinking" shimmer chip) and only opens on click, the current default
|
|
92
|
+
* (owner ruling, 2026-08-26). Meaningless when `reasoning` is `'compact'`
|
|
93
|
+
* or `'off'`. Forwarded to every `MessageBody` as `reasoningDefaultOpen`. */
|
|
94
|
+
reasoningOpen?: boolean;
|
|
76
95
|
/** Optional header title shown on the left of the header. */
|
|
77
96
|
chatTitle?: string;
|
|
78
97
|
/** Optional model list. When set (>1 model) a ModelSwitcher is shown in the
|
|
@@ -90,6 +109,17 @@ export interface ChatThreadProps {
|
|
|
90
109
|
headerStart?: boolean;
|
|
91
110
|
/** Whether the host has `slot="header-end"` content (right of the controls). */
|
|
92
111
|
headerEnd?: boolean;
|
|
112
|
+
/** Extra content rendered in the header-end region, AFTER `slot="header-end"`.
|
|
113
|
+
* This is a JSX escape hatch for a caller composing `ChatThread` directly as a
|
|
114
|
+
* Solid component (no shadow-DOM host, so there is no light-DOM node to slot) —
|
|
115
|
+
* the `kai-dock`-docked construct widget is the motivating case: it needs its
|
|
116
|
+
* own close affordance to sit IN the header row, sharing it with the title
|
|
117
|
+
* instead of floating as a second control with no visible relationship to the
|
|
118
|
+
* chat surface. Renders alongside the named slot rather than replacing it, so a
|
|
119
|
+
* real `slot="header-end"` consumer and this prop can both be present. Counts
|
|
120
|
+
* toward `showHeader()` the same as `headerEnd`, so a construct with no title
|
|
121
|
+
* and only this content still gets a header row to sit in. */
|
|
122
|
+
headerEndContent?: JSX.Element;
|
|
93
123
|
// ── Composition slots ─────────────────────────────────────────────────────
|
|
94
124
|
// Each flag below is set by the `<kai-chat>` facade when matching light-DOM
|
|
95
125
|
// `slot="…"` content is projected, and gates one composition slot. Two kinds:
|
|
@@ -106,6 +136,17 @@ export interface ChatThreadProps {
|
|
|
106
136
|
sidebar?: boolean;
|
|
107
137
|
/** REPLACE: custom zero-state rendered in the message area while the thread is empty (replaces the empty message list only; the composer and its suggestions still render). */
|
|
108
138
|
empty?: boolean;
|
|
139
|
+
/** REPLACE, JSX form: the empty-state content itself, for a caller composing
|
|
140
|
+
* `ChatThread` directly as a Solid component rather than through the `<kai-chat>`
|
|
141
|
+
* shadow-DOM boundary that `empty`/`slot="empty"` targets. Renders INSIDE this
|
|
142
|
+
* component's own tree — so a caller passing the kit's own `<Empty>` composition
|
|
143
|
+
* (`components/empty.tsx`) gets it fully styled by the adopted stylesheet, unlike
|
|
144
|
+
* `slot="empty"`: that slot only ever receives LIGHT-DOM children of the shadow
|
|
145
|
+
* HOST, and light-DOM nodes are outside the shadow root's adopted stylesheets, so
|
|
146
|
+
* Tailwind-utility-class content projected there renders bare. Takes priority
|
|
147
|
+
* over `empty`/`slot="empty"` when both are set — the two are alternate delivery
|
|
148
|
+
* mechanisms for the same region, not additive like `headerEndContent`. */
|
|
149
|
+
emptyContent?: JSX.Element;
|
|
109
150
|
/** REPLACE: full custom composer in place of the built-in prompt input. The
|
|
110
151
|
* projected content wires its own submit (the data-flow boundary). */
|
|
111
152
|
composer?: boolean;
|
|
@@ -120,8 +161,12 @@ export interface ChatThreadProps {
|
|
|
120
161
|
accept?: MediaTypeFilter;
|
|
121
162
|
/** Files the composer refused because `accept` excluded them. */
|
|
122
163
|
onAttachmentsRejected?: (rejected: RejectedAttachment[]) => void;
|
|
123
|
-
/**
|
|
124
|
-
|
|
164
|
+
/** When `false`, hides the built-in paperclip attach button. Defaults to
|
|
165
|
+
* `true` (undeclared keeps today's behavior: attach visible), matching
|
|
166
|
+
* `DefaultPromptInput`'s own default: only an explicit `false` hides it. */
|
|
167
|
+
attach?: boolean;
|
|
168
|
+
/** Show a web-search (Globe) button in the input toolbar; calls `onWebSearch`. */
|
|
169
|
+
webSearch?: boolean;
|
|
125
170
|
/** Show a Voice (Mic) button in the input toolbar; fires a `voice` event. */
|
|
126
171
|
voice?: boolean;
|
|
127
172
|
/** Rich entity triggers. Each `{ char, kind, items }` opens a caret-anchored
|
|
@@ -140,7 +185,7 @@ export interface ChatThreadProps {
|
|
|
140
185
|
onSuggestionClick?: (value: string) => void;
|
|
141
186
|
onModelChange?: (modelId: string) => void;
|
|
142
187
|
onMessageAction?: (detail: MessageActionDetail) => void;
|
|
143
|
-
|
|
188
|
+
onWebSearch?: () => void;
|
|
144
189
|
onVoice?: () => void;
|
|
145
190
|
/** Receive the imperative controller once mounted. The kai-chat facade forwards
|
|
146
191
|
* these as element methods (focus/clear/send/scrollToBottom). */
|
|
@@ -194,6 +239,30 @@ export interface ChatThreadController {
|
|
|
194
239
|
* Contract: `id` must be unique per message. It already had to be — the
|
|
195
240
|
* feedback/copy state above this list is keyed by it.
|
|
196
241
|
*/
|
|
242
|
+
/** How an ASSISTANT row aligns its parts across the column (K-D11).
|
|
243
|
+
*
|
|
244
|
+
* `stretch`, not `start`. An assistant turn is a `flex flex-col` box, so under
|
|
245
|
+
* `items-start` every part is a flex item with a fit-content cross size —
|
|
246
|
+
* `min(max-content, column)`. Prose is wider than the column so a text bubble
|
|
247
|
+
* looked right, and a generative-UI card was as wide as its widest button: the
|
|
248
|
+
* ops-console parameters form measured 285px inside a 768px column while the
|
|
249
|
+
* approval card beside it filled all 768. A card in a chat thread filling its
|
|
250
|
+
* column is a fact about the medium, and no consumer can reach it — the card
|
|
251
|
+
* element is created inside `<kai-chat>`'s shadow root.
|
|
252
|
+
*
|
|
253
|
+
* Stretch rather than `w-full` on the cards, for two reasons: it is one lever
|
|
254
|
+
* instead of one per card surface (the Solid `Card` root AND every `kai-*`
|
|
255
|
+
* card host, whose shadow wrapper is `display: contents`), and stretching only
|
|
256
|
+
* applies where the cross size is `auto`, so a part that states its own width
|
|
257
|
+
* is untouched — `Attachments variant="grid"` stays `w-fit`. `Tool` and
|
|
258
|
+
* `Reasoning` already asked for `w-full` explicitly; this is the same
|
|
259
|
+
* intention, applied once.
|
|
260
|
+
*
|
|
261
|
+
* USER rows keep `items-end`: their bubble is `max-w-[85%]` and right aligned,
|
|
262
|
+
* and stretching it would break both.
|
|
263
|
+
*/
|
|
264
|
+
const ASSISTANT_ALIGN = 'items-stretch';
|
|
265
|
+
|
|
197
266
|
export function ChatThread(props: ChatThreadProps) {
|
|
198
267
|
const outer = useChatConfig();
|
|
199
268
|
const reveal = () => (props.actionsReveal === 'hover' ? 'hover' : 'always');
|
|
@@ -228,7 +297,7 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
228
297
|
if ((props.suggestionMode ?? 'submit') === 'fill') { handleChange(v); props.onSuggestionClick?.(v); }
|
|
229
298
|
else { props.onSubmit?.({ value: v, attachments: attachments() }); afterSubmit(); }
|
|
230
299
|
};
|
|
231
|
-
const showHeader = () => !!(props.chatTitle || props.models || props.context || props.headerStart || props.headerEnd);
|
|
300
|
+
const showHeader = () => !!(props.chatTitle || props.models || props.context || props.headerStart || props.headerEnd || props.headerEndContent);
|
|
232
301
|
// Suggestions are conversation starters: show only on an empty thread unless
|
|
233
302
|
// the host opts into persisting them.
|
|
234
303
|
const visibleSuggestions = () =>
|
|
@@ -304,6 +373,11 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
304
373
|
{/* Consumer-injected trailing controls (share, settings, …).
|
|
305
374
|
Projects light-DOM `slot="header-end"` children of <kai-chat>. */}
|
|
306
375
|
<slot name="header-end" />
|
|
376
|
+
{/* JSX escape hatch for a Solid-composed caller with no shadow-DOM
|
|
377
|
+
host to slot into (see the prop doc) — a docked widget's own
|
|
378
|
+
close affordance is the motivating case, sharing this row with
|
|
379
|
+
the title instead of floating as an unrelated second control. */}
|
|
380
|
+
{props.headerEndContent}
|
|
307
381
|
</div>
|
|
308
382
|
</header>
|
|
309
383
|
</Show>
|
|
@@ -314,11 +388,15 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
314
388
|
<div class="relative flex-1 overflow-hidden">
|
|
315
389
|
<ChatContainer class="h-full px-4 py-3">
|
|
316
390
|
<ChatContainerContent class="mx-auto w-full max-w-3xl space-y-4">
|
|
317
|
-
{/* REPLACE — custom empty-state
|
|
391
|
+
{/* REPLACE — custom empty-state content, shown only while the thread is
|
|
318
392
|
empty. The component still owns WHEN it shows (data state); the
|
|
319
|
-
consumer owns WHAT it looks like.
|
|
320
|
-
|
|
321
|
-
|
|
393
|
+
consumer owns WHAT it looks like. `emptyContent` (JSX, rendered
|
|
394
|
+
in-tree and fully styled) wins over `empty`/`slot="empty"`
|
|
395
|
+
(light-DOM projection) when both are set — see the prop doc. */}
|
|
396
|
+
<Show when={(props.empty || props.emptyContent) && props.messages.length === 0}>
|
|
397
|
+
<Show when={props.emptyContent} fallback={<slot name="empty" />}>
|
|
398
|
+
{props.emptyContent}
|
|
399
|
+
</Show>
|
|
322
400
|
</Show>
|
|
323
401
|
{/* Keyed by message id (see the note above this component), so a
|
|
324
402
|
streaming delta updates the row instead of replacing it. */}
|
|
@@ -335,8 +413,19 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
335
413
|
const body = (
|
|
336
414
|
<MessageBody
|
|
337
415
|
parts={m().parts}
|
|
416
|
+
/* F-21: streaming-ness = the thread's ONE existing
|
|
417
|
+
loading signal + being the last message and an
|
|
418
|
+
assistant turn. No second streaming source. The
|
|
419
|
+
reasoning disclosure no longer auto-opens on it
|
|
420
|
+
by default (Task 19f, owner ruling 2026-08-26) —
|
|
421
|
+
only the trigger's shimmer reflects streaming
|
|
422
|
+
unless `reasoningOpen` opts back in. */
|
|
423
|
+
isStreaming={props.loading === true && m().role === 'assistant' && i() === props.messages.length - 1}
|
|
424
|
+
reasoningMode={props.reasoning}
|
|
425
|
+
reasoningDefaultOpen={props.reasoningOpen}
|
|
338
426
|
cardTypes={props.cardTypes}
|
|
339
427
|
cardSchemas={props.cardSchemas}
|
|
428
|
+
cardHostElement={props.cardHostElement}
|
|
340
429
|
isUser={m().role === 'user'}
|
|
341
430
|
markdown={m().role === 'assistant'}
|
|
342
431
|
actions={m().actions}
|
|
@@ -356,7 +445,7 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
356
445
|
<Show
|
|
357
446
|
when={m().avatar}
|
|
358
447
|
fallback={
|
|
359
|
-
<Message role={m().role} class={`${rowGroup()}${m().role === 'user' ? 'flex-col items-end' :
|
|
448
|
+
<Message role={m().role} class={`${rowGroup()}${m().role === 'user' ? 'flex-col items-end' : `flex-col ${ASSISTANT_ALIGN}`}`}>
|
|
360
449
|
{body}
|
|
361
450
|
</Message>
|
|
362
451
|
}
|
|
@@ -364,7 +453,7 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
364
453
|
{(av) => (
|
|
365
454
|
<Message role={m().role} class={rowGroup()}>
|
|
366
455
|
<MessageAvatar src={av().src ?? ''} alt={av().alt ?? ''} fallback={av().fallback} />
|
|
367
|
-
<div class={`flex min-w-0 flex-1 flex-col ${m().role === 'user' ? 'items-end' :
|
|
456
|
+
<div class={`flex min-w-0 flex-1 flex-col ${m().role === 'user' ? 'items-end' : ASSISTANT_ALIGN}`}>
|
|
368
457
|
{body}
|
|
369
458
|
</div>
|
|
370
459
|
</Message>
|
|
@@ -379,7 +468,10 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
379
468
|
</ChatContainerContent>
|
|
380
469
|
<Show when={showScrollButton()}>
|
|
381
470
|
<div class="absolute bottom-4 left-1/2 flex w-full max-w-3xl -translate-x-1/2 justify-center px-5">
|
|
382
|
-
|
|
471
|
+
{/* The button now owns its elevation (kai-elevation); a `shadow-sm`
|
|
472
|
+
here would set box-shadow a second time and the winner would
|
|
473
|
+
be stylesheet order, not this call site. */}
|
|
474
|
+
<ScrollButton />
|
|
383
475
|
</div>
|
|
384
476
|
</Show>
|
|
385
477
|
</ChatContainer>
|
|
@@ -401,11 +493,11 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
401
493
|
value={current()} placeholder={props.placeholder} loading={props.loading === true}
|
|
402
494
|
suggestions={visibleSuggestions()} attachments={attachments()}
|
|
403
495
|
accept={props.accept} onAttachmentsRejected={props.onAttachmentsRejected}
|
|
404
|
-
|
|
496
|
+
attach={props.attach} webSearch={props.webSearch === true} voice={props.voice === true}
|
|
405
497
|
triggers={props.triggers} kindIcons={props.kindIcons}
|
|
406
498
|
onValueChange={handleChange} onSubmit={handleSubmit} onSuggestionClick={handleSuggestionClick}
|
|
407
499
|
onAttachmentsChange={(a) => { setAttachments(a); props.onAttachmentsChange?.(a); }}
|
|
408
|
-
|
|
500
|
+
onWebSearch={() => props.onWebSearch?.()} onVoice={() => props.onVoice?.()}
|
|
409
501
|
/>
|
|
410
502
|
}
|
|
411
503
|
>
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from 'solid-js';
|
|
15
15
|
import { cn } from '../utils/cn';
|
|
16
16
|
import { Button } from '../ui/button';
|
|
17
|
+
import { Radio } from '../ui/radio';
|
|
17
18
|
import { HoverCard } from '../ui/hover-card';
|
|
18
19
|
import { Card } from './card';
|
|
19
20
|
import { DismissedStub } from './dismissed-stub';
|
|
@@ -21,7 +22,7 @@ import type { CardEnvelope, CardEvent, CardHost, CardResolution } from '../primi
|
|
|
21
22
|
import { useCardResolution } from './use-card-resolution';
|
|
22
23
|
import { emitCardEvent } from '../primitives/card-routing';
|
|
23
24
|
import { useCardHost } from '../primitives/card-host';
|
|
24
|
-
import { Check
|
|
25
|
+
import { Check } from 'lucide-solid';
|
|
25
26
|
|
|
26
27
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
27
28
|
// Types (choice.schema.json) — AUTHORED IN ../primitives/card-data-types.ts.
|
|
@@ -430,6 +431,10 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
|
|
|
430
431
|
const groupLabel = (): string => local.heading ?? local.data?.prompt ?? 'Choose an option';
|
|
431
432
|
const promptId = `kai-choice-prompt-${uid}`;
|
|
432
433
|
const otherInputId = `kai-choice-other-${uid}`;
|
|
434
|
+
// The shared `name` is what makes the rows ONE native control: mutual exclusion,
|
|
435
|
+
// form participation and "2 of 4" from a screen reader all come from it. Scoped to
|
|
436
|
+
// this card's uid so two ChoiceCards on a page never de-select each other.
|
|
437
|
+
const radioName = `kai-choice-${uid}`;
|
|
433
438
|
|
|
434
439
|
return (
|
|
435
440
|
<Show when={valid()} fallback={<Card heading={local.heading} errorMessage={errorMessage()} />}>
|
|
@@ -443,14 +448,18 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
|
|
|
443
448
|
when={!res.isDeferred()}
|
|
444
449
|
fallback={<DismissedStub type={CHOICE_CARD_TYPE} title={local.heading} onReopen={onReopen} />}
|
|
445
450
|
>
|
|
446
|
-
|
|
451
|
+
{/* `prompt` is the card's supporting sentence — the same role `form`'s
|
|
452
|
+
`description` plays — so it goes through the shell's `description`
|
|
453
|
+
slot rather than being re-implemented in the body. That is what gives
|
|
454
|
+
it the muted colour: at full `text-foreground` it was the same colour
|
|
455
|
+
as the heading above it, which flattened the hierarchy. `descriptionId`
|
|
456
|
+
keeps the radiogroup's `aria-describedby` pointing at it. */}
|
|
457
|
+
<Card
|
|
458
|
+
heading={local.heading}
|
|
459
|
+
description={local.data?.prompt}
|
|
460
|
+
descriptionId={promptId}
|
|
461
|
+
>
|
|
447
462
|
<div class={cn('flex flex-col gap-3', local.class)}>
|
|
448
|
-
<Show when={local.data?.prompt}>
|
|
449
|
-
<p id={promptId} class="text-sm text-foreground">
|
|
450
|
-
{local.data?.prompt}
|
|
451
|
-
</p>
|
|
452
|
-
</Show>
|
|
453
|
-
|
|
454
463
|
<Show
|
|
455
464
|
when={!res.isResolved()}
|
|
456
465
|
fallback={<ResolvedChoice choice={resolvedChoice()!} optimistic={res.isOptimistic()} />}
|
|
@@ -462,7 +471,7 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
|
|
|
462
471
|
aria-describedby={local.data?.prompt ? promptId : undefined}
|
|
463
472
|
aria-disabled={isDisabled() ? 'true' : undefined}
|
|
464
473
|
class={cn(
|
|
465
|
-
'divide-y divide-border overflow-hidden rounded-lg border border-
|
|
474
|
+
'divide-y divide-border overflow-hidden rounded-lg border border-border',
|
|
466
475
|
isDisabled() && 'cursor-not-allowed opacity-60',
|
|
467
476
|
)}
|
|
468
477
|
onKeyDown={onGroupKeyDown}
|
|
@@ -477,8 +486,10 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
|
|
|
477
486
|
return (
|
|
478
487
|
<ListRow
|
|
479
488
|
opt={opt}
|
|
489
|
+
name={radioName}
|
|
480
490
|
checked={checked()}
|
|
481
491
|
tabStop={tabStop()}
|
|
492
|
+
groupDisabled={isDisabled()}
|
|
482
493
|
descId={descId}
|
|
483
494
|
hasDesc={hasDesc()}
|
|
484
495
|
onPick={() => {
|
|
@@ -518,14 +529,23 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
|
|
|
518
529
|
|
|
519
530
|
<div class="flex w-full flex-wrap items-center justify-between gap-2">
|
|
520
531
|
<Show when={local.data?.dismissible === true}>
|
|
532
|
+
{/* The contract `dismiss` verb — a footer ACTION (it emits
|
|
533
|
+
`{kind:'dismiss'}` and collapses the card to a re-openable
|
|
534
|
+
stub), not the chrome close in `Card`'s own `dismissible`
|
|
535
|
+
prop, which the contract cards deliberately never set.
|
|
536
|
+
Labelled rather than a bare ghost ✕: every other control in
|
|
537
|
+
this row says what it does, and an unlabelled 28px glyph
|
|
538
|
+
300px from the primary button reads as stray chrome. Same
|
|
539
|
+
shape in all four cards — `form` already looked like this.
|
|
540
|
+
`aria-label` is kept (redundant with the text) so the
|
|
541
|
+
element tests that select on it keep working. */}
|
|
521
542
|
<Button
|
|
522
543
|
type="button"
|
|
523
544
|
variant="ghost"
|
|
524
|
-
size="icon-sm"
|
|
525
545
|
aria-label="Dismiss"
|
|
526
546
|
onClick={onDismiss}
|
|
527
547
|
>
|
|
528
|
-
|
|
548
|
+
Dismiss
|
|
529
549
|
</Button>
|
|
530
550
|
</Show>
|
|
531
551
|
<Button type="button" class="ml-auto" disabled={!canSubmit()} onClick={submit}>
|
|
@@ -542,14 +562,19 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
|
|
|
542
562
|
}
|
|
543
563
|
|
|
544
564
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
545
|
-
// Internal list-row presentation: a
|
|
546
|
-
// selectable-list
|
|
565
|
+
// Internal list-row presentation: a `<label>` row wrapping a REAL
|
|
566
|
+
// `<input type="radio">` (the `Radio` primitive), with the kit's selectable-list
|
|
567
|
+
// styling.
|
|
547
568
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
548
569
|
|
|
549
570
|
interface RowProps {
|
|
550
571
|
opt: ChoiceOption;
|
|
572
|
+
/** The shared form-control name every radio in this card's group carries. */
|
|
573
|
+
name: string;
|
|
551
574
|
checked: boolean;
|
|
552
575
|
tabStop: boolean;
|
|
576
|
+
/** The whole group is frozen (`disabled` prop), not just this row. */
|
|
577
|
+
groupDisabled: boolean;
|
|
553
578
|
descId: string;
|
|
554
579
|
hasDesc: boolean;
|
|
555
580
|
onPick: () => void;
|
|
@@ -558,7 +583,7 @@ interface RowProps {
|
|
|
558
583
|
|
|
559
584
|
function RecommendedPill(): JSX.Element {
|
|
560
585
|
return (
|
|
561
|
-
<span class="inline-flex items-center rounded-full bg-[var(--color-primary)] px-1.5 py-0.5 text-
|
|
586
|
+
<span class="inline-flex items-center rounded-full bg-[var(--color-primary)] px-1.5 py-0.5 text-micro font-medium uppercase leading-none tracking-wide text-[var(--color-primary-foreground,white)]">
|
|
562
587
|
Recommended
|
|
563
588
|
</span>
|
|
564
589
|
);
|
|
@@ -587,25 +612,76 @@ function IconBadge(props: { name: string }): JSX.Element {
|
|
|
587
612
|
);
|
|
588
613
|
}
|
|
589
614
|
|
|
615
|
+
/**
|
|
616
|
+
* One row of the radiogroup.
|
|
617
|
+
*
|
|
618
|
+
* The control is a REAL `<input type="radio">` (the `Radio` primitive over
|
|
619
|
+
* `.kai-radio`), not the `aria-hidden` ring-shaped `<span>` this row used to draw.
|
|
620
|
+
* That is what buys form participation — a `name`, a `value`, and a real entry in a
|
|
621
|
+
* native `FormData` — which a `<div role="radio">` can never have, and it removes the
|
|
622
|
+
* fourth hand-rolled radio from the kit.
|
|
623
|
+
*
|
|
624
|
+
* WHY `role="radio"` IS ON THE INPUT AND NOT ON THE ROW. `choice-card.tsx`'s own
|
|
625
|
+
* keyboard handler and `response-compare.tsx` both drive `[role="radio"]`, an
|
|
626
|
+
* ATTRIBUTE selector that a native radio's implicit role does not match, so the role
|
|
627
|
+
* has to be written somewhere. Putting it on the `<label>` wrapper (which the plan
|
|
628
|
+
* suggested) would nest a radio inside a radio and announce the row twice; putting it
|
|
629
|
+
* on the input states the role the input already has. `aria-checked` / `aria-disabled`
|
|
630
|
+
* are likewise redundant with `checked` / `disabled` and kept in lockstep with them —
|
|
631
|
+
* they are the state the element tests read, and a redundant ARIA attribute that never
|
|
632
|
+
* disagrees with the DOM is harmless where a second radio in the a11y tree is not.
|
|
633
|
+
*
|
|
634
|
+
* The row's `<label>` is what makes a click anywhere on it select the option, which is
|
|
635
|
+
* the browser's own behaviour rather than the `onClick` re-implementation it replaces.
|
|
636
|
+
* The `onClick` is kept as well: it is the path the existing tests fire, and both paths
|
|
637
|
+
* land on the same idempotent `select()`.
|
|
638
|
+
*/
|
|
590
639
|
function ListRow(props: RowProps): JSX.Element {
|
|
640
|
+
// The row is inert when the option itself is disabled OR the whole group is frozen.
|
|
641
|
+
// The group case has to reach the native `disabled` attribute: an enabled input
|
|
642
|
+
// would visibly check itself on click while `select()` refuses the state change, and
|
|
643
|
+
// the DOM would then be lying about the selection.
|
|
644
|
+
const inert = (): boolean => props.opt.disabled === true || props.groupDisabled;
|
|
645
|
+
const pick = (): void => {
|
|
646
|
+
if (inert()) return;
|
|
647
|
+
props.onPick();
|
|
648
|
+
};
|
|
591
649
|
return (
|
|
592
|
-
<
|
|
593
|
-
|
|
594
|
-
aria-checked={props.checked}
|
|
595
|
-
aria-disabled={props.opt.disabled ? 'true' : undefined}
|
|
596
|
-
aria-describedby={props.hasDesc ? props.descId : undefined}
|
|
597
|
-
data-option-id={props.opt.id}
|
|
598
|
-
tabindex={props.opt.disabled ? -1 : props.tabStop ? 0 : -1}
|
|
599
|
-
onClick={() => !props.opt.disabled && props.onPick()}
|
|
600
|
-
onFocus={props.onFocus}
|
|
650
|
+
<label
|
|
651
|
+
onClick={pick}
|
|
601
652
|
class={cn(
|
|
602
|
-
'flex items-center gap-3 px-3 py-2.5 text-sm
|
|
653
|
+
'flex items-center gap-3 px-3 py-2.5 text-sm transition-colors',
|
|
654
|
+
// The focus ring stays on the ROW and stays inset — the same 2px inset ring the
|
|
655
|
+
// focusable `div` painted. Focus now lands on the input inside it, so the
|
|
656
|
+
// selector moves from `focus-visible:` to `has-[:focus-visible]:`; the input's
|
|
657
|
+
// own outline is suppressed below so there is one ring, not two. That is the
|
|
658
|
+
// pattern src/elements/styles.css sanctions at its `:focus-visible` rule.
|
|
659
|
+
'has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-inset has-[:focus-visible]:ring-ring',
|
|
603
660
|
props.opt.disabled
|
|
604
661
|
? 'cursor-not-allowed opacity-60 text-foreground'
|
|
605
662
|
: 'cursor-pointer hover:bg-muted/50',
|
|
606
663
|
props.checked ? 'bg-accent font-medium text-accent-foreground' : 'text-foreground',
|
|
607
664
|
)}
|
|
608
665
|
>
|
|
666
|
+
{/* FIRST in the row, ahead of any media. It used to sit AFTER the thumbnail/icon
|
|
667
|
+
badge, so a list where only some options carried media had a ragged control
|
|
668
|
+
column (measured 98px vs 50px in the `WithOther` story). The control column is
|
|
669
|
+
the one thing every row shares, so it leads. */}
|
|
670
|
+
<Radio
|
|
671
|
+
role="radio"
|
|
672
|
+
name={props.name}
|
|
673
|
+
value={props.opt.id}
|
|
674
|
+
checked={props.checked}
|
|
675
|
+
disabled={inert()}
|
|
676
|
+
aria-checked={props.checked}
|
|
677
|
+
aria-disabled={props.opt.disabled ? 'true' : undefined}
|
|
678
|
+
aria-describedby={props.hasDesc ? props.descId : undefined}
|
|
679
|
+
data-option-id={props.opt.id}
|
|
680
|
+
tabindex={props.opt.disabled ? -1 : props.tabStop ? 0 : -1}
|
|
681
|
+
class="focus-visible:outline-none"
|
|
682
|
+
onChange={pick}
|
|
683
|
+
onFocus={props.onFocus}
|
|
684
|
+
/>
|
|
609
685
|
<Show when={props.opt.media?.image}>
|
|
610
686
|
<HoverCard
|
|
611
687
|
openDelay={150}
|
|
@@ -623,17 +699,6 @@ function ListRow(props: RowProps): JSX.Element {
|
|
|
623
699
|
<Show when={!props.opt.media?.image && props.opt.media?.icon}>
|
|
624
700
|
<IconBadge name={props.opt.media!.icon!} />
|
|
625
701
|
</Show>
|
|
626
|
-
<span
|
|
627
|
-
aria-hidden="true"
|
|
628
|
-
class={cn(
|
|
629
|
-
'flex h-[1.125rem] w-[1.125rem] shrink-0 items-center justify-center rounded-full border-[1.5px] transition-colors',
|
|
630
|
-
props.checked ? 'border-[var(--color-primary)]' : 'border-input bg-background',
|
|
631
|
-
)}
|
|
632
|
-
>
|
|
633
|
-
<Show when={props.checked}>
|
|
634
|
-
<span class="h-2 w-2 rounded-full bg-[var(--color-primary)]" />
|
|
635
|
-
</Show>
|
|
636
|
-
</span>
|
|
637
702
|
<span class="flex min-w-0 flex-col gap-0.5">
|
|
638
703
|
<span class="flex items-center gap-2">
|
|
639
704
|
<span class="truncate">{props.opt.label}</span>
|
|
@@ -652,7 +717,7 @@ function ListRow(props: RowProps): JSX.Element {
|
|
|
652
717
|
{props.opt.meta}
|
|
653
718
|
</span>
|
|
654
719
|
</Show>
|
|
655
|
-
</
|
|
720
|
+
</label>
|
|
656
721
|
);
|
|
657
722
|
}
|
|
658
723
|
|
|
@@ -663,7 +728,7 @@ function ResolvedChoice(props: {
|
|
|
663
728
|
const c = props.choice;
|
|
664
729
|
return (
|
|
665
730
|
<div
|
|
666
|
-
class="flex items-center gap-3 rounded-lg border border-
|
|
731
|
+
class="flex items-center gap-3 rounded-lg border border-border bg-accent px-3 py-2.5 text-sm font-medium text-accent-foreground"
|
|
667
732
|
role={props.optimistic ? 'status' : undefined}
|
|
668
733
|
>
|
|
669
734
|
<Check size={16} aria-hidden="true" />
|
|
@@ -228,7 +228,7 @@ export function Coachmark(props: CoachmarkProps) {
|
|
|
228
228
|
<Show when={props.badge}>
|
|
229
229
|
<span
|
|
230
230
|
part="badge"
|
|
231
|
-
class={cn('inline-flex items-center rounded-full px-1.5 py-0.5 text-
|
|
231
|
+
class={cn('inline-flex items-center rounded-full px-1.5 py-0.5 text-micro font-medium uppercase leading-none tracking-wide', BADGE_CLS)}
|
|
232
232
|
>
|
|
233
233
|
{props.badge}
|
|
234
234
|
</span>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type JSX, splitProps, createResource, Show } from 'solid-js';
|
|
1
|
+
import { type JSX, splitProps, createResource, createSignal, onCleanup, Show } from 'solid-js';
|
|
2
|
+
import { Copy, Check } from 'lucide-solid';
|
|
2
3
|
import { cn } from '../utils/cn';
|
|
3
4
|
import { useChatConfig } from '../primitives/chat-config';
|
|
4
5
|
import { highlight, isCodeHighlightingEnabled } from '../primitives/highlighter';
|
|
@@ -7,10 +8,69 @@ import { highlight, isCodeHighlightingEnabled } from '../primitives/highlighter'
|
|
|
7
8
|
|
|
8
9
|
export interface CodeBlockProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
9
10
|
children?: JSX.Element;
|
|
11
|
+
/**
|
|
12
|
+
* Render a copy button in a header row above the code.
|
|
13
|
+
*
|
|
14
|
+
* DEFAULT OFF, and deliberately. This component is shared by three surfaces —
|
|
15
|
+
* `markdown.tsx` (every fenced block in every assistant message), `artifact.tsx`
|
|
16
|
+
* (the code panel), and the `<kai-code-block>` facade — and only the ELEMENT is
|
|
17
|
+
* documented as shipping a copy button. Defaulting on would add one to every code
|
|
18
|
+
* block in every message, a kit-wide visible change. The facade opts in; the other
|
|
19
|
+
* two are untouched, which `tests/elements/code-block.test.tsx` pins both
|
|
20
|
+
* behaviourally and at the call sites.
|
|
21
|
+
*/
|
|
22
|
+
copy?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The exact text the copy button puts on the clipboard.
|
|
25
|
+
*
|
|
26
|
+
* Named for what it DOES rather than what it holds, because the distinction is the
|
|
27
|
+
* whole contract: by the time the button is clicked the source has been through
|
|
28
|
+
* Shiki, so anything read back out of the DOM is either `<span>`-laden HTML or a
|
|
29
|
+
* `textContent` reconstruction — and the reconstruction is where tabs, blank lines
|
|
30
|
+
* and trailing newlines quietly change. This prop never goes near the highlighter.
|
|
31
|
+
*/
|
|
32
|
+
copyText?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** How long the button stays in its acknowledged state. Matches MessageCopyButton. */
|
|
36
|
+
const COPIED_MS = 2000;
|
|
37
|
+
|
|
38
|
+
function CodeBlockCopyButton(props: { text: string }) {
|
|
39
|
+
const [copied, setCopied] = createSignal(false);
|
|
40
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
41
|
+
// A click just before unmount would otherwise set state on a disposed owner.
|
|
42
|
+
onCleanup(() => clearTimeout(timer));
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
part="copy"
|
|
48
|
+
// The name carries the state, so a screen-reader user gets the same
|
|
49
|
+
// acknowledgement the icon swap gives a sighted one. The glyph is decorative.
|
|
50
|
+
aria-label={copied() ? 'Copied' : 'Copy code'}
|
|
51
|
+
class={cn(
|
|
52
|
+
'inline-flex items-center justify-center rounded-md p-1.5',
|
|
53
|
+
'text-muted-foreground transition-colors hover:bg-muted hover:text-foreground',
|
|
54
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
55
|
+
)}
|
|
56
|
+
onClick={() => {
|
|
57
|
+
// Same mechanism as MessageCopyButton. A rejected promise (no permission,
|
|
58
|
+
// insecure context) must not become an unhandled rejection.
|
|
59
|
+
void Promise.resolve(navigator.clipboard?.writeText(props.text)).catch(() => {});
|
|
60
|
+
setCopied(true);
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
timer = setTimeout(() => setCopied(false), COPIED_MS);
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<Show when={copied()} fallback={<Copy size={14} aria-hidden="true" />}>
|
|
66
|
+
<Check size={14} aria-hidden="true" class="text-emerald-500" />
|
|
67
|
+
</Show>
|
|
68
|
+
</button>
|
|
69
|
+
);
|
|
10
70
|
}
|
|
11
71
|
|
|
12
72
|
function CodeBlock(props: CodeBlockProps) {
|
|
13
|
-
const [local, rest] = splitProps(props, ['children', 'class']);
|
|
73
|
+
const [local, rest] = splitProps(props, ['children', 'class', 'copy', 'copyText']);
|
|
14
74
|
return (
|
|
15
75
|
<div
|
|
16
76
|
class={cn(
|
|
@@ -23,6 +83,15 @@ function CodeBlock(props: CodeBlockProps) {
|
|
|
23
83
|
)}
|
|
24
84
|
{...rest}
|
|
25
85
|
>
|
|
86
|
+
<Show when={local.copy}>
|
|
87
|
+
{/* A header row, NOT an overlay on the code: the code region scrolls
|
|
88
|
+
horizontally, so a button inside it would ride off-screen with the code.
|
|
89
|
+
Always visible — hover-only affordances fail touch outright and are
|
|
90
|
+
undiscoverable everywhere else. */}
|
|
91
|
+
<CodeBlockGroup class="justify-end border-b border-border px-2 py-1">
|
|
92
|
+
<CodeBlockCopyButton text={local.copyText ?? ''} />
|
|
93
|
+
</CodeBlockGroup>
|
|
94
|
+
</Show>
|
|
26
95
|
{local.children}
|
|
27
96
|
</div>
|
|
28
97
|
);
|
|
@@ -53,8 +122,8 @@ function CodeBlockCode(props: CodeBlockCodeProps) {
|
|
|
53
122
|
|
|
54
123
|
const codeTextSize = () => {
|
|
55
124
|
switch (config.proseSize()) {
|
|
56
|
-
case 'xs': return 'text-
|
|
57
|
-
case 'sm': return 'text-
|
|
125
|
+
case 'xs': return 'text-caption';
|
|
126
|
+
case 'sm': return 'text-compact';
|
|
58
127
|
case 'base': return 'text-sm';
|
|
59
128
|
case 'lg': return 'text-base';
|
|
60
129
|
}
|
|
@@ -62,7 +131,11 @@ function CodeBlockCode(props: CodeBlockCodeProps) {
|
|
|
62
131
|
|
|
63
132
|
const classNames = () =>
|
|
64
133
|
cn(
|
|
65
|
-
|
|
134
|
+
// `kai-focus-inset`: this region fills a rounded `overflow: clip` wrapper,
|
|
135
|
+
// so the default focus outline (offset 2px, i.e. entirely outside the
|
|
136
|
+
// border box) was clipped away and keyboard users saw nothing. The inset
|
|
137
|
+
// ring is drawn inside the box, where the wrapper cannot erase it.
|
|
138
|
+
'w-full overflow-x-auto kai-focus-inset [&>pre]:px-4 [&>pre]:py-4',
|
|
66
139
|
codeTextSize(),
|
|
67
140
|
local.class
|
|
68
141
|
);
|
|
@@ -102,4 +175,7 @@ function CodeBlockGroup(props: CodeBlockGroupProps) {
|
|
|
102
175
|
);
|
|
103
176
|
}
|
|
104
177
|
|
|
178
|
+
// CodeBlockCopyButton is deliberately NOT exported: `src/index.ts` re-exports this
|
|
179
|
+
// module's public trio, and adding a fourth symbol here that index.ts does not carry
|
|
180
|
+
// would create a half-public component. It is an implementation detail of `copy`.
|
|
105
181
|
export { CodeBlock, CodeBlockCode, CodeBlockGroup };
|