@kitn.ai/ui 0.20.0 → 0.21.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 +44 -45
- package/dist/components/artifact-card.d.ts +51 -0
- package/dist/components/artifact.d.ts +1 -1
- package/dist/components/attachments.d.ts +2 -2
- package/dist/components/audio-visualizer/index.d.ts +3 -3
- package/dist/components/audio-visualizer/labs/lab-choreography.d.ts +59 -0
- package/dist/components/audio-visualizer/labs/lab-shaders.d.ts +22 -0
- package/dist/components/audio-visualizer/labs/lab-visualizer.d.ts +18 -0
- package/dist/components/audio-visualizer/variant-aurora.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-bar.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-custom.d.ts +2 -2
- package/dist/components/audio-visualizer/variant-grid.d.ts +1 -1
- package/dist/components/audio-visualizer/variant-radial.d.ts +1 -1
- package/dist/components/audio-visualizer/variant-wave.d.ts +1 -1
- package/dist/components/card-fallback.d.ts +12 -2
- package/dist/components/card-renderer.d.ts +63 -2
- package/dist/components/chat-container.d.ts +3 -0
- package/dist/components/chat-scope-picker.d.ts +2 -2
- package/dist/components/chat-thread.d.ts +68 -18
- package/dist/components/choice-card.d.ts +3 -28
- package/dist/components/composer-dom.d.ts +1 -1
- package/dist/components/composer-highlight.d.ts +1 -1
- package/dist/components/composer-history.d.ts +1 -1
- package/dist/components/composer.d.ts +1 -1
- package/dist/components/confirm-card.d.ts +3 -18
- package/dist/components/context.d.ts +8 -4
- package/dist/components/conversation-item.d.ts +1 -1
- package/dist/components/conversation-list.d.ts +4 -3
- package/dist/components/embed.d.ts +3 -3
- package/dist/components/file-tree.d.ts +11 -22
- package/dist/components/form-widgets.d.ts +1 -1
- package/dist/components/form.d.ts +3 -44
- package/dist/components/link-preview.d.ts +3 -3
- package/dist/components/loader.d.ts +30 -48
- package/dist/components/message-skills.d.ts +4 -0
- package/dist/components/message.d.ts +84 -21
- package/dist/components/model-switcher.d.ts +2 -2
- package/dist/components/prompt-input.d.ts +2 -2
- package/dist/components/response-compare-types.d.ts +11 -2
- package/dist/components/response-compare.d.ts +3 -3
- package/dist/components/source.d.ts +12 -3
- package/dist/components/tasks-card.d.ts +3 -31
- package/dist/components/thread.d.ts +17 -5
- package/dist/components/toast.d.ts +2 -2
- package/dist/components/tool-classify.d.ts +8 -0
- package/dist/components/tool-types.d.ts +29 -1
- package/dist/components/tool.d.ts +3 -3
- package/dist/components/use-card-resolution.d.ts +1 -1
- package/dist/create-tween-D3Y5qiij.js +338 -0
- package/dist/create-tween-DGKY1FDY.js +341 -0
- package/dist/create-tween-paho5Rh-.js +7 -0
- package/dist/custom-elements.json +1681 -454
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.js +1 -1
- package/dist/elements/avatar.js +1 -1
- package/dist/elements/badge.js +1 -1
- package/dist/elements/button.js +1 -1
- package/dist/elements/card.js +1 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-types.d.ts +70 -10
- 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-Tj58ES_d.js → Icon-C-2LvGDG.js} +1 -1
- package/dist/elements/chunks/{action-icons-ns1Fnedj.js → action-icons-CaYoImEk.js} +1 -1
- package/dist/elements/chunks/arrow-left-Bb46H7w9.js +1 -0
- package/dist/elements/chunks/artifact-BBIscOxJ.js +1 -0
- package/dist/elements/chunks/{attachments-mCB5F1Fq.js → attachments-BB56zoji.js} +1 -1
- package/dist/elements/chunks/audio-visualizer-BxOx4THz.js +1 -0
- package/dist/elements/chunks/{badge-B4Zuajl2.js → badge-SxLSZ19R.js} +1 -1
- package/dist/elements/chunks/{button-ZWNif3--.js → button-CmkUkz6c.js} +1 -1
- package/dist/elements/chunks/card-renderer-Db4ffkWv.js +1 -0
- package/dist/elements/chunks/card-tags-D8lZ-C_U.js +1 -0
- package/dist/elements/chunks/chat-thread-B3Ks-cKr.js +1 -0
- package/dist/elements/chunks/check-C8desc_h.js +1 -0
- package/dist/elements/chunks/chevron-down-DKtc2HNB.js +1 -0
- package/dist/elements/chunks/chevron-right-Dw4GPhwL.js +1 -0
- package/dist/elements/chunks/choice-card-Q9cYYDyM.js +1 -0
- package/dist/elements/chunks/circle-BeokBsvB.js +1 -0
- package/dist/elements/chunks/circle-check-wJSqt859.js +1 -0
- package/dist/elements/chunks/{circle-x-B7faJFlY.js → circle-x-D0MEfxOM.js} +1 -1
- package/dist/elements/chunks/code-block-C8VzAraE.js +1 -0
- package/dist/elements/chunks/{collapsible-DM1Ckd4e.js → collapsible-Cvj61obE.js} +1 -1
- package/dist/elements/chunks/{composer-BguS0QL_.js → composer-D86ztyL0.js} +2 -2
- package/dist/elements/chunks/confirm-card-BdyDAGLw.js +1 -0
- package/dist/elements/chunks/context-WHDl-xSx.js +1 -0
- package/dist/elements/chunks/conversation-list-CdR14_Y4.js +1 -0
- package/dist/elements/chunks/create-tween-C_A_B638.js +7 -0
- package/dist/elements/chunks/{default-input-Cqqilg9t.js → default-input-CSZqUM81.js} +1 -1
- package/dist/elements/chunks/define-BzPnNsXg.js +1 -0
- package/dist/elements/chunks/{disclosure-CIZ5c8nO.js → disclosure-DVKsoc1F.js} +1 -1
- package/dist/elements/chunks/{download-Dsbg3ste.js → download-BjA54IMJ.js} +1 -1
- package/dist/elements/chunks/{dropdown-BDd1-Zgi.js → dropdown-BjVVHWXH.js} +1 -1
- package/dist/elements/chunks/{ellipsis-Dw6qrss-.js → ellipsis-BNedmqfi.js} +1 -1
- package/dist/elements/chunks/embed-DQsl4XEq.js +1 -0
- package/dist/elements/chunks/{external-link-Vt74zAoL.js → external-link-Di-dw2CU.js} +1 -1
- package/dist/elements/chunks/{file-text-CZrZF6_v.js → file-text-B3sAODCV.js} +1 -1
- package/dist/elements/chunks/{file-tree-DF4okKyo.js → file-tree-Dgto-bMn.js} +1 -1
- package/dist/elements/chunks/{folder-glP5rha4.js → folder-D6rMadFV.js} +1 -1
- package/dist/elements/chunks/form-CILxvhQ7.js +1 -0
- package/dist/elements/chunks/hover-card-BtiF4XiU.js +1 -0
- package/dist/elements/chunks/{icon-BLb8beoi.js → icon-DKkmze3y.js} +1 -1
- package/dist/elements/chunks/{info-CIsv92JK.js → info-CWRjZkuy.js} +1 -1
- package/dist/elements/chunks/{input-Dds1kFyt.js → input--QJBK6BA.js} +1 -1
- package/dist/elements/chunks/{kbd-De_xbgi9.js → kbd-C3zuKh8R.js} +1 -1
- package/dist/elements/chunks/{link-i4r_k7z4.js → link-BL5JA7Q0.js} +1 -1
- package/dist/elements/chunks/link-preview-CU37bbXh.js +1 -0
- package/dist/elements/chunks/{loader-BmQH4s3V.js → loader-XmpyxyRH.js} +1 -1
- package/dist/elements/chunks/{markdown-Zi21WhUz.js → markdown-3Gv5UZ_o.js} +1 -1
- package/dist/elements/chunks/message-CnBA2OUt.js +1 -0
- package/dist/elements/chunks/message-X5NZbq-W.js +1 -0
- package/dist/elements/chunks/message-square-CnpAm_P0.js +1 -0
- package/dist/elements/chunks/{minimize-2--ifeCGrl.js → minimize-2-DwyrUTcM.js} +1 -1
- package/dist/elements/chunks/{model-switcher-B94YAKsM.js → model-switcher-CclaJEHt.js} +1 -1
- package/dist/elements/chunks/{overlay-kD8dwRv_.js → overlay-Cepd-KcS.js} +1 -1
- package/dist/elements/chunks/{paperclip-q21dYOSK.js → paperclip-CdwKjrlu.js} +1 -1
- package/dist/elements/chunks/{progress-bar-BrAIIaaJ.js → progress-bar-DMVXBD3b.js} +1 -1
- package/dist/elements/chunks/{prompt-suggestion-Bjn2K-fR.js → prompt-suggestion-Di_WZE_5.js} +1 -1
- package/dist/elements/chunks/reasoning-D0jMch5u.js +1 -0
- package/dist/elements/chunks/{resizable-Dp4JExql.js → resizable-1znbL1Gv.js} +1 -1
- package/dist/elements/chunks/{rotate-cw-CFWWkpj2.js → rotate-cw-DVhhgx8n.js} +1 -1
- package/dist/elements/chunks/{scroll-area-Bb0kHH1j.js → scroll-area-Bs4bFS1P.js} +1 -1
- package/dist/elements/chunks/scroll-button-Bet5NLc4.js +1 -0
- package/dist/elements/chunks/{separator-9kRgr1X9.js → separator-C5Gh8qGC.js} +1 -1
- package/dist/elements/chunks/{settings-B8lmec4D.js → settings-5vmEFg0K.js} +1 -1
- package/dist/elements/chunks/{settings-group-I0hEuXiN.js → settings-group-BWLB38kz.js} +1 -1
- package/dist/elements/chunks/{share-C3lTiYGt.js → share-BZ3G_DaB.js} +1 -1
- package/dist/elements/chunks/skeleton-DafPJQTp.js +1 -0
- package/dist/elements/chunks/slots-CIw9RlAe.js +1 -0
- package/dist/elements/chunks/source-D_qj_6uJ.js +1 -0
- package/dist/elements/chunks/{star-CSJtGxgv.js → star-Cuv7j2IE.js} +1 -1
- package/dist/elements/chunks/{store-CHyW95TP.js → store-C1StPHfW.js} +1 -1
- package/dist/elements/chunks/tasks-card-EyoBZfpA.js +1 -0
- package/dist/elements/chunks/{text-shimmer-Cn9TmNpI.js → text-shimmer-D9gRSh34.js} +1 -1
- package/dist/elements/chunks/textarea-DuhtHfcm.js +1 -0
- package/dist/elements/chunks/{thumbs-up-PNVVThEF.js → thumbs-up-B9hwv43w.js} +1 -1
- package/dist/elements/chunks/{toast-store-DKbQn3Wn.js → toast-store-Bm3DUEDe.js} +1 -1
- package/dist/elements/chunks/{tool-DjTRpIpl.js → tool-CDtW7L0z.js} +1 -1
- package/dist/elements/chunks/tooltip-eB226mGK.js +1 -0
- package/dist/elements/chunks/{triangle-alert-DsHTHCPm.js → triangle-alert-C1SNzTqK.js} +1 -1
- package/dist/elements/chunks/use-card-resolution-YZh2MvI5.js +1 -0
- package/dist/elements/chunks/{variant-aurora-Dc4nQUia.js → variant-aurora-Cj1F7D0l.js} +2 -2
- package/dist/elements/chunks/{variant-custom-y_3kVQhS.js → variant-custom-T9gpt7QE.js} +1 -1
- package/dist/elements/chunks/{variant-wave-D5OR1-9Z.js → variant-wave-CxU9z-TE.js} +2 -2
- package/dist/elements/chunks/{video-B7vmenoC.js → video-CK4R7Rus.js} +1 -1
- package/dist/elements/chunks/x-D1qx0aLd.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-list.js +1 -1
- package/dist/elements/default-input.d.ts +7 -7
- package/dist/elements/define.d.ts +1 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/disclosure.d.ts +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/element-data-types.d.ts +107 -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 +8 -6
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +2 -2
- 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 +16 -0
- 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/validate-messages.d.ts +25 -0
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +2725 -214
- package/dist/index.d.ts +163 -125
- package/dist/index.js +6336 -5802
- package/dist/index.server.d.ts +5 -0
- package/dist/index.server.js +16913 -0
- package/dist/kai-provider.es.d.ts +5 -0
- package/dist/kai-provider.es.js +79 -77
- package/dist/kai.es.d.ts +5 -0
- package/dist/kai.es.js +1 -1
- package/dist/llms/llms-full.txt +762 -148
- package/dist/llms/llms.txt +57 -5
- package/dist/mcp.es.js +4010 -770
- package/dist/primitives/card-component-types.d.ts +11 -0
- package/dist/primitives/card-data-types.d.ts +196 -0
- package/dist/primitives/card-host.d.ts +1 -1
- package/dist/primitives/card-recovery.d.ts +3 -3
- package/dist/primitives/card-registry.d.ts +4 -14
- package/dist/primitives/card-resolution.d.ts +1 -1
- package/dist/primitives/card-routing.d.ts +1 -1
- package/dist/primitives/card-tags.d.ts +11 -0
- package/dist/primitives/card-validate-cards.d.ts +57 -0
- package/dist/primitives/card-validate-generator.testlib.d.ts +20 -0
- package/dist/primitives/card-validate-schemas.d.ts +17 -0
- package/dist/primitives/card-validate.d.ts +24 -0
- package/dist/primitives/create-kai-chat.d.ts +3 -3
- package/dist/primitives/embed-providers.d.ts +1 -1
- package/dist/primitives/link-preview.d.ts +1 -1
- package/dist/primitives/message-feedback.d.ts +2 -2
- package/dist/primitives/toast-store.d.ts +1 -1
- package/dist/react/index.d.ts +717 -299
- package/dist/react/use-kai-chat.d.ts +8 -4
- package/dist/react.d.ts +5 -0
- package/dist/react.js +417 -316
- package/dist/register-impl-bzrZ44ft.js +145 -0
- package/dist/remote/host-embed.d.ts +1 -1
- package/dist/remote/index.d.ts +2 -2
- package/dist/remote/provider-runtime.d.ts +2 -2
- package/dist/remote/provider.d.ts +2 -2
- package/dist/remote/validate.d.ts +1 -1
- package/dist/remote/wire.d.ts +1 -1
- package/dist/schemas/artifact.schema.json +92 -0
- package/dist/schemas/embed.schema.json +0 -2
- package/dist/schemas/from-tool-call.d.ts +48 -0
- package/dist/schemas/index.d.ts +48 -0
- package/dist/schemas/link.schema.json +0 -2
- package/dist/schemas/provider-subsets.d.ts +136 -0
- package/dist/schemas/registry.d.ts +140 -0
- package/dist/schemas/tool-defs.d.ts +121 -0
- package/dist/schemas.d.ts +5 -0
- package/dist/schemas.js +717 -0
- package/dist/solid-BCJUQqwS.js +22750 -0
- package/dist/solid-BdENBcEq.js +20108 -0
- package/dist/solid.d.ts +66 -0
- package/dist/solid.js +261 -0
- package/dist/solid.server.d.ts +5 -0
- package/dist/solid.server.js +261 -0
- package/dist/state/index.d.ts +14 -4
- package/dist/state/messages.d.ts +8 -3
- package/dist/state/mock.d.ts +46 -0
- package/dist/state/parts.d.ts +85 -0
- package/dist/state/stream.d.ts +15 -9
- package/dist/state.d.ts +5 -0
- package/dist/state.js +253 -84
- package/dist/ui/action-icons.d.ts +1 -1
- package/dist/ui/collapsible.d.ts +11 -6
- package/dist/ui/dropdown.d.ts +47 -42
- package/dist/ui/pane-group.d.ts +1 -1
- package/dist/variant-aurora-1wMcXRUS.js +381 -0
- package/dist/variant-aurora-BKHHiOgL.js +391 -0
- package/dist/{variant-aurora-DVtGqVH6.js → variant-aurora-DOb_o4pC.js} +1 -1
- package/dist/{variant-custom-Dif-IV4u.js → variant-custom-CJGB0Kbw.js} +1 -1
- package/dist/variant-custom-Co0CkXJF.js +200 -0
- package/dist/variant-custom-DWSSgmd2.js +170 -0
- package/dist/variant-wave-C3-45cL6.js +218 -0
- package/dist/{variant-wave-DbfNdvSi.js → variant-wave-CEODq-9q.js} +1 -1
- package/dist/variant-wave-jdXBnCUZ.js +208 -0
- package/dist/wire/chunk.d.ts +171 -0
- package/dist/wire/consume.d.ts +26 -0
- package/dist/wire/encode.d.ts +148 -0
- package/dist/wire/formats/anthropic.d.ts +2 -0
- package/dist/wire/formats/openai.d.ts +5 -0
- package/dist/wire/index.d.ts +14 -0
- package/dist/wire/read.d.ts +26 -0
- package/dist/wire/sink-helpers.d.ts +21 -0
- package/dist/wire/sse.d.ts +33 -0
- package/dist/wire.d.ts +5 -53
- package/dist/wire.js +898 -0
- package/frameworks/react/index.tsx +183 -154
- package/frameworks/react/runtime.tsx +21 -1
- package/frameworks/react/use-kai-chat.tsx +11 -1
- package/llms-full.txt +762 -148
- package/llms.txt +57 -5
- package/package.json +65 -7
- package/src/agent-tooling/archetypes.ts +96 -0
- package/src/agent-tooling/integrations/anthropic.ts +412 -0
- package/src/agent-tooling/integrations/cloudflare.ts +136 -52
- package/src/agent-tooling/integrations/langgraph.ts +67 -13
- package/src/agent-tooling/integrations/mastra.ts +120 -12
- package/src/agent-tooling/integrations/mock.ts +43 -8
- package/src/agent-tooling/integrations/ollama.ts +61 -24
- package/src/agent-tooling/integrations/openai.ts +87 -0
- package/src/agent-tooling/integrations/openrouter.ts +56 -8
- package/src/agent-tooling/integrations/pi.ts +79 -23
- package/src/agent-tooling/integrations/pydantic-ai.ts +24 -2
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +84 -13
- package/src/agent-tooling/mcp/manifest.ts +206 -25
- package/src/agent-tooling/mcp/tools/debug.ts +13 -7
- package/src/agent-tooling/mcp/tools/reference.ts +285 -6
- package/src/agent-tooling/mcp/tools/scaffold.ts +3345 -675
- package/src/agent-tooling/registry.ts +17 -0
- package/src/agent-tooling/types.ts +229 -1
- package/src/components/artifact-card.tsx +120 -0
- package/src/components/audio-visualizer/index.tsx +9 -0
- package/src/components/audio-visualizer/labs/lab-choreography.ts +187 -0
- package/src/components/audio-visualizer/labs/lab-shaders.ts +363 -0
- package/src/components/audio-visualizer/labs/lab-visualizer.tsx +102 -0
- package/src/components/audio-visualizer/shader-canvas.tsx +31 -1
- package/src/components/card-fallback.tsx +42 -10
- package/src/components/card-renderer.tsx +146 -9
- package/src/components/chat-container.tsx +4 -0
- package/src/components/chat-thread.tsx +118 -51
- package/src/components/choice-card.tsx +17 -29
- package/src/components/composer.tsx +12 -2
- package/src/components/confirm-card.tsx +24 -20
- package/src/components/context.tsx +12 -4
- package/src/components/conversation-list.tsx +18 -2
- package/src/components/file-tree.tsx +11 -22
- package/src/components/file-upload.tsx +14 -8
- package/src/components/form.tsx +14 -47
- package/src/components/loader.tsx +39 -12
- package/src/components/message-skills.tsx +4 -0
- package/src/components/message.tsx +294 -46
- package/src/components/reasoning.tsx +92 -5
- package/src/components/response-compare-types.ts +25 -0
- package/src/components/response-compare.tsx +4 -9
- package/src/components/source.tsx +50 -24
- package/src/components/tasks-card.tsx +10 -33
- package/src/components/thread.tsx +84 -42
- package/src/components/toast.tsx +11 -5
- package/src/components/tool-classify.ts +26 -0
- package/src/components/tool-types.ts +31 -1
- package/src/elements/agent-card.tsx +11 -11
- package/src/elements/artifact.tsx +15 -7
- package/src/elements/attachments.tsx +6 -4
- package/src/elements/avatar.tsx +1 -1
- package/src/elements/button.tsx +3 -3
- package/src/elements/card.tsx +2 -2
- package/src/elements/cards.tsx +109 -14
- package/src/elements/chain-of-thought.tsx +14 -10
- package/src/elements/chat-scope-picker.tsx +9 -7
- package/src/elements/chat-types.ts +59 -6
- package/src/elements/chat-workspace.tsx +50 -9
- package/src/elements/chat.tsx +64 -3
- package/src/elements/choice.tsx +8 -8
- package/src/elements/coachmark.tsx +1 -1
- package/src/elements/command.tsx +8 -23
- package/src/elements/compare.tsx +7 -6
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +8 -8
- package/src/elements/confirm-card.tsx +9 -9
- package/src/elements/context-meter.tsx +4 -18
- package/src/elements/conversation-list.tsx +14 -9
- package/src/elements/default-input.tsx +3 -3
- package/src/elements/define.tsx +178 -43
- package/src/elements/editable-label.tsx +17 -2
- package/src/elements/element-data-types.ts +112 -0
- package/src/elements/element-meta.json +1596 -304
- package/src/elements/element-types.d.ts +2636 -211
- package/src/elements/feedback-bar.tsx +1 -1
- package/src/elements/file-tree.tsx +1 -1
- package/src/elements/file-upload.tsx +1 -1
- package/src/elements/form.tsx +14 -6
- package/src/elements/input.tsx +1 -1
- package/src/elements/kbd.tsx +1 -1
- package/src/elements/menu.tsx +10 -29
- package/src/elements/message-skills.tsx +7 -9
- package/src/elements/message.tsx +200 -32
- package/src/elements/model-switcher.tsx +5 -3
- package/src/elements/pane.tsx +1 -1
- package/src/elements/prompt-dock.tsx +8 -8
- package/src/elements/prompt-input.tsx +10 -10
- package/src/elements/prompt-suggestions.tsx +7 -4
- package/src/elements/reasoning.tsx +1 -1
- package/src/elements/register.ts +9 -0
- package/src/elements/resizable.globals.d.ts +36 -0
- package/src/elements/resizable.tsx +24 -13
- package/src/elements/response-stream.tsx +1 -1
- package/src/elements/scroll-button.tsx +1 -1
- package/src/elements/search.tsx +6 -1
- package/src/elements/segmented.tsx +5 -11
- package/src/elements/separator.tsx +1 -1
- package/src/elements/slots.ts +57 -24
- package/src/elements/source.tsx +21 -22
- package/src/elements/switch.tsx +3 -3
- package/src/elements/tasks.tsx +5 -4
- package/src/elements/thread.tsx +36 -4
- package/src/elements/toast.tsx +3 -2
- package/src/elements/tool.tsx +1 -1
- package/src/elements/validate-messages.ts +76 -0
- package/src/elements/voice-input.tsx +7 -7
- package/src/elements/voice-output.tsx +4 -4
- package/src/index.ts +141 -6
- package/src/primitives/card-component-types.ts +63 -0
- package/src/primitives/card-data-types.ts +312 -0
- package/src/primitives/card-recovery.ts +13 -2
- package/src/primitives/card-registry.tsx +34 -23
- package/src/primitives/card-schemas/artifact.schema.json +92 -0
- package/src/primitives/card-schemas/embed.schema.json +0 -2
- package/src/primitives/card-schemas/link.schema.json +0 -2
- package/src/primitives/card-tags.ts +61 -0
- package/src/primitives/card-validate-cards.ts +255 -0
- package/src/primitives/card-validate-generator.testlib.ts +38 -0
- package/src/primitives/card-validate-schemas.ts +58 -0
- package/src/primitives/card-validate.ts +48 -20
- package/src/primitives/create-tween.ts +54 -2
- package/src/primitives/message-feedback.ts +3 -2
- package/src/primitives/use-audio-analysis.ts +20 -1
- package/src/primitives/use-sequencer.ts +20 -1
- package/src/schemas/from-tool-call.ts +151 -0
- package/src/schemas/index.ts +340 -0
- package/src/schemas/provider-subsets.ts +538 -0
- package/src/schemas/registry.ts +355 -0
- package/src/schemas/tool-defs.ts +492 -0
- package/src/solid.ts +150 -0
- package/src/state/index.ts +27 -1
- package/src/state/messages.ts +21 -4
- package/src/state/mock.ts +211 -0
- package/src/state/parts.ts +295 -0
- package/src/state/stream.ts +56 -58
- package/src/ui/collapsible.tsx +17 -5
- package/src/ui/dropdown.tsx +46 -12
- package/src/ui/overlay.tsx +10 -4
- package/src/wire/chunk.ts +201 -0
- package/src/wire/consume.ts +440 -0
- package/src/wire/encode.ts +474 -0
- package/src/wire/formats/anthropic.ts +267 -0
- package/src/wire/formats/openai.ts +187 -0
- package/src/wire/index.ts +52 -0
- package/src/wire/read.ts +152 -0
- package/src/wire/sink-helpers.ts +50 -0
- package/src/wire/sse.ts +140 -0
- package/dist/create-tween-hvpmSuwW.js +0 -7
- package/dist/elements/chunks/arrow-left-DQocUFvy.js +0 -1
- package/dist/elements/chunks/audio-visualizer-BqNy7dfm.js +0 -1
- package/dist/elements/chunks/card-BkHbp4BA.js +0 -1
- package/dist/elements/chunks/chat-thread-CkRN6pZ7.js +0 -1
- package/dist/elements/chunks/check-B9-gn6SS.js +0 -1
- package/dist/elements/chunks/chevron-down-BNsxc2hk.js +0 -1
- package/dist/elements/chunks/chevron-right-Hv00StBO.js +0 -1
- package/dist/elements/chunks/circle-0e3KJTDr.js +0 -1
- package/dist/elements/chunks/circle-check-BNAS0JEy.js +0 -1
- package/dist/elements/chunks/code-block-BFUHn6Va.js +0 -1
- package/dist/elements/chunks/context-Cb7k4F-v.js +0 -1
- package/dist/elements/chunks/conversation-list-CvoAkWhN.js +0 -1
- package/dist/elements/chunks/create-tween-oVvAIG8r.js +0 -7
- package/dist/elements/chunks/define-D1din7Ld.js +0 -1
- package/dist/elements/chunks/hover-card-Cmm6Q53m.js +0 -1
- package/dist/elements/chunks/message-BqyXDN3-.js +0 -1
- package/dist/elements/chunks/message-feedback-Djr3z6Yb.js +0 -1
- package/dist/elements/chunks/message-square-vFAgbDaX.js +0 -1
- package/dist/elements/chunks/reasoning-DTpCGtnY.js +0 -1
- package/dist/elements/chunks/scroll-button-B0cH5fXf.js +0 -1
- package/dist/elements/chunks/skeleton-DSXW5Yya.js +0 -1
- package/dist/elements/chunks/slots--V19HwUb.js +0 -1
- package/dist/elements/chunks/textarea-CUwqAX0G.js +0 -1
- package/dist/elements/chunks/tooltip-CgBZCVKp.js +0 -1
- package/dist/elements/chunks/use-card-resolution-BSu_XHjH.js +0 -1
- package/dist/elements/chunks/x-GRUeeQFT.js +0 -1
- package/dist/host-embed.d.ts +0 -33
- package/dist/kai-provider.d.ts +0 -2
- package/dist/origin.d.ts +0 -5
- package/dist/provider-runtime.d.ts +0 -27
- package/dist/register-impl-DQyejdfb.js +0 -145
- package/dist/validate.d.ts +0 -4
- package/dist/version.d.ts +0 -4
- package/src/components/component-meta.json +0 -4550
- package/src/elements/card-media.jpg +0 -0
- package/src/elements/resizable.d.ts +0 -27
- package/src/elements/video-poster.jpg +0 -0
|
@@ -1,26 +1,70 @@
|
|
|
1
|
-
import { type JSX, For, createSignal, splitProps, Show } from "solid-js";
|
|
1
|
+
import { type JSX, For, Index, Switch, Match, createMemo, createSignal, splitProps, Show } from "solid-js";
|
|
2
2
|
import { Tooltip } from "../ui/tooltip";
|
|
3
3
|
import { Copy, Check } from "lucide-solid";
|
|
4
4
|
import { cn } from "../utils/cn";
|
|
5
5
|
import { Markdown } from "./markdown";
|
|
6
6
|
import { Button } from "../ui/button";
|
|
7
7
|
import { actionIcon, BUILTIN_ACTION_LABEL } from "../ui/action-icons";
|
|
8
|
-
import type { ChatMessageAction, CustomAction, FeedbackVote } from "../elements/chat-types";
|
|
8
|
+
import type { ChatMessageAction, CustomAction, FeedbackVote, MessagePart, MessageSource } from "../elements/chat-types";
|
|
9
9
|
import { useChatConfig, textClass } from "../primitives/chat-config";
|
|
10
10
|
import { Reasoning, ReasoningTrigger, ReasoningContent } from "./reasoning";
|
|
11
|
-
import { Tool
|
|
12
|
-
import { Attachments, Attachment, AttachmentPreview, AttachmentInfo
|
|
11
|
+
import { Tool } from "./tool";
|
|
12
|
+
import { Attachments, Attachment, AttachmentPreview, AttachmentInfo } from "./attachments";
|
|
13
|
+
import { Source, SourceTrigger, SourceContent, SourceList } from "./source";
|
|
14
|
+
import { CardRenderer, type CardSchemaMap } from "./card-renderer";
|
|
15
|
+
import type { CardComponentMap } from "../primitives/card-registry";
|
|
13
16
|
|
|
14
17
|
// --- Message ---
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
/** Who is speaking in a message row. This is the SEMANTIC role of the message,
|
|
20
|
+
* not an ARIA role — see `MessageProps['role']`. */
|
|
21
|
+
export type MessageRole = 'user' | 'assistant' | 'system';
|
|
22
|
+
|
|
23
|
+
/** The accessible name given to a row that declares a speaker. `role="article"`
|
|
24
|
+
* supports naming; the bare `<div>` a role-less `<Message>` renders does not,
|
|
25
|
+
* so the label is only attached alongside that role. */
|
|
26
|
+
const MESSAGE_ROLE_LABEL: Record<MessageRole, string> = {
|
|
27
|
+
user: 'User message',
|
|
28
|
+
assistant: 'Assistant message',
|
|
29
|
+
system: 'System message',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export interface MessageProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'role'> {
|
|
17
33
|
children: JSX.Element;
|
|
34
|
+
/** Who is speaking: `'user'`, `'assistant'` or `'system'`.
|
|
35
|
+
*
|
|
36
|
+
* This SHADOWS the inherited ARIA `role` attribute on purpose, and is the
|
|
37
|
+
* reason `JSX.HTMLAttributes` is `Omit`ted above. Before this existed, `role`
|
|
38
|
+
* resolved to ARIA and was spread straight onto the row, so `<Message
|
|
39
|
+
* role="user">` shipped `role="user"` on a div — not a valid ARIA role, and a
|
|
40
|
+
* critical axe `aria-roles` violation ("Role must be one of the valid ARIA
|
|
41
|
+
* roles: user") on every message that used the prop. Chromium discards the
|
|
42
|
+
* unknown token and computes `generic`, so the damage was a failed a11y audit
|
|
43
|
+
* and a row with no accessible role or name at all, not a mis-announced one.
|
|
44
|
+
*
|
|
45
|
+
* It is now consumed here and never reaches the DOM. What the row gets
|
|
46
|
+
* instead is a VALID ARIA role that can carry a name — `role="article"` plus
|
|
47
|
+
* an `aria-label` naming the speaker — and `data-role` for styling and
|
|
48
|
+
* querying. Omit the prop and the row stays an unlabelled `<div>`, exactly as
|
|
49
|
+
* before, so the many role-less call sites are untouched.
|
|
50
|
+
*
|
|
51
|
+
* The trade-off: the row's ARIA role can no longer be set through this prop.
|
|
52
|
+
* Pass `aria-label` (or any other ARIA attribute) to override the default
|
|
53
|
+
* naming — the spread below still wins over what this computes. */
|
|
54
|
+
role?: MessageRole;
|
|
18
55
|
}
|
|
19
56
|
|
|
20
57
|
function Message(props: MessageProps) {
|
|
21
|
-
const [local, rest] = splitProps(props, ["children", "class"]);
|
|
58
|
+
const [local, rest] = splitProps(props, ["children", "class", "role"]);
|
|
22
59
|
return (
|
|
23
|
-
<div
|
|
60
|
+
<div
|
|
61
|
+
part="row"
|
|
62
|
+
class={cn("flex items-start gap-3", local.class)}
|
|
63
|
+
data-role={local.role}
|
|
64
|
+
role={local.role ? 'article' : undefined}
|
|
65
|
+
aria-label={local.role ? MESSAGE_ROLE_LABEL[local.role] : undefined}
|
|
66
|
+
{...rest}
|
|
67
|
+
>
|
|
24
68
|
{local.children}
|
|
25
69
|
</div>
|
|
26
70
|
);
|
|
@@ -29,8 +73,13 @@ function Message(props: MessageProps) {
|
|
|
29
73
|
// --- MessageAvatar ---
|
|
30
74
|
|
|
31
75
|
export interface MessageAvatarProps {
|
|
32
|
-
|
|
33
|
-
|
|
76
|
+
/** Avatar image URL. Optional: with no `src` the component renders `fallback`
|
|
77
|
+
* (initials), which is the whole point of having a fallback — requiring `src`
|
|
78
|
+
* forced `<MessageAvatar src="" …/>` at every initials-only call site. */
|
|
79
|
+
src?: string;
|
|
80
|
+
/** Alt text for the image. Only meaningful alongside `src`; defaults to `''`
|
|
81
|
+
* so an avatar stays decorative rather than announcing a filename. */
|
|
82
|
+
alt?: string;
|
|
34
83
|
fallback?: string;
|
|
35
84
|
class?: string;
|
|
36
85
|
}
|
|
@@ -53,7 +102,7 @@ function MessageAvatar(props: MessageAvatarProps) {
|
|
|
53
102
|
>
|
|
54
103
|
<img
|
|
55
104
|
src={props.src}
|
|
56
|
-
alt={props.alt}
|
|
105
|
+
alt={props.alt ?? ''}
|
|
57
106
|
class="h-full w-full object-cover"
|
|
58
107
|
/>
|
|
59
108
|
</Show>
|
|
@@ -253,18 +302,26 @@ function MessageActionBar(props: MessageActionBarProps) {
|
|
|
253
302
|
// --- MessageBody ---
|
|
254
303
|
|
|
255
304
|
export interface MessageBodyProps {
|
|
256
|
-
/** The message text
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
|
|
305
|
+
/** The message's ordered parts (text, reasoning, tool, card, source, file),
|
|
306
|
+
* rendered in a single pass in the order they appear. A run of consecutive
|
|
307
|
+
* `source` parts renders as ONE citation row (`part="citations"`), placed
|
|
308
|
+
* OUTSIDE the message bubble so a citation is never confused with a link the
|
|
309
|
+
* model typed into its prose. */
|
|
310
|
+
parts: MessagePart[];
|
|
311
|
+
/** Add/override card type -> component entries, forwarded to `CardRenderer`
|
|
312
|
+
* for `card` parts. */
|
|
313
|
+
cardTypes?: CardComponentMap;
|
|
314
|
+
/** JSON Schemas for the card types this app renders, keyed by envelope type,
|
|
315
|
+
* forwarded to `CardRenderer` for `card` parts. The companion of `cardTypes`:
|
|
316
|
+
* that says what DRAWS a card, this says what a VALID one looks like.
|
|
317
|
+
* `createCardRegistry(...).validationSchemas` is exactly this shape. Without it
|
|
318
|
+
* the kit checks its own seven built-ins and leaves your own card type
|
|
319
|
+
* unvalidated. A schema here WINS over a built-in of the same name. */
|
|
320
|
+
cardSchemas?: CardSchemaMap;
|
|
264
321
|
/** Whether this is a user message (right-aligned bubble) vs an assistant
|
|
265
322
|
* message (full-width transparent). */
|
|
266
323
|
isUser: boolean;
|
|
267
|
-
/** Whether
|
|
324
|
+
/** Whether text parts render as markdown. */
|
|
268
325
|
markdown: boolean;
|
|
269
326
|
/** Action-bar entries — built-in names and/or custom descriptors. When empty
|
|
270
327
|
* the bar is not rendered. */
|
|
@@ -289,44 +346,235 @@ export interface MessageBodyProps {
|
|
|
289
346
|
afterBody?: JSX.Element;
|
|
290
347
|
}
|
|
291
348
|
|
|
349
|
+
/** One render group over an ordered `parts` array. Two part types collapse runs:
|
|
350
|
+
* consecutive `file` parts become a single `'files'` group so they share one
|
|
351
|
+
* `<Attachments>` row (matching the pre-parts layout) instead of each opening
|
|
352
|
+
* its own, and consecutive `source` parts become a single `'sources'` group so
|
|
353
|
+
* the N citations one search produced are ONE wrapped row rather than N stacked
|
|
354
|
+
* rows. Every other part is its own `'single'` group. Pure and order-preserving:
|
|
355
|
+
* it only decides where the wrapper boundaries fall, never reorders or drops
|
|
356
|
+
* anything, so a group sits exactly where its parts sat in `parts`. */
|
|
357
|
+
export type MessagePartGroup =
|
|
358
|
+
| { kind: 'single'; part: Exclude<MessagePart, { type: 'file' } | { type: 'source' }> }
|
|
359
|
+
| { kind: 'files'; parts: Extract<MessagePart, { type: 'file' }>[] }
|
|
360
|
+
| { kind: 'sources'; parts: Extract<MessagePart, { type: 'source' }>[] };
|
|
361
|
+
|
|
362
|
+
export function groupMessageParts(parts: MessagePart[]): MessagePartGroup[] {
|
|
363
|
+
const groups: MessagePartGroup[] = [];
|
|
364
|
+
for (const part of parts) {
|
|
365
|
+
if (part.type === 'file') {
|
|
366
|
+
const last = groups[groups.length - 1];
|
|
367
|
+
if (last?.kind === 'files') {
|
|
368
|
+
groups[groups.length - 1] = { kind: 'files', parts: [...last.parts, part] };
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
groups.push({ kind: 'files', parts: [part] });
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (part.type === 'source') {
|
|
375
|
+
const last = groups[groups.length - 1];
|
|
376
|
+
if (last?.kind === 'sources') {
|
|
377
|
+
groups[groups.length - 1] = { kind: 'sources', parts: [...last.parts, part] };
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
groups.push({ kind: 'sources', parts: [part] });
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
groups.push({ kind: 'single', part });
|
|
384
|
+
}
|
|
385
|
+
return groups;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** The citation chip's label. `index` when the model numbered its citations;
|
|
389
|
+
* otherwise `undefined` so `SourceTrigger` falls back to the domain. A source
|
|
390
|
+
* with NO url has no domain to fall back to, so its title (then a generic word)
|
|
391
|
+
* stands in rather than rendering an empty chip. */
|
|
392
|
+
function citationLabel(s: MessageSource): string | number | undefined {
|
|
393
|
+
if (s.index !== undefined) return s.index;
|
|
394
|
+
if (s.url) return undefined;
|
|
395
|
+
return s.title || 'Source';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** The hover card's headline. Every field is optional, so fall back through
|
|
399
|
+
* title -> url -> a generic word. */
|
|
400
|
+
function citationTitle(s: MessageSource): string {
|
|
401
|
+
return s.title || s.url || 'Source';
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Narrow a group to one variant IN A SINGLE READ.
|
|
405
|
+
*
|
|
406
|
+
* The render body below reads its group/part through accessors (see the
|
|
407
|
+
* `<Index>` note in `MessageBody`), and TypeScript cannot narrow a
|
|
408
|
+
* discriminated union across two separate accessor CALLS —
|
|
409
|
+
* `g().kind === 'files' && g()` leaves the second call widened, because as far
|
|
410
|
+
* as the compiler knows the two calls could return different values. So the
|
|
411
|
+
* test and the cast happen together, on one already-read value. Returns
|
|
412
|
+
* `false` rather than `undefined` so it reads as a plain falsy `<Match when>`. */
|
|
413
|
+
function groupAs<T extends MessagePartGroup['kind']>(
|
|
414
|
+
group: MessagePartGroup,
|
|
415
|
+
kind: T,
|
|
416
|
+
): Extract<MessagePartGroup, { kind: T }> | false {
|
|
417
|
+
return group.kind === kind ? (group as Extract<MessagePartGroup, { kind: T }>) : false;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** The same single-read narrowing for `MessagePart`. */
|
|
421
|
+
function partAs<T extends MessagePart['type']>(
|
|
422
|
+
part: MessagePart,
|
|
423
|
+
type: T,
|
|
424
|
+
): Extract<MessagePart, { type: T }> | false {
|
|
425
|
+
return part.type === type ? (part as Extract<MessagePart, { type: T }>) : false;
|
|
426
|
+
}
|
|
427
|
+
|
|
292
428
|
/**
|
|
293
|
-
* The shared message body:
|
|
294
|
-
*
|
|
295
|
-
*
|
|
429
|
+
* The shared message body: the message's `parts` rendered in a single ordered
|
|
430
|
+
* pass (text, reasoning, tool calls, generative-UI cards, citations and file
|
|
431
|
+
* attachments interleaved exactly as they appear), followed by the action bar.
|
|
432
|
+
* Runs of `source` and `file` parts each collapse into one row. This is the
|
|
433
|
+
* single source of truth for how a message renders, consumed by `ChatThread`
|
|
296
434
|
* (the `<For>` over `messages`), the standalone `<kai-message>` facade, and (in
|
|
297
435
|
* future) `kai-compare` for each candidate. Pure/prop-driven: all interaction
|
|
298
436
|
* state (copied, feedback vote) is owned above and passed in.
|
|
299
437
|
*/
|
|
300
438
|
function MessageBody(props: MessageBodyProps) {
|
|
439
|
+
const groups = createMemo(() => groupMessageParts(props.parts));
|
|
301
440
|
return (
|
|
302
441
|
<>
|
|
303
442
|
{/* before-body (inject): a per-message header above everything else. */}
|
|
304
443
|
<Show when={props.beforeBody}>{props.beforeBody}</Show>
|
|
305
|
-
<
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
444
|
+
{/* <Index>, NOT <For>, on purpose — this is load-bearing.
|
|
445
|
+
*
|
|
446
|
+
* A streaming message re-renders once per delta with a brand-new `parts`
|
|
447
|
+
* array (a new reference IS the re-render signal), and
|
|
448
|
+
* `groupMessageParts` allocates fresh wrapper objects on top of that. A
|
|
449
|
+
* <For> is REFERENCE-keyed, so every chunk looks like an entirely new
|
|
450
|
+
* list and every row is torn down and rebuilt. Everything the user has
|
|
451
|
+
* done inside a row dies with it: expanding a tool panel or a reasoning
|
|
452
|
+
* block mid-stream silently did nothing, because the disclosure opened
|
|
453
|
+
* and was discarded microseconds later by the next token (a live probe
|
|
454
|
+
* caught the collapsibles' `createUniqueId()` walking cl-9 -> cl-11 ->
|
|
455
|
+
* cl-15 across three deltas).
|
|
456
|
+
*
|
|
457
|
+
* <Index> keys by POSITION and hands each row its value as a SIGNAL, so
|
|
458
|
+
* a row stays mounted while its content keeps updating. That is exactly
|
|
459
|
+
* the shape of a stream: the folds behind `parts`
|
|
460
|
+
* (appendTextPart/appendReasoningPart/upsertToolPart) only ever append
|
|
461
|
+
* to the end or replace one part IN PLACE with the same variant — never
|
|
462
|
+
* reorder, never change a part's type — so a part's position is a stable
|
|
463
|
+
* identity, and a growing text/reasoning block or a tool patched from
|
|
464
|
+
* `input-streaming` to `output-available` reaches the DOM through the
|
|
465
|
+
* accessor instead of through a remount.
|
|
466
|
+
*
|
|
467
|
+
* The trade-off of position-keying: splicing a part out of the MIDDLE of
|
|
468
|
+
* a message shifts the rows after it, so their local state (an open
|
|
469
|
+
* disclosure) stays with the position rather than following the part.
|
|
470
|
+
* <For> got that right for a STATIC array and got streaming wrong every
|
|
471
|
+
* single time — no consumer splices mid-message, every consumer streams.
|
|
472
|
+
*
|
|
473
|
+
* This only works while the children read through the accessors below:
|
|
474
|
+
* capturing `g().part` once re-freezes the row at its first delta. */}
|
|
475
|
+
<Index each={groups()}>
|
|
476
|
+
{(group) => (
|
|
477
|
+
<Switch fallback={null}>
|
|
478
|
+
<Match when={groupAs(group(), 'files')}>
|
|
479
|
+
{(g) => (
|
|
480
|
+
<Attachments variant="inline" class={props.isUser ? 'mb-2 justify-end' : 'mb-2'}>
|
|
481
|
+
{/* Reference-keyed <For> is right HERE: the run's part objects
|
|
482
|
+
are carried over untouched by the folds, and an attachment
|
|
483
|
+
holds no state worth preserving. */}
|
|
484
|
+
<For each={g().parts}>
|
|
485
|
+
{(fp) => (
|
|
486
|
+
<Attachment data={fp.attachment}>
|
|
487
|
+
<AttachmentPreview />
|
|
488
|
+
<AttachmentInfo />
|
|
489
|
+
</Attachment>
|
|
490
|
+
)}
|
|
491
|
+
</For>
|
|
492
|
+
</Attachments>
|
|
493
|
+
)}
|
|
494
|
+
</Match>
|
|
495
|
+
<Match when={groupAs(group(), 'sources')}>
|
|
496
|
+
{(g) => (
|
|
497
|
+
// OUTSIDE the bubble, deliberately. A citation nested inside
|
|
498
|
+
// `MessageContent` is indistinguishable from a link the MODEL
|
|
499
|
+
// typed into its prose — which is exactly how the first version
|
|
500
|
+
// of the S12 conformance check passed while proving nothing.
|
|
501
|
+
// `SourceList` is its own container, so the row is a sibling of
|
|
502
|
+
// the content part, and `part="citations"` lets a consumer target
|
|
503
|
+
// it through the shadow boundary.
|
|
504
|
+
<SourceList part="citations" class={props.isUser ? 'justify-end' : undefined}>
|
|
505
|
+
{/* Reference-keyed <For> is right HERE, as with files: the
|
|
506
|
+
run's part objects are carried over untouched by the folds. */}
|
|
507
|
+
<For each={g().parts}>
|
|
508
|
+
{(sp) => (
|
|
509
|
+
<Source href={sp.source.url}>
|
|
510
|
+
<SourceTrigger label={citationLabel(sp.source)} />
|
|
511
|
+
<SourceContent
|
|
512
|
+
title={citationTitle(sp.source)}
|
|
513
|
+
description={sp.source.snippet ?? ''}
|
|
514
|
+
/>
|
|
515
|
+
</Source>
|
|
516
|
+
)}
|
|
517
|
+
</For>
|
|
518
|
+
</SourceList>
|
|
519
|
+
)}
|
|
520
|
+
</Match>
|
|
521
|
+
<Match when={groupAs(group(), 'single')}>
|
|
522
|
+
{(g) => {
|
|
523
|
+
// An ACCESSOR, never a captured value: the row outlives the
|
|
524
|
+
// delta that rebuilt this part, so every read below has to go
|
|
525
|
+
// through here for the new content to land. `partAs` does the
|
|
526
|
+
// type test and the narrowing cast in one read (see its note).
|
|
527
|
+
const part = () => g().part;
|
|
528
|
+
const reasoning = () => partAs(part(), 'reasoning');
|
|
529
|
+
// A reasoning part with NO text is a round-trip carrier, not
|
|
530
|
+
// something to show. Anthropic's redacted_thinking blocks carry
|
|
531
|
+
// an opaque blob with no readable text, and the block assembled
|
|
532
|
+
// at content_block_stop carries the verbatim payload the encoder
|
|
533
|
+
// must echo back. Both are empty-text parts that MUST stay in
|
|
534
|
+
// `parts` (the encoder needs them, in order) and must not render
|
|
535
|
+
// a blank disclosure.
|
|
536
|
+
const carrierOnly = () => { const r = reasoning(); return r !== false && r.text === ''; };
|
|
537
|
+
const shownReasoning = () => { const r = reasoning(); return r !== false && r.text !== '' && r; };
|
|
538
|
+
return (
|
|
539
|
+
<Switch fallback={null}>
|
|
540
|
+
<Match when={partAs(part(), 'text')}>
|
|
541
|
+
{(p) => (
|
|
542
|
+
<MessageContent
|
|
543
|
+
part="bubble content"
|
|
544
|
+
markdown={props.markdown}
|
|
545
|
+
class={props.isUser
|
|
546
|
+
? 'bg-muted text-primary max-w-[85%] rounded-2xl px-4 py-2'
|
|
547
|
+
: 'bg-transparent p-0'}
|
|
548
|
+
>
|
|
549
|
+
{p().text}
|
|
550
|
+
</MessageContent>
|
|
551
|
+
)}
|
|
552
|
+
</Match>
|
|
553
|
+
<Match when={carrierOnly()}>{null}</Match>
|
|
554
|
+
<Match when={shownReasoning()}>
|
|
555
|
+
{(p) => (
|
|
556
|
+
<Reasoning class="mb-2 w-full">
|
|
557
|
+
<ReasoningTrigger>{p().label ?? 'Reasoning'}</ReasoningTrigger>
|
|
558
|
+
<ReasoningContent markdown>{p().text}</ReasoningContent>
|
|
559
|
+
</Reasoning>
|
|
560
|
+
)}
|
|
561
|
+
</Match>
|
|
562
|
+
<Match when={partAs(part(), 'tool')}>
|
|
563
|
+
{(p) => <Tool toolPart={p().tool} class="mb-2 w-full" />}
|
|
564
|
+
</Match>
|
|
565
|
+
<Match when={partAs(part(), 'card')}>
|
|
566
|
+
{(p) => <CardRenderer envelope={p().envelope} types={props.cardTypes} schemas={props.cardSchemas} />}
|
|
567
|
+
</Match>
|
|
568
|
+
{/* No `source` match here on purpose: source parts never
|
|
569
|
+
reach a 'single' group — they are collapsed into a
|
|
570
|
+
'sources' run above and rendered as one citation row. */}
|
|
571
|
+
</Switch>
|
|
572
|
+
);
|
|
573
|
+
}}
|
|
574
|
+
</Match>
|
|
575
|
+
</Switch>
|
|
311
576
|
)}
|
|
312
|
-
</
|
|
313
|
-
<For each={props.tools ?? []}>{(tp) => <Tool toolPart={tp} class="mb-2 w-full" />}</For>
|
|
314
|
-
<Show when={props.attachments?.length}>
|
|
315
|
-
<Attachments variant="inline" class={props.isUser ? 'mb-2 justify-end' : 'mb-2'}>
|
|
316
|
-
<For each={props.attachments!}>
|
|
317
|
-
{(att) => (<Attachment data={att}><AttachmentPreview /><AttachmentInfo /></Attachment>)}
|
|
318
|
-
</For>
|
|
319
|
-
</Attachments>
|
|
320
|
-
</Show>
|
|
321
|
-
<MessageContent
|
|
322
|
-
part="bubble content"
|
|
323
|
-
markdown={props.markdown}
|
|
324
|
-
class={props.isUser
|
|
325
|
-
? 'bg-muted text-primary max-w-[85%] rounded-2xl px-4 py-2'
|
|
326
|
-
: 'bg-transparent p-0'}
|
|
327
|
-
>
|
|
328
|
-
{props.content}
|
|
329
|
-
</MessageContent>
|
|
577
|
+
</Index>
|
|
330
578
|
<Show when={(props.actions?.length ?? 0) > 0}>
|
|
331
579
|
<MessageActionBar
|
|
332
580
|
actions={props.actions!}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type JSX, type Accessor, splitProps, createSignal, createContext, useContext, createEffect, onCleanup, Show } from 'solid-js';
|
|
1
|
+
import { type JSX, type Accessor, splitProps, createSignal, createContext, useContext, createEffect, createUniqueId, onCleanup, Show } from 'solid-js';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
3
|
import { ChevronDown } from 'lucide-solid';
|
|
4
4
|
import { Markdown } from './markdown';
|
|
@@ -8,6 +8,16 @@ interface ReasoningContextValue {
|
|
|
8
8
|
isOpen: () => boolean;
|
|
9
9
|
onOpenChange: (open: boolean) => void;
|
|
10
10
|
disabled: () => boolean;
|
|
11
|
+
/** Id minted on the root and shared by the trigger's `aria-controls` and the
|
|
12
|
+
* content's `id` — the same wiring `Collapsible` uses, so a screen reader can
|
|
13
|
+
* name the panel this disclosure toggles. */
|
|
14
|
+
contentId: string;
|
|
15
|
+
/** ReasoningTrigger registers its button here on mount; the content reads it
|
|
16
|
+
* when a panel holding focus collapses, so focus has somewhere to land. */
|
|
17
|
+
registerTrigger: (el: HTMLElement | undefined) => void;
|
|
18
|
+
/** The registered trigger. Deliberately a plain getter over a mutable
|
|
19
|
+
* variable, NOT a signal — registration must not re-run the content effect. */
|
|
20
|
+
trigger: () => HTMLElement | undefined;
|
|
11
21
|
}
|
|
12
22
|
|
|
13
23
|
/** Imperative open controller, handed to a parent (the kai-reasoning facade) via
|
|
@@ -78,8 +88,24 @@ function Reasoning(props: ReasoningProps) {
|
|
|
78
88
|
// by disabled — the facade's wireDisclosure applies disabled-gating itself.
|
|
79
89
|
local.controllerRef?.({ open: isOpen, setOpen: handleOpenChange });
|
|
80
90
|
|
|
91
|
+
const contentId = createUniqueId();
|
|
92
|
+
|
|
93
|
+
// The trigger element, for the focus hand-off in ReasoningContent. Read once,
|
|
94
|
+
// at the moment a panel collapses — a signal would only add a spurious
|
|
95
|
+
// dependency to the content effect when the trigger mounts.
|
|
96
|
+
let triggerEl: HTMLElement | undefined;
|
|
97
|
+
|
|
81
98
|
return (
|
|
82
|
-
<ReasoningContext.Provider
|
|
99
|
+
<ReasoningContext.Provider
|
|
100
|
+
value={{
|
|
101
|
+
isOpen,
|
|
102
|
+
onOpenChange: handleOpenChange,
|
|
103
|
+
disabled,
|
|
104
|
+
contentId,
|
|
105
|
+
registerTrigger: (el) => { triggerEl = el; },
|
|
106
|
+
trigger: () => triggerEl,
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
83
109
|
<div class={local.class}>{local.children}</div>
|
|
84
110
|
</ReasoningContext.Provider>
|
|
85
111
|
);
|
|
@@ -92,13 +118,34 @@ export interface ReasoningTriggerProps extends JSX.ButtonHTMLAttributes<HTMLButt
|
|
|
92
118
|
}
|
|
93
119
|
|
|
94
120
|
function ReasoningTrigger(props: ReasoningTriggerProps) {
|
|
95
|
-
|
|
96
|
-
|
|
121
|
+
// `ref` is split out rather than left in `rest`: the trigger takes its own ref
|
|
122
|
+
// to register as the panel's focus-return target, and a spread `ref` would
|
|
123
|
+
// silently replace it. The consumer's ref is forwarded below instead.
|
|
124
|
+
const [local, rest] = splitProps(props, ['children', 'class', 'ref']);
|
|
125
|
+
const { isOpen, onOpenChange, disabled, contentId, registerTrigger } = useReasoningContext();
|
|
97
126
|
|
|
98
127
|
return (
|
|
128
|
+
// The same disclosure contract as CollapsibleTrigger: an explicit
|
|
129
|
+
// `type="button"` (so a trigger inside a form never submits it),
|
|
130
|
+
// `aria-expanded` + `aria-controls` so assistive tech can read the state and
|
|
131
|
+
// find the panel, and the `data-state` / `data-expanded` / `data-closed`
|
|
132
|
+
// handles styling and tests hang off. `{...rest}` stays LAST so a consumer
|
|
133
|
+
// can still override any of them.
|
|
99
134
|
<button
|
|
135
|
+
ref={(el) => {
|
|
136
|
+
registerTrigger(el);
|
|
137
|
+
// Solid hands a component's `ref` down as a callback; tolerate a raw
|
|
138
|
+
// element for the hand-written case.
|
|
139
|
+
if (typeof local.ref === 'function') (local.ref as (el: HTMLButtonElement) => void)(el);
|
|
140
|
+
}}
|
|
141
|
+
type="button"
|
|
100
142
|
class={cn('flex cursor-pointer items-center gap-2 text-meta', local.class)}
|
|
101
143
|
disabled={disabled() || undefined}
|
|
144
|
+
aria-expanded={isOpen()}
|
|
145
|
+
aria-controls={contentId}
|
|
146
|
+
data-expanded={isOpen() ? '' : undefined}
|
|
147
|
+
data-closed={isOpen() ? undefined : ''}
|
|
148
|
+
data-state={isOpen() ? 'open' : 'closed'}
|
|
102
149
|
onClick={() => { if (!disabled()) onOpenChange(!isOpen()); }}
|
|
103
150
|
{...rest}
|
|
104
151
|
>
|
|
@@ -125,11 +172,29 @@ export interface ReasoningContentProps extends JSX.HTMLAttributes<HTMLDivElement
|
|
|
125
172
|
|
|
126
173
|
function ReasoningContent(props: ReasoningContentProps) {
|
|
127
174
|
const [local, rest] = splitProps(props, ['children', 'class', 'contentClass', 'markdown']);
|
|
128
|
-
const { isOpen } = useReasoningContext();
|
|
175
|
+
const { isOpen, contentId, trigger } = useReasoningContext();
|
|
129
176
|
|
|
130
177
|
let contentRef: HTMLDivElement | undefined;
|
|
131
178
|
let innerRef: HTMLDivElement | undefined;
|
|
132
179
|
|
|
180
|
+
/** Hand focus back to the trigger when the panel about to go inert is holding
|
|
181
|
+
* it. Chromium keeps `activeElement` on the now-inert node for the rest of
|
|
182
|
+
* the task and then resets the document's focus to `<body>` — so without this
|
|
183
|
+
* a user who was inside the panel loses the focus ring entirely and their
|
|
184
|
+
* next Tab restarts from the top. `Collapsible` has exactly this gap: it sets
|
|
185
|
+
* `bool:inert` and never looks at where focus was. Runs BEFORE the attribute
|
|
186
|
+
* lands, so the move is ours rather than the browser's fallback. */
|
|
187
|
+
const releaseFocus = () => {
|
|
188
|
+
if (!contentRef) return;
|
|
189
|
+
// getRootNode(), not `document`: inside the <kai-reasoning> shadow root
|
|
190
|
+
// document.activeElement only ever resolves to the HOST element.
|
|
191
|
+
const root = contentRef.getRootNode() as Document | ShadowRoot;
|
|
192
|
+
const active = root.activeElement;
|
|
193
|
+
// A disabled trigger cannot take focus; the browser then falls back to
|
|
194
|
+
// <body>, which is no worse than not trying.
|
|
195
|
+
if (active && contentRef.contains(active)) trigger()?.focus();
|
|
196
|
+
};
|
|
197
|
+
|
|
133
198
|
createEffect(() => {
|
|
134
199
|
if (!contentRef || !innerRef) return;
|
|
135
200
|
|
|
@@ -141,21 +206,43 @@ function ReasoningContent(props: ReasoningContentProps) {
|
|
|
141
206
|
|
|
142
207
|
if (isOpen()) {
|
|
143
208
|
contentRef.style.maxHeight = `${innerRef.scrollHeight}px`;
|
|
209
|
+
// Dropped in the same tick as the open, NOT on transitionend: the panel is
|
|
210
|
+
// interactive from the first frame, so an inert outliving the animation can
|
|
211
|
+
// never swallow the first click or Tab into it.
|
|
212
|
+
contentRef.removeAttribute('inert');
|
|
144
213
|
} else {
|
|
145
214
|
contentRef.style.maxHeight = '0px';
|
|
215
|
+
releaseFocus();
|
|
216
|
+
// Applied in the same tick as the collapse, again NOT on transitionend:
|
|
217
|
+
// `inert` has no visual effect of its own, so the 150ms max-height
|
|
218
|
+
// transition still runs to completion underneath it (verified in Chromium:
|
|
219
|
+
// transitionend fires and the final computed max-height is reached), while
|
|
220
|
+
// the a11y tree matches the collapsed intent immediately instead of 150ms
|
|
221
|
+
// late. setAttribute rather than the `inert` IDL property because Solid
|
|
222
|
+
// routes `inert` through Properties (el.inert = …), which jsdom does not
|
|
223
|
+
// reflect back to an attribute — the same reason Collapsible uses `bool:`.
|
|
224
|
+
contentRef.setAttribute('inert', '');
|
|
146
225
|
}
|
|
147
226
|
|
|
148
227
|
onCleanup(dispose);
|
|
149
228
|
});
|
|
150
229
|
|
|
151
230
|
return (
|
|
231
|
+
// `id` matches the trigger's `aria-controls`; `data-state` gives styling and
|
|
232
|
+
// tests an attribute handle on the panel instead of forcing them to read the
|
|
233
|
+
// animated `max-height`. `inert` while collapsed (as CollapsibleContent does)
|
|
234
|
+
// is owned by the effect above, not bound here — it has to be sequenced
|
|
235
|
+
// against the focus hand-off, and a second reactive owner would race it.
|
|
236
|
+
// `{...rest}` stays LAST so a consumer can still override.
|
|
152
237
|
<div
|
|
153
238
|
ref={contentRef}
|
|
239
|
+
id={contentId}
|
|
154
240
|
class={cn(
|
|
155
241
|
'overflow-hidden transition-[max-height] duration-150 ease-out',
|
|
156
242
|
local.class
|
|
157
243
|
)}
|
|
158
244
|
style={{ 'max-height': '0px' }}
|
|
245
|
+
data-state={isOpen() ? 'open' : 'closed'}
|
|
159
246
|
{...rest}
|
|
160
247
|
>
|
|
161
248
|
<div
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// isolation.
|
|
7
7
|
import type { ToolPart } from './tool';
|
|
8
8
|
import type { AttachmentData } from './attachments';
|
|
9
|
+
import type { MessagePart } from '../elements/chat-types';
|
|
9
10
|
|
|
10
11
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
12
|
// Types
|
|
@@ -125,3 +126,27 @@ export function isAnyStreaming(pair: ComparePair | null): boolean {
|
|
|
125
126
|
if (!pair) return false;
|
|
126
127
|
return pair.some((c) => c.streaming === true);
|
|
127
128
|
}
|
|
129
|
+
|
|
130
|
+
/** Map a `CompareCandidate`'s flat legacy fields (content/reasoning/tools/
|
|
131
|
+
* attachments) into the ordered `MessagePart[]` `MessageBody` now renders.
|
|
132
|
+
* `CompareCandidate` stays its own independent shape (it is `<kai-compare>`'s
|
|
133
|
+
* public `data` JSON, not a `ChatMessage`). This is purely a render-time
|
|
134
|
+
* adapter, applied at each `MessageBody` call site. Order: reasoning (the
|
|
135
|
+
* thinking trace) → tool calls (the actions taken) → attachments (anything
|
|
136
|
+
* produced) → the final text answer, mirroring how an assistant turn reads. */
|
|
137
|
+
export function candidateToParts(candidate: CompareCandidate): MessagePart[] {
|
|
138
|
+
const parts: MessagePart[] = [];
|
|
139
|
+
if (candidate.reasoning) {
|
|
140
|
+
parts.push({ type: 'reasoning', text: candidate.reasoning.text, label: candidate.reasoning.label });
|
|
141
|
+
}
|
|
142
|
+
for (const tool of candidate.tools ?? []) {
|
|
143
|
+
parts.push({ type: 'tool', tool });
|
|
144
|
+
}
|
|
145
|
+
for (const attachment of candidate.attachments ?? []) {
|
|
146
|
+
parts.push({ type: 'file', attachment });
|
|
147
|
+
}
|
|
148
|
+
if (candidate.content) {
|
|
149
|
+
parts.push({ type: 'text', text: candidate.content });
|
|
150
|
+
}
|
|
151
|
+
return parts;
|
|
152
|
+
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type CompareSelection,
|
|
25
25
|
type ResponseCompareData,
|
|
26
26
|
buildSelection,
|
|
27
|
+
candidateToParts,
|
|
27
28
|
isAnyStreaming,
|
|
28
29
|
normalizeCandidates,
|
|
29
30
|
} from './response-compare-types';
|
|
@@ -37,7 +38,7 @@ export type {
|
|
|
37
38
|
ResponseCompareData,
|
|
38
39
|
CompareSelection,
|
|
39
40
|
} from './response-compare-types';
|
|
40
|
-
export { normalizeCandidates, buildSelection, isAnyStreaming } from './response-compare-types';
|
|
41
|
+
export { normalizeCandidates, buildSelection, isAnyStreaming, candidateToParts } from './response-compare-types';
|
|
41
42
|
|
|
42
43
|
export type CompareLayout = 'auto' | 'columns' | 'tabs';
|
|
43
44
|
|
|
@@ -386,10 +387,7 @@ function CompareColumn(props: ColumnProps): JSX.Element {
|
|
|
386
387
|
when={props.candidate.streaming}
|
|
387
388
|
fallback={
|
|
388
389
|
<MessageBody
|
|
389
|
-
|
|
390
|
-
reasoning={props.candidate.reasoning}
|
|
391
|
-
tools={props.candidate.tools}
|
|
392
|
-
attachments={props.candidate.attachments}
|
|
390
|
+
parts={candidateToParts(props.candidate)}
|
|
393
391
|
isUser={false}
|
|
394
392
|
markdown={true}
|
|
395
393
|
/>
|
|
@@ -445,10 +443,7 @@ function CollapsedWinner(props: {
|
|
|
445
443
|
{(c) => (
|
|
446
444
|
<div class="min-w-0">
|
|
447
445
|
<MessageBody
|
|
448
|
-
|
|
449
|
-
reasoning={c().reasoning}
|
|
450
|
-
tools={c().tools}
|
|
451
|
-
attachments={c().attachments}
|
|
446
|
+
parts={candidateToParts(c())}
|
|
452
447
|
isUser={false}
|
|
453
448
|
markdown={true}
|
|
454
449
|
/>
|