@kitnai/chat 0.1.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 (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +314 -0
  3. package/dist/bash-InADTalH.js +6 -0
  4. package/dist/core-AYMC6_lb.js +5874 -0
  5. package/dist/engine-javascript-vq0WuIJl.js +2643 -0
  6. package/dist/github-dark-dimmed-DUshB20C.js +4 -0
  7. package/dist/github-light-JYsPkUQd.js +4 -0
  8. package/dist/javascript-C25yR2R2.js +6 -0
  9. package/dist/json-DxJze_jm.js +6 -0
  10. package/dist/kitn-chat.es.js +6632 -0
  11. package/dist/tsx-B8rCNbgL.js +6 -0
  12. package/dist/typescript-RycA9KXf.js +6 -0
  13. package/package.json +80 -0
  14. package/src/components/attachments.stories.tsx +304 -0
  15. package/src/components/attachments.tsx +394 -0
  16. package/src/components/chain-of-thought.stories.tsx +212 -0
  17. package/src/components/chain-of-thought.tsx +139 -0
  18. package/src/components/chat-container.stories.tsx +188 -0
  19. package/src/components/chat-container.tsx +78 -0
  20. package/src/components/chat-scope-picker.tsx +47 -0
  21. package/src/components/checkpoint.stories.tsx +103 -0
  22. package/src/components/checkpoint.tsx +81 -0
  23. package/src/components/code-block.stories.tsx +151 -0
  24. package/src/components/code-block.tsx +99 -0
  25. package/src/components/context.stories.tsx +180 -0
  26. package/src/components/context.tsx +323 -0
  27. package/src/components/conversation-item.stories.tsx +126 -0
  28. package/src/components/conversation-item.tsx +18 -0
  29. package/src/components/conversation-list.stories.tsx +134 -0
  30. package/src/components/conversation-list.tsx +100 -0
  31. package/src/components/empty.stories.tsx +435 -0
  32. package/src/components/empty.tsx +166 -0
  33. package/src/components/feedback-bar.stories.tsx +101 -0
  34. package/src/components/feedback-bar.tsx +58 -0
  35. package/src/components/file-upload.stories.tsx +157 -0
  36. package/src/components/file-upload.tsx +161 -0
  37. package/src/components/image.stories.tsx +90 -0
  38. package/src/components/image.tsx +67 -0
  39. package/src/components/loader.stories.tsx +182 -0
  40. package/src/components/loader.tsx +333 -0
  41. package/src/components/markdown.stories.tsx +181 -0
  42. package/src/components/markdown.tsx +81 -0
  43. package/src/components/message-narrow.stories.tsx +330 -0
  44. package/src/components/message-skills.stories.tsx +212 -0
  45. package/src/components/message-skills.tsx +36 -0
  46. package/src/components/message.stories.tsx +282 -0
  47. package/src/components/message.tsx +149 -0
  48. package/src/components/model-switcher.stories.tsx +98 -0
  49. package/src/components/model-switcher.tsx +36 -0
  50. package/src/components/prompt-input.stories.tsx +223 -0
  51. package/src/components/prompt-input.tsx +190 -0
  52. package/src/components/prompt-suggestion.stories.tsx +143 -0
  53. package/src/components/prompt-suggestion.tsx +115 -0
  54. package/src/components/reasoning.stories.tsx +141 -0
  55. package/src/components/reasoning.tsx +157 -0
  56. package/src/components/response-stream.tsx +103 -0
  57. package/src/components/scroll-button.stories.tsx +101 -0
  58. package/src/components/scroll-button.tsx +33 -0
  59. package/src/components/slash-command.stories.tsx +164 -0
  60. package/src/components/slash-command.tsx +223 -0
  61. package/src/components/source.stories.tsx +125 -0
  62. package/src/components/source.tsx +129 -0
  63. package/src/components/text-shimmer.stories.tsx +88 -0
  64. package/src/components/text-shimmer.tsx +37 -0
  65. package/src/components/thinking-bar.stories.tsx +88 -0
  66. package/src/components/thinking-bar.tsx +50 -0
  67. package/src/components/tool.stories.tsx +154 -0
  68. package/src/components/tool.tsx +173 -0
  69. package/src/components/voice-input.stories.tsx +84 -0
  70. package/src/components/voice-input.tsx +103 -0
  71. package/src/elements/chat-types.ts +14 -0
  72. package/src/elements/chat.tsx +111 -0
  73. package/src/elements/compiled.css +2 -0
  74. package/src/elements/conversation-list.tsx +26 -0
  75. package/src/elements/css.ts +5 -0
  76. package/src/elements/default-input.tsx +53 -0
  77. package/src/elements/define.tsx +54 -0
  78. package/src/elements/kitn-chat.stories.tsx +105 -0
  79. package/src/elements/kitn-conversation-list.stories.tsx +177 -0
  80. package/src/elements/kitn-prompt-input.stories.tsx +123 -0
  81. package/src/elements/prompt-input.tsx +39 -0
  82. package/src/elements/register.ts +9 -0
  83. package/src/elements/styles.css +12 -0
  84. package/src/index.ts +128 -0
  85. package/src/primitives/chat-config.tsx +76 -0
  86. package/src/primitives/highlighter.ts +150 -0
  87. package/src/primitives/use-auto-resize.ts +31 -0
  88. package/src/primitives/use-stick-to-bottom.ts +43 -0
  89. package/src/primitives/use-text-stream.ts +112 -0
  90. package/src/primitives/use-voice-recorder.ts +50 -0
  91. package/src/stories/chat-panel-layout.stories.tsx +144 -0
  92. package/src/stories/chat-scene.tsx +570 -0
  93. package/src/stories/checkpoint-restore.stories.tsx +224 -0
  94. package/src/stories/context-usage.stories.tsx +155 -0
  95. package/src/stories/conversation-with-reasoning.stories.tsx +151 -0
  96. package/src/stories/conversation-with-sources.stories.tsx +165 -0
  97. package/src/stories/docs/GettingStarted.mdx +76 -0
  98. package/src/stories/docs/Installation.mdx +48 -0
  99. package/src/stories/docs/Integrations.mdx +110 -0
  100. package/src/stories/docs/Introduction.mdx +29 -0
  101. package/src/stories/docs/Theming.mdx +87 -0
  102. package/src/stories/docs/theme-editor/canvas.tsx +32 -0
  103. package/src/stories/docs/theme-editor/inspector.tsx +66 -0
  104. package/src/stories/docs/theme-editor/presets.test.ts +32 -0
  105. package/src/stories/docs/theme-editor/presets.ts +64 -0
  106. package/src/stories/docs/theme-editor/theme-css.test.ts +19 -0
  107. package/src/stories/docs/theme-editor/theme-css.ts +15 -0
  108. package/src/stories/docs/theme-editor/theme-editor.tsx +145 -0
  109. package/src/stories/docs/theme-tokens.tsx +174 -0
  110. package/src/stories/full-chat.stories.tsx +18 -0
  111. package/src/stories/message-actions.stories.tsx +167 -0
  112. package/src/stories/prompt-input-variants.stories.tsx +179 -0
  113. package/src/stories/streaming-response.stories.tsx +234 -0
  114. package/src/stories/theme-editor.stories.tsx +16 -0
  115. package/src/stories/token-reference.stories.tsx +18 -0
  116. package/src/types.ts +41 -0
  117. package/src/ui/avatar.stories.tsx +104 -0
  118. package/src/ui/avatar.tsx +23 -0
  119. package/src/ui/badge.stories.tsx +87 -0
  120. package/src/ui/badge.tsx +21 -0
  121. package/src/ui/button.stories.tsx +146 -0
  122. package/src/ui/button.tsx +37 -0
  123. package/src/ui/collapsible.tsx +14 -0
  124. package/src/ui/dialog.tsx +21 -0
  125. package/src/ui/dropdown.tsx +26 -0
  126. package/src/ui/hover-card.tsx +48 -0
  127. package/src/ui/resizable.stories.tsx +171 -0
  128. package/src/ui/resizable.tsx +219 -0
  129. package/src/ui/scroll-area.tsx +13 -0
  130. package/src/ui/separator.stories.tsx +82 -0
  131. package/src/ui/separator.tsx +10 -0
  132. package/src/ui/skeleton.stories.tsx +338 -0
  133. package/src/ui/skeleton.tsx +16 -0
  134. package/src/ui/textarea.tsx +21 -0
  135. package/src/ui/tooltip.stories.tsx +75 -0
  136. package/src/ui/tooltip.tsx +22 -0
  137. package/src/utils/cn.ts +6 -0
  138. package/theme.css +115 -0
@@ -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 <kitn-chat>, <kitn-conversation-list>, <kitn-prompt-input>
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-chat': JSX.HTMLAttributes<HTMLElement>;
12
+ }
13
+ }
14
+ }
15
+
16
+ const sampleMessages: ChatMessage[] = [
17
+ { id: '1', role: 'user', content: 'How do I center a div?' },
18
+ {
19
+ id: '2',
20
+ role: 'assistant',
21
+ content:
22
+ 'The modern way is a grid:\n\n```css\n.box {\n display: grid;\n place-items: center;\n}\n```\n\nThat centers the child on both axes.',
23
+ actions: ['copy', 'like', 'dislike'],
24
+ },
25
+ ];
26
+
27
+ /** Live demo of the actual `<kitn-chat>` custom element (Shadow DOM and all). */
28
+ function ChatElement() {
29
+ let el: (HTMLElement & { messages?: ChatMessage[] }) | undefined;
30
+ onMount(() => {
31
+ if (el) el.messages = sampleMessages;
32
+ });
33
+ return <kitn-chat ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', height: '560px' }} />;
34
+ }
35
+
36
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
37
+ <kitn-chat id="chat" style="display:block; height:100vh;"></kitn-chat>
38
+
39
+ <script type="module">
40
+ import '@kitnai/chat/elements'; // registers the custom elements
41
+
42
+ const chat = document.getElementById('chat');
43
+ chat.messages = [
44
+ { id: '1', role: 'user', content: 'How do I center a div?' },
45
+ { id: '2', role: 'assistant', content: 'Use \`display: grid; place-items: center;\`' },
46
+ ];
47
+
48
+ // events are CustomEvents on the element (they do not bubble)
49
+ chat.addEventListener('submit', (e) => console.log('user sent:', e.detail.value));
50
+ </script>`;
51
+
52
+ const SOLID_SNIPPET = `import '@kitnai/chat/elements'; // registers the custom elements
53
+ import { onMount } from 'solid-js';
54
+ import type { ChatMessage } from '@kitnai/chat/elements';
55
+
56
+ function Chat() {
57
+ let el: HTMLElement & { messages?: ChatMessage[] };
58
+ const messages: ChatMessage[] = [
59
+ { id: '1', role: 'user', content: 'How do I center a div?' },
60
+ { id: '2', role: 'assistant', content: 'Use \`display: grid; place-items: center;\`' },
61
+ ];
62
+ onMount(() => { el.messages = messages; });
63
+ return (
64
+ <kitn-chat
65
+ ref={el}
66
+ style={{ display: 'block', height: '100vh' }}
67
+ on:submit={(e) => console.log('user sent:', e.detail.value)}
68
+ />
69
+ );
70
+ }`;
71
+
72
+ const meta = {
73
+ title: 'Web Components/kitn-chat',
74
+ tags: ['autodocs'],
75
+ parameters: {
76
+ layout: 'fullscreen',
77
+ docs: {
78
+ description: {
79
+ component: [
80
+ '`<kitn-chat>` is the framework-agnostic **web component** version of the chat UI — a complete message thread plus prompt input, isolated in **Shadow DOM** so the host page\'s CSS can\'t leak in and the kit\'s styles can\'t leak out. SolidJS is bundled in, so the host needs nothing.',
81
+ '**When to use:** dropping a full chat into a non-Solid app (React, Vue, Svelte, plain HTML), or anywhere you want zero style conflicts. If you *are* in SolidJS and want fine-grained control, compose the primitives (`ChatContainer`, `Message`, `PromptInput`) instead.',
82
+ '**How to use:** register once with `import \'@kitnai/chat/elements\'`, set rich data as JS **properties** (`el.messages = [...]`), and listen for **CustomEvents** (`submit`, `messageaction`, `valuechange`) directly on the element.',
83
+ '**Placement:** as a top-level panel or full-page surface. Give it an explicit height (e.g. `height: 100vh`).',
84
+ 'See the **Code** tab below for the HTML usage; the *SolidJS* story shows the same element inside a Solid component.',
85
+ ].join('\n\n'),
86
+ },
87
+ },
88
+ },
89
+ } satisfies Meta;
90
+
91
+ export default meta;
92
+ type Story = StoryObj;
93
+
94
+ /** The element used the plain-HTML / any-framework way. */
95
+ export const Default: Story = {
96
+ render: () => <ChatElement />,
97
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
98
+ };
99
+
100
+ /** The same element used inside a SolidJS component (properties via `ref`, events via `on:`). */
101
+ export const InSolidJS: Story = {
102
+ name: 'In SolidJS',
103
+ render: () => <ChatElement />,
104
+ parameters: { docs: { source: { code: SOLID_SNIPPET, language: 'tsx' } } },
105
+ };
@@ -0,0 +1,177 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { onMount } from 'solid-js';
3
+ import './register'; // side effect: registers <kitn-chat>, <kitn-conversation-list>, <kitn-prompt-input>
4
+ import type { ConversationGroup, ConversationSummary } from '../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-conversation-list': JSX.HTMLAttributes<HTMLElement>;
12
+ }
13
+ }
14
+ }
15
+
16
+ const sampleGroups: ConversationGroup[] = [
17
+ { id: 'g-work', name: 'Work', sortOrder: 0, createdAt: '2026-06-01T09:00:00.000Z' },
18
+ { id: 'g-personal', name: 'Personal', sortOrder: 1, createdAt: '2026-06-02T09:00:00.000Z' },
19
+ ];
20
+
21
+ const sampleConversations: ConversationSummary[] = [
22
+ {
23
+ id: 'c-1',
24
+ title: 'Q2 launch plan',
25
+ groupId: 'g-work',
26
+ scope: { type: 'collection' },
27
+ messageCount: 12,
28
+ lastMessageAt: '2026-06-09T14:20:00.000Z',
29
+ updatedAt: '2026-06-09T14:20:00.000Z',
30
+ },
31
+ {
32
+ id: 'c-2',
33
+ title: 'API migration notes',
34
+ groupId: 'g-work',
35
+ scope: { type: 'collection' },
36
+ messageCount: 5,
37
+ lastMessageAt: '2026-06-08T11:05:00.000Z',
38
+ updatedAt: '2026-06-08T11:05:00.000Z',
39
+ },
40
+ {
41
+ id: 'c-3',
42
+ title: 'Weekend trip ideas',
43
+ groupId: 'g-personal',
44
+ scope: { type: 'collection' },
45
+ messageCount: 8,
46
+ lastMessageAt: '2026-06-07T19:42:00.000Z',
47
+ updatedAt: '2026-06-07T19:42:00.000Z',
48
+ },
49
+ {
50
+ id: 'c-4',
51
+ title: 'Untitled chat',
52
+ scope: { type: 'collection' },
53
+ messageCount: 1,
54
+ lastMessageAt: '2026-06-10T08:00:00.000Z',
55
+ updatedAt: '2026-06-10T08:00:00.000Z',
56
+ },
57
+ ];
58
+
59
+ /** Live demo of the actual `<kitn-conversation-list>` custom element (Shadow DOM and all). */
60
+ function ConversationListElement() {
61
+ let el:
62
+ | (HTMLElement & {
63
+ groups?: ConversationGroup[];
64
+ conversations?: ConversationSummary[];
65
+ activeId?: string;
66
+ })
67
+ | undefined;
68
+ onMount(() => {
69
+ if (el) {
70
+ el.groups = sampleGroups;
71
+ el.conversations = sampleConversations;
72
+ el.activeId = 'c-1';
73
+ }
74
+ });
75
+ return (
76
+ <kitn-conversation-list
77
+ ref={(e) => (el = e as HTMLElement)}
78
+ style={{ display: 'block', width: '300px', height: '560px' }}
79
+ />
80
+ );
81
+ }
82
+
83
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
84
+ <kitn-conversation-list id="list" style="display:block; width:300px; height:100vh;"></kitn-conversation-list>
85
+
86
+ <script type="module">
87
+ import '@kitnai/chat/elements'; // registers the custom elements
88
+
89
+ const list = document.getElementById('list');
90
+ list.groups = [
91
+ { id: 'g-work', name: 'Work', sortOrder: 0, createdAt: '2026-06-01T09:00:00.000Z' },
92
+ ];
93
+ list.conversations = [
94
+ {
95
+ id: 'c-1', title: 'Q2 launch plan', groupId: 'g-work',
96
+ scope: { type: 'collection' }, messageCount: 12,
97
+ lastMessageAt: '2026-06-09T14:20:00.000Z', updatedAt: '2026-06-09T14:20:00.000Z',
98
+ },
99
+ ];
100
+ list.activeId = 'c-1';
101
+
102
+ // events are CustomEvents on the element (they do not bubble)
103
+ list.addEventListener('select', (e) => console.log('opened:', e.detail.id));
104
+ list.addEventListener('newchat', () => console.log('new chat'));
105
+ list.addEventListener('togglesidebar', () => console.log('toggle sidebar'));
106
+ </script>`;
107
+
108
+ const SOLID_SNIPPET = `import '@kitnai/chat/elements'; // registers the custom elements
109
+ import { onMount } from 'solid-js';
110
+ import type { ConversationGroup, ConversationSummary } from '@kitnai/chat';
111
+
112
+ function Sidebar() {
113
+ let el: HTMLElement & {
114
+ groups?: ConversationGroup[];
115
+ conversations?: ConversationSummary[];
116
+ activeId?: string;
117
+ };
118
+ const groups: ConversationGroup[] = [
119
+ { id: 'g-work', name: 'Work', sortOrder: 0, createdAt: '2026-06-01T09:00:00.000Z' },
120
+ ];
121
+ const conversations: ConversationSummary[] = [
122
+ {
123
+ id: 'c-1', title: 'Q2 launch plan', groupId: 'g-work',
124
+ scope: { type: 'collection' }, messageCount: 12,
125
+ lastMessageAt: '2026-06-09T14:20:00.000Z', updatedAt: '2026-06-09T14:20:00.000Z',
126
+ },
127
+ ];
128
+ onMount(() => {
129
+ el.groups = groups;
130
+ el.conversations = conversations;
131
+ el.activeId = 'c-1';
132
+ });
133
+ return (
134
+ <kitn-conversation-list
135
+ ref={el}
136
+ style={{ display: 'block', width: '300px', height: '100vh' }}
137
+ on:select={(e) => console.log('opened:', e.detail.id)}
138
+ on:newchat={() => console.log('new chat')}
139
+ on:togglesidebar={() => console.log('toggle sidebar')}
140
+ />
141
+ );
142
+ }`;
143
+
144
+ const meta = {
145
+ title: 'Web Components/kitn-conversation-list',
146
+ tags: ['autodocs'],
147
+ parameters: {
148
+ layout: 'fullscreen',
149
+ docs: {
150
+ description: {
151
+ component: [
152
+ '`<kitn-conversation-list>` is the framework-agnostic **web component** version of the chat sidebar — a searchable, grouped list of conversations with a "new chat" button, isolated in **Shadow DOM** so the host page\'s CSS can\'t leak in and the kit\'s styles can\'t leak out. SolidJS is bundled in, so the host needs nothing.',
153
+ '**When to use:** adding a conversation switcher to a non-Solid app (React, Vue, Svelte, plain HTML), or anywhere you want zero style conflicts. If you *are* in SolidJS and want fine-grained control, compose the `ConversationList` primitive instead.',
154
+ '**How to use:** register once with `import \'@kitnai/chat/elements\'`, set rich data as JS **properties** (`el.groups = [...]`, `el.conversations = [...]`, `el.activeId = \'c-1\'`), and listen for **CustomEvents** (`select`, `newchat`, `togglesidebar`) directly on the element.',
155
+ '**Placement:** as a fixed-width side panel next to the chat surface. Give it an explicit width and height (e.g. `width: 300px; height: 100vh`).',
156
+ 'See the **Code** tab below for the HTML usage; the *SolidJS* story shows the same element inside a Solid component.',
157
+ ].join('\n\n'),
158
+ },
159
+ },
160
+ },
161
+ } satisfies Meta;
162
+
163
+ export default meta;
164
+ type Story = StoryObj;
165
+
166
+ /** The element used the plain-HTML / any-framework way. */
167
+ export const Default: Story = {
168
+ render: () => <ConversationListElement />,
169
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
170
+ };
171
+
172
+ /** The same element used inside a SolidJS component (properties via `ref`, events via `on:`). */
173
+ export const InSolidJS: Story = {
174
+ name: 'In SolidJS',
175
+ render: () => <ConversationListElement />,
176
+ parameters: { docs: { source: { code: SOLID_SNIPPET, language: 'tsx' } } },
177
+ };
@@ -0,0 +1,123 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { onMount } from 'solid-js';
3
+ import './register'; // side effect: registers <kitn-chat>, <kitn-conversation-list>, <kitn-prompt-input>
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-prompt-input': JSX.HTMLAttributes<HTMLElement>;
11
+ }
12
+ }
13
+ }
14
+
15
+ const sampleSuggestions: string[] = [
16
+ 'Summarize this thread',
17
+ 'Draft a reply',
18
+ 'Explain like I am five',
19
+ ];
20
+
21
+ /** Live demo of the actual `<kitn-prompt-input>` custom element (Shadow DOM and all). */
22
+ function PromptInputElement() {
23
+ let el:
24
+ | (HTMLElement & {
25
+ value?: string;
26
+ placeholder?: string;
27
+ disabled?: boolean;
28
+ loading?: boolean;
29
+ suggestions?: string[];
30
+ })
31
+ | undefined;
32
+ onMount(() => {
33
+ if (el) {
34
+ el.placeholder = 'Ask anything...';
35
+ el.suggestions = sampleSuggestions;
36
+ }
37
+ });
38
+ return (
39
+ <kitn-prompt-input
40
+ ref={(e) => (el = e as HTMLElement)}
41
+ style={{ display: 'block', width: '100%', padding: '16px' }}
42
+ />
43
+ );
44
+ }
45
+
46
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
47
+ <kitn-prompt-input id="input" style="display:block; width:100%;"></kitn-prompt-input>
48
+
49
+ <script type="module">
50
+ import '@kitnai/chat/elements'; // registers the custom elements
51
+
52
+ const input = document.getElementById('input');
53
+ input.placeholder = 'Ask anything...';
54
+ input.suggestions = ['Summarize this thread', 'Draft a reply'];
55
+ // input.loading = true; // shows the busy state while a response streams
56
+ // input.disabled = true; // blocks typing and submit
57
+
58
+ // events are CustomEvents on the element (they do not bubble)
59
+ input.addEventListener('submit', (e) => console.log('send:', e.detail.value));
60
+ input.addEventListener('valuechange', (e) => console.log('typing:', e.detail.value));
61
+ input.addEventListener('suggestionclick', (e) => console.log('picked:', e.detail.value));
62
+ </script>`;
63
+
64
+ const SOLID_SNIPPET = `import '@kitnai/chat/elements'; // registers the custom elements
65
+ import { onMount } from 'solid-js';
66
+
67
+ function Composer() {
68
+ let el: HTMLElement & {
69
+ value?: string;
70
+ placeholder?: string;
71
+ disabled?: boolean;
72
+ loading?: boolean;
73
+ suggestions?: string[];
74
+ };
75
+ onMount(() => {
76
+ el.placeholder = 'Ask anything...';
77
+ el.suggestions = ['Summarize this thread', 'Draft a reply'];
78
+ });
79
+ return (
80
+ <kitn-prompt-input
81
+ ref={el}
82
+ style={{ display: 'block', width: '100%' }}
83
+ on:submit={(e) => console.log('send:', e.detail.value)}
84
+ on:valuechange={(e) => console.log('typing:', e.detail.value)}
85
+ on:suggestionclick={(e) => console.log('picked:', e.detail.value)}
86
+ />
87
+ );
88
+ }`;
89
+
90
+ const meta = {
91
+ title: 'Web Components/kitn-prompt-input',
92
+ tags: ['autodocs'],
93
+ parameters: {
94
+ layout: 'fullscreen',
95
+ docs: {
96
+ description: {
97
+ component: [
98
+ '`<kitn-prompt-input>` is the framework-agnostic **web component** version of the chat composer — an auto-resizing textarea with a send button and optional suggestion chips, isolated in **Shadow DOM** so the host page\'s CSS can\'t leak in and the kit\'s styles can\'t leak out. SolidJS is bundled in, so the host needs nothing.',
99
+ '**When to use:** adding a message composer to a non-Solid app (React, Vue, Svelte, plain HTML), or anywhere you want zero style conflicts. If you *are* in SolidJS and want fine-grained control, compose the `PromptInput` primitives instead.',
100
+ '**How to use:** register once with `import \'@kitnai/chat/elements\'`, configure it with JS **properties** (`placeholder`, `value`, `disabled`, `loading`, `suggestions`), and listen for **CustomEvents** (`submit`, `valuechange`, `suggestionclick`) directly on the element. Leave `value` unset to let the element manage its own input state.',
101
+ '**Placement:** pinned to the bottom of a chat surface, full width. Set `loading` while a response streams to show the busy state, and `disabled` to block input entirely.',
102
+ 'See the **Code** tab below for the HTML usage; the *SolidJS* story shows the same element inside a Solid component.',
103
+ ].join('\n\n'),
104
+ },
105
+ },
106
+ },
107
+ } satisfies Meta;
108
+
109
+ export default meta;
110
+ type Story = StoryObj;
111
+
112
+ /** The element used the plain-HTML / any-framework way. */
113
+ export const Default: Story = {
114
+ render: () => <PromptInputElement />,
115
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
116
+ };
117
+
118
+ /** The same element used inside a SolidJS component (properties via `ref`, events via `on:`). */
119
+ export const InSolidJS: Story = {
120
+ name: 'In SolidJS',
121
+ render: () => <PromptInputElement />,
122
+ parameters: { docs: { source: { code: SOLID_SNIPPET, language: 'tsx' } } },
123
+ };
@@ -0,0 +1,39 @@
1
+ import { createSignal } from 'solid-js';
2
+ import { defineKitnElement } from './define';
3
+ import { DefaultPromptInput } from './default-input';
4
+
5
+ interface Props extends Record<string, unknown> {
6
+ value?: string;
7
+ placeholder?: string;
8
+ disabled?: boolean;
9
+ loading?: boolean;
10
+ suggestions?: string[];
11
+ }
12
+
13
+ defineKitnElement<Props>('kitn-prompt-input', {
14
+ value: undefined,
15
+ placeholder: 'Send a message...',
16
+ disabled: false,
17
+ loading: false,
18
+ suggestions: undefined,
19
+ }, (props, { dispatch }) => {
20
+ const [internal, setInternal] = createSignal(props.value ?? '');
21
+ const current = () => props.value ?? internal();
22
+
23
+ const handleChange = (v: string) => { setInternal(v); dispatch('valuechange', { value: v }); };
24
+ const handleSubmit = () => dispatch('submit', { value: current() });
25
+ const handleSuggestionClick = (v: string) => { handleChange(v); dispatch('suggestionclick', { value: v }); };
26
+
27
+ return (
28
+ <DefaultPromptInput
29
+ value={current()}
30
+ placeholder={props.placeholder}
31
+ disabled={props.disabled}
32
+ loading={props.loading}
33
+ suggestions={props.suggestions}
34
+ onValueChange={handleChange}
35
+ onSubmit={handleSubmit}
36
+ onSuggestionClick={handleSuggestionClick}
37
+ />
38
+ );
39
+ });
@@ -0,0 +1,9 @@
1
+ // Single entry that registers all kitn custom elements. Importing this file
2
+ // (or the built bundle) defines the elements as a side effect.
3
+ import './conversation-list';
4
+ import './prompt-input';
5
+ import './chat';
6
+
7
+ export type { ChatMessage, ChatMessageAction } from './chat-types';
8
+ export { configureCodeHighlighting, isCodeHighlightingEnabled } from '../primitives/highlighter';
9
+ export type { CodeHighlightingOptions } from '../primitives/highlighter';
@@ -0,0 +1,12 @@
1
+ /* Element-bundle stylesheet. Compiled to compiled.css and injected into each
2
+ custom element's shadow root. Mirrors .storybook/styles.css but scans src/. */
3
+ @import "tailwindcss";
4
+ @import "../../theme.css";
5
+ @plugin "@tailwindcss/typography";
6
+
7
+ @source "../";
8
+
9
+ @layer base {
10
+ :host { display: block; }
11
+ * { border-color: var(--color-border); }
12
+ }
package/src/index.ts ADDED
@@ -0,0 +1,128 @@
1
+ // Shared types (folded in from @tab-zen/shared)
2
+ export type { ModelOption, SearchFilters, ConversationScope, ConversationSummary, ConversationGroup } from './types';
3
+
4
+ // Utilities
5
+ export { cn } from './utils/cn';
6
+
7
+ // Layer 1: Headless Primitives
8
+ export { useAutoResize } from './primitives/use-auto-resize';
9
+ export { useStickToBottom } from './primitives/use-stick-to-bottom';
10
+ export { useTextStream } from './primitives/use-text-stream';
11
+ export type { UseTextStreamOptions, TextStreamSegment } from './primitives/use-text-stream';
12
+ export { useVoiceRecorder } from './primitives/use-voice-recorder';
13
+ export type { UseVoiceRecorderOptions } from './primitives/use-voice-recorder';
14
+ export { ChatConfig, useChatConfig, proseClass, textClass } from './primitives/chat-config';
15
+ export type { ChatConfigValue, ProseSize, ChatConfigProps } from './primitives/chat-config';
16
+ export { configureCodeHighlighting, isCodeHighlightingEnabled } from './primitives/highlighter';
17
+ export type { CodeHighlightingOptions } from './primitives/highlighter';
18
+
19
+ // Layer 2: UI Primitives
20
+ export { Button, buttonVariants } from './ui/button';
21
+ export type { ButtonProps } from './ui/button';
22
+ export { Avatar } from './ui/avatar';
23
+ export type { AvatarProps } from './ui/avatar';
24
+ export { Tooltip } from './ui/tooltip';
25
+ export { HoverCard } from './ui/hover-card';
26
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent } from './ui/collapsible';
27
+ export { ScrollArea } from './ui/scroll-area';
28
+ export { Dropdown, DropdownTrigger, DropdownContent, DropdownItem } from './ui/dropdown';
29
+ export { Textarea } from './ui/textarea';
30
+ export type { TextareaProps } from './ui/textarea';
31
+ export { Badge } from './ui/badge';
32
+ export type { BadgeProps } from './ui/badge';
33
+ export { Separator } from './ui/separator';
34
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle } from './ui/resizable';
35
+ export type { ResizablePanelGroupProps, ResizablePanelProps, ResizableHandleProps } from './ui/resizable';
36
+ export { Skeleton } from './ui/skeleton';
37
+ export { Dialog, DialogTrigger, DialogContent } from './ui/dialog';
38
+
39
+ // Layer 3: AI/Feature Components
40
+ export {
41
+ ChatContainer, ChatContainerRoot, ChatContainerContent, ChatContainerScrollAnchor,
42
+ useChatContainer,
43
+ } from './components/chat-container';
44
+ export { Message, MessageAvatar, MessageContent, MessageActions, MessageAction, MessageCopyButton } from './components/message';
45
+ export type { MessageCopyButtonProps } from './components/message';
46
+ export { MessageSkills } from './components/message-skills';
47
+ export type { Skill as MessageSkill } from './components/message-skills';
48
+ export {
49
+ PromptInput, PromptInputTextarea, PromptInputActions, PromptInputAction,
50
+ usePromptInput,
51
+ } from './components/prompt-input';
52
+ export { SlashCommand } from './components/slash-command';
53
+ export type { SlashCommandItem, SlashCommandProps } from './components/slash-command';
54
+ export { ResponseStream } from './components/response-stream';
55
+ export { Markdown } from './components/markdown';
56
+ export { CodeBlock, CodeBlockCode, CodeBlockGroup } from './components/code-block';
57
+ export { Loader } from './components/loader';
58
+ export type { LoaderVariant, LoaderSize, LoaderProps } from './components/loader';
59
+ export {
60
+ CircularLoader, ClassicLoader, PulseLoader, PulseDotLoader,
61
+ DotsLoader, TypingLoader, WaveLoader, BarsLoader,
62
+ TerminalLoader, TextBlinkLoader, TextShimmerLoader, TextDotsLoader,
63
+ } from './components/loader';
64
+ export { FeedbackBar } from './components/feedback-bar';
65
+ export {
66
+ ChainOfThought, ChainOfThoughtStep, ChainOfThoughtTrigger,
67
+ ChainOfThoughtContent, ChainOfThoughtItem,
68
+ } from './components/chain-of-thought';
69
+ export { Source, SourceTrigger, SourceContent, SourceList } from './components/source';
70
+ export { PromptSuggestion } from './components/prompt-suggestion';
71
+ export {
72
+ Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent, emptyMediaVariants,
73
+ } from './components/empty';
74
+ export type {
75
+ EmptyProps, EmptyHeaderProps, EmptyMediaProps, EmptyTitleProps, EmptyDescriptionProps, EmptyContentProps,
76
+ } from './components/empty';
77
+ export { ScrollButton } from './components/scroll-button';
78
+ export { TextShimmer } from './components/text-shimmer';
79
+ export { Checkpoint, CheckpointIcon, CheckpointTrigger } from './components/checkpoint';
80
+ export type { CheckpointProps, CheckpointIconProps, CheckpointTriggerProps } from './components/checkpoint';
81
+ export {
82
+ Context,
83
+ ContextTrigger,
84
+ ContextContent,
85
+ ContextContentHeader,
86
+ ContextContentBody,
87
+ ContextContentFooter,
88
+ ContextInputUsage,
89
+ ContextOutputUsage,
90
+ ContextReasoningUsage,
91
+ ContextCacheUsage,
92
+ } from './components/context';
93
+ export type {
94
+ ContextProps,
95
+ ContextTriggerProps,
96
+ ContextContentProps,
97
+ ContextContentHeaderProps,
98
+ ContextContentBodyProps,
99
+ ContextContentFooterProps,
100
+ ContextUsageRowProps,
101
+ } from './components/context';
102
+ export { VoiceInput } from './components/voice-input';
103
+ export { ConversationList } from './components/conversation-list';
104
+ export { ConversationItem } from './components/conversation-item';
105
+ export { ModelSwitcher } from './components/model-switcher';
106
+ export { ChatScopePicker } from './components/chat-scope-picker';
107
+ export { Tool } from './components/tool';
108
+ export type { ToolPart, ToolProps } from './components/tool';
109
+ export { ThinkingBar } from './components/thinking-bar';
110
+ export type { ThinkingBarProps } from './components/thinking-bar';
111
+ export { Reasoning, ReasoningTrigger, ReasoningContent } from './components/reasoning';
112
+ export type { ReasoningProps, ReasoningTriggerProps, ReasoningContentProps } from './components/reasoning';
113
+ export { Image } from './components/image';
114
+ export type { ImageProps, GeneratedImageLike } from './components/image';
115
+ export { FileUpload, FileUploadTrigger, FileUploadContent } from './components/file-upload';
116
+ export type { FileUploadProps, FileUploadTriggerProps, FileUploadContentProps } from './components/file-upload';
117
+ export {
118
+ Attachments, Attachment, AttachmentPreview, AttachmentInfo, AttachmentRemove,
119
+ AttachmentHoverCard, AttachmentHoverCardTrigger, AttachmentHoverCardContent,
120
+ AttachmentEmpty, getMediaCategory, getAttachmentLabel,
121
+ useAttachmentsContext, useAttachmentContext,
122
+ } from './components/attachments';
123
+ export type {
124
+ AttachmentData, AttachmentMediaCategory, AttachmentVariant,
125
+ AttachmentsProps, AttachmentProps, AttachmentPreviewProps,
126
+ AttachmentInfoProps, AttachmentRemoveProps, AttachmentEmptyProps,
127
+ AttachmentHoverCardProps, AttachmentHoverCardTriggerProps, AttachmentHoverCardContentProps,
128
+ } from './components/attachments';
@@ -0,0 +1,76 @@
1
+ import { createContext, useContext, type Accessor, type JSX } from 'solid-js';
2
+
3
+ export type ProseSize = 'xs' | 'sm' | 'base' | 'lg';
4
+
5
+ export interface ChatConfigValue {
6
+ /** Prose/text size for messages, markdown, and UI elements */
7
+ proseSize: Accessor<ProseSize>;
8
+ /** Shiki theme for code blocks */
9
+ codeTheme: Accessor<string>;
10
+ /** Node Kobalte overlays portal into; undefined → document.body */
11
+ portalMount: Accessor<HTMLElement | undefined>;
12
+ /** Whether code blocks are syntax-highlighted; false → plain text, no Shiki loaded */
13
+ codeHighlight: Accessor<boolean>;
14
+ }
15
+
16
+ const defaultConfig: ChatConfigValue = {
17
+ proseSize: () => 'base' as ProseSize,
18
+ codeTheme: () => 'github-dark-dimmed',
19
+ portalMount: () => undefined,
20
+ codeHighlight: () => true,
21
+ };
22
+
23
+ const ChatConfigContext = createContext<ChatConfigValue>(defaultConfig);
24
+
25
+ export interface ChatConfigProps {
26
+ proseSize?: ProseSize;
27
+ codeTheme?: string;
28
+ portalMount?: HTMLElement;
29
+ codeHighlight?: boolean;
30
+ children: JSX.Element;
31
+ }
32
+
33
+ /**
34
+ * Provides chat-wide appearance settings to all child components.
35
+ * Set once at the top level — MessageContent, Markdown, CodeBlock,
36
+ * ConversationList, and PromptInput all read from this.
37
+ */
38
+ export function ChatConfig(props: ChatConfigProps) {
39
+ const value: ChatConfigValue = {
40
+ proseSize: () => props.proseSize ?? 'base',
41
+ codeTheme: () => props.codeTheme ?? 'github-dark-dimmed',
42
+ portalMount: () => props.portalMount,
43
+ codeHighlight: () => props.codeHighlight ?? true,
44
+ };
45
+
46
+ return (
47
+ <ChatConfigContext.Provider value={value}>
48
+ {props.children}
49
+ </ChatConfigContext.Provider>
50
+ );
51
+ }
52
+
53
+ /** Read the current chat config. Returns defaults if no provider is present. */
54
+ export function useChatConfig(): ChatConfigValue {
55
+ return useContext(ChatConfigContext)!;
56
+ }
57
+
58
+ /** Maps prose size to Tailwind prose class */
59
+ export function proseClass(size: ProseSize): string {
60
+ switch (size) {
61
+ case 'xs': return 'prose-sm text-xs';
62
+ case 'sm': return 'prose-sm';
63
+ case 'base': return '';
64
+ case 'lg': return 'prose-lg';
65
+ }
66
+ }
67
+
68
+ /** Maps prose size to a text class for non-prose elements (sidebar, input) */
69
+ export function textClass(size: ProseSize): string {
70
+ switch (size) {
71
+ case 'xs': return 'text-xs';
72
+ case 'sm': return 'text-sm';
73
+ case 'base': return 'text-base';
74
+ case 'lg': return 'text-lg';
75
+ }
76
+ }