@kitn.ai/ui 0.25.2 → 0.26.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 +2 -2
- package/dist/components/attachment-types.d.ts +46 -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/chat-thread.d.ts +3 -41
- package/dist/components/code-block.d.ts +22 -0
- package/dist/components/conversation-item.d.ts +54 -1
- package/dist/components/conversation-list.d.ts +65 -0
- package/dist/components/form.d.ts +42 -0
- package/dist/components/message.d.ts +7 -0
- package/dist/components/toast.d.ts +2 -1
- 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/{create-tween-B5Y4Im8G.js → create-tween-BFT6c-Y8.js} +1 -1
- package/dist/{create-tween-4z1XYLkR.js → create-tween-CJNlzoaO.js} +1 -1
- package/dist/{create-tween-CUzOlaMS.js → create-tween-CQED4T0g.js} +1 -1
- package/dist/custom-elements.json +2931 -2572
- 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/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/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/chunks/{Icon-b5A8hlHs.js → Icon--Y8vsFal.js} +1 -1
- package/dist/elements/chunks/action-icons-D9u3OzYW.js +1 -0
- package/dist/elements/chunks/arrow-left-CVh1TeN7.js +1 -0
- package/dist/elements/chunks/{artifact-BSuPYFHY.js → artifact-BkYcfIlZ.js} +1 -1
- package/dist/elements/chunks/attachments-DwfNlubq.js +1 -0
- package/dist/elements/chunks/audio-visualizer-D7lB6mvR.js +1 -0
- package/dist/elements/chunks/{badge-TIMiJFKc.js → badge-Q0-Cmwzz.js} +1 -1
- package/dist/elements/chunks/{button-B9okqG73.js → button-flUbeufF.js} +1 -1
- package/dist/elements/chunks/{card-renderer-CSoTL_e1.js → card-renderer-3Dyl-FpN.js} +1 -1
- package/dist/elements/chunks/check-B5kBPKeo.js +1 -0
- package/dist/elements/chunks/chevron-down-DZ1GS39h.js +1 -0
- package/dist/elements/chunks/chevron-right-CvP7OUd2.js +1 -0
- package/dist/elements/chunks/{choice-card-CGMc3kek.js → choice-card-D4QUIOWZ.js} +1 -1
- package/dist/elements/chunks/circle-CDf9g6MQ.js +1 -0
- package/dist/elements/chunks/circle-check-CA47Nqbz.js +1 -0
- package/dist/elements/chunks/{circle-x-mhH7pOK2.js → circle-x-CJ_HVbC8.js} +1 -1
- package/dist/elements/chunks/code-block-BdLiTitd.js +1 -0
- package/dist/elements/chunks/collapsible-BzbXpPzr.js +1 -0
- package/dist/elements/chunks/{composer-CZZmtYOT.js → composer-Byve-n8K.js} +2 -2
- package/dist/elements/chunks/{confirm-card-BWjf2-xo.js → confirm-card-B0flE3MN.js} +1 -1
- package/dist/elements/chunks/context-VLIL3sJ-.js +1 -0
- package/dist/elements/chunks/controllable-DTwTIlwp.js +1 -0
- package/dist/elements/chunks/conversation-item-BDztezOG.js +1 -0
- package/dist/elements/chunks/copy-Nf-kqxXH.js +1 -0
- package/dist/elements/chunks/{create-tween-CUrM6D_o.js → create-tween-c4Z8TDYz.js} +2 -2
- package/dist/elements/chunks/default-input-C2iLFq0b.js +1 -0
- package/dist/elements/chunks/define-wjHR1MkU.js +1 -0
- package/dist/elements/chunks/disclosure-CcNbyLy0.js +1 -0
- package/dist/elements/chunks/{download-DnCyWFJE.js → download-C4V7tBdb.js} +1 -1
- package/dist/elements/chunks/dropdown-BgWTthUx.js +1 -0
- package/dist/elements/chunks/{ellipsis-CVxqfsDZ.js → ellipsis-7gU33mL2.js} +1 -1
- package/dist/elements/chunks/embed-D3KewsIJ.js +1 -0
- package/dist/elements/chunks/{external-link-DG2sRdQL.js → external-link-CSOEsNlx.js} +1 -1
- package/dist/elements/chunks/{file-text-D0J9F6M7.js → file-text-D1G8khle.js} +1 -1
- package/dist/elements/chunks/{file-tree-yGWNK0Js.js → file-tree-zvB2i7P9.js} +1 -1
- package/dist/elements/chunks/{folder-NV1pi_ud.js → folder-WW2LGLid.js} +1 -1
- package/dist/elements/chunks/form-CQirdC4W.js +1 -0
- package/dist/elements/chunks/hover-card-CJJkfHTK.js +1 -0
- package/dist/elements/chunks/icon-Cfgmf4fa.js +1 -0
- package/dist/elements/chunks/{info-C8r5Q6cc.js → info-DX3YMOt6.js} +1 -1
- package/dist/elements/chunks/input-BtW9U6Sq.js +1 -0
- package/dist/elements/chunks/{kbd-OGJDUT-O.js → kbd-LGXwuT_V.js} +1 -1
- package/dist/elements/chunks/{link-BdsfLB7b.js → link-2My69BqT.js} +1 -1
- package/dist/elements/chunks/{link-preview-Cf5KOP-i.js → link-preview-OQ9IEq0f.js} +1 -1
- package/dist/elements/chunks/{loader-B5QzK_8_.js → loader-iOxc_Tu-.js} +1 -1
- package/dist/elements/chunks/{markdown-DbozyYqa.js → markdown-DgBFaGAz.js} +1 -1
- package/dist/elements/chunks/{message-DtXKPqiR.js → message-VMZohrXx.js} +1 -1
- package/dist/elements/chunks/message-circle-CpXMM-_n.js +1 -0
- package/dist/elements/chunks/message-square-DxcWtETQ.js +1 -0
- package/dist/elements/chunks/message-tZmV2A3p.js +1 -0
- package/dist/elements/chunks/{minimize-2-SegJ_oku.js → minimize-2-DPIUKTrT.js} +1 -1
- package/dist/elements/chunks/{model-switcher-D7JrFFHp.js → model-switcher-DsT8tFB0.js} +1 -1
- package/dist/elements/chunks/{overlay-CV7z2YuX.js → overlay-DuiwdXjm.js} +1 -1
- package/dist/elements/chunks/{paperclip-Bg0ITU3b.js → paperclip-CRZVp4z_.js} +1 -1
- package/dist/elements/chunks/{progress-bar-D1_rzt6N.js → progress-bar-DNy66e_E.js} +1 -1
- package/dist/elements/chunks/{prompt-suggestion-D5Sm8xF1.js → prompt-suggestion-Bn64Nl7L.js} +1 -1
- package/dist/elements/chunks/reasoning-Emn9N4-T.js +1 -0
- package/dist/elements/chunks/{resizable-Dpzjlvp4.js → resizable-D181b4fM.js} +1 -1
- package/dist/elements/chunks/{rotate-cw-DzVDJOA1.js → rotate-cw-CPWe37D9.js} +1 -1
- package/dist/elements/chunks/{scroll-area-93A1bbeG.js → scroll-area-ClZkZxIk.js} +1 -1
- package/dist/elements/chunks/scroll-button-DPE-t3x_.js +1 -0
- package/dist/elements/chunks/{separator-DH2r_ZSZ.js → separator-CeJHQqLW.js} +1 -1
- package/dist/elements/chunks/{settings-BFMOd6yB.js → settings-CqaHgJF9.js} +1 -1
- package/dist/elements/chunks/{settings-group-B3VZKIcz.js → settings-group-CjqjLCOn.js} +1 -1
- package/dist/elements/chunks/share-CyfnNEqp.js +1 -0
- package/dist/elements/chunks/{skeleton-N8BuWYmf.js → skeleton-D8akBnKS.js} +1 -1
- package/dist/elements/chunks/slots-DaNjX0rc.js +1 -0
- package/dist/elements/chunks/{source-w9V0NnBn.js → source-UWCvmVak.js} +1 -1
- package/dist/elements/chunks/{star-DHvACxtm.js → star-DoHFAikQ.js} +1 -1
- package/dist/elements/chunks/store-Cjq0qwYN.js +1 -0
- package/dist/elements/chunks/{tasks-card-lPxoodlA.js → tasks-card-CmZ5xh6v.js} +1 -1
- package/dist/elements/chunks/{text-shimmer-BY_rI3s5.js → text-shimmer-DYn09zrz.js} +1 -1
- package/dist/elements/chunks/textarea-skiGHURM.js +1 -0
- package/dist/elements/chunks/{thumbs-up-DQe0hvkZ.js → thumbs-up-DX2Ijk3H.js} +1 -1
- package/dist/elements/chunks/{toast-store-VUZZBhzH.js → toast-store-CFnQpThX.js} +1 -1
- package/dist/elements/chunks/{tool-C1YtxwPu.js → tool-DKQ2D1DP.js} +1 -1
- package/dist/elements/chunks/{tooltip-BqwnFWhn.js → tooltip-B0G99y8w.js} +1 -1
- package/dist/elements/chunks/{triangle-alert-B6or7F5u.js → triangle-alert-F9cyUEyy.js} +1 -1
- package/dist/elements/chunks/{use-card-resolution-DJg32jGH.js → use-card-resolution-DoA2f_EU.js} +1 -1
- package/dist/elements/chunks/{variant-aurora-QXTZPyST.js → variant-aurora-qt6Zct8M.js} +2 -2
- package/dist/elements/chunks/variant-custom-tJp22_BQ.js +1 -0
- package/dist/{variant-wave-BwzrHD1s.js → elements/chunks/variant-wave-CaQ5ti39.js} +2 -2
- package/dist/elements/chunks/x-B6UnOiLw.js +1 -0
- 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.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 +149 -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-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-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/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/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/slots.d.ts +35 -3
- 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 +232 -148
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5456 -4931
- package/dist/index.server.js +4142 -3669
- package/dist/kai-provider.es.js +40 -40
- package/dist/kai.es.js +1 -1
- package/dist/llms/llms-full.txt +184 -69
- package/dist/llms/llms.txt +3 -3
- package/dist/mcp.es.js +2669 -546
- 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/react/index.d.ts +115 -266
- package/dist/react.js +247 -221
- package/dist/register-impl-DpE7icIb.js +293 -0
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/tool-defs.d.ts +35 -0
- package/dist/schemas.js +403 -310
- package/dist/{solid-BMuDmFo_.js → solid-BtdqNZI7.js} +8435 -7394
- package/dist/{solid-CGYNdLRR.js → solid-CnavwBye.js} +5987 -5035
- package/dist/solid.d.ts +5 -0
- package/dist/solid.js +210 -203
- package/dist/solid.server.js +210 -203
- package/dist/state/index.d.ts +4 -0
- 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 +269 -169
- package/dist/types.d.ts +4 -2
- package/dist/ui/dock.d.ts +109 -0
- package/dist/ui/hover-card.d.ts +44 -0
- package/dist/{variant-aurora-DwSOfraQ.js → variant-aurora-B8II3-I1.js} +27 -27
- package/dist/{variant-aurora-wKvAQ5wp.js → variant-aurora-CMNWOSJR.js} +28 -28
- package/dist/{variant-aurora-XEkPT0_z.js → variant-aurora-CcuDMWwQ.js} +2 -2
- package/dist/{variant-custom-eFIg2XWQ.js → variant-custom-BdvYC7af.js} +40 -40
- package/dist/{variant-custom-BhS9wIpU.js → variant-custom-C714mqKl.js} +65 -65
- package/dist/variant-custom-L16JLLZy.js +1 -0
- package/dist/{elements/chunks/variant-wave-DDCwhLas.js → variant-wave-2S4La2Ka.js} +2 -2
- package/dist/{variant-wave-CbVsmgwT.js → variant-wave-D7ruiA1W.js} +25 -25
- package/dist/{variant-wave-CUWGx9Bn.js → variant-wave-ecwykeyL.js} +21 -21
- 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/frameworks/react/index.tsx +132 -93
- package/llms-full.txt +184 -69
- package/llms.txt +3 -3
- package/package.json +26 -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 +275 -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 +61 -5
- package/src/agent-tooling/mcp/tools/reference.ts +440 -4
- 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/registry.ts +19 -7
- package/src/agent-tooling/route-emit.ts +26 -2
- package/src/components/attachment-types.ts +47 -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/chat-container.tsx +4 -1
- package/src/components/chat-thread.tsx +36 -7
- package/src/components/code-block.tsx +79 -3
- package/src/components/composer.tsx +8 -1
- package/src/components/conversation-item.tsx +114 -4
- package/src/components/conversation-list.tsx +212 -12
- package/src/components/embed.tsx +4 -0
- package/src/components/form.tsx +41 -14
- package/src/components/message.tsx +75 -5
- package/src/components/response-stream.tsx +13 -7
- package/src/components/screen.tsx +5 -0
- package/src/components/scroll-button.tsx +7 -0
- package/src/components/toast.tsx +11 -5
- 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 +6 -7
- package/src/elements/audio-visualizer.tsx +48 -4
- package/src/elements/chat-workspace.tsx +159 -287
- package/src/elements/chat.tsx +12 -9
- 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/conversation-item.tsx +80 -0
- package/src/elements/conversation-list.tsx +60 -11
- package/src/elements/default-input.tsx +5 -5
- 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 +204 -0
- package/src/elements/dropdown.tsx +123 -0
- package/src/elements/element-diagnostics.ts +392 -0
- package/src/elements/element-manifest.json +12 -0
- package/src/elements/element-meta.json +672 -439
- package/src/elements/element-nonscalar.json +170 -0
- package/src/elements/element-types.d.ts +232 -148
- package/src/elements/menu.tsx +13 -0
- package/src/elements/prompt-dock.tsx +2 -1
- package/src/elements/prompt-input.tsx +8 -8
- package/src/elements/register-impl.ts +46 -0
- package/src/elements/register.ts +28 -0
- package/src/elements/resizable.tsx +52 -25
- package/src/elements/scroll-button.tsx +5 -0
- package/src/elements/slots.ts +107 -12
- package/src/elements/styles.css +30 -6
- package/src/elements/thinking-bar.tsx +16 -7
- package/src/elements/toast.tsx +3 -0
- 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 +2 -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 +297 -6
- package/src/solid.ts +5 -0
- package/src/state/index.ts +12 -0
- 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/dialog.tsx +6 -0
- package/src/ui/dock.tsx +445 -0
- package/src/ui/dropdown.tsx +67 -23
- package/src/ui/hover-card.tsx +156 -6
- package/src/ui/input.tsx +29 -10
- package/src/ui/resizable.tsx +11 -0
- 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/dist/elements/chunks/action-icons-DW9muWrY.js +0 -1
- package/dist/elements/chunks/arrow-left-iJVnQmxA.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/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/circle-BG7VkPep.js +0 -1
- package/dist/elements/chunks/circle-check-GqvO2UBs.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/context-hKjNqiyQ.js +0 -1
- package/dist/elements/chunks/conversation-list-CsGJjSKG.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/dropdown-CmNna5lT.js +0 -1
- package/dist/elements/chunks/embed-CjAD2jSi.js +0 -1
- package/dist/elements/chunks/form-Cp8Y00Wn.js +0 -1
- package/dist/elements/chunks/hover-card-6KtXpLwT.js +0 -1
- package/dist/elements/chunks/icon-97R8SR2p.js +0 -1
- package/dist/elements/chunks/input-DrseEjhp.js +0 -1
- package/dist/elements/chunks/message-CE4k-qaj.js +0 -1
- package/dist/elements/chunks/message-square-C9ovmtOo.js +0 -1
- package/dist/elements/chunks/reasoning-BrxQfpfI.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/store-HIPGhNz9.js +0 -1
- package/dist/elements/chunks/textarea-DPfuhswM.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/x-BOhjBeOd.js +0 -1
- package/dist/primitives/card-validate-generator.testlib.d.ts +0 -20
- package/dist/register-impl-DGmGzrEM.js +0 -145
- package/dist/variant-custom-MkTNTycd.js +0 -1
- package/src/primitives/card-validate-generator.testlib.ts +0 -38
|
@@ -30,6 +30,160 @@ export function CollapsedRail(props: CollapsedRailProps) {
|
|
|
30
30
|
);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Read a conversation item's identity the way the container does: the
|
|
34
|
+
* `conversationId` property, else the `conversation-id` attribute, else the
|
|
35
|
+
* host `id`. The property is what a framework loop sets; the attributes are
|
|
36
|
+
* the plain-HTML spellings (host `id` matches the `<kai-conversation>`
|
|
37
|
+
* data-carrier precedent). */
|
|
38
|
+
export function readConversationItemId(el: Element): string {
|
|
39
|
+
const prop = (el as Element & { conversationId?: unknown }).conversationId;
|
|
40
|
+
if (typeof prop === 'string' && prop) return prop;
|
|
41
|
+
return el.getAttribute('conversation-id') ?? el.id;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ConversationItemsControllerOptions {
|
|
45
|
+
/** The current slotted item hosts, in DOM order. */
|
|
46
|
+
getItems: () => HTMLElement[];
|
|
47
|
+
/** The container's active conversation id. */
|
|
48
|
+
getActiveId: () => string | undefined;
|
|
49
|
+
/** Item activation (click / Enter / Space). Surfaces as
|
|
50
|
+
* `kai-conversation-select` on the element. */
|
|
51
|
+
onSelect: (id: string) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ConversationItemsController {
|
|
55
|
+
/** Re-derive the ARIA/roving-tabindex bookkeeping over the current items.
|
|
56
|
+
* Call on slotchange / child mutation and whenever `activeId` changes. */
|
|
57
|
+
sync(): void;
|
|
58
|
+
/** The container's items-region click handler (composed-path aware). */
|
|
59
|
+
handleClick(e: MouseEvent): void;
|
|
60
|
+
/** The container's items-region keydown handler (arrows, Home/End,
|
|
61
|
+
* Enter/Space). */
|
|
62
|
+
handleKeyDown(e: KeyboardEvent): void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The parent-item contract of item mode (spec 2026-08-20 § 2a), as a pure-DOM
|
|
67
|
+
* controller so it is host-agnostic: the `kai-conversations` facade wires it over
|
|
68
|
+
* its slotted `kai-conversation-item` children, and the jsdom contract tests
|
|
69
|
+
* drive it over plain nodes. Solid context cannot cross the element boundary
|
|
70
|
+
* (each facade is its own Solid root), so the channel is DOM traversal by
|
|
71
|
+
* construction:
|
|
72
|
+
*
|
|
73
|
+
* - selection flows container to item — exactly one item's BODY node (the
|
|
74
|
+
* shadow body of a `kai-conversation-item`, else the node itself; see
|
|
75
|
+
* `bodyOf`) is `aria-current="true"`, plus the `active` property on the
|
|
76
|
+
* host for the item's own styling hook;
|
|
77
|
+
* - `role="button"` is ensured on each item's body node (an authored role is
|
|
78
|
+
* left alone);
|
|
79
|
+
* - roving tabindex — exactly one body node `tabindex="0"` (the active
|
|
80
|
+
* item's, else the first's), the rest `-1`, re-derived on every `sync()`;
|
|
81
|
+
* menu content keeps its natural tab order (it is the body's SIBLING, per
|
|
82
|
+
* the ratified 2026-08-20 sibling restructure);
|
|
83
|
+
* - activation (click / Enter / Space) calls `onSelect` with the item's id, and
|
|
84
|
+
* is SUPPRESSED when the composed path crosses the item's `menu` region
|
|
85
|
+
* (light-DOM `slot="menu"` content or the shadow `data-kai-item-menu`
|
|
86
|
+
* wrapper), so the consumer's own popover never also selects the row;
|
|
87
|
+
* - ArrowUp/ArrowDown/Home/End move focus item-to-item, tabindex following the
|
|
88
|
+
* focused item.
|
|
89
|
+
*/
|
|
90
|
+
export function createConversationItemsController(
|
|
91
|
+
opts: ConversationItemsControllerOptions,
|
|
92
|
+
): ConversationItemsController {
|
|
93
|
+
const itemFromEvent = (e: Event): HTMLElement | undefined => {
|
|
94
|
+
const items = opts.getItems();
|
|
95
|
+
return e.composedPath().find((n): n is HTMLElement => items.includes(n as HTMLElement));
|
|
96
|
+
};
|
|
97
|
+
/** The item's ACTIVATION node — the target of role/aria-current/tabindex/
|
|
98
|
+
* focus. For a `kai-conversation-item` host that is its shadow body (the
|
|
99
|
+
* sibling restructure, ratified 2026-08-20: the host is the row listitem
|
|
100
|
+
* wrapping the body AND the consumer's tabbable menu, so the control
|
|
101
|
+
* semantics must sit below it). A bare node with no such body is its own
|
|
102
|
+
* control. */
|
|
103
|
+
const bodyOf = (item: HTMLElement): HTMLElement =>
|
|
104
|
+
(item.shadowRoot?.querySelector('[data-kai-item-body]') as HTMLElement | null) ?? item;
|
|
105
|
+
const menuInPath = (e: Event): boolean =>
|
|
106
|
+
e.composedPath().some(
|
|
107
|
+
(n) =>
|
|
108
|
+
n instanceof Element &&
|
|
109
|
+
(n.hasAttribute('data-kai-item-menu') || n.getAttribute('slot') === 'menu'),
|
|
110
|
+
);
|
|
111
|
+
// Write-on-change only. `setAttribute` records a mutation even when the value
|
|
112
|
+
// is identical, and the facade re-syncs from a MutationObserver over these very
|
|
113
|
+
// nodes — unconditional writes would feed the observer forever.
|
|
114
|
+
const setAttr = (el: Element, name: string, value: string) => {
|
|
115
|
+
if (el.getAttribute(name) !== value) el.setAttribute(name, value);
|
|
116
|
+
};
|
|
117
|
+
/** An item whose shadow body has not RENDERED yet must not be stamped: the
|
|
118
|
+
* fallback would write control semantics onto the HOST, they would stick
|
|
119
|
+
* (the item facade defers to an authored role), and axe then sees exactly
|
|
120
|
+
* the role="button"-host-with-focusable-menu shape the restructure removed.
|
|
121
|
+
* Measured in the focus-order probe: the container's first sync can run
|
|
122
|
+
* before the item elements upgrade. A later sync catches them — the
|
|
123
|
+
* facade's own mount mutates host attributes, which re-runs sync through
|
|
124
|
+
* the container's MutationObserver read(). Bare nodes (no dash: the jsdom
|
|
125
|
+
* stand-ins) are always ready. */
|
|
126
|
+
const readyBodies = (items: HTMLElement[]) => {
|
|
127
|
+
const out = new Map<HTMLElement, HTMLElement>();
|
|
128
|
+
for (const item of items) {
|
|
129
|
+
const body = bodyOf(item);
|
|
130
|
+
if (body === item && item.localName.includes('-')) continue;
|
|
131
|
+
out.set(item, body);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const rove = (items: HTMLElement[], target: HTMLElement | undefined) => {
|
|
137
|
+
for (const [item, body] of readyBodies(items)) setAttr(body, 'tabindex', item === target ? '0' : '-1');
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const sync = () => {
|
|
141
|
+
const items = opts.getItems();
|
|
142
|
+
const activeId = opts.getActiveId();
|
|
143
|
+
const bodies = readyBodies(items);
|
|
144
|
+
let anchor: HTMLElement | undefined;
|
|
145
|
+
for (const [item, body] of bodies) {
|
|
146
|
+
const isActive = activeId !== undefined && readConversationItemId(item) === activeId;
|
|
147
|
+
if (!body.hasAttribute('role')) body.setAttribute('role', 'button');
|
|
148
|
+
setAttr(body, 'aria-current', isActive ? 'true' : 'false');
|
|
149
|
+
const host = item as HTMLElement & { active?: boolean };
|
|
150
|
+
if (host.active !== isActive) host.active = isActive;
|
|
151
|
+
if (isActive) anchor = item;
|
|
152
|
+
}
|
|
153
|
+
rove(items, anchor ?? (bodies.keys().next().value as HTMLElement | undefined));
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
sync,
|
|
158
|
+
handleClick(e) {
|
|
159
|
+
const item = itemFromEvent(e);
|
|
160
|
+
if (!item || menuInPath(e)) return;
|
|
161
|
+
opts.onSelect(readConversationItemId(item));
|
|
162
|
+
},
|
|
163
|
+
handleKeyDown(e) {
|
|
164
|
+
const items = opts.getItems();
|
|
165
|
+
if (items.length === 0) return;
|
|
166
|
+
const item = itemFromEvent(e);
|
|
167
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
168
|
+
if (!item || menuInPath(e)) return;
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
opts.onSelect(readConversationItemId(item));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
let next: HTMLElement | undefined;
|
|
174
|
+
const idx = item ? items.indexOf(item) : items.findIndex((i) => bodyOf(i).getAttribute('tabindex') === '0');
|
|
175
|
+
if (e.key === 'ArrowDown') next = items[Math.min(idx + 1, items.length - 1)];
|
|
176
|
+
else if (e.key === 'ArrowUp') next = items[Math.max(idx - 1, 0)];
|
|
177
|
+
else if (e.key === 'Home') next = items[0];
|
|
178
|
+
else if (e.key === 'End') next = items[items.length - 1];
|
|
179
|
+
if (!next) return;
|
|
180
|
+
e.preventDefault();
|
|
181
|
+
rove(items, next);
|
|
182
|
+
bodyOf(next).focus();
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
33
187
|
export interface ConversationListProps {
|
|
34
188
|
groups: ConversationGroup[];
|
|
35
189
|
conversations: ConversationSummary[];
|
|
@@ -51,6 +205,20 @@ export interface ConversationListProps {
|
|
|
51
205
|
/** Receive the imperative controller once mounted. The `kai-conversations`
|
|
52
206
|
* facade uses it to focus / clear the internal search input. */
|
|
53
207
|
controllerRef?: (controller: ConversationListController) => void;
|
|
208
|
+
/** Item mode (spec 2026-08-20 § 2a): the consumer's OWN rows, rendered inside
|
|
209
|
+
* a list region in place of the data rows. When set, the built-in search
|
|
210
|
+
* filter, grouping and empty/no-match states do not apply — the consumer's
|
|
211
|
+
* loop owns them — while the chrome (header, search box, new-chat, footer)
|
|
212
|
+
* still renders and `onSearchChange` still reports queries. The
|
|
213
|
+
* `kai-conversations` facade passes its default `<slot>` here when it detects
|
|
214
|
+
* `kai-conversation-item` children. */
|
|
215
|
+
items?: JSX.Element;
|
|
216
|
+
/** Keydown handler for the item-mode list region (the facade wires
|
|
217
|
+
* `createConversationItemsController.handleKeyDown`). */
|
|
218
|
+
itemsKeyDown?: (e: KeyboardEvent) => void;
|
|
219
|
+
/** Click handler for the item-mode list region (the facade wires
|
|
220
|
+
* `createConversationItemsController.handleClick`). */
|
|
221
|
+
itemsClick?: (e: MouseEvent) => void;
|
|
54
222
|
class?: string;
|
|
55
223
|
}
|
|
56
224
|
|
|
@@ -64,8 +232,10 @@ export interface ConversationListController {
|
|
|
64
232
|
}
|
|
65
233
|
|
|
66
234
|
export function ConversationList(props: ConversationListProps) {
|
|
67
|
-
const [local] = splitProps(props, ['groups', 'conversations', 'activeId', 'onSelect', 'onNewChat', 'onToggleSidebar', 'header', 'footer', 'empty', 'compact', 'onSearchChange', 'controllerRef', 'class']);
|
|
235
|
+
const [local] = splitProps(props, ['groups', 'conversations', 'activeId', 'onSelect', 'onNewChat', 'onToggleSidebar', 'header', 'footer', 'empty', 'compact', 'onSearchChange', 'controllerRef', 'items', 'itemsKeyDown', 'itemsClick', 'class']);
|
|
68
236
|
const [searchQuery, setSearchQuery] = createSignal('');
|
|
237
|
+
// Item mode: the consumer's own rows replace the data rendering wholesale.
|
|
238
|
+
const itemMode = createMemo(() => local.items != null);
|
|
69
239
|
const isEmpty = createMemo(() => local.conversations.length === 0);
|
|
70
240
|
// The search query is owned here; setQuery is the single mutation point so both
|
|
71
241
|
// typing and the imperative clearSearch() notify the facade (→ kai-search).
|
|
@@ -133,7 +303,7 @@ export function ConversationList(props: ConversationListProps) {
|
|
|
133
303
|
>
|
|
134
304
|
{local.header}
|
|
135
305
|
</Show>
|
|
136
|
-
<Show when={!isEmpty()}>
|
|
306
|
+
<Show when={itemMode() || !isEmpty()}>
|
|
137
307
|
<div class="px-3 pb-2">
|
|
138
308
|
<div class="flex items-center gap-2 rounded-md bg-surface-strong px-2.5 py-1.5">
|
|
139
309
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-muted-foreground"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
|
@@ -143,22 +313,51 @@ export function ConversationList(props: ConversationListProps) {
|
|
|
143
313
|
</div>
|
|
144
314
|
</div>
|
|
145
315
|
</Show>
|
|
316
|
+
{/* Item mode: the consumer's own rows in a list region. The container's
|
|
317
|
+
filter/grouping/empty states do not apply — the consumer's loop owns
|
|
318
|
+
them (spec § 2a batteries boundary). */}
|
|
319
|
+
<Show when={itemMode()}>
|
|
320
|
+
<ScrollArea class="flex-1 px-2">
|
|
321
|
+
<div
|
|
322
|
+
role="list"
|
|
323
|
+
aria-label="Conversations"
|
|
324
|
+
part="items"
|
|
325
|
+
class="space-y-0.5 py-1"
|
|
326
|
+
onKeyDown={(e) => local.itemsKeyDown?.(e)}
|
|
327
|
+
onClick={(e) => local.itemsClick?.(e)}
|
|
328
|
+
>
|
|
329
|
+
{local.items}
|
|
330
|
+
</div>
|
|
331
|
+
</ScrollArea>
|
|
332
|
+
</Show>
|
|
146
333
|
{/* list, or the empty state (replace) when there are no conversations. */}
|
|
147
334
|
<Show
|
|
148
|
-
when={!isEmpty()}
|
|
335
|
+
when={!itemMode() && !isEmpty()}
|
|
149
336
|
fallback={
|
|
150
|
-
<Show
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
337
|
+
<Show when={!itemMode()}>
|
|
338
|
+
<Show
|
|
339
|
+
when={local.empty}
|
|
340
|
+
fallback={
|
|
341
|
+
<div class="flex flex-1 flex-col items-center justify-center gap-1 p-6 text-center text-sm text-muted-foreground">
|
|
342
|
+
No conversations yet
|
|
343
|
+
</div>
|
|
344
|
+
}
|
|
345
|
+
>
|
|
346
|
+
{local.empty}
|
|
347
|
+
</Show>
|
|
159
348
|
</Show>
|
|
160
349
|
}
|
|
161
350
|
>
|
|
351
|
+
{/* F-04, decide loudly: a query matching nothing renders a VISIBLE
|
|
352
|
+
no-match state, keyed off the FILTERED count — distinct from the
|
|
353
|
+
zero-conversations empty state above, which keys off the unfiltered
|
|
354
|
+
list (and still owns the `empty` override). */}
|
|
355
|
+
<Show when={filteredConversations().length === 0}>
|
|
356
|
+
<div class="flex flex-1 flex-col items-center justify-center gap-1 p-6 text-center text-sm text-muted-foreground">
|
|
357
|
+
No conversations match your search
|
|
358
|
+
</div>
|
|
359
|
+
</Show>
|
|
360
|
+
<Show when={filteredConversations().length > 0}>
|
|
162
361
|
<ScrollArea class="flex-1 px-2">
|
|
163
362
|
<For each={local.groups}>
|
|
164
363
|
{(group) => {
|
|
@@ -183,6 +382,7 @@ export function ConversationList(props: ConversationListProps) {
|
|
|
183
382
|
</Show>
|
|
184
383
|
</Show>
|
|
185
384
|
</ScrollArea>
|
|
385
|
+
</Show>
|
|
186
386
|
</Show>
|
|
187
387
|
{/* footer (inject): a row below the list (account / settings / …). */}
|
|
188
388
|
<Show when={local.footer}>
|
package/src/components/embed.tsx
CHANGED
|
@@ -159,6 +159,10 @@ export function Embed(props: EmbedProps): JSX.Element {
|
|
|
159
159
|
</button>
|
|
160
160
|
}
|
|
161
161
|
>
|
|
162
|
+
{/* `outline-none` is DELIBERATE here and must stay: a `tabindex="-1"`
|
|
163
|
+
wrapper focused programmatically after the user activates the
|
|
164
|
+
player, so keyboard context lands on the embed. Not a tab stop —
|
|
165
|
+
the iframe inside it takes focus from there. */}
|
|
162
166
|
<div ref={(el) => (playerRegion = el)} tabindex="-1" class="absolute inset-0 outline-none">
|
|
163
167
|
<iframe
|
|
164
168
|
src={(resolved() as { value: ResolvedEmbed }).value.embedUrl}
|
package/src/components/form.tsx
CHANGED
|
@@ -748,19 +748,7 @@ function FieldRow(props: FieldRowProps): JSX.Element {
|
|
|
748
748
|
.filter(Boolean)
|
|
749
749
|
.join(' ') || undefined;
|
|
750
750
|
|
|
751
|
-
const common = (
|
|
752
|
-
id,
|
|
753
|
-
value: props.value(),
|
|
754
|
-
field: props.field,
|
|
755
|
-
disabled: props.disabled || props.field.readOnly === true,
|
|
756
|
-
placeholder: placeholder(),
|
|
757
|
-
required: props.required,
|
|
758
|
-
invalid: Boolean(props.error()),
|
|
759
|
-
describedBy: describedBy(),
|
|
760
|
-
label: label(),
|
|
761
|
-
onInput: props.onInput,
|
|
762
|
-
onBlur: props.onBlur,
|
|
763
|
-
});
|
|
751
|
+
const common = fieldCommon(props, id, placeholder, label, describedBy);
|
|
764
752
|
|
|
765
753
|
// A nested fieldset / repeater / checkbox-group provide their own grouping
|
|
766
754
|
// label, so the row's <label> is only rendered for simple single controls.
|
|
@@ -784,7 +772,7 @@ function FieldRow(props: FieldRowProps): JSX.Element {
|
|
|
784
772
|
</p>
|
|
785
773
|
</Show>
|
|
786
774
|
|
|
787
|
-
<WidgetSwitch widget={widget()} common={common
|
|
775
|
+
<WidgetSwitch widget={widget()} common={common} fieldKey={props.fieldKey} />
|
|
788
776
|
|
|
789
777
|
<Show when={props.error()}>
|
|
790
778
|
<p id={errorId} role="alert" class="text-xs text-destructive dark:text-red-400">
|
|
@@ -795,6 +783,45 @@ function FieldRow(props: FieldRowProps): JSX.Element {
|
|
|
795
783
|
);
|
|
796
784
|
}
|
|
797
785
|
|
|
786
|
+
/**
|
|
787
|
+
* The widget prop bag for one field row. **Internal** — exported only so the
|
|
788
|
+
* subscription contract can be pinned directly
|
|
789
|
+
* (`tests/components/form-field-subscriptions.test.tsx`); it is not re-exported
|
|
790
|
+
* from the package.
|
|
791
|
+
*
|
|
792
|
+
* GETTERS, NOT AN OBJECT FACTORY, and this is the whole point (K-D12b). The
|
|
793
|
+
* factory read `props.value()` alongside everything else and rebuilt the bag on
|
|
794
|
+
* every call, so a reader of ANY prop — `invalid`, say — subscribed to `value`
|
|
795
|
+
* too and re-ran on every keystroke. Combined with the `<input>` being rebuilt
|
|
796
|
+
* when its class expression re-ran (K-D12a), that is what made typing into a
|
|
797
|
+
* `kai-form` text field lose focus after each character.
|
|
798
|
+
*
|
|
799
|
+
* One stable object with one getter per prop: each reader tracks exactly the
|
|
800
|
+
* signal behind the prop it read. The object identity is constant, so the
|
|
801
|
+
* `WidgetSwitch` prop below never changes either.
|
|
802
|
+
*/
|
|
803
|
+
export function fieldCommon(
|
|
804
|
+
props: FieldRowProps,
|
|
805
|
+
id: string,
|
|
806
|
+
placeholder: () => string | undefined,
|
|
807
|
+
label: () => string,
|
|
808
|
+
describedBy: () => string | undefined = () => undefined,
|
|
809
|
+
): ReturnType<FieldRowCommon> {
|
|
810
|
+
return {
|
|
811
|
+
id,
|
|
812
|
+
get value() { return props.value(); },
|
|
813
|
+
get field() { return props.field; },
|
|
814
|
+
get disabled() { return props.disabled || props.field.readOnly === true; },
|
|
815
|
+
get placeholder() { return placeholder(); },
|
|
816
|
+
get required() { return props.required; },
|
|
817
|
+
get invalid() { return Boolean(props.error()); },
|
|
818
|
+
get describedBy() { return describedBy(); },
|
|
819
|
+
get label() { return label(); },
|
|
820
|
+
onInput: (v) => props.onInput(v),
|
|
821
|
+
onBlur: () => props.onBlur(),
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
|
|
798
825
|
interface WidgetSwitchProps {
|
|
799
826
|
widget: WidgetKind;
|
|
800
827
|
fieldKey: string;
|
|
@@ -9,7 +9,17 @@ import type { ChatMessageAction, CustomAction, FeedbackVote, MessagePart, Messag
|
|
|
9
9
|
import { useChatConfig, textClass } from "../primitives/chat-config";
|
|
10
10
|
import { Reasoning, ReasoningTrigger, ReasoningContent } from "./reasoning";
|
|
11
11
|
import { Tool } from "./tool";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
Attachments,
|
|
14
|
+
Attachment,
|
|
15
|
+
AttachmentPreview,
|
|
16
|
+
AttachmentInfo,
|
|
17
|
+
AttachmentHoverCard,
|
|
18
|
+
AttachmentHoverCardTrigger,
|
|
19
|
+
AttachmentHoverCardContent,
|
|
20
|
+
getAttachmentLabel,
|
|
21
|
+
getMediaCategory,
|
|
22
|
+
} from "./attachments";
|
|
13
23
|
import { Source, SourceTrigger, SourceContent, SourceList } from "./source";
|
|
14
24
|
import { CardRenderer, type CardSchemaMap } from "./card-renderer";
|
|
15
25
|
import type { CardComponentMap } from "../primitives/card-registry";
|
|
@@ -336,6 +346,13 @@ export interface MessageBodyProps {
|
|
|
336
346
|
activeFeedback?: FeedbackVote;
|
|
337
347
|
/** When true, the copy button shows its "copied" check icon. */
|
|
338
348
|
copied?: boolean;
|
|
349
|
+
/** Whether this message is the one currently streaming. Forwarded to each
|
|
350
|
+
* reasoning part's `<Reasoning>` so the disclosure auto-opens while the
|
|
351
|
+
* model is thinking and settles back once the stream ends (F-21: without it
|
|
352
|
+
* the user watches a static collapsed "Reasoning" label for the whole
|
|
353
|
+
* thinking window). The caller owns the definition of "streaming" — for
|
|
354
|
+
* `ChatThread` that is `loading` + being the last assistant message. */
|
|
355
|
+
isStreaming?: boolean;
|
|
339
356
|
/** INJECT slot projected at the TOP of the body — above the reasoning / tools /
|
|
340
357
|
* content. A per-message header: a model-name label, a role + timestamp line.
|
|
341
358
|
* In the `<kai-message>` shadow this is `<slot name="before-body" />`. */
|
|
@@ -477,15 +494,68 @@ function MessageBody(props: MessageBodyProps) {
|
|
|
477
494
|
<Switch fallback={null}>
|
|
478
495
|
<Match when={groupAs(group(), 'files')}>
|
|
479
496
|
{(g) => (
|
|
480
|
-
|
|
497
|
+
/* `grid`, NOT `inline`. The inline chip gives an image a 20x20
|
|
498
|
+
preview — a thumbnail nobody can read — while the 96px tile
|
|
499
|
+
and the hover-card full preview both already existed here and
|
|
500
|
+
the thread used neither.
|
|
501
|
+
|
|
502
|
+
WHY GRID AND NOT LIST, since `list` shows the filename
|
|
503
|
+
outright: `list` stacks (`flex-col`), so a message carrying
|
|
504
|
+
four attachments becomes four full-width rows and pushes the
|
|
505
|
+
conversation off screen. `grid` is `flex-wrap w-fit`, so the
|
|
506
|
+
same four are one 96px-tall row of tiles. Density is what
|
|
507
|
+
keeps a thread readable, and it is the whole reason the
|
|
508
|
+
thread is not the composer's chip strip.
|
|
509
|
+
|
|
510
|
+
A grid tile draws no filename of its own, and hiding one
|
|
511
|
+
behind the hover card is not good enough: that serves a
|
|
512
|
+
pointer and nothing else. So `<AttachmentInfo>` renders a
|
|
513
|
+
VISIBLE truncated caption on every non-image tile — no hover,
|
|
514
|
+
no focus, no tap required — and the hover card is an upgrade
|
|
515
|
+
to the full name and media type rather than the only way to
|
|
516
|
+
get either. */
|
|
517
|
+
<Attachments variant="grid" class={props.isUser ? 'mb-2 ml-auto' : 'mb-2'}>
|
|
481
518
|
{/* Reference-keyed <For> is right HERE: the run's part objects
|
|
482
519
|
are carried over untouched by the folds, and an attachment
|
|
483
520
|
holds no state worth preserving. */}
|
|
484
521
|
<For each={g().parts}>
|
|
485
522
|
{(fp) => (
|
|
486
523
|
<Attachment data={fp.attachment}>
|
|
487
|
-
<
|
|
488
|
-
|
|
524
|
+
<AttachmentHoverCard>
|
|
525
|
+
<AttachmentHoverCardTrigger class="block size-full">
|
|
526
|
+
<AttachmentPreview />
|
|
527
|
+
<AttachmentInfo />
|
|
528
|
+
</AttachmentHoverCardTrigger>
|
|
529
|
+
<AttachmentHoverCardContent>
|
|
530
|
+
{/* An image gets the full preview; everything else
|
|
531
|
+
gets the name and type the tile could not fit. */}
|
|
532
|
+
<Show
|
|
533
|
+
when={
|
|
534
|
+
getMediaCategory(fp.attachment) === 'image' &&
|
|
535
|
+
fp.attachment.type === 'file' &&
|
|
536
|
+
fp.attachment.url
|
|
537
|
+
}
|
|
538
|
+
fallback={
|
|
539
|
+
<>
|
|
540
|
+
<div class="text-body font-medium">
|
|
541
|
+
{getAttachmentLabel(fp.attachment)}
|
|
542
|
+
</div>
|
|
543
|
+
<Show when={fp.attachment.mediaType}>
|
|
544
|
+
<div class="text-muted-foreground text-caption">
|
|
545
|
+
{fp.attachment.mediaType}
|
|
546
|
+
</div>
|
|
547
|
+
</Show>
|
|
548
|
+
</>
|
|
549
|
+
}
|
|
550
|
+
>
|
|
551
|
+
<img
|
|
552
|
+
alt={getAttachmentLabel(fp.attachment)}
|
|
553
|
+
class="block max-h-64 max-w-xs rounded object-contain"
|
|
554
|
+
src={fp.attachment.url}
|
|
555
|
+
/>
|
|
556
|
+
</Show>
|
|
557
|
+
</AttachmentHoverCardContent>
|
|
558
|
+
</AttachmentHoverCard>
|
|
489
559
|
</Attachment>
|
|
490
560
|
)}
|
|
491
561
|
</For>
|
|
@@ -553,7 +623,7 @@ function MessageBody(props: MessageBodyProps) {
|
|
|
553
623
|
<Match when={carrierOnly()}>{null}</Match>
|
|
554
624
|
<Match when={shownReasoning()}>
|
|
555
625
|
{(p) => (
|
|
556
|
-
<Reasoning class="mb-2 w-full">
|
|
626
|
+
<Reasoning class="mb-2 w-full" isStreaming={props.isStreaming}>
|
|
557
627
|
<ReasoningTrigger>{p().label ?? 'Reasoning'}</ReasoningTrigger>
|
|
558
628
|
<ReasoningContent markdown>{p().text}</ReasoningContent>
|
|
559
629
|
</Reasoning>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { splitProps, Show, For, createEffect, on } from 'solid-js';
|
|
2
|
+
import { Dynamic } from 'solid-js/web';
|
|
2
3
|
import { cn } from '../utils/cn';
|
|
3
|
-
import { useTextStream } from '../primitives/use-text-stream';
|
|
4
|
+
import { useTextStream, defaultFadeDuration, defaultSegmentDelay } from '../primitives/use-text-stream';
|
|
4
5
|
|
|
5
6
|
export type Mode = 'typewriter' | 'fade';
|
|
6
7
|
|
|
@@ -30,6 +31,7 @@ function ResponseStream(props: ResponseStreamProps) {
|
|
|
30
31
|
speed: speed(),
|
|
31
32
|
characterChunkSize: local.characterChunkSize,
|
|
32
33
|
fadeDuration: local.fadeDuration,
|
|
34
|
+
segmentDelay: local.segmentDelay,
|
|
33
35
|
});
|
|
34
36
|
|
|
35
37
|
createEffect(on(
|
|
@@ -39,15 +41,20 @@ function ResponseStream(props: ResponseStreamProps) {
|
|
|
39
41
|
}
|
|
40
42
|
));
|
|
41
43
|
|
|
44
|
+
// Deferred: `isComplete` INITIALISES true (nothing has streamed yet), so an
|
|
45
|
+
// undeferred first run would announce completion at mount — the same
|
|
46
|
+
// no-event-on-mount contract kai-tool pins for kai-open-change. Only the
|
|
47
|
+
// false -> true transition of a real stream may call onComplete.
|
|
42
48
|
createEffect(on(
|
|
43
49
|
() => stream.isComplete(),
|
|
44
50
|
(complete) => {
|
|
45
51
|
if (complete) local.onComplete?.();
|
|
46
|
-
}
|
|
52
|
+
},
|
|
53
|
+
{ defer: true }
|
|
47
54
|
));
|
|
48
55
|
|
|
49
56
|
const fadeStyle = () => {
|
|
50
|
-
const dur = local.fadeDuration ??
|
|
57
|
+
const dur = local.fadeDuration ?? defaultFadeDuration(speed());
|
|
51
58
|
return `
|
|
52
59
|
@keyframes fadeIn {
|
|
53
60
|
from { opacity: 0; }
|
|
@@ -66,12 +73,11 @@ function ResponseStream(props: ResponseStreamProps) {
|
|
|
66
73
|
|
|
67
74
|
const segDelay = () => {
|
|
68
75
|
if (typeof local.segmentDelay === 'number') return Math.max(0, local.segmentDelay);
|
|
69
|
-
|
|
70
|
-
return Math.max(1, Math.round(100 / Math.sqrt(normalizedSpeed)));
|
|
76
|
+
return defaultSegmentDelay(speed());
|
|
71
77
|
};
|
|
72
78
|
|
|
73
79
|
return (
|
|
74
|
-
<div class={local.class}>
|
|
80
|
+
<Dynamic component={local.as ?? 'div'} class={local.class}>
|
|
75
81
|
<Show
|
|
76
82
|
when={mode() === 'fade'}
|
|
77
83
|
fallback={<>{stream.displayedText()}</>}
|
|
@@ -96,7 +102,7 @@ function ResponseStream(props: ResponseStreamProps) {
|
|
|
96
102
|
</For>
|
|
97
103
|
</div>
|
|
98
104
|
</Show>
|
|
99
|
-
</
|
|
105
|
+
</Dynamic>
|
|
100
106
|
);
|
|
101
107
|
}
|
|
102
108
|
|
|
@@ -144,6 +144,11 @@ export function Screen(props: ScreenProps) {
|
|
|
144
144
|
data-expanded={presence.state() === 'open' ? '' : undefined}
|
|
145
145
|
data-closed={presence.state() === 'closed' ? '' : undefined}
|
|
146
146
|
onKeyDown={onKeyDown}
|
|
147
|
+
// `outline-none` is DELIBERATE here and must stay: this is the
|
|
148
|
+
// `tabindex="-1"` screen container, focused programmatically on open so
|
|
149
|
+
// keyboard and screen-reader context follows the screen. It is not a
|
|
150
|
+
// tab stop, so a ring around the whole surface would be noise. Its
|
|
151
|
+
// interactive children carry their own.
|
|
147
152
|
class={cn(
|
|
148
153
|
'absolute inset-0 z-40 flex flex-col bg-background text-foreground outline-none',
|
|
149
154
|
'animate-in fade-in-0 slide-in-from-bottom-2 motion-reduce:animate-none',
|
|
@@ -17,6 +17,13 @@ function ScrollButton(props: ScrollButtonProps) {
|
|
|
17
17
|
variant={props.variant ?? 'outline'}
|
|
18
18
|
size={props.size ?? 'sm'}
|
|
19
19
|
aria-label="Scroll to bottom"
|
|
20
|
+
// At the bottom of the thread this button is fully transparent and
|
|
21
|
+
// pointer-inert, but it stayed in the tab order — a keyboard user landed
|
|
22
|
+
// on a control they could not see, with a focus ring painted on nothing.
|
|
23
|
+
// Take it out of the tab order and hide it from assistive tech while it
|
|
24
|
+
// is invisible; both revert the moment it animates back in.
|
|
25
|
+
tabindex={isAtBottom() ? -1 : 0}
|
|
26
|
+
aria-hidden={isAtBottom() ? 'true' : undefined}
|
|
20
27
|
class={cn(
|
|
21
28
|
'h-10 w-10 rounded-full transition-all duration-150 ease-out',
|
|
22
29
|
!isAtBottom()
|
package/src/components/toast.tsx
CHANGED
|
@@ -320,11 +320,17 @@ function anchorStyle(position: ToastPosition, r: TargetRect): Record<string, str
|
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
/** The overlay layer both region roots sit on (F-20). One token, one default:
|
|
324
|
+
* your app chrome must stay below the toast layer; set `--kai-toast-z`
|
|
325
|
+
* (default 100) on the host to move the layer. */
|
|
326
|
+
const TOAST_Z = 'var(--kai-toast-z, 100)';
|
|
327
|
+
|
|
323
328
|
/**
|
|
324
329
|
* Renders a stack of toasts at a viewport edge. Caps the visible count at `max`
|
|
325
330
|
* (newest on top); overflow waits in a queue and is promoted as visible toasts
|
|
326
331
|
* leave. `aria-live=polite` / `role=region` so screen readers announce new
|
|
327
|
-
* toasts without stealing focus.
|
|
332
|
+
* toasts without stealing focus. Your app chrome must stay below the toast
|
|
333
|
+
* layer; set `--kai-toast-z` (default 100) on the host to move the layer.
|
|
328
334
|
*/
|
|
329
335
|
export function ToastRegion(props: ToastRegionProps) {
|
|
330
336
|
const max = () => props.max ?? 3;
|
|
@@ -425,12 +431,12 @@ export function ToastRegion(props: ToastRegionProps) {
|
|
|
425
431
|
aria-live="polite"
|
|
426
432
|
data-stack="expanded"
|
|
427
433
|
class={cn(
|
|
428
|
-
'pointer-events-none fixed
|
|
434
|
+
'pointer-events-none fixed flex flex-col gap-2',
|
|
429
435
|
anchor()
|
|
430
436
|
? ANCHOR_FLEX[position()]
|
|
431
437
|
: cn('max-w-[min(30rem,calc(100vw-2rem))]', POSITION_CLASSES[position()]),
|
|
432
438
|
)}
|
|
433
|
-
style={anchor() ? anchorStyle(position(), anchor()!) : undefined}
|
|
439
|
+
style={{ 'z-index': TOAST_Z, ...(anchor() ? anchorStyle(position(), anchor()!) : undefined) }}
|
|
434
440
|
>
|
|
435
441
|
<For each={visible()}>
|
|
436
442
|
{(item) => (
|
|
@@ -456,8 +462,8 @@ export function ToastRegion(props: ToastRegionProps) {
|
|
|
456
462
|
onPointerLeave={() => setHovered(false)}
|
|
457
463
|
onFocusIn={() => setFocused(true)}
|
|
458
464
|
onFocusOut={() => setFocused(false)}
|
|
459
|
-
class={cn('pointer-events-none fixed
|
|
460
|
-
style={anchor() ? anchorStyle(position(), anchor()!) : undefined}
|
|
465
|
+
class={cn('pointer-events-none fixed', !anchor() && POSITION_CLASSES[position()])}
|
|
466
|
+
style={{ 'z-index': TOAST_Z, ...(anchor() ? anchorStyle(position(), anchor()!) : undefined) }}
|
|
461
467
|
>
|
|
462
468
|
{/* Relative stage with an explicit height so it's ONE continuous hover
|
|
463
469
|
surface (pointer-events-auto) — no flicker when the cursor crosses gaps
|
|
@@ -38,6 +38,10 @@ export interface VoiceInputProps {
|
|
|
38
38
|
/** Receive the imperative controller once mounted. The `<kai-voice-input>`
|
|
39
39
|
* facade forwards these as element methods (start/stop). */
|
|
40
40
|
controllerRef?: (controller: VoiceInputController) => void;
|
|
41
|
+
/** A recognition session failed or produced nothing. `error` is the platform
|
|
42
|
+
* error code, the thrown exception's name, or `no-result` when the session
|
|
43
|
+
* ended with no error and no text. The facade maps this to kai-voice-error. */
|
|
44
|
+
onError?: (detail: { source: 'recognition'; error: string; message: string }) => void;
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
export function VoiceInput(props: VoiceInputProps) {
|
|
@@ -59,15 +63,43 @@ export function VoiceInput(props: VoiceInputProps) {
|
|
|
59
63
|
|
|
60
64
|
// Native path: SpeechRecognition resolves with the final transcript when it
|
|
61
65
|
// ends (click again or controller.stop()). Final text → onTranscription.
|
|
66
|
+
// Every other outcome reaches onError, so a session can never end in silence
|
|
67
|
+
// (W4 symptom 1: runtime errors set a signal nothing read, and an empty
|
|
68
|
+
// result fired no event at all).
|
|
62
69
|
async function beginRecognition() {
|
|
70
|
+
let errored = false;
|
|
63
71
|
try {
|
|
64
72
|
const text = await speech.start({
|
|
65
73
|
lang: props.lang,
|
|
66
74
|
onInterim: props.interim ? (t) => props.onInterim?.(t) : undefined,
|
|
75
|
+
onError: (error, message) => {
|
|
76
|
+
errored = true;
|
|
77
|
+
props.onError?.({ source: 'recognition', error, message });
|
|
78
|
+
},
|
|
67
79
|
});
|
|
68
|
-
if (text.trim())
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
if (text.trim()) {
|
|
81
|
+
local.onTranscription(text.trim());
|
|
82
|
+
} else if (!errored) {
|
|
83
|
+
// No error, no text: the session ended cleanly having heard nothing.
|
|
84
|
+
// Reported through the SAME error event (as `no-result`) rather than an
|
|
85
|
+
// empty kai-transcription, so kai-transcription keeps its contract
|
|
86
|
+
// (final text exists) and one listener covers every failed session; the
|
|
87
|
+
// code distinguishes "user said nothing" from "recognition broke".
|
|
88
|
+
props.onError?.({
|
|
89
|
+
source: 'recognition',
|
|
90
|
+
error: 'no-result',
|
|
91
|
+
message: 'Speech recognition ended without capturing any text',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
// start() itself threw (unsupported, or the platform refused to start).
|
|
96
|
+
if (!errored) {
|
|
97
|
+
props.onError?.({
|
|
98
|
+
source: 'recognition',
|
|
99
|
+
error: err instanceof Error ? err.name : 'unknown',
|
|
100
|
+
message: err instanceof Error ? err.message : String(err),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
71
103
|
}
|
|
72
104
|
}
|
|
73
105
|
|