@kitnai/chat 0.7.0 → 0.8.1
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 +9 -9
- package/dist/custom-elements.json +1626 -883
- package/dist/kitn-chat.es.js +36 -36
- package/dist/llms/llms-full.txt +303 -142
- package/dist/llms/llms.txt +18 -18
- package/dist/schemas/card-envelope.schema.json +14 -0
- package/dist/schemas/card-event.schema.json +12 -0
- package/dist/schemas/confirm.schema.json +65 -0
- package/dist/schemas/embed.schema.json +65 -0
- package/dist/schemas/form.result.schema.json +7 -0
- package/dist/schemas/form.schema.json +33 -0
- package/dist/schemas/link.schema.json +56 -0
- package/dist/schemas/task-list.result.schema.json +16 -0
- package/dist/schemas/task-list.schema.json +78 -0
- package/dist/theme.tokens.css +65 -65
- package/dist/tsx-B8rCNbgL.js +1 -0
- package/dist/typescript-RycA9KXf.js +1 -0
- package/frameworks/react/index.tsx +356 -189
- package/frameworks/react/runtime.tsx +2 -2
- package/llms-full.txt +303 -142
- package/llms.txt +18 -18
- package/package.json +5 -2
- package/src/components/artifact.stories.tsx +138 -0
- package/src/components/artifact.tsx +581 -0
- package/src/components/attachments.stories.tsx +7 -8
- package/src/components/attachments.tsx +2 -2
- package/src/components/card.tsx +110 -0
- package/src/components/chain-of-thought.stories.tsx +7 -8
- package/src/components/chat-container.stories.tsx +7 -8
- package/src/components/chat-container.tsx +4 -0
- package/src/components/checkpoint.stories.tsx +7 -8
- package/src/components/checkpoint.tsx +3 -0
- package/src/components/code-block.stories.tsx +8 -9
- package/src/components/code-block.tsx +5 -2
- package/src/components/component-meta.json +3419 -0
- package/src/components/confirm-card.stories.tsx +74 -0
- package/src/components/confirm-card.tsx +299 -0
- package/src/components/context.stories.tsx +7 -8
- package/src/components/conversation-item.stories.tsx +7 -8
- package/src/components/conversation-item.tsx +2 -2
- package/src/components/conversation-list.stories.tsx +7 -8
- package/src/components/conversation-list.tsx +1 -1
- package/src/components/embed.tsx +196 -0
- package/src/components/empty.stories.tsx +8 -9
- package/src/components/feedback-bar.stories.tsx +7 -8
- package/src/components/file-tree.stories.tsx +73 -0
- package/src/components/file-tree.tsx +383 -0
- package/src/components/file-upload.stories.tsx +7 -8
- package/src/components/form-widgets.tsx +461 -0
- package/src/components/form.tsx +796 -0
- package/src/components/image.stories.tsx +7 -8
- package/src/components/link-card.tsx +194 -0
- package/src/components/loader.stories.tsx +7 -8
- package/src/components/markdown.stories.tsx +7 -8
- package/src/components/message-narrow.stories.tsx +12 -13
- package/src/components/message-skills.stories.tsx +16 -17
- package/src/components/message.stories.tsx +17 -18
- package/src/components/model-switcher.stories.tsx +7 -8
- package/src/components/prompt-input.stories.tsx +8 -9
- package/src/components/prompt-suggestion.stories.tsx +7 -8
- package/src/components/prompt-suggestion.tsx +3 -3
- package/src/components/reasoning.stories.tsx +7 -8
- package/src/components/scroll-button.stories.tsx +7 -8
- package/src/components/slash-command.stories.tsx +8 -9
- package/src/components/slash-command.tsx +2 -2
- package/src/components/source.stories.tsx +7 -8
- package/src/components/source.tsx +1 -1
- package/src/components/task-list-card.stories.tsx +78 -0
- package/src/components/task-list-card.tsx +388 -0
- package/src/components/text-shimmer.stories.tsx +7 -8
- package/src/components/thinking-bar.stories.tsx +7 -8
- package/src/components/tool.stories.tsx +7 -8
- package/src/components/tool.tsx +2 -2
- package/src/components/voice-input.stories.tsx +7 -8
- package/src/elements/artifact.stories.tsx +291 -0
- package/src/elements/artifact.tsx +72 -0
- package/src/elements/{kitn-attachments.stories.tsx → attachments.stories.tsx} +11 -20
- package/src/elements/attachments.tsx +4 -4
- package/src/elements/card.stories.tsx +118 -0
- package/src/elements/card.tsx +40 -0
- package/src/elements/catalog.stories.tsx +491 -0
- package/src/elements/{kitn-chain-of-thought.stories.tsx → chain-of-thought.stories.tsx} +13 -22
- package/src/elements/chain-of-thought.tsx +3 -3
- package/src/elements/{kitn-chat-scope-picker.stories.tsx → chat-scope-picker.stories.tsx} +10 -19
- package/src/elements/chat-scope-picker.tsx +4 -4
- package/src/elements/{kitn-chat-workspace.stories.tsx → chat-workspace.stories.tsx} +15 -23
- package/src/elements/chat-workspace.tsx +2 -2
- package/src/elements/{kitn-chat.stories.tsx → chat.stories.tsx} +12 -20
- package/src/elements/chat.tsx +2 -2
- package/src/elements/{kitn-checkpoint.stories.tsx → checkpoint.stories.tsx} +11 -20
- package/src/elements/checkpoint.tsx +8 -4
- package/src/elements/{kitn-code-block.stories.tsx → code-block.stories.tsx} +10 -19
- package/src/elements/code-block.tsx +3 -3
- package/src/elements/compiled.css +1 -1
- package/src/elements/composed-shell.stories.tsx +316 -0
- package/src/elements/confirm-card.stories.tsx +186 -0
- package/src/elements/confirm-card.tsx +45 -0
- package/src/elements/{kitn-context-meter.stories.tsx → context-meter.stories.tsx} +10 -19
- package/src/elements/context-meter.tsx +3 -3
- package/src/elements/{kitn-conversation-list.stories.tsx → conversation-list.stories.tsx} +12 -20
- package/src/elements/conversation-list.tsx +2 -2
- package/src/elements/css.ts +1 -1
- package/src/elements/define.tsx +10 -10
- package/src/elements/element-meta.json +1379 -733
- package/src/elements/element-types.d.ts +251 -125
- package/src/elements/embed.stories.tsx +197 -0
- package/src/elements/embed.tsx +35 -0
- package/src/elements/{kitn-empty.stories.tsx → empty.stories.tsx} +12 -21
- package/src/elements/empty.tsx +3 -3
- package/src/elements/{kitn-feedback-bar.stories.tsx → feedback-bar.stories.tsx} +11 -20
- package/src/elements/feedback-bar.tsx +4 -4
- package/src/elements/file-tree.stories.tsx +133 -0
- package/src/elements/file-tree.tsx +52 -0
- package/src/elements/{kitn-file-upload.stories.tsx → file-upload.stories.tsx} +12 -21
- package/src/elements/file-upload.tsx +4 -4
- package/src/elements/form.stories.tsx +204 -0
- package/src/elements/form.tsx +37 -0
- package/src/elements/{kitn-image.stories.tsx → image.stories.tsx} +10 -19
- package/src/elements/image.tsx +3 -3
- package/src/elements/link-card.stories.tsx +193 -0
- package/src/elements/link-card.tsx +34 -0
- package/src/elements/{kitn-loader.stories.tsx → loader.stories.tsx} +11 -20
- package/src/elements/loader.tsx +3 -3
- package/src/elements/{kitn-markdown.stories.tsx → markdown.stories.tsx} +10 -19
- package/src/elements/markdown.tsx +3 -3
- package/src/elements/{kitn-message-skills.stories.tsx → message-skills.stories.tsx} +10 -19
- package/src/elements/message-skills.tsx +3 -3
- package/src/elements/{kitn-message.stories.tsx → message.stories.tsx} +12 -21
- package/src/elements/message.tsx +5 -5
- package/src/elements/{kitn-model-switcher.stories.tsx → model-switcher.stories.tsx} +10 -19
- package/src/elements/model-switcher.tsx +5 -5
- package/src/elements/{kitn-prompt-input.stories.tsx → prompt-input.stories.tsx} +14 -22
- package/src/elements/prompt-input.tsx +3 -3
- package/src/elements/{kitn-prompt-suggestions.stories.tsx → prompt-suggestions.stories.tsx} +13 -22
- package/src/elements/prompt-suggestions.tsx +4 -4
- package/src/elements/{kitn-reasoning.stories.tsx → reasoning.stories.tsx} +10 -19
- package/src/elements/reasoning.tsx +4 -4
- package/src/elements/register.ts +11 -1
- package/src/elements/resizable.stories.tsx +200 -0
- package/src/elements/resizable.tsx +264 -0
- package/src/elements/{kitn-response-stream.stories.tsx → response-stream.stories.tsx} +10 -19
- package/src/elements/response-stream.tsx +4 -4
- package/src/elements/{kitn-source-list.stories.tsx → source-list.stories.tsx} +11 -20
- package/src/elements/{kitn-source.stories.tsx → source.stories.tsx} +12 -21
- package/src/elements/source.tsx +5 -5
- package/src/elements/styles.css +140 -1
- package/src/elements/task-list-card.stories.tsx +194 -0
- package/src/elements/task-list-card.tsx +40 -0
- package/src/elements/{kitn-text-shimmer.stories.tsx → text-shimmer.stories.tsx} +10 -19
- package/src/elements/text-shimmer.tsx +3 -3
- package/src/elements/{kitn-thinking-bar.stories.tsx → thinking-bar.stories.tsx} +11 -20
- package/src/elements/thinking-bar.tsx +5 -5
- package/src/elements/{kitn-tool.stories.tsx → tool.stories.tsx} +10 -19
- package/src/elements/tool.tsx +3 -3
- package/src/elements/{kitn-voice-input.stories.tsx → voice-input.stories.tsx} +10 -19
- package/src/elements/voice-input.tsx +4 -4
- package/src/index.ts +94 -2
- package/src/primitives/card-contract.ts +60 -0
- package/src/primitives/card-host.tsx +35 -0
- package/src/primitives/card-routing.ts +79 -0
- package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
- package/src/primitives/card-schemas/card-event.schema.json +12 -0
- package/src/primitives/card-schemas/confirm.schema.json +65 -0
- package/src/primitives/card-schemas/embed.schema.json +65 -0
- package/src/primitives/card-schemas/form.result.schema.json +7 -0
- package/src/primitives/card-schemas/form.schema.json +33 -0
- package/src/primitives/card-schemas/link.schema.json +56 -0
- package/src/primitives/card-schemas/task-list.result.schema.json +16 -0
- package/src/primitives/card-schemas/task-list.schema.json +78 -0
- package/src/primitives/card-validate.ts +95 -0
- package/src/primitives/embed-providers.ts +254 -0
- package/src/primitives/highlighter.ts +4 -0
- package/src/primitives/link-preview.ts +87 -0
- package/src/primitives/pdf-preview.ts +121 -0
- package/src/stories/chat-panel-layout.stories.tsx +2 -1
- package/src/stories/chat-scene.tsx +22 -21
- package/src/stories/checkpoint-restore.stories.tsx +10 -10
- package/src/stories/conversation-with-reasoning.stories.tsx +4 -4
- package/src/stories/conversation-with-sources.stories.tsx +7 -7
- package/src/stories/docs/Accessibility.mdx +2 -2
- package/src/stories/docs/ForAIAgents.mdx +3 -3
- package/src/stories/docs/GettingStarted.mdx +2 -2
- package/src/stories/docs/Installation.mdx +2 -2
- package/src/stories/docs/Integrations.mdx +29 -29
- package/src/stories/docs/Introduction.mdx +3 -3
- package/src/stories/docs/Theming.mdx +2 -2
- package/src/stories/docs/element-controls.ts +32 -0
- package/src/stories/docs/theme-editor/theme-editor.tsx +1 -0
- package/src/stories/examples/ChoosingComponents.mdx +94 -0
- package/src/stories/examples/sample-data.ts +79 -0
- package/src/stories/message-actions.stories.tsx +13 -13
- package/src/stories/pattern-centered-conversation.stories.tsx +3 -3
- package/src/stories/pattern-docked-widget.stories.tsx +1 -1
- package/src/stories/pattern-empty-state.stories.tsx +3 -3
- package/src/stories/prompt-input-variants.stories.tsx +13 -13
- package/src/stories/streaming-response.stories.tsx +3 -3
- package/src/stories/typography.stories.tsx +4 -4
- package/src/ui/avatar.stories.tsx +7 -8
- package/src/ui/badge.stories.tsx +7 -8
- package/src/ui/button.stories.tsx +8 -9
- package/src/ui/button.tsx +1 -0
- package/src/ui/collapsible.stories.tsx +6 -7
- package/src/ui/dropdown.stories.tsx +6 -7
- package/src/ui/hover-card.stories.tsx +6 -7
- package/src/ui/resizable.stories.tsx +74 -9
- package/src/ui/resizable.tsx +351 -71
- package/src/ui/scroll-area.stories.tsx +6 -7
- package/src/ui/scroll-area.tsx +3 -1
- package/src/ui/separator.stories.tsx +7 -8
- package/src/ui/skeleton.stories.tsx +7 -8
- package/src/ui/textarea.stories.tsx +6 -7
- package/src/ui/tooltip.stories.tsx +8 -9
- package/theme.css +65 -65
- package/src/stories/docs/element-spec.tsx +0 -86
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// src/primitives/pdf-preview.ts
|
|
2
|
+
// On-demand PDF renderer built on pdf.js, loaded from a CDN only when a PDF is
|
|
3
|
+
// actually shown — so a component set that never renders a PDF ships and runs with
|
|
4
|
+
// ZERO pdf.js bytes (the ~482 KB gzip library is never fetched). When a PDF does
|
|
5
|
+
// appear, pdf.js is dynamically imported from the pinned CDN build and every page
|
|
6
|
+
// is rendered to a <canvas>. Hosts override the loader (self-host / CSP / pin) or
|
|
7
|
+
// disable inline rendering via configurePdfPreview(). Mirrors highlighter.ts.
|
|
8
|
+
|
|
9
|
+
/** Minimal shape of the pdf.js module + objects we rely on. */
|
|
10
|
+
export interface PdfViewportLike {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
}
|
|
14
|
+
export interface PdfPageLike {
|
|
15
|
+
getViewport(opts: { scale: number }): PdfViewportLike;
|
|
16
|
+
render(opts: {
|
|
17
|
+
canvasContext: CanvasRenderingContext2D | null;
|
|
18
|
+
viewport: PdfViewportLike;
|
|
19
|
+
}): { promise: Promise<void> };
|
|
20
|
+
}
|
|
21
|
+
export interface PdfDocumentLike {
|
|
22
|
+
numPages: number;
|
|
23
|
+
getPage(n: number): Promise<PdfPageLike>;
|
|
24
|
+
}
|
|
25
|
+
export interface PdfjsLike {
|
|
26
|
+
getDocument(src: { url: string }): { promise: Promise<PdfDocumentLike> };
|
|
27
|
+
GlobalWorkerOptions: { workerSrc: string };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface PdfPreviewOptions {
|
|
31
|
+
/** Turn inline PDF rendering on/off globally. When false, always show the card. */
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
/** Override the pdf.js module loader (self-host / CSP / version pin). */
|
|
34
|
+
load?: () => Promise<PdfjsLike>;
|
|
35
|
+
/** Worker URL. Default = the matching jsDelivr worker for the pinned version. */
|
|
36
|
+
workerSrc?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Pinned, exact (reproducible) — NOT a range. */
|
|
40
|
+
const PDFJS_VERSION = '6.0.227';
|
|
41
|
+
const CDN = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${PDFJS_VERSION}/build`;
|
|
42
|
+
const DEFAULT_LOAD = (): Promise<PdfjsLike> =>
|
|
43
|
+
// Template literal + @vite-ignore keeps this a runtime fetch (never bundled).
|
|
44
|
+
import(/* @vite-ignore */ `${CDN}/pdf.min.mjs`) as Promise<PdfjsLike>;
|
|
45
|
+
const DEFAULT_WORKER_SRC = `${CDN}/pdf.worker.min.mjs`;
|
|
46
|
+
|
|
47
|
+
let enabled = true;
|
|
48
|
+
let loader: () => Promise<PdfjsLike> = DEFAULT_LOAD;
|
|
49
|
+
let workerSrc = DEFAULT_WORKER_SRC;
|
|
50
|
+
|
|
51
|
+
export function configurePdfPreview(options: PdfPreviewOptions): void {
|
|
52
|
+
if (options.enabled !== undefined) enabled = options.enabled;
|
|
53
|
+
if (options.workerSrc !== undefined) workerSrc = options.workerSrc;
|
|
54
|
+
if (options.load !== undefined) {
|
|
55
|
+
loader = options.load;
|
|
56
|
+
pdfjsPromise = null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isPdfPreviewEnabled(): boolean {
|
|
61
|
+
return enabled;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function __resetPdfPreviewForTests(): void {
|
|
65
|
+
enabled = true;
|
|
66
|
+
loader = DEFAULT_LOAD;
|
|
67
|
+
workerSrc = DEFAULT_WORKER_SRC;
|
|
68
|
+
pdfjsPromise = null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let pdfjsPromise: Promise<PdfjsLike> | null = null;
|
|
72
|
+
|
|
73
|
+
/** Load pdf.js once (singleton); set the worker src. Re-loads if the loader changed. */
|
|
74
|
+
function loadPdfjs(): Promise<PdfjsLike> {
|
|
75
|
+
if (!pdfjsPromise) {
|
|
76
|
+
const active = loader;
|
|
77
|
+
pdfjsPromise = (async () => {
|
|
78
|
+
const mod = await active();
|
|
79
|
+
const pdfjs = ((mod as unknown as { default?: PdfjsLike }).default ?? mod) as PdfjsLike;
|
|
80
|
+
pdfjs.GlobalWorkerOptions.workerSrc = workerSrc;
|
|
81
|
+
return pdfjs;
|
|
82
|
+
})();
|
|
83
|
+
}
|
|
84
|
+
return pdfjsPromise;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Render EVERY page of the PDF at `url` into `container` as stacked <canvas>
|
|
89
|
+
* elements fit to `pxWidth` CSS pixels (rendered at devicePixelRatio for
|
|
90
|
+
* crispness). Clears `container` first. Resolves `{ pages }`. THROWS on
|
|
91
|
+
* load / CORS / parse failure — the caller catches and shows the fallback card.
|
|
92
|
+
*/
|
|
93
|
+
export async function renderPdfInto(
|
|
94
|
+
url: string,
|
|
95
|
+
container: HTMLElement,
|
|
96
|
+
pxWidth: number,
|
|
97
|
+
): Promise<{ pages: number }> {
|
|
98
|
+
const pdfjs = await loadPdfjs();
|
|
99
|
+
const doc = await pdfjs.getDocument({ url }).promise;
|
|
100
|
+
container.replaceChildren();
|
|
101
|
+
const dpr =
|
|
102
|
+
typeof window !== 'undefined' && window.devicePixelRatio ? window.devicePixelRatio : 1;
|
|
103
|
+
for (let n = 1; n <= doc.numPages; n++) {
|
|
104
|
+
const page = await doc.getPage(n);
|
|
105
|
+
const base = page.getViewport({ scale: 1 });
|
|
106
|
+
const scale = base.width > 0 ? pxWidth / base.width : 1;
|
|
107
|
+
const viewport = page.getViewport({ scale: scale * dpr });
|
|
108
|
+
const canvas = document.createElement('canvas');
|
|
109
|
+
canvas.width = Math.max(1, Math.floor(viewport.width));
|
|
110
|
+
canvas.height = Math.max(1, Math.floor(viewport.height));
|
|
111
|
+
canvas.setAttribute('role', 'img');
|
|
112
|
+
canvas.setAttribute('aria-label', `Page ${n}`);
|
|
113
|
+
canvas.style.width = '100%';
|
|
114
|
+
canvas.style.height = 'auto';
|
|
115
|
+
canvas.style.display = 'block';
|
|
116
|
+
const ctx = canvas.getContext('2d');
|
|
117
|
+
container.appendChild(canvas);
|
|
118
|
+
await page.render({ canvasContext: ctx, viewport }).promise;
|
|
119
|
+
}
|
|
120
|
+
return { pages: doc.numPages };
|
|
121
|
+
}
|
|
@@ -31,7 +31,7 @@ const userMsg2 = 'And from what I understand, does that need to be another brows
|
|
|
31
31
|
|
|
32
32
|
function CopyButton(props: { text: string }) {
|
|
33
33
|
return (
|
|
34
|
-
<button onClick={() => navigator.clipboard.writeText(props.text)}>
|
|
34
|
+
<button aria-label="Copy message" onClick={() => navigator.clipboard.writeText(props.text)}>
|
|
35
35
|
<Copy size={14} />
|
|
36
36
|
</button>
|
|
37
37
|
);
|
|
@@ -130,6 +130,7 @@ export const ChatGPTStyle: Story = {
|
|
|
130
130
|
<Button
|
|
131
131
|
size="icon-sm"
|
|
132
132
|
class="rounded-full"
|
|
133
|
+
aria-label="Send message"
|
|
133
134
|
disabled={!input().trim()}
|
|
134
135
|
>
|
|
135
136
|
<ArrowUp class="size-4" />
|
|
@@ -256,6 +256,7 @@ export function ChatScene(props: { class?: string }) {
|
|
|
256
256
|
</div>
|
|
257
257
|
<div class="flex items-center gap-2">
|
|
258
258
|
<select
|
|
259
|
+
aria-label="Reading size"
|
|
259
260
|
class="bg-muted/40 text-xs text-muted-foreground rounded-lg px-2 py-1.5 outline-none hover:bg-muted/60 transition-colors cursor-pointer"
|
|
260
261
|
value={proseSize()}
|
|
261
262
|
onChange={(e) => setProseSize(e.currentTarget.value as ProseSize)}
|
|
@@ -305,10 +306,10 @@ export function ChatScene(props: { class?: string }) {
|
|
|
305
306
|
understand why.
|
|
306
307
|
</MessageContent>
|
|
307
308
|
<MessageActions class="flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
|
|
308
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
309
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Edit message">
|
|
309
310
|
<Pencil class="size-3.5" />
|
|
310
311
|
</Button>
|
|
311
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
312
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
|
|
312
313
|
<Copy class="size-3.5" />
|
|
313
314
|
</Button>
|
|
314
315
|
</MessageActions>
|
|
@@ -325,16 +326,16 @@ export function ChatScene(props: { class?: string }) {
|
|
|
325
326
|
{assistantResponse1}
|
|
326
327
|
</MessageContent>
|
|
327
328
|
<MessageActions class="-ml-2.5 flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
|
|
328
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
329
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
|
|
329
330
|
<Copy class="size-3.5" />
|
|
330
331
|
</Button>
|
|
331
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
332
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Good response">
|
|
332
333
|
<ThumbsUp class="size-3.5" />
|
|
333
334
|
</Button>
|
|
334
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
335
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Bad response">
|
|
335
336
|
<ThumbsDown class="size-3.5" />
|
|
336
337
|
</Button>
|
|
337
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
338
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Regenerate response">
|
|
338
339
|
<RefreshCw class="size-3.5" />
|
|
339
340
|
</Button>
|
|
340
341
|
</MessageActions>
|
|
@@ -349,10 +350,10 @@ export function ChatScene(props: { class?: string }) {
|
|
|
349
350
|
useEffect?
|
|
350
351
|
</MessageContent>
|
|
351
352
|
<MessageActions class="flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
|
|
352
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
353
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Edit message">
|
|
353
354
|
<Pencil class="size-3.5" />
|
|
354
355
|
</Button>
|
|
355
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
356
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
|
|
356
357
|
<Copy class="size-3.5" />
|
|
357
358
|
</Button>
|
|
358
359
|
</MessageActions>
|
|
@@ -369,16 +370,16 @@ export function ChatScene(props: { class?: string }) {
|
|
|
369
370
|
{assistantResponse2}
|
|
370
371
|
</MessageContent>
|
|
371
372
|
<MessageActions class="-ml-2.5 flex gap-0">
|
|
372
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
373
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
|
|
373
374
|
<Copy class="size-3.5" />
|
|
374
375
|
</Button>
|
|
375
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
376
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Good response">
|
|
376
377
|
<ThumbsUp class="size-3.5" />
|
|
377
378
|
</Button>
|
|
378
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
379
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Bad response">
|
|
379
380
|
<ThumbsDown class="size-3.5" />
|
|
380
381
|
</Button>
|
|
381
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
382
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Regenerate response">
|
|
382
383
|
<RefreshCw class="size-3.5" />
|
|
383
384
|
</Button>
|
|
384
385
|
</MessageActions>
|
|
@@ -445,16 +446,16 @@ export function ChatScene(props: { class?: string }) {
|
|
|
445
446
|
SolidJS's fine-grained reactivity really shines here — updating a single item in React triggers a full virtual DOM diff of all 10,000 items, while SolidJS surgically updates only the changed DOM node.`}
|
|
446
447
|
</MessageContent>
|
|
447
448
|
<MessageActions class="-ml-2.5 flex gap-0">
|
|
448
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
449
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
|
|
449
450
|
<Copy class="size-3.5" />
|
|
450
451
|
</Button>
|
|
451
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
452
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Good response">
|
|
452
453
|
<ThumbsUp class="size-3.5" />
|
|
453
454
|
</Button>
|
|
454
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
455
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Bad response">
|
|
455
456
|
<ThumbsDown class="size-3.5" />
|
|
456
457
|
</Button>
|
|
457
|
-
<Button variant="ghost" size="icon-sm" class="rounded-full">
|
|
458
|
+
<Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Regenerate response">
|
|
458
459
|
<RefreshCw class="size-3.5" />
|
|
459
460
|
</Button>
|
|
460
461
|
</MessageActions>
|
|
@@ -533,26 +534,26 @@ SolidJS's fine-grained reactivity really shines here — updating a single item
|
|
|
533
534
|
/>
|
|
534
535
|
<PromptInputActions class="mt-2 flex w-full items-center justify-between gap-2 px-3 pb-3">
|
|
535
536
|
<div class="flex items-center gap-2">
|
|
536
|
-
<Button variant="outline" size="icon-sm" class="rounded-full">
|
|
537
|
+
<Button variant="outline" size="icon-sm" class="rounded-full" aria-label="Add">
|
|
537
538
|
<Plus class="size-4" />
|
|
538
539
|
</Button>
|
|
539
|
-
<Button variant="outline" size="sm" class="rounded-full gap-1">
|
|
540
|
+
<Button variant="outline" size="sm" class="rounded-full gap-1" aria-label="Search the web">
|
|
540
541
|
<Globe class="size-4" />
|
|
541
542
|
Search
|
|
542
543
|
</Button>
|
|
543
|
-
<Button variant="outline" size="icon-sm" class="rounded-full">
|
|
544
|
+
<Button variant="outline" size="icon-sm" class="rounded-full" aria-label="More options">
|
|
544
545
|
<MoreHorizontal class="size-4" />
|
|
545
546
|
</Button>
|
|
546
547
|
</div>
|
|
547
548
|
<div class="flex items-center gap-2">
|
|
548
|
-
<Button variant="outline" size="icon-sm" class="rounded-full">
|
|
549
|
+
<Button variant="outline" size="icon-sm" class="rounded-full" aria-label="Voice input">
|
|
549
550
|
<Mic class="size-4" />
|
|
550
551
|
</Button>
|
|
551
552
|
<Button
|
|
552
553
|
size="icon-sm"
|
|
553
554
|
class="rounded-full"
|
|
554
555
|
disabled={!inputValue().trim()}
|
|
555
|
-
|
|
556
|
+
aria-label="Send message">
|
|
556
557
|
<ArrowUp class="size-4" />
|
|
557
558
|
</Button>
|
|
558
559
|
</div>
|
|
@@ -67,9 +67,9 @@ Key design decisions:
|
|
|
67
67
|
- Support filtering via query params: \`?status=active&assignee=me\``}
|
|
68
68
|
</MessageContent>
|
|
69
69
|
<MessageActions>
|
|
70
|
-
<Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
|
|
71
|
-
<Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
|
|
72
|
-
<Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
|
|
70
|
+
<Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
|
|
71
|
+
<Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
|
|
72
|
+
<Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
|
|
73
73
|
</MessageActions>
|
|
74
74
|
</div>
|
|
75
75
|
</Message>
|
|
@@ -128,9 +128,9 @@ function authMiddleware(req, res, next) {
|
|
|
128
128
|
**The tradeoff:** you can't instantly revoke JWTs. Mitigate this with short expiry + a blocklist for critical cases (password change, account compromise).`}
|
|
129
129
|
</MessageContent>
|
|
130
130
|
<MessageActions>
|
|
131
|
-
<Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
|
|
132
|
-
<Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
|
|
133
|
-
<Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
|
|
131
|
+
<Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
|
|
132
|
+
<Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
|
|
133
|
+
<Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
|
|
134
134
|
</MessageActions>
|
|
135
135
|
</div>
|
|
136
136
|
</Message>
|
|
@@ -197,9 +197,9 @@ interface ApiError {
|
|
|
197
197
|
Always include the \`requestId\` -- it's invaluable for correlating client errors with server logs.`}
|
|
198
198
|
</MessageContent>
|
|
199
199
|
<MessageActions>
|
|
200
|
-
<Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
|
|
201
|
-
<Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
|
|
202
|
-
<Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
|
|
200
|
+
<Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
|
|
201
|
+
<Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
|
|
202
|
+
<Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
|
|
203
203
|
</MessageActions>
|
|
204
204
|
</div>
|
|
205
205
|
</Message>
|
|
@@ -212,7 +212,7 @@ Always include the \`requestId\` -- it's invaluable for correlating client error
|
|
|
212
212
|
<PromptInput>
|
|
213
213
|
<PromptInputTextarea placeholder="Continue the API design..." />
|
|
214
214
|
<PromptInputActions class="justify-end">
|
|
215
|
-
<Button variant="default" size="icon-sm" class="rounded-full">
|
|
215
|
+
<Button variant="default" size="icon-sm" class="rounded-full" aria-label="Send message">
|
|
216
216
|
<ArrowUp class="size-4" />
|
|
217
217
|
</Button>
|
|
218
218
|
</PromptInputActions>
|
|
@@ -125,9 +125,9 @@ Move to a custom sync layer if:
|
|
|
125
125
|
</MessageContent>
|
|
126
126
|
|
|
127
127
|
<MessageActions>
|
|
128
|
-
<Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
|
|
129
|
-
<Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
|
|
130
|
-
<Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
|
|
128
|
+
<Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
|
|
129
|
+
<Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
|
|
130
|
+
<Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
|
|
131
131
|
</MessageActions>
|
|
132
132
|
</div>
|
|
133
133
|
</Message>
|
|
@@ -140,7 +140,7 @@ Move to a custom sync layer if:
|
|
|
140
140
|
<PromptInput>
|
|
141
141
|
<PromptInputTextarea placeholder="Ask a follow-up..." />
|
|
142
142
|
<PromptInputActions class="justify-end">
|
|
143
|
-
<Button variant="default" size="icon-sm" class="rounded-full">
|
|
143
|
+
<Button variant="default" size="icon-sm" class="rounded-full" aria-label="Send message">
|
|
144
144
|
<ArrowUp class="size-4" />
|
|
145
145
|
</Button>
|
|
146
146
|
</PromptInputActions>
|
|
@@ -75,9 +75,9 @@ The pattern is consistent: CPU-intensive tasks like **image processing, cryptogr
|
|
|
75
75
|
</SourceList>
|
|
76
76
|
|
|
77
77
|
<MessageActions>
|
|
78
|
-
<Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
|
|
79
|
-
<Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
|
|
80
|
-
<Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
|
|
78
|
+
<Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
|
|
79
|
+
<Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
|
|
80
|
+
<Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
|
|
81
81
|
</MessageActions>
|
|
82
82
|
</div>
|
|
83
83
|
</Message>
|
|
@@ -139,9 +139,9 @@ The consensus from the Chrome team's analysis is: **use Wasm for compute-heavy i
|
|
|
139
139
|
</SourceList>
|
|
140
140
|
|
|
141
141
|
<MessageActions>
|
|
142
|
-
<Button variant="ghost" size="icon-sm"><Copy class="size-3.5" /></Button>
|
|
143
|
-
<Button variant="ghost" size="icon-sm"><ThumbsUp class="size-3.5" /></Button>
|
|
144
|
-
<Button variant="ghost" size="icon-sm"><ThumbsDown class="size-3.5" /></Button>
|
|
142
|
+
<Button variant="ghost" size="icon-sm" aria-label="Copy message"><Copy class="size-3.5" /></Button>
|
|
143
|
+
<Button variant="ghost" size="icon-sm" aria-label="Good response"><ThumbsUp class="size-3.5" /></Button>
|
|
144
|
+
<Button variant="ghost" size="icon-sm" aria-label="Bad response"><ThumbsDown class="size-3.5" /></Button>
|
|
145
145
|
</MessageActions>
|
|
146
146
|
</div>
|
|
147
147
|
</Message>
|
|
@@ -154,7 +154,7 @@ The consensus from the Chrome team's analysis is: **use Wasm for compute-heavy i
|
|
|
154
154
|
<PromptInput>
|
|
155
155
|
<PromptInputTextarea placeholder="Ask about WebAssembly..." />
|
|
156
156
|
<PromptInputActions class="justify-end">
|
|
157
|
-
<Button variant="default" size="icon-sm" class="rounded-full">
|
|
157
|
+
<Button variant="default" size="icon-sm" class="rounded-full" aria-label="Send message">
|
|
158
158
|
<ArrowUp class="size-4" />
|
|
159
159
|
</Button>
|
|
160
160
|
</PromptInputActions>
|
|
@@ -14,7 +14,7 @@ This page describes the accessibility posture, the keyboard model, known limitat
|
|
|
14
14
|
|
|
15
15
|
All text and UI chrome meet WCAG 2.1 AA contrast requirements (4.5:1 for body text, 3:1 for large text and UI components) in the default light and dark palettes.
|
|
16
16
|
|
|
17
|
-
The `theme` attribute on every element (`light | dark | auto`) controls which token set loads. `auto` (the default) follows the OS `prefers-color-scheme` media query, so users who configure dark mode at the OS level get it automatically. Override either set with `--
|
|
17
|
+
The `theme` attribute on every element (`light | dark | auto`) controls which token set loads. `auto` (the default) follows the OS `prefers-color-scheme` media query, so users who configure dark mode at the OS level get it automatically. Override either set with `--kc-color-*` tokens — if you do, verify contrast yourself; the kit cannot audit tokens it does not control.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
@@ -113,7 +113,7 @@ The kit does not carry a "WCAG AA certified" certification — no automated tool
|
|
|
113
113
|
## What integrators need to do
|
|
114
114
|
|
|
115
115
|
1. **Do not hide focus rings.** The kit renders visible focus rings via `:focus-visible`. Do not add `outline: none` or `outline: 0` in your host-page CSS targeting `*` or the element host.
|
|
116
|
-
2. **Verify contrast if you override tokens.** Use a contrast checker after overriding any `--
|
|
116
|
+
2. **Verify contrast if you override tokens.** Use a contrast checker after overriding any `--kc-color-*` token.
|
|
117
117
|
3. **Provide meaningful conversation titles.** The conversation list announces the `title` field to screen readers. "Chat 1" is less useful than "React integration help".
|
|
118
118
|
4. **Set a meaningful `placeholder`.** The textarea placeholder doubles as an accessible name when no label is associated.
|
|
119
119
|
5. **Check your theme choice.** Using `theme="light"` in a page the user has set to dark mode (or vice versa) can create contrast mismatches between kit components and host-page content. `theme="auto"` (the default) avoids this.
|
|
@@ -35,12 +35,12 @@ An HTML attribute is always a string, so passing `messages`, `models`, `context`
|
|
|
35
35
|
`suggestions`, or `slashCommands` as an attribute silently fails.
|
|
36
36
|
|
|
37
37
|
```js
|
|
38
|
-
const chat = document.querySelector('
|
|
38
|
+
const chat = document.querySelector('kc-chat');
|
|
39
39
|
chat.messages = [{ id: '1', role: 'assistant', content: 'Hi!' }]; // ✅ property
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
```html
|
|
43
|
-
<
|
|
43
|
+
<kc-chat messages="[...]"></kc-chat> <!-- ❌ never works -->
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
Only scalar props (string / number / boolean) work as attributes — for example
|
|
@@ -60,7 +60,7 @@ Only scalar props (string / number / boolean) work as attributes — for example
|
|
|
60
60
|
|
|
61
61
|
```js
|
|
62
62
|
import '@kitnai/chat/elements';
|
|
63
|
-
const chat = document.querySelector('
|
|
63
|
+
const chat = document.querySelector('kc-chat');
|
|
64
64
|
chat.messages = [];
|
|
65
65
|
|
|
66
66
|
chat.addEventListener('submit', async (e) => {
|
|
@@ -5,14 +5,14 @@ import * as FullChat from '../full-chat.stories';
|
|
|
5
5
|
|
|
6
6
|
# Getting Started
|
|
7
7
|
|
|
8
|
-
`<
|
|
8
|
+
`<kc-chat>` is **transport-agnostic**: you give it a `messages` array, it renders the conversation, and it emits a `submit` event when the user sends. You own the request and the streaming; the component owns the UI.
|
|
9
9
|
|
|
10
10
|
## A web component in ~10 lines
|
|
11
11
|
|
|
12
12
|
Set rich data as JS **properties** and listen for **events** on the element:
|
|
13
13
|
|
|
14
14
|
```html
|
|
15
|
-
<
|
|
15
|
+
<kc-chat id="chat" style="display:block; height:100vh;"></kc-chat>
|
|
16
16
|
|
|
17
17
|
<script type="module">
|
|
18
18
|
import '@kitnai/chat/elements';
|
|
@@ -29,7 +29,7 @@ The `@kitnai/chat` entry is shipped as source, so your bundler tree-shakes it do
|
|
|
29
29
|
|
|
30
30
|
## Web components (React, Vue, plain HTML, …)
|
|
31
31
|
|
|
32
|
-
Build the element bundle, then import it as a **side effect** — that registers `<
|
|
32
|
+
Build the element bundle, then import it as a **side effect** — that registers `<kc-chat>`, `<kc-conversations>`, and `<kc-prompt-input>`:
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
npm run build # emits dist/kitn-chat.es.js
|
|
@@ -60,7 +60,7 @@ The element bundle is a self-contained ES module, so you can load it straight fr
|
|
|
60
60
|
import 'https://unpkg.com/@kitnai/chat/dist/kitn-chat.es.js';
|
|
61
61
|
</script>
|
|
62
62
|
|
|
63
|
-
<
|
|
63
|
+
<kc-chat></kc-chat>
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
- The URLs above track the **latest** release (no version) — they never go stale, so they're ideal for demos and quick starts.
|
|
@@ -13,13 +13,13 @@ import { Meta } from '@storybook/addon-docs/blocks';
|
|
|
13
13
|
Arrays and objects **must** be set as JavaScript **properties** on the DOM element — never as HTML attributes. An HTML attribute is always a string, so passing `messages`, `models`, `context`, `suggestions`, or `slashCommands` as an attribute silently fails or is ignored.
|
|
14
14
|
|
|
15
15
|
```js
|
|
16
|
-
const chat = document.querySelector('
|
|
16
|
+
const chat = document.querySelector('kc-chat');
|
|
17
17
|
|
|
18
18
|
// ✅ correct — set as a JS property
|
|
19
19
|
chat.messages = [{ id: '1', role: 'assistant', content: 'Hello!' }];
|
|
20
20
|
|
|
21
21
|
// ❌ wrong — HTML attribute, always a string, never works
|
|
22
|
-
// <
|
|
22
|
+
// <kc-chat messages="[...]"></kc-chat>
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
Scalar values (strings, numbers, booleans) work as attributes: `placeholder`, `loading`, `theme`, `prose-size`, and so on.
|
|
@@ -30,7 +30,7 @@ Scalar values (strings, numbers, booleans) work as attributes: `placeholder`, `l
|
|
|
30
30
|
|
|
31
31
|
Import the element bundle once as a side-effect (it registers all `kitn-*` custom elements), then set properties and listen for events in a `<script type="module">` block.
|
|
32
32
|
|
|
33
|
-
> **Want the whole shell in one tag?** `<
|
|
33
|
+
> **Want the whole shell in one tag?** `<kc-workspace>` bundles the conversation-list sidebar, the drag-to-resize handle, and the full chat thread together. Set `conversations`, `messages`, and optionally `models` as properties; listen for `conversationselect` and `submit`. See the <a href="?path=/docs/web-components-kc-workspace--docs">kc-workspace story</a> and the <a href="https://github.com/kitn-ai/chat/blob/main/docs/web-components.md#kc-workspace--kcworkspace">web-components.md reference</a> for the full API.
|
|
34
34
|
|
|
35
35
|
```html
|
|
36
36
|
<!DOCTYPE html>
|
|
@@ -40,7 +40,7 @@ Import the element bundle once as a side-effect (it registers all `kitn-*` custo
|
|
|
40
40
|
<link rel="stylesheet" href="./node_modules/@kitnai/chat/dist/theme.tokens.css" />
|
|
41
41
|
</head>
|
|
42
42
|
<body style="height: 100vh; margin: 0;">
|
|
43
|
-
<
|
|
43
|
+
<kc-chat id="chat" style="display: block; height: 100%;"></kc-chat>
|
|
44
44
|
|
|
45
45
|
<script type="module">
|
|
46
46
|
import '@kitnai/chat/elements';
|
|
@@ -89,10 +89,10 @@ Scalars can go directly in the HTML as attributes:
|
|
|
89
89
|
|
|
90
90
|
```html
|
|
91
91
|
<!-- theme, placeholder, and loading are scalar → safe as attributes -->
|
|
92
|
-
<
|
|
92
|
+
<kc-chat
|
|
93
93
|
theme="dark"
|
|
94
94
|
placeholder="Ask anything…"
|
|
95
|
-
></
|
|
95
|
+
></kc-chat>
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
---
|
|
@@ -102,7 +102,7 @@ Scalars can go directly in the HTML as attributes:
|
|
|
102
102
|
The kit ships auto-generated, typed React wrappers under `@kitnai/chat/react`. They handle the ref plumbing internally — rich props are set as DOM **properties** and CustomEvents are exposed as `on<Event>` handlers, so you write idiomatic JSX without touching refs yourself.
|
|
103
103
|
|
|
104
104
|
```tsx
|
|
105
|
-
import {
|
|
105
|
+
import { KcChat, KcConversations } from '@kitnai/chat/react';
|
|
106
106
|
import { useState } from 'react';
|
|
107
107
|
|
|
108
108
|
type Message = {
|
|
@@ -137,7 +137,7 @@ export function App() {
|
|
|
137
137
|
};
|
|
138
138
|
|
|
139
139
|
return (
|
|
140
|
-
<
|
|
140
|
+
<KcChat
|
|
141
141
|
messages={messages}
|
|
142
142
|
suggestions={['Summarize the chat', 'Start fresh']}
|
|
143
143
|
onSubmit={handleSubmit}
|
|
@@ -169,18 +169,18 @@ Component names are the PascalCase of the tag name:
|
|
|
169
169
|
|
|
170
170
|
```tsx
|
|
171
171
|
import {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
172
|
+
KcChat,
|
|
173
|
+
KcConversations,
|
|
174
|
+
KcPromptInput,
|
|
175
|
+
KcMessage,
|
|
176
|
+
KcMarkdown,
|
|
177
|
+
KcCodeBlock,
|
|
178
|
+
KcReasoning,
|
|
179
|
+
KcTool,
|
|
180
|
+
KcContext,
|
|
181
|
+
KcModelSwitcher,
|
|
182
|
+
KcAttachments,
|
|
183
|
+
KcLoader,
|
|
184
184
|
// …all 27 elements
|
|
185
185
|
} from '@kitnai/chat/react';
|
|
186
186
|
```
|
|
@@ -223,7 +223,7 @@ export function Chat() {
|
|
|
223
223
|
return () => el.removeEventListener('submit', onSubmit);
|
|
224
224
|
}, []);
|
|
225
225
|
|
|
226
|
-
return <
|
|
226
|
+
return <kc-chat ref={chatRef} style={{ display: 'block', height: '100vh' }} />;
|
|
227
227
|
}
|
|
228
228
|
```
|
|
229
229
|
|
|
@@ -254,7 +254,7 @@ const handleSubmit = (e: CustomEvent<{ value: string }>) => {
|
|
|
254
254
|
|
|
255
255
|
<template>
|
|
256
256
|
<!-- Arrays/objects → .prop modifier; scalars → plain attributes -->
|
|
257
|
-
<
|
|
257
|
+
<kc-chat
|
|
258
258
|
:messages.prop="messages"
|
|
259
259
|
placeholder="Ask anything…"
|
|
260
260
|
theme="auto"
|
|
@@ -275,7 +275,7 @@ Add `@kitnai/chat/elements` to your `vite.config.ts` / `env.d.ts` once so Vue's
|
|
|
275
275
|
|
|
276
276
|
### Sidebar + chat together (Vue)
|
|
277
277
|
|
|
278
|
-
Cross-element coordination goes through the host component — the `<
|
|
278
|
+
Cross-element coordination goes through the host component — the `<kc-conversations>` fires `select`, you update a reactive ref, and pass it into `<kc-chat>`:
|
|
279
279
|
|
|
280
280
|
```html
|
|
281
281
|
<script setup lang="ts">
|
|
@@ -297,13 +297,13 @@ const onSelect = (e: CustomEvent<{ id: string }>) => {
|
|
|
297
297
|
|
|
298
298
|
<template>
|
|
299
299
|
<div style="display: flex; height: 100vh;">
|
|
300
|
-
<
|
|
300
|
+
<kc-conversations
|
|
301
301
|
:conversations.prop="conversations"
|
|
302
302
|
:active-id="activeId"
|
|
303
303
|
style="width: 260px"
|
|
304
304
|
@select="onSelect"
|
|
305
305
|
/>
|
|
306
|
-
<
|
|
306
|
+
<kc-chat
|
|
307
307
|
:messages.prop="messages"
|
|
308
308
|
style="flex: 1"
|
|
309
309
|
/>
|
|
@@ -333,7 +333,7 @@ Svelte's template compiler sets DOM **properties** when you bind with `bind:` or
|
|
|
333
333
|
</script>
|
|
334
334
|
|
|
335
335
|
<!-- use:action pattern to set properties -->
|
|
336
|
-
<
|
|
336
|
+
<kc-chat
|
|
337
337
|
use:setProps={{ messages }}
|
|
338
338
|
style="display: block; height: 100vh"
|
|
339
339
|
on:submit={handleSubmit}
|
|
@@ -393,7 +393,7 @@ export class AppComponent {
|
|
|
393
393
|
|
|
394
394
|
```html
|
|
395
395
|
<!-- app.component.html -->
|
|
396
|
-
<
|
|
396
|
+
<kc-chat
|
|
397
397
|
[messages]="messages"
|
|
398
398
|
[models]="models"
|
|
399
399
|
[loading]="loading"
|
|
@@ -401,7 +401,7 @@ export class AppComponent {
|
|
|
401
401
|
style="display: block; height: 100vh"
|
|
402
402
|
(submit)="onSubmit($event)"
|
|
403
403
|
(modelchange)="onModelChange($event)"
|
|
404
|
-
></
|
|
404
|
+
></kc-chat>
|
|
405
405
|
```
|
|
406
406
|
|
|
407
407
|
> **Why `[messages]` works:** Angular's `[prop]` binding writes to the element's DOM property (not an attribute), so the property-vs-attribute rule is satisfied automatically. Scalars like `theme` can be plain attributes. `CUSTOM_ELEMENTS_SCHEMA` is required so Angular doesn't error on the unknown `kitn-*` tags.
|
|
@@ -509,4 +509,4 @@ async function speakCloud(text) {
|
|
|
509
509
|
|
|
510
510
|
## Speech-to-text
|
|
511
511
|
|
|
512
|
-
The reverse direction is built in — the kit ships `<
|
|
512
|
+
The reverse direction is built in — the kit ships `<kc-voice-input>` (and a `VoiceInput` SolidJS component). Find it in the sidebar under the component stories.
|