@kitn.ai/ui 0.17.0 → 0.18.1
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 +28 -5
- 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 +37 -12
- package/dist/components/choice-card.d.ts +26 -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/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 +12 -0
- 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.d.ts +14 -0
- package/dist/components/screen.d.ts +46 -0
- package/dist/components/tasks-card.d.ts +49 -1
- package/dist/components/toast.d.ts +12 -2
- package/dist/components/tool-types.d.ts +10 -0
- package/dist/components/tool.d.ts +8 -8
- 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 +8392 -1860
- package/dist/elements/agent-card.d.ts +1 -0
- package/dist/elements/agent-card.js +1 -0
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/autoloader.js +1 -1
- 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 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-types.d.ts +47 -0
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/chunks/Icon-C2LTALdM.js +1 -0
- package/dist/elements/chunks/action-icons-Dtl8Ox5D.js +1 -0
- package/dist/elements/chunks/arrow-left-GbBceGBE.js +1 -0
- package/dist/elements/chunks/attachments-DLJcWCDV.js +1 -0
- package/dist/elements/chunks/badge-BTNZTODL.js +1 -0
- package/dist/elements/chunks/button-DBDZebGr.js +1 -0
- package/dist/elements/chunks/card-YfCyI4fH.js +1 -0
- package/dist/elements/chunks/chat-thread-Dz2qbzvI.js +1 -0
- package/dist/elements/chunks/check-fhmRksq_.js +1 -0
- package/dist/elements/chunks/chevron-down-DcRg1aRU.js +1 -0
- package/dist/elements/chunks/chevron-right-ChzBcNJ-.js +1 -0
- package/dist/elements/chunks/circle-B4fTe9v6.js +1 -0
- package/dist/elements/chunks/circle-check-CJV-3Rem.js +1 -0
- package/dist/elements/chunks/circle-x-pDtzlghQ.js +1 -0
- package/dist/elements/chunks/cn-CU7UAWzk.js +1 -0
- package/dist/elements/chunks/code-block-DZ6mz6vs.js +1 -0
- package/dist/elements/chunks/collapsible-CWUAShMC.js +1 -0
- package/dist/elements/chunks/composer-BZyPU6gD.js +65 -0
- package/dist/elements/chunks/context-Cx6eQA_G.js +1 -0
- package/dist/elements/chunks/conversation-list-C4xTQSpT.js +1 -0
- package/dist/elements/chunks/default-input-BZcwyKcP.js +1 -0
- package/dist/elements/chunks/define-CNSKFZCd.js +1 -0
- package/dist/elements/chunks/disclosure-BcBs--Jf.js +1 -0
- package/dist/elements/chunks/download-BDQwcFav.js +1 -0
- package/dist/elements/chunks/dropdown-wgs8xCJZ.js +1 -0
- package/dist/elements/chunks/ellipsis-CUiEJ6R1.js +1 -0
- package/dist/elements/chunks/external-link-DJUmC7g2.js +1 -0
- package/dist/elements/chunks/file-text-DOYfYvrh.js +1 -0
- package/dist/elements/chunks/file-tree-DQzNQrbr.js +1 -0
- package/dist/elements/chunks/folder-DBDUBANz.js +1 -0
- package/dist/elements/chunks/hover-card-BQGdGAQ3.js +1 -0
- package/dist/elements/chunks/icon-CAyRqVrg.js +1 -0
- package/dist/elements/chunks/{index-CTBEJeo6.js → index-BCRhi84a.js} +1 -1
- package/dist/elements/chunks/info-DBQl-m6e.js +1 -0
- package/dist/elements/chunks/input-DZD67EvW.js +1 -0
- package/dist/elements/chunks/kbd-DbyVrlnU.js +1 -0
- package/dist/elements/chunks/link-vko7AttA.js +1 -0
- package/dist/elements/chunks/loader-Cg6rO8tQ.js +1 -0
- package/dist/elements/chunks/{markdown-CMTGx9r8.js → markdown-n3Xbj3PR.js} +9 -9
- package/dist/elements/chunks/message-CgKytiSB.js +1 -0
- package/dist/elements/chunks/message-feedback-Ds3-US0e.js +1 -0
- package/dist/elements/chunks/message-square-MmRkvc-M.js +1 -0
- package/dist/elements/chunks/minimize-2-3kCdE0q3.js +1 -0
- package/dist/elements/chunks/model-switcher-BMtXGwLU.js +1 -0
- package/dist/elements/chunks/overlay-B1-MBI6h.js +1 -0
- package/dist/elements/chunks/paperclip-DDEfmkHc.js +1 -0
- package/dist/elements/chunks/progress-bar-Dwbiyv3s.js +1 -0
- package/dist/elements/chunks/prompt-suggestion-9lvgFcEa.js +1 -0
- package/dist/elements/chunks/reasoning-DzHWsdAJ.js +1 -0
- package/dist/elements/chunks/resizable-Bq_eHxUm.js +1 -0
- package/dist/elements/chunks/rotate-cw-jpisOdOV.js +1 -0
- package/dist/elements/chunks/scroll-area-g5Ze3K6f.js +1 -0
- package/dist/elements/chunks/separator-Bmjc5XyX.js +1 -0
- package/dist/elements/chunks/settings-Cz1bMJP9.js +1 -0
- package/dist/elements/chunks/settings-group-9imY3kRA.js +1 -0
- package/dist/elements/chunks/share-BdenF0wQ.js +1 -0
- package/dist/elements/chunks/skeleton-Pjc5jiaY.js +1 -0
- package/dist/elements/chunks/slots-DkZ-Cmha.js +1 -0
- package/dist/elements/chunks/star-DY6z1UUH.js +1 -0
- package/dist/elements/chunks/store-th4VyJXM.js +1 -0
- package/dist/elements/chunks/text-shimmer-CEi-tlXr.js +1 -0
- package/dist/elements/chunks/textarea-CboigarR.js +1 -0
- package/dist/elements/chunks/thumbs-up-BR33KLfA.js +1 -0
- package/dist/elements/chunks/toast-store-BEMda0fv.js +1 -0
- package/dist/elements/chunks/tool-TMued3WO.js +1 -0
- package/dist/elements/chunks/tooltip-Cft_Rxiw.js +1 -0
- package/dist/elements/chunks/triangle-alert-B07fX3Q8.js +1 -0
- package/dist/elements/chunks/use-card-resolution-M36zVciB.js +1 -0
- package/dist/elements/chunks/video-MKp3LmNT.js +1 -0
- package/dist/elements/chunks/x-Ckwicf6g.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 -1
- package/dist/elements/command.d.ts +1 -0
- package/dist/elements/command.js +1 -0
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.d.ts +1 -0
- package/dist/elements/composer.js +1 -0
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/css.d.ts +1 -0
- package/dist/elements/default-input.d.ts +51 -0
- package/dist/elements/define.d.ts +53 -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 -1
- package/dist/elements/empty.js +1 -1
- package/dist/elements/feedback-bar.js +1 -1
- package/dist/elements/file-tree.js +1 -1
- package/dist/elements/file-upload.js +1 -1
- package/dist/elements/form.js +1 -1
- package/dist/elements/hover-card.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 -1
- 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 -1
- package/dist/elements/loader.js +1 -1
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/menu.d.ts +1 -0
- package/dist/elements/menu.js +1 -0
- package/dist/elements/message-skills.js +1 -1
- package/dist/elements/message.js +1 -1
- package/dist/elements/model-switcher.js +1 -1
- package/dist/elements/nav.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 -1
- 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.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/register-impl.d.ts +0 -0
- package/dist/elements/register.d.ts +13 -0
- package/dist/elements/remote.d.ts +1 -0
- package/dist/elements/remote.js +1 -0
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +2 -2
- 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 -1
- 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.js +1 -1
- package/dist/elements/status.d.ts +1 -0
- package/dist/elements/status.js +1 -0
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.d.ts +1 -0
- package/dist/elements/tabs.js +1 -0
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.d.ts +1 -0
- package/dist/elements/tooltip.js +1 -0
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.d.ts +1 -0
- package/dist/elements/voice-output.js +1 -0
- package/dist/elements.d.ts +733 -58
- package/dist/engine-javascript-C1x7zo1_.js +141 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +8255 -5703
- package/dist/kai.es.js +1 -0
- package/dist/llms/llms-full.txt +1146 -64
- package/dist/llms/llms.txt +4 -4
- package/dist/mcp.es.js +62 -7
- 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/toast-store.d.ts +32 -2
- package/dist/primitives/use-speech-recognition.d.ts +26 -0
- package/dist/react/index.d.ts +1085 -97
- package/dist/react/runtime.d.ts +9 -2
- package/dist/react/use-kai-chat.d.ts +41 -0
- package/dist/react.js +572 -193
- package/dist/register-impl-cKscku5m.js +141 -0
- package/dist/schemas/tasks.schema.json +2 -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 +77 -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 +19 -1
- package/frameworks/react/index.tsx +1042 -157
- package/frameworks/react/runtime.tsx +30 -1
- package/frameworks/react/use-kai-chat.tsx +87 -0
- package/llms-full.txt +1146 -64
- package/llms.txt +4 -4
- package/package.json +31 -10
- package/src/agent-tooling/mcp/manifest.ts +14 -0
- package/src/agent-tooling/mcp/tools/debug.ts +11 -7
- package/src/agent-tooling/mcp/tools/reference.ts +32 -0
- package/src/agent-tooling/mcp/tools/scaffold.ts +30 -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 +225 -116
- package/src/components/choice-card.tsx +103 -11
- package/src/components/coachmark.tsx +249 -0
- package/src/components/component-meta.json +699 -110
- 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 +54 -1
- package/src/components/conversation-item.tsx +69 -7
- package/src/components/conversation-list.tsx +134 -37
- package/src/components/file-tree.tsx +197 -47
- package/src/components/form-widgets.tsx +9 -7
- package/src/components/form.tsx +140 -17
- 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 +23 -4
- 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.tsx +31 -2
- package/src/components/screen.tsx +176 -0
- package/src/components/tasks-card.tsx +341 -21
- package/src/components/toast.tsx +119 -31
- package/src/components/tool-types.ts +10 -0
- package/src/components/tool.tsx +22 -16
- 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/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 +2 -2
- package/src/elements/chat-workspace.tsx +153 -41
- package/src/elements/chat.tsx +45 -23
- 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 +24 -2
- package/src/elements/compiled.css +2 -2
- 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 +87 -7
- 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 +129 -1
- package/src/elements/element-meta.json +5562 -1336
- package/src/elements/element-types.d.ts +732 -57
- 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 +58 -11
- 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 +38 -3
- package/src/elements/register.ts +11 -3
- 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/switch.tsx +89 -14
- package/src/elements/tabs.tsx +95 -0
- package/src/elements/tasks.tsx +70 -14
- package/src/elements/toast.tsx +17 -2
- 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 +5 -2
- package/src/primitives/card-schemas/tasks.schema.json +2 -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/toast-store.ts +43 -2
- 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 +218 -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 +117 -0
- package/src/ui/pane-group.tsx +291 -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 +52 -8
- package/theme.css +113 -0
- package/dist/components/slash-command.d.ts +0 -15
- package/dist/elements/chunks/Icon-D_Zmwmwh.js +0 -1
- package/dist/elements/chunks/action-icons-PVfIvcuG.js +0 -1
- package/dist/elements/chunks/attachments-Dp6vqtrc.js +0 -1
- package/dist/elements/chunks/button-CKyYD9G4.js +0 -1
- package/dist/elements/chunks/card-Cjho-PYk.js +0 -1
- package/dist/elements/chunks/chat-thread-RT808iPf.js +0 -1
- package/dist/elements/chunks/check-CDYaMYrM.js +0 -1
- package/dist/elements/chunks/chevron-down-3SBiq8HF.js +0 -1
- package/dist/elements/chunks/chevron-right-k2UFlIVK.js +0 -1
- package/dist/elements/chunks/cn-Dogu6PbV.js +0 -1
- package/dist/elements/chunks/code-block-BYUmfFSd.js +0 -1
- package/dist/elements/chunks/collapsible-BiNwdOn6.js +0 -1
- package/dist/elements/chunks/context-18g6oi4J.js +0 -1
- package/dist/elements/chunks/conversation-list-D9Y0NVdo.js +0 -1
- package/dist/elements/chunks/default-input-CSAoYYMd.js +0 -1
- package/dist/elements/chunks/download-D8K0Je8r.js +0 -1
- package/dist/elements/chunks/dropdown-bUs2iTia.js +0 -1
- package/dist/elements/chunks/external-link-6mA0wxab.js +0 -1
- package/dist/elements/chunks/file-text-DnaYk3Dm.js +0 -1
- package/dist/elements/chunks/file-tree-CgtRLlxK.js +0 -1
- package/dist/elements/chunks/hover-card-CIGw62gP.js +0 -1
- package/dist/elements/chunks/link-BlGSeoPy.js +0 -1
- package/dist/elements/chunks/loader-DvhR4qa0.js +0 -1
- package/dist/elements/chunks/message-CDZwXjhL.js +0 -1
- package/dist/elements/chunks/message-feedback-CYx81QiA.js +0 -1
- package/dist/elements/chunks/model-switcher-O_tUhoOT.js +0 -1
- package/dist/elements/chunks/overlay-DydXYMQW.js +0 -1
- package/dist/elements/chunks/prompt-suggestion-ldlWV5kd.js +0 -1
- package/dist/elements/chunks/reasoning-2PUnHlmr.js +0 -1
- package/dist/elements/chunks/resizable-B5WNbSeb.js +0 -1
- package/dist/elements/chunks/star-t9tI-P0q.js +0 -1
- package/dist/elements/chunks/store-C6rIooZz.js +0 -1
- package/dist/elements/chunks/text-shimmer-uh9V_KUh.js +0 -1
- package/dist/elements/chunks/textarea-3nm6Ia9U.js +0 -1
- package/dist/elements/chunks/thumbs-up-BBtguGld.js +0 -1
- package/dist/elements/chunks/toast-store-D8cL3fWl.js +0 -1
- package/dist/elements/chunks/tool-GJ3vwp1U.js +0 -1
- package/dist/elements/chunks/tooltip-DwKAz4XR.js +0 -1
- package/dist/elements/chunks/triangle-alert-Ct1zt2JL.js +0 -1
- package/dist/elements/chunks/use-card-resolution-D-_0Alzy.js +0 -1
- package/dist/elements/chunks/video-mVYiM99-.js +0 -1
- package/dist/elements/chunks/x-CzxL27Ch.js +0 -1
- package/dist/kitn-chat.es.js +0 -1
- package/dist/register-JyTglR6m.js +0 -1
- package/dist/register-impl-C6wlSsts.js +0 -77
- package/src/components/slash-command.tsx +0 -232
- package/src/elements/framework-usage.json +0 -588
- /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
|
@@ -30,8 +30,30 @@ export interface FileTreeFile {
|
|
|
30
30
|
language?: string;
|
|
31
31
|
/** Kind — drives the icon + whether Code applies. */
|
|
32
32
|
type?: 'html' | 'pdf' | 'image' | 'other';
|
|
33
|
+
/** Lines added vs the base. Rendered as a trailing `+N` stat (success hue,
|
|
34
|
+
* tabular-nums). Only shown when present; omit for a plain file row. */
|
|
35
|
+
additions?: number;
|
|
36
|
+
/** Lines removed vs the base. Rendered as a trailing `-N` stat (error hue). */
|
|
37
|
+
deletions?: number;
|
|
38
|
+
/** Change status vs the base. Drives a small trailing status letter in the
|
|
39
|
+
* conventional VCS hue (added=green, modified=amber, deleted=red,
|
|
40
|
+
* renamed=blue, untracked=muted). Only shown when present. */
|
|
41
|
+
status?: 'added' | 'modified' | 'deleted' | 'renamed' | 'untracked';
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
/** A change status a file can carry. */
|
|
45
|
+
export type FileStatus = NonNullable<FileTreeFile['status']>;
|
|
46
|
+
|
|
47
|
+
/** Per-status letter, accessible word, and tool-hue text class. Conventional VCS
|
|
48
|
+
* colors so a changed-files view reads at a glance. */
|
|
49
|
+
const STATUS_META: Record<FileStatus, { letter: string; label: string; class: string }> = {
|
|
50
|
+
added: { letter: 'A', label: 'added', class: 'text-tool-green' },
|
|
51
|
+
modified: { letter: 'M', label: 'modified', class: 'text-tool-amber' },
|
|
52
|
+
deleted: { letter: 'D', label: 'deleted', class: 'text-tool-red' },
|
|
53
|
+
renamed: { letter: 'R', label: 'renamed', class: 'text-tool-blue' },
|
|
54
|
+
untracked: { letter: 'U', label: 'untracked', class: 'text-muted-foreground' },
|
|
55
|
+
};
|
|
56
|
+
|
|
35
57
|
/** A folder node in the built tree. */
|
|
36
58
|
export interface FileTreeFolderNode {
|
|
37
59
|
kind: 'folder';
|
|
@@ -134,6 +156,48 @@ function iconFor(type: FileTreeFile['type']) {
|
|
|
134
156
|
}
|
|
135
157
|
}
|
|
136
158
|
|
|
159
|
+
/** Every folder path in the tree (render order) — drives collapse-all/expand-all. */
|
|
160
|
+
function collectFolderPaths(nodes: FileTreeNode[], out: string[] = []): string[] {
|
|
161
|
+
for (const n of nodes) {
|
|
162
|
+
if (n.kind === 'folder') {
|
|
163
|
+
out.push(n.path);
|
|
164
|
+
collectFolderPaths(n.children, out);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Every leaf file in the tree — drives the summary header totals. */
|
|
171
|
+
function collectFiles(nodes: FileTreeNode[], out: FileTreeFile[] = []): FileTreeFile[] {
|
|
172
|
+
for (const n of nodes) {
|
|
173
|
+
if (n.kind === 'folder') collectFiles(n.children, out);
|
|
174
|
+
else out.push(n.file);
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Whether a file carries any diff metadata (status / additions / deletions). */
|
|
180
|
+
function hasDiffMeta(file: FileTreeFile): boolean {
|
|
181
|
+
return file.status !== undefined || file.additions !== undefined || file.deletions !== undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Accessible name for a changed-file row: the file name plus its status and diff
|
|
186
|
+
* stats, e.g. `App.tsx, modified, 3 additions, 1 deletion`. Returns `undefined`
|
|
187
|
+
* when the file has no diff metadata, so a plain row keeps its text content as
|
|
188
|
+
* the accessible name (unchanged behavior).
|
|
189
|
+
*/
|
|
190
|
+
function fileAccessibleName(name: string, file: FileTreeFile): string | undefined {
|
|
191
|
+
if (!hasDiffMeta(file)) return undefined;
|
|
192
|
+
const parts: string[] = [];
|
|
193
|
+
if (file.status) parts.push(STATUS_META[file.status].label);
|
|
194
|
+
if (file.additions !== undefined)
|
|
195
|
+
parts.push(`${file.additions} addition${file.additions === 1 ? '' : 's'}`);
|
|
196
|
+
if (file.deletions !== undefined)
|
|
197
|
+
parts.push(`${file.deletions} deletion${file.deletions === 1 ? '' : 's'}`);
|
|
198
|
+
return [name, ...parts].join(', ');
|
|
199
|
+
}
|
|
200
|
+
|
|
137
201
|
export interface FileTreeProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'onSelect'> {
|
|
138
202
|
/** Flat file list; folders are derived from `/`-delimited paths. */
|
|
139
203
|
files: FileTreeFile[];
|
|
@@ -143,6 +207,10 @@ export interface FileTreeProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>,
|
|
|
143
207
|
onSelect?: (path: string, file: FileTreeFile) => void;
|
|
144
208
|
/** Folder paths expanded by default. When omitted, all folders start open. */
|
|
145
209
|
defaultExpanded?: string[];
|
|
210
|
+
/** Show a summary header above the tree: the changed-file count, the summed
|
|
211
|
+
* `+additions / -deletions`, and a Collapse-all/Expand-all toggle wired to the
|
|
212
|
+
* folder-expand state. Off by default (no header — unchanged behavior). */
|
|
213
|
+
summary?: boolean;
|
|
146
214
|
}
|
|
147
215
|
|
|
148
216
|
/**
|
|
@@ -157,6 +225,7 @@ export function FileTree(props: FileTreeProps): JSX.Element {
|
|
|
157
225
|
'activeFile',
|
|
158
226
|
'onSelect',
|
|
159
227
|
'defaultExpanded',
|
|
228
|
+
'summary',
|
|
160
229
|
'class',
|
|
161
230
|
]);
|
|
162
231
|
|
|
@@ -196,6 +265,23 @@ export function FileTree(props: FileTreeProps): JSX.Element {
|
|
|
196
265
|
}
|
|
197
266
|
};
|
|
198
267
|
|
|
268
|
+
// Summary header derivations (only read when `summary` is set).
|
|
269
|
+
const folderPaths = createMemo(() => collectFolderPaths(tree()));
|
|
270
|
+
const leafFiles = createMemo(() => collectFiles(tree()));
|
|
271
|
+
const fileCount = () => leafFiles().length;
|
|
272
|
+
const totalAdds = () => leafFiles().reduce((sum, f) => sum + (f.additions ?? 0), 0);
|
|
273
|
+
const totalDels = () => leafFiles().reduce((sum, f) => sum + (f.deletions ?? 0), 0);
|
|
274
|
+
const anyOpen = () => folderPaths().some((p) => isOpen(p));
|
|
275
|
+
const collapseAll = () => {
|
|
276
|
+
if (defaultAllOpen()) setClosedSet(new Set<string>(folderPaths()));
|
|
277
|
+
else setOpenSet(new Set<string>());
|
|
278
|
+
};
|
|
279
|
+
const expandAll = () => {
|
|
280
|
+
if (defaultAllOpen()) setClosedSet(new Set<string>());
|
|
281
|
+
else setOpenSet(new Set<string>(folderPaths()));
|
|
282
|
+
};
|
|
283
|
+
const toggleAll = () => (anyOpen() ? collapseAll() : expandAll());
|
|
284
|
+
|
|
199
285
|
const [focusedPath, setFocusedPath] = createSignal<string | undefined>();
|
|
200
286
|
|
|
201
287
|
const selectFile = (node: FileTreeFileNode) => {
|
|
@@ -247,27 +333,59 @@ export function FileTree(props: FileTreeProps): JSX.Element {
|
|
|
247
333
|
};
|
|
248
334
|
|
|
249
335
|
return (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
336
|
+
<>
|
|
337
|
+
<Show when={local.summary}>
|
|
338
|
+
<div
|
|
339
|
+
part="summary"
|
|
340
|
+
class="flex items-center gap-2 px-2 py-1.5 text-xs text-muted-foreground border-b border-border"
|
|
341
|
+
>
|
|
342
|
+
<span class="font-medium text-foreground">
|
|
343
|
+
{fileCount()} {fileCount() === 1 ? 'file' : 'files'} changed
|
|
344
|
+
</span>
|
|
345
|
+
<Show when={totalAdds() > 0}>
|
|
346
|
+
<span class="text-tool-green tabular-nums">+{totalAdds()}</span>
|
|
347
|
+
</Show>
|
|
348
|
+
<Show when={totalDels() > 0}>
|
|
349
|
+
<span class="text-tool-red tabular-nums">-{totalDels()}</span>
|
|
350
|
+
</Show>
|
|
351
|
+
<Show when={folderPaths().length > 0}>
|
|
352
|
+
<button
|
|
353
|
+
type="button"
|
|
354
|
+
class={cn(
|
|
355
|
+
'ml-auto rounded-md px-1.5 py-0.5 font-medium text-muted-foreground outline-none',
|
|
356
|
+
'hover:bg-muted hover:text-foreground',
|
|
357
|
+
'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset',
|
|
358
|
+
)}
|
|
359
|
+
aria-label={anyOpen() ? 'Collapse all folders' : 'Expand all folders'}
|
|
360
|
+
onClick={toggleAll}
|
|
361
|
+
>
|
|
362
|
+
{anyOpen() ? 'Collapse all' : 'Expand all'}
|
|
363
|
+
</button>
|
|
364
|
+
</Show>
|
|
365
|
+
</div>
|
|
366
|
+
</Show>
|
|
367
|
+
<div
|
|
368
|
+
role="tree"
|
|
369
|
+
aria-label="Files"
|
|
370
|
+
class={cn('text-sm select-none', local.class)}
|
|
371
|
+
{...rest}
|
|
372
|
+
>
|
|
373
|
+
<For each={tree()}>
|
|
374
|
+
{(node) => (
|
|
375
|
+
<TreeNode
|
|
376
|
+
node={node}
|
|
377
|
+
depth={0}
|
|
378
|
+
isOpen={isOpen}
|
|
379
|
+
toggle={toggle}
|
|
380
|
+
activeFile={() => local.activeFile}
|
|
381
|
+
focusedPath={focusedPath}
|
|
382
|
+
onSelectFile={selectFile}
|
|
383
|
+
onKeyDown={onKeyDown}
|
|
384
|
+
/>
|
|
385
|
+
)}
|
|
386
|
+
</For>
|
|
387
|
+
</div>
|
|
388
|
+
</>
|
|
271
389
|
);
|
|
272
390
|
}
|
|
273
391
|
|
|
@@ -300,32 +418,64 @@ function TreeNode(props: TreeNodeProps): JSX.Element {
|
|
|
300
418
|
return (
|
|
301
419
|
<Show
|
|
302
420
|
when={props.node.kind === 'folder'}
|
|
303
|
-
fallback={
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
421
|
+
fallback={(() => {
|
|
422
|
+
const file = (props.node as FileTreeFileNode).file;
|
|
423
|
+
const ariaName = fileAccessibleName(node().name, file);
|
|
424
|
+
const hasMeta = ariaName !== undefined;
|
|
425
|
+
const status = file.status ? STATUS_META[file.status] : undefined;
|
|
426
|
+
return (
|
|
427
|
+
<div
|
|
428
|
+
role="treeitem"
|
|
429
|
+
aria-selected={isActive()}
|
|
430
|
+
aria-label={ariaName}
|
|
431
|
+
data-tree-path={props.node.path}
|
|
432
|
+
data-tree-kind="file"
|
|
433
|
+
tabindex={tabIndex()}
|
|
434
|
+
class={cn(
|
|
435
|
+
'flex items-center gap-1.5 rounded-md py-1 pr-2 cursor-pointer outline-none',
|
|
436
|
+
'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset',
|
|
437
|
+
isActive()
|
|
438
|
+
? 'bg-primary/10 text-foreground font-medium'
|
|
439
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground',
|
|
440
|
+
)}
|
|
441
|
+
style={indent()}
|
|
442
|
+
onClick={() => props.onSelectFile(props.node as FileTreeFileNode)}
|
|
443
|
+
onKeyDown={(e) => props.onKeyDown(e, props.node)}
|
|
444
|
+
>
|
|
445
|
+
<span class="w-3.5 shrink-0" />
|
|
446
|
+
{(() => {
|
|
447
|
+
const Icon = iconFor(file.type);
|
|
448
|
+
return <Icon size={14} class="shrink-0 opacity-70" aria-hidden="true" />;
|
|
449
|
+
})()}
|
|
450
|
+
<span class={cn('truncate', hasMeta && 'min-w-0 flex-1')}>{node().name}</span>
|
|
451
|
+
<Show when={hasMeta}>
|
|
452
|
+
<span
|
|
453
|
+
class="ml-auto flex items-center gap-1.5 shrink-0 pl-2 text-xs"
|
|
454
|
+
aria-hidden="true"
|
|
455
|
+
>
|
|
456
|
+
<Show when={file.additions !== undefined}>
|
|
457
|
+
<span part="stat-additions" class="text-tool-green tabular-nums">
|
|
458
|
+
+{file.additions}
|
|
459
|
+
</span>
|
|
460
|
+
</Show>
|
|
461
|
+
<Show when={file.deletions !== undefined}>
|
|
462
|
+
<span part="stat-deletions" class="text-tool-red tabular-nums">
|
|
463
|
+
-{file.deletions}
|
|
464
|
+
</span>
|
|
465
|
+
</Show>
|
|
466
|
+
<Show when={status}>
|
|
467
|
+
<span
|
|
468
|
+
part="status"
|
|
469
|
+
class={cn('w-3 text-center font-mono font-semibold', status!.class)}
|
|
470
|
+
>
|
|
471
|
+
{status!.letter}
|
|
472
|
+
</span>
|
|
473
|
+
</Show>
|
|
474
|
+
</span>
|
|
475
|
+
</Show>
|
|
476
|
+
</div>
|
|
477
|
+
);
|
|
478
|
+
})()}
|
|
329
479
|
>
|
|
330
480
|
<div role="none">
|
|
331
481
|
<div
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type JSX, For, Show, createSignal } from 'solid-js';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
3
|
import { Textarea } from '../ui/textarea';
|
|
4
|
+
import { Input, FIELD_BASE as inputBase } from '../ui/input';
|
|
4
5
|
import { Star } from 'lucide-solid';
|
|
5
6
|
import type { FormField } from './form';
|
|
6
7
|
|
|
@@ -19,9 +20,6 @@ export interface WidgetProps {
|
|
|
19
20
|
onBlur: () => void;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const inputBase =
|
|
23
|
-
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:pointer-events-none';
|
|
24
|
-
|
|
25
23
|
function ariaProps(p: WidgetProps) {
|
|
26
24
|
return {
|
|
27
25
|
'aria-required': p.required || undefined,
|
|
@@ -52,20 +50,24 @@ export function TextWidget(
|
|
|
52
50
|
return 'text';
|
|
53
51
|
}
|
|
54
52
|
};
|
|
53
|
+
// Render the bare `Input` control (no label/hint/error): `kai-form`'s FieldRow
|
|
54
|
+
// already supplies the label, description, and inline error around the widget.
|
|
55
|
+
// `Input` owns the field styling (its `FIELD_BASE` is the former `inputBase`),
|
|
56
|
+
// so the rendered input is byte-identical to the old raw `<input>`.
|
|
55
57
|
return (
|
|
56
|
-
<
|
|
58
|
+
<Input
|
|
57
59
|
id={props.id}
|
|
58
60
|
data-control
|
|
59
61
|
type={inputType()}
|
|
60
|
-
class={cn(inputBase, props.invalid && 'border-destructive dark:border-red-400/70')}
|
|
61
62
|
value={(props.value as string) ?? ''}
|
|
62
63
|
placeholder={props.placeholder}
|
|
64
|
+
invalid={props.invalid}
|
|
63
65
|
disabled={props.disabled}
|
|
64
66
|
minLength={props.field.minLength}
|
|
65
67
|
maxLength={props.field.maxLength}
|
|
66
68
|
{...ariaProps(props)}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
onValueInput={(value) => props.onInput(value)}
|
|
70
|
+
onValueChange={() => props.onBlur()}
|
|
69
71
|
/>
|
|
70
72
|
);
|
|
71
73
|
}
|
package/src/components/form.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
createMemo,
|
|
9
9
|
createEffect,
|
|
10
10
|
on,
|
|
11
|
+
onMount,
|
|
11
12
|
ErrorBoundary,
|
|
12
13
|
} from 'solid-js';
|
|
13
14
|
import { createStore, produce, unwrap } from 'solid-js/store';
|
|
@@ -355,6 +356,25 @@ export function buildResult(
|
|
|
355
356
|
// The <Form> component.
|
|
356
357
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
357
358
|
|
|
359
|
+
/** Imperative handle exposed via `controllerRef` — surfaces the form's latent
|
|
360
|
+
* capabilities (focus the first/first-invalid control, validate, programmatic
|
|
361
|
+
* send, reset to defaults, dismiss/reopen) so the `<kai-form>` facade can forward
|
|
362
|
+
* them as instance methods. */
|
|
363
|
+
export interface FormController {
|
|
364
|
+
/** Focus the first control, or the first INVALID control after a failed validation. */
|
|
365
|
+
focus(options?: FocusOptions): void;
|
|
366
|
+
/** Run full validation + submit: focus the first invalid field on failure, else emit `submit`. */
|
|
367
|
+
send(): void;
|
|
368
|
+
/** Run client-side validation now and return per-field errors WITHOUT submitting. */
|
|
369
|
+
validate(): { valid: boolean; errors?: Record<string, string> };
|
|
370
|
+
/** Re-seed from each field's `default` and clear errors. */
|
|
371
|
+
reset(): void;
|
|
372
|
+
/** Trigger the dismiss path (emit `dismiss` + collapse to the re-openable stub). */
|
|
373
|
+
dismiss(): void;
|
|
374
|
+
/** Re-open a dismissed card from its stub (emit `reopen`). */
|
|
375
|
+
reopen(): void;
|
|
376
|
+
}
|
|
377
|
+
|
|
358
378
|
export interface FormProps {
|
|
359
379
|
/** The form definition (CardEnvelope.data). */
|
|
360
380
|
data?: FormDefinition;
|
|
@@ -370,6 +390,16 @@ export interface FormProps {
|
|
|
370
390
|
class?: string;
|
|
371
391
|
/** When set, render the chromed read-only view instead of the form inputs. */
|
|
372
392
|
resolution?: CardResolution;
|
|
393
|
+
/** Controlled field values — when set, this wins over internal/uncontrolled state. */
|
|
394
|
+
values?: Record<string, unknown>;
|
|
395
|
+
/** Initial values overlaying the schema defaults (uncontrolled seed). */
|
|
396
|
+
defaultValues?: Record<string, unknown>;
|
|
397
|
+
/** Disable all fields + submit. */
|
|
398
|
+
disabled?: boolean;
|
|
399
|
+
/** Fires on input with the current coerced values + validity (distinct from submit). */
|
|
400
|
+
onValuesChange?: (payload: { values: Record<string, unknown>; valid: boolean }) => void;
|
|
401
|
+
/** Receive the imperative controller once mounted. */
|
|
402
|
+
controllerRef?: (controller: FormController) => void;
|
|
373
403
|
}
|
|
374
404
|
|
|
375
405
|
const DEFAULT_FORM: FormDefinition = { type: 'object', properties: {} };
|
|
@@ -383,7 +413,10 @@ const DEFAULT_FORM: FormDefinition = { type: 'object', properties: {} };
|
|
|
383
413
|
*/
|
|
384
414
|
export function Form(props: FormProps): JSX.Element {
|
|
385
415
|
const merged = mergeProps({ cardId: 'kai-form' }, props);
|
|
386
|
-
const [local] = splitProps(merged, [
|
|
416
|
+
const [local] = splitProps(merged, [
|
|
417
|
+
'data', 'cardId', 'heading', 'host', 'hostElement', 'class', 'resolution',
|
|
418
|
+
'values', 'defaultValues', 'disabled', 'onValuesChange', 'controllerRef',
|
|
419
|
+
]);
|
|
387
420
|
|
|
388
421
|
const ctxHost = useCardHost();
|
|
389
422
|
|
|
@@ -414,7 +447,34 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
414
447
|
const [values, setValues] = createStore<Record<string, unknown>>({});
|
|
415
448
|
const [errors, setErrors] = createStore<Record<string, string>>({});
|
|
416
449
|
|
|
450
|
+
// Build the seed value map: schema `default`s, overlaid by `defaultValues`, then
|
|
451
|
+
// (when controlled) the live `values` prop. The controlled prop always wins.
|
|
452
|
+
const seedMap = (d: FormDefinition): Record<string, unknown> => {
|
|
453
|
+
const next: Record<string, unknown> = {};
|
|
454
|
+
for (const [key, field] of Object.entries(d.properties ?? {})) {
|
|
455
|
+
if (field.default !== undefined) next[key] = field.default;
|
|
456
|
+
else if (field.type === 'array') next[key] = [];
|
|
457
|
+
}
|
|
458
|
+
if (local.defaultValues) Object.assign(next, local.defaultValues);
|
|
459
|
+
if (local.values) Object.assign(next, local.values);
|
|
460
|
+
return next;
|
|
461
|
+
};
|
|
462
|
+
|
|
417
463
|
const seed = (d: FormDefinition): void => {
|
|
464
|
+
const next = seedMap(d);
|
|
465
|
+
setValues(produce((s) => {
|
|
466
|
+
for (const k of Object.keys(s)) delete s[k];
|
|
467
|
+
Object.assign(s, next);
|
|
468
|
+
}));
|
|
469
|
+
setErrors(produce((s) => {
|
|
470
|
+
for (const k of Object.keys(s)) delete s[k];
|
|
471
|
+
}));
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// Reset to defaults only (ignore the controlled `values`/`defaultValues` overlay):
|
|
475
|
+
// re-seed from each field's schema `default` and clear errors. Used by reset().
|
|
476
|
+
const seedDefaults = (): void => {
|
|
477
|
+
const d = def();
|
|
418
478
|
const next: Record<string, unknown> = {};
|
|
419
479
|
for (const [key, field] of Object.entries(d.properties ?? {})) {
|
|
420
480
|
if (field.default !== undefined) next[key] = field.default;
|
|
@@ -432,6 +492,16 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
432
492
|
// Reseed whenever a NEW valid definition arrives.
|
|
433
493
|
createEffect(on(() => local.data, () => { if (envelopeValid().ok) seed(def()); }));
|
|
434
494
|
|
|
495
|
+
// Controlled values: when the consumer drives `values`, mirror it into the store
|
|
496
|
+
// (the controlled prop wins over local edits). Deferred so mount's seed runs first.
|
|
497
|
+
createEffect(on(() => local.values, (v) => {
|
|
498
|
+
if (!v) return;
|
|
499
|
+
setValues(produce((s) => {
|
|
500
|
+
for (const k of Object.keys(s)) delete s[k];
|
|
501
|
+
Object.assign(s, v);
|
|
502
|
+
}));
|
|
503
|
+
}, { defer: true }));
|
|
504
|
+
|
|
435
505
|
// ready + error lifecycle emits.
|
|
436
506
|
createEffect(on(envelopeValid, (state) => {
|
|
437
507
|
if (state.ok) emit({ kind: 'ready', cardId: local.cardId });
|
|
@@ -450,6 +520,12 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
450
520
|
const setField = (key: string, raw: unknown): void => {
|
|
451
521
|
setValues(key, raw);
|
|
452
522
|
if (errors[key]) setErrors(key, undefined as unknown as string);
|
|
523
|
+
// Fire the live change signal: current coerced values + validity (distinct from
|
|
524
|
+
// the terminal submit). Validation here is read-only — it doesn't surface errors.
|
|
525
|
+
const snapshot = unwrap(values) as Record<string, unknown>;
|
|
526
|
+
const out = buildResult(def(), snapshot);
|
|
527
|
+
const { valid } = validateForm(def(), snapshot);
|
|
528
|
+
local.onValuesChange?.({ values: out, valid });
|
|
453
529
|
};
|
|
454
530
|
|
|
455
531
|
const validateField = (key: string): void => {
|
|
@@ -462,12 +538,21 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
462
538
|
setErrors(key, single.fieldErrors[key]);
|
|
463
539
|
};
|
|
464
540
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
541
|
+
// Resolve the root to query controls inside (the live <form> when called from the
|
|
542
|
+
// submit event, else the host element's shadow root for a programmatic call).
|
|
543
|
+
const queryRoot = (formEl?: HTMLElement | null): ParentNode =>
|
|
544
|
+
formEl?.closest('form') ?? formEl ?? local.hostElement?.shadowRoot ?? document;
|
|
545
|
+
|
|
546
|
+
// Focus the control for a field key (the `[data-control]` inside its `[data-field]`).
|
|
547
|
+
const focusControl = (key: string, root: ParentNode, options?: FocusOptions): void => {
|
|
548
|
+
root.querySelector<HTMLElement>(`[data-field="${cssEscape(key)}"] [data-control]`)?.focus(options);
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// Run full validation; on failure surface the per-field errors + focus the first
|
|
552
|
+
// invalid control and return false; on success emit `submit` + resolve. Shared by
|
|
553
|
+
// the form's onSubmit handler and the controller's send().
|
|
554
|
+
const runSubmit = (formEl?: HTMLElement | null): boolean => {
|
|
555
|
+
if (res.isResolved()) return false;
|
|
471
556
|
const snapshot = unwrap(values);
|
|
472
557
|
const result = validateForm(def(), snapshot as Record<string, unknown>);
|
|
473
558
|
setErrors(produce((s) => {
|
|
@@ -476,18 +561,23 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
476
561
|
}));
|
|
477
562
|
if (!result.valid) {
|
|
478
563
|
const firstBad = keys().find((k) => result.fieldErrors[k]);
|
|
479
|
-
if (firstBad
|
|
480
|
-
|
|
481
|
-
queueMicrotask(() =>
|
|
482
|
-
const root: ParentNode = formEl?.closest('form') ?? formEl ?? document;
|
|
483
|
-
root.querySelector<HTMLElement>(`[data-field="${cssEscape(firstBad)}"] [data-control]`)?.focus();
|
|
484
|
-
});
|
|
564
|
+
if (firstBad) {
|
|
565
|
+
const root = queryRoot(formEl);
|
|
566
|
+
queueMicrotask(() => focusControl(firstBad, root));
|
|
485
567
|
}
|
|
486
|
-
return;
|
|
568
|
+
return false;
|
|
487
569
|
}
|
|
488
570
|
const out = buildResult(def(), snapshot as Record<string, unknown>);
|
|
489
571
|
emit({ kind: 'submit', cardId: local.cardId, data: out });
|
|
490
572
|
res.setLocal({ kind: 'submit', data: out });
|
|
573
|
+
return true;
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
const onSubmit = (e: Event): void => {
|
|
577
|
+
e.preventDefault();
|
|
578
|
+
// Capture the <form> synchronously — `e.currentTarget` is nulled out once the
|
|
579
|
+
// event has finished dispatching (so it can't be read in a later microtask).
|
|
580
|
+
runSubmit(e.currentTarget as HTMLElement | null);
|
|
491
581
|
};
|
|
492
582
|
|
|
493
583
|
// Dismiss: emit `dismiss` AND optimistically flip to a `dismissed` resolution so
|
|
@@ -499,6 +589,39 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
499
589
|
};
|
|
500
590
|
const onReopen = (): void => emit({ kind: 'reopen', cardId: local.cardId });
|
|
501
591
|
|
|
592
|
+
const disabled = (): boolean => local.disabled === true;
|
|
593
|
+
|
|
594
|
+
// Imperative controller (Pattern C): hand the facade a handle over the form's
|
|
595
|
+
// latent capabilities. focus targets the first control (or the first INVALID one
|
|
596
|
+
// after a failed validation); validate runs the existing validateForm and surfaces
|
|
597
|
+
// the per-field errors WITHOUT submitting; send runs the same path as the Submit
|
|
598
|
+
// button; reset re-seeds from schema defaults; dismiss/reopen drive the stub.
|
|
599
|
+
onMount(() => {
|
|
600
|
+
local.controllerRef?.({
|
|
601
|
+
focus: (options) => {
|
|
602
|
+
const root = queryRoot();
|
|
603
|
+
const firstBad = keys().find((k) => errors[k]);
|
|
604
|
+
const target = firstBad ?? keys()[0];
|
|
605
|
+
if (target) focusControl(target, root, options);
|
|
606
|
+
},
|
|
607
|
+
send: () => { runSubmit(); },
|
|
608
|
+
validate: () => {
|
|
609
|
+
const snapshot = unwrap(values) as Record<string, unknown>;
|
|
610
|
+
const result = validateForm(def(), snapshot);
|
|
611
|
+
setErrors(produce((s) => {
|
|
612
|
+
for (const k of Object.keys(s)) delete s[k];
|
|
613
|
+
Object.assign(s, result.fieldErrors);
|
|
614
|
+
}));
|
|
615
|
+
return result.valid
|
|
616
|
+
? { valid: true }
|
|
617
|
+
: { valid: false, errors: result.fieldErrors };
|
|
618
|
+
},
|
|
619
|
+
reset: () => seedDefaults(),
|
|
620
|
+
dismiss: () => onDismiss(),
|
|
621
|
+
reopen: () => onReopen(),
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
|
|
502
625
|
const actions = createMemo(() => def()['x-kai-actions'] ?? []);
|
|
503
626
|
const submitLabel = () => def()['x-kai-submitLabel'] ?? 'Submit';
|
|
504
627
|
const dismissible = () => def()['x-kai-dismissible'] === true;
|
|
@@ -556,7 +679,7 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
556
679
|
</Button>
|
|
557
680
|
)}
|
|
558
681
|
</For>
|
|
559
|
-
<Button type="submit" form={formId()}>
|
|
682
|
+
<Button type="submit" form={formId()} disabled={disabled()}>
|
|
560
683
|
{submitLabel()}
|
|
561
684
|
</Button>
|
|
562
685
|
</div>
|
|
@@ -583,7 +706,7 @@ export function Form(props: FormProps): JSX.Element {
|
|
|
583
706
|
inlineMax={inlineMax()}
|
|
584
707
|
value={() => values[key]}
|
|
585
708
|
error={() => errors[key]}
|
|
586
|
-
disabled={
|
|
709
|
+
disabled={disabled()}
|
|
587
710
|
onInput={(v) => setField(key, v)}
|
|
588
711
|
onBlur={() => validateField(key)}
|
|
589
712
|
/>
|
|
@@ -678,7 +801,7 @@ function FieldRow(props: FieldRowProps): JSX.Element {
|
|
|
678
801
|
['fieldset', 'repeater', 'checkbox-group', 'multiselect', 'radio', 'taglist'].includes(widget());
|
|
679
802
|
|
|
680
803
|
return (
|
|
681
|
-
<div class="flex flex-col gap-2 rounded-xl bg-
|
|
804
|
+
<div class="flex flex-col gap-2 rounded-xl bg-surface p-3.5" data-field={props.fieldKey}>
|
|
682
805
|
<Show when={!isGrouped()}>
|
|
683
806
|
<label for={id} class="text-sm font-medium text-foreground">
|
|
684
807
|
{label()}
|
package/src/components/image.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type JSX, splitProps, createSignal, createEffect, onCleanup, Show } from 'solid-js';
|
|
2
2
|
import { cn } from '../utils/cn';
|
|
3
|
+
import { Skeleton } from '../ui/skeleton';
|
|
3
4
|
|
|
4
5
|
export interface GeneratedImageLike {
|
|
5
6
|
base64?: string;
|
|
@@ -44,13 +45,10 @@ function Image(props: ImageProps) {
|
|
|
44
45
|
<Show
|
|
45
46
|
when={src()}
|
|
46
47
|
fallback={
|
|
47
|
-
<
|
|
48
|
+
<Skeleton
|
|
48
49
|
aria-label={local.alt}
|
|
49
50
|
role="img"
|
|
50
|
-
class={cn(
|
|
51
|
-
'h-auto max-w-full animate-pulse overflow-hidden rounded-md bg-muted',
|
|
52
|
-
local.class
|
|
53
|
-
)}
|
|
51
|
+
class={cn('h-auto max-w-full overflow-hidden', local.class)}
|
|
54
52
|
/>
|
|
55
53
|
}
|
|
56
54
|
>
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
Show,
|
|
11
11
|
} from 'solid-js';
|
|
12
12
|
import { cn } from '../utils/cn';
|
|
13
|
+
import { Skeleton } from '../ui/skeleton';
|
|
13
14
|
import { Link as LinkIcon } from 'lucide-solid';
|
|
14
15
|
import type { CardEvent } from '../primitives/card-contract';
|
|
15
16
|
import {
|
|
@@ -153,11 +154,11 @@ export function LinkPreview(props: LinkPreviewProps): JSX.Element {
|
|
|
153
154
|
>
|
|
154
155
|
<Show when={loading()}>
|
|
155
156
|
{/* Skeleton while the bare-URL fetcher resolves. */}
|
|
156
|
-
<
|
|
157
|
+
<Skeleton class="aspect-[16/9] w-full rounded-none" />
|
|
157
158
|
<div class="space-y-2 p-3">
|
|
158
|
-
<
|
|
159
|
-
<
|
|
160
|
-
<
|
|
159
|
+
<Skeleton class="h-3 w-1/3" />
|
|
160
|
+
<Skeleton class="h-4 w-2/3" />
|
|
161
|
+
<Skeleton class="h-3 w-full" />
|
|
161
162
|
</div>
|
|
162
163
|
</Show>
|
|
163
164
|
<Show when={!loading()}>
|
|
@@ -11,6 +11,9 @@ export interface MarkdownProps {
|
|
|
11
11
|
id?: string;
|
|
12
12
|
class?: string;
|
|
13
13
|
codeTheme?: string;
|
|
14
|
+
/** Optional `::part` name(s) to expose on the rendered root. Lets callers
|
|
15
|
+
* (e.g. the message bubble) surface a styleable part through the shadow. */
|
|
16
|
+
part?: string;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
interface ParsedBlock {
|
|
@@ -49,13 +52,13 @@ function MarkdownBlock(props: { content: string }) {
|
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
function Markdown(props: MarkdownProps) {
|
|
52
|
-
const [local] = splitProps(props, ['content', 'id', 'class', 'codeTheme']);
|
|
55
|
+
const [local] = splitProps(props, ['content', 'id', 'class', 'codeTheme', 'part']);
|
|
53
56
|
const config = useChatConfig();
|
|
54
57
|
const blockId = () => local.id ?? createUniqueId();
|
|
55
58
|
const blocks = createMemo(() => parseMarkdownIntoBlocks(local.content));
|
|
56
59
|
|
|
57
60
|
return (
|
|
58
|
-
<div class={cn('chat-markdown max-w-none break-words whitespace-normal [&>div:first-child>p:first-child]:mt-0 [&>div:last-child>p:last-child]:mb-0', textClass(config.proseSize()), local.class)}>
|
|
61
|
+
<div part={local.part} class={cn('chat-markdown max-w-none break-words whitespace-normal [&>div:first-child>p:first-child]:mt-0 [&>div:last-child>p:last-child]:mb-0', textClass(config.proseSize()), local.class)}>
|
|
59
62
|
<For each={blocks()}>
|
|
60
63
|
{(block) => (
|
|
61
64
|
<Switch>
|