@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.
- package/README.md +35 -5
- package/dist/custom-elements.json +2969 -0
- package/dist/kitn-chat.es.js +52 -39
- package/dist/llms/llms-full.txt +718 -0
- package/dist/llms/llms.txt +104 -0
- package/dist/theme.tokens.css +137 -0
- package/frameworks/react/index.tsx +584 -0
- package/frameworks/react/runtime.tsx +94 -0
- package/llms-full.txt +718 -0
- package/llms.txt +104 -0
- package/package.json +53 -6
- 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/chat-thread.tsx +217 -0
- 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 +20 -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-workspace.tsx +122 -0
- package/src/elements/chat.tsx +31 -228
- 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 +98 -12
- package/src/elements/element-types.d.ts +444 -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-chat-workspace.stories.tsx +195 -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 +32 -0
- package/src/elements/response-stream.tsx +40 -0
- package/src/elements/source.tsx +67 -0
- package/src/elements/styles.css +14 -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 +3 -3
- 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 +29 -2
- package/src/stories/docs/Integrations.mdx +417 -15
- package/src/stories/docs/Introduction.mdx +17 -8
- package/src/stories/docs/Theming.mdx +1 -1
- package/src/stories/pattern-centered-conversation.stories.tsx +93 -0
- package/src/stories/pattern-docked-widget.stories.tsx +93 -0
- package/src/stories/pattern-empty-state.stories.tsx +76 -0
- package/src/stories/typography.stories.tsx +78 -0
- package/src/ui/button.tsx +1 -1
- package/src/ui/collapsible.stories.tsx +70 -0
- package/src/ui/collapsible.tsx +119 -8
- package/src/ui/dropdown.stories.tsx +60 -0
- package/src/ui/dropdown.tsx +177 -12
- package/src/ui/hover-card.stories.tsx +78 -0
- package/src/ui/hover-card.tsx +147 -26
- package/src/ui/overlay.stories.tsx +115 -0
- package/src/ui/overlay.tsx +151 -0
- package/src/ui/scroll-area.stories.tsx +51 -0
- package/src/ui/textarea.stories.tsx +77 -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 +76 -43
- package/src/ui/dialog.tsx +0 -21
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineKitnElement } from './define';
|
|
2
|
+
import { FeedbackBar } from '../components/feedback-bar';
|
|
3
|
+
|
|
4
|
+
interface Props extends Record<string, unknown> {
|
|
5
|
+
/** The banner label (e.g. "Was this helpful?"). Attribute: `bar-title`
|
|
6
|
+
* (`title` is avoided — it's a global HTML attribute). */
|
|
7
|
+
barTitle?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Events fired by `<kitn-feedback-bar>`. */
|
|
11
|
+
interface Events {
|
|
12
|
+
/** The user clicked thumbs-up. */
|
|
13
|
+
helpful: void;
|
|
14
|
+
/** The user clicked thumbs-down. */
|
|
15
|
+
nothelpful: void;
|
|
16
|
+
/** The user dismissed the banner. */
|
|
17
|
+
close: void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `<kitn-feedback-bar>` — an inline thumbs up/down feedback banner. Emits
|
|
22
|
+
* `helpful` / `nothelpful` / `close`.
|
|
23
|
+
*/
|
|
24
|
+
defineKitnElement<Props, Events>('kitn-feedback-bar', {
|
|
25
|
+
barTitle: 'Was this helpful?',
|
|
26
|
+
}, (props, { dispatch }) => (
|
|
27
|
+
<FeedbackBar
|
|
28
|
+
title={props.barTitle}
|
|
29
|
+
onHelpful={() => dispatch('helpful')}
|
|
30
|
+
onNotHelpful={() => dispatch('nothelpful')}
|
|
31
|
+
onClose={() => dispatch('close')}
|
|
32
|
+
/>
|
|
33
|
+
));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { defineKitnElement } from './define';
|
|
2
|
+
import { FileUpload, FileUploadTrigger } from '../components/file-upload';
|
|
3
|
+
import { Upload } from 'lucide-solid';
|
|
4
|
+
|
|
5
|
+
interface Props extends Record<string, unknown> {
|
|
6
|
+
/** Allow selecting multiple files (default true). */
|
|
7
|
+
multiple?: boolean;
|
|
8
|
+
/** `accept` attribute for the file picker (e.g. `image/*`). */
|
|
9
|
+
accept?: string;
|
|
10
|
+
/** Disable the dropzone — no clicking, no drag-and-drop. */
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
/** Default dropzone label (overridable via the default slot). */
|
|
13
|
+
label?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Events fired by `<kitn-file-upload>`. */
|
|
17
|
+
interface Events {
|
|
18
|
+
/** Files were picked or dropped. */
|
|
19
|
+
filesadded: { files: File[] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `<kitn-file-upload>` — a click/drag-drop dropzone. Emits `filesadded`. The
|
|
24
|
+
* default dropzone label can be replaced with your own markup via the default
|
|
25
|
+
* `<slot>` (a "Route 2" custom-content slot).
|
|
26
|
+
*/
|
|
27
|
+
defineKitnElement<Props, Events>('kitn-file-upload', {
|
|
28
|
+
multiple: true,
|
|
29
|
+
accept: undefined,
|
|
30
|
+
disabled: false,
|
|
31
|
+
label: 'Click or drop files to upload',
|
|
32
|
+
}, (props, { dispatch, flag }) => (
|
|
33
|
+
<FileUpload
|
|
34
|
+
multiple={flag('multiple')}
|
|
35
|
+
accept={props.accept}
|
|
36
|
+
disabled={flag('disabled')}
|
|
37
|
+
onFilesAdded={(files) => dispatch('filesadded', { files })}
|
|
38
|
+
>
|
|
39
|
+
<FileUploadTrigger class="border-border bg-muted/30 hover:bg-muted/60 text-muted-foreground flex w-full cursor-pointer flex-col items-center justify-center gap-2 rounded-xl border border-dashed px-6 py-8 text-sm transition-colors">
|
|
40
|
+
<Upload class="size-5" />
|
|
41
|
+
<slot>{props.label}</slot>
|
|
42
|
+
</FileUploadTrigger>
|
|
43
|
+
</FileUpload>
|
|
44
|
+
));
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineKitnElement } from './define';
|
|
2
|
+
import { Image } from '../components/image';
|
|
3
|
+
|
|
4
|
+
interface Props extends Record<string, unknown> {
|
|
5
|
+
/** Base64-encoded image data (pair with `media-type`). */
|
|
6
|
+
base64?: string;
|
|
7
|
+
/** Raw image bytes (set as a JS property). */
|
|
8
|
+
bytes?: Uint8Array;
|
|
9
|
+
/** Alt text. */
|
|
10
|
+
alt?: string;
|
|
11
|
+
/** MIME type (default `image/png`). */
|
|
12
|
+
mediaType?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `<kitn-image>` — renders a base64 or byte-array image with a skeleton
|
|
17
|
+
* fallback while it resolves. `base64`/`alt`/`media-type` via attributes;
|
|
18
|
+
* `bytes` via property.
|
|
19
|
+
*/
|
|
20
|
+
defineKitnElement<Props>('kitn-image', {
|
|
21
|
+
base64: undefined,
|
|
22
|
+
bytes: undefined,
|
|
23
|
+
alt: '',
|
|
24
|
+
mediaType: undefined,
|
|
25
|
+
}, (props) => (
|
|
26
|
+
<Image
|
|
27
|
+
alt={props.alt ?? ''}
|
|
28
|
+
base64={props.base64}
|
|
29
|
+
uint8Array={props.bytes}
|
|
30
|
+
mediaType={props.mediaType}
|
|
31
|
+
/>
|
|
32
|
+
));
|
|
@@ -0,0 +1,181 @@
|
|
|
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 { AttachmentData } from '../components/attachments';
|
|
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-attachments': JSX.HTMLAttributes<HTMLElement> & {
|
|
12
|
+
variant?: string;
|
|
13
|
+
'hover-card'?: boolean | string;
|
|
14
|
+
removable?: boolean | string;
|
|
15
|
+
'show-media-type'?: boolean | string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Mixed file/source items (icons + labels) — mirrors Components/Attachments.
|
|
22
|
+
const sampleItems: AttachmentData[] = [
|
|
23
|
+
{
|
|
24
|
+
id: '1',
|
|
25
|
+
type: 'file',
|
|
26
|
+
filename: 'mountain-landscape.jpg',
|
|
27
|
+
mediaType: 'image/jpeg',
|
|
28
|
+
url: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=400&fit=crop',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: '2',
|
|
32
|
+
type: 'file',
|
|
33
|
+
filename: 'sunset-beach.png',
|
|
34
|
+
mediaType: 'image/png',
|
|
35
|
+
url: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=400&h=400&fit=crop',
|
|
36
|
+
},
|
|
37
|
+
{ id: '3', type: 'file', filename: 'architecture-report.pdf', mediaType: 'application/pdf' },
|
|
38
|
+
{ id: '4', type: 'file', filename: 'demo-recording.mp4', mediaType: 'video/mp4' },
|
|
39
|
+
{ id: '5', type: 'file', filename: 'podcast-episode.mp3', mediaType: 'audio/mpeg' },
|
|
40
|
+
{ id: '6', type: 'source-document', filename: 'SolidJS Documentation', title: 'SolidJS Reactivity Guide', url: 'https://solidjs.com/docs' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// Image-only items — used for the grid thumbnails + hover-card preview stories.
|
|
44
|
+
const imageItems: AttachmentData[] = [
|
|
45
|
+
{
|
|
46
|
+
id: 'img-1',
|
|
47
|
+
type: 'file',
|
|
48
|
+
filename: 'mountain-landscape.jpg',
|
|
49
|
+
mediaType: 'image/jpeg',
|
|
50
|
+
url: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=400&fit=crop',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'img-2',
|
|
54
|
+
type: 'file',
|
|
55
|
+
filename: 'sunset-beach.png',
|
|
56
|
+
mediaType: 'image/png',
|
|
57
|
+
url: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=400&h=400&fit=crop',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
/** Render `<kitn-attachments>` with an `items` property and the given flags. */
|
|
62
|
+
function AttachmentsElement(props: {
|
|
63
|
+
items: AttachmentData[];
|
|
64
|
+
variant?: string;
|
|
65
|
+
removable?: boolean;
|
|
66
|
+
hoverCard?: boolean;
|
|
67
|
+
showMediaType?: boolean;
|
|
68
|
+
}) {
|
|
69
|
+
let el: (HTMLElement & { items?: AttachmentData[] }) | undefined;
|
|
70
|
+
onMount(() => {
|
|
71
|
+
if (!el) return;
|
|
72
|
+
el.items = props.items;
|
|
73
|
+
if (props.variant) el.setAttribute('variant', props.variant);
|
|
74
|
+
if (props.hoverCard) el.setAttribute('hover-card', '');
|
|
75
|
+
if (props.showMediaType) el.setAttribute('show-media-type', '');
|
|
76
|
+
if (props.removable) {
|
|
77
|
+
el.setAttribute('removable', '');
|
|
78
|
+
el.addEventListener('remove', ((e: CustomEvent<{ id: string }>) => {
|
|
79
|
+
el!.items = (el!.items ?? []).filter((x) => x.id !== e.detail.id);
|
|
80
|
+
}) as EventListener);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return (
|
|
84
|
+
<kitn-attachments
|
|
85
|
+
ref={(e) => (el = e as HTMLElement)}
|
|
86
|
+
style={{ display: 'block', padding: '24px', 'max-width': '720px' }}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
92
|
+
<kitn-attachments id="att" variant="grid" removable></kitn-attachments>
|
|
93
|
+
|
|
94
|
+
<script type="module">
|
|
95
|
+
import '@kitnai/chat/elements'; // registers the custom elements
|
|
96
|
+
|
|
97
|
+
const att = document.getElementById('att');
|
|
98
|
+
att.items = [
|
|
99
|
+
{ id: '1', type: 'file', filename: 'mountain-landscape.jpg',
|
|
100
|
+
mediaType: 'image/jpeg', url: 'https://.../mountain.jpg' },
|
|
101
|
+
{ id: '2', type: 'file', filename: 'spec.pdf', mediaType: 'application/pdf' },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
// events are CustomEvents on the element (they do not bubble)
|
|
105
|
+
att.addEventListener('remove', (e) => {
|
|
106
|
+
att.items = att.items.filter((x) => x.id !== e.detail.id);
|
|
107
|
+
});
|
|
108
|
+
</script>`;
|
|
109
|
+
|
|
110
|
+
const HOVER_SNIPPET = `<!-- inline/list chips with a hover-card image preview -->
|
|
111
|
+
<kitn-attachments id="att" variant="inline" hover-card></kitn-attachments>
|
|
112
|
+
|
|
113
|
+
<script type="module">
|
|
114
|
+
import '@kitnai/chat/elements';
|
|
115
|
+
|
|
116
|
+
const att = document.getElementById('att');
|
|
117
|
+
// image attachments show their thumbnail in the hover card on hover;
|
|
118
|
+
// non-image attachments show their label + media type instead.
|
|
119
|
+
att.items = [
|
|
120
|
+
{ id: '1', type: 'file', filename: 'mountain-landscape.jpg',
|
|
121
|
+
mediaType: 'image/jpeg', url: 'https://.../mountain.jpg' },
|
|
122
|
+
];
|
|
123
|
+
</script>`;
|
|
124
|
+
|
|
125
|
+
const meta = {
|
|
126
|
+
title: 'Web Components/kitn-attachments',
|
|
127
|
+
tags: ['autodocs'],
|
|
128
|
+
parameters: {
|
|
129
|
+
layout: 'fullscreen',
|
|
130
|
+
docs: {
|
|
131
|
+
description: {
|
|
132
|
+
component: [
|
|
133
|
+
'`<kitn-attachments>` is the framework-agnostic **web component** for a set of file/source attachments, and the exemplar for the "collapse a compound primitive to ONE configurable element" pattern: the sub-parts the SolidJS layer composes become attributes here. Isolated in **Shadow DOM**.',
|
|
134
|
+
'**When to use:** rendering attachment chips/tiles in a non-Solid app. In SolidJS, compose the `Attachment*` primitives for fully custom layouts.',
|
|
135
|
+
"**How to use:** register once with `import '@kitnai/chat/elements'`, set the data via the `items` **property**, pick a layout with `variant` (`grid` | `inline` | `list`), add `removable` to get per-item remove buttons (emits a `remove` **CustomEvent** with `{ id }`), and `hover-card` for inline/list previews (image attachments preview their thumbnail).",
|
|
136
|
+
'See the **Code** tab for HTML usage.',
|
|
137
|
+
].join('\n\n'),
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
} satisfies Meta;
|
|
142
|
+
|
|
143
|
+
export default meta;
|
|
144
|
+
type Story = StoryObj;
|
|
145
|
+
|
|
146
|
+
/** Visual tiles (the default `grid` variant) — image attachments render as real
|
|
147
|
+
* `<img>` thumbnails; non-image files fall back to a media-type icon. */
|
|
148
|
+
export const Grid: Story = {
|
|
149
|
+
render: () => <AttachmentsElement items={sampleItems} variant="grid" />,
|
|
150
|
+
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** Icon + label chips (`inline`) — files shown as a small preview + filename. */
|
|
154
|
+
export const Inline: Story = {
|
|
155
|
+
render: () => <AttachmentsElement items={sampleItems} variant="inline" />,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/** Detailed rows (`list`) with the media type beneath each filename. */
|
|
159
|
+
export const List: Story = {
|
|
160
|
+
render: () => <AttachmentsElement items={sampleItems} variant="list" showMediaType />,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/** Removable rows — clicking the remove button fires a `remove` event. */
|
|
164
|
+
export const RemovableList: Story = {
|
|
165
|
+
name: 'Removable List',
|
|
166
|
+
render: () => <AttachmentsElement items={sampleItems} variant="list" removable />,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** Image thumbnails in a grid (real `<img>` previews). */
|
|
170
|
+
export const ImageGrid: Story = {
|
|
171
|
+
name: 'Image Grid',
|
|
172
|
+
render: () => <AttachmentsElement items={imageItems} variant="grid" />,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/** Inline chips with a hover-card preview — hover an image chip to see its
|
|
176
|
+
* thumbnail enlarge in the hover card. */
|
|
177
|
+
export const WithHoverCard: Story = {
|
|
178
|
+
name: 'With Hover Card',
|
|
179
|
+
render: () => <AttachmentsElement items={imageItems} variant="inline" hoverCard />,
|
|
180
|
+
parameters: { docs: { source: { code: HOVER_SNIPPET, language: 'html' } } },
|
|
181
|
+
};
|
|
@@ -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
|
+
interface Step {
|
|
6
|
+
label: string;
|
|
7
|
+
content?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
11
|
+
declare module 'solid-js' {
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
13
|
+
namespace JSX {
|
|
14
|
+
interface IntrinsicElements {
|
|
15
|
+
'kitn-chain-of-thought': JSX.HTMLAttributes<HTMLElement>;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const steps: Step[] = [
|
|
21
|
+
{ label: 'Understand the request', content: 'The user wants a composable set of web components.' },
|
|
22
|
+
{ label: 'Design the API', content: 'Route 1: variant + flags + events; rich data via properties.' },
|
|
23
|
+
{ label: 'Build & verify' },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/** Render `<kitn-chain-of-thought>` with the `steps` set as a JS property. */
|
|
27
|
+
function CotElement(props: { steps: Step[] }) {
|
|
28
|
+
let el: (HTMLElement & { steps?: Step[] }) | undefined;
|
|
29
|
+
onMount(() => {
|
|
30
|
+
if (el) el.steps = props.steps;
|
|
31
|
+
});
|
|
32
|
+
return (
|
|
33
|
+
<kitn-chain-of-thought ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '24px', 'max-width': '560px' }} />
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
38
|
+
<kitn-chain-of-thought id="cot"></kitn-chain-of-thought>
|
|
39
|
+
|
|
40
|
+
<script type="module">
|
|
41
|
+
import '@kitnai/chat/elements'; // registers the custom elements
|
|
42
|
+
|
|
43
|
+
document.getElementById('cot').steps = [
|
|
44
|
+
{ label: 'Understand the request', content: 'The user wants a composable set.' },
|
|
45
|
+
{ label: 'Design the API', content: 'Route 1: variant + flags + events.' },
|
|
46
|
+
{ label: 'Build & verify' },
|
|
47
|
+
];
|
|
48
|
+
</script>`;
|
|
49
|
+
|
|
50
|
+
const meta = {
|
|
51
|
+
title: 'Web Components/kitn-chain-of-thought',
|
|
52
|
+
tags: ['autodocs'],
|
|
53
|
+
parameters: {
|
|
54
|
+
layout: 'fullscreen',
|
|
55
|
+
docs: {
|
|
56
|
+
description: {
|
|
57
|
+
component: [
|
|
58
|
+
'`<kitn-chain-of-thought>` is the framework-agnostic **web component** for step-by-step reasoning — a connected list of steps, each with optional collapsible detail — isolated in **Shadow DOM**. The compound primitive collapses to a single `steps` data model (Route 1).',
|
|
59
|
+
'**When to use:** surfacing an agent\'s plan or reasoning trace in a non-Solid app. In SolidJS, compose the `ChainOfThought` primitives for finer control.',
|
|
60
|
+
"**How to use:** register once with `import '@kitnai/chat/elements'`, then set the `steps` **property** — an array of `{ label, content? }`. Steps with `content` become expandable.",
|
|
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
|
+
/** A three-step reasoning trace; the last step has no detail. */
|
|
72
|
+
export const Default: Story = {
|
|
73
|
+
render: () => <CotElement steps={steps} />,
|
|
74
|
+
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
75
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
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-chat-scope-picker': JSX.HTMLAttributes<HTMLElement> & {
|
|
11
|
+
'current-label'?: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Render `<kitn-chat-scope-picker>` with author/tag options set as properties. */
|
|
18
|
+
function ScopePickerElement(props: { authors: string[]; tags: string[] }) {
|
|
19
|
+
let el: (HTMLElement & { availableAuthors?: string[]; availableTags?: string[] }) | undefined;
|
|
20
|
+
onMount(() => {
|
|
21
|
+
if (!el) return;
|
|
22
|
+
el.availableAuthors = props.authors;
|
|
23
|
+
el.availableTags = props.tags;
|
|
24
|
+
el.addEventListener('scopechange', (e) => {
|
|
25
|
+
const ev = e as CustomEvent<{ filters: unknown }>;
|
|
26
|
+
console.log('scopechange', ev.detail.filters ?? 'all');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
return (
|
|
30
|
+
<kitn-chat-scope-picker ref={(e) => (el = e as HTMLElement)} style={{ display: 'inline-block', padding: '40px' }} />
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
35
|
+
<kitn-chat-scope-picker id="scope"></kitn-chat-scope-picker>
|
|
36
|
+
|
|
37
|
+
<script type="module">
|
|
38
|
+
import '@kitnai/chat/elements'; // registers the custom elements
|
|
39
|
+
|
|
40
|
+
const scope = document.getElementById('scope');
|
|
41
|
+
scope.availableAuthors = ['Rob', 'Alex'];
|
|
42
|
+
scope.availableTags = ['design', 'api'];
|
|
43
|
+
// undefined filters means "All Content"
|
|
44
|
+
scope.addEventListener('scopechange', (e) => console.log(e.detail.filters ?? 'all'));
|
|
45
|
+
</script>`;
|
|
46
|
+
|
|
47
|
+
const meta = {
|
|
48
|
+
title: 'Web Components/kitn-chat-scope-picker',
|
|
49
|
+
tags: ['autodocs'],
|
|
50
|
+
parameters: {
|
|
51
|
+
layout: 'fullscreen',
|
|
52
|
+
docs: {
|
|
53
|
+
description: {
|
|
54
|
+
component: [
|
|
55
|
+
'`<kitn-chat-scope-picker>` is the framework-agnostic **web component** for scoping a chat by author or tag — a dropdown that emits the chosen filters — isolated in **Shadow DOM**.',
|
|
56
|
+
'**When to use:** letting users narrow a conversation/search to a subset of content. In SolidJS, use the `ChatScopePicker` primitive.',
|
|
57
|
+
"**How to use:** register once with `import '@kitnai/chat/elements'`, set the `availableAuthors` / `availableTags` **properties** (and optionally `current-label`), and listen for the `scopechange` **CustomEvent** (`undefined` filters = \"All Content\").",
|
|
58
|
+
'See the **Code** tab for HTML usage.',
|
|
59
|
+
].join('\n\n'),
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
} satisfies Meta;
|
|
64
|
+
|
|
65
|
+
export default meta;
|
|
66
|
+
type Story = StoryObj;
|
|
67
|
+
|
|
68
|
+
/** Authors and tags available as scope filters. */
|
|
69
|
+
export const Default: Story = {
|
|
70
|
+
render: () => <ScopePickerElement authors={['Rob', 'Alex']} tags={['design', 'api']} />,
|
|
71
|
+
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
72
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { onMount } from 'solid-js';
|
|
3
|
+
import './register'; // side effect: registers all kitn custom elements including <kitn-chat-workspace>
|
|
4
|
+
import type { ConversationGroup, ConversationSummary, ModelOption } from '../types';
|
|
5
|
+
import type { ChatMessage } from './chat-types';
|
|
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-chat-workspace': JSX.HTMLAttributes<HTMLElement>;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const sampleGroups: ConversationGroup[] = [
|
|
18
|
+
{ id: 'today', name: 'Today', sortOrder: 0, createdAt: '2026-06-13T00:00:00.000Z' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const sampleConversations: ConversationSummary[] = [
|
|
22
|
+
{
|
|
23
|
+
id: '1',
|
|
24
|
+
title: 'Web component architecture',
|
|
25
|
+
groupId: 'today',
|
|
26
|
+
scope: { type: 'document' },
|
|
27
|
+
messageCount: 12,
|
|
28
|
+
lastMessageAt: '2026-06-13T15:30:00.000Z',
|
|
29
|
+
updatedAt: '2026-06-13T15:30:00.000Z',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: '2',
|
|
33
|
+
title: 'Theming & tokens',
|
|
34
|
+
groupId: 'today',
|
|
35
|
+
scope: { type: 'document' },
|
|
36
|
+
messageCount: 5,
|
|
37
|
+
lastMessageAt: '2026-06-13T11:20:00.000Z',
|
|
38
|
+
updatedAt: '2026-06-13T11:20:00.000Z',
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const sampleModels: ModelOption[] = [
|
|
43
|
+
{ id: 'claude-4', name: 'Claude 4 Opus', provider: 'Anthropic' },
|
|
44
|
+
{ id: 'gpt-4o', name: 'GPT-4o', provider: 'OpenAI' },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const sampleMessages: ChatMessage[] = [
|
|
48
|
+
{ id: 'm1', role: 'user', content: 'How do I drop the whole chat app in with one tag?' },
|
|
49
|
+
{
|
|
50
|
+
id: 'm2',
|
|
51
|
+
role: 'assistant',
|
|
52
|
+
content:
|
|
53
|
+
'Use `<kitn-chat-workspace>` — set `conversations`, `messages`, and `models` as properties and listen for `conversationselect` + `submit`.',
|
|
54
|
+
actions: ['copy', 'like'],
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
/** Live demo of the actual `<kitn-chat-workspace>` custom element (Shadow DOM and all). */
|
|
59
|
+
function WorkspaceElement() {
|
|
60
|
+
let el:
|
|
61
|
+
| (HTMLElement & {
|
|
62
|
+
groups?: ConversationGroup[];
|
|
63
|
+
conversations?: ConversationSummary[];
|
|
64
|
+
activeId?: string;
|
|
65
|
+
messages?: ChatMessage[];
|
|
66
|
+
models?: ModelOption[];
|
|
67
|
+
currentModel?: string;
|
|
68
|
+
chatTitle?: string;
|
|
69
|
+
})
|
|
70
|
+
| undefined;
|
|
71
|
+
onMount(() => {
|
|
72
|
+
if (el) {
|
|
73
|
+
el.groups = sampleGroups;
|
|
74
|
+
el.conversations = sampleConversations;
|
|
75
|
+
el.activeId = '1';
|
|
76
|
+
el.messages = sampleMessages;
|
|
77
|
+
el.models = sampleModels;
|
|
78
|
+
el.currentModel = 'claude-4';
|
|
79
|
+
el.chatTitle = 'Web component architecture';
|
|
80
|
+
el.addEventListener('conversationselect', (e) => console.log('select', (e as CustomEvent).detail));
|
|
81
|
+
el.addEventListener('submit', (e) => console.log('submit', (e as unknown as CustomEvent).detail));
|
|
82
|
+
el.addEventListener('sidebartoggle', (e) => console.log('sidebartoggle', (e as CustomEvent).detail));
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return (
|
|
86
|
+
<div style={{ height: '720px', width: '100%' }}>
|
|
87
|
+
<kitn-chat-workspace
|
|
88
|
+
ref={(e) => (el = e as HTMLElement)}
|
|
89
|
+
style={{ display: 'block', height: '100%' }}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
96
|
+
<kitn-chat-workspace id="workspace" style="display:block; height:100vh;"></kitn-chat-workspace>
|
|
97
|
+
|
|
98
|
+
<script type="module">
|
|
99
|
+
import '@kitnai/chat/elements'; // registers the custom elements
|
|
100
|
+
|
|
101
|
+
const workspace = document.getElementById('workspace');
|
|
102
|
+
workspace.conversations = [
|
|
103
|
+
{
|
|
104
|
+
id: '1', title: 'Web component architecture',
|
|
105
|
+
scope: { type: 'document' }, messageCount: 12,
|
|
106
|
+
lastMessageAt: '2026-06-13T15:30:00Z', updatedAt: '2026-06-13T15:30:00Z',
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
workspace.messages = [
|
|
110
|
+
{ id: 'm1', role: 'user', content: 'How do I drop the whole chat app in with one tag?' },
|
|
111
|
+
{ id: 'm2', role: 'assistant', content: 'Use <kitn-chat-workspace> — set conversations, messages, and models as properties.' },
|
|
112
|
+
];
|
|
113
|
+
workspace.models = [
|
|
114
|
+
{ id: 'claude-4', name: 'Claude 4 Opus', provider: 'Anthropic' },
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
// events are CustomEvents on the element (they do not bubble)
|
|
118
|
+
workspace.addEventListener('conversationselect', (e) => console.log('selected conversation:', e.detail.id));
|
|
119
|
+
workspace.addEventListener('submit', (e) => console.log('user sent:', e.detail.value));
|
|
120
|
+
workspace.addEventListener('sidebartoggle', (e) => console.log('sidebar collapsed:', e.detail.collapsed));
|
|
121
|
+
</script>`;
|
|
122
|
+
|
|
123
|
+
const SOLID_SNIPPET = `import '@kitnai/chat/elements'; // registers the custom elements
|
|
124
|
+
import { onMount } from 'solid-js';
|
|
125
|
+
import type { ConversationSummary, ModelOption } from '@kitnai/chat';
|
|
126
|
+
import type { ChatMessage } from '@kitnai/chat/elements';
|
|
127
|
+
|
|
128
|
+
function Workspace() {
|
|
129
|
+
let el: HTMLElement & {
|
|
130
|
+
conversations?: ConversationSummary[];
|
|
131
|
+
messages?: ChatMessage[];
|
|
132
|
+
models?: ModelOption[];
|
|
133
|
+
activeId?: string;
|
|
134
|
+
};
|
|
135
|
+
const conversations: ConversationSummary[] = [
|
|
136
|
+
{
|
|
137
|
+
id: '1', title: 'Web component architecture',
|
|
138
|
+
scope: { type: 'document' }, messageCount: 12,
|
|
139
|
+
lastMessageAt: '2026-06-13T15:30:00Z', updatedAt: '2026-06-13T15:30:00Z',
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
const messages: ChatMessage[] = [
|
|
143
|
+
{ id: 'm1', role: 'user', content: 'How do I drop the whole chat app in with one tag?' },
|
|
144
|
+
{ id: 'm2', role: 'assistant', content: 'Use <kitn-chat-workspace> — set conversations, messages, and models as properties.' },
|
|
145
|
+
];
|
|
146
|
+
onMount(() => {
|
|
147
|
+
el.conversations = conversations;
|
|
148
|
+
el.messages = messages;
|
|
149
|
+
el.activeId = '1';
|
|
150
|
+
el.addEventListener('conversationselect', (e) => console.log('selected:', e.detail.id));
|
|
151
|
+
el.addEventListener('submit', (e) => console.log('user sent:', e.detail.value));
|
|
152
|
+
el.addEventListener('sidebartoggle', (e) => console.log('sidebar collapsed:', e.detail.collapsed));
|
|
153
|
+
});
|
|
154
|
+
return (
|
|
155
|
+
<kitn-chat-workspace
|
|
156
|
+
ref={el}
|
|
157
|
+
style={{ display: 'block', height: '100vh' }}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
}`;
|
|
161
|
+
|
|
162
|
+
const meta = {
|
|
163
|
+
title: 'Web Components/kitn-chat-workspace',
|
|
164
|
+
tags: ['autodocs'],
|
|
165
|
+
parameters: {
|
|
166
|
+
layout: 'fullscreen',
|
|
167
|
+
docs: {
|
|
168
|
+
description: {
|
|
169
|
+
component: [
|
|
170
|
+
'`<kitn-chat-workspace>` is the full chat shell as a single **web component** — a resizable split layout with a collapsible conversation list on the left and a full message thread on the right, all isolated in **Shadow DOM**. SolidJS is bundled in, so the host needs nothing.',
|
|
171
|
+
'**When to use:** dropping an entire chat application shell into a non-Solid app (React, Vue, Svelte, plain HTML), or anywhere you want zero style conflicts and a ready-made list+chat layout. If you *are* in SolidJS and want fine-grained control, compose the `ConversationList` and `ChatThread` primitives directly.',
|
|
172
|
+
'**How to use:** register once with `import \'@kitnai/chat/elements\'`, set rich data as JS **properties** (`el.conversations = [...]`, `el.messages = [...]`, `el.models = [...]`), and listen for **CustomEvents** (`conversationselect`, `submit`, `sidebartoggle`, `newchat`) directly on the element.',
|
|
173
|
+
'**Placement:** as a full-page surface or large panel. Give it an explicit height (e.g. `height: 100vh`). The sidebar is drag-resizable and can be collapsed via the toggle button in its header.',
|
|
174
|
+
'See the **Code** tab below for the HTML usage; the *SolidJS* story shows the same element inside a Solid component.',
|
|
175
|
+
].join('\n\n'),
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
} satisfies Meta;
|
|
180
|
+
|
|
181
|
+
export default meta;
|
|
182
|
+
type Story = StoryObj;
|
|
183
|
+
|
|
184
|
+
/** The element used the plain-HTML / any-framework way. */
|
|
185
|
+
export const Default: Story = {
|
|
186
|
+
render: () => <WorkspaceElement />,
|
|
187
|
+
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/** The same element used inside a SolidJS component (properties via `ref` + `onMount`, events via `addEventListener`). */
|
|
191
|
+
export const InSolidJS: Story = {
|
|
192
|
+
name: 'In SolidJS',
|
|
193
|
+
render: () => <WorkspaceElement />,
|
|
194
|
+
parameters: { docs: { source: { code: SOLID_SNIPPET, language: 'tsx' } } },
|
|
195
|
+
};
|