@kitnai/chat 0.3.1 → 0.4.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.
Files changed (106) hide show
  1. package/README.md +11 -0
  2. package/dist/custom-elements.json +2494 -0
  3. package/dist/kitn-chat.es.js +52 -39
  4. package/dist/llms/llms-full.txt +667 -0
  5. package/dist/llms/llms.txt +104 -0
  6. package/dist/theme.tokens.css +133 -0
  7. package/frameworks/react/index.tsx +530 -0
  8. package/frameworks/react/runtime.tsx +94 -0
  9. package/llms-full.txt +667 -0
  10. package/llms.txt +104 -0
  11. package/package.json +34 -5
  12. package/src/components/attachments.tsx +4 -2
  13. package/src/components/chain-of-thought.tsx +1 -1
  14. package/src/components/chat-scope-picker.tsx +2 -2
  15. package/src/components/checkpoint.tsx +7 -3
  16. package/src/components/context.tsx +14 -18
  17. package/src/components/conversation-item.tsx +1 -1
  18. package/src/components/conversation-list.tsx +5 -4
  19. package/src/components/message-skills.tsx +1 -1
  20. package/src/components/message.tsx +1 -0
  21. package/src/components/model-switcher.tsx +3 -3
  22. package/src/components/prompt-input.tsx +15 -2
  23. package/src/components/reasoning.tsx +2 -2
  24. package/src/components/scroll-button.tsx +1 -0
  25. package/src/components/slash-command.tsx +17 -8
  26. package/src/components/source.tsx +2 -2
  27. package/src/components/thinking-bar.tsx +2 -2
  28. package/src/components/tool.tsx +17 -6
  29. package/src/components/voice-input.tsx +5 -1
  30. package/src/elements/attachments.tsx +132 -0
  31. package/src/elements/chain-of-thought.tsx +45 -0
  32. package/src/elements/chat-scope-picker.tsx +36 -0
  33. package/src/elements/chat.tsx +51 -7
  34. package/src/elements/checkpoint.tsx +43 -0
  35. package/src/elements/code-block.tsx +42 -0
  36. package/src/elements/compiled.css +1 -1
  37. package/src/elements/context-meter.tsx +71 -0
  38. package/src/elements/conversation-list.tsx +6 -0
  39. package/src/elements/default-input.tsx +22 -1
  40. package/src/elements/define.tsx +97 -11
  41. package/src/elements/element-types.d.ts +404 -0
  42. package/src/elements/empty.tsx +29 -0
  43. package/src/elements/feedback-bar.tsx +33 -0
  44. package/src/elements/file-upload.tsx +44 -0
  45. package/src/elements/image.tsx +32 -0
  46. package/src/elements/kitn-attachments.stories.tsx +181 -0
  47. package/src/elements/kitn-chain-of-thought.stories.tsx +75 -0
  48. package/src/elements/kitn-chat-scope-picker.stories.tsx +72 -0
  49. package/src/elements/kitn-checkpoint.stories.tsx +71 -0
  50. package/src/elements/kitn-code-block.stories.tsx +82 -0
  51. package/src/elements/kitn-context-meter.stories.tsx +85 -0
  52. package/src/elements/kitn-empty.stories.tsx +110 -0
  53. package/src/elements/kitn-feedback-bar.stories.tsx +73 -0
  54. package/src/elements/kitn-file-upload.stories.tsx +81 -0
  55. package/src/elements/kitn-image.stories.tsx +70 -0
  56. package/src/elements/kitn-loader.stories.tsx +87 -0
  57. package/src/elements/kitn-markdown.stories.tsx +75 -0
  58. package/src/elements/kitn-message-skills.stories.tsx +74 -0
  59. package/src/elements/kitn-message.stories.tsx +105 -0
  60. package/src/elements/kitn-model-switcher.stories.tsx +80 -0
  61. package/src/elements/kitn-prompt-input.stories.tsx +74 -16
  62. package/src/elements/kitn-prompt-suggestions.stories.tsx +157 -0
  63. package/src/elements/kitn-reasoning.stories.tsx +76 -0
  64. package/src/elements/kitn-response-stream.stories.tsx +79 -0
  65. package/src/elements/kitn-source-list.stories.tsx +77 -0
  66. package/src/elements/kitn-source.stories.tsx +87 -0
  67. package/src/elements/kitn-text-shimmer.stories.tsx +63 -0
  68. package/src/elements/kitn-thinking-bar.stories.tsx +72 -0
  69. package/src/elements/kitn-tool.stories.tsx +88 -0
  70. package/src/elements/kitn-voice-input.stories.tsx +87 -0
  71. package/src/elements/loader.tsx +25 -0
  72. package/src/elements/markdown.tsx +38 -0
  73. package/src/elements/message-skills.tsx +22 -0
  74. package/src/elements/message.tsx +125 -0
  75. package/src/elements/model-switcher.tsx +35 -0
  76. package/src/elements/prompt-input.tsx +83 -7
  77. package/src/elements/prompt-suggestions.tsx +58 -0
  78. package/src/elements/reasoning.tsx +50 -0
  79. package/src/elements/register.ts +31 -0
  80. package/src/elements/response-stream.tsx +40 -0
  81. package/src/elements/source.tsx +67 -0
  82. package/src/elements/text-shimmer.tsx +28 -0
  83. package/src/elements/thinking-bar.tsx +34 -0
  84. package/src/elements/tool.tsx +23 -0
  85. package/src/elements/voice-input.tsx +41 -0
  86. package/src/index.ts +0 -1
  87. package/src/primitives/chat-config.tsx +2 -2
  88. package/src/stories/docs/Accessibility.mdx +119 -0
  89. package/src/stories/docs/ForAIAgents.mdx +93 -0
  90. package/src/stories/docs/GettingStarted.mdx +2 -2
  91. package/src/stories/docs/Installation.mdx +2 -2
  92. package/src/stories/docs/Integrations.mdx +415 -15
  93. package/src/stories/docs/Introduction.mdx +5 -5
  94. package/src/stories/docs/Theming.mdx +1 -1
  95. package/src/stories/typography.stories.tsx +78 -0
  96. package/src/ui/button.tsx +1 -1
  97. package/src/ui/collapsible.tsx +119 -8
  98. package/src/ui/dropdown.tsx +177 -12
  99. package/src/ui/hover-card.tsx +147 -26
  100. package/src/ui/overlay.tsx +151 -0
  101. package/src/ui/textarea.tsx +1 -1
  102. package/src/ui/tooltip.stories.tsx +1 -1
  103. package/src/ui/tooltip.tsx +59 -13
  104. package/src/utils/cn.ts +19 -1
  105. package/theme.css +72 -43
  106. package/src/ui/dialog.tsx +0 -21
@@ -0,0 +1,110 @@
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
+
5
+ // The web components are custom DOM elements, so declare the tags for JSX.
6
+ declare module 'solid-js' {
7
+ // eslint-disable-next-line @typescript-eslint/no-namespace
8
+ namespace JSX {
9
+ interface IntrinsicElements {
10
+ 'kitn-empty': JSX.HTMLAttributes<HTMLElement>;
11
+ }
12
+ }
13
+ }
14
+
15
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
16
+ <kitn-empty
17
+ empty-title="No conversations yet"
18
+ description="Start a new chat to see it appear here."
19
+ >
20
+ <!-- Route 2 slots: your own icon and actions -->
21
+ <svg slot="media" width="28" height="28" viewBox="0 0 24 24" fill="none"
22
+ stroke="currentColor" stroke-width="2">
23
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
24
+ </svg>
25
+
26
+ <!-- Slotted actions are light DOM, so style them with your own page CSS
27
+ (a ghost button with a + icon, here styled inline for portability). -->
28
+ <button
29
+ style="display:inline-flex; align-items:center; gap:.45rem;
30
+ font:500 13.5px/1 system-ui,sans-serif; padding:.5rem .85rem;
31
+ border-radius:9px; border:1px solid var(--color-border, #e5e5e5);
32
+ background:transparent; color:inherit; cursor:pointer;"
33
+ >
34
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
35
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
36
+ <path d="M5 12h14" /><path d="M12 5v14" />
37
+ </svg>
38
+ New chat
39
+ </button>
40
+ </kitn-empty>
41
+
42
+ <script type="module">
43
+ import '@kitnai/chat/elements'; // registers the custom elements
44
+ </script>`;
45
+
46
+ const meta = {
47
+ title: 'Web Components/kitn-empty',
48
+ tags: ['autodocs'],
49
+ parameters: {
50
+ layout: 'fullscreen',
51
+ docs: {
52
+ description: {
53
+ component: [
54
+ '`<kitn-empty>` is the framework-agnostic **web component** for an empty-state block — an icon, a title, a description, and actions — isolated in **Shadow DOM**.',
55
+ '**When to use:** placeholder UI for an empty list/thread in a non-Solid app. In SolidJS, compose the `Empty*` primitives.',
56
+ "**How to use:** register once with `import '@kitnai/chat/elements'`, set `empty-title` (note `empty-title`, not `title`) and `description` via attributes, and use the **slots** (\"Route 2\") to project your own icon (`slot=\"media\"`) and actions (the default slot).",
57
+ 'See the **Code** tab for HTML usage.',
58
+ ].join('\n\n'),
59
+ },
60
+ },
61
+ },
62
+ } satisfies Meta;
63
+
64
+ export default meta;
65
+ type Story = StoryObj;
66
+
67
+ /** Render the actual `<kitn-empty>` custom element with slotted children. */
68
+ function EmptyElement() {
69
+ let el: HTMLElement | undefined;
70
+ onMount(() => {
71
+ if (!el) return;
72
+ el.setAttribute('empty-title', 'No conversations yet');
73
+ el.setAttribute('description', 'Start a new chat to see it appear here.');
74
+ });
75
+ return (
76
+ <kitn-empty ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '24px' }}>
77
+ <svg slot="media" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
78
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
79
+ </svg>
80
+ {/* Slotted actions are light DOM — style them with the host page's own
81
+ CSS. Here a ghost "+ New chat" button, styled inline so it renders
82
+ correctly in any context (mirrors the composable example's pill-btn). */}
83
+ <button
84
+ style={{
85
+ display: 'inline-flex',
86
+ 'align-items': 'center',
87
+ gap: '0.45rem',
88
+ font: '500 13.5px/1 system-ui, sans-serif',
89
+ padding: '0.5rem 0.85rem',
90
+ 'border-radius': '9px',
91
+ border: '1px solid var(--color-border, #e5e5e5)',
92
+ background: 'transparent',
93
+ color: 'inherit',
94
+ cursor: 'pointer',
95
+ }}
96
+ >
97
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
98
+ <path d="M5 12h14" /><path d="M12 5v14" />
99
+ </svg>
100
+ New chat
101
+ </button>
102
+ </kitn-empty>
103
+ );
104
+ }
105
+
106
+ /** An empty state with a slotted icon and an action button. */
107
+ export const Default: Story = {
108
+ render: () => <EmptyElement />,
109
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
110
+ };
@@ -0,0 +1,73 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import './register'; // side effect: registers the custom elements
3
+
4
+ // The web components are custom DOM elements, so declare the tags for JSX.
5
+ declare module 'solid-js' {
6
+ // eslint-disable-next-line @typescript-eslint/no-namespace
7
+ namespace JSX {
8
+ interface IntrinsicElements {
9
+ 'kitn-feedback-bar': JSX.HTMLAttributes<HTMLElement> & {
10
+ 'bar-title'?: string;
11
+ 'on:helpful'?: (e: CustomEvent) => void;
12
+ 'on:nothelpful'?: (e: CustomEvent) => void;
13
+ 'on:close'?: (e: CustomEvent) => void;
14
+ };
15
+ }
16
+ }
17
+ }
18
+
19
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
20
+ <kitn-feedback-bar bar-title="Was this helpful?"></kitn-feedback-bar>
21
+
22
+ <script type="module">
23
+ import '@kitnai/chat/elements'; // registers the custom elements
24
+
25
+ const bar = document.querySelector('kitn-feedback-bar');
26
+ bar.addEventListener('helpful', () => console.log('👍'));
27
+ bar.addEventListener('nothelpful', () => console.log('👎'));
28
+ bar.addEventListener('close', () => bar.remove());
29
+ </script>`;
30
+
31
+ const meta = {
32
+ title: 'Web Components/kitn-feedback-bar',
33
+ tags: ['autodocs'],
34
+ parameters: {
35
+ layout: 'fullscreen',
36
+ docs: {
37
+ description: {
38
+ component: [
39
+ '`<kitn-feedback-bar>` is the framework-agnostic **web component** for an inline thumbs up/down feedback banner with a dismiss button — isolated in **Shadow DOM**.',
40
+ '**When to use:** collecting a quick reaction after an answer or a completed task. In SolidJS, use the `FeedbackBar` primitive.',
41
+ "**How to use:** register once with `import '@kitnai/chat/elements'`, set the label via the `bar-title` attribute (`title` is avoided — it's a global HTML attribute), and listen for the `helpful` / `nothelpful` / `close` **CustomEvents**.",
42
+ 'See the **Code** tab for HTML usage.',
43
+ ].join('\n\n'),
44
+ },
45
+ },
46
+ },
47
+ } satisfies Meta;
48
+
49
+ export default meta;
50
+ type Story = StoryObj;
51
+
52
+ /** The default feedback prompt. */
53
+ export const Default: Story = {
54
+ render: () => (
55
+ <div style={{ padding: '24px', 'max-width': '480px' }}>
56
+ <kitn-feedback-bar
57
+ on:helpful={() => console.log('helpful')}
58
+ on:nothelpful={() => console.log('not helpful')}
59
+ on:close={() => console.log('closed')}
60
+ />
61
+ </div>
62
+ ),
63
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
64
+ };
65
+
66
+ /** A custom label via the `bar-title` attribute. */
67
+ export const CustomTitle: Story = {
68
+ render: () => (
69
+ <div style={{ padding: '24px', 'max-width': '480px' }}>
70
+ <kitn-feedback-bar bar-title="Did this answer your question?" />
71
+ </div>
72
+ ),
73
+ };
@@ -0,0 +1,81 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import './register'; // side effect: registers the custom elements
3
+
4
+ // The web components are custom DOM elements, so declare the tags for JSX.
5
+ declare module 'solid-js' {
6
+ // eslint-disable-next-line @typescript-eslint/no-namespace
7
+ namespace JSX {
8
+ interface IntrinsicElements {
9
+ 'kitn-file-upload': JSX.HTMLAttributes<HTMLElement> & {
10
+ multiple?: boolean | string;
11
+ accept?: string;
12
+ disabled?: boolean | string;
13
+ label?: string;
14
+ 'on:filesadded'?: (e: CustomEvent<{ files: File[] }>) => void;
15
+ };
16
+ }
17
+ }
18
+ }
19
+
20
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
21
+ <kitn-file-upload accept="image/*" label="Drop images here"></kitn-file-upload>
22
+
23
+ <script type="module">
24
+ import '@kitnai/chat/elements'; // registers the custom elements
25
+
26
+ document.querySelector('kitn-file-upload')
27
+ .addEventListener('filesadded', (e) =>
28
+ console.log(e.detail.files.map((f) => f.name)));
29
+ </script>`;
30
+
31
+ const meta = {
32
+ title: 'Web Components/kitn-file-upload',
33
+ tags: ['autodocs'],
34
+ parameters: {
35
+ layout: 'fullscreen',
36
+ docs: {
37
+ description: {
38
+ component: [
39
+ '`<kitn-file-upload>` is the framework-agnostic **web component** for a click / drag-and-drop file dropzone — isolated in **Shadow DOM**.',
40
+ '**When to use:** accepting file or image uploads in a non-Solid app. In SolidJS, compose the `FileUpload` primitives.',
41
+ "**How to use:** register once with `import '@kitnai/chat/elements'`, set the `accept` / `multiple` / `label` attributes, and listen for the `filesadded` **CustomEvent** (`e.detail.files` is a `File[]`). The default dropzone label can be replaced with your own markup via the default `<slot>`.",
42
+ 'See the **Code** tab for HTML usage.',
43
+ ].join('\n\n'),
44
+ },
45
+ },
46
+ },
47
+ } satisfies Meta;
48
+
49
+ export default meta;
50
+ type Story = StoryObj;
51
+
52
+ /** A dropzone accepting any files. */
53
+ export const Default: Story = {
54
+ render: () => (
55
+ <div style={{ padding: '24px', 'max-width': '480px' }}>
56
+ <kitn-file-upload
57
+ on:filesadded={(e: CustomEvent<{ files: File[] }>) =>
58
+ console.log(e.detail.files.map((f) => f.name))}
59
+ />
60
+ </div>
61
+ ),
62
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
63
+ };
64
+
65
+ /** Restricted to images, single file, with a custom label. */
66
+ export const ImagesOnly: Story = {
67
+ render: () => (
68
+ <div style={{ padding: '24px', 'max-width': '480px' }}>
69
+ <kitn-file-upload accept="image/*" multiple={false} label="Click or drop an image" />
70
+ </div>
71
+ ),
72
+ };
73
+
74
+ /** A disabled dropzone (non-interactive). */
75
+ export const Disabled: Story = {
76
+ render: () => (
77
+ <div style={{ padding: '24px', 'max-width': '480px' }}>
78
+ <kitn-file-upload disabled label="Uploads are disabled" />
79
+ </div>
80
+ ),
81
+ };
@@ -0,0 +1,70 @@
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
+
5
+ // The web components are custom DOM elements, so declare the tags for JSX.
6
+ declare module 'solid-js' {
7
+ // eslint-disable-next-line @typescript-eslint/no-namespace
8
+ namespace JSX {
9
+ interface IntrinsicElements {
10
+ 'kitn-image': JSX.HTMLAttributes<HTMLElement>;
11
+ }
12
+ }
13
+ }
14
+
15
+ // A small inline SVG, base64-encoded — the same trick the composable example uses.
16
+ const sampleSvg =
17
+ '<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96"><rect width="96" height="96" rx="16" fill="#7c3aed"/><text x="48" y="62" font-size="44" text-anchor="middle" fill="white">★</text></svg>';
18
+ const sampleBase64 = btoa(unescape(encodeURIComponent(sampleSvg)));
19
+
20
+ /** Render the actual `<kitn-image>` custom element with base64 + media-type. */
21
+ function ImageElement() {
22
+ let el: (HTMLElement & { base64?: string; alt?: string }) | undefined;
23
+ onMount(() => {
24
+ if (el) {
25
+ el.base64 = sampleBase64;
26
+ el.alt = 'A purple star';
27
+ el.setAttribute('media-type', 'image/svg+xml');
28
+ }
29
+ });
30
+ return <kitn-image ref={(e) => (el = e as HTMLElement)} style={{ display: 'inline-block', padding: '16px' }} />;
31
+ }
32
+
33
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
34
+ <kitn-image id="img" media-type="image/png" alt="A chart"></kitn-image>
35
+
36
+ <script type="module">
37
+ import '@kitnai/chat/elements'; // registers the custom elements
38
+
39
+ const img = document.getElementById('img');
40
+ img.base64 = '<...base64 image data...>';
41
+ // or set raw bytes as a property:
42
+ // img.bytes = new Uint8Array([...]);
43
+ </script>`;
44
+
45
+ const meta = {
46
+ title: 'Web Components/kitn-image',
47
+ tags: ['autodocs'],
48
+ parameters: {
49
+ layout: 'fullscreen',
50
+ docs: {
51
+ description: {
52
+ component: [
53
+ '`<kitn-image>` is the framework-agnostic **web component** that renders a base64 or byte-array image, showing a skeleton fallback while it resolves, isolated in **Shadow DOM**.',
54
+ '**When to use:** displaying model-generated or in-memory images (without a hosted URL) in a non-Solid app. In SolidJS, use the `Image` primitive directly.',
55
+ "**How to use:** register once with `import '@kitnai/chat/elements'`, set `base64` (paired with the `media-type` attribute) or set raw `bytes` as a **property**, and add `alt` text.",
56
+ 'See the **Code** tab for HTML usage.',
57
+ ].join('\n\n'),
58
+ },
59
+ },
60
+ },
61
+ } satisfies Meta;
62
+
63
+ export default meta;
64
+ type Story = StoryObj;
65
+
66
+ /** A base64-encoded SVG with a media type and alt text. */
67
+ export const Default: Story = {
68
+ render: () => <ImageElement />,
69
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
70
+ };
@@ -0,0 +1,87 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import './register'; // side effect: registers the custom elements
3
+
4
+ // The web components are custom DOM elements, so declare the tags for JSX.
5
+ declare module 'solid-js' {
6
+ // eslint-disable-next-line @typescript-eslint/no-namespace
7
+ namespace JSX {
8
+ interface IntrinsicElements {
9
+ 'kitn-loader': JSX.HTMLAttributes<HTMLElement> & {
10
+ variant?: string;
11
+ size?: string;
12
+ text?: string;
13
+ };
14
+ }
15
+ }
16
+ }
17
+
18
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
19
+ <kitn-loader variant="dots" size="md"></kitn-loader>
20
+
21
+ <script type="module">
22
+ import '@kitnai/chat/elements'; // registers the custom elements
23
+ </script>`;
24
+
25
+ const meta = {
26
+ title: 'Web Components/kitn-loader',
27
+ tags: ['autodocs'],
28
+ parameters: {
29
+ layout: 'fullscreen',
30
+ docs: {
31
+ description: {
32
+ component: [
33
+ '`<kitn-loader>` is the framework-agnostic **web component** for an animated busy indicator — a dozen styles (circular, dots, wave, bars, text-shimmer, …) selected via the `variant` attribute, isolated in **Shadow DOM**.',
34
+ '**When to use:** showing a small "working" indicator anywhere outside the chat thread (toolbars, buttons, panels). In SolidJS, use the `Loader` primitive directly.',
35
+ "**How to use:** register once with `import '@kitnai/chat/elements'`, then set `variant`, `size`, and (for text variants) `text` as plain HTML attributes.",
36
+ 'See the **Code** tab for HTML usage.',
37
+ ].join('\n\n'),
38
+ },
39
+ },
40
+ },
41
+ } satisfies Meta;
42
+
43
+ export default meta;
44
+ type Story = StoryObj;
45
+
46
+ const VARIANTS = [
47
+ 'circular', 'classic', 'pulse', 'pulse-dot', 'dots', 'typing',
48
+ 'wave', 'bars', 'terminal', 'text-blink', 'text-shimmer', 'loading-dots',
49
+ ];
50
+
51
+ /** The default circular spinner. */
52
+ export const Default: Story = {
53
+ render: () => (
54
+ <div style={{ padding: '24px' }}>
55
+ <kitn-loader variant="circular" size="md" />
56
+ </div>
57
+ ),
58
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
59
+ };
60
+
61
+ /** Every variant at the medium size, side by side. */
62
+ export const AllVariants: Story = {
63
+ render: () => (
64
+ <div style={{ display: 'flex', 'flex-wrap': 'wrap', gap: '32px', 'align-items': 'center', padding: '24px' }}>
65
+ {VARIANTS.map((v) => (
66
+ <div style={{ display: 'flex', 'flex-direction': 'column', 'align-items': 'center', gap: '8px', 'min-width': '90px' }}>
67
+ <kitn-loader variant={v} size="md" text="Loading" />
68
+ <code style={{ 'font-size': '11px', opacity: 0.6 }}>{v}</code>
69
+ </div>
70
+ ))}
71
+ </div>
72
+ ),
73
+ };
74
+
75
+ /** The three sizes (`sm` / `md` / `lg`) of the dots variant. */
76
+ export const Sizes: Story = {
77
+ render: () => (
78
+ <div style={{ display: 'flex', gap: '32px', 'align-items': 'center', padding: '24px' }}>
79
+ {['sm', 'md', 'lg'].map((s) => (
80
+ <div style={{ display: 'flex', 'flex-direction': 'column', 'align-items': 'center', gap: '8px' }}>
81
+ <kitn-loader variant="dots" size={s} />
82
+ <code style={{ 'font-size': '11px', opacity: 0.6 }}>{s}</code>
83
+ </div>
84
+ ))}
85
+ </div>
86
+ ),
87
+ };
@@ -0,0 +1,75 @@
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
+
5
+ // The web components are custom DOM elements, so declare the tags for JSX.
6
+ declare module 'solid-js' {
7
+ // eslint-disable-next-line @typescript-eslint/no-namespace
8
+ namespace JSX {
9
+ interface IntrinsicElements {
10
+ 'kitn-markdown': JSX.HTMLAttributes<HTMLElement>;
11
+ }
12
+ }
13
+ }
14
+
15
+ const sampleMarkdown = `### Markdown
16
+ Renders **bold**, _italic_, \`code\`, and lists:
17
+ - one
18
+ - two
19
+
20
+ > and blockquotes.
21
+
22
+ \`\`\`ts
23
+ export function add(a: number, b: number): number {
24
+ return a + b;
25
+ }
26
+ \`\`\``;
27
+
28
+ /** Render the actual `<kitn-markdown>` custom element with a `content` property. */
29
+ function MarkdownElement(props: { content: string }) {
30
+ let el: (HTMLElement & { content?: string }) | undefined;
31
+ onMount(() => {
32
+ if (el) el.content = props.content;
33
+ });
34
+ return (
35
+ <kitn-markdown ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px', 'max-width': '720px' }} />
36
+ );
37
+ }
38
+
39
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
40
+ <kitn-markdown id="md" code-theme="github-dark-dimmed"></kitn-markdown>
41
+
42
+ <script type="module">
43
+ import '@kitnai/chat/elements'; // registers the custom elements
44
+
45
+ const md = document.getElementById('md');
46
+ md.content = '### Hello\\nRenders **bold**, _italic_, and \\\`code\\\`.';
47
+ // md.setAttribute('code-highlight', 'false'); // skip Shiki entirely
48
+ </script>`;
49
+
50
+ const meta = {
51
+ title: 'Web Components/kitn-markdown',
52
+ tags: ['autodocs'],
53
+ parameters: {
54
+ layout: 'fullscreen',
55
+ docs: {
56
+ description: {
57
+ component: [
58
+ '`<kitn-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**.',
59
+ '**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.',
60
+ "**How to use:** register once with `import '@kitnai/chat/elements'`, set the source via the `content` **property** (`el.content = '...'`), and tune rendering with the `prose-size`, `code-theme`, and `code-highlight` attributes.",
61
+ 'See the **Code** tab for HTML usage.',
62
+ ].join('\n\n'),
63
+ },
64
+ },
65
+ },
66
+ } satisfies Meta;
67
+
68
+ export default meta;
69
+ type Story = StoryObj;
70
+
71
+ /** Headings, emphasis, lists, a blockquote, and a highlighted code fence. */
72
+ export const Default: Story = {
73
+ render: () => <MarkdownElement content={sampleMarkdown} />,
74
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
75
+ };
@@ -0,0 +1,74 @@
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
+
5
+ // The web components are custom DOM elements, so declare the tags for JSX.
6
+ declare module 'solid-js' {
7
+ // eslint-disable-next-line @typescript-eslint/no-namespace
8
+ namespace JSX {
9
+ interface IntrinsicElements {
10
+ 'kitn-message-skills': JSX.HTMLAttributes<HTMLElement>;
11
+ }
12
+ }
13
+ }
14
+
15
+ interface Skill {
16
+ id: string;
17
+ name: string;
18
+ }
19
+
20
+ const sampleSkills: Skill[] = [
21
+ { id: 's1', name: 'web-search' },
22
+ { id: 's2', name: 'code' },
23
+ ];
24
+
25
+ /** Render the actual `<kitn-message-skills>` custom element with a `skills` property. */
26
+ function MessageSkillsElement(props: { skills: Skill[] }) {
27
+ let el: (HTMLElement & { skills?: Skill[] }) | undefined;
28
+ onMount(() => {
29
+ if (el) el.skills = props.skills;
30
+ });
31
+ return (
32
+ <kitn-message-skills ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px' }} />
33
+ );
34
+ }
35
+
36
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
37
+ <kitn-message-skills id="skills"></kitn-message-skills>
38
+
39
+ <script type="module">
40
+ import '@kitnai/chat/elements'; // registers the custom elements
41
+
42
+ const skills = document.getElementById('skills');
43
+ skills.skills = [
44
+ { id: 's1', name: 'web-search' },
45
+ { id: 's2', name: 'code' },
46
+ ];
47
+ </script>`;
48
+
49
+ const meta = {
50
+ title: 'Web Components/kitn-message-skills',
51
+ tags: ['autodocs'],
52
+ parameters: {
53
+ layout: 'fullscreen',
54
+ docs: {
55
+ description: {
56
+ component: [
57
+ '`<kitn-message-skills>` is the framework-agnostic **web component** that badges which skills were active for a message, isolated in **Shadow DOM**.',
58
+ '**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.',
59
+ "**How to use:** register once with `import '@kitnai/chat/elements'`, then set the `skills` **property** to an array of `{ id, name }`.",
60
+ 'See the **Code** tab for HTML usage.',
61
+ ].join('\n\n'),
62
+ },
63
+ },
64
+ },
65
+ } satisfies Meta;
66
+
67
+ export default meta;
68
+ type Story = StoryObj;
69
+
70
+ /** Two active-skill badges. */
71
+ export const Default: Story = {
72
+ render: () => <MessageSkillsElement skills={sampleSkills} />,
73
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
74
+ };
@@ -0,0 +1,105 @@
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
+
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
+ 'kitn-message': JSX.HTMLAttributes<HTMLElement>;
12
+ }
13
+ }
14
+ }
15
+
16
+ const assistantMessage: ChatMessage = {
17
+ id: 'm-a',
18
+ role: 'assistant',
19
+ content:
20
+ "Here's the plan, with a quick code sample:\n\n```js\nconst kit = useKitn();\n```\n\nThat wires the kit into your component.",
21
+ reasoning: { text: 'The user wants X, so I should do Y then Z.', label: 'Reasoning' },
22
+ tools: [{ type: 'search', state: 'output-available', input: { query: 'kitn docs' }, output: { hits: 3 } }],
23
+ attachments: [
24
+ {
25
+ id: '1',
26
+ type: 'file',
27
+ filename: 'architecture.png',
28
+ mediaType: 'image/png',
29
+ url:
30
+ 'data:image/svg+xml;utf8,' +
31
+ encodeURIComponent(
32
+ '<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>',
33
+ ),
34
+ },
35
+ ],
36
+ actions: ['copy', 'like', 'dislike', 'regenerate'],
37
+ };
38
+
39
+ const userMessage: ChatMessage = {
40
+ id: 'm-u',
41
+ role: 'user',
42
+ content: 'How do I compose these myself?',
43
+ };
44
+
45
+ /** Render the actual `<kitn-message>` custom element with a `message` property. */
46
+ function MessageElement(props: { message: ChatMessage }) {
47
+ let el: (HTMLElement & { message?: ChatMessage }) | undefined;
48
+ onMount(() => {
49
+ if (el) el.message = props.message;
50
+ });
51
+ return (
52
+ <kitn-message ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px', 'max-width': '720px' }} />
53
+ );
54
+ }
55
+
56
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
57
+ <kitn-message id="msg" style="display:block;"></kitn-message>
58
+
59
+ <script type="module">
60
+ import '@kitnai/chat/elements'; // registers the custom elements
61
+
62
+ const msg = document.getElementById('msg');
63
+ msg.message = {
64
+ id: 'm-a', role: 'assistant',
65
+ content: "Here's the plan:\\n\\n\\\`\\\`\\\`js\\nconst kit = useKitn();\\n\\\`\\\`\\\`",
66
+ reasoning: { text: 'The user wants X, so I should do Y.' },
67
+ tools: [{ type: 'search', state: 'output-available', output: { hits: 3 } }],
68
+ actions: ['copy', 'like', 'dislike', 'regenerate'],
69
+ };
70
+
71
+ // events are CustomEvents on the element (they do not bubble)
72
+ msg.addEventListener('messageaction', (e) => console.log(e.detail.action, e.detail.messageId));
73
+ </script>`;
74
+
75
+ const meta = {
76
+ title: 'Web Components/kitn-message',
77
+ tags: ['autodocs'],
78
+ parameters: {
79
+ layout: 'fullscreen',
80
+ docs: {
81
+ description: {
82
+ component: [
83
+ '`<kitn-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 `<kitn-chat>` uses per message). It is the keystone of the "compose your own message list" pattern, isolated in **Shadow DOM**.',
84
+ "**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.",
85
+ "**How to use:** register once with `import '@kitnai/chat/elements'`, set the whole row via the `message` **property** (`el.message = {...}`), and listen for the `messageaction` **CustomEvent** for action-button clicks. For simple cases, set `role` + `content` attributes instead of a full object.",
86
+ 'See the **Code** tab for HTML usage.',
87
+ ].join('\n\n'),
88
+ },
89
+ },
90
+ },
91
+ } satisfies Meta;
92
+
93
+ export default meta;
94
+ type Story = StoryObj;
95
+
96
+ /** A rich assistant message: markdown, reasoning, a tool call, an attachment, and actions. */
97
+ export const Assistant: Story = {
98
+ render: () => <MessageElement message={assistantMessage} />,
99
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
100
+ };
101
+
102
+ /** A plain user message (markdown defaults off for the user role). */
103
+ export const User: Story = {
104
+ render: () => <MessageElement message={userMessage} />,
105
+ };