@kitn.ai/ui 0.16.0 → 0.18.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 +55 -17
- package/dist/components/artifact.d.ts +39 -1
- package/dist/components/attachment-types.d.ts +12 -0
- package/dist/components/attachments.d.ts +2 -10
- package/dist/components/card.d.ts +27 -0
- package/dist/components/chain-of-thought.d.ts +78 -2
- package/dist/components/chat-scope-picker.d.ts +7 -0
- package/dist/components/chat-thread.d.ts +39 -16
- package/dist/components/choice-card.d.ts +27 -0
- package/dist/components/coachmark.d.ts +49 -0
- package/dist/components/composer-dom.d.ts +35 -0
- package/dist/components/composer-highlight.d.ts +48 -0
- package/dist/components/composer-history.d.ts +34 -0
- package/dist/components/composer.d.ts +86 -0
- package/dist/components/confirm-card.d.ts +17 -0
- package/dist/components/conversation-item.d.ts +9 -0
- package/dist/components/conversation-list.d.ts +36 -1
- package/dist/components/dismissed-stub.d.ts +22 -0
- package/dist/components/file-tree.d.ts +15 -0
- package/dist/components/form.d.ts +34 -0
- package/dist/components/markdown.d.ts +3 -0
- package/dist/components/message.d.ts +71 -2
- package/dist/components/model-switcher.d.ts +7 -0
- package/dist/components/prompt-input.d.ts +13 -4
- package/dist/components/prompt-suggestion.d.ts +9 -1
- package/dist/components/reasoning.d.ts +14 -1
- package/dist/components/response-compare-types.d.ts +66 -0
- package/dist/components/response-compare.d.ts +65 -0
- package/dist/components/screen.d.ts +46 -0
- package/dist/components/tasks-card.d.ts +50 -1
- package/dist/components/toast.d.ts +58 -0
- package/dist/components/tool-types.d.ts +10 -0
- package/dist/components/tool.d.ts +8 -8
- package/dist/components/use-card-resolution.d.ts +10 -6
- package/dist/components/voice-input.d.ts +27 -0
- package/dist/components/voice-output.d.ts +33 -0
- package/dist/core-C8fzo39E.js +12 -0
- package/dist/custom-elements.json +8472 -1691
- package/dist/elements/agent-card.d.ts +1 -0
- package/dist/elements/agent-card.js +1 -0
- package/dist/elements/artifact.js +1 -0
- package/dist/elements/attachments.js +1 -0
- package/dist/elements/autoloader.d.ts +2 -0
- package/dist/elements/autoloader.js +1 -0
- package/dist/elements/avatar.d.ts +1 -0
- package/dist/elements/avatar.js +1 -0
- package/dist/elements/badge.d.ts +1 -0
- package/dist/elements/badge.js +1 -0
- package/dist/elements/button.d.ts +1 -0
- package/dist/elements/button.js +1 -0
- package/dist/elements/card.js +1 -0
- package/dist/elements/cards.d.ts +1 -0
- package/dist/elements/cards.js +1 -0
- package/dist/elements/chain-of-thought.d.ts +0 -12
- package/dist/elements/chain-of-thought.js +1 -0
- package/dist/elements/chat-scope-picker.js +1 -0
- package/dist/elements/chat-types.d.ts +8 -2
- package/dist/elements/chat-workspace.js +1 -0
- package/dist/elements/chat.js +1 -0
- package/dist/elements/checkpoint.js +1 -0
- package/dist/elements/choice.js +1 -0
- package/dist/elements/chunks/Icon-DLLBeLG5.js +1 -0
- package/dist/elements/chunks/action-icons-UB4V5FNq.js +1 -0
- package/dist/elements/chunks/arrow-left-C-gwlUmx.js +1 -0
- package/dist/elements/chunks/attachments-CgbErf1r.js +1 -0
- package/dist/elements/chunks/badge-DTQpgB6N.js +1 -0
- package/dist/elements/chunks/bash-InADTalH.js +1 -0
- package/dist/elements/chunks/button-XDUM6lXs.js +1 -0
- package/dist/elements/chunks/card-CBlFZ-7I.js +1 -0
- package/dist/elements/chunks/card-routing-C986KuUA.js +1 -0
- package/dist/elements/chunks/chat-thread-BIhmqwNL.js +1 -0
- package/dist/elements/chunks/check-hGUoVIKb.js +1 -0
- package/dist/elements/chunks/chevron-down-DDoaSUtO.js +1 -0
- package/dist/elements/chunks/chevron-right-CdGEUakB.js +1 -0
- package/dist/elements/chunks/circle-BKAZdmzb.js +1 -0
- package/dist/elements/chunks/circle-check-CmYG-y3I.js +1 -0
- package/dist/elements/chunks/circle-x-DewgAEym.js +1 -0
- package/dist/elements/chunks/cn-CU7UAWzk.js +1 -0
- package/dist/elements/chunks/code-block-DedwxwFI.js +1 -0
- package/dist/elements/chunks/collapsible-FfgmTHtM.js +1 -0
- package/dist/elements/chunks/composer-Cfbw5HKT.js +65 -0
- package/dist/elements/chunks/context-DwJmxlRE.js +1 -0
- package/dist/elements/chunks/conversation-list-DsCQMfPq.js +1 -0
- package/dist/elements/chunks/core-AYMC6_lb.js +12 -0
- package/dist/elements/chunks/css-M7EaDHN_.js +1 -0
- package/dist/elements/chunks/default-input-BuqsoENC.js +1 -0
- package/dist/elements/chunks/define-rGsaWsLL.js +1 -0
- package/dist/elements/chunks/disclosure-CwoAkpMH.js +1 -0
- package/dist/elements/chunks/download-DJ06G3Bz.js +1 -0
- package/dist/elements/chunks/dropdown-BFg8ri_R.js +1 -0
- package/dist/elements/chunks/ellipsis-dlN6YvGw.js +1 -0
- package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +141 -0
- package/dist/elements/chunks/external-link-DBX2lc_R.js +1 -0
- package/dist/elements/chunks/file-text-HfwUx4F9.js +1 -0
- package/dist/elements/chunks/file-tree-C6Toj71u.js +1 -0
- package/dist/elements/chunks/folder-MYuqa9Xp.js +1 -0
- package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +1 -0
- package/dist/elements/chunks/github-light-JYsPkUQd.js +1 -0
- package/dist/elements/chunks/hover-card-gE7iyXdW.js +1 -0
- package/dist/elements/chunks/html-CPZ3oZQ7.js +1 -0
- package/dist/elements/chunks/icon-Vqz4ivrY.js +1 -0
- package/dist/elements/chunks/index-BCRhi84a.js +1 -0
- package/dist/elements/chunks/info-B_toFS0l.js +1 -0
- package/dist/elements/chunks/input-gzbHYZvQ.js +1 -0
- package/dist/elements/chunks/javascript-C25yR2R2.js +1 -0
- package/dist/elements/chunks/json-DxJze_jm.js +1 -0
- package/dist/elements/chunks/kbd-n49c4KZm.js +1 -0
- package/dist/elements/chunks/link-CKPs9yM0.js +1 -0
- package/dist/elements/chunks/loader-q6MamGus.js +1 -0
- package/dist/elements/chunks/markdown-DBrcqq4S.js +64 -0
- package/dist/elements/chunks/message-CTqaYI0s.js +1 -0
- package/dist/elements/chunks/message-feedback-Cvzm03w0.js +1 -0
- package/dist/elements/chunks/message-square-CUg9eRCi.js +1 -0
- package/dist/elements/chunks/minimize-2-l2klxP6K.js +1 -0
- package/dist/elements/chunks/model-switcher-CmyjiHO1.js +1 -0
- package/dist/elements/chunks/overlay-C8sOYqjT.js +1 -0
- package/dist/elements/chunks/paperclip-42Ia7xHD.js +1 -0
- package/dist/elements/chunks/progress-bar-tzKMWAwO.js +1 -0
- package/dist/elements/chunks/prompt-suggestion-D3F6vP_A.js +1 -0
- package/dist/elements/chunks/reasoning-BJNmmyov.js +1 -0
- package/dist/elements/chunks/resizable-BRsk19qI.js +1 -0
- package/dist/elements/chunks/rotate-cw-BgcggLHC.js +1 -0
- package/dist/elements/chunks/scroll-area-CgGbFFbT.js +1 -0
- package/dist/elements/chunks/separator-8hM4GdHE.js +1 -0
- package/dist/elements/chunks/settings-CaUIYOg-.js +1 -0
- package/dist/elements/chunks/settings-group-CYjOVIZQ.js +1 -0
- package/dist/elements/chunks/share-BTYM3Ddt.js +1 -0
- package/dist/elements/chunks/skeleton-DsTYrPUe.js +1 -0
- package/dist/elements/chunks/slots-DkZ-Cmha.js +1 -0
- package/dist/elements/chunks/star-9R3LBQ9n.js +1 -0
- package/dist/elements/chunks/store-CYl5Y0_u.js +1 -0
- package/dist/elements/chunks/svelte--5p79yCD.js +1 -0
- package/dist/elements/chunks/text-shimmer-CaYGv8Se.js +1 -0
- package/dist/elements/chunks/textarea-NEr2SLcD.js +1 -0
- package/dist/elements/chunks/thumbs-up-DZYZkFfc.js +1 -0
- package/dist/elements/chunks/toast-store-ClxVZRcc.js +1 -0
- package/dist/elements/chunks/tool-CHZOmoe9.js +1 -0
- package/dist/elements/chunks/tooltip-WpHElELC.js +1 -0
- package/dist/elements/chunks/triangle-alert-BlS80PUg.js +1 -0
- package/dist/elements/chunks/tsx-B8rCNbgL.js +1 -0
- package/dist/elements/chunks/typescript-RycA9KXf.js +1 -0
- package/dist/elements/chunks/use-card-resolution-VSVoxDeJ.js +1 -0
- package/dist/elements/chunks/video-DxTPZqT8.js +1 -0
- package/dist/elements/chunks/vue-BmIZj4XD.js +1 -0
- package/dist/elements/chunks/x-C1qtndxn.js +1 -0
- package/dist/elements/coachmark.d.ts +1 -0
- package/dist/elements/coachmark.js +1 -0
- package/dist/elements/code-block.js +1 -0
- package/dist/elements/command.d.ts +1 -0
- package/dist/elements/command.js +1 -0
- package/dist/elements/compare.d.ts +1 -0
- package/dist/elements/compare.js +1 -0
- package/dist/elements/composer.d.ts +1 -0
- package/dist/elements/composer.js +1 -0
- package/dist/elements/confirm-card.js +1 -0
- package/dist/elements/context-meter.js +1 -0
- package/dist/elements/conversation-list.d.ts +1 -11
- package/dist/elements/conversation-list.js +1 -0
- package/dist/elements/default-input.d.ts +20 -9
- package/dist/elements/define.d.ts +11 -0
- package/dist/elements/dialog.d.ts +1 -0
- package/dist/elements/dialog.js +1 -0
- package/dist/elements/disclosure.d.ts +37 -0
- package/dist/elements/editable-label.d.ts +1 -0
- package/dist/elements/editable-label.js +1 -0
- package/dist/elements/embed.js +1 -0
- package/dist/elements/empty.js +1 -0
- package/dist/elements/feedback-bar.js +1 -0
- package/dist/elements/file-tree.js +1 -0
- package/dist/elements/file-upload.js +1 -0
- package/dist/elements/form.js +1 -0
- package/dist/elements/hover-card.d.ts +1 -0
- package/dist/elements/hover-card.js +1 -0
- package/dist/elements/icon.d.ts +1 -0
- package/dist/elements/icon.js +1 -0
- package/dist/elements/image.js +1 -0
- package/dist/elements/input.d.ts +1 -0
- package/dist/elements/input.js +1 -0
- package/dist/elements/kbd.d.ts +1 -0
- package/dist/elements/kbd.js +1 -0
- package/dist/elements/link-preview.js +1 -0
- package/dist/elements/loader.js +1 -0
- package/dist/elements/markdown.js +1 -0
- package/dist/elements/menu.d.ts +1 -0
- package/dist/elements/menu.js +1 -0
- package/dist/elements/message-skills.d.ts +0 -16
- package/dist/elements/message-skills.js +1 -0
- package/dist/elements/message.js +1 -0
- package/dist/elements/model-switcher.d.ts +1 -11
- package/dist/elements/model-switcher.js +1 -0
- package/dist/elements/nav.d.ts +1 -0
- package/dist/elements/nav.js +1 -0
- package/dist/elements/notice.d.ts +1 -0
- package/dist/elements/notice.js +1 -0
- package/dist/elements/pane-group.d.ts +1 -0
- package/dist/elements/pane-group.js +1 -0
- package/dist/elements/pane.d.ts +1 -0
- package/dist/elements/pane.js +1 -0
- package/dist/elements/popover.js +1 -0
- package/dist/elements/progress-bar.d.ts +1 -0
- package/dist/elements/progress-bar.js +1 -0
- package/dist/elements/prompt-dock.d.ts +1 -0
- package/dist/elements/prompt-dock.js +1 -0
- package/dist/elements/prompt-input.d.ts +1 -9
- package/dist/elements/prompt-input.js +1 -0
- package/dist/elements/prompt-suggestions.d.ts +0 -6
- package/dist/elements/prompt-suggestions.js +1 -0
- package/dist/elements/reasoning.js +1 -0
- package/dist/elements/register.d.ts +10 -0
- package/dist/elements/remote.js +1 -0
- package/dist/elements/resizable.d.ts +1 -17
- package/dist/elements/resizable.js +1 -0
- package/dist/elements/response-stream.js +14 -0
- package/dist/elements/screen.d.ts +1 -0
- package/dist/elements/screen.js +1 -0
- package/dist/elements/scroll-area.d.ts +1 -0
- package/dist/elements/scroll-area.js +1 -0
- package/dist/elements/scroll-button.js +1 -0
- package/dist/elements/search.d.ts +1 -0
- package/dist/elements/search.js +1 -0
- package/dist/elements/segmented.d.ts +1 -0
- package/dist/elements/segmented.js +1 -0
- package/dist/elements/separator.d.ts +1 -0
- package/dist/elements/separator.js +1 -0
- package/dist/elements/setting-item.d.ts +1 -0
- package/dist/elements/setting-item.js +1 -0
- package/dist/elements/settings-group.d.ts +1 -0
- package/dist/elements/settings-group.js +1 -0
- package/dist/elements/skeleton.d.ts +1 -0
- package/dist/elements/skeleton.js +1 -0
- package/dist/elements/slots.d.ts +176 -0
- package/dist/elements/source.d.ts +0 -17
- package/dist/elements/source.js +1 -0
- package/dist/elements/status.d.ts +1 -0
- package/dist/elements/status.js +1 -0
- package/dist/elements/switch.js +1 -0
- package/dist/elements/tabs.d.ts +1 -0
- package/dist/elements/tabs.js +1 -0
- package/dist/elements/tasks.js +1 -0
- package/dist/elements/text-shimmer.js +1 -0
- package/dist/elements/thinking-bar.js +1 -0
- package/dist/elements/toast.d.ts +1 -0
- package/dist/elements/toast.js +1 -0
- package/dist/elements/tool.js +1 -0
- package/dist/elements/tooltip.d.ts +1 -0
- package/dist/elements/tooltip.js +1 -0
- package/dist/elements/voice-input.js +1 -0
- package/dist/elements/voice-output.d.ts +1 -0
- package/dist/elements/voice-output.js +1 -0
- package/dist/elements.d.ts +772 -61
- package/dist/engine-javascript-C1x7zo1_.js +141 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.js +14405 -9810
- package/dist/kai.es.js +1 -0
- package/dist/llms/llms-full.txt +1234 -107
- package/dist/llms/llms.txt +4 -4
- package/dist/mcp.es.js +217 -6
- package/dist/primitives/card-contract.d.ts +20 -4
- package/dist/primitives/card-recovery.d.ts +55 -0
- package/dist/primitives/composer-model.d.ts +22 -0
- package/dist/primitives/composer-triggers.d.ts +19 -0
- package/dist/primitives/create-kai-chat.d.ts +38 -0
- package/dist/primitives/message-feedback.d.ts +35 -0
- package/dist/primitives/toast-store.d.ts +118 -0
- package/dist/primitives/use-speech-recognition.d.ts +26 -0
- package/dist/react/index.d.ts +1166 -98
- package/dist/react/runtime.d.ts +9 -2
- package/dist/react/use-kai-chat.d.ts +41 -0
- package/dist/react.js +575 -186
- package/dist/register-impl-CYWGkgbI.js +141 -0
- package/dist/schemas/choice.schema.json +5 -0
- package/dist/schemas/tasks.schema.json +7 -2
- package/dist/state/index.d.ts +4 -0
- package/dist/state/messages.d.ts +11 -0
- package/dist/state/stream.d.ts +22 -0
- package/dist/state/suggestions.d.ts +4 -0
- package/dist/state.js +117 -0
- package/dist/theme.tokens.css +87 -0
- package/dist/types.d.ts +4 -0
- package/dist/ui/agent-card.d.ts +61 -0
- package/dist/ui/button.d.ts +5 -2
- package/dist/ui/card.d.ts +61 -0
- package/dist/ui/collapsible.d.ts +15 -1
- package/dist/ui/command.d.ts +33 -0
- package/dist/ui/dialog.d.ts +42 -0
- package/dist/ui/dropdown.d.ts +95 -1
- package/dist/ui/editable-label.d.ts +26 -0
- package/dist/ui/hover-card.d.ts +13 -1
- package/dist/ui/icon.d.ts +18 -0
- package/dist/ui/input.d.ts +33 -0
- package/dist/ui/kbd.d.ts +21 -0
- package/dist/ui/nav.d.ts +62 -0
- package/dist/ui/notice.d.ts +31 -0
- package/dist/ui/overlay.d.ts +12 -0
- package/dist/ui/pane-grid.d.ts +58 -0
- package/dist/ui/pane-group.d.ts +75 -0
- package/dist/ui/pane.d.ts +78 -0
- package/dist/ui/popover.d.ts +11 -1
- package/dist/ui/progress-bar.d.ts +23 -0
- package/dist/ui/prompt-dock.d.ts +96 -0
- package/dist/ui/resizable.d.ts +12 -0
- package/dist/ui/scroll-area.d.ts +4 -0
- package/dist/ui/segmented.d.ts +28 -0
- package/dist/ui/settings-group.d.ts +32 -0
- package/dist/ui/skeleton.d.ts +12 -0
- package/dist/ui/stat.d.ts +16 -0
- package/dist/ui/status.d.ts +13 -0
- package/dist/ui/switch.d.ts +8 -1
- package/dist/ui/tabs.d.ts +35 -0
- package/dist/ui/tooltip.d.ts +20 -1
- package/frameworks/react/index.tsx +1100 -163
- package/frameworks/react/runtime.tsx +30 -1
- package/frameworks/react/use-kai-chat.tsx +87 -0
- package/llms-full.txt +1234 -107
- package/llms.txt +4 -4
- package/package.json +36 -12
- package/src/agent-tooling/mcp/manifest.ts +14 -0
- package/src/agent-tooling/mcp/tools/debug.ts +194 -6
- package/src/agent-tooling/mcp/tools/reference.ts +32 -0
- package/src/agent-tooling/mcp/tools/scaffold.ts +162 -1
- package/src/components/artifact.tsx +98 -13
- package/src/components/attachment-types.ts +13 -0
- package/src/components/attachments.tsx +4 -14
- package/src/components/card.tsx +109 -15
- package/src/components/chain-of-thought.tsx +210 -1
- package/src/components/chat-scope-picker.tsx +13 -3
- package/src/components/chat-thread.tsx +238 -137
- package/src/components/choice-card.tsx +136 -15
- package/src/components/coachmark.tsx +245 -0
- package/src/components/component-meta.json +921 -108
- package/src/components/composer-dom.ts +159 -0
- package/src/components/composer-highlight.ts +242 -0
- package/src/components/composer-history.ts +82 -0
- package/src/components/composer.tsx +979 -0
- package/src/components/confirm-card.tsx +69 -2
- package/src/components/conversation-item.tsx +69 -7
- package/src/components/conversation-list.tsx +134 -37
- package/src/components/dismissed-stub.tsx +80 -0
- package/src/components/file-tree.tsx +197 -47
- package/src/components/form-widgets.tsx +9 -7
- package/src/components/form.tsx +161 -20
- package/src/components/image.tsx +3 -5
- package/src/components/link-preview.tsx +5 -4
- package/src/components/markdown.tsx +5 -2
- package/src/components/message.tsx +174 -14
- package/src/components/model-switcher.tsx +19 -4
- package/src/components/prompt-input.tsx +46 -75
- package/src/components/prompt-suggestion.tsx +48 -16
- package/src/components/reasoning.tsx +29 -6
- package/src/components/response-compare-types.ts +127 -0
- package/src/components/response-compare.tsx +460 -0
- package/src/components/screen.tsx +176 -0
- package/src/components/tasks-card.tsx +374 -23
- package/src/components/toast.tsx +486 -0
- package/src/components/tool-types.ts +10 -0
- package/src/components/tool.tsx +22 -16
- package/src/components/use-card-resolution.ts +24 -8
- package/src/components/voice-input.tsx +102 -19
- package/src/components/voice-output.tsx +202 -0
- package/src/elements/agent-card.tsx +67 -0
- package/src/elements/artifact.tsx +53 -4
- package/src/elements/attachments.tsx +1 -0
- package/src/elements/autoloader.ts +89 -0
- package/src/elements/avatar.tsx +33 -0
- package/src/elements/badge.tsx +32 -0
- package/src/elements/button.tsx +126 -0
- package/src/elements/card-media.jpg +0 -0
- package/src/elements/card.tsx +124 -31
- package/src/elements/cards.tsx +81 -7
- package/src/elements/chain-of-thought.tsx +77 -32
- package/src/elements/chat-scope-picker.tsx +35 -8
- package/src/elements/chat-types.ts +9 -2
- package/src/elements/chat-workspace.tsx +157 -43
- package/src/elements/chat.tsx +49 -25
- package/src/elements/choice.tsx +71 -19
- package/src/elements/coachmark.tsx +90 -0
- package/src/elements/command.tsx +209 -0
- package/src/elements/compare.tsx +110 -0
- package/src/elements/compiled.css +1 -1
- package/src/elements/composer.tsx +151 -0
- package/src/elements/confirm-card.tsx +36 -11
- package/src/elements/conversation-list.tsx +80 -11
- package/src/elements/default-input.tsx +75 -53
- package/src/elements/define.tsx +47 -6
- package/src/elements/dialog.tsx +98 -0
- package/src/elements/disclosure.ts +73 -0
- package/src/elements/editable-label.tsx +125 -0
- package/src/elements/element-manifest.json +310 -0
- package/src/elements/element-meta.json +5954 -1605
- package/src/elements/element-types.d.ts +771 -60
- package/src/elements/file-tree.tsx +12 -2
- package/src/elements/file-upload.tsx +1 -1
- package/src/elements/form.tsx +54 -11
- package/src/elements/hover-card.tsx +80 -0
- package/src/elements/icon-names.json +48 -0
- package/src/elements/icon.tsx +41 -0
- package/src/elements/input.tsx +203 -0
- package/src/elements/kbd.tsx +45 -0
- package/src/elements/menu.tsx +217 -0
- package/src/elements/message.tsx +84 -55
- package/src/elements/model-switcher.tsx +27 -1
- package/src/elements/nav.tsx +87 -0
- package/src/elements/notice.tsx +53 -0
- package/src/elements/pane-group.tsx +119 -0
- package/src/elements/pane.tsx +129 -0
- package/src/elements/popover.tsx +39 -17
- package/src/elements/progress-bar.tsx +39 -0
- package/src/elements/prompt-dock.tsx +81 -0
- package/src/elements/prompt-input.tsx +112 -63
- package/src/elements/prompt-suggestions.tsx +25 -9
- package/src/elements/reasoning.tsx +30 -9
- package/src/elements/register-impl.ts +41 -3
- package/src/elements/register.ts +20 -3
- package/src/elements/remote.tsx +4 -0
- package/src/elements/resizable.tsx +48 -11
- package/src/elements/screen.tsx +90 -0
- package/src/elements/scroll-area.tsx +33 -0
- package/src/elements/search.tsx +180 -0
- package/src/elements/segmented.tsx +135 -0
- package/src/elements/separator.tsx +36 -0
- package/src/elements/setting-item.tsx +50 -0
- package/src/elements/settings-group.tsx +37 -0
- package/src/elements/skeleton.tsx +45 -0
- package/src/elements/slots.ts +607 -0
- package/src/elements/source.tsx +13 -6
- package/src/elements/status.tsx +42 -0
- package/src/elements/styles.css +19 -0
- package/src/elements/switch.tsx +89 -14
- package/src/elements/tabs.tsx +95 -0
- package/src/elements/tasks.tsx +70 -14
- package/src/elements/toast.tsx +89 -0
- package/src/elements/tool.tsx +43 -9
- package/src/elements/tooltip.tsx +85 -0
- package/src/elements/video-poster.jpg +0 -0
- package/src/elements/voice-input.tsx +66 -16
- package/src/elements/voice-output.tsx +69 -0
- package/src/index.ts +35 -2
- package/src/primitives/card-contract.ts +14 -5
- package/src/primitives/card-recovery.ts +149 -0
- package/src/primitives/card-routing.ts +3 -0
- package/src/primitives/card-schemas/choice.schema.json +5 -0
- package/src/primitives/card-schemas/tasks.schema.json +7 -2
- package/src/primitives/composer-model.ts +49 -0
- package/src/primitives/composer-triggers.ts +35 -0
- package/src/primitives/create-kai-chat.ts +66 -0
- package/src/primitives/message-feedback.ts +123 -0
- package/src/primitives/toast-store.ts +276 -0
- package/src/primitives/use-speech-recognition.ts +135 -0
- package/src/state/index.ts +6 -0
- package/src/state/messages.ts +36 -0
- package/src/state/stream.ts +92 -0
- package/src/state/suggestions.ts +9 -0
- package/src/types.ts +4 -0
- package/src/ui/agent-card.tsx +212 -0
- package/src/ui/button.tsx +16 -5
- package/src/ui/card.tsx +245 -0
- package/src/ui/collapsible.tsx +30 -4
- package/src/ui/command.tsx +99 -0
- package/src/ui/dialog.tsx +217 -0
- package/src/ui/dropdown.tsx +375 -13
- package/src/ui/editable-label.tsx +112 -0
- package/src/ui/hover-card.tsx +28 -5
- package/src/ui/icon.tsx +110 -0
- package/src/ui/input.tsx +132 -0
- package/src/ui/kbd.tsx +118 -0
- package/src/ui/nav.tsx +282 -0
- package/src/ui/notice.tsx +84 -0
- package/src/ui/overlay.tsx +108 -2
- package/src/ui/pane-grid.tsx +116 -0
- package/src/ui/pane-group.tsx +280 -0
- package/src/ui/pane.tsx +193 -0
- package/src/ui/popover.tsx +16 -2
- package/src/ui/progress-bar.tsx +83 -0
- package/src/ui/prompt-dock.tsx +145 -0
- package/src/ui/resizable.tsx +69 -1
- package/src/ui/scroll-area.tsx +14 -3
- package/src/ui/segmented.tsx +109 -0
- package/src/ui/settings-group.tsx +67 -0
- package/src/ui/skeleton.tsx +62 -5
- package/src/ui/stat.tsx +41 -0
- package/src/ui/status.tsx +48 -0
- package/src/ui/switch.tsx +49 -23
- package/src/ui/tabs.tsx +168 -0
- package/src/ui/tooltip.tsx +57 -7
- package/theme.css +113 -0
- package/dist/components/slash-command.d.ts +0 -15
- package/dist/kitn-chat.es.js +0 -1
- package/dist/register-impl-wCsFPoh9.js +0 -77
- package/dist/register-ujhr1nBS.js +0 -1
- package/src/components/slash-command.tsx +0 -232
- package/src/elements/framework-usage.json +0 -563
- /package/dist/{kitn-chat-provider.d.ts → kai-provider.d.ts} +0 -0
- /package/dist/{kitn-chat-provider.es.js → kai-provider.es.js} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineWebComponent } from './define';
|
|
2
|
-
import { VoiceInput } from '../components/voice-input';
|
|
2
|
+
import { VoiceInput, type VoiceInputController } from '../components/voice-input';
|
|
3
3
|
|
|
4
4
|
interface Props extends Record<string, unknown> {
|
|
5
5
|
/**
|
|
@@ -10,32 +10,82 @@ interface Props extends Record<string, unknown> {
|
|
|
10
10
|
transcribe?: (audio: Blob) => Promise<string>;
|
|
11
11
|
/** Disable the mic button (non-interactive). */
|
|
12
12
|
disabled?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* BCP-47 language tag for the native `SpeechRecognition` path (e.g. `en-US`).
|
|
15
|
+
* Attribute: `recognition-lang` (the plain `lang` attribute is reserved by
|
|
16
|
+
* `HTMLElement` and can't be a custom-element property). No effect when
|
|
17
|
+
* `transcribe` is set or the browser lacks SpeechRecognition.
|
|
18
|
+
*/
|
|
19
|
+
recognitionLang?: string;
|
|
20
|
+
/** Emit live partial transcripts (`kai-transcript-interim`) during native
|
|
21
|
+
* recognition. Attribute: `interim`. No-op on the transcribe/fallback paths. */
|
|
22
|
+
interim?: boolean;
|
|
13
23
|
}
|
|
14
24
|
|
|
15
25
|
/** Events fired by `<kai-voice-input>`. */
|
|
16
26
|
interface Events {
|
|
17
27
|
/** Raw audio captured (before transcription) — for hosts that prefer to
|
|
18
|
-
* handle transcription themselves instead of via the `transcribe` property.
|
|
28
|
+
* handle transcription themselves instead of via the `transcribe` property.
|
|
29
|
+
* Also the unsupported-fallback signal: no `transcribe`, no SpeechRecognition,
|
|
30
|
+
* so only the blob is produced (no text). */
|
|
19
31
|
'kai-audio-captured': { blob: Blob };
|
|
20
|
-
/**
|
|
32
|
+
/** Final transcript — the `transcribe` property resolved, OR native
|
|
33
|
+
* `SpeechRecognition` produced final text (no `transcribe` set). */
|
|
21
34
|
'kai-transcription': { text: string };
|
|
35
|
+
/** Live partial transcript during native recognition (only when `interim` is
|
|
36
|
+
* set). Fires repeatedly before the final `kai-transcription`. */
|
|
37
|
+
'kai-transcript-interim': { text: string };
|
|
38
|
+
/** Recording started or stopped — lets the host drive its own UI (waveform,
|
|
39
|
+
* push-to-talk indicator) in sync with the mic. Fires on real transitions
|
|
40
|
+
* only (manual click and programmatic start()/stop()), never on mount. */
|
|
41
|
+
'kai-recording-change': { recording: boolean };
|
|
22
42
|
}
|
|
23
43
|
|
|
24
44
|
/**
|
|
25
|
-
* `<kai-voice-input>` — a mic button that records and transcribes.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
45
|
+
* `<kai-voice-input>` — a mic button that records and transcribes. Works
|
|
46
|
+
* natively by default: with no `transcribe` callback it uses the browser's
|
|
47
|
+
* `SpeechRecognition` (Chrome/Safari; no Firefox — and cloud-based in Chrome).
|
|
48
|
+
* Set `el.transcribe` to route audio through your own async transcriber instead.
|
|
49
|
+
* Where neither is available it records the blob and emits `kai-audio-captured`
|
|
50
|
+
* with no text. Also emits `kai-transcription` (final text) and, with `interim`,
|
|
51
|
+
* `kai-transcript-interim` (live partials).
|
|
28
52
|
*/
|
|
29
53
|
defineWebComponent<Props, Events>('kai-voice-input', {
|
|
30
54
|
transcribe: undefined,
|
|
31
55
|
disabled: false,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
56
|
+
recognitionLang: undefined,
|
|
57
|
+
interim: false,
|
|
58
|
+
}, (props, { dispatch, flag, expose }) => {
|
|
59
|
+
// Pattern C: the VoiceInput component owns useVoiceRecorder; it hands up a
|
|
60
|
+
// start/stop controller and an onRecordingChange callback. The facade captures
|
|
61
|
+
// the controller and exposes delegating methods (start/stop both run the same
|
|
62
|
+
// record→transcribe path as the mic click, so manual + programmatic emit the
|
|
63
|
+
// same kai-audio-captured/kai-transcription/kai-recording-change events).
|
|
64
|
+
let controller: VoiceInputController | undefined;
|
|
65
|
+
expose({
|
|
66
|
+
/** Begin recording programmatically (e.g. push-to-talk bound to a global
|
|
67
|
+
* key). Runs the same getUserMedia path as clicking the mic; no-ops if
|
|
68
|
+
* already recording. */
|
|
69
|
+
start: () => controller?.start(),
|
|
70
|
+
/** Stop the in-progress recording, producing the blob (→ kai-audio-captured)
|
|
71
|
+
* and running transcription. Pairs with start() for push-to-talk. */
|
|
72
|
+
stop: () => controller?.stop(),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<VoiceInput
|
|
77
|
+
disabled={flag('disabled')}
|
|
78
|
+
hasTranscribe={typeof props.transcribe === 'function'}
|
|
79
|
+
lang={props.recognitionLang}
|
|
80
|
+
interim={flag('interim')}
|
|
81
|
+
onTranscribe={async (blob) => {
|
|
82
|
+
dispatch('kai-audio-captured', { blob });
|
|
83
|
+
return props.transcribe ? props.transcribe(blob) : '';
|
|
84
|
+
}}
|
|
85
|
+
onTranscription={(text) => dispatch('kai-transcription', { text })}
|
|
86
|
+
onInterim={(text) => dispatch('kai-transcript-interim', { text })}
|
|
87
|
+
onRecordingChange={(recording) => dispatch('kai-recording-change', { recording })}
|
|
88
|
+
controllerRef={(c) => (controller = c)}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { defineWebComponent } from './define';
|
|
2
|
+
import { VoiceOutput, type VoiceOutputController } from '../components/voice-output';
|
|
3
|
+
|
|
4
|
+
interface Props extends Record<string, unknown> {
|
|
5
|
+
/** The utterance to read aloud. */
|
|
6
|
+
text?: string;
|
|
7
|
+
/** Speak automatically when `text` is set/changed. */
|
|
8
|
+
autoplay?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* TTS model seam the host supplies — given text, returns an audio `Blob` to
|
|
11
|
+
* play. This is a **function-valued property** (`el.synthesize = async text =>
|
|
12
|
+
* blob`); when set, the native `speechSynthesis` path is bypassed. Mirrors
|
|
13
|
+
* `<kai-voice-input>`'s `transcribe`. A value-returning callback can't be
|
|
14
|
+
* modelled as a fire-and-forget event, hence a property.
|
|
15
|
+
*/
|
|
16
|
+
synthesize?: (text: string) => Promise<Blob>;
|
|
17
|
+
/** Disable the button (non-interactive). */
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Events fired by `<kai-voice-output>`. */
|
|
22
|
+
interface Events {
|
|
23
|
+
/** Playback started or stopped — drive your own UI in sync. Fires on real
|
|
24
|
+
* transitions only (manual click and programmatic speak()/stop()), never on mount. */
|
|
25
|
+
'kai-speaking-change': { speaking: boolean };
|
|
26
|
+
/** The model path (`synthesize`) resolved audio — the raw `Blob` before playback. */
|
|
27
|
+
'kai-synthesized': { blob: Blob };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* `<kai-voice-output>` — a speaker button that reads `text` aloud. Native by
|
|
32
|
+
* default (`speechSynthesis`); set `el.synthesize` to route through your TTS
|
|
33
|
+
* model instead. The output sibling of `<kai-voice-input>`. Emits
|
|
34
|
+
* `kai-speaking-change` and (model path) `kai-synthesized`.
|
|
35
|
+
*/
|
|
36
|
+
defineWebComponent<Props, Events>('kai-voice-output', {
|
|
37
|
+
text: '',
|
|
38
|
+
autoplay: false,
|
|
39
|
+
synthesize: undefined,
|
|
40
|
+
disabled: false,
|
|
41
|
+
}, (props, { dispatch, flag, expose }) => {
|
|
42
|
+
// Pattern C: the VoiceOutput component owns playback; it hands up a
|
|
43
|
+
// speak/pause/resume/stop controller. The facade captures it and exposes
|
|
44
|
+
// delegating methods (manual + programmatic run the same path, so both emit
|
|
45
|
+
// the same kai-speaking-change / kai-synthesized events).
|
|
46
|
+
let controller: VoiceOutputController | undefined;
|
|
47
|
+
expose({
|
|
48
|
+
/** Speak the current `text` (native, or via `synthesize` if set). */
|
|
49
|
+
speak: () => controller?.speak(),
|
|
50
|
+
/** Pause playback (resumable). */
|
|
51
|
+
pause: () => controller?.pause(),
|
|
52
|
+
/** Resume paused playback. */
|
|
53
|
+
resume: () => controller?.resume(),
|
|
54
|
+
/** Stop playback and reset. */
|
|
55
|
+
stop: () => controller?.stop(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<VoiceOutput
|
|
60
|
+
text={(props.text as string | undefined) ?? ''}
|
|
61
|
+
autoplay={flag('autoplay')}
|
|
62
|
+
disabled={flag('disabled')}
|
|
63
|
+
onSynthesize={props.synthesize ? (text) => props.synthesize!(text) : undefined}
|
|
64
|
+
onSpeakingChange={(speaking) => dispatch('kai-speaking-change', { speaking })}
|
|
65
|
+
onSynthesized={(blob) => dispatch('kai-synthesized', { blob })}
|
|
66
|
+
controllerRef={(c) => (controller = c)}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,8 @@ export type { ModelOption, SearchFilters, ConversationScope, ConversationSummary
|
|
|
5
5
|
export { cn } from './utils/cn';
|
|
6
6
|
|
|
7
7
|
// Layer 1: Headless Primitives
|
|
8
|
+
export { createKaiChat } from './primitives/create-kai-chat';
|
|
9
|
+
export type { CreateKaiChatOptions, KaiChatStore } from './primitives/create-kai-chat';
|
|
8
10
|
export { useAutoResize } from './primitives/use-auto-resize';
|
|
9
11
|
export { useStickToBottom } from './primitives/use-stick-to-bottom';
|
|
10
12
|
export { useTextStream } from './primitives/use-text-stream';
|
|
@@ -18,6 +20,16 @@ export type { CodeHighlightingOptions } from './primitives/highlighter';
|
|
|
18
20
|
export { configurePdfPreview, isPdfPreviewEnabled } from './primitives/pdf-preview';
|
|
19
21
|
export type { PdfPreviewOptions } from './primitives/pdf-preview';
|
|
20
22
|
|
|
23
|
+
// Toasts: imperative `toast()` API + the reactive store behind <kai-toast-region>
|
|
24
|
+
export { toast, configureToasts, ensureMounted as ensureToastRegion, getToasts } from './primitives/toast-store';
|
|
25
|
+
export type {
|
|
26
|
+
ToastItem, ToastVariant, ToastAction, ToastOptions, ToastHandle, ToastFn, ToastConfig,
|
|
27
|
+
} from './primitives/toast-store';
|
|
28
|
+
export { Toast, ToastRegion } from './components/toast';
|
|
29
|
+
export type {
|
|
30
|
+
ToastRegionProps, ToastDismissReason, ToastPosition,
|
|
31
|
+
} from './components/toast';
|
|
32
|
+
|
|
21
33
|
// Card Contract (generative-UI foundation)
|
|
22
34
|
export { CARD_CONTRACT_VERSION } from './primitives/card-contract';
|
|
23
35
|
export type {
|
|
@@ -27,6 +39,10 @@ export { applyResolution, resolutionFromEvent } from './primitives/card-resoluti
|
|
|
27
39
|
export { CardProvider, useCardHost } from './primitives/card-host';
|
|
28
40
|
export type { CardProviderProps } from './primitives/card-host';
|
|
29
41
|
export { CARD_EVENT_NAME, emitCardEvent, routeCardEvent, listenForCardEvents } from './primitives/card-routing';
|
|
42
|
+
export { dismissRecovery, defaultIsReopenable } from './primitives/card-recovery';
|
|
43
|
+
export type {
|
|
44
|
+
RecoveryToast, ReopenEnv, DismissRecoveryOptions,
|
|
45
|
+
} from './primitives/card-recovery';
|
|
30
46
|
export { validateAgainstSchema } from './primitives/card-validate';
|
|
31
47
|
export type { JsonSchema, ValidationResult } from './primitives/card-validate';
|
|
32
48
|
|
|
@@ -47,6 +63,8 @@ export type { CardComponent, CardComponentMap, CardTagMap } from './primitives/c
|
|
|
47
63
|
// Card: kai-card (base shell) + kai-form (JSON-Schema form renderer)
|
|
48
64
|
export { Card } from './components/card';
|
|
49
65
|
export type { CardProps } from './components/card';
|
|
66
|
+
export { DismissedStub, stubIntent } from './components/dismissed-stub';
|
|
67
|
+
export type { DismissedStubProps, DismissedCardType } from './components/dismissed-stub';
|
|
50
68
|
export { Form, validateForm, buildResult, widgetFor, orderedKeys, coerceValue } from './components/form';
|
|
51
69
|
export type {
|
|
52
70
|
FormProps,
|
|
@@ -168,14 +186,26 @@ export {
|
|
|
168
186
|
} from './components/chat-container';
|
|
169
187
|
export { Message, MessageAvatar, MessageContent, MessageActions, MessageAction, MessageCopyButton } from './components/message';
|
|
170
188
|
export type { MessageCopyButtonProps } from './components/message';
|
|
189
|
+
export { ResponseCompare, useResolved } from './components/response-compare';
|
|
190
|
+
export type { ResponseCompareProps, CompareLayout, ResolvedController } from './components/response-compare';
|
|
191
|
+
export {
|
|
192
|
+
normalizeCandidates,
|
|
193
|
+
buildSelection,
|
|
194
|
+
isAnyStreaming,
|
|
195
|
+
} from './components/response-compare';
|
|
196
|
+
export type {
|
|
197
|
+
CompareCandidate,
|
|
198
|
+
ComparePair,
|
|
199
|
+
CompareCollapse,
|
|
200
|
+
ResponseCompareData,
|
|
201
|
+
CompareSelection,
|
|
202
|
+
} from './components/response-compare';
|
|
171
203
|
export { MessageSkills } from './components/message-skills';
|
|
172
204
|
export type { Skill as MessageSkill } from './components/message-skills';
|
|
173
205
|
export {
|
|
174
206
|
PromptInput, PromptInputTextarea, PromptInputActions, PromptInputAction,
|
|
175
207
|
usePromptInput,
|
|
176
208
|
} from './components/prompt-input';
|
|
177
|
-
export { SlashCommand } from './components/slash-command';
|
|
178
|
-
export type { SlashCommandItem, SlashCommandProps } from './components/slash-command';
|
|
179
209
|
export { ResponseStream } from './components/response-stream';
|
|
180
210
|
export { Markdown } from './components/markdown';
|
|
181
211
|
export { CodeBlock, CodeBlockCode, CodeBlockGroup } from './components/code-block';
|
|
@@ -251,3 +281,6 @@ export type {
|
|
|
251
281
|
AttachmentInfoProps, AttachmentRemoveProps, AttachmentEmptyProps,
|
|
252
282
|
AttachmentHoverCardProps, AttachmentHoverCardTriggerProps, AttachmentHoverCardContentProps,
|
|
253
283
|
} from './components/attachments';
|
|
284
|
+
|
|
285
|
+
// Chat message types — public API for consumers who need to type their own message arrays.
|
|
286
|
+
export type { ChatMessage, ChatMessageAction, CustomAction, AvatarData, FeedbackVote } from './elements/chat-types';
|
|
@@ -38,18 +38,24 @@ export type CardEvent =
|
|
|
38
38
|
| { kind: 'resize'; cardId: string; height: number }
|
|
39
39
|
| { kind: 'state'; cardId: string; patch: unknown }
|
|
40
40
|
| { kind: 'dismiss'; cardId: string }
|
|
41
|
+
| { kind: 'reopen'; cardId: string }
|
|
41
42
|
| { kind: 'error'; cardId: string; message: string };
|
|
42
43
|
|
|
43
44
|
export type CardEventKind = CardEvent['kind'];
|
|
44
45
|
|
|
45
46
|
/** How a card was resolved by the user — the re-hydration channel for the chromed
|
|
46
|
-
* read-only state.
|
|
47
|
-
* resolution is just the event that resolved the
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* read-only state. The terminal kinds (`action`/`submit`) mirror the two terminal
|
|
48
|
+
* CardEvents (minus `cardId`): the resolution is just the event that resolved the
|
|
49
|
+
* card. `dismissed` is a DEFERRED (non-terminal) state — the user set the card
|
|
50
|
+
* aside; it can be re-opened. `expired` is terminal — a dismissed card the host
|
|
51
|
+
* decided can no longer be re-opened (e.g. the agent proceeded, or it went stale).
|
|
52
|
+
* `at` is optional ISO-8601 provenance (data only; never rendered). Additive —
|
|
53
|
+
* does not bump the contract version. */
|
|
50
54
|
export type CardResolution =
|
|
51
55
|
| { kind: 'action'; action: string; payload?: unknown; at?: string }
|
|
52
|
-
| { kind: 'submit'; data: unknown; at?: string }
|
|
56
|
+
| { kind: 'submit'; data: unknown; at?: string }
|
|
57
|
+
| { kind: 'dismissed'; at?: string }
|
|
58
|
+
| { kind: 'expired'; reason?: string; at?: string };
|
|
53
59
|
|
|
54
60
|
/** What every card is handed (via native context or the iframe bridge). */
|
|
55
61
|
export interface CardHost {
|
|
@@ -65,6 +71,9 @@ export interface CardPolicy {
|
|
|
65
71
|
onOpen?: (url: string, target: 'tab' | 'artifact') => void;
|
|
66
72
|
onState?: (cardId: string, patch: unknown) => void;
|
|
67
73
|
onDismiss?: (cardId: string) => void;
|
|
74
|
+
/** A dismissed card asked to be re-opened. The host decides whether it can come
|
|
75
|
+
* back (clear the resolution → live again) or has expired (stamp `expired`). */
|
|
76
|
+
onReopen?: (cardId: string) => void;
|
|
68
77
|
onError?: (cardId: string, message: string) => void;
|
|
69
78
|
/** Cap on send-prompt: 'compose' (default) forbids silent sends. 'send' to allow. */
|
|
70
79
|
maxSendPromptMode?: 'compose' | 'send';
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// src/primitives/card-recovery.ts
|
|
2
|
+
// Recovery glue for the card dismiss/reopen flow. `dismissRecovery` builds the
|
|
3
|
+
// `{ onDismiss, onReopen }` half of a CardPolicy for a host that keeps its cards in
|
|
4
|
+
// some store: it writes a `dismissed` resolution onto the matching envelope (a NEW
|
|
5
|
+
// array reference, never an in-place mutation, so a Solid/React host re-renders),
|
|
6
|
+
// optionally shows an injected "Dismissed · Undo" toast (Undo restores the prior
|
|
7
|
+
// resolution), and on reopen decides — via `isReopenable` — whether the card comes
|
|
8
|
+
// back live (resolution cleared) or `expired`.
|
|
9
|
+
//
|
|
10
|
+
// The toast is an INJECTED ADAPTER, never an import — cards (and this helper) stay
|
|
11
|
+
// decoupled from the toast module. The re-appear rule is the host's: "the agent
|
|
12
|
+
// already proceeded" is a host fact, surfaced through `isReopenable`.
|
|
13
|
+
import type { CardEnvelope, CardPolicy, CardResolution } from './card-contract';
|
|
14
|
+
|
|
15
|
+
/** The minimal toast surface this helper needs. Inject a real adapter (e.g. one
|
|
16
|
+
* backed by `kai-toast-region`) — this module never imports the toast itself. */
|
|
17
|
+
export interface RecoveryToast {
|
|
18
|
+
show(opts: {
|
|
19
|
+
message: string;
|
|
20
|
+
action?: { label: string; onClick: () => void };
|
|
21
|
+
durationMs?: number;
|
|
22
|
+
}): { dismiss(): void };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Context handed to a custom `isReopenable` predicate. */
|
|
26
|
+
export interface ReopenEnv {
|
|
27
|
+
cardId: string;
|
|
28
|
+
/** The card's current resolution at reopen time (should be `dismissed`). */
|
|
29
|
+
resolution: CardResolution | undefined;
|
|
30
|
+
/** `Date.now()` at reopen time (injected for testability). */
|
|
31
|
+
now: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DismissRecoveryOptions {
|
|
35
|
+
/** Read the current cards array. */
|
|
36
|
+
get: () => CardEnvelope[];
|
|
37
|
+
/** Write the next cards array (a NEW reference). */
|
|
38
|
+
set: (next: CardEnvelope[]) => void;
|
|
39
|
+
/** Optional injected toast adapter for the "Dismissed · Undo" affordance. */
|
|
40
|
+
toast?: RecoveryToast;
|
|
41
|
+
/** Override the default re-openable rule. Return false to expire instead. */
|
|
42
|
+
isReopenable?: (env: ReopenEnv) => boolean;
|
|
43
|
+
/** A dismissed card older than this (ms since `dismissed.at`) is no longer
|
|
44
|
+
* re-openable (→ expired). Omit/Infinity = never stale. */
|
|
45
|
+
staleAfterMs?: number;
|
|
46
|
+
/** Auto-dismiss the Undo toast after this long. Default 6000ms. */
|
|
47
|
+
undoMs?: number;
|
|
48
|
+
/** Injected clock (defaults to Date.now); keeps the helper testable. */
|
|
49
|
+
now?: () => number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The terminal resolution kinds that block a reopen (a finished card can't come back). */
|
|
53
|
+
const TERMINAL_KINDS = new Set<CardResolution['kind']>(['action', 'submit', 'expired']);
|
|
54
|
+
|
|
55
|
+
/** Default re-openable rule: a card can come back unless it carries a terminal
|
|
56
|
+
* resolution OR it has gone stale (`staleAfterMs` elapsed since `dismissed.at`).
|
|
57
|
+
* A `dismissed` card with no/invalid `at` is always re-openable. */
|
|
58
|
+
export function defaultIsReopenable(env: ReopenEnv, staleAfterMs?: number): boolean {
|
|
59
|
+
const r = env.resolution;
|
|
60
|
+
if (r && TERMINAL_KINDS.has(r.kind)) return false;
|
|
61
|
+
if (
|
|
62
|
+
staleAfterMs !== undefined &&
|
|
63
|
+
Number.isFinite(staleAfterMs) &&
|
|
64
|
+
r?.kind === 'dismissed' &&
|
|
65
|
+
typeof r.at === 'string'
|
|
66
|
+
) {
|
|
67
|
+
const at = Date.parse(r.at);
|
|
68
|
+
if (!Number.isNaN(at) && env.now - at >= staleAfterMs) return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Return a new cards array with `cardId`'s envelope stamped with `resolution`
|
|
74
|
+
* (or cleared when `resolution` is undefined). Same reference if nothing changed. */
|
|
75
|
+
function withResolution(
|
|
76
|
+
cards: CardEnvelope[],
|
|
77
|
+
cardId: string,
|
|
78
|
+
resolution: CardResolution | undefined,
|
|
79
|
+
): CardEnvelope[] {
|
|
80
|
+
let changed = false;
|
|
81
|
+
const next = cards.map((c) => {
|
|
82
|
+
if (c.id !== cardId) return c;
|
|
83
|
+
changed = true;
|
|
84
|
+
if (resolution === undefined) {
|
|
85
|
+
if (c.resolution === undefined) return c;
|
|
86
|
+
const { resolution: _drop, ...rest } = c;
|
|
87
|
+
return rest;
|
|
88
|
+
}
|
|
89
|
+
return { ...c, resolution };
|
|
90
|
+
});
|
|
91
|
+
return changed ? next : cards;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The current resolution on `cardId`, if any. */
|
|
95
|
+
function resolutionOf(cards: CardEnvelope[], cardId: string): CardResolution | undefined {
|
|
96
|
+
return cards.find((c) => c.id === cardId)?.resolution;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Build the `{ onDismiss, onReopen }` policy handlers for the card dismiss/recovery
|
|
101
|
+
* flow over a host store (`get`/`set`).
|
|
102
|
+
*
|
|
103
|
+
* - `onDismiss(cardId)` — stamps `{ kind:'dismissed', at }` immutably and, when a
|
|
104
|
+
* `toast` is injected, shows "Dismissed" with an Undo that restores the card's
|
|
105
|
+
* prior resolution (live again when there was none).
|
|
106
|
+
* - `onReopen(cardId)` — clears the resolution (live) when `isReopenable`, else
|
|
107
|
+
* stamps `{ kind:'expired', at }`.
|
|
108
|
+
*/
|
|
109
|
+
export function dismissRecovery(
|
|
110
|
+
opts: DismissRecoveryOptions,
|
|
111
|
+
): Pick<CardPolicy, 'onDismiss' | 'onReopen'> {
|
|
112
|
+
const now = opts.now ?? (() => Date.now());
|
|
113
|
+
const reopenable = opts.isReopenable ?? ((env: ReopenEnv) => defaultIsReopenable(env, opts.staleAfterMs));
|
|
114
|
+
const undoMs = opts.undoMs ?? 6000;
|
|
115
|
+
|
|
116
|
+
const onDismiss = (cardId: string): void => {
|
|
117
|
+
const before = opts.get();
|
|
118
|
+
const prior = resolutionOf(before, cardId);
|
|
119
|
+
const dismissed: CardResolution = { kind: 'dismissed', at: new Date(now()).toISOString() };
|
|
120
|
+
opts.set(withResolution(before, cardId, dismissed));
|
|
121
|
+
|
|
122
|
+
opts.toast?.show({
|
|
123
|
+
message: 'Dismissed',
|
|
124
|
+
durationMs: undoMs,
|
|
125
|
+
action: {
|
|
126
|
+
label: 'Undo',
|
|
127
|
+
onClick: () => {
|
|
128
|
+
// Restore the prior resolution (undefined → live again). Read fresh in
|
|
129
|
+
// case the store moved on; only touch this card.
|
|
130
|
+
opts.set(withResolution(opts.get(), cardId, prior));
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const onReopen = (cardId: string): void => {
|
|
137
|
+
const before = opts.get();
|
|
138
|
+
const current = resolutionOf(before, cardId);
|
|
139
|
+
const env: ReopenEnv = { cardId, resolution: current, now: now() };
|
|
140
|
+
if (reopenable(env)) {
|
|
141
|
+
opts.set(withResolution(before, cardId, undefined));
|
|
142
|
+
} else {
|
|
143
|
+
const expired: CardResolution = { kind: 'expired', at: new Date(now()).toISOString() };
|
|
144
|
+
opts.set(withResolution(before, cardId, expired));
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return { onDismiss, onReopen };
|
|
149
|
+
}
|
|
@@ -59,6 +59,9 @@ export function routeCardEvent(policy: CardPolicy | undefined, event: CardEvent)
|
|
|
59
59
|
case 'dismiss':
|
|
60
60
|
p.onDismiss ? p.onDismiss(event.cardId) : warnNoHandler('dismiss');
|
|
61
61
|
break;
|
|
62
|
+
case 'reopen':
|
|
63
|
+
p.onReopen ? p.onReopen(event.cardId) : warnNoHandler('reopen');
|
|
64
|
+
break;
|
|
62
65
|
case 'error':
|
|
63
66
|
p.onError ? p.onError(event.cardId, event.message) : warnNoHandler('error');
|
|
64
67
|
break;
|
|
@@ -65,6 +65,11 @@
|
|
|
65
65
|
"allowOther": {
|
|
66
66
|
"description": "Optional free-text escape ('none of these / specify'). `true` appends a final 'Other…' option; an object customizes its `label`/`placeholder`. Selecting it reveals a text input; the shared Submit button then emits `{ kind:'action', action:'__other__', payload:{ text } }`.",
|
|
67
67
|
"x-kai-control": "allow-other"
|
|
68
|
+
},
|
|
69
|
+
"dismissible": {
|
|
70
|
+
"type": "boolean",
|
|
71
|
+
"default": false,
|
|
72
|
+
"description": "Show a close affordance that emits the `dismiss` verb. The card collapses to a re-openable 'dismissed' stub; the host decides via `onReopen` whether it comes back live or `expired`."
|
|
68
73
|
}
|
|
69
74
|
}
|
|
70
75
|
}
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"properties": {
|
|
9
9
|
"mode": {
|
|
10
10
|
"type": "string",
|
|
11
|
-
"enum": ["select"],
|
|
11
|
+
"enum": ["select", "progress"],
|
|
12
12
|
"default": "select",
|
|
13
|
-
"description": "
|
|
13
|
+
"description": "'select' (default) = toggle + a confirm button that emits `submit`. 'progress' = an onboarding-checklist look (header `done / total` count, circular indicators, per-item title/description, no confirm button, where checking a row IS the action). Both share the selection model.",
|
|
14
14
|
"x-kai-mode": true
|
|
15
15
|
},
|
|
16
16
|
"heading": {
|
|
@@ -73,6 +73,11 @@
|
|
|
73
73
|
"minimum": 1,
|
|
74
74
|
"description": "Optional maximum selectable; further toggles are blocked once reached.",
|
|
75
75
|
"x-kai-select-max": true
|
|
76
|
+
},
|
|
77
|
+
"dismissible": {
|
|
78
|
+
"type": "boolean",
|
|
79
|
+
"default": false,
|
|
80
|
+
"description": "Show a close affordance that emits the `dismiss` verb. The card collapses to a re-openable 'dismissed' stub; the host decides via `onReopen` whether it comes back live or `expired`."
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
83
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface EntityRef {
|
|
2
|
+
kind: string;
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
icon?: string;
|
|
6
|
+
promptText?: string;
|
|
7
|
+
data?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export type Segment =
|
|
10
|
+
| { type: 'text'; text: string }
|
|
11
|
+
| { type: 'entity'; entity: EntityRef };
|
|
12
|
+
export type ComposerDoc = Segment[];
|
|
13
|
+
|
|
14
|
+
export function normalizeValue(value: string | ComposerDoc | null | undefined): ComposerDoc {
|
|
15
|
+
if (value == null) return [];
|
|
16
|
+
if (typeof value === 'string') return value.length ? [{ type: 'text', text: value }] : [];
|
|
17
|
+
const out: ComposerDoc = [];
|
|
18
|
+
for (const seg of value) {
|
|
19
|
+
if (seg.type === 'text') {
|
|
20
|
+
if (!seg.text) continue;
|
|
21
|
+
const last = out[out.length - 1];
|
|
22
|
+
// Replace (don't mutate) the prior segment so every object in the returned
|
|
23
|
+
// doc is freshly built and never an aliased/held reference.
|
|
24
|
+
if (last && last.type === 'text') out[out.length - 1] = { type: 'text', text: last.text + seg.text };
|
|
25
|
+
else out.push({ type: 'text', text: seg.text });
|
|
26
|
+
} else {
|
|
27
|
+
out.push({ type: 'entity', entity: seg.entity });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function serializeToText(
|
|
34
|
+
doc: ComposerDoc,
|
|
35
|
+
opts?: { entity?: (e: EntityRef) => string },
|
|
36
|
+
): string {
|
|
37
|
+
let out = '';
|
|
38
|
+
for (const seg of doc) {
|
|
39
|
+
if (seg.type === 'text') out += seg.text;
|
|
40
|
+
else out += opts?.entity ? opts.entity(seg.entity) : (seg.entity.promptText ?? seg.entity.label);
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
export function entitiesOf(doc: ComposerDoc): EntityRef[] {
|
|
45
|
+
return doc.filter((s): s is Extract<Segment, { type: 'entity' }> => s.type === 'entity').map((s) => s.entity);
|
|
46
|
+
}
|
|
47
|
+
export function docIsEmpty(doc: ComposerDoc): boolean {
|
|
48
|
+
return doc.every((s) => s.type === 'text' && s.text.length === 0);
|
|
49
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface ActiveTrigger { char: string; query: string; start: number; }
|
|
2
|
+
|
|
3
|
+
export function detectTrigger(text: string, caret: number, chars: string[]): ActiveTrigger | null {
|
|
4
|
+
// Caret is a position within `text` (0..text.length). Out-of-range = no trigger.
|
|
5
|
+
if (caret < 0 || caret > text.length) return null;
|
|
6
|
+
for (let i = caret - 1; i >= 0; i--) {
|
|
7
|
+
const c = text[i];
|
|
8
|
+
if (/\s/.test(c)) return null; // hit whitespace before any trigger char
|
|
9
|
+
if (chars.includes(c)) {
|
|
10
|
+
const boundaryOk = i === 0 || /\s/.test(text[i - 1]);
|
|
11
|
+
if (!boundaryOk) return null;
|
|
12
|
+
return { char: c, query: text.slice(i + 1, caret), start: i };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Wraps detectTrigger with a list of trigger definitions.
|
|
20
|
+
* Returns { def, query, start } when a trigger is active, else null.
|
|
21
|
+
* Accepts generic defs so it does not import from composer.tsx (no cycle).
|
|
22
|
+
*/
|
|
23
|
+
export function activeTriggerFor<T extends { char: string; kind: string }>(
|
|
24
|
+
text: string,
|
|
25
|
+
caret: number,
|
|
26
|
+
defs: T[],
|
|
27
|
+
): { def: T; query: string; start: number } | null {
|
|
28
|
+
if (!defs.length) return null;
|
|
29
|
+
const chars = defs.map((d) => d.char);
|
|
30
|
+
const hit = detectTrigger(text, caret, chars);
|
|
31
|
+
if (!hit) return null;
|
|
32
|
+
const def = defs.find((d) => d.char === hit.char);
|
|
33
|
+
if (!def) return null;
|
|
34
|
+
return { def, query: hit.query, start: hit.start };
|
|
35
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/primitives/create-kai-chat.ts
|
|
2
|
+
import { createSignal } from 'solid-js';
|
|
3
|
+
import type { ChatMessage } from '../elements/chat-types';
|
|
4
|
+
import type { AttachmentData } from '../components/attachments';
|
|
5
|
+
import {
|
|
6
|
+
appendMessage, updateMessage, removeMessage, addSuggestion, removeSuggestion,
|
|
7
|
+
createAssistantStream, onStreamSettled, type AssistantStream, type SetMessages,
|
|
8
|
+
} from '../state';
|
|
9
|
+
|
|
10
|
+
export interface CreateKaiChatOptions {
|
|
11
|
+
/** Seed messages, read once at creation and copied. Later changes are ignored — drive updates through the returned ops. */
|
|
12
|
+
initialMessages?: ChatMessage[];
|
|
13
|
+
/** Seed suggestions, read once at creation and copied. Later changes are ignored. */
|
|
14
|
+
initialSuggestions?: string[];
|
|
15
|
+
onSubmit?: (detail: { value: string; attachments: AttachmentData[] }) => void | Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Solid store: consumer-owned chat state + the same ergonomic ops as `useKaiChat`. */
|
|
19
|
+
export interface KaiChatStore {
|
|
20
|
+
messages: () => ChatMessage[];
|
|
21
|
+
setMessages: SetMessages;
|
|
22
|
+
suggestions: () => string[];
|
|
23
|
+
loading: () => boolean;
|
|
24
|
+
append: (msg: ChatMessage) => void;
|
|
25
|
+
update: (id: string, patch: Partial<ChatMessage> | ((m: ChatMessage) => ChatMessage)) => void;
|
|
26
|
+
remove: (id: string) => void;
|
|
27
|
+
addSuggestion: (s: string) => void;
|
|
28
|
+
removeSuggestion: (s: string) => void;
|
|
29
|
+
clearSuggestions: () => void;
|
|
30
|
+
streamAssistant: (init?: Partial<ChatMessage>) => AssistantStream;
|
|
31
|
+
handleSubmit: (event: CustomEvent<{ value: string; attachments: AttachmentData[] }>) => void;
|
|
32
|
+
/** Spread onto `<kai-chat {...chat.bind} />` (reactive getters). Wire submit via `on:kai-submit={chat.handleSubmit}`. */
|
|
33
|
+
bind: { readonly messages: ChatMessage[]; readonly loading: boolean; readonly suggestions: string[] };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createKaiChat(options: CreateKaiChatOptions = {}): KaiChatStore {
|
|
37
|
+
// Copy the seed arrays so a caller mutating the array it passed in can't reach our state.
|
|
38
|
+
const [messages, setMessagesSignal] = createSignal<ChatMessage[]>([...(options.initialMessages ?? [])]);
|
|
39
|
+
const [suggestions, setSuggestions] = createSignal<string[]>([...(options.initialSuggestions ?? [])]);
|
|
40
|
+
const [loading, setLoading] = createSignal(false);
|
|
41
|
+
|
|
42
|
+
const setMessages: SetMessages = (updater) => setMessagesSignal((prev) => updater(prev));
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
messages,
|
|
46
|
+
setMessages,
|
|
47
|
+
suggestions,
|
|
48
|
+
loading,
|
|
49
|
+
append: (msg) => setMessages((prev) => appendMessage(prev, msg)),
|
|
50
|
+
update: (id, patch) => setMessages((prev) => updateMessage(prev, id, patch)),
|
|
51
|
+
remove: (id) => setMessages((prev) => removeMessage(prev, id)),
|
|
52
|
+
addSuggestion: (s) => setSuggestions((prev) => addSuggestion(prev, s)),
|
|
53
|
+
removeSuggestion: (s) => setSuggestions((prev) => removeSuggestion(prev, s)),
|
|
54
|
+
clearSuggestions: () => setSuggestions([]),
|
|
55
|
+
streamAssistant: (init) => {
|
|
56
|
+
setLoading(true);
|
|
57
|
+
return onStreamSettled(createAssistantStream(setMessages, init), () => setLoading(false));
|
|
58
|
+
},
|
|
59
|
+
handleSubmit: (event) => { void options.onSubmit?.(event.detail); },
|
|
60
|
+
bind: {
|
|
61
|
+
get messages() { return messages(); },
|
|
62
|
+
get loading() { return loading(); },
|
|
63
|
+
get suggestions() { return suggestions(); },
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|