@kitnai/chat 0.3.0 → 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.
- package/README.md +11 -0
- package/dist/custom-elements.json +2494 -0
- package/dist/kitn-chat.es.js +52 -39
- package/dist/llms/llms-full.txt +667 -0
- package/dist/llms/llms.txt +104 -0
- package/dist/theme.tokens.css +133 -0
- package/frameworks/react/index.tsx +530 -0
- package/frameworks/react/runtime.tsx +94 -0
- package/llms-full.txt +667 -0
- package/llms.txt +104 -0
- package/package.json +34 -5
- package/src/components/attachments.tsx +4 -2
- package/src/components/chain-of-thought.tsx +1 -1
- package/src/components/chat-scope-picker.tsx +2 -2
- package/src/components/checkpoint.tsx +7 -3
- package/src/components/context.tsx +14 -18
- package/src/components/conversation-item.tsx +1 -1
- package/src/components/conversation-list.tsx +5 -4
- package/src/components/message-skills.tsx +1 -1
- package/src/components/message.tsx +1 -0
- package/src/components/model-switcher.tsx +3 -3
- package/src/components/prompt-input.tsx +15 -2
- package/src/components/reasoning.tsx +2 -2
- package/src/components/scroll-button.tsx +1 -0
- package/src/components/slash-command.tsx +17 -8
- package/src/components/source.tsx +2 -2
- package/src/components/thinking-bar.tsx +2 -2
- package/src/components/tool.tsx +17 -6
- package/src/components/voice-input.tsx +5 -1
- package/src/elements/attachments.tsx +132 -0
- package/src/elements/chain-of-thought.tsx +45 -0
- package/src/elements/chat-scope-picker.tsx +36 -0
- package/src/elements/chat.tsx +51 -7
- package/src/elements/checkpoint.tsx +43 -0
- package/src/elements/code-block.tsx +42 -0
- package/src/elements/compiled.css +1 -1
- package/src/elements/context-meter.tsx +71 -0
- package/src/elements/conversation-list.tsx +6 -0
- package/src/elements/default-input.tsx +22 -1
- package/src/elements/define.tsx +102 -13
- package/src/elements/element-types.d.ts +404 -0
- package/src/elements/empty.tsx +29 -0
- package/src/elements/feedback-bar.tsx +33 -0
- package/src/elements/file-upload.tsx +44 -0
- package/src/elements/image.tsx +32 -0
- package/src/elements/kitn-attachments.stories.tsx +181 -0
- package/src/elements/kitn-chain-of-thought.stories.tsx +75 -0
- package/src/elements/kitn-chat-scope-picker.stories.tsx +72 -0
- package/src/elements/kitn-checkpoint.stories.tsx +71 -0
- package/src/elements/kitn-code-block.stories.tsx +82 -0
- package/src/elements/kitn-context-meter.stories.tsx +85 -0
- package/src/elements/kitn-empty.stories.tsx +110 -0
- package/src/elements/kitn-feedback-bar.stories.tsx +73 -0
- package/src/elements/kitn-file-upload.stories.tsx +81 -0
- package/src/elements/kitn-image.stories.tsx +70 -0
- package/src/elements/kitn-loader.stories.tsx +87 -0
- package/src/elements/kitn-markdown.stories.tsx +75 -0
- package/src/elements/kitn-message-skills.stories.tsx +74 -0
- package/src/elements/kitn-message.stories.tsx +105 -0
- package/src/elements/kitn-model-switcher.stories.tsx +80 -0
- package/src/elements/kitn-prompt-input.stories.tsx +74 -16
- package/src/elements/kitn-prompt-suggestions.stories.tsx +157 -0
- package/src/elements/kitn-reasoning.stories.tsx +76 -0
- package/src/elements/kitn-response-stream.stories.tsx +79 -0
- package/src/elements/kitn-source-list.stories.tsx +77 -0
- package/src/elements/kitn-source.stories.tsx +87 -0
- package/src/elements/kitn-text-shimmer.stories.tsx +63 -0
- package/src/elements/kitn-thinking-bar.stories.tsx +72 -0
- package/src/elements/kitn-tool.stories.tsx +88 -0
- package/src/elements/kitn-voice-input.stories.tsx +87 -0
- package/src/elements/loader.tsx +25 -0
- package/src/elements/markdown.tsx +38 -0
- package/src/elements/message-skills.tsx +22 -0
- package/src/elements/message.tsx +125 -0
- package/src/elements/model-switcher.tsx +35 -0
- package/src/elements/prompt-input.tsx +83 -7
- package/src/elements/prompt-suggestions.tsx +58 -0
- package/src/elements/reasoning.tsx +50 -0
- package/src/elements/register.ts +31 -0
- package/src/elements/response-stream.tsx +40 -0
- package/src/elements/source.tsx +67 -0
- package/src/elements/text-shimmer.tsx +28 -0
- package/src/elements/thinking-bar.tsx +34 -0
- package/src/elements/tool.tsx +23 -0
- package/src/elements/voice-input.tsx +41 -0
- package/src/index.ts +0 -1
- package/src/primitives/chat-config.tsx +2 -2
- package/src/stories/docs/Accessibility.mdx +119 -0
- package/src/stories/docs/ForAIAgents.mdx +93 -0
- package/src/stories/docs/GettingStarted.mdx +2 -2
- package/src/stories/docs/Installation.mdx +2 -2
- package/src/stories/docs/Integrations.mdx +415 -15
- package/src/stories/docs/Introduction.mdx +5 -5
- package/src/stories/docs/Theming.mdx +1 -1
- package/src/stories/typography.stories.tsx +78 -0
- package/src/ui/button.tsx +1 -1
- package/src/ui/collapsible.tsx +119 -8
- package/src/ui/dropdown.tsx +177 -12
- package/src/ui/hover-card.tsx +147 -26
- package/src/ui/overlay.tsx +151 -0
- package/src/ui/textarea.tsx +1 -1
- package/src/ui/tooltip.stories.tsx +1 -1
- package/src/ui/tooltip.tsx +59 -13
- package/src/utils/cn.ts +19 -1
- package/theme.css +72 -43
- package/src/ui/dialog.tsx +0 -21
|
@@ -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
|
-
|
|
35
|
-
|
|
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
|
|
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
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
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-reasoning': JSX.HTMLAttributes<HTMLElement>;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const sampleText =
|
|
16
|
+
'First I parse the request, then I plan the steps, then I execute and verify each one before responding.';
|
|
17
|
+
|
|
18
|
+
/** Render the actual `<kitn-reasoning>` custom element with a `text` property. */
|
|
19
|
+
function ReasoningElement(props: { text: string; streaming?: boolean }) {
|
|
20
|
+
let el: (HTMLElement & { text?: string; streaming?: boolean }) | undefined;
|
|
21
|
+
onMount(() => {
|
|
22
|
+
if (el) {
|
|
23
|
+
el.text = props.text;
|
|
24
|
+
if (props.streaming) el.streaming = true;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return (
|
|
28
|
+
<kitn-reasoning ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px', 'max-width': '720px' }} />
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
33
|
+
<kitn-reasoning id="reason" label="Reasoning"></kitn-reasoning>
|
|
34
|
+
|
|
35
|
+
<script type="module">
|
|
36
|
+
import '@kitnai/chat/elements'; // registers the custom elements
|
|
37
|
+
|
|
38
|
+
const reason = document.getElementById('reason');
|
|
39
|
+
reason.text = 'First I parse the request, then I plan the steps, then I execute.';
|
|
40
|
+
// reason.streaming = true; // auto-expands while a thought streams in
|
|
41
|
+
|
|
42
|
+
// events are CustomEvents on the element (they do not bubble)
|
|
43
|
+
reason.addEventListener('openchange', (e) => console.log('open:', e.detail.open));
|
|
44
|
+
</script>`;
|
|
45
|
+
|
|
46
|
+
const meta = {
|
|
47
|
+
title: 'Web Components/kitn-reasoning',
|
|
48
|
+
tags: ['autodocs'],
|
|
49
|
+
parameters: {
|
|
50
|
+
layout: 'fullscreen',
|
|
51
|
+
docs: {
|
|
52
|
+
description: {
|
|
53
|
+
component: [
|
|
54
|
+
'`<kitn-reasoning>` is the framework-agnostic **web component** for a collapsible reasoning/thinking block that auto-expands while a thought is `streaming`, isolated in **Shadow DOM**.',
|
|
55
|
+
'**When to use:** surfacing model chain-of-thought in a non-Solid app. In SolidJS, compose the `Reasoning` primitives directly.',
|
|
56
|
+
"**How to use:** register once with `import '@kitnai/chat/elements'`, set the body via the `text` **property**, set the `streaming` flag while it streams in, optionally drive the controlled `open` property, and listen for the `openchange` **CustomEvent**.",
|
|
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
|
+
/** A collapsed reasoning block (the trigger toggles it). */
|
|
68
|
+
export const Default: Story = {
|
|
69
|
+
render: () => <ReasoningElement text={sampleText} />,
|
|
70
|
+
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** With `streaming` set, the block auto-expands. */
|
|
74
|
+
export const Streaming: Story = {
|
|
75
|
+
render: () => <ReasoningElement text={sampleText} streaming />,
|
|
76
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
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-response-stream': JSX.HTMLAttributes<HTMLElement> & {
|
|
11
|
+
mode?: string;
|
|
12
|
+
speed?: number;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const STREAM_TEXT =
|
|
19
|
+
"This text reveals with a typewriter animation, streamed character by character — exactly how you'd render a live assistant reply.";
|
|
20
|
+
|
|
21
|
+
/** Render `<kitn-response-stream>` with the `text` set as a JS property. */
|
|
22
|
+
function StreamElement(props: { text: string; mode?: string; speed?: number }) {
|
|
23
|
+
let el: (HTMLElement & { text?: string }) | undefined;
|
|
24
|
+
onMount(() => {
|
|
25
|
+
if (el) el.text = props.text;
|
|
26
|
+
});
|
|
27
|
+
return (
|
|
28
|
+
<kitn-response-stream
|
|
29
|
+
ref={(e) => (el = e as HTMLElement)}
|
|
30
|
+
mode={props.mode}
|
|
31
|
+
speed={props.speed}
|
|
32
|
+
style={{ display: 'block', padding: '24px', 'max-width': '640px', 'line-height': 1.6 }}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
38
|
+
<kitn-response-stream id="stream" mode="typewriter" speed="20"></kitn-response-stream>
|
|
39
|
+
|
|
40
|
+
<script type="module">
|
|
41
|
+
import '@kitnai/chat/elements'; // registers the custom elements
|
|
42
|
+
|
|
43
|
+
const stream = document.getElementById('stream');
|
|
44
|
+
// text can be a string, or an AsyncIterable<string> for live streaming
|
|
45
|
+
stream.text = "Hello, this reveals one character at a time…";
|
|
46
|
+
stream.addEventListener('complete', () => console.log('done'));
|
|
47
|
+
</script>`;
|
|
48
|
+
|
|
49
|
+
const meta = {
|
|
50
|
+
title: 'Web Components/kitn-response-stream',
|
|
51
|
+
tags: ['autodocs'],
|
|
52
|
+
parameters: {
|
|
53
|
+
layout: 'fullscreen',
|
|
54
|
+
docs: {
|
|
55
|
+
description: {
|
|
56
|
+
component: [
|
|
57
|
+
'`<kitn-response-stream>` is the framework-agnostic **web component** that reveals text with a typewriter or fade animation — the building block for streamed assistant replies, isolated in **Shadow DOM**.',
|
|
58
|
+
'**When to use:** animating a response as it arrives. Pass a finished string to replay an animation, or an `AsyncIterable<string>` to drive it from a live stream. In SolidJS, use the `ResponseStream` primitive.',
|
|
59
|
+
"**How to use:** register once with `import '@kitnai/chat/elements'`, set the `text` **property** (string or async iterable), tune `mode` (`typewriter` / `fade`) and `speed`, and listen for the `complete` **CustomEvent**.",
|
|
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
|
+
/** Typewriter reveal (the default). */
|
|
71
|
+
export const Typewriter: Story = {
|
|
72
|
+
render: () => <StreamElement text={STREAM_TEXT} mode="typewriter" speed={20} />,
|
|
73
|
+
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Fade-in reveal, segment by segment. */
|
|
77
|
+
export const Fade: Story = {
|
|
78
|
+
render: () => <StreamElement text={STREAM_TEXT} mode="fade" speed={10} />,
|
|
79
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
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-source-list': JSX.HTMLAttributes<HTMLElement>;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SourceItem {
|
|
16
|
+
href: string;
|
|
17
|
+
title?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
label?: string;
|
|
20
|
+
showFavicon?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const sampleSources: SourceItem[] = [
|
|
24
|
+
{ href: 'https://kitn.dev', title: 'kitn — the kit', description: 'Composable SolidJS + web-component chat UI.', showFavicon: true },
|
|
25
|
+
{ href: 'https://solidjs.com', title: 'SolidJS', description: 'A reactive UI library.', showFavicon: true },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** Render the actual `<kitn-source-list>` custom element with a `sources` property. */
|
|
29
|
+
function SourceListElement() {
|
|
30
|
+
let el: (HTMLElement & { sources?: SourceItem[] }) | undefined;
|
|
31
|
+
onMount(() => {
|
|
32
|
+
if (el) el.sources = sampleSources;
|
|
33
|
+
});
|
|
34
|
+
return (
|
|
35
|
+
<kitn-source-list 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-source-list id="srcs" show-favicon></kitn-source-list>
|
|
41
|
+
|
|
42
|
+
<script type="module">
|
|
43
|
+
import '@kitnai/chat/elements'; // registers the custom elements
|
|
44
|
+
|
|
45
|
+
const srcs = document.getElementById('srcs');
|
|
46
|
+
srcs.sources = [
|
|
47
|
+
{ href: 'https://kitn.dev', title: 'kitn — the kit', description: 'Composable chat UI.' },
|
|
48
|
+
{ href: 'https://solidjs.com', title: 'SolidJS', description: 'A reactive UI library.' },
|
|
49
|
+
];
|
|
50
|
+
</script>`;
|
|
51
|
+
|
|
52
|
+
const meta = {
|
|
53
|
+
title: 'Web Components/kitn-source-list',
|
|
54
|
+
tags: ['autodocs'],
|
|
55
|
+
parameters: {
|
|
56
|
+
layout: 'fullscreen',
|
|
57
|
+
docs: {
|
|
58
|
+
description: {
|
|
59
|
+
component: [
|
|
60
|
+
'`<kitn-source-list>` is the framework-agnostic **web component** for a wrapped row of citation links (each with its own hover-card preview), isolated in **Shadow DOM**.',
|
|
61
|
+
'**When to use:** showing the sources behind an assistant answer in a non-Solid app. For a single citation, use `<kitn-source>`; in SolidJS, compose `SourceList` + `Source`.',
|
|
62
|
+
"**How to use:** register once with `import '@kitnai/chat/elements'`, set the data via the `sources` **property** (each item: `href`, `title`, `description`, `label`, `showFavicon`), and set `show-favicon` to enable favicons for all items (a per-item `showFavicon` overrides it).",
|
|
63
|
+
'See the **Code** tab for HTML usage.',
|
|
64
|
+
].join('\n\n'),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
} satisfies Meta;
|
|
69
|
+
|
|
70
|
+
export default meta;
|
|
71
|
+
type Story = StoryObj;
|
|
72
|
+
|
|
73
|
+
/** Two citations rendered as a wrapped list with favicons. */
|
|
74
|
+
export const Default: Story = {
|
|
75
|
+
render: () => <SourceListElement />,
|
|
76
|
+
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
77
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
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-source': JSX.HTMLAttributes<HTMLElement>;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Render the actual `<kitn-source>` custom element configured by attributes. */
|
|
16
|
+
function SourceElement(props: {
|
|
17
|
+
href: string;
|
|
18
|
+
label?: string;
|
|
19
|
+
headline?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
showFavicon?: boolean;
|
|
22
|
+
}) {
|
|
23
|
+
let el: HTMLElement | undefined;
|
|
24
|
+
onMount(() => {
|
|
25
|
+
if (!el) return;
|
|
26
|
+
el.setAttribute('href', props.href);
|
|
27
|
+
if (props.label) el.setAttribute('label', props.label);
|
|
28
|
+
if (props.headline) el.setAttribute('headline', props.headline);
|
|
29
|
+
if (props.description) el.setAttribute('description', props.description);
|
|
30
|
+
if (props.showFavicon) el.setAttribute('show-favicon', '');
|
|
31
|
+
});
|
|
32
|
+
return <kitn-source ref={(e) => (el = e as HTMLElement)} style={{ display: 'inline-block', padding: '16px' }} />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
36
|
+
<kitn-source
|
|
37
|
+
href="https://kitn.dev"
|
|
38
|
+
label="kitn"
|
|
39
|
+
headline="kitn — the kit"
|
|
40
|
+
description="Composable SolidJS + web-component chat UI."
|
|
41
|
+
show-favicon
|
|
42
|
+
></kitn-source>
|
|
43
|
+
|
|
44
|
+
<script type="module">
|
|
45
|
+
import '@kitnai/chat/elements'; // registers the custom elements
|
|
46
|
+
</script>`;
|
|
47
|
+
|
|
48
|
+
const meta = {
|
|
49
|
+
title: 'Web Components/kitn-source',
|
|
50
|
+
tags: ['autodocs'],
|
|
51
|
+
parameters: {
|
|
52
|
+
layout: 'fullscreen',
|
|
53
|
+
docs: {
|
|
54
|
+
description: {
|
|
55
|
+
component: [
|
|
56
|
+
'`<kitn-source>` is the framework-agnostic **web component** for a single citation link with a hover-card preview, isolated in **Shadow DOM**.',
|
|
57
|
+
'**When to use:** inlining a single source citation in a non-Solid app. For multiple sources, use `<kitn-source-list>`; in SolidJS, compose the `Source` primitives.',
|
|
58
|
+
"**How to use:** register once with `import '@kitnai/chat/elements'`, then set `href` (the link, also the default label/favicon source), `label`, `headline` (the hover headline — note `headline`, not `title`), `description`, and the `show-favicon` flag via attributes.",
|
|
59
|
+
'See the **Code** tab for HTML usage.',
|
|
60
|
+
].join('\n\n'),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
} satisfies Meta;
|
|
65
|
+
|
|
66
|
+
export default meta;
|
|
67
|
+
type Story = StoryObj;
|
|
68
|
+
|
|
69
|
+
/** A citation with a custom label, hover headline/description, and favicon. */
|
|
70
|
+
export const Default: Story = {
|
|
71
|
+
render: () => (
|
|
72
|
+
<SourceElement
|
|
73
|
+
href="https://kitn.dev"
|
|
74
|
+
label="kitn"
|
|
75
|
+
headline="kitn — the kit"
|
|
76
|
+
description="Composable SolidJS + web-component chat UI."
|
|
77
|
+
showFavicon
|
|
78
|
+
/>
|
|
79
|
+
),
|
|
80
|
+
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** With no `label`, the trigger falls back to the domain. */
|
|
84
|
+
export const DomainLabel: Story = {
|
|
85
|
+
name: 'Domain Label',
|
|
86
|
+
render: () => <SourceElement href="https://solidjs.com" description="A reactive UI library." />,
|
|
87
|
+
};
|