@kitn.ai/ui 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/bin/mcp.js +22 -0
- package/dist/bash-InADTalH.js +6 -1
- package/dist/components/artifact.d.ts +58 -0
- package/dist/components/attachments.d.ts +67 -0
- package/dist/components/card-fallback.d.ts +10 -0
- package/dist/components/card-renderer.d.ts +13 -0
- package/dist/components/card.d.ts +28 -0
- package/dist/components/chain-of-thought.d.ts +29 -0
- package/dist/components/chat-container.d.ts +19 -0
- package/dist/components/chat-scope-picker.d.ts +9 -0
- package/dist/components/chat-thread.d.ts +93 -0
- package/dist/components/checkpoint.d.ts +20 -0
- package/dist/components/choice-card.d.ts +77 -0
- package/dist/components/code-block.d.ts +16 -0
- package/dist/components/confirm-card.d.ts +57 -0
- package/dist/components/context.d.ts +65 -0
- package/dist/components/conversation-item.d.ts +8 -0
- package/dist/components/conversation-list.d.ts +11 -0
- package/dist/components/embed.d.ts +21 -0
- package/dist/components/empty.d.ts +43 -0
- package/dist/components/feedback-bar.d.ts +45 -0
- package/dist/components/file-tree.d.ts +57 -0
- package/dist/components/file-upload.d.ts +16 -0
- package/dist/components/form-widgets.d.ts +31 -0
- package/dist/components/form.d.ts +103 -0
- package/dist/components/image.d.ts +12 -0
- package/dist/components/link-preview.d.ts +23 -0
- package/dist/components/loader.d.ts +60 -0
- package/dist/components/markdown.d.ts +8 -0
- package/dist/components/message-skills.d.ts +15 -0
- package/dist/components/message.d.ts +54 -0
- package/dist/components/model-switcher.d.ts +8 -0
- package/dist/components/prompt-input.d.ts +38 -0
- package/dist/components/prompt-suggestion.d.ts +13 -0
- package/dist/components/reasoning.d.ts +20 -0
- package/dist/components/response-stream.d.ts +14 -0
- package/dist/components/scroll-button.d.ts +7 -0
- package/dist/components/slash-command.d.ts +15 -0
- package/dist/components/source.d.ts +23 -0
- package/dist/components/tasks-card.d.ts +71 -0
- package/dist/components/text-shimmer.d.ts +9 -0
- package/dist/components/thinking-bar.d.ts +10 -0
- package/dist/components/tool.d.ts +16 -0
- package/dist/components/use-card-resolution.d.ts +12 -0
- package/dist/components/voice-input.d.ts +7 -0
- package/dist/core-AYMC6_lb.js +5874 -12
- package/dist/css-M7EaDHN_.js +6 -1
- package/dist/elements/artifact.d.ts +1 -0
- package/dist/elements/attachments.d.ts +1 -0
- package/dist/elements/card.d.ts +1 -0
- package/dist/elements/cards.d.ts +0 -0
- package/dist/elements/chain-of-thought.d.ts +13 -0
- package/dist/elements/chat-scope-picker.d.ts +1 -0
- package/dist/elements/chat-types.d.ts +41 -0
- package/dist/elements/chat-workspace.d.ts +1 -0
- package/dist/elements/chat.d.ts +1 -0
- package/dist/elements/checkpoint.d.ts +1 -0
- package/dist/elements/choice.d.ts +1 -0
- package/dist/elements/code-block.d.ts +1 -0
- package/dist/elements/confirm-card.d.ts +1 -0
- package/dist/elements/context-meter.d.ts +1 -0
- package/dist/elements/conversation-list.d.ts +11 -0
- package/dist/elements/css.d.ts +1 -0
- package/dist/elements/default-input.d.ts +40 -0
- package/dist/elements/define.d.ts +42 -0
- package/dist/elements/embed.d.ts +1 -0
- package/dist/elements/empty.d.ts +1 -0
- package/dist/elements/feedback-bar.d.ts +1 -0
- package/dist/elements/file-tree.d.ts +1 -0
- package/dist/elements/file-upload.d.ts +1 -0
- package/dist/elements/form.d.ts +1 -0
- package/dist/elements/image.d.ts +1 -0
- package/dist/elements/link-preview.d.ts +1 -0
- package/dist/elements/loader.d.ts +1 -0
- package/dist/elements/markdown.d.ts +1 -0
- package/dist/elements/message-skills.d.ts +17 -0
- package/dist/elements/message.d.ts +1 -0
- package/dist/elements/model-switcher.d.ts +11 -0
- package/dist/elements/popover.d.ts +1 -0
- package/dist/elements/prompt-input.d.ts +9 -0
- package/dist/elements/prompt-suggestions.d.ts +7 -0
- package/dist/elements/reasoning.d.ts +1 -0
- package/dist/elements/register-impl.d.ts +0 -0
- package/dist/elements/register.d.ts +3 -0
- package/dist/elements/remote.d.ts +1 -0
- package/dist/elements/resizable.d.ts +17 -0
- package/dist/elements/response-stream.d.ts +1 -0
- package/dist/elements/scroll-button.d.ts +1 -0
- package/dist/elements/source.d.ts +18 -0
- package/dist/elements/switch.d.ts +1 -0
- package/dist/elements/tasks.d.ts +1 -0
- package/dist/elements/text-shimmer.d.ts +1 -0
- package/dist/elements/thinking-bar.d.ts +1 -0
- package/dist/elements/tool.d.ts +1 -0
- package/dist/elements/voice-input.d.ts +1 -0
- package/dist/elements.d.ts +732 -0
- package/dist/engine-javascript-vq0WuIJl.js +2516 -14
- package/dist/github-dark-dimmed-DUshB20C.js +4 -1
- package/dist/github-light-JYsPkUQd.js +4 -1
- package/dist/html-CPZ3oZQ7.js +10 -1
- package/dist/index.d.ts +112 -2
- package/dist/index.js +13551 -0
- package/dist/javascript-C25yR2R2.js +6 -1
- package/dist/json-DxJze_jm.js +6 -1
- package/dist/kitn-chat.es.js +1 -77
- package/dist/llms/llms-full.txt +1 -1
- package/dist/llms/llms.txt +1 -1
- package/dist/mcp.es.js +2140 -0
- package/dist/primitives/card-contract.d.ts +103 -0
- package/dist/primitives/card-host.d.ts +12 -0
- package/dist/primitives/card-registry.d.ts +17 -0
- package/dist/primitives/card-resolution.d.ts +8 -0
- package/dist/primitives/card-routing.d.ts +12 -0
- package/dist/primitives/card-validate.d.ts +25 -0
- package/dist/primitives/chat-config.d.ts +31 -0
- package/dist/primitives/controllable.d.ts +13 -0
- package/dist/primitives/embed-providers.d.ts +65 -0
- package/dist/primitives/highlighter.d.ts +23 -0
- package/dist/primitives/link-preview.d.ts +47 -0
- package/dist/primitives/pdf-preview.d.ts +50 -0
- package/dist/primitives/use-auto-resize.d.ts +8 -0
- package/dist/primitives/use-resize-observer.d.ts +1 -0
- package/dist/primitives/use-stick-to-bottom.d.ts +5 -0
- package/dist/primitives/use-text-stream.d.ts +19 -0
- package/dist/primitives/use-voice-recorder.d.ts +9 -0
- package/dist/react/index.d.ts +1118 -0
- package/dist/react/runtime.d.ts +15 -0
- package/dist/react.js +265 -0
- package/dist/register-impl-wCsFPoh9.js +77 -0
- package/dist/register-ujhr1nBS.js +1 -0
- package/dist/remote/host-embed.d.ts +33 -0
- package/dist/remote/index.d.ts +2 -0
- package/dist/remote/origin.d.ts +5 -0
- package/dist/remote/provider-runtime.d.ts +27 -0
- package/dist/remote/provider.d.ts +2 -0
- package/dist/remote/validate.d.ts +4 -0
- package/dist/remote/version.d.ts +4 -0
- package/dist/remote/wire.d.ts +53 -0
- package/dist/svelte--5p79yCD.js +15 -1
- package/dist/tsx-B8rCNbgL.js +6 -1
- package/dist/types.d.ts +42 -0
- package/dist/typescript-RycA9KXf.js +6 -1
- package/dist/ui/action-icons.d.ts +11 -0
- package/dist/ui/avatar.d.ts +8 -0
- package/dist/ui/badge.d.ts +9 -0
- package/dist/ui/button.d.ts +10 -0
- package/dist/ui/collapsible.d.ts +27 -0
- package/dist/ui/dropdown.d.ts +20 -0
- package/dist/ui/hover-card.d.ts +28 -0
- package/dist/ui/overlay.d.ts +67 -0
- package/dist/ui/popover.d.ts +32 -0
- package/dist/ui/resizable.d.ts +76 -0
- package/dist/ui/scroll-area.d.ts +5 -0
- package/dist/ui/separator.d.ts +5 -0
- package/dist/ui/skeleton.d.ts +5 -0
- package/dist/ui/switch.d.ts +19 -0
- package/dist/ui/textarea.d.ts +6 -0
- package/dist/ui/tooltip.d.ts +8 -0
- package/dist/utils/cn.d.ts +2 -0
- package/dist/vue-BmIZj4XD.js +33 -1
- package/frameworks/react/index.tsx +4 -0
- package/frameworks/react/runtime.tsx +16 -2
- package/llms-full.txt +1 -1
- package/llms.txt +1 -1
- package/package.json +29 -11
- package/src/agent-tooling/archetypes.ts +46 -0
- package/src/agent-tooling/integrations/cloudflare.ts +88 -0
- package/src/agent-tooling/integrations/langgraph.ts +60 -0
- package/src/agent-tooling/integrations/mastra.ts +27 -0
- package/src/agent-tooling/integrations/mock.ts +34 -0
- package/src/agent-tooling/integrations/ollama.ts +47 -0
- package/src/agent-tooling/integrations/openrouter.ts +26 -0
- package/src/agent-tooling/integrations/pi.ts +40 -0
- package/src/agent-tooling/integrations/pydantic-ai.ts +52 -0
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +44 -0
- package/src/agent-tooling/mcp/manifest.ts +128 -0
- package/src/agent-tooling/mcp/server.ts +59 -0
- package/src/agent-tooling/mcp/stdio.ts +18 -0
- package/src/agent-tooling/mcp/tools/debug.ts +305 -0
- package/src/agent-tooling/mcp/tools/reference.ts +132 -0
- package/src/agent-tooling/mcp/tools/scaffold.ts +1627 -0
- package/src/agent-tooling/mcp/tools/theme.ts +340 -0
- package/src/agent-tooling/mcp/tools/types.ts +14 -0
- package/src/agent-tooling/registry.ts +41 -0
- package/src/agent-tooling/types.ts +30 -0
- package/src/elements/compiled.css +1 -1
- package/src/elements/define.tsx +7 -1
- package/src/elements/element-types.d.ts +2 -1
- package/src/elements/register-impl.ts +55 -0
- package/src/elements/register.ts +17 -51
- package/theme.css +4 -2
- package/src/components/artifact.stories.tsx +0 -138
- package/src/components/attachments.stories.tsx +0 -303
- package/src/components/attachments.test.tsx +0 -64
- package/src/components/chain-of-thought.stories.tsx +0 -211
- package/src/components/chat-container.stories.tsx +0 -187
- package/src/components/chat-thread.test.tsx +0 -58
- package/src/components/checkpoint.stories.tsx +0 -102
- package/src/components/code-block.stories.tsx +0 -150
- package/src/components/confirm-card.stories.tsx +0 -74
- package/src/components/context.stories.tsx +0 -229
- package/src/components/context.test.tsx +0 -133
- package/src/components/conversation-item.stories.tsx +0 -125
- package/src/components/conversation-list.stories.tsx +0 -133
- package/src/components/default-input.test.tsx +0 -111
- package/src/components/empty.stories.tsx +0 -434
- package/src/components/feedback-bar.stories.tsx +0 -138
- package/src/components/feedback-bar.test.tsx +0 -103
- package/src/components/file-tree.stories.tsx +0 -73
- package/src/components/file-upload.stories.tsx +0 -156
- package/src/components/image.stories.tsx +0 -89
- package/src/components/loader.stories.tsx +0 -181
- package/src/components/markdown.stories.tsx +0 -180
- package/src/components/message-action-bar.test.tsx +0 -128
- package/src/components/message-narrow.stories.tsx +0 -329
- package/src/components/message-skills.stories.tsx +0 -211
- package/src/components/message.stories.tsx +0 -281
- package/src/components/model-switcher.stories.tsx +0 -123
- package/src/components/model-switcher.test.tsx +0 -65
- package/src/components/prompt-input.stories.tsx +0 -222
- package/src/components/prompt-suggestion.stories.tsx +0 -142
- package/src/components/reasoning.stories.tsx +0 -140
- package/src/components/resizable.test.tsx +0 -226
- package/src/components/scroll-button.stories.tsx +0 -100
- package/src/components/slash-command.stories.tsx +0 -163
- package/src/components/source-list.test.tsx +0 -152
- package/src/components/source.stories.tsx +0 -124
- package/src/components/tasks-card.stories.tsx +0 -78
- package/src/components/text-shimmer.stories.tsx +0 -87
- package/src/components/thinking-bar.stories.tsx +0 -87
- package/src/components/tool.stories.tsx +0 -153
- package/src/components/voice-input.stories.tsx +0 -83
- package/src/elements/artifact.stories.tsx +0 -506
- package/src/elements/attachments.stories.tsx +0 -190
- package/src/elements/card.stories.tsx +0 -113
- package/src/elements/cards.stories.tsx +0 -59
- package/src/elements/catalog.stories.tsx +0 -491
- package/src/elements/chain-of-thought.declarative.test.tsx +0 -153
- package/src/elements/chain-of-thought.stories.tsx +0 -113
- package/src/elements/chat-scope-picker.stories.tsx +0 -73
- package/src/elements/chat-workspace.stories.tsx +0 -288
- package/src/elements/chat.stories.tsx +0 -151
- package/src/elements/checkpoint.stories.tsx +0 -72
- package/src/elements/choice.stories.tsx +0 -240
- package/src/elements/code-block.stories.tsx +0 -83
- package/src/elements/composed-shell.stories.tsx +0 -316
- package/src/elements/confirm-card.stories.tsx +0 -227
- package/src/elements/context-meter.stories.tsx +0 -151
- package/src/elements/conversation-list.declarative.test.tsx +0 -137
- package/src/elements/conversation-list.stories.tsx +0 -243
- package/src/elements/embed.stories.tsx +0 -213
- package/src/elements/empty.stories.tsx +0 -111
- package/src/elements/feedback-bar.stories.tsx +0 -114
- package/src/elements/file-tree.stories.tsx +0 -134
- package/src/elements/file-upload.stories.tsx +0 -82
- package/src/elements/form.stories.tsx +0 -248
- package/src/elements/image.stories.tsx +0 -71
- package/src/elements/link-preview.stories.tsx +0 -209
- package/src/elements/loader.stories.tsx +0 -88
- package/src/elements/markdown.stories.tsx +0 -76
- package/src/elements/message-skills.declarative.test.tsx +0 -104
- package/src/elements/message-skills.stories.tsx +0 -114
- package/src/elements/message.stories.tsx +0 -218
- package/src/elements/model-switcher.declarative.test.tsx +0 -130
- package/src/elements/model-switcher.stories.tsx +0 -198
- package/src/elements/popover.stories.tsx +0 -80
- package/src/elements/prompt-input-slash-command.test.tsx +0 -128
- package/src/elements/prompt-input-stoppable.test.tsx +0 -88
- package/src/elements/prompt-input-toolbar-actions.test.tsx +0 -120
- package/src/elements/prompt-input.stories.tsx +0 -331
- package/src/elements/prompt-suggestions.declarative.test.tsx +0 -164
- package/src/elements/prompt-suggestions.stories.tsx +0 -213
- package/src/elements/reasoning.stories.tsx +0 -77
- package/src/elements/remote.stories.tsx +0 -314
- package/src/elements/resizable.stories.tsx +0 -426
- package/src/elements/response-stream.stories.tsx +0 -79
- package/src/elements/scroll-button.stories.tsx +0 -202
- package/src/elements/scroll-button.test.tsx +0 -148
- package/src/elements/source-list.stories.tsx +0 -143
- package/src/elements/source.stories.tsx +0 -88
- package/src/elements/switch.stories.tsx +0 -56
- package/src/elements/tasks.stories.tsx +0 -237
- package/src/elements/text-shimmer.stories.tsx +0 -64
- package/src/elements/thinking-bar.stories.tsx +0 -72
- package/src/elements/tool.stories.tsx +0 -89
- package/src/elements/voice-input.stories.tsx +0 -88
- package/src/primitives/controllable.test.ts +0 -47
- package/src/stories/chat-panel-layout.stories.tsx +0 -145
- package/src/stories/chat-scene.tsx +0 -571
- package/src/stories/checkpoint-restore.stories.tsx +0 -256
- package/src/stories/context-usage.stories.tsx +0 -212
- package/src/stories/conversation-with-reasoning.stories.tsx +0 -182
- package/src/stories/conversation-with-sources.stories.tsx +0 -471
- package/src/stories/docs/Accessibility.mdx +0 -119
- package/src/stories/docs/ChildElements.mdx +0 -269
- package/src/stories/docs/ForAIAgents.mdx +0 -162
- package/src/stories/docs/GettingStarted.mdx +0 -65
- package/src/stories/docs/Installation.mdx +0 -83
- package/src/stories/docs/Introduction.mdx +0 -50
- package/src/stories/docs/SolidJsAdvanced.mdx +0 -17
- package/src/stories/docs/Theming.mdx +0 -85
- package/src/stories/docs/element-controls.ts +0 -121
- package/src/stories/docs/frameworks/Angular.mdx +0 -245
- package/src/stories/docs/frameworks/Html.mdx +0 -227
- package/src/stories/docs/frameworks/Overview.mdx +0 -47
- package/src/stories/docs/frameworks/React.mdx +0 -227
- package/src/stories/docs/frameworks/Solid.mdx +0 -173
- package/src/stories/docs/frameworks/Svelte.mdx +0 -194
- package/src/stories/docs/frameworks/Vue.mdx +0 -238
- package/src/stories/docs/generative-ui-overview.mdx +0 -186
- package/src/stories/docs/recipes/SpeechToText.mdx +0 -7
- package/src/stories/docs/recipes/Streaming.mdx +0 -68
- package/src/stories/docs/recipes/TextToSpeech.mdx +0 -35
- package/src/stories/docs/theme-editor/canvas.tsx +0 -32
- package/src/stories/docs/theme-editor/inspector.tsx +0 -66
- package/src/stories/docs/theme-editor/presets.test.ts +0 -32
- package/src/stories/docs/theme-editor/presets.ts +0 -64
- package/src/stories/docs/theme-editor/theme-css.test.ts +0 -19
- package/src/stories/docs/theme-editor/theme-css.ts +0 -15
- package/src/stories/docs/theme-editor/theme-editor.tsx +0 -146
- package/src/stories/docs/theme-tokens.tsx +0 -174
- package/src/stories/examples/ChoosingComponents.mdx +0 -106
- package/src/stories/examples/sample-data.ts +0 -79
- package/src/stories/examples/usage/checkpoint-restore.ts +0 -156
- package/src/stories/examples/usage/context-usage.ts +0 -591
- package/src/stories/examples/usage/conversation-with-reasoning.ts +0 -224
- package/src/stories/examples/usage/conversation-with-sources.ts +0 -589
- package/src/stories/examples/usage/empty-state.ts +0 -144
- package/src/stories/examples/usage/full-chat-app.ts +0 -277
- package/src/stories/examples/usage/index.ts +0 -63
- package/src/stories/examples/usage/message-actions.ts +0 -704
- package/src/stories/examples/usage/pattern-centered-conversation.ts +0 -446
- package/src/stories/examples/usage/pattern-chat-panel-layout.ts +0 -442
- package/src/stories/examples/usage/pattern-docked-widget.ts +0 -479
- package/src/stories/examples/usage/prompt-input-variants.ts +0 -1356
- package/src/stories/examples/usage/streaming-response.ts +0 -687
- package/src/stories/examples/usage/types.ts +0 -33
- package/src/stories/full-chat.stories.tsx +0 -18
- package/src/stories/message-actions.stories.tsx +0 -230
- package/src/stories/pattern-centered-conversation.stories.tsx +0 -93
- package/src/stories/pattern-docked-widget.stories.tsx +0 -93
- package/src/stories/pattern-empty-state.stories.tsx +0 -76
- package/src/stories/prompt-input-variants.stories.tsx +0 -456
- package/src/stories/streaming-response.stories.tsx +0 -438
- package/src/stories/theme-editor.stories.tsx +0 -16
- package/src/stories/token-reference.stories.tsx +0 -18
- package/src/stories/typography.stories.tsx +0 -78
- package/src/ui/avatar.stories.tsx +0 -103
- package/src/ui/badge.stories.tsx +0 -86
- package/src/ui/button.stories.tsx +0 -145
- package/src/ui/collapsible.stories.tsx +0 -69
- package/src/ui/dropdown.stories.tsx +0 -59
- package/src/ui/hover-card.stories.tsx +0 -77
- package/src/ui/hover-card.test.tsx +0 -30
- package/src/ui/overlay.stories.tsx +0 -115
- package/src/ui/popover.stories.tsx +0 -81
- package/src/ui/popover.test.tsx +0 -99
- package/src/ui/resizable.stories.tsx +0 -236
- package/src/ui/scroll-area.stories.tsx +0 -50
- package/src/ui/separator.stories.tsx +0 -81
- package/src/ui/skeleton.stories.tsx +0 -337
- package/src/ui/switch.stories.tsx +0 -51
- package/src/ui/switch.test.tsx +0 -59
- package/src/ui/textarea.stories.tsx +0 -76
- package/src/ui/tooltip.stories.tsx +0 -74
|
@@ -1,76 +0,0 @@
|
|
|
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-markdown': JSX.HTMLAttributes<HTMLElement>;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const sampleMarkdown = `### Markdown
|
|
17
|
-
Renders **bold**, _italic_, \`code\`, and lists:
|
|
18
|
-
- one
|
|
19
|
-
- two
|
|
20
|
-
|
|
21
|
-
> and blockquotes.
|
|
22
|
-
|
|
23
|
-
\`\`\`ts
|
|
24
|
-
export function add(a: number, b: number): number {
|
|
25
|
-
return a + b;
|
|
26
|
-
}
|
|
27
|
-
\`\`\``;
|
|
28
|
-
|
|
29
|
-
/** Render the actual `<kai-markdown>` custom element with a `content` property. */
|
|
30
|
-
function MarkdownElement(props: { content: string }) {
|
|
31
|
-
let el: (HTMLElement & { content?: string }) | undefined;
|
|
32
|
-
onMount(() => {
|
|
33
|
-
if (el) el.content = props.content;
|
|
34
|
-
});
|
|
35
|
-
return (
|
|
36
|
-
<kai-markdown ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px', 'max-width': '720px' }} />
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
41
|
-
<kai-markdown id="md" code-theme="github-dark-dimmed"></kai-markdown>
|
|
42
|
-
|
|
43
|
-
<script type="module">
|
|
44
|
-
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
45
|
-
|
|
46
|
-
const md = document.getElementById('md');
|
|
47
|
-
md.content = '### Hello\\nRenders **bold**, _italic_, and \\\`code\\\`.';
|
|
48
|
-
// md.setAttribute('code-highlight', 'false'); // skip Shiki entirely
|
|
49
|
-
</script>`;
|
|
50
|
-
|
|
51
|
-
const meta = {
|
|
52
|
-
title: 'Components/Markdown',
|
|
53
|
-
tags: ['autodocs'],
|
|
54
|
-
argTypes: argTypesFor('kai-markdown'),
|
|
55
|
-
parameters: {
|
|
56
|
-
layout: 'fullscreen',
|
|
57
|
-
docs: {
|
|
58
|
-
description: specDescription('kai-markdown', [
|
|
59
|
-
'`<kai-markdown>` is the framework-agnostic **web component** that renders a markdown string (with fenced-code syntax highlighting via Shiki) as a standalone element, isolated in **Shadow DOM**.',
|
|
60
|
-
'**When to use:** showing model output or any markdown in a non-Solid app without pulling in a markdown stack. In SolidJS, use the `Markdown` primitive directly.',
|
|
61
|
-
'**Placement:** as a block inside a message row, doc viewer, or standalone content pane; it is `display: block` and expands to fill its container width, so constrain width on the parent.',
|
|
62
|
-
"**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the source via the `content` **property** (`el.content = '...'`), and tune rendering with the `prose-size`, `code-theme`, and `code-highlight` attributes.",
|
|
63
|
-
'See the **Code** tab for HTML usage.',
|
|
64
|
-
]),
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
} satisfies Meta;
|
|
68
|
-
|
|
69
|
-
export default meta;
|
|
70
|
-
type Story = StoryObj;
|
|
71
|
-
|
|
72
|
-
/** Headings, emphasis, lists, a blockquote, and a highlighted code fence. */
|
|
73
|
-
export const Default: Story = {
|
|
74
|
-
render: () => <MarkdownElement content={sampleMarkdown} />,
|
|
75
|
-
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
76
|
-
};
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the declarative `<kai-skill>` light-DOM API of `<kai-skills>`.
|
|
3
|
-
*
|
|
4
|
-
* Strategy: the `defineWebComponent` call registers a real Shadow-DOM custom
|
|
5
|
-
* element which requires a full browser environment (Constructable Stylesheets,
|
|
6
|
-
* shadow roots, etc.) and is therefore not suitable for jsdom unit tests.
|
|
7
|
-
* Instead we:
|
|
8
|
-
* 1. Test the exported `parseKaiSkillElement` helper in isolation — it has no
|
|
9
|
-
* DOM dependencies beyond `Element`, which jsdom provides perfectly.
|
|
10
|
-
* 2. Test that the merged list of prop + slotted items renders badge spans
|
|
11
|
-
* correctly via `MessageSkills` directly, mirroring the pattern used in
|
|
12
|
-
* `prompt-suggestions.declarative.test.tsx`.
|
|
13
|
-
*/
|
|
14
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
15
|
-
import '@testing-library/jest-dom/vitest';
|
|
16
|
-
import { render, cleanup } from '@solidjs/testing-library';
|
|
17
|
-
import { For } from 'solid-js';
|
|
18
|
-
import { parseKaiSkillElement } from './message-skills';
|
|
19
|
-
import { MessageSkills } from '../components/message-skills';
|
|
20
|
-
|
|
21
|
-
afterEach(cleanup);
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// parseKaiSkillElement — pure helper
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
|
|
27
|
-
describe('parseKaiSkillElement', () => {
|
|
28
|
-
function makeNode(textContent: string, id?: string): Element {
|
|
29
|
-
const el = document.createElement('kai-skill');
|
|
30
|
-
el.textContent = textContent;
|
|
31
|
-
if (id !== undefined) el.setAttribute('id', id);
|
|
32
|
-
return el;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
it('uses textContent as name', () => {
|
|
36
|
-
const item = parseKaiSkillElement(makeNode('Web Search'));
|
|
37
|
-
expect(item).toMatchObject({ name: 'Web Search' });
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('uses id attribute as id when present', () => {
|
|
41
|
-
const item = parseKaiSkillElement(makeNode('Web Search', 'web-search'));
|
|
42
|
-
expect(item).toMatchObject({ id: 'web-search', name: 'Web Search' });
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('falls back to textContent as id when id attribute is absent', () => {
|
|
46
|
-
const item = parseKaiSkillElement(makeNode('Web Search'));
|
|
47
|
-
expect(item).toMatchObject({ id: 'Web Search', name: 'Web Search' });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('trims leading/trailing whitespace from textContent', () => {
|
|
51
|
-
const item = parseKaiSkillElement(makeNode(' Code Interpreter '));
|
|
52
|
-
expect(item).toMatchObject({ name: 'Code Interpreter' });
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('handles empty textContent with an id attribute', () => {
|
|
56
|
-
const item = parseKaiSkillElement(makeNode('', 'code'));
|
|
57
|
-
expect(item).toMatchObject({ id: 'code', name: '' });
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
// Merge + render: prop skills + slotted skills combine correctly
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
interface Skill {
|
|
66
|
-
id: string;
|
|
67
|
-
name: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
describe('MessageSkills rendering with merged skills', () => {
|
|
71
|
-
it('renders one badge span per skill', () => {
|
|
72
|
-
const { getByText } = render(() => (
|
|
73
|
-
<MessageSkills skills={[{ id: 'web-search', name: 'Web Search' }, { id: 'code', name: 'Code' }]} />
|
|
74
|
-
));
|
|
75
|
-
expect(getByText('Web Search')).toBeInTheDocument();
|
|
76
|
-
expect(getByText('Code')).toBeInTheDocument();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('renders nothing when skills is empty', () => {
|
|
80
|
-
const { container } = render(() => <MessageSkills skills={[]} />);
|
|
81
|
-
expect(container.firstChild).toBeNull();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('renders prop items before declarative (slotted) items', () => {
|
|
85
|
-
// Simulate the merge: prop first, slotted second.
|
|
86
|
-
const propSkills: Skill[] = [{ id: 'prop', name: 'Prop Skill' }];
|
|
87
|
-
const slottedSkills: Skill[] = [{ id: 'slotted', name: 'Slotted Skill' }];
|
|
88
|
-
const merged = [...propSkills, ...slottedSkills];
|
|
89
|
-
|
|
90
|
-
const { getAllByText } = render(() => <MessageSkills skills={merged} />);
|
|
91
|
-
// Both must be present; order is enforced by DOM order
|
|
92
|
-
expect(getAllByText(/Prop Skill|Slotted Skill/)).toHaveLength(2);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('parseKaiSkillElement produces items that render correctly', () => {
|
|
96
|
-
const el = document.createElement('kai-skill');
|
|
97
|
-
el.textContent = 'Memory';
|
|
98
|
-
el.setAttribute('id', 'memory');
|
|
99
|
-
|
|
100
|
-
const parsed = parseKaiSkillElement(el);
|
|
101
|
-
const { getByText } = render(() => <MessageSkills skills={[parsed]} />);
|
|
102
|
-
expect(getByText('Memory')).toBeInTheDocument();
|
|
103
|
-
});
|
|
104
|
-
});
|
|
@@ -1,114 +0,0 @@
|
|
|
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-skills': JSX.HTMLAttributes<HTMLElement>;
|
|
12
|
-
/** Light-DOM data carrier for declarative skill badges inside `<kai-skills>`. */
|
|
13
|
-
'kai-skill': JSX.HTMLAttributes<HTMLElement> & {
|
|
14
|
-
id?: string;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface Skill {
|
|
21
|
-
id: string;
|
|
22
|
-
name: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const sampleSkills: Skill[] = [
|
|
26
|
-
{ id: 's1', name: 'web-search' },
|
|
27
|
-
{ id: 's2', name: 'code' },
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
/** Render the actual `<kai-skills>` custom element with a `skills` property. */
|
|
31
|
-
function MessageSkillsElement(props: { skills: Skill[] }) {
|
|
32
|
-
let el: (HTMLElement & { skills?: Skill[] }) | undefined;
|
|
33
|
-
onMount(() => {
|
|
34
|
-
if (el) el.skills = props.skills;
|
|
35
|
-
});
|
|
36
|
-
return (
|
|
37
|
-
<kai-skills ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px' }} />
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
42
|
-
<kai-skills id="skills"></kai-skills>
|
|
43
|
-
|
|
44
|
-
<script type="module">
|
|
45
|
-
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
46
|
-
|
|
47
|
-
const skills = document.getElementById('skills');
|
|
48
|
-
skills.skills = [
|
|
49
|
-
{ id: 's1', name: 'web-search' },
|
|
50
|
-
{ id: 's2', name: 'code' },
|
|
51
|
-
];
|
|
52
|
-
</script>`;
|
|
53
|
-
|
|
54
|
-
const meta = {
|
|
55
|
-
title: 'Components/Skills',
|
|
56
|
-
tags: ['autodocs'],
|
|
57
|
-
argTypes: argTypesFor('kai-skills'),
|
|
58
|
-
parameters: {
|
|
59
|
-
layout: 'fullscreen',
|
|
60
|
-
docs: {
|
|
61
|
-
description: specDescription('kai-skills', [
|
|
62
|
-
'`<kai-skills>` is the framework-agnostic **web component** that badges which skills were active for a message, isolated in **Shadow DOM**.',
|
|
63
|
-
'**When to use:** annotating a message row with the skills/tools it used, in a non-Solid app. In SolidJS, use the `MessageSkills` primitive directly.',
|
|
64
|
-
'**Placement:** beneath or inline with the message content, typically in the footer of a message row alongside action buttons; it is `display: block` and wraps badges horizontally.',
|
|
65
|
-
"**How to use:** register once with `import '@kitn.ai/ui/elements'`. Then either (a) set the `skills` **property** to an array of `{ id, name }` objects, or (b) compose `<kai-skill>` child elements — light-DOM data carriers hidden by the Shadow DOM — where the tag's text content is the badge label and the optional `id` attribute is the stable identifier. When both are provided, prop items render first and declarative children are appended after.",
|
|
66
|
-
'See the **Code** tab for HTML usage.',
|
|
67
|
-
]),
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
} satisfies Meta;
|
|
71
|
-
|
|
72
|
-
export default meta;
|
|
73
|
-
type Story = StoryObj;
|
|
74
|
-
|
|
75
|
-
/** Two active-skill badges set via the `skills` JS property. */
|
|
76
|
-
export const Default: Story = {
|
|
77
|
-
render: () => <MessageSkillsElement skills={sampleSkills} />,
|
|
78
|
-
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const DECLARATIVE_HTML_SNIPPET = `<!-- Works in any framework or plain HTML — no JS property assignment needed -->
|
|
82
|
-
<kai-skills>
|
|
83
|
-
<kai-skill id="web-search">Web Search</kai-skill>
|
|
84
|
-
<kai-skill id="code">Code</kai-skill>
|
|
85
|
-
<kai-skill id="memory">Memory</kai-skill>
|
|
86
|
-
</kai-skills>
|
|
87
|
-
|
|
88
|
-
<script type="module">
|
|
89
|
-
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
90
|
-
</script>`;
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Declare each skill badge as a `<kai-skill>` child element — no `skills`
|
|
94
|
-
* property or JS array wiring needed. The tag's text content is the badge
|
|
95
|
-
* label; the optional `id` attribute is the stable identifier (falls back to
|
|
96
|
-
* the label when absent). Children are light-DOM data carriers hidden by the
|
|
97
|
-
* Shadow DOM — pure data, no visible output of their own.
|
|
98
|
-
* Mix with the `skills` prop: prop items render first, declarative children after.
|
|
99
|
-
*/
|
|
100
|
-
export const DeclarativeSkills: Story = {
|
|
101
|
-
name: 'Declarative Skills (kai-skill)',
|
|
102
|
-
render: () => (
|
|
103
|
-
<kai-skills style={{ display: 'block', padding: '16px' }}>
|
|
104
|
-
<kai-skill id="web-search">Web Search</kai-skill>
|
|
105
|
-
<kai-skill id="code">Code</kai-skill>
|
|
106
|
-
<kai-skill id="memory">Memory</kai-skill>
|
|
107
|
-
</kai-skills>
|
|
108
|
-
),
|
|
109
|
-
parameters: {
|
|
110
|
-
docs: {
|
|
111
|
-
source: { code: DECLARATIVE_HTML_SNIPPET, language: 'html' },
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
};
|
|
@@ -1,218 +0,0 @@
|
|
|
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 type { ChatMessage } from './chat-types';
|
|
5
|
-
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
6
|
-
|
|
7
|
-
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
8
|
-
declare module 'solid-js' {
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
10
|
-
namespace JSX {
|
|
11
|
-
interface IntrinsicElements {
|
|
12
|
-
'kai-message': JSX.HTMLAttributes<HTMLElement>;
|
|
13
|
-
'kai-action': JSX.HTMLAttributes<HTMLElement> & { icon?: string; tooltip?: string };
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const assistantMessage: ChatMessage = {
|
|
19
|
-
id: 'm-a',
|
|
20
|
-
role: 'assistant',
|
|
21
|
-
content:
|
|
22
|
-
"Here's the plan, with a quick code sample:\n\n```js\nconst kit = useChat();\n```\n\nThat wires the kit into your component.",
|
|
23
|
-
reasoning: { text: 'The user wants X, so I should do Y then Z.', label: 'Reasoning' },
|
|
24
|
-
tools: [{ type: 'search', state: 'output-available', input: { query: 'kitn docs' }, output: { hits: 3 } }],
|
|
25
|
-
attachments: [
|
|
26
|
-
{
|
|
27
|
-
id: '1',
|
|
28
|
-
type: 'file',
|
|
29
|
-
filename: 'architecture.png',
|
|
30
|
-
mediaType: 'image/png',
|
|
31
|
-
url:
|
|
32
|
-
'data:image/svg+xml;utf8,' +
|
|
33
|
-
encodeURIComponent(
|
|
34
|
-
'<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96"><rect width="96" height="96" rx="12" fill="#7c3aed"/><text x="48" y="60" font-size="42" text-anchor="middle" fill="white">◆</text></svg>',
|
|
35
|
-
),
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
actions: ['copy', 'like', 'dislike', 'regenerate'],
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const userMessage: ChatMessage = {
|
|
42
|
-
id: 'm-u',
|
|
43
|
-
role: 'user',
|
|
44
|
-
content: 'How do I compose these myself?',
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const AVATAR_SVG =
|
|
48
|
-
'data:image/svg+xml;utf8,' +
|
|
49
|
-
encodeURIComponent(
|
|
50
|
-
'<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><rect width="64" height="64" fill="#10b981"/><text x="32" y="42" font-size="28" text-anchor="middle" fill="white">K</text></svg>',
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
/** An assistant message with an avatar (from `message.avatar`) plus custom + built-in actions. */
|
|
54
|
-
const avatarMessage: ChatMessage = {
|
|
55
|
-
id: 'm-av',
|
|
56
|
-
role: 'assistant',
|
|
57
|
-
content: 'I have an avatar, a custom **Share** action, and the built-in copy/like actions.',
|
|
58
|
-
avatar: { src: AVATAR_SVG, alt: 'Kitn', fallback: 'K' },
|
|
59
|
-
actions: ['copy', 'like', { id: 'share', label: 'Share', icon: 'share' }],
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
/** Render the actual `<kai-message>` custom element with a `message` property and optional attrs. */
|
|
63
|
-
function MessageElement(props: { message: ChatMessage; actionsReveal?: 'always' | 'hover' }) {
|
|
64
|
-
let el: (HTMLElement & { message?: ChatMessage }) | undefined;
|
|
65
|
-
onMount(() => {
|
|
66
|
-
if (!el) return;
|
|
67
|
-
if (props.actionsReveal) el.setAttribute('actions-reveal', props.actionsReveal);
|
|
68
|
-
el.message = props.message;
|
|
69
|
-
el.addEventListener('kai-message-action', (e) =>
|
|
70
|
-
console.log('kai-message-action', (e as CustomEvent).detail.action),
|
|
71
|
-
);
|
|
72
|
-
});
|
|
73
|
-
return (
|
|
74
|
-
<kai-message ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px', 'max-width': '720px' }} />
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
79
|
-
<kai-message id="msg" style="display:block;"></kai-message>
|
|
80
|
-
|
|
81
|
-
<script type="module">
|
|
82
|
-
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
83
|
-
|
|
84
|
-
const msg = document.getElementById('msg');
|
|
85
|
-
msg.message = {
|
|
86
|
-
id: 'm-a', role: 'assistant',
|
|
87
|
-
content: "Here's the plan:\\n\\n\\\`\\\`\\\`js\\nconst kit = useChat();\\n\\\`\\\`\\\`",
|
|
88
|
-
reasoning: { text: 'The user wants X, so I should do Y.' },
|
|
89
|
-
tools: [{ type: 'search', state: 'output-available', output: { hits: 3 } }],
|
|
90
|
-
actions: ['copy', 'like', 'dislike', 'regenerate'],
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
// events are CustomEvents on the element (they do not bubble)
|
|
94
|
-
msg.addEventListener('kai-message-action', (e) => console.log(e.detail.action, e.detail.messageId));
|
|
95
|
-
</script>`;
|
|
96
|
-
|
|
97
|
-
const meta = {
|
|
98
|
-
title: 'Components/Message',
|
|
99
|
-
tags: ['autodocs'],
|
|
100
|
-
argTypes: argTypesFor('kai-message'),
|
|
101
|
-
parameters: {
|
|
102
|
-
layout: 'fullscreen',
|
|
103
|
-
docs: {
|
|
104
|
-
description: specDescription('kai-message', [
|
|
105
|
-
'`<kai-message>` is the framework-agnostic **web component** for a single message row — markdown/plain content, an optional reasoning block, tool calls, attachments, and action buttons — all rendered from one `message` object (the same shape `<kai-chat>` uses per message). It is the keystone of the "compose your own message list" pattern, isolated in **Shadow DOM**.',
|
|
106
|
-
"**When to use:** building a custom message thread in a non-Solid app, or anywhere you want to lay out the list yourself but keep the kit's rich message rendering. In SolidJS, compose the `Message` primitives for finer control.",
|
|
107
|
-
"**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the whole row via the `message` **property** (`el.message = {...}`), and listen for the `kai-message-action` **CustomEvent** for action-button clicks. For simple cases, set `role` + `content` attributes instead of a full object.",
|
|
108
|
-
'See the **Code** tab for HTML usage.',
|
|
109
|
-
]),
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
} satisfies Meta;
|
|
113
|
-
|
|
114
|
-
export default meta;
|
|
115
|
-
type Story = StoryObj;
|
|
116
|
-
|
|
117
|
-
/** A rich assistant message: markdown, reasoning, a tool call, an attachment, and actions. */
|
|
118
|
-
export const Assistant: Story = {
|
|
119
|
-
render: () => <MessageElement message={assistantMessage} />,
|
|
120
|
-
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
/** A plain user message (markdown defaults off for the user role). */
|
|
124
|
-
export const User: Story = {
|
|
125
|
-
render: () => <MessageElement message={userMessage} />,
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
/** An avatar (from `message.avatar`) laid out beside the content column, with a
|
|
129
|
-
* custom **Share** action (curated `icon`) alongside the built-in copy/like. */
|
|
130
|
-
export const WithAvatarAndCustomAction: Story = {
|
|
131
|
-
name: 'Avatar + Custom Action',
|
|
132
|
-
render: () => <MessageElement message={avatarMessage} />,
|
|
133
|
-
parameters: {
|
|
134
|
-
docs: {
|
|
135
|
-
source: {
|
|
136
|
-
code: `<kai-message id="msg" style="display:block;"></kai-message>
|
|
137
|
-
<script type="module">
|
|
138
|
-
import '@kitn.ai/ui/elements';
|
|
139
|
-
const msg = document.getElementById('msg');
|
|
140
|
-
msg.message = {
|
|
141
|
-
id: 'm-av', role: 'assistant', content: 'I have an avatar and a custom Share action.',
|
|
142
|
-
avatar: { src: '/k.png', alt: 'Kitn', fallback: 'K' },
|
|
143
|
-
// built-in names AND custom { id, label, icon } descriptors:
|
|
144
|
-
actions: ['copy', 'like', { id: 'share', label: 'Share', icon: 'share' }],
|
|
145
|
-
};
|
|
146
|
-
msg.addEventListener('kai-message-action', (e) => console.log(e.detail.action)); // 'copy' | 'like' | 'share'
|
|
147
|
-
</script>`,
|
|
148
|
-
language: 'html',
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
/** Declarative actions — `<kai-action>` light-DOM children instead of a
|
|
155
|
-
* `message.actions` array. Each carries `id`, a curated `icon`, an optional
|
|
156
|
-
* `tooltip`, and optional text (the accessible label). Great for plain HTML. */
|
|
157
|
-
export const DeclarativeActions: Story = {
|
|
158
|
-
name: 'Declarative Actions (kai-action)',
|
|
159
|
-
render: () => {
|
|
160
|
-
let el: HTMLElement | undefined;
|
|
161
|
-
onMount(() => {
|
|
162
|
-
if (!el) return;
|
|
163
|
-
el.setAttribute('avatar-fallback', 'AI');
|
|
164
|
-
el.setAttribute(
|
|
165
|
-
'content',
|
|
166
|
-
'Declare each button as a `<kai-action>` child — no `message` object or JS wiring needed.',
|
|
167
|
-
);
|
|
168
|
-
el.addEventListener('kai-message-action', (e) =>
|
|
169
|
-
console.log('kai-message-action', (e as CustomEvent).detail.action),
|
|
170
|
-
);
|
|
171
|
-
});
|
|
172
|
-
return (
|
|
173
|
-
<kai-message
|
|
174
|
-
ref={(e) => (el = e as HTMLElement)}
|
|
175
|
-
style={{ display: 'block', padding: '16px', 'max-width': '720px' }}
|
|
176
|
-
>
|
|
177
|
-
<kai-action id="copy" icon="copy" tooltip="Copy"></kai-action>
|
|
178
|
-
<kai-action id="regenerate" icon="regenerate" tooltip="Regenerate"></kai-action>
|
|
179
|
-
<kai-action id="share" icon="share" tooltip="Share">Share</kai-action>
|
|
180
|
-
<kai-action id="bookmark" icon="bookmark" tooltip="Bookmark">Bookmark</kai-action>
|
|
181
|
-
</kai-message>
|
|
182
|
-
);
|
|
183
|
-
},
|
|
184
|
-
parameters: {
|
|
185
|
-
docs: {
|
|
186
|
-
source: {
|
|
187
|
-
code: `<kai-message role="assistant" avatar-fallback="AI" content="..." style="display:block;">
|
|
188
|
-
<kai-action id="copy" icon="copy" tooltip="Copy"></kai-action>
|
|
189
|
-
<kai-action id="regenerate" icon="regenerate" tooltip="Regenerate"></kai-action>
|
|
190
|
-
<kai-action id="share" icon="share" tooltip="Share">Share</kai-action>
|
|
191
|
-
<kai-action id="bookmark" icon="bookmark" tooltip="Bookmark">Bookmark</kai-action>
|
|
192
|
-
</kai-message>
|
|
193
|
-
|
|
194
|
-
<script type="module">
|
|
195
|
-
import '@kitn.ai/ui/elements';
|
|
196
|
-
document.querySelector('kai-message')
|
|
197
|
-
.addEventListener('kai-message-action', (e) => console.log(e.detail.action)); // 'copy' | 'share' | …
|
|
198
|
-
</script>`,
|
|
199
|
-
language: 'html',
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
/** `actions-reveal="hover"` — the action bar is hidden until you hover the row. */
|
|
206
|
-
export const ActionsRevealOnHover: Story = {
|
|
207
|
-
name: 'Actions Reveal on Hover',
|
|
208
|
-
render: () => <MessageElement message={avatarMessage} actionsReveal="hover" />,
|
|
209
|
-
parameters: {
|
|
210
|
-
docs: {
|
|
211
|
-
source: {
|
|
212
|
-
code: `<!-- actions-reveal: 'always' (default) | 'hover' -->
|
|
213
|
-
<kai-message actions-reveal="hover" id="msg" style="display:block;"></kai-message>`,
|
|
214
|
-
language: 'html',
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
};
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the declarative `<kai-model>` light-DOM API of
|
|
3
|
-
* `<kai-model-switcher>`.
|
|
4
|
-
*
|
|
5
|
-
* Strategy: the `defineWebComponent` call registers a real Shadow-DOM custom
|
|
6
|
-
* element which requires a full browser environment (Constructable Stylesheets,
|
|
7
|
-
* shadow roots, etc.) and is therefore not suitable for jsdom unit tests.
|
|
8
|
-
* Instead we:
|
|
9
|
-
* 1. Test the exported `parseKaiModelElement` helper in isolation — it has no
|
|
10
|
-
* DOM dependencies beyond `Element`, which jsdom provides perfectly.
|
|
11
|
-
* 2. Test that the merged list of prop + slotted models combines correctly,
|
|
12
|
-
* mirroring the pattern used in `prompt-suggestions.declarative.test.tsx`.
|
|
13
|
-
*/
|
|
14
|
-
import { describe, it, expect } from 'vitest';
|
|
15
|
-
import { parseKaiModelElement } from './model-switcher';
|
|
16
|
-
import type { ModelOption } from '../types';
|
|
17
|
-
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// parseKaiModelElement — pure helper
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
describe('parseKaiModelElement', () => {
|
|
23
|
-
function makeNode(textContent: string, id?: string, provider?: string): Element {
|
|
24
|
-
const el = document.createElement('kai-model');
|
|
25
|
-
el.textContent = textContent;
|
|
26
|
-
if (id !== undefined) el.setAttribute('id', id);
|
|
27
|
-
if (provider !== undefined) el.setAttribute('provider', provider);
|
|
28
|
-
return el;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
it('maps textContent to name', () => {
|
|
32
|
-
const item = parseKaiModelElement(makeNode('GPT-4o', 'gpt-4o'));
|
|
33
|
-
expect(item.name).toBe('GPT-4o');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('maps the id attribute to id', () => {
|
|
37
|
-
const item = parseKaiModelElement(makeNode('GPT-4o', 'gpt-4o'));
|
|
38
|
-
expect(item.id).toBe('gpt-4o');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('maps the provider attribute to provider', () => {
|
|
42
|
-
const item = parseKaiModelElement(makeNode('GPT-4o', 'gpt-4o', 'OpenAI'));
|
|
43
|
-
expect(item.provider).toBe('OpenAI');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('sets provider to undefined when the attribute is absent', () => {
|
|
47
|
-
const item = parseKaiModelElement(makeNode('Claude Sonnet', 'sonnet'));
|
|
48
|
-
expect(item.provider).toBeUndefined();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('trims leading/trailing whitespace from textContent (name)', () => {
|
|
52
|
-
const item = parseKaiModelElement(makeNode(' Claude Sonnet ', 'sonnet'));
|
|
53
|
-
expect(item.name).toBe('Claude Sonnet');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('returns empty string for id when id attribute is absent', () => {
|
|
57
|
-
const item = parseKaiModelElement(makeNode('GPT-4o'));
|
|
58
|
-
expect(item.id).toBe('');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('produces the full ModelOption shape', () => {
|
|
62
|
-
const item = parseKaiModelElement(makeNode('GPT-4o', 'gpt-4o', 'OpenAI'));
|
|
63
|
-
expect(item).toEqual<ModelOption>({ id: 'gpt-4o', name: 'GPT-4o', provider: 'OpenAI', description: undefined, group: undefined });
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('maps the description and group attributes', () => {
|
|
67
|
-
const el = document.createElement('kai-model');
|
|
68
|
-
el.textContent = 'GPT-4o';
|
|
69
|
-
el.setAttribute('id', 'gpt-4o');
|
|
70
|
-
el.setAttribute('description', 'Flagship model');
|
|
71
|
-
el.setAttribute('group', 'Legacy models');
|
|
72
|
-
const item = parseKaiModelElement(el);
|
|
73
|
-
expect(item.description).toBe('Flagship model');
|
|
74
|
-
expect(item.group).toBe('Legacy models');
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
// Merge logic: prop models + slotted models combine correctly
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
|
|
82
|
-
describe('prop + declarative merge', () => {
|
|
83
|
-
function makeNode(textContent: string, id: string, provider?: string): Element {
|
|
84
|
-
const el = document.createElement('kai-model');
|
|
85
|
-
el.textContent = textContent;
|
|
86
|
-
el.setAttribute('id', id);
|
|
87
|
-
if (provider !== undefined) el.setAttribute('provider', provider);
|
|
88
|
-
return el;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
it('renders prop items before declarative (slotted) items', () => {
|
|
92
|
-
const propModels: ModelOption[] = [{ id: 'opus', name: 'Claude Opus', provider: 'Anthropic' }];
|
|
93
|
-
const slottedModels = [makeNode('GPT-4o', 'gpt-4o', 'OpenAI')].map(parseKaiModelElement);
|
|
94
|
-
const merged = [...propModels, ...slottedModels];
|
|
95
|
-
expect(merged).toHaveLength(2);
|
|
96
|
-
expect(merged[0].id).toBe('opus');
|
|
97
|
-
expect(merged[1].id).toBe('gpt-4o');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('works with only declarative children (empty prop array)', () => {
|
|
101
|
-
const slottedModels = [
|
|
102
|
-
makeNode('GPT-4o', 'gpt-4o', 'OpenAI'),
|
|
103
|
-
makeNode('GPT-4o mini', 'gpt-4o-mini', 'OpenAI'),
|
|
104
|
-
].map(parseKaiModelElement);
|
|
105
|
-
const merged = [...[], ...slottedModels];
|
|
106
|
-
expect(merged).toHaveLength(2);
|
|
107
|
-
expect(merged[0]).toMatchObject({ id: 'gpt-4o', name: 'GPT-4o', provider: 'OpenAI' });
|
|
108
|
-
expect(merged[1]).toMatchObject({ id: 'gpt-4o-mini', name: 'GPT-4o mini', provider: 'OpenAI' });
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('works with only prop models (no declarative children)', () => {
|
|
112
|
-
const propModels: ModelOption[] = [
|
|
113
|
-
{ id: 'sonnet', name: 'Claude Sonnet', provider: 'Anthropic' },
|
|
114
|
-
{ id: 'haiku', name: 'Claude Haiku', provider: 'Anthropic' },
|
|
115
|
-
];
|
|
116
|
-
const merged = [...propModels, ...([] as ModelOption[])];
|
|
117
|
-
expect(merged).toHaveLength(2);
|
|
118
|
-
expect(merged[0].id).toBe('sonnet');
|
|
119
|
-
expect(merged[1].id).toBe('haiku');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('parseKaiModelElement produces items with the correct event payload key (id → modelId)', () => {
|
|
123
|
-
// Confirm the parsed `id` field is what gets dispatched as `modelId` in kai-model-change.
|
|
124
|
-
// The element fires: dispatch('kai-model-change', { modelId }) where modelId = item.id.
|
|
125
|
-
const node = makeNode('GPT-4o', 'gpt-4o', 'OpenAI');
|
|
126
|
-
const parsed = parseKaiModelElement(node);
|
|
127
|
-
// The model-switcher component passes item.id as the modelId in the event.
|
|
128
|
-
expect(parsed.id).toBe('gpt-4o'); // this becomes e.detail.modelId
|
|
129
|
-
});
|
|
130
|
-
});
|