@kitnai/chat 0.3.1 → 0.5.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 (119) hide show
  1. package/README.md +35 -5
  2. package/dist/custom-elements.json +2969 -0
  3. package/dist/kitn-chat.es.js +52 -39
  4. package/dist/llms/llms-full.txt +718 -0
  5. package/dist/llms/llms.txt +104 -0
  6. package/dist/theme.tokens.css +137 -0
  7. package/frameworks/react/index.tsx +584 -0
  8. package/frameworks/react/runtime.tsx +94 -0
  9. package/llms-full.txt +718 -0
  10. package/llms.txt +104 -0
  11. package/package.json +53 -6
  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/chat-thread.tsx +217 -0
  16. package/src/components/checkpoint.tsx +7 -3
  17. package/src/components/context.tsx +14 -18
  18. package/src/components/conversation-item.tsx +1 -1
  19. package/src/components/conversation-list.tsx +5 -4
  20. package/src/components/message-skills.tsx +1 -1
  21. package/src/components/message.tsx +1 -0
  22. package/src/components/model-switcher.tsx +3 -3
  23. package/src/components/prompt-input.tsx +20 -2
  24. package/src/components/reasoning.tsx +2 -2
  25. package/src/components/scroll-button.tsx +1 -0
  26. package/src/components/slash-command.tsx +17 -8
  27. package/src/components/source.tsx +2 -2
  28. package/src/components/thinking-bar.tsx +2 -2
  29. package/src/components/tool.tsx +17 -6
  30. package/src/components/voice-input.tsx +5 -1
  31. package/src/elements/attachments.tsx +132 -0
  32. package/src/elements/chain-of-thought.tsx +45 -0
  33. package/src/elements/chat-scope-picker.tsx +36 -0
  34. package/src/elements/chat-workspace.tsx +122 -0
  35. package/src/elements/chat.tsx +31 -228
  36. package/src/elements/checkpoint.tsx +43 -0
  37. package/src/elements/code-block.tsx +42 -0
  38. package/src/elements/compiled.css +1 -1
  39. package/src/elements/context-meter.tsx +71 -0
  40. package/src/elements/conversation-list.tsx +6 -0
  41. package/src/elements/default-input.tsx +22 -1
  42. package/src/elements/define.tsx +98 -12
  43. package/src/elements/element-types.d.ts +444 -0
  44. package/src/elements/empty.tsx +29 -0
  45. package/src/elements/feedback-bar.tsx +33 -0
  46. package/src/elements/file-upload.tsx +44 -0
  47. package/src/elements/image.tsx +32 -0
  48. package/src/elements/kitn-attachments.stories.tsx +181 -0
  49. package/src/elements/kitn-chain-of-thought.stories.tsx +75 -0
  50. package/src/elements/kitn-chat-scope-picker.stories.tsx +72 -0
  51. package/src/elements/kitn-chat-workspace.stories.tsx +195 -0
  52. package/src/elements/kitn-checkpoint.stories.tsx +71 -0
  53. package/src/elements/kitn-code-block.stories.tsx +82 -0
  54. package/src/elements/kitn-context-meter.stories.tsx +85 -0
  55. package/src/elements/kitn-empty.stories.tsx +110 -0
  56. package/src/elements/kitn-feedback-bar.stories.tsx +73 -0
  57. package/src/elements/kitn-file-upload.stories.tsx +81 -0
  58. package/src/elements/kitn-image.stories.tsx +70 -0
  59. package/src/elements/kitn-loader.stories.tsx +87 -0
  60. package/src/elements/kitn-markdown.stories.tsx +75 -0
  61. package/src/elements/kitn-message-skills.stories.tsx +74 -0
  62. package/src/elements/kitn-message.stories.tsx +105 -0
  63. package/src/elements/kitn-model-switcher.stories.tsx +80 -0
  64. package/src/elements/kitn-prompt-input.stories.tsx +74 -16
  65. package/src/elements/kitn-prompt-suggestions.stories.tsx +157 -0
  66. package/src/elements/kitn-reasoning.stories.tsx +76 -0
  67. package/src/elements/kitn-response-stream.stories.tsx +79 -0
  68. package/src/elements/kitn-source-list.stories.tsx +77 -0
  69. package/src/elements/kitn-source.stories.tsx +87 -0
  70. package/src/elements/kitn-text-shimmer.stories.tsx +63 -0
  71. package/src/elements/kitn-thinking-bar.stories.tsx +72 -0
  72. package/src/elements/kitn-tool.stories.tsx +88 -0
  73. package/src/elements/kitn-voice-input.stories.tsx +87 -0
  74. package/src/elements/loader.tsx +25 -0
  75. package/src/elements/markdown.tsx +38 -0
  76. package/src/elements/message-skills.tsx +22 -0
  77. package/src/elements/message.tsx +125 -0
  78. package/src/elements/model-switcher.tsx +35 -0
  79. package/src/elements/prompt-input.tsx +83 -7
  80. package/src/elements/prompt-suggestions.tsx +58 -0
  81. package/src/elements/reasoning.tsx +50 -0
  82. package/src/elements/register.ts +32 -0
  83. package/src/elements/response-stream.tsx +40 -0
  84. package/src/elements/source.tsx +67 -0
  85. package/src/elements/styles.css +14 -0
  86. package/src/elements/text-shimmer.tsx +28 -0
  87. package/src/elements/thinking-bar.tsx +34 -0
  88. package/src/elements/tool.tsx +23 -0
  89. package/src/elements/voice-input.tsx +41 -0
  90. package/src/index.ts +0 -1
  91. package/src/primitives/chat-config.tsx +3 -3
  92. package/src/stories/docs/Accessibility.mdx +119 -0
  93. package/src/stories/docs/ForAIAgents.mdx +93 -0
  94. package/src/stories/docs/GettingStarted.mdx +2 -2
  95. package/src/stories/docs/Installation.mdx +29 -2
  96. package/src/stories/docs/Integrations.mdx +417 -15
  97. package/src/stories/docs/Introduction.mdx +17 -8
  98. package/src/stories/docs/Theming.mdx +1 -1
  99. package/src/stories/pattern-centered-conversation.stories.tsx +93 -0
  100. package/src/stories/pattern-docked-widget.stories.tsx +93 -0
  101. package/src/stories/pattern-empty-state.stories.tsx +76 -0
  102. package/src/stories/typography.stories.tsx +78 -0
  103. package/src/ui/button.tsx +1 -1
  104. package/src/ui/collapsible.stories.tsx +70 -0
  105. package/src/ui/collapsible.tsx +119 -8
  106. package/src/ui/dropdown.stories.tsx +60 -0
  107. package/src/ui/dropdown.tsx +177 -12
  108. package/src/ui/hover-card.stories.tsx +78 -0
  109. package/src/ui/hover-card.tsx +147 -26
  110. package/src/ui/overlay.stories.tsx +115 -0
  111. package/src/ui/overlay.tsx +151 -0
  112. package/src/ui/scroll-area.stories.tsx +51 -0
  113. package/src/ui/textarea.stories.tsx +77 -0
  114. package/src/ui/textarea.tsx +1 -1
  115. package/src/ui/tooltip.stories.tsx +1 -1
  116. package/src/ui/tooltip.tsx +59 -13
  117. package/src/utils/cn.ts +19 -1
  118. package/theme.css +76 -43
  119. package/src/ui/dialog.tsx +0 -21
@@ -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
+ };
@@ -0,0 +1,80 @@
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 { ModelOption } 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-model-switcher': JSX.HTMLAttributes<HTMLElement>;
12
+ }
13
+ }
14
+ }
15
+
16
+ const models: ModelOption[] = [
17
+ { id: 'opus', name: 'Claude Opus', provider: 'Anthropic' },
18
+ { id: 'sonnet', name: 'Claude Sonnet', provider: 'Anthropic' },
19
+ { id: 'haiku', name: 'Claude Haiku', provider: 'Anthropic' },
20
+ ];
21
+
22
+ /** Render `<kitn-model-switcher>` with `models` set as a property; tracks selection. */
23
+ function SwitcherElement(props: { models: ModelOption[]; current?: string }) {
24
+ let el: (HTMLElement & { models?: ModelOption[]; currentModel?: string }) | undefined;
25
+ onMount(() => {
26
+ if (!el) return;
27
+ el.models = props.models;
28
+ el.currentModel = props.current ?? props.models[0]?.id;
29
+ // reflect the selection back so the trigger label updates
30
+ el.addEventListener('modelchange', (e) => {
31
+ const ev = e as CustomEvent<{ modelId: string }>;
32
+ el!.currentModel = ev.detail.modelId;
33
+ });
34
+ });
35
+ return (
36
+ <kitn-model-switcher ref={(e) => (el = e as HTMLElement)} style={{ display: 'inline-block', padding: '40px' }} />
37
+ );
38
+ }
39
+
40
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
41
+ <kitn-model-switcher id="ms"></kitn-model-switcher>
42
+
43
+ <script type="module">
44
+ import '@kitnai/chat/elements'; // registers the custom elements
45
+
46
+ const ms = document.getElementById('ms');
47
+ ms.models = [
48
+ { id: 'opus', name: 'Claude Opus', provider: 'Anthropic' },
49
+ { id: 'sonnet', name: 'Claude Sonnet', provider: 'Anthropic' },
50
+ ];
51
+ ms.currentModel = 'opus';
52
+ ms.addEventListener('modelchange', (e) => { ms.currentModel = e.detail.modelId; });
53
+ </script>`;
54
+
55
+ const meta = {
56
+ title: 'Web Components/kitn-model-switcher',
57
+ tags: ['autodocs'],
58
+ parameters: {
59
+ layout: 'fullscreen',
60
+ docs: {
61
+ description: {
62
+ component: [
63
+ '`<kitn-model-switcher>` is the framework-agnostic **web component** for picking the active model — a dropdown showing each model\'s name and provider — isolated in **Shadow DOM**. It mirrors the switcher inside `<kitn-chat>` as a standalone, composable piece.',
64
+ '**When to use:** building your own chat header and want the model picker on its own. In SolidJS, use the `ModelSwitcher` primitive.',
65
+ "**How to use:** register once with `import '@kitnai/chat/elements'`, set the `models` **property** (and optionally `currentModel`), and listen for the `modelchange` **CustomEvent**. Note: like the underlying primitive, it only renders when more than one model is provided.",
66
+ 'See the **Code** tab for HTML usage.',
67
+ ].join('\n\n'),
68
+ },
69
+ },
70
+ },
71
+ } satisfies Meta;
72
+
73
+ export default meta;
74
+ type Story = StoryObj;
75
+
76
+ /** A three-model picker; selecting updates the trigger label. */
77
+ export const Default: Story = {
78
+ render: () => <SwitcherElement models={models} current="opus" />,
79
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
80
+ };
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { onMount } from 'solid-js';
3
3
  import './register'; // side effect: registers <kitn-chat>, <kitn-conversation-list>, <kitn-prompt-input>
4
+ import type { AttachmentData } from '../components/attachments';
4
5
 
5
6
  // The web components are custom DOM elements, so declare the tags for JSX.
6
7
  declare module 'solid-js' {
@@ -18,26 +19,43 @@ const sampleSuggestions: string[] = [
18
19
  'Explain like I am five',
19
20
  ];
20
21
 
22
+ function imgData(fill: string, glyph: string) {
23
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96"><rect width="96" height="96" rx="12" fill="${fill}"/><text x="48" y="60" font-size="42" text-anchor="middle" fill="white">${glyph}</text></svg>`;
24
+ return 'data:image/svg+xml;utf8,' + encodeURIComponent(svg);
25
+ }
26
+
27
+ const sampleAttachments: AttachmentData[] = [
28
+ { id: 'a1', type: 'file', filename: 'architecture.png', mediaType: 'image/png', url: imgData('#7c3aed', '◆') },
29
+ { id: 'a2', type: 'file', filename: 'spec.pdf', mediaType: 'application/pdf' },
30
+ ];
31
+
32
+ interface PromptInputEl extends HTMLElement {
33
+ value?: string;
34
+ placeholder?: string;
35
+ disabled?: boolean;
36
+ loading?: boolean;
37
+ suggestions?: string[];
38
+ search?: boolean;
39
+ voice?: boolean;
40
+ attachments?: AttachmentData[];
41
+ }
42
+
21
43
  /** 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;
44
+ function PromptInputElement(props?: { search?: boolean; voice?: boolean; attachments?: AttachmentData[] }) {
45
+ let el: PromptInputEl | undefined;
32
46
  onMount(() => {
33
- if (el) {
34
- el.placeholder = 'Ask anything...';
35
- el.suggestions = sampleSuggestions;
36
- }
47
+ if (!el) return;
48
+ el.placeholder = 'Ask anything...';
49
+ el.suggestions = sampleSuggestions;
50
+ if (props?.search) el.setAttribute('search', '');
51
+ if (props?.voice) el.setAttribute('voice', '');
52
+ if (props?.attachments) el.attachments = props.attachments;
53
+ el.addEventListener('search', () => console.log('search clicked'));
54
+ el.addEventListener('voice', () => console.log('voice clicked'));
37
55
  });
38
56
  return (
39
57
  <kitn-prompt-input
40
- ref={(e) => (el = e as HTMLElement)}
58
+ ref={(e) => (el = e as PromptInputEl)}
41
59
  style={{ display: 'block', width: '100%', padding: '16px' }}
42
60
  />
43
61
  );
@@ -97,7 +115,7 @@ const meta = {
97
115
  component: [
98
116
  '`<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
117
  '**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.',
118
+ '**How to use:** register once with `import \'@kitnai/chat/elements\'`, configure it with JS **properties** (`placeholder`, `value`, `disabled`, `loading`, `suggestions`, `attachments`) and flag attributes (`search`, `voice` to show the Globe/Mic toolbar buttons), and listen for **CustomEvents** (`submit`, `valuechange`, `suggestionclick`, `search`, `voice`) directly on the element. Leave `value` unset to let the element manage its own input state; seed `attachments` to pre-populate staged files.',
101
119
  '**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
120
  'See the **Code** tab below for the HTML usage; the *SolidJS* story shows the same element inside a Solid component.',
103
121
  ].join('\n\n'),
@@ -121,3 +139,43 @@ export const InSolidJS: Story = {
121
139
  render: () => <PromptInputElement />,
122
140
  parameters: { docs: { source: { code: SOLID_SNIPPET, language: 'tsx' } } },
123
141
  };
142
+
143
+ const TOOLBAR_SNIPPET = `<!-- show the Search (Globe) + Voice (Mic) toolbar buttons -->
144
+ <kitn-prompt-input id="input" search voice></kitn-prompt-input>
145
+
146
+ <script type="module">
147
+ import '@kitnai/chat/elements';
148
+ const input = document.getElementById('input');
149
+ input.addEventListener('search', () => console.log('search clicked'));
150
+ input.addEventListener('voice', () => console.log('voice clicked'));
151
+ </script>`;
152
+
153
+ /** With the **microphone** (and search) toolbar buttons enabled via the `voice`
154
+ * and `search` flags. Clicking them fires `voice` / `search` CustomEvents. */
155
+ export const WithVoiceAndSearch: Story = {
156
+ name: 'With Voice & Search',
157
+ render: () => <PromptInputElement search voice />,
158
+ parameters: { docs: { source: { code: TOOLBAR_SNIPPET, language: 'html' } } },
159
+ };
160
+
161
+ const ATTACHMENTS_SNIPPET = `<!-- seed staged attachments without an upload -->
162
+ <kitn-prompt-input id="input" voice></kitn-prompt-input>
163
+
164
+ <script type="module">
165
+ import '@kitnai/chat/elements';
166
+ const input = document.getElementById('input');
167
+ input.attachments = [
168
+ { id: 'a1', type: 'file', filename: 'architecture.png',
169
+ mediaType: 'image/png', url: 'data:image/svg+xml;utf8,...' },
170
+ { id: 'a2', type: 'file', filename: 'spec.pdf', mediaType: 'application/pdf' },
171
+ ];
172
+ </script>`;
173
+
174
+ /** Pre-populated with a couple of **attachments** (an image + a file) via the
175
+ * `attachments` property, with the mic shown too. The paperclip still adds
176
+ * more, and each chip can be removed. */
177
+ export const WithAttachments: Story = {
178
+ name: 'With Attachments',
179
+ render: () => <PromptInputElement voice attachments={sampleAttachments} />,
180
+ parameters: { docs: { source: { code: ATTACHMENTS_SNIPPET, language: 'html' } } },
181
+ };
@@ -0,0 +1,157 @@
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
+ type Item = string | { label: string; value?: string };
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
+ 'kitn-prompt-suggestions': JSX.HTMLAttributes<HTMLElement> & {
13
+ variant?: string;
14
+ size?: string;
15
+ block?: boolean | string;
16
+ highlight?: string;
17
+ };
18
+ }
19
+ }
20
+ }
21
+
22
+ const suggestions: Item[] = [
23
+ 'Explain the architecture',
24
+ 'Show me a code example',
25
+ "What's deferred?",
26
+ ];
27
+
28
+ /** Render `<kitn-prompt-suggestions>` with `suggestions` set as a property. */
29
+ function SuggestionsElement(props: { suggestions: Item[]; variant?: string; size?: string; block?: boolean; highlight?: string }) {
30
+ let el: (HTMLElement & { suggestions?: Item[] }) | undefined;
31
+ onMount(() => {
32
+ if (!el) return;
33
+ el.suggestions = props.suggestions;
34
+ el.addEventListener('select', (e) => {
35
+ const ev = e as CustomEvent<{ value: string }>;
36
+ console.log('select', ev.detail.value);
37
+ });
38
+ });
39
+ return (
40
+ <kitn-prompt-suggestions
41
+ ref={(e) => (el = e as HTMLElement)}
42
+ variant={props.variant}
43
+ size={props.size}
44
+ block={props.block ? true : undefined}
45
+ highlight={props.highlight}
46
+ style={{ display: 'block', padding: '24px', 'max-width': '560px' }}
47
+ />
48
+ );
49
+ }
50
+
51
+ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
52
+ <kitn-prompt-suggestions id="suggs" variant="outline"></kitn-prompt-suggestions>
53
+
54
+ <script type="module">
55
+ import '@kitnai/chat/elements'; // registers the custom elements
56
+
57
+ const suggs = document.getElementById('suggs');
58
+ suggs.suggestions = ['Explain the architecture', 'Show me a code example'];
59
+ suggs.addEventListener('select', (e) => console.log(e.detail.value));
60
+ </script>`;
61
+
62
+ const meta = {
63
+ title: 'Web Components/kitn-prompt-suggestions',
64
+ tags: ['autodocs'],
65
+ parameters: {
66
+ layout: 'fullscreen',
67
+ docs: {
68
+ description: {
69
+ component: [
70
+ '`<kitn-prompt-suggestions>` is the framework-agnostic **web component** for a row (or list) of clickable suggestion chips — starter prompts or follow-ups — isolated in **Shadow DOM**.',
71
+ '**When to use:** offering the user quick prompts to click instead of type, usually above an input. In SolidJS, use the `PromptSuggestion` primitive.',
72
+ "**How to use:** register once with `import '@kitnai/chat/elements'`, set the `suggestions` **property** (strings, or `{ label, value }` when the displayed text differs from the emitted value), choose a `variant` and `size` (`sm` | `md` | `lg`; pills default to `lg`), optionally add the `block` flag for full-width rows or a `highlight` substring to emphasize, and listen for the `select` **CustomEvent**.",
73
+ 'See the **Code** tab for HTML usage.',
74
+ ].join('\n\n'),
75
+ },
76
+ },
77
+ },
78
+ } satisfies Meta;
79
+
80
+ export default meta;
81
+ type Story = StoryObj;
82
+
83
+ /** Default outline pills, wrapping in a row. */
84
+ export const Default: Story = {
85
+ render: () => <SuggestionsElement suggestions={suggestions} variant="outline" />,
86
+ parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
87
+ };
88
+
89
+ /** Ghost variant. */
90
+ export const Ghost: Story = {
91
+ render: () => <SuggestionsElement suggestions={suggestions} variant="ghost" />,
92
+ };
93
+
94
+ /** Full-width left-aligned rows via the `block` flag. */
95
+ export const Block: Story = {
96
+ render: () => <SuggestionsElement suggestions={suggestions} variant="outline" block />,
97
+ };
98
+
99
+ const searchSuggestions: Item[] = [
100
+ 'How does SolidJS handle reactivity?',
101
+ 'What makes SolidJS fast?',
102
+ 'SolidJS vs Svelte comparison',
103
+ ];
104
+
105
+ /** Filtered-search list: each row highlights the matched substring via the
106
+ * `highlight` attribute (which forces the list-row layout). */
107
+ export const WithHighlightedSearch: Story = {
108
+ name: 'With Highlighted Search',
109
+ render: () => (
110
+ <SuggestionsElement suggestions={searchSuggestions} highlight="Solid" />
111
+ ),
112
+ parameters: {
113
+ docs: {
114
+ source: {
115
+ code: `<kitn-prompt-suggestions id="suggs" highlight="Solid"></kitn-prompt-suggestions>
116
+
117
+ <script type="module">
118
+ import '@kitnai/chat/elements';
119
+ const suggs = document.getElementById('suggs');
120
+ suggs.suggestions = [
121
+ 'How does SolidJS handle reactivity?',
122
+ 'What makes SolidJS fast?',
123
+ 'SolidJS vs Svelte comparison',
124
+ ];
125
+ </script>`,
126
+ language: 'html',
127
+ },
128
+ },
129
+ },
130
+ };
131
+
132
+ /** Sizes side by side — the default pill (`lg`) vs the smaller `sm` pill. */
133
+ export const Sizes: Story = {
134
+ render: () => (
135
+ <div style={{ display: 'flex', 'flex-direction': 'column', gap: '8px', padding: '24px' }}>
136
+ <div>
137
+ <div style={{ 'font-size': '12px', opacity: '0.6', margin: '0 0 4px' }}>Default (lg)</div>
138
+ <SuggestionsElement suggestions={suggestions} variant="outline" />
139
+ </div>
140
+ <div>
141
+ <div style={{ 'font-size': '12px', opacity: '0.6', margin: '0 0 4px' }}>Small (sm)</div>
142
+ <SuggestionsElement suggestions={suggestions} variant="outline" size="sm" />
143
+ </div>
144
+ </div>
145
+ ),
146
+ parameters: {
147
+ docs: {
148
+ source: {
149
+ code: `<!-- default pill -->
150
+ <kitn-prompt-suggestions variant="outline"></kitn-prompt-suggestions>
151
+ <!-- smaller pill -->
152
+ <kitn-prompt-suggestions variant="outline" size="sm"></kitn-prompt-suggestions>`,
153
+ language: 'html',
154
+ },
155
+ },
156
+ },
157
+ };