@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,43 +1,126 @@
|
|
|
1
|
-
import { splitProps, Show, For, createSignal } from 'solid-js';
|
|
1
|
+
import { splitProps, Show, For, createSignal, createEffect, on } from 'solid-js';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
3
|
import { Button } from '../ui/button';
|
|
4
4
|
import { Tooltip } from '../ui/tooltip';
|
|
5
5
|
import { useVoiceRecorder } from '../primitives/use-voice-recorder';
|
|
6
|
+
import { useSpeechRecognition } from '../primitives/use-speech-recognition';
|
|
7
|
+
|
|
8
|
+
/** Imperative handle exposed via `controllerRef` — surfaces the recorder's latent
|
|
9
|
+
* start/stop so the `<kai-voice-input>` facade can forward them as instance
|
|
10
|
+
* methods (push-to-talk). Both run the SAME getUserMedia → blob → transcription
|
|
11
|
+
* path as clicking the mic, so manual + programmatic emit identically. */
|
|
12
|
+
export interface VoiceInputController {
|
|
13
|
+
/** Begin recording programmatically (same path as clicking the mic). */
|
|
14
|
+
start(): void;
|
|
15
|
+
/** Stop the in-progress recording (produces the blob → onTranscribe). */
|
|
16
|
+
stop(): void;
|
|
17
|
+
}
|
|
6
18
|
|
|
7
19
|
export interface VoiceInputProps {
|
|
8
20
|
onTranscribe: (audio: Blob) => Promise<string>;
|
|
9
21
|
onTranscription: (text: string) => void;
|
|
10
22
|
disabled?: boolean;
|
|
11
23
|
class?: string;
|
|
24
|
+
/** Host supplied a `transcribe` callback. When true the MediaRecorder →
|
|
25
|
+
* onTranscribe path runs. When false the component prefers native
|
|
26
|
+
* SpeechRecognition (capable browsers), falling back to record-only. */
|
|
27
|
+
hasTranscribe?: boolean;
|
|
28
|
+
/** BCP-47 language tag for native recognition (e.g. `en-US`). */
|
|
29
|
+
lang?: string;
|
|
30
|
+
/** Emit live partial transcripts via `onInterim` (native path only). */
|
|
31
|
+
interim?: boolean;
|
|
32
|
+
/** Live partial transcript from native recognition (when `interim`). */
|
|
33
|
+
onInterim?: (text: string) => void;
|
|
34
|
+
/** Fires whenever recording starts or stops. Guarded against the spurious
|
|
35
|
+
* initial `false` — only true transitions emit (the facade maps this to
|
|
36
|
+
* kai-recording-change). */
|
|
37
|
+
onRecordingChange?: (recording: boolean) => void;
|
|
38
|
+
/** Receive the imperative controller once mounted. The `<kai-voice-input>`
|
|
39
|
+
* facade forwards these as element methods (start/stop). */
|
|
40
|
+
controllerRef?: (controller: VoiceInputController) => void;
|
|
12
41
|
}
|
|
13
42
|
|
|
14
43
|
export function VoiceInput(props: VoiceInputProps) {
|
|
15
44
|
const [local] = splitProps(props, ['onTranscribe', 'onTranscription', 'disabled', 'class']);
|
|
16
45
|
const { isRecording, start, stop } = useVoiceRecorder();
|
|
46
|
+
const speech = useSpeechRecognition({ interim: props.interim });
|
|
17
47
|
const [isProcessing, setIsProcessing] = createSignal(false);
|
|
18
48
|
|
|
49
|
+
// Path selection (§6): a `transcribe` callback always wins (MediaRecorder →
|
|
50
|
+
// host transcriber). Otherwise prefer native SpeechRecognition when the browser
|
|
51
|
+
// supports it. The unsupported-no-callback case falls through to recording the
|
|
52
|
+
// blob (→ onTranscribe → kai-audio-captured) with no text.
|
|
53
|
+
const useNative = () => !props.hasTranscribe && speech.isSupported;
|
|
54
|
+
|
|
19
55
|
const label = () =>
|
|
20
|
-
isProcessing() ? 'Transcribing...' :
|
|
56
|
+
isProcessing() ? 'Transcribing...' : isActive() ? 'Stop recording' : 'Voice input';
|
|
21
57
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
58
|
+
const isActive = () => (useNative() ? speech.isListening() : isRecording());
|
|
59
|
+
|
|
60
|
+
// Native path: SpeechRecognition resolves with the final transcript when it
|
|
61
|
+
// ends (click again or controller.stop()). Final text → onTranscription.
|
|
62
|
+
async function beginRecognition() {
|
|
63
|
+
try {
|
|
64
|
+
const text = await speech.start({
|
|
65
|
+
lang: props.lang,
|
|
66
|
+
onInterim: props.interim ? (t) => props.onInterim?.(t) : undefined,
|
|
67
|
+
});
|
|
68
|
+
if (text.trim()) local.onTranscription(text.trim());
|
|
69
|
+
} catch {
|
|
70
|
+
/* surfaced via speech.error; the mic returns to idle */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Begin recording, then run transcription once the blob resolves. Shared by the
|
|
75
|
+
// mic click and the imperative start() so both flows emit identically. start()
|
|
76
|
+
// resolves its Blob when the recorder stops (click again or controller.stop()).
|
|
77
|
+
async function beginRecording() {
|
|
78
|
+
try {
|
|
79
|
+
const blob = await start();
|
|
80
|
+
setIsProcessing(true);
|
|
26
81
|
try {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const text = await local.onTranscribe(blob);
|
|
31
|
-
if (text.trim()) local.onTranscription(text.trim());
|
|
32
|
-
} finally {
|
|
33
|
-
setIsProcessing(false);
|
|
34
|
-
}
|
|
35
|
-
} catch {
|
|
82
|
+
const text = await local.onTranscribe(blob);
|
|
83
|
+
if (text.trim()) local.onTranscription(text.trim());
|
|
84
|
+
} finally {
|
|
36
85
|
setIsProcessing(false);
|
|
37
86
|
}
|
|
87
|
+
} catch {
|
|
88
|
+
setIsProcessing(false);
|
|
38
89
|
}
|
|
39
90
|
}
|
|
40
91
|
|
|
92
|
+
// Drive whichever path is active (§6: start/stop must follow the live path).
|
|
93
|
+
function begin() {
|
|
94
|
+
if (useNative()) void beginRecognition();
|
|
95
|
+
else void beginRecording();
|
|
96
|
+
}
|
|
97
|
+
function end() {
|
|
98
|
+
if (useNative()) speech.stop();
|
|
99
|
+
else stop();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function handleClick() {
|
|
103
|
+
if (isActive()) end();
|
|
104
|
+
else begin();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Emit recording transitions to the host. `on(..., { defer: true })` skips the
|
|
108
|
+
// initial mount value (a spurious {recording:false}) — only real start/stop
|
|
109
|
+
// transitions fire. Drives BOTH manual (click) and programmatic (start/stop)
|
|
110
|
+
// since they all flip the active path's signal (MediaRecorder or recognition).
|
|
111
|
+
createEffect(on(isActive, (recording) => {
|
|
112
|
+
props.onRecordingChange?.(recording);
|
|
113
|
+
}, { defer: true }));
|
|
114
|
+
|
|
115
|
+
// Imperative controller (Pattern C): hand the facade a start/stop handle.
|
|
116
|
+
// start() runs the active path; stop() ends the in-progress session (resolving
|
|
117
|
+
// the active path's promise). Guard start() so a double-start while already
|
|
118
|
+
// active is a no-op (matches the click toggle).
|
|
119
|
+
props.controllerRef?.({
|
|
120
|
+
start: () => { if (!isActive()) begin(); },
|
|
121
|
+
stop: () => end(),
|
|
122
|
+
});
|
|
123
|
+
|
|
41
124
|
// Mic icon (not recording)
|
|
42
125
|
const MicIcon = () => (
|
|
43
126
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -65,7 +148,7 @@ export function VoiceInput(props: VoiceInputProps) {
|
|
|
65
148
|
return (
|
|
66
149
|
<div class={cn('relative inline-flex items-center justify-center', local.class)}>
|
|
67
150
|
{/* Animated pulse rings when recording */}
|
|
68
|
-
<Show when={
|
|
151
|
+
<Show when={isActive()}>
|
|
69
152
|
<For each={[0, 1, 2]}>
|
|
70
153
|
{(index) => (
|
|
71
154
|
<div
|
|
@@ -88,16 +171,16 @@ export function VoiceInput(props: VoiceInputProps) {
|
|
|
88
171
|
disabled={local.disabled || isProcessing()}
|
|
89
172
|
class={cn(
|
|
90
173
|
'relative z-10 rounded-full transition-all duration-300',
|
|
91
|
-
|
|
174
|
+
isActive() && 'bg-destructive text-white hover:bg-destructive/80',
|
|
92
175
|
)}
|
|
93
176
|
>
|
|
94
177
|
<Show when={isProcessing()}>
|
|
95
178
|
<Spinner />
|
|
96
179
|
</Show>
|
|
97
|
-
<Show when={!isProcessing() &&
|
|
180
|
+
<Show when={!isProcessing() && isActive()}>
|
|
98
181
|
<StopIcon />
|
|
99
182
|
</Show>
|
|
100
|
-
<Show when={!isProcessing() && !
|
|
183
|
+
<Show when={!isProcessing() && !isActive()}>
|
|
101
184
|
<MicIcon />
|
|
102
185
|
</Show>
|
|
103
186
|
</Button>
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { splitProps, Show, createSignal, createEffect, on, onCleanup } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
import { Button } from '../ui/button';
|
|
4
|
+
import { Tooltip } from '../ui/tooltip';
|
|
5
|
+
|
|
6
|
+
/** Imperative handle exposed via `controllerRef` — surfaces the playback controls
|
|
7
|
+
* so the `<kai-voice-output>` facade can forward them as instance methods. Both
|
|
8
|
+
* the native (speechSynthesis) and model (synthesize → Audio) paths run through
|
|
9
|
+
* these, so manual clicks and programmatic calls behave identically. */
|
|
10
|
+
export interface VoiceOutputController {
|
|
11
|
+
/** Speak the current `text` (native by default, model if `synthesize` is set). */
|
|
12
|
+
speak(): void;
|
|
13
|
+
/** Pause playback (resumable). */
|
|
14
|
+
pause(): void;
|
|
15
|
+
/** Resume paused playback. */
|
|
16
|
+
resume(): void;
|
|
17
|
+
/** Stop playback and reset. */
|
|
18
|
+
stop(): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface VoiceOutputProps {
|
|
22
|
+
/** The utterance to read aloud. */
|
|
23
|
+
text: string;
|
|
24
|
+
/** Speak automatically when `text` is set/changed. */
|
|
25
|
+
autoplay?: boolean;
|
|
26
|
+
/** TTS model seam: given text, return an audio Blob to play. When set, the
|
|
27
|
+
* native speechSynthesis path is bypassed. Mirrors VoiceInput's `onTranscribe`. */
|
|
28
|
+
onSynthesize?: (text: string) => Promise<Blob>;
|
|
29
|
+
/** Fires whenever playback starts or stops. */
|
|
30
|
+
onSpeakingChange?: (speaking: boolean) => void;
|
|
31
|
+
/** Fires once the model path resolves audio (model path only). */
|
|
32
|
+
onSynthesized?: (blob: Blob) => void;
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
class?: string;
|
|
35
|
+
/** Receive the imperative controller once mounted. The `<kai-voice-output>`
|
|
36
|
+
* facade forwards these as element methods (speak/pause/resume/stop). */
|
|
37
|
+
controllerRef?: (controller: VoiceOutputController) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** True when the browser exposes the Web Speech synthesis API. */
|
|
41
|
+
function hasSpeechSynthesis(): boolean {
|
|
42
|
+
return typeof window !== 'undefined' && 'speechSynthesis' in window;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function VoiceOutput(props: VoiceOutputProps) {
|
|
46
|
+
const [local] = splitProps(props, ['text', 'disabled', 'class']);
|
|
47
|
+
const [isSpeaking, setIsSpeaking] = createSignal(false);
|
|
48
|
+
const [isLoading, setIsLoading] = createSignal(false);
|
|
49
|
+
|
|
50
|
+
// The model path plays through one reused <Audio>; the native path drives
|
|
51
|
+
// speechSynthesis. Only one is live at a time (branch on onSynthesize).
|
|
52
|
+
let audio: HTMLAudioElement | undefined;
|
|
53
|
+
let objectUrl: string | undefined;
|
|
54
|
+
let utterance: SpeechSynthesisUtterance | undefined;
|
|
55
|
+
|
|
56
|
+
// No way to speak at all → render disabled. A model seam OR native synthesis
|
|
57
|
+
// is enough; absent both, the control is inert (see the facade/spec).
|
|
58
|
+
const capable = () => !!props.onSynthesize || hasSpeechSynthesis();
|
|
59
|
+
|
|
60
|
+
const label = () =>
|
|
61
|
+
isLoading() ? 'Synthesizing...' : isSpeaking() ? 'Stop' : 'Read aloud';
|
|
62
|
+
|
|
63
|
+
function revokeUrl() {
|
|
64
|
+
if (objectUrl) {
|
|
65
|
+
URL.revokeObjectURL(objectUrl);
|
|
66
|
+
objectUrl = undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Native path: speechSynthesis.speak(new SpeechSynthesisUtterance(text)).
|
|
71
|
+
function speakNative() {
|
|
72
|
+
if (!hasSpeechSynthesis() || !local.text) return;
|
|
73
|
+
window.speechSynthesis.cancel();
|
|
74
|
+
utterance = new SpeechSynthesisUtterance(local.text);
|
|
75
|
+
utterance.onend = () => setIsSpeaking(false);
|
|
76
|
+
utterance.onerror = () => setIsSpeaking(false);
|
|
77
|
+
setIsSpeaking(true);
|
|
78
|
+
window.speechSynthesis.speak(utterance);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Model path: await onSynthesize(text) → play the Blob via an <Audio>.
|
|
82
|
+
async function speakModel() {
|
|
83
|
+
if (!props.onSynthesize || !local.text) return;
|
|
84
|
+
setIsLoading(true);
|
|
85
|
+
try {
|
|
86
|
+
const blob = await props.onSynthesize(local.text);
|
|
87
|
+
props.onSynthesized?.(blob);
|
|
88
|
+
revokeUrl();
|
|
89
|
+
objectUrl = URL.createObjectURL(blob);
|
|
90
|
+
audio ??= new Audio();
|
|
91
|
+
audio.src = objectUrl;
|
|
92
|
+
audio.onended = () => setIsSpeaking(false);
|
|
93
|
+
audio.onerror = () => setIsSpeaking(false);
|
|
94
|
+
setIsSpeaking(true);
|
|
95
|
+
await audio.play();
|
|
96
|
+
} catch {
|
|
97
|
+
setIsSpeaking(false);
|
|
98
|
+
} finally {
|
|
99
|
+
setIsLoading(false);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function speak() {
|
|
104
|
+
if (props.onSynthesize) void speakModel();
|
|
105
|
+
else speakNative();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function stop() {
|
|
109
|
+
if (props.onSynthesize) {
|
|
110
|
+
if (audio) {
|
|
111
|
+
audio.pause();
|
|
112
|
+
audio.currentTime = 0;
|
|
113
|
+
}
|
|
114
|
+
} else if (hasSpeechSynthesis()) {
|
|
115
|
+
window.speechSynthesis.cancel();
|
|
116
|
+
}
|
|
117
|
+
setIsSpeaking(false);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function pause() {
|
|
121
|
+
if (props.onSynthesize) audio?.pause();
|
|
122
|
+
else if (hasSpeechSynthesis()) window.speechSynthesis.pause();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resume() {
|
|
126
|
+
if (props.onSynthesize) void audio?.play();
|
|
127
|
+
else if (hasSpeechSynthesis()) window.speechSynthesis.resume();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function handleClick() {
|
|
131
|
+
if (isSpeaking()) stop();
|
|
132
|
+
else speak();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Emit speaking transitions to the host. `defer: true` skips the spurious
|
|
136
|
+
// initial `false` — only real start/stop transitions fire.
|
|
137
|
+
createEffect(on(isSpeaking, (speaking) => {
|
|
138
|
+
props.onSpeakingChange?.(speaking);
|
|
139
|
+
}, { defer: true }));
|
|
140
|
+
|
|
141
|
+
// Autoplay: speak when text first arrives / changes (defer skips the mount
|
|
142
|
+
// value so an empty seed doesn't speak).
|
|
143
|
+
createEffect(on(() => local.text, (text) => {
|
|
144
|
+
if (props.autoplay && text) speak();
|
|
145
|
+
}, { defer: true }));
|
|
146
|
+
|
|
147
|
+
props.controllerRef?.({ speak, pause, resume, stop });
|
|
148
|
+
|
|
149
|
+
onCleanup(() => {
|
|
150
|
+
stop();
|
|
151
|
+
revokeUrl();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Speaker icon (idle)
|
|
155
|
+
const SpeakerIcon = () => (
|
|
156
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
157
|
+
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
|
|
158
|
+
<path d="M19.07 4.93a10 10 0 010 14.14M15.54 8.46a5 5 0 010 7.07" />
|
|
159
|
+
</svg>
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Square stop icon (speaking)
|
|
163
|
+
const StopIcon = () => (
|
|
164
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
|
|
165
|
+
<rect x="4" y="4" width="16" height="16" rx="2" />
|
|
166
|
+
</svg>
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Spinner (model synthesis in flight)
|
|
170
|
+
const Spinner = () => (
|
|
171
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="animate-spin">
|
|
172
|
+
<path d="M21 12a9 9 0 11-6.219-8.56" />
|
|
173
|
+
</svg>
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<div class={cn('relative inline-flex items-center justify-center', local.class)}>
|
|
178
|
+
<Tooltip content={label()}>
|
|
179
|
+
<Button
|
|
180
|
+
part="button"
|
|
181
|
+
variant="ghost"
|
|
182
|
+
size="icon-sm"
|
|
183
|
+
aria-label={label()}
|
|
184
|
+
aria-pressed={isSpeaking()}
|
|
185
|
+
onClick={handleClick}
|
|
186
|
+
disabled={local.disabled || !capable() || isLoading()}
|
|
187
|
+
class={cn('relative z-10 rounded-full transition-all duration-300')}
|
|
188
|
+
>
|
|
189
|
+
<Show when={isLoading()}>
|
|
190
|
+
<Spinner />
|
|
191
|
+
</Show>
|
|
192
|
+
<Show when={!isLoading() && isSpeaking()}>
|
|
193
|
+
<StopIcon />
|
|
194
|
+
</Show>
|
|
195
|
+
<Show when={!isLoading() && !isSpeaking()}>
|
|
196
|
+
<SpeakerIcon />
|
|
197
|
+
</Show>
|
|
198
|
+
</Button>
|
|
199
|
+
</Tooltip>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { defineWebComponent } from './define';
|
|
2
|
+
import { AgentCard, type AgentStatus } from '../ui/agent-card';
|
|
3
|
+
|
|
4
|
+
interface Props extends Record<string, unknown> {
|
|
5
|
+
/** The agent's name — the primary label. Attribute: `name`. */
|
|
6
|
+
name?: string;
|
|
7
|
+
/** Selected / focused state: highlighted border + surface. Attribute: `active`. */
|
|
8
|
+
active?: boolean;
|
|
9
|
+
/** Raise a prominent "Needs you" pill plus a glowing amber edge — the
|
|
10
|
+
* attention-routing signal that pulls focus to this agent. Attribute:
|
|
11
|
+
* `needs-attention`. */
|
|
12
|
+
needsAttention?: boolean;
|
|
13
|
+
/** Run status — a JS PROPERTY (object), not an attribute. Shape:
|
|
14
|
+
* `{ tone, label?, pulse? }`, where `tone` is one of `working` | `idle` |
|
|
15
|
+
* `done` | `error` | `blocked` (maps to the kit's tool hues), `label` is an
|
|
16
|
+
* optional short string beside the dot, and `pulse` animates the dot. Set it
|
|
17
|
+
* with `el.status = { tone: 'working', label: 'Working', pulse: true }`. */
|
|
18
|
+
status?: AgentStatus;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Events fired by `<kai-agent-card>`. */
|
|
22
|
+
interface Events {
|
|
23
|
+
/** The card was activated — clicked, or Enter / Space while focused. Promote
|
|
24
|
+
* this agent back to focus. */
|
|
25
|
+
'kai-activate': void;
|
|
26
|
+
/** The trailing "..." kebab was clicked. The consumer opens its own menu; the
|
|
27
|
+
* card only surfaces the affordance (the click does not also activate the card). */
|
|
28
|
+
'kai-menu': void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `<kai-agent-card>` — the compact glanceable card for one agent in a multi-agent
|
|
33
|
+
* workspace. The agent name with a leading tone-colored status dot, an optional
|
|
34
|
+
* "Needs you" attention treatment, an `active` (selected) state, and a trailing
|
|
35
|
+
* "..." overflow button.
|
|
36
|
+
*
|
|
37
|
+
* Set `status` as a JS property (it's an object); `name` is a string attribute and
|
|
38
|
+
* `active` / `needs-attention` are boolean attributes. Listen for `kai-activate`
|
|
39
|
+
* (promote to focus) and `kai-menu` (open your own per-agent menu).
|
|
40
|
+
*
|
|
41
|
+
* ```html
|
|
42
|
+
* <kai-agent-card name="Refactor bot" active></kai-agent-card>
|
|
43
|
+
* <script type="module">
|
|
44
|
+
* import '@kitn.ai/ui/elements';
|
|
45
|
+
* const card = document.querySelector('kai-agent-card');
|
|
46
|
+
* card.status = { tone: 'working', label: 'Working', pulse: true };
|
|
47
|
+
* card.addEventListener('kai-activate', () => focusAgent());
|
|
48
|
+
* card.addEventListener('kai-menu', () => openMenu());
|
|
49
|
+
* </script>
|
|
50
|
+
* ```
|
|
51
|
+
* Recolor the status dot via `::part(status)` and the kebab via `::part(menu)`.
|
|
52
|
+
*/
|
|
53
|
+
defineWebComponent<Props, Events>('kai-agent-card', {
|
|
54
|
+
name: undefined,
|
|
55
|
+
active: undefined,
|
|
56
|
+
needsAttention: undefined,
|
|
57
|
+
status: undefined,
|
|
58
|
+
}, (props, { dispatch, flag }) => (
|
|
59
|
+
<AgentCard
|
|
60
|
+
name={(props.name as string) ?? ''}
|
|
61
|
+
active={flag('active')}
|
|
62
|
+
needsAttention={flag('needsAttention')}
|
|
63
|
+
status={(props.status as AgentStatus) ?? { tone: 'idle' }}
|
|
64
|
+
onActivate={() => dispatch('kai-activate')}
|
|
65
|
+
onMenu={() => dispatch('kai-menu')}
|
|
66
|
+
/>
|
|
67
|
+
));
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { createSignal, onMount, onCleanup } from 'solid-js';
|
|
2
2
|
import { defineWebComponent } from './define';
|
|
3
|
-
import { Artifact, type ArtifactFile, type ArtifactTab } from '../components/artifact';
|
|
3
|
+
import { Artifact, type ArtifactController, type ArtifactFile, type ArtifactTab } from '../components/artifact';
|
|
4
4
|
|
|
5
5
|
interface Props extends Record<string, unknown> {
|
|
6
6
|
/** URL the preview iframe frames. Consumer-controlled. */
|
|
7
7
|
src?: string;
|
|
8
8
|
/** Files for the Code tab tree + each file's preview `url`. Set as a JS property (array). */
|
|
9
9
|
files: ArtifactFile[];
|
|
10
|
-
/**
|
|
10
|
+
/** Controlled active tab: `preview` or `code`. When set, the artifact follows it
|
|
11
|
+
* (re-asserted on change). Leave unset for an uncontrolled tab (see `defaultTab`). */
|
|
11
12
|
tab?: ArtifactTab;
|
|
13
|
+
/** Uncontrolled INITIAL tab (used only when `tab` is unset). Default `preview`.
|
|
14
|
+
* Seeds the starting tab; the user can then switch freely without the consumer
|
|
15
|
+
* re-asserting a controlled `tab`. */
|
|
16
|
+
defaultTab?: ArtifactTab;
|
|
12
17
|
/** Selected file path — syncs the tree highlight, Code source, and preview. */
|
|
13
18
|
activeFile?: string;
|
|
14
19
|
/** iframe `sandbox` override. Secure default `allow-scripts allow-forms` (NOT `allow-same-origin`). */
|
|
@@ -35,6 +40,11 @@ interface Props extends Record<string, unknown> {
|
|
|
35
40
|
standalone?: boolean;
|
|
36
41
|
/** Show the address but make it read-only (visible, nav-tracking, non-editable). */
|
|
37
42
|
readonlyPath?: boolean;
|
|
43
|
+
/** Friendly address shown in the path field instead of the real current url
|
|
44
|
+
* (read-only, non-navigable). Use when the framed url is not consumer-facing
|
|
45
|
+
* (e.g. a `data:` blob) so a clean address shows instead of leaking it. Scalar
|
|
46
|
+
* string: set as the `display-url` attribute or the `displayUrl` property. */
|
|
47
|
+
displayUrl?: string;
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
interface Events extends Record<string, unknown> {
|
|
@@ -59,7 +69,8 @@ interface Events extends Record<string, unknown> {
|
|
|
59
69
|
defineWebComponent<Props, Events>('kai-artifact', {
|
|
60
70
|
src: undefined,
|
|
61
71
|
files: [],
|
|
62
|
-
tab:
|
|
72
|
+
tab: undefined,
|
|
73
|
+
defaultTab: undefined,
|
|
63
74
|
activeFile: undefined,
|
|
64
75
|
sandbox: 'allow-scripts allow-forms',
|
|
65
76
|
iframeTitle: undefined,
|
|
@@ -73,9 +84,44 @@ defineWebComponent<Props, Events>('kai-artifact', {
|
|
|
73
84
|
noTabs: false,
|
|
74
85
|
standalone: false,
|
|
75
86
|
readonlyPath: false,
|
|
76
|
-
|
|
87
|
+
displayUrl: undefined,
|
|
88
|
+
}, (props, { element, dispatch, flag, expose }) => {
|
|
77
89
|
const [maximized, setMaximized] = createSignal(flag('maximized'));
|
|
78
90
|
|
|
91
|
+
// ── Imperative API (instance methods on the host) ──────────────────────────
|
|
92
|
+
// Pattern C: the Artifact component owns the history stack + tab/file/maximize
|
|
93
|
+
// state and hands up a controller; the facade captures it and exposes delegating
|
|
94
|
+
// methods. Every internal handler still fires its event (navigate → kai-navigate;
|
|
95
|
+
// selectFile → kai-file-select; maximize/restore → kai-maximize-change). Names are
|
|
96
|
+
// collision-checked against the props: openExternal (not openInTab — a prop),
|
|
97
|
+
// maximize/restore (not maximized — a prop), navigate/selectFile (not src/activeFile
|
|
98
|
+
// — props); NO tab-switching method (tab is a prop → switch via `el.tab`).
|
|
99
|
+
let controller: ArtifactController | undefined;
|
|
100
|
+
expose({
|
|
101
|
+
/** Go back in the artifact's own history stack (no-op when there's no prior entry). */
|
|
102
|
+
back: () => controller?.back(),
|
|
103
|
+
/** Go forward in the history stack (no-op when there's no forward entry). */
|
|
104
|
+
forward: () => controller?.forward(),
|
|
105
|
+
/** Force-reload the current preview url (also re-renders an inline PDF). */
|
|
106
|
+
reload: () => controller?.reload(),
|
|
107
|
+
/** Navigate to the `src` home url (no-op when there's no `src`). */
|
|
108
|
+
home: () => controller?.home(),
|
|
109
|
+
/** Push + load a url in the preview — the path-field submit path (fires kai-navigate). */
|
|
110
|
+
navigate: (url: string) => controller?.navigate(url),
|
|
111
|
+
/** Select a file by path: highlights the tree, shows its source, navigates the
|
|
112
|
+
* preview (fires kai-file-select + kai-navigate). Named selectFile to avoid the
|
|
113
|
+
* `activeFile` prop. */
|
|
114
|
+
selectFile: (path: string) => controller?.selectFile(path),
|
|
115
|
+
/** Open the current url in a new browser tab (no-op when there's no concrete url).
|
|
116
|
+
* Named openExternal, NOT openInTab — that's a prop (toolbar button visibility). */
|
|
117
|
+
openExternal: () => controller?.openExternal(),
|
|
118
|
+
/** Enter the maximized view-state (fires kai-maximize-change{maximized:true}).
|
|
119
|
+
* Named maximize, NOT maximized — that's a prop. */
|
|
120
|
+
maximize: () => controller?.maximize(),
|
|
121
|
+
/** Exit the maximized view-state (fires kai-maximize-change{maximized:false}). */
|
|
122
|
+
restore: () => controller?.restore(),
|
|
123
|
+
});
|
|
124
|
+
|
|
79
125
|
const onMaximizeChange = (next: boolean) => {
|
|
80
126
|
setMaximized(next);
|
|
81
127
|
// 1) The PROTOCOL intent — raw, bubbling + composed (NOT via dispatch()).
|
|
@@ -113,6 +159,7 @@ defineWebComponent<Props, Events>('kai-artifact', {
|
|
|
113
159
|
src={props.src}
|
|
114
160
|
files={props.files}
|
|
115
161
|
tab={props.tab}
|
|
162
|
+
defaultTab={props.defaultTab}
|
|
116
163
|
activeFile={props.activeFile}
|
|
117
164
|
sandbox={props.sandbox}
|
|
118
165
|
iframeTitle={props.iframeTitle}
|
|
@@ -126,10 +173,12 @@ defineWebComponent<Props, Events>('kai-artifact', {
|
|
|
126
173
|
showTabs={!flag('noTabs')}
|
|
127
174
|
standalone={flag('standalone')}
|
|
128
175
|
readonlyPath={flag('readonlyPath')}
|
|
176
|
+
displayUrl={props.displayUrl}
|
|
129
177
|
onMaximizeChange={onMaximizeChange}
|
|
130
178
|
onNavigate={(url) => dispatch('kai-navigate', { url })}
|
|
131
179
|
onTabChange={(tab) => dispatch('kai-tab-change', { tab })}
|
|
132
180
|
onFileSelect={(path) => dispatch('kai-file-select', { path })}
|
|
181
|
+
controllerRef={(c) => (controller = c)}
|
|
133
182
|
/>
|
|
134
183
|
</div>
|
|
135
184
|
</>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// The kai-* autoloader (opt-in, additive — the Web Awesome model).
|
|
2
|
+
//
|
|
3
|
+
// Include this ONE module (via a CDN/static `<script type="module">` tag) and it
|
|
4
|
+
// watches the DOM for undefined kai-* elements and dynamically imports each
|
|
5
|
+
// element's module on demand — so a page that only uses <kai-chat> never downloads
|
|
6
|
+
// the others. The register-all bundle (@kitn.ai/ui/elements) stays the default;
|
|
7
|
+
// this is the delivery for NO-BUILD / CDN / static-served pages.
|
|
8
|
+
//
|
|
9
|
+
// IMPORTANT — this is a CDN / static-file pattern (it resolves sibling element
|
|
10
|
+
// modules relative to its own URL). It is NOT importable through a bundler: Vite /
|
|
11
|
+
// webpack relocate `import.meta.url` away from the element files and cannot analyze
|
|
12
|
+
// the dynamic import, so it 404s. In a BUNDLED app use per-element imports
|
|
13
|
+
// (`import '@kitn.ai/ui/elements/<el>'`) or the register-all bundle. (Advanced: host
|
|
14
|
+
// dist/elements/ yourself and call setAutoloaderBasePath('<url>/') before use.)
|
|
15
|
+
//
|
|
16
|
+
// Tag → module is resolved through the generated manifest (filenames don't always
|
|
17
|
+
// equal the tag, and some modules register more than one tag), and modules are
|
|
18
|
+
// fetched relative to THIS module's URL.
|
|
19
|
+
import manifest from './element-manifest.json';
|
|
20
|
+
|
|
21
|
+
const tagToModule: Record<string, string> = manifest.tags;
|
|
22
|
+
const inFlight = new Set<string>();
|
|
23
|
+
|
|
24
|
+
// Base = the directory of this module. Computed via string ops on import.meta.url
|
|
25
|
+
// (NOT `new URL('./x', import.meta.url)`, which Vite rewrites into a static-asset
|
|
26
|
+
// glob that resolves to undefined at runtime).
|
|
27
|
+
const BASE = import.meta.url.slice(0, import.meta.url.lastIndexOf('/') + 1);
|
|
28
|
+
|
|
29
|
+
/** Override where element modules are fetched from (e.g. a CDN). Call before use. */
|
|
30
|
+
let baseOverride: string | null = null;
|
|
31
|
+
export function setAutoloaderBasePath(path: string): void {
|
|
32
|
+
baseOverride = path.endsWith('/') ? path : path + '/';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function register(tag: string): Promise<void> {
|
|
36
|
+
if (customElements.get(tag) || inFlight.has(tag)) return;
|
|
37
|
+
const file = tagToModule[tag];
|
|
38
|
+
if (!file) return; // not one of ours
|
|
39
|
+
inFlight.add(tag);
|
|
40
|
+
try {
|
|
41
|
+
await import(/* @vite-ignore */ `${baseOverride ?? BASE}${file}.js`);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
inFlight.delete(tag);
|
|
44
|
+
warnOnce(tag, err);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// One actionable warning per session (the failure is almost always "imported
|
|
49
|
+
// through a bundler" — see the header note), instead of a silent per-tag error.
|
|
50
|
+
let warned = false;
|
|
51
|
+
function warnOnce(tag: string, err: unknown): void {
|
|
52
|
+
if (warned) return;
|
|
53
|
+
warned = true;
|
|
54
|
+
const msg = (err as { message?: string })?.message ?? String(err);
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.warn(
|
|
57
|
+
`[kai-autoloader] could not load "${tag}" (${msg}). The autoloader is a CDN / ` +
|
|
58
|
+
`static-file tool — load it from a <script type="module" src=".../@kitn.ai/ui/dist/elements/autoloader.js">. ` +
|
|
59
|
+
`It is NOT importable through a bundler. In a bundled app, register elements with per-element imports ` +
|
|
60
|
+
`(import '@kitn.ai/ui/elements/<el>') or the register-all bundle (import '@kitn.ai/ui/elements'). To drive ` +
|
|
61
|
+
`the autoloader from a bundler, host dist/elements/ and call setAutoloaderBasePath('<url>/') before use.`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function discover(root: ParentNode | Element): void {
|
|
66
|
+
const tags = new Set<string>();
|
|
67
|
+
const self = (root as Element).tagName?.toLowerCase?.();
|
|
68
|
+
if (self && self in tagToModule && !customElements.get(self)) tags.add(self);
|
|
69
|
+
root.querySelectorAll?.(':not(:defined)').forEach((el) => {
|
|
70
|
+
const t = el.tagName.toLowerCase();
|
|
71
|
+
if (t in tagToModule) tags.add(t);
|
|
72
|
+
});
|
|
73
|
+
tags.forEach(register);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Start watching `root` (default: the whole document) for undefined kai-* elements. */
|
|
77
|
+
export function startAutoloader(root: ParentNode = document): void {
|
|
78
|
+
discover(root as Element);
|
|
79
|
+
new MutationObserver((mutations) => {
|
|
80
|
+
for (const m of mutations)
|
|
81
|
+
for (const node of m.addedNodes)
|
|
82
|
+
if (node.nodeType === 1) discover(node as Element);
|
|
83
|
+
}).observe(document.documentElement, { childList: true, subtree: true });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Self-start on import (the <script type="module"> use case). Deferred to a
|
|
87
|
+
// microtask so a consumer that calls setAutoloaderBasePath() right after importing
|
|
88
|
+
// (the advanced bundler-from-CDN path) takes effect before the first discovery pass.
|
|
89
|
+
if (typeof document !== 'undefined') queueMicrotask(() => startAutoloader());
|