@kitn.ai/ui 0.14.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/LICENSE +21 -0
- package/README.md +394 -0
- package/dist/bash-InADTalH.js +1 -0
- package/dist/core-AYMC6_lb.js +12 -0
- package/dist/css-M7EaDHN_.js +1 -0
- package/dist/custom-elements.json +4739 -0
- package/dist/engine-javascript-vq0WuIJl.js +141 -0
- package/dist/github-dark-dimmed-DUshB20C.js +1 -0
- package/dist/github-light-JYsPkUQd.js +1 -0
- package/dist/host-embed.d.ts +33 -0
- package/dist/html-CPZ3oZQ7.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/javascript-C25yR2R2.js +1 -0
- package/dist/json-DxJze_jm.js +1 -0
- package/dist/kitn-chat-provider.d.ts +2 -0
- package/dist/kitn-chat-provider.es.js +240 -0
- package/dist/kitn-chat.es.js +77 -0
- package/dist/llms/llms-full.txt +1018 -0
- package/dist/llms/llms.txt +104 -0
- package/dist/origin.d.ts +5 -0
- package/dist/provider-runtime.d.ts +27 -0
- package/dist/schemas/card-envelope.schema.json +14 -0
- package/dist/schemas/card-event.schema.json +12 -0
- package/dist/schemas/choice.schema.json +70 -0
- package/dist/schemas/confirm.schema.json +65 -0
- package/dist/schemas/embed.schema.json +65 -0
- package/dist/schemas/form.result.schema.json +7 -0
- package/dist/schemas/form.schema.json +33 -0
- package/dist/schemas/link.schema.json +56 -0
- package/dist/schemas/tasks.result.schema.json +16 -0
- package/dist/schemas/tasks.schema.json +78 -0
- package/dist/svelte--5p79yCD.js +1 -0
- package/dist/theme.tokens.css +176 -0
- package/dist/tsx-B8rCNbgL.js +1 -0
- package/dist/typescript-RycA9KXf.js +1 -0
- package/dist/validate.d.ts +4 -0
- package/dist/version.d.ts +4 -0
- package/dist/vue-BmIZj4XD.js +1 -0
- package/dist/wire.d.ts +53 -0
- package/frameworks/react/index.tsx +948 -0
- package/frameworks/react/runtime.tsx +94 -0
- package/llms-full.txt +1018 -0
- package/llms.txt +104 -0
- package/package.json +141 -0
- package/src/components/artifact.stories.tsx +138 -0
- package/src/components/artifact.tsx +743 -0
- package/src/components/attachments.stories.tsx +303 -0
- package/src/components/attachments.test.tsx +64 -0
- package/src/components/attachments.tsx +399 -0
- package/src/components/card-fallback.tsx +28 -0
- package/src/components/card-renderer.tsx +52 -0
- package/src/components/card.tsx +110 -0
- package/src/components/chain-of-thought.stories.tsx +211 -0
- package/src/components/chain-of-thought.tsx +139 -0
- package/src/components/chat-container.stories.tsx +187 -0
- package/src/components/chat-container.tsx +82 -0
- package/src/components/chat-scope-picker.tsx +47 -0
- package/src/components/chat-thread.test.tsx +58 -0
- package/src/components/chat-thread.tsx +249 -0
- package/src/components/checkpoint.stories.tsx +102 -0
- package/src/components/checkpoint.tsx +88 -0
- package/src/components/choice-card.tsx +569 -0
- package/src/components/code-block.stories.tsx +150 -0
- package/src/components/code-block.tsx +105 -0
- package/src/components/component-meta.json +3735 -0
- package/src/components/confirm-card.stories.tsx +74 -0
- package/src/components/confirm-card.tsx +315 -0
- package/src/components/context.stories.tsx +229 -0
- package/src/components/context.test.tsx +133 -0
- package/src/components/context.tsx +363 -0
- package/src/components/conversation-item.stories.tsx +125 -0
- package/src/components/conversation-item.tsx +16 -0
- package/src/components/conversation-list.stories.tsx +133 -0
- package/src/components/conversation-list.tsx +101 -0
- package/src/components/default-input.test.tsx +111 -0
- package/src/components/embed.tsx +196 -0
- package/src/components/empty.stories.tsx +434 -0
- package/src/components/empty.tsx +166 -0
- package/src/components/feedback-bar.stories.tsx +138 -0
- package/src/components/feedback-bar.test.tsx +103 -0
- package/src/components/feedback-bar.tsx +166 -0
- package/src/components/file-tree.stories.tsx +73 -0
- package/src/components/file-tree.tsx +383 -0
- package/src/components/file-upload.stories.tsx +156 -0
- package/src/components/file-upload.tsx +161 -0
- package/src/components/form-widgets.tsx +461 -0
- package/src/components/form.tsx +873 -0
- package/src/components/image.stories.tsx +89 -0
- package/src/components/image.tsx +67 -0
- package/src/components/link-preview.tsx +194 -0
- package/src/components/loader.stories.tsx +181 -0
- package/src/components/loader.tsx +333 -0
- package/src/components/markdown.stories.tsx +180 -0
- package/src/components/markdown.tsx +81 -0
- package/src/components/message-action-bar.test.tsx +128 -0
- package/src/components/message-narrow.stories.tsx +329 -0
- package/src/components/message-skills.stories.tsx +211 -0
- package/src/components/message-skills.tsx +36 -0
- package/src/components/message.stories.tsx +281 -0
- package/src/components/message.tsx +227 -0
- package/src/components/model-switcher.stories.tsx +123 -0
- package/src/components/model-switcher.test.tsx +65 -0
- package/src/components/model-switcher.tsx +87 -0
- package/src/components/prompt-input.stories.tsx +222 -0
- package/src/components/prompt-input.tsx +211 -0
- package/src/components/prompt-suggestion.stories.tsx +142 -0
- package/src/components/prompt-suggestion.tsx +115 -0
- package/src/components/reasoning.stories.tsx +140 -0
- package/src/components/reasoning.tsx +156 -0
- package/src/components/resizable.test.tsx +226 -0
- package/src/components/response-stream.tsx +103 -0
- package/src/components/scroll-button.stories.tsx +100 -0
- package/src/components/scroll-button.tsx +34 -0
- package/src/components/slash-command.stories.tsx +163 -0
- package/src/components/slash-command.tsx +232 -0
- package/src/components/source-list.test.tsx +152 -0
- package/src/components/source.stories.tsx +124 -0
- package/src/components/source.tsx +129 -0
- package/src/components/tasks-card.stories.tsx +78 -0
- package/src/components/tasks-card.tsx +432 -0
- package/src/components/text-shimmer.stories.tsx +87 -0
- package/src/components/text-shimmer.tsx +37 -0
- package/src/components/thinking-bar.stories.tsx +87 -0
- package/src/components/thinking-bar.tsx +50 -0
- package/src/components/tool.stories.tsx +153 -0
- package/src/components/tool.tsx +184 -0
- package/src/components/use-card-resolution.ts +28 -0
- package/src/components/voice-input.stories.tsx +83 -0
- package/src/components/voice-input.tsx +107 -0
- package/src/elements/artifact.stories.tsx +506 -0
- package/src/elements/artifact.tsx +137 -0
- package/src/elements/attachments.stories.tsx +190 -0
- package/src/elements/attachments.tsx +136 -0
- package/src/elements/card.stories.tsx +113 -0
- package/src/elements/card.tsx +40 -0
- package/src/elements/cards.stories.tsx +59 -0
- package/src/elements/cards.tsx +97 -0
- package/src/elements/catalog.stories.tsx +491 -0
- package/src/elements/chain-of-thought.declarative.test.tsx +153 -0
- package/src/elements/chain-of-thought.stories.tsx +113 -0
- package/src/elements/chain-of-thought.tsx +92 -0
- package/src/elements/chat-scope-picker.stories.tsx +73 -0
- package/src/elements/chat-scope-picker.tsx +36 -0
- package/src/elements/chat-types.ts +42 -0
- package/src/elements/chat-workspace.stories.tsx +288 -0
- package/src/elements/chat-workspace.tsx +161 -0
- package/src/elements/chat.stories.tsx +151 -0
- package/src/elements/chat.tsx +80 -0
- package/src/elements/checkpoint.stories.tsx +72 -0
- package/src/elements/checkpoint.tsx +47 -0
- package/src/elements/choice.stories.tsx +240 -0
- package/src/elements/choice.tsx +48 -0
- package/src/elements/code-block.stories.tsx +83 -0
- package/src/elements/code-block.tsx +42 -0
- package/src/elements/compiled.css +2 -0
- package/src/elements/composed-shell.stories.tsx +316 -0
- package/src/elements/confirm-card.stories.tsx +227 -0
- package/src/elements/confirm-card.tsx +51 -0
- package/src/elements/context-meter.stories.tsx +151 -0
- package/src/elements/context-meter.tsx +121 -0
- package/src/elements/conversation-list.declarative.test.tsx +137 -0
- package/src/elements/conversation-list.stories.tsx +243 -0
- package/src/elements/conversation-list.tsx +80 -0
- package/src/elements/css.ts +5 -0
- package/src/elements/default-input.tsx +247 -0
- package/src/elements/define.tsx +171 -0
- package/src/elements/element-meta.json +3314 -0
- package/src/elements/element-types.d.ts +710 -0
- package/src/elements/embed.stories.tsx +213 -0
- package/src/elements/embed.tsx +35 -0
- package/src/elements/empty.stories.tsx +111 -0
- package/src/elements/empty.tsx +29 -0
- package/src/elements/feedback-bar.stories.tsx +114 -0
- package/src/elements/feedback-bar.tsx +61 -0
- package/src/elements/file-tree.stories.tsx +134 -0
- package/src/elements/file-tree.tsx +52 -0
- package/src/elements/file-upload.stories.tsx +82 -0
- package/src/elements/file-upload.tsx +44 -0
- package/src/elements/form.stories.tsx +248 -0
- package/src/elements/form.tsx +43 -0
- package/src/elements/framework-usage.json +563 -0
- package/src/elements/image.stories.tsx +71 -0
- package/src/elements/image.tsx +32 -0
- package/src/elements/link-preview.stories.tsx +209 -0
- package/src/elements/link-preview.tsx +34 -0
- package/src/elements/loader.stories.tsx +88 -0
- package/src/elements/loader.tsx +25 -0
- package/src/elements/markdown.stories.tsx +76 -0
- package/src/elements/markdown.tsx +38 -0
- package/src/elements/message-skills.declarative.test.tsx +104 -0
- package/src/elements/message-skills.stories.tsx +114 -0
- package/src/elements/message-skills.tsx +85 -0
- package/src/elements/message.stories.tsx +218 -0
- package/src/elements/message.tsx +162 -0
- package/src/elements/model-switcher.declarative.test.tsx +130 -0
- package/src/elements/model-switcher.stories.tsx +198 -0
- package/src/elements/model-switcher.tsx +93 -0
- package/src/elements/popover.stories.tsx +80 -0
- package/src/elements/popover.tsx +59 -0
- package/src/elements/prompt-input-slash-command.test.tsx +128 -0
- package/src/elements/prompt-input-stoppable.test.tsx +88 -0
- package/src/elements/prompt-input-toolbar-actions.test.tsx +120 -0
- package/src/elements/prompt-input.stories.tsx +331 -0
- package/src/elements/prompt-input.tsx +185 -0
- package/src/elements/prompt-suggestions.declarative.test.tsx +164 -0
- package/src/elements/prompt-suggestions.stories.tsx +213 -0
- package/src/elements/prompt-suggestions.tsx +94 -0
- package/src/elements/reasoning.stories.tsx +77 -0
- package/src/elements/reasoning.tsx +50 -0
- package/src/elements/register.ts +56 -0
- package/src/elements/remote.stories.tsx +314 -0
- package/src/elements/remote.tsx +221 -0
- package/src/elements/resizable.d.ts +27 -0
- package/src/elements/resizable.stories.tsx +426 -0
- package/src/elements/resizable.tsx +553 -0
- package/src/elements/response-stream.stories.tsx +79 -0
- package/src/elements/response-stream.tsx +40 -0
- package/src/elements/scroll-button.stories.tsx +202 -0
- package/src/elements/scroll-button.test.tsx +148 -0
- package/src/elements/scroll-button.tsx +126 -0
- package/src/elements/source-list.stories.tsx +143 -0
- package/src/elements/source.stories.tsx +88 -0
- package/src/elements/source.tsx +120 -0
- package/src/elements/styles.css +199 -0
- package/src/elements/switch.stories.tsx +56 -0
- package/src/elements/switch.tsx +43 -0
- package/src/elements/tasks.stories.tsx +237 -0
- package/src/elements/tasks.tsx +46 -0
- package/src/elements/text-shimmer.stories.tsx +64 -0
- package/src/elements/text-shimmer.tsx +28 -0
- package/src/elements/thinking-bar.stories.tsx +72 -0
- package/src/elements/thinking-bar.tsx +34 -0
- package/src/elements/tool.stories.tsx +89 -0
- package/src/elements/tool.tsx +23 -0
- package/src/elements/voice-input.stories.tsx +88 -0
- package/src/elements/voice-input.tsx +41 -0
- package/src/index.ts +253 -0
- package/src/primitives/card-contract.ts +71 -0
- package/src/primitives/card-host.tsx +35 -0
- package/src/primitives/card-registry.tsx +67 -0
- package/src/primitives/card-resolution.ts +36 -0
- package/src/primitives/card-routing.ts +79 -0
- package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
- package/src/primitives/card-schemas/card-event.schema.json +12 -0
- package/src/primitives/card-schemas/choice.schema.json +70 -0
- package/src/primitives/card-schemas/confirm.schema.json +65 -0
- package/src/primitives/card-schemas/embed.schema.json +65 -0
- package/src/primitives/card-schemas/form.result.schema.json +7 -0
- package/src/primitives/card-schemas/form.schema.json +33 -0
- package/src/primitives/card-schemas/link.schema.json +56 -0
- package/src/primitives/card-schemas/tasks.result.schema.json +16 -0
- package/src/primitives/card-schemas/tasks.schema.json +78 -0
- package/src/primitives/card-validate.ts +95 -0
- package/src/primitives/chat-config.tsx +76 -0
- package/src/primitives/controllable.test.ts +47 -0
- package/src/primitives/controllable.ts +21 -0
- package/src/primitives/embed-providers.ts +254 -0
- package/src/primitives/highlighter.ts +157 -0
- package/src/primitives/link-preview.ts +87 -0
- package/src/primitives/pdf-preview.ts +121 -0
- package/src/primitives/use-auto-resize.ts +31 -0
- package/src/primitives/use-resize-observer.ts +12 -0
- package/src/primitives/use-stick-to-bottom.ts +43 -0
- package/src/primitives/use-text-stream.ts +112 -0
- package/src/primitives/use-voice-recorder.ts +50 -0
- package/src/remote/host-embed.ts +345 -0
- package/src/remote/index.ts +2 -0
- package/src/remote/origin.ts +30 -0
- package/src/remote/provider-runtime.ts +259 -0
- package/src/remote/provider.ts +2 -0
- package/src/remote/validate.ts +22 -0
- package/src/remote/version.ts +12 -0
- package/src/remote/wire.ts +48 -0
- package/src/stories/chat-panel-layout.stories.tsx +145 -0
- package/src/stories/chat-scene.tsx +571 -0
- package/src/stories/checkpoint-restore.stories.tsx +256 -0
- package/src/stories/context-usage.stories.tsx +212 -0
- package/src/stories/conversation-with-reasoning.stories.tsx +182 -0
- package/src/stories/conversation-with-sources.stories.tsx +471 -0
- package/src/stories/docs/Accessibility.mdx +119 -0
- package/src/stories/docs/ChildElements.mdx +269 -0
- package/src/stories/docs/ForAIAgents.mdx +162 -0
- package/src/stories/docs/GettingStarted.mdx +65 -0
- package/src/stories/docs/Installation.mdx +83 -0
- package/src/stories/docs/Introduction.mdx +50 -0
- package/src/stories/docs/SolidJsAdvanced.mdx +17 -0
- package/src/stories/docs/Theming.mdx +85 -0
- package/src/stories/docs/element-controls.ts +121 -0
- package/src/stories/docs/frameworks/Angular.mdx +245 -0
- package/src/stories/docs/frameworks/Html.mdx +227 -0
- package/src/stories/docs/frameworks/Overview.mdx +47 -0
- package/src/stories/docs/frameworks/React.mdx +227 -0
- package/src/stories/docs/frameworks/Solid.mdx +173 -0
- package/src/stories/docs/frameworks/Svelte.mdx +194 -0
- package/src/stories/docs/frameworks/Vue.mdx +238 -0
- package/src/stories/docs/generative-ui-overview.mdx +186 -0
- package/src/stories/docs/recipes/SpeechToText.mdx +7 -0
- package/src/stories/docs/recipes/Streaming.mdx +68 -0
- package/src/stories/docs/recipes/TextToSpeech.mdx +35 -0
- package/src/stories/docs/theme-editor/canvas.tsx +32 -0
- package/src/stories/docs/theme-editor/inspector.tsx +66 -0
- package/src/stories/docs/theme-editor/presets.test.ts +32 -0
- package/src/stories/docs/theme-editor/presets.ts +64 -0
- package/src/stories/docs/theme-editor/theme-css.test.ts +19 -0
- package/src/stories/docs/theme-editor/theme-css.ts +15 -0
- package/src/stories/docs/theme-editor/theme-editor.tsx +146 -0
- package/src/stories/docs/theme-tokens.tsx +174 -0
- package/src/stories/examples/ChoosingComponents.mdx +106 -0
- package/src/stories/examples/sample-data.ts +79 -0
- package/src/stories/examples/usage/checkpoint-restore.ts +156 -0
- package/src/stories/examples/usage/context-usage.ts +591 -0
- package/src/stories/examples/usage/conversation-with-reasoning.ts +224 -0
- package/src/stories/examples/usage/conversation-with-sources.ts +589 -0
- package/src/stories/examples/usage/empty-state.ts +144 -0
- package/src/stories/examples/usage/full-chat-app.ts +277 -0
- package/src/stories/examples/usage/index.ts +63 -0
- package/src/stories/examples/usage/message-actions.ts +704 -0
- package/src/stories/examples/usage/pattern-centered-conversation.ts +446 -0
- package/src/stories/examples/usage/pattern-chat-panel-layout.ts +442 -0
- package/src/stories/examples/usage/pattern-docked-widget.ts +479 -0
- package/src/stories/examples/usage/prompt-input-variants.ts +1356 -0
- package/src/stories/examples/usage/streaming-response.ts +687 -0
- package/src/stories/examples/usage/types.ts +33 -0
- package/src/stories/full-chat.stories.tsx +18 -0
- package/src/stories/message-actions.stories.tsx +230 -0
- package/src/stories/pattern-centered-conversation.stories.tsx +93 -0
- package/src/stories/pattern-docked-widget.stories.tsx +93 -0
- package/src/stories/pattern-empty-state.stories.tsx +76 -0
- package/src/stories/prompt-input-variants.stories.tsx +456 -0
- package/src/stories/streaming-response.stories.tsx +438 -0
- package/src/stories/theme-editor.stories.tsx +16 -0
- package/src/stories/token-reference.stories.tsx +18 -0
- package/src/stories/typography.stories.tsx +78 -0
- package/src/types.ts +47 -0
- package/src/ui/action-icons.ts +52 -0
- package/src/ui/avatar.stories.tsx +103 -0
- package/src/ui/avatar.tsx +23 -0
- package/src/ui/badge.stories.tsx +86 -0
- package/src/ui/badge.tsx +21 -0
- package/src/ui/button.stories.tsx +145 -0
- package/src/ui/button.tsx +38 -0
- package/src/ui/collapsible.stories.tsx +69 -0
- package/src/ui/collapsible.tsx +125 -0
- package/src/ui/dropdown.stories.tsx +59 -0
- package/src/ui/dropdown.tsx +196 -0
- package/src/ui/hover-card.stories.tsx +77 -0
- package/src/ui/hover-card.test.tsx +30 -0
- package/src/ui/hover-card.tsx +172 -0
- package/src/ui/overlay.stories.tsx +115 -0
- package/src/ui/overlay.tsx +158 -0
- package/src/ui/popover.stories.tsx +81 -0
- package/src/ui/popover.test.tsx +99 -0
- package/src/ui/popover.tsx +92 -0
- package/src/ui/resizable.stories.tsx +236 -0
- package/src/ui/resizable.tsx +576 -0
- package/src/ui/scroll-area.stories.tsx +50 -0
- package/src/ui/scroll-area.tsx +15 -0
- package/src/ui/separator.stories.tsx +81 -0
- package/src/ui/separator.tsx +10 -0
- package/src/ui/skeleton.stories.tsx +337 -0
- package/src/ui/skeleton.tsx +16 -0
- package/src/ui/switch.stories.tsx +51 -0
- package/src/ui/switch.test.tsx +59 -0
- package/src/ui/switch.tsx +62 -0
- package/src/ui/textarea.stories.tsx +76 -0
- package/src/ui/textarea.tsx +21 -0
- package/src/ui/tooltip.stories.tsx +74 -0
- package/src/ui/tooltip.tsx +68 -0
- package/src/utils/cn.ts +24 -0
- package/theme.css +187 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { createSignal, onCleanup, onMount } from 'solid-js';
|
|
2
|
+
import { defineWebComponent } from './define';
|
|
3
|
+
import { ChatThread, type ChatThreadProps, type ChatThreadContextUsage } from '../components/chat-thread';
|
|
4
|
+
import type { AttachmentData } from '../components/attachments';
|
|
5
|
+
import type { SlashCommandItem } from '../components/slash-command';
|
|
6
|
+
import type { ProseSize } from '../primitives/chat-config';
|
|
7
|
+
import type { ModelOption } from '../types';
|
|
8
|
+
|
|
9
|
+
type Props = Omit<ChatThreadProps,
|
|
10
|
+
'class' | 'onValueChange' | 'onSubmit' | 'onSuggestionClick' | 'onModelChange'
|
|
11
|
+
| 'onMessageAction' | 'onSearch' | 'onVoice' | 'onSlashSelect'> & Record<string, unknown>;
|
|
12
|
+
|
|
13
|
+
interface Events {
|
|
14
|
+
/** User submitted a message. */
|
|
15
|
+
'kai-submit': { value: string; attachments: AttachmentData[] };
|
|
16
|
+
/** Fired on every input change. */
|
|
17
|
+
'kai-value-change': { value: string };
|
|
18
|
+
/** A suggestion chip was clicked (only in `suggestion-mode="fill"`). */
|
|
19
|
+
'kai-suggestion-click': { value: string };
|
|
20
|
+
/** An action button on a message was clicked. `action` is the built-in name or custom id. */
|
|
21
|
+
'kai-message-action': { messageId: string; action: string };
|
|
22
|
+
/** The header model switcher changed. */
|
|
23
|
+
'kai-model-change': { modelId: string };
|
|
24
|
+
/** A slash command was chosen from the palette. */
|
|
25
|
+
'kai-slash-select': { command: SlashCommandItem };
|
|
26
|
+
/** The Search button was clicked. */
|
|
27
|
+
'kai-search': Record<string, never>;
|
|
28
|
+
/** The Mic / voice button was clicked. */
|
|
29
|
+
'kai-voice': Record<string, never>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
defineWebComponent<Props, Events>('kai-chat', {
|
|
33
|
+
messages: [], value: undefined, placeholder: 'Send a message...', loading: false,
|
|
34
|
+
suggestions: undefined, suggestionMode: 'submit', persistSuggestions: false, proseSize: 'sm',
|
|
35
|
+
codeTheme: 'github-dark-dimmed', codeHighlight: true, chatTitle: undefined,
|
|
36
|
+
models: undefined, currentModel: undefined, context: undefined, scrollButton: true,
|
|
37
|
+
search: false, voice: false, slashCommands: undefined, slashActiveIds: undefined, slashCompact: false,
|
|
38
|
+
actionsReveal: 'always',
|
|
39
|
+
}, (props, { dispatch, flag, element }) => {
|
|
40
|
+
// Detect consumer-projected header controls so the header opens for them even
|
|
41
|
+
// without a title/models/context. Mirrors the <kai-model> light-DOM read pattern.
|
|
42
|
+
const [hasHeaderStart, setHasHeaderStart] = createSignal(false);
|
|
43
|
+
const [hasHeaderEnd, setHasHeaderEnd] = createSignal(false);
|
|
44
|
+
onMount(() => {
|
|
45
|
+
const read = () => {
|
|
46
|
+
setHasHeaderStart(!!element.querySelector(':scope > [slot="header-start"]'));
|
|
47
|
+
setHasHeaderEnd(!!element.querySelector(':scope > [slot="header-end"]'));
|
|
48
|
+
};
|
|
49
|
+
read();
|
|
50
|
+
const observer = new MutationObserver(read);
|
|
51
|
+
observer.observe(element, { childList: true });
|
|
52
|
+
onCleanup(() => observer.disconnect());
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<ChatThread
|
|
57
|
+
messages={props.messages} value={props.value as string | undefined} placeholder={props.placeholder as string}
|
|
58
|
+
loading={flag('loading')} suggestions={props.suggestions as string[] | undefined}
|
|
59
|
+
suggestionMode={props.suggestionMode as 'submit' | 'fill'} persistSuggestions={flag('persistSuggestions')}
|
|
60
|
+
proseSize={props.proseSize as ProseSize}
|
|
61
|
+
codeTheme={props.codeTheme as string} codeHighlight={flag('codeHighlight')}
|
|
62
|
+
chatTitle={props.chatTitle as string | undefined} models={props.models as ModelOption[] | undefined}
|
|
63
|
+
currentModel={props.currentModel as string | undefined} context={props.context as ChatThreadContextUsage | undefined}
|
|
64
|
+
scrollButton={props.scrollButton !== false} search={flag('search')} voice={flag('voice')}
|
|
65
|
+
slashCommands={props.slashCommands as SlashCommandItem[] | undefined}
|
|
66
|
+
slashActiveIds={props.slashActiveIds as string[] | undefined} slashCompact={flag('slashCompact')}
|
|
67
|
+
actionsReveal={props.actionsReveal as 'always' | 'hover'}
|
|
68
|
+
onValueChange={(value) => dispatch('kai-value-change', { value })}
|
|
69
|
+
onSubmit={(detail) => dispatch('kai-submit', detail)}
|
|
70
|
+
onSuggestionClick={(value) => dispatch('kai-suggestion-click', { value })}
|
|
71
|
+
onModelChange={(modelId) => dispatch('kai-model-change', { modelId })}
|
|
72
|
+
onMessageAction={(detail) => dispatch('kai-message-action', detail)}
|
|
73
|
+
onSearch={() => dispatch('kai-search', {})}
|
|
74
|
+
onVoice={() => dispatch('kai-voice', {})}
|
|
75
|
+
onSlashSelect={(command) => dispatch('kai-slash-select', { command })}
|
|
76
|
+
headerStart={hasHeaderStart()}
|
|
77
|
+
headerEnd={hasHeaderEnd()}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { onMount } from 'solid-js';
|
|
3
|
+
import './register'; // side effect: registers the custom elements
|
|
4
|
+
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
5
|
+
|
|
6
|
+
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
7
|
+
declare module 'solid-js' {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
9
|
+
namespace JSX {
|
|
10
|
+
interface IntrinsicElements {
|
|
11
|
+
'kai-checkpoint': JSX.HTMLAttributes<HTMLElement>;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Render the actual `<kai-checkpoint>` custom element configured by attributes. */
|
|
17
|
+
function CheckpointElement(props: { label?: string; tooltip?: string; variant?: string; size?: string }) {
|
|
18
|
+
let el: HTMLElement | undefined;
|
|
19
|
+
onMount(() => {
|
|
20
|
+
if (!el) return;
|
|
21
|
+
if (props.label) el.setAttribute('label', props.label);
|
|
22
|
+
if (props.tooltip) el.setAttribute('tooltip', props.tooltip);
|
|
23
|
+
if (props.variant) el.setAttribute('variant', props.variant);
|
|
24
|
+
if (props.size) el.setAttribute('size', props.size);
|
|
25
|
+
el.addEventListener('kai-select', () => console.log('checkpoint selected'));
|
|
26
|
+
});
|
|
27
|
+
return <kai-checkpoint ref={(e) => (el = e as HTMLElement)} style={{ display: 'inline-block', padding: '16px' }} />;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
31
|
+
<kai-checkpoint label="Restore" tooltip="Restore this checkpoint" variant="outline" size="sm"></kai-checkpoint>
|
|
32
|
+
|
|
33
|
+
<script type="module">
|
|
34
|
+
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
35
|
+
|
|
36
|
+
const cp = document.querySelector('kai-checkpoint');
|
|
37
|
+
// events are CustomEvents on the element (they do not bubble)
|
|
38
|
+
cp.addEventListener('kai-select', () => console.log('restore checkpoint'));
|
|
39
|
+
</script>`;
|
|
40
|
+
|
|
41
|
+
const meta = {
|
|
42
|
+
title: 'Components/Checkpoint',
|
|
43
|
+
tags: ['autodocs'],
|
|
44
|
+
argTypes: argTypesFor('kai-checkpoint'),
|
|
45
|
+
parameters: {
|
|
46
|
+
layout: 'fullscreen',
|
|
47
|
+
docs: {
|
|
48
|
+
description: specDescription('kai-checkpoint', [
|
|
49
|
+
'`<kai-checkpoint>` is the framework-agnostic **web component** for a bookmark/checkpoint button (with an optional tooltip and label), isolated in **Shadow DOM**.',
|
|
50
|
+
'**When to use:** marking a restore point in a conversation in a non-Solid app. In SolidJS, compose the `Checkpoint` primitives.',
|
|
51
|
+
'**Placement:** inline inside a message row (e.g. in the action bar) or in a chat toolbar; it is a compact `inline-block` button and does not need a dedicated row.',
|
|
52
|
+
"**How to use:** register once with `import '@kitn.ai/ui/elements'`, set `label`, `tooltip`, `variant` (`ghost` | `default` | `outline`), and `size` via attributes, and listen for the `kai-select` **CustomEvent** on click.",
|
|
53
|
+
'See the **Code** tab for HTML usage.',
|
|
54
|
+
]),
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
} satisfies Meta;
|
|
58
|
+
|
|
59
|
+
export default meta;
|
|
60
|
+
type Story = StoryObj;
|
|
61
|
+
|
|
62
|
+
/** A labeled checkpoint button. (Add a `tooltip` attribute for a hover hint — see the Code tab.) */
|
|
63
|
+
export const Labeled: Story = {
|
|
64
|
+
render: () => <CheckpointElement label="Restore" variant="outline" />,
|
|
65
|
+
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** Icon-only (no label), using an `icon` size. */
|
|
69
|
+
export const IconOnly: Story = {
|
|
70
|
+
name: 'Icon Only',
|
|
71
|
+
render: () => <CheckpointElement size="icon-sm" />,
|
|
72
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Show } from 'solid-js';
|
|
2
|
+
import { defineWebComponent } from './define';
|
|
3
|
+
import { Checkpoint, CheckpointIcon, CheckpointTrigger } from '../components/checkpoint';
|
|
4
|
+
|
|
5
|
+
interface Props extends Record<string, unknown> {
|
|
6
|
+
/** Optional text beside the icon. */
|
|
7
|
+
label?: string;
|
|
8
|
+
/** Tooltip on hover. */
|
|
9
|
+
tooltip?: string;
|
|
10
|
+
/** Visual button style. */
|
|
11
|
+
variant?: 'ghost' | 'default' | 'outline';
|
|
12
|
+
/** Button size (use an `icon*` size for an icon-only checkpoint). */
|
|
13
|
+
size?: 'sm' | 'md' | 'lg' | 'icon' | 'icon-sm';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Events fired by `<kai-checkpoint>`. */
|
|
17
|
+
interface Events {
|
|
18
|
+
/** The checkpoint was clicked. */
|
|
19
|
+
'kai-select': void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `<kai-checkpoint>` — a bookmark/checkpoint button (optional tooltip + label).
|
|
24
|
+
* Emits `select`.
|
|
25
|
+
*/
|
|
26
|
+
defineWebComponent<Props, Events>('kai-checkpoint', {
|
|
27
|
+
label: undefined,
|
|
28
|
+
tooltip: undefined,
|
|
29
|
+
variant: 'ghost',
|
|
30
|
+
size: 'sm',
|
|
31
|
+
}, (props, { dispatch }) => (
|
|
32
|
+
<Checkpoint>
|
|
33
|
+
<CheckpointTrigger
|
|
34
|
+
tooltip={props.tooltip}
|
|
35
|
+
// Icon-only (no visible label) needs an accessible name: prefer the
|
|
36
|
+
// tooltip text, else a sensible default. With a visible label, the text
|
|
37
|
+
// is the name — leave aria-label unset so it isn't duplicated.
|
|
38
|
+
aria-label={props.label ? undefined : (props.tooltip ?? 'Checkpoint')}
|
|
39
|
+
variant={props.variant}
|
|
40
|
+
size={props.size}
|
|
41
|
+
onClick={() => dispatch('kai-select')}
|
|
42
|
+
>
|
|
43
|
+
<CheckpointIcon />
|
|
44
|
+
<Show when={props.label}><span class="ml-1.5">{props.label}</span></Show>
|
|
45
|
+
</CheckpointTrigger>
|
|
46
|
+
</Checkpoint>
|
|
47
|
+
));
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { createSignal, onMount, type JSX } from 'solid-js';
|
|
3
|
+
import './choice';
|
|
4
|
+
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
5
|
+
import type { ChoiceCardData } from '../components/choice-card';
|
|
6
|
+
import type { CardEvent } from '../primitives/card-contract';
|
|
7
|
+
|
|
8
|
+
declare module 'solid-js' {
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
10
|
+
namespace JSX {
|
|
11
|
+
interface IntrinsicElements {
|
|
12
|
+
'kai-choice': JSX.HTMLAttributes<HTMLElement> & {
|
|
13
|
+
heading?: string;
|
|
14
|
+
'card-id'?: string;
|
|
15
|
+
ref?: (el: HTMLElement) => void;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ChoiceEl = HTMLElement & { data?: ChoiceCardData; resolution?: Record<string, unknown> };
|
|
22
|
+
|
|
23
|
+
function Frame(props: { children: JSX.Element }) {
|
|
24
|
+
return <div style={{ 'max-width': '460px' }}>{props.children}</div>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Mounts a <kai-choice>, sets `.data`, logs the emitted CardEvent under the render. */
|
|
28
|
+
function ChoiceDemo(props: { def: ChoiceCardData; cardId: string; heading?: string }) {
|
|
29
|
+
const [log, setLog] = createSignal<CardEvent[]>([]);
|
|
30
|
+
let el: ChoiceEl | undefined;
|
|
31
|
+
onMount(() => {
|
|
32
|
+
if (!el) return;
|
|
33
|
+
el.data = props.def;
|
|
34
|
+
el.addEventListener('kai-card', (e) => {
|
|
35
|
+
const detail = (e as CustomEvent<CardEvent>).detail;
|
|
36
|
+
setLog((prev) => [...prev, detail]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
return (
|
|
40
|
+
<Frame>
|
|
41
|
+
<div style={{ display: 'flex', 'flex-direction': 'column', gap: '12px' }}>
|
|
42
|
+
<kai-choice ref={(e) => (el = e as ChoiceEl)} card-id={props.cardId} heading={props.heading} />
|
|
43
|
+
<pre
|
|
44
|
+
style={{
|
|
45
|
+
margin: 0,
|
|
46
|
+
'max-height': '180px',
|
|
47
|
+
overflow: 'auto',
|
|
48
|
+
background: 'var(--color-muted, #f4f4f5)',
|
|
49
|
+
'border-radius': '8px',
|
|
50
|
+
padding: '8px',
|
|
51
|
+
'font-size': '12px',
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{log().length === 0 ? '// emitted CardEvents appear here' : JSON.stringify(log(), null, 2)}
|
|
55
|
+
</pre>
|
|
56
|
+
</div>
|
|
57
|
+
</Frame>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const PLANS: ChoiceCardData = {
|
|
62
|
+
prompt: 'Which plan fits your team?',
|
|
63
|
+
options: [
|
|
64
|
+
{ id: 'free', label: 'Free', description: 'For trying things out', meta: '$0' },
|
|
65
|
+
{
|
|
66
|
+
id: 'pro',
|
|
67
|
+
label: 'Pro',
|
|
68
|
+
description: 'For growing teams',
|
|
69
|
+
meta: '$12/seat',
|
|
70
|
+
recommended: true,
|
|
71
|
+
payload: { plan: 'pro' },
|
|
72
|
+
},
|
|
73
|
+
{ id: 'enterprise', label: 'Enterprise', description: 'SSO, audit log, SLA', meta: 'Contact us' },
|
|
74
|
+
{ id: 'legacy', label: 'Legacy', description: 'No longer available', disabled: true },
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const TEMPLATES: ChoiceCardData = {
|
|
79
|
+
prompt: 'Pick a workspace template.',
|
|
80
|
+
submitLabel: 'Create workspace',
|
|
81
|
+
options: [
|
|
82
|
+
{
|
|
83
|
+
id: 'blank',
|
|
84
|
+
label: 'Blank',
|
|
85
|
+
description: 'Start from scratch',
|
|
86
|
+
media: { image: 'https://placehold.co/80x80/png?text=Blank', imageAlt: 'A blank canvas' },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'kanban',
|
|
90
|
+
label: 'Kanban',
|
|
91
|
+
description: 'Board with columns',
|
|
92
|
+
recommended: true,
|
|
93
|
+
media: { image: 'https://placehold.co/80x80/png?text=Kanban', imageAlt: 'A kanban board' },
|
|
94
|
+
payload: { template: 'kanban' },
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'docs',
|
|
98
|
+
label: 'Docs',
|
|
99
|
+
description: 'Wiki-style pages',
|
|
100
|
+
media: { image: 'https://placehold.co/80x80/png?text=Docs', imageAlt: 'A document page' },
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const QUICK_REPLIES: ChoiceCardData = {
|
|
106
|
+
prompt: 'How did this answer land?',
|
|
107
|
+
options: [
|
|
108
|
+
{ id: 'great', label: 'That solved it', payload: { sentiment: 'positive' } },
|
|
109
|
+
{ id: 'partly', label: 'Partly — needs more', payload: { sentiment: 'neutral' } },
|
|
110
|
+
{ id: 'no', label: 'Not what I meant', payload: { sentiment: 'negative' } },
|
|
111
|
+
],
|
|
112
|
+
allowOther: { label: 'Something else…', placeholder: 'Tell me what you expected' },
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const HEADING_MAP: Record<string, string | undefined> = {
|
|
116
|
+
'card-plans': 'Choose a plan',
|
|
117
|
+
'card-templates': 'New workspace',
|
|
118
|
+
'card-replies': undefined,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const HTML_SNIPPET = (def: ChoiceCardData, cardId: string) => {
|
|
122
|
+
const heading = HEADING_MAP[cardId];
|
|
123
|
+
return `<kai-choice${heading ? ` heading="${heading}"` : ''}></kai-choice>
|
|
124
|
+
<script type="module">
|
|
125
|
+
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
126
|
+
|
|
127
|
+
const el = document.querySelector('kai-choice');
|
|
128
|
+
// \`data\` is the CardEnvelope.data (set as a property).
|
|
129
|
+
el.data = ${JSON.stringify(def, null, 2)};
|
|
130
|
+
|
|
131
|
+
// Cards bubble ONE \`kai-card\` CustomEvent carrying a typed CardEvent.
|
|
132
|
+
el.addEventListener('kai-card', (e) => {
|
|
133
|
+
const ev = e.detail; // { kind:'action', cardId, action, payload? } | { kind:'ready', ... } | ...
|
|
134
|
+
if (ev.kind === 'action') console.log('chose', ev.action, ev.payload);
|
|
135
|
+
});
|
|
136
|
+
</script>`;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const meta = {
|
|
140
|
+
title: 'Generative UI/Cards/kai-choice',
|
|
141
|
+
tags: ['autodocs'],
|
|
142
|
+
argTypes: argTypesFor('kai-choice'),
|
|
143
|
+
parameters: {
|
|
144
|
+
layout: 'padded',
|
|
145
|
+
docs: {
|
|
146
|
+
description: specDescription('kai-choice', [
|
|
147
|
+
"`<kai-choice>` is a **single-select** 'pick one of N rich options' card (set via the `data` **property**): an optional prompt + a radiogroup of list rows + a **Submit** button. Clicking a row (or Space/Enter on the focused row) **selects** it locally — nothing is emitted. Submit then emits the Card contract's **`action`** verb up a bubbling **`kai-card`** CustomEvent of `{ kind:'action', cardId, action: option.id, payload? }`, then **resolves** the card (the chosen option shown read-only) so the same pick can't double-fire. The Submit label is `submitLabel` (default 'Submit').",
|
|
148
|
+
'**Rich-but-bounded options:** every field except `id`/`label` is optional — `description`, `media` (image + `imageAlt`, or a named `icon`), `meta` (trailing price/badge), `recommended` (a pill), `disabled` (inert + skipped in keyboard nav), and an opaque `payload` echoed back. A weaker model can just omit them.',
|
|
149
|
+
"**`allowOther`** (opt-in) appends a final 'Other…' row: selecting it reveals an inline text input. The single shared **Submit** stays disabled until the text is non-empty; submitting emits `{ kind:'action', action:'__other__', payload:{ text } }`.",
|
|
150
|
+
'**Accessibility:** the options are a WAI-ARIA `radiogroup` (`role="radio"` + `aria-checked`) with **roving tabindex** — one tab stop in, Arrow keys move focus (skipping disabled), Enter/Space select the focused row. Disabled options are `aria-disabled` and not focusable; option images carry alt text; the Other input has a label. **0 axe violations** (light + dark).',
|
|
151
|
+
'**Events** (all frozen Card-contract verbs): `ready` on mount, `action` on Submit, `error` for a malformed definition (renders the inline `kai-card` error). It **never invents events**, and — because it carries a registry entry — `<kai-cards>` / `renderCard` dispatch it automatically.',
|
|
152
|
+
]),
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
} satisfies Meta;
|
|
156
|
+
|
|
157
|
+
export default meta;
|
|
158
|
+
type Story = StoryObj;
|
|
159
|
+
|
|
160
|
+
/** A list of plans with a `recommended` pill, `meta` prices, and a disabled legacy row.
|
|
161
|
+
* Select a row, then click Submit to emit the `action`. */
|
|
162
|
+
export const Plans: Story = {
|
|
163
|
+
render: () => <ChoiceDemo def={PLANS} cardId="card-plans" heading="Choose a plan" />,
|
|
164
|
+
parameters: { docs: { source: { code: HTML_SNIPPET(PLANS, 'card-plans'), language: 'html' } } },
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/** Rows with leading images (each `media.image` carries `imageAlt`) and a custom `submitLabel`. */
|
|
168
|
+
export const WithMedia: Story = {
|
|
169
|
+
render: () => <ChoiceDemo def={TEMPLATES} cardId="card-templates" heading="New workspace" />,
|
|
170
|
+
parameters: { docs: { source: { code: HTML_SNIPPET(TEMPLATES, 'card-templates'), language: 'html' } } },
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/** Quick replies with the `allowOther` free-text escape: select Other, type, then Submit. */
|
|
174
|
+
export const AllowOther: Story = {
|
|
175
|
+
render: () => <ChoiceDemo def={QUICK_REPLIES} cardId="card-replies" />,
|
|
176
|
+
parameters: { docs: { source: { code: HTML_SNIPPET(QUICK_REPLIES, 'card-replies'), language: 'html' } } },
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const RESOLVED_CHOICE: ChoiceCardData = {
|
|
180
|
+
prompt: 'Which plan?',
|
|
181
|
+
options: [
|
|
182
|
+
{ id: 'free', label: 'Free', meta: '$0' },
|
|
183
|
+
{ id: 'pro', label: 'Pro', meta: '$20/mo' },
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/** The card after the user chose **Pro** — only the chosen option shown, no radiogroup or Submit. */
|
|
188
|
+
export const Resolved: Story = {
|
|
189
|
+
name: 'Resolved (read-only)',
|
|
190
|
+
render: () => {
|
|
191
|
+
let el: ChoiceEl | undefined;
|
|
192
|
+
onMount(() => {
|
|
193
|
+
if (!el) return;
|
|
194
|
+
el.data = RESOLVED_CHOICE;
|
|
195
|
+
el.resolution = { kind: 'action', action: 'pro' };
|
|
196
|
+
});
|
|
197
|
+
return (
|
|
198
|
+
<Frame>
|
|
199
|
+
<kai-choice ref={(e) => (el = e as ChoiceEl)} card-id="card-resolved-choice" heading="Choose a plan" />
|
|
200
|
+
</Frame>
|
|
201
|
+
);
|
|
202
|
+
},
|
|
203
|
+
parameters: {
|
|
204
|
+
docs: {
|
|
205
|
+
source: {
|
|
206
|
+
code: `<kai-choice heading="Choose a plan"></kai-choice>
|
|
207
|
+
<script type="module">
|
|
208
|
+
import '@kitn.ai/ui/elements';
|
|
209
|
+
const el = document.querySelector('kai-choice');
|
|
210
|
+
el.data = ${JSON.stringify(RESOLVED_CHOICE, null, 2)};
|
|
211
|
+
// Setting .resolution renders the chromed read-only view — no radiogroup or Submit.
|
|
212
|
+
el.resolution = { kind: 'action', action: 'pro' };
|
|
213
|
+
</script>`,
|
|
214
|
+
language: 'html',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/** A malformed `data` (empty `options`) → the inline error state + an `error` event. */
|
|
221
|
+
export const ErrorState: Story = {
|
|
222
|
+
render: () => <ChoiceDemo def={{ options: [] } as unknown as ChoiceCardData} cardId="card-bad" />,
|
|
223
|
+
parameters: {
|
|
224
|
+
docs: {
|
|
225
|
+
source: {
|
|
226
|
+
code: `<kai-choice></kai-choice>
|
|
227
|
+
<script type="module">
|
|
228
|
+
import '@kitn.ai/ui/elements';
|
|
229
|
+
const el = document.querySelector('kai-choice');
|
|
230
|
+
// No options → inline error state + an \`error\` event.
|
|
231
|
+
el.data = { options: [] };
|
|
232
|
+
el.addEventListener('kai-card', (e) => {
|
|
233
|
+
if (e.detail.kind === 'error') console.warn('choice error:', e.detail.message);
|
|
234
|
+
});
|
|
235
|
+
</script>`,
|
|
236
|
+
language: 'html',
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { defineWebComponent } from './define';
|
|
2
|
+
import { ChoiceCard, type ChoiceCardData } from '../components/choice-card';
|
|
3
|
+
import type { CardResolution } from '../primitives/card-contract';
|
|
4
|
+
|
|
5
|
+
interface Props extends Record<string, unknown> {
|
|
6
|
+
/** The choice definition (the CardEnvelope.data). Set as a JS PROPERTY:
|
|
7
|
+
* `el.data = { prompt, options:[…], allowOther?, submitLabel? }`. Import
|
|
8
|
+
* `ChoiceCardData` from `@kitn.ai/ui` for the full shape. */
|
|
9
|
+
data?: Record<string, unknown>;
|
|
10
|
+
/** Stable card id correlating every emitted CardEvent. Attribute: `card-id`. */
|
|
11
|
+
cardId?: string;
|
|
12
|
+
/** Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. */
|
|
13
|
+
heading?: string;
|
|
14
|
+
/** Set when the user resolved this card; renders the read-only view. Property:
|
|
15
|
+
* `el.resolution = { kind:'action', action:'…' }`. */
|
|
16
|
+
resolution?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `<kai-choice>` — a single-select **"pick one of N rich options"** card (set via the
|
|
21
|
+
* `data` property): a prompt + a radiogroup of list rows. Clicking a row **selects** it
|
|
22
|
+
* (no emit); the **Submit** button below then emits the Card contract's **`action`** verb
|
|
23
|
+
* up a bubbling **`kai-card`** CustomEvent (`{ kind:'action', cardId, action: option.id,
|
|
24
|
+
* payload? }`) and resolves the card so the same pick can't double-fire. An optional
|
|
25
|
+
* `allowOther` free-text escape appends a selectable "Other…" row that reveals an inline
|
|
26
|
+
* input; the same Submit emits `action:'__other__'` with `{ text }`. Also emits `ready` on
|
|
27
|
+
* mount and `error` for a malformed definition (inline error). Routes through a
|
|
28
|
+
* `CardProvider` when present, else the bubbling `kai-card` event. Isolated in Shadow DOM;
|
|
29
|
+
* theme-aware via the shared tokens.
|
|
30
|
+
*/
|
|
31
|
+
defineWebComponent<Props>(
|
|
32
|
+
'kai-choice',
|
|
33
|
+
{
|
|
34
|
+
data: undefined,
|
|
35
|
+
cardId: undefined,
|
|
36
|
+
heading: undefined,
|
|
37
|
+
resolution: undefined,
|
|
38
|
+
},
|
|
39
|
+
(props, { element }) => (
|
|
40
|
+
<ChoiceCard
|
|
41
|
+
data={props.data as ChoiceCardData | undefined}
|
|
42
|
+
cardId={props.cardId ?? (element.id || 'kai-choice')}
|
|
43
|
+
heading={props.heading}
|
|
44
|
+
resolution={props.resolution as CardResolution | undefined}
|
|
45
|
+
hostElement={element}
|
|
46
|
+
/>
|
|
47
|
+
),
|
|
48
|
+
);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { onMount } from 'solid-js';
|
|
3
|
+
import './register'; // side effect: registers the custom elements
|
|
4
|
+
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
5
|
+
|
|
6
|
+
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
7
|
+
declare module 'solid-js' {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
9
|
+
namespace JSX {
|
|
10
|
+
interface IntrinsicElements {
|
|
11
|
+
'kai-code-block': JSX.HTMLAttributes<HTMLElement>;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const sampleCode = `export function add(a: number, b: number): number {
|
|
17
|
+
return a + b;
|
|
18
|
+
}`;
|
|
19
|
+
|
|
20
|
+
const pythonCode = `def fib(n):
|
|
21
|
+
a, b = 0, 1
|
|
22
|
+
for _ in range(n):
|
|
23
|
+
a, b = b, a + b
|
|
24
|
+
return a`;
|
|
25
|
+
|
|
26
|
+
/** Render the actual `<kai-code-block>` custom element with a `code` property. */
|
|
27
|
+
function CodeBlockElement(props: { code: string; language?: string }) {
|
|
28
|
+
let el: (HTMLElement & { code?: string }) | undefined;
|
|
29
|
+
onMount(() => {
|
|
30
|
+
if (el) {
|
|
31
|
+
el.code = props.code;
|
|
32
|
+
if (props.language) el.setAttribute('language', props.language);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return (
|
|
36
|
+
<kai-code-block
|
|
37
|
+
ref={(e) => (el = e as HTMLElement)}
|
|
38
|
+
style={{ display: 'block', padding: '16px', 'max-width': '720px' }}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
44
|
+
<kai-code-block id="code" language="ts" code-theme="github-dark-dimmed"></kai-code-block>
|
|
45
|
+
|
|
46
|
+
<script type="module">
|
|
47
|
+
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
48
|
+
|
|
49
|
+
const code = document.getElementById('code');
|
|
50
|
+
code.code = 'export function add(a, b) {\\n return a + b;\\n}';
|
|
51
|
+
</script>`;
|
|
52
|
+
|
|
53
|
+
const meta = {
|
|
54
|
+
title: 'Components/CodeBlock',
|
|
55
|
+
tags: ['autodocs'],
|
|
56
|
+
argTypes: argTypesFor('kai-code-block'),
|
|
57
|
+
parameters: {
|
|
58
|
+
layout: 'fullscreen',
|
|
59
|
+
docs: {
|
|
60
|
+
description: specDescription('kai-code-block', [
|
|
61
|
+
'`<kai-code-block>` is the framework-agnostic **web component** for a single syntax-highlighted code block, complete with a copy button, isolated in **Shadow DOM**.',
|
|
62
|
+
'**When to use:** dropping a highlighted snippet into a non-Solid app. In SolidJS, compose `CodeBlock` + `CodeBlockCode` directly.',
|
|
63
|
+
'**Placement:** as a block inside a message row, tutorial card, or documentation pane; it is `display: block` and fills container width, so set `max-width` on the parent or the element to control line lengths.',
|
|
64
|
+
"**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the source via the `code` **property** (`el.code = '...'`), and pick a grammar with the `language` attribute (defaults to `tsx`). Tune highlighting with `code-theme` / `code-highlight`.",
|
|
65
|
+
'See the **Code** tab for HTML usage.',
|
|
66
|
+
]),
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
} satisfies Meta;
|
|
70
|
+
|
|
71
|
+
export default meta;
|
|
72
|
+
type Story = StoryObj;
|
|
73
|
+
|
|
74
|
+
/** A TypeScript snippet (the default `tsx` grammar). */
|
|
75
|
+
export const TypeScript: Story = {
|
|
76
|
+
render: () => <CodeBlockElement code={sampleCode} language="ts" />,
|
|
77
|
+
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** A different grammar via the `language` attribute. */
|
|
81
|
+
export const Python: Story = {
|
|
82
|
+
render: () => <CodeBlockElement code={pythonCode} language="python" />,
|
|
83
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { defineWebComponent } from './define';
|
|
2
|
+
import { CodeBlock, CodeBlockCode } from '../components/code-block';
|
|
3
|
+
import { ChatConfig, useChatConfig, type ProseSize } from '../primitives/chat-config';
|
|
4
|
+
|
|
5
|
+
interface Props extends Record<string, unknown> {
|
|
6
|
+
/** The source code to render. */
|
|
7
|
+
code: string;
|
|
8
|
+
/** Language grammar (e.g. `js`, `python`). Defaults to `tsx`. */
|
|
9
|
+
language?: string;
|
|
10
|
+
/** Shiki theme name. */
|
|
11
|
+
codeTheme?: string;
|
|
12
|
+
/** Disable syntax highlighting (renders plain text, no Shiki). */
|
|
13
|
+
codeHighlight?: boolean;
|
|
14
|
+
/** Code text sizing. */
|
|
15
|
+
proseSize?: ProseSize;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `<kai-code-block>` — one syntax-highlighted code block (with a copy button).
|
|
20
|
+
* Code via the `code` property; `language`/`code-theme` via attributes.
|
|
21
|
+
*/
|
|
22
|
+
defineWebComponent<Props>('kai-code-block', {
|
|
23
|
+
code: '',
|
|
24
|
+
language: undefined,
|
|
25
|
+
codeTheme: 'github-dark-dimmed',
|
|
26
|
+
codeHighlight: true,
|
|
27
|
+
proseSize: 'sm',
|
|
28
|
+
}, (props, { flag }) => {
|
|
29
|
+
const outer = useChatConfig();
|
|
30
|
+
return (
|
|
31
|
+
<ChatConfig
|
|
32
|
+
proseSize={props.proseSize}
|
|
33
|
+
codeTheme={props.codeTheme}
|
|
34
|
+
codeHighlight={flag('codeHighlight')}
|
|
35
|
+
portalMount={outer.portalMount()}
|
|
36
|
+
>
|
|
37
|
+
<CodeBlock>
|
|
38
|
+
<CodeBlockCode code={props.code} language={props.language} />
|
|
39
|
+
</CodeBlock>
|
|
40
|
+
</ChatConfig>
|
|
41
|
+
);
|
|
42
|
+
});
|