@kitnai/chat 0.4.0 → 0.6.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 +74 -5
- package/dist/custom-elements.json +475 -0
- package/dist/kitn-chat.es.js +18 -18
- package/dist/llms/llms-full.txt +55 -4
- package/dist/llms/llms.txt +3 -3
- package/dist/theme.tokens.css +31 -3
- package/frameworks/react/index.tsx +54 -0
- package/llms-full.txt +55 -4
- package/llms.txt +3 -3
- package/package.json +20 -2
- package/src/components/chat-thread.tsx +217 -0
- package/src/components/prompt-input.tsx +5 -0
- package/src/elements/chat-workspace.tsx +122 -0
- package/src/elements/chat.tsx +30 -271
- package/src/elements/compiled.css +1 -1
- package/src/elements/define.tsx +1 -1
- package/src/elements/element-types.d.ts +40 -0
- package/src/elements/kitn-chat-workspace.stories.tsx +195 -0
- package/src/elements/register.ts +1 -0
- package/src/elements/styles.css +35 -0
- package/src/primitives/chat-config.tsx +1 -1
- package/src/stories/docs/Installation.mdx +27 -0
- package/src/stories/docs/Integrations.mdx +2 -0
- package/src/stories/docs/Introduction.mdx +12 -3
- 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/ui/collapsible.stories.tsx +70 -0
- package/src/ui/dropdown.stories.tsx +60 -0
- package/src/ui/hover-card.stories.tsx +78 -0
- package/src/ui/overlay.stories.tsx +115 -0
- package/src/ui/overlay.tsx +1 -1
- package/src/ui/scroll-area.stories.tsx +51 -0
- package/src/ui/scroll-area.tsx +1 -1
- package/src/ui/textarea.stories.tsx +77 -0
- package/theme.css +31 -3
package/dist/llms/llms-full.txt
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @kitnai/chat
|
|
5
5
|
|
|
6
|
-
>
|
|
6
|
+
> Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. 28 `kitn-*` custom elements: streaming responses, markdown + code rendering, reasoning/tool panels, attachments, conversation sidebar, voice input. Zero framework dependency for consumers; the SolidJS runtime it is authored in is bundled in, so the host needs nothing.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
@@ -37,7 +37,7 @@ Drop an element into any framework (React, Vue, plain HTML). Data in via JS prop
|
|
|
37
37
|
- `<kitn-prompt-input>` — standalone composer with send button.
|
|
38
38
|
|
|
39
39
|
**Layer 2 — composable primitives** (`import { … } from '@kitnai/chat'`):
|
|
40
|
-
All
|
|
40
|
+
All 28 elements are also exported individually. Use them for custom layouts or features `<kitn-chat>` does not expose (ChainOfThought, FeedbackBar, ThinkingBar, VoiceInput, …). Your bundler tree-shakes the rest.
|
|
41
41
|
|
|
42
42
|
## Key rules for the web components
|
|
43
43
|
|
|
@@ -99,7 +99,7 @@ For Tailwind builds: `@import "@kitnai/chat/theme.css"` in your CSS.
|
|
|
99
99
|
|
|
100
100
|
## Docs
|
|
101
101
|
|
|
102
|
-
- Full element reference (all
|
|
102
|
+
- Full element reference (all 28 elements, every prop/event): ./llms-full.txt — https://kitn.dev/llms-full.txt
|
|
103
103
|
- Machine-readable Custom Elements Manifest: https://unpkg.com/@kitnai/chat/dist/custom-elements.json
|
|
104
104
|
- Working examples: https://github.com/kitn-ai/chat/tree/main/examples
|
|
105
105
|
- Storybook: https://storybook.kitn.dev
|
|
@@ -173,7 +173,7 @@ The same rule applies to every array/object property (`models`, `context`, `sugg
|
|
|
173
173
|
|
|
174
174
|
---
|
|
175
175
|
|
|
176
|
-
## Element reference (
|
|
176
|
+
## Element reference (28 elements, generated from custom-elements.json)
|
|
177
177
|
|
|
178
178
|
Every element also accepts the `theme` attribute. Array/object properties are marked with a `—` attribute: they must be set as JS properties.
|
|
179
179
|
|
|
@@ -269,6 +269,57 @@ _No events._
|
|
|
269
269
|
|
|
270
270
|
---
|
|
271
271
|
|
|
272
|
+
### `kitn-chat-workspace` / `KitnChatWorkspace`
|
|
273
|
+
|
|
274
|
+
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
275
|
+
|
|
276
|
+
| Property | Attribute | Type | Description |
|
|
277
|
+
|---|---|---|---|
|
|
278
|
+
| `groups` | — | `{ id: string; userId?: undefined \| string; teamId?: undefined \| string; name: string; sortOrder: number; createdAt: string }[]` | Pre-bucketed conversation groups for the sidebar. Set as a JS property. |
|
|
279
|
+
| `conversations` | — | `{ id: string; title: string; groupId?: undefined \| string; scope: { type: "document" \| "collection"; documentId?: undefined \| string; filters?: undefined \| { tags?: undefined \| string[]; authors?: undefined \| string[]; contentType?: undefined \| "transcript" \| "markdown"; dateRange?: undefined \| { from: string; to: string } } }; messageCount: number; lastMessageAt: string; updatedAt: string }[]` | Flat conversation list (auto-bucketed if `groups` is empty). Set as a JS property. |
|
|
280
|
+
| `activeId` | `active-id` | `undefined \| string` | Id of the open conversation, highlighted in the sidebar. |
|
|
281
|
+
| `messages` | — | `{ id: string; role: "user" \| "assistant"; content: string; reasoning?: undefined \| { text: string; label?: undefined \| string }; tools?: undefined \| { type: string; state: "input-streaming" \| "input-available" \| "output-available" \| "output-error"; input?: undefined \| Record<string, unknown>; output?: undefined \| Record<string, unknown>; toolCallId?: undefined \| string; errorText?: undefined \| string }[]; attachments?: undefined \| { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[]; actions?: undefined \| ("copy" \| "like" \| "dislike" \| "regenerate" \| "edit")[] }[]` | The active conversation's message thread, newest last. Set as a JS property. |
|
|
282
|
+
| `value` | `value` | `undefined \| string` | |
|
|
283
|
+
| `placeholder` | `placeholder` | `undefined \| string` | |
|
|
284
|
+
| `loading` | `loading` | `undefined \| false \| true` | |
|
|
285
|
+
| `suggestions` | — | `undefined \| string[]` | |
|
|
286
|
+
| `suggestionMode` | `suggestion-mode` | `undefined \| "submit" \| "fill"` | |
|
|
287
|
+
| `proseSize` | `prose-size` | `undefined \| "xs" \| "sm" \| "base" \| "lg"` | |
|
|
288
|
+
| `codeTheme` | `code-theme` | `undefined \| string` | |
|
|
289
|
+
| `codeHighlight` | `code-highlight` | `undefined \| false \| true` | |
|
|
290
|
+
| `chatTitle` | `chat-title` | `undefined \| string` | |
|
|
291
|
+
| `models` | — | `undefined \| { id: string; name: string; provider?: undefined \| string }[]` | |
|
|
292
|
+
| `currentModel` | `current-model` | `undefined \| string` | |
|
|
293
|
+
| `context` | — | `undefined \| { usedTokens: number; maxTokens: number; inputTokens?: undefined \| number; outputTokens?: undefined \| number; estimatedCost?: undefined \| number }` | |
|
|
294
|
+
| `scrollButton` | `scroll-button` | `undefined \| false \| true` | |
|
|
295
|
+
| `search` | `search` | `undefined \| false \| true` | |
|
|
296
|
+
| `voice` | `voice` | `undefined \| false \| true` | |
|
|
297
|
+
| `slashCommands` | — | `undefined \| { id: string; label: string; description?: undefined \| string; category?: undefined \| string }[]` | |
|
|
298
|
+
| `slashActiveIds` | — | `undefined \| string[]` | |
|
|
299
|
+
| `slashCompact` | `slash-compact` | `undefined \| false \| true` | |
|
|
300
|
+
| `sidebarWidth` | `sidebar-width` | `undefined \| number` | Sidebar default width as a percent of the workspace (default 22). |
|
|
301
|
+
| `sidebarMinWidth` | `sidebar-min-width` | `undefined \| number` | Sidebar min width in px (default 200). |
|
|
302
|
+
| `sidebarMaxWidth` | `sidebar-max-width` | `undefined \| number` | Sidebar max width in px (default 420). |
|
|
303
|
+
| `sidebarCollapsed` | `sidebar-collapsed` | `undefined \| false \| true` | Initial collapsed state of the sidebar (default false). |
|
|
304
|
+
|
|
305
|
+
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
306
|
+
|
|
307
|
+
| Event | `detail` type | Description |
|
|
308
|
+
|---|---|---|
|
|
309
|
+
| `conversationselect` | `CustomEvent<unknown>` | |
|
|
310
|
+
| `messageaction` | `CustomEvent<unknown>` | |
|
|
311
|
+
| `modelchange` | `CustomEvent<unknown>` | |
|
|
312
|
+
| `newchat` | `CustomEvent<unknown>` | |
|
|
313
|
+
| `search` | `CustomEvent<unknown>` | |
|
|
314
|
+
| `sidebartoggle` | `CustomEvent<unknown>` | |
|
|
315
|
+
| `slashselect` | `CustomEvent<unknown>` | |
|
|
316
|
+
| `submit` | `CustomEvent<unknown>` | |
|
|
317
|
+
| `suggestionclick` | `CustomEvent<unknown>` | |
|
|
318
|
+
| `valuechange` | `CustomEvent<unknown>` | |
|
|
319
|
+
| `voice` | `CustomEvent<unknown>` | |
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
272
323
|
### `kitn-checkpoint` / `KitnCheckpoint`
|
|
273
324
|
|
|
274
325
|
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
package/dist/llms/llms.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- AUTO-GENERATED by scripts/gen-llms.mjs — do not edit by hand. Run `npm run build`. -->
|
|
2
2
|
# @kitnai/chat
|
|
3
3
|
|
|
4
|
-
>
|
|
4
|
+
> Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. 28 `kitn-*` custom elements: streaming responses, markdown + code rendering, reasoning/tool panels, attachments, conversation sidebar, voice input. Zero framework dependency for consumers; the SolidJS runtime it is authored in is bundled in, so the host needs nothing.
|
|
5
5
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
@@ -35,7 +35,7 @@ Drop an element into any framework (React, Vue, plain HTML). Data in via JS prop
|
|
|
35
35
|
- `<kitn-prompt-input>` — standalone composer with send button.
|
|
36
36
|
|
|
37
37
|
**Layer 2 — composable primitives** (`import { … } from '@kitnai/chat'`):
|
|
38
|
-
All
|
|
38
|
+
All 28 elements are also exported individually. Use them for custom layouts or features `<kitn-chat>` does not expose (ChainOfThought, FeedbackBar, ThinkingBar, VoiceInput, …). Your bundler tree-shakes the rest.
|
|
39
39
|
|
|
40
40
|
## Key rules for the web components
|
|
41
41
|
|
|
@@ -97,7 +97,7 @@ For Tailwind builds: `@import "@kitnai/chat/theme.css"` in your CSS.
|
|
|
97
97
|
|
|
98
98
|
## Docs
|
|
99
99
|
|
|
100
|
-
- Full element reference (all
|
|
100
|
+
- Full element reference (all 28 elements, every prop/event): ./llms-full.txt — https://kitn.dev/llms-full.txt
|
|
101
101
|
- Machine-readable Custom Elements Manifest: https://unpkg.com/@kitnai/chat/dist/custom-elements.json
|
|
102
102
|
- Working examples: https://github.com/kitn-ai/chat/tree/main/examples
|
|
103
103
|
- Storybook: https://storybook.kitn.dev
|
package/dist/theme.tokens.css
CHANGED
|
@@ -21,7 +21,17 @@
|
|
|
21
21
|
--color-destructive-foreground: var(--kitn-color-destructive-foreground, hsl(0 0% 98%));
|
|
22
22
|
--color-border: var(--kitn-color-border, hsl(240 5.9% 90%));
|
|
23
23
|
--color-input: var(--kitn-color-input, hsl(240 5.9% 90%));
|
|
24
|
-
|
|
24
|
+
/* Focus ring — a deliberate blue (not a neutral) so keyboard focus is always
|
|
25
|
+
obvious and on-brand in both modes. Light uses a strong blue; dark uses a
|
|
26
|
+
brighter one for contrast on dark surfaces. Both clear WCAG 2.1 non-text
|
|
27
|
+
contrast (≥3:1) against the kit's backgrounds. Override via --kitn-color-ring. */
|
|
28
|
+
--color-ring: var(--kitn-color-ring, hsl(217 91% 53%));
|
|
29
|
+
/* Custom scrollbars — thin, rounded, subtle; thumb strengthens on hover. A
|
|
30
|
+
consistent cross-platform look (the kit styles its OWN scroll regions inside
|
|
31
|
+
the shadow root; the track stays transparent so nothing is forced where no
|
|
32
|
+
scrollbar is needed). Override via --kitn-color-scrollbar-thumb(-hover). */
|
|
33
|
+
--color-scrollbar-thumb: var(--kitn-color-scrollbar-thumb, hsl(240 5% 80%));
|
|
34
|
+
--color-scrollbar-thumb-hover: var(--kitn-color-scrollbar-thumb-hover, hsl(240 4% 64%));
|
|
25
35
|
--color-sidebar: var(--kitn-color-sidebar, hsl(0 0% 100%));
|
|
26
36
|
/* Inline `code` accent. Blue text on a translucent blue chip. */
|
|
27
37
|
--color-code-foreground: var(--kitn-color-code-foreground, hsl(224.3 76.3% 48%));
|
|
@@ -86,7 +96,9 @@
|
|
|
86
96
|
--color-destructive-foreground: var(--kitn-color-destructive-foreground, hsl(0 0% 98%));
|
|
87
97
|
--color-border: var(--kitn-color-border, hsl(45 4% 17%));
|
|
88
98
|
--color-input: var(--kitn-color-input, hsl(45 4% 17%));
|
|
89
|
-
--color-ring: var(--kitn-color-ring, hsl(
|
|
99
|
+
--color-ring: var(--kitn-color-ring, hsl(217 91% 68%));
|
|
100
|
+
--color-scrollbar-thumb: var(--kitn-color-scrollbar-thumb, hsl(45 3% 30%));
|
|
101
|
+
--color-scrollbar-thumb-hover: var(--kitn-color-scrollbar-thumb-hover, hsl(45 3% 42%));
|
|
90
102
|
--color-sidebar: var(--kitn-color-sidebar, hsl(50 2% 7%));
|
|
91
103
|
--color-code-foreground: var(--kitn-color-code-foreground, hsl(213 94% 78%));
|
|
92
104
|
|
|
@@ -130,4 +142,20 @@
|
|
|
130
142
|
.chat-markdown th, .chat-markdown td { border: 1px solid var(--color-border); padding: 0.4em 0.6em; text-align: left; }
|
|
131
143
|
.chat-markdown th { font-weight: 600; background: color-mix(in oklab, var(--color-muted-foreground) 8%, transparent); }
|
|
132
144
|
|
|
133
|
-
|
|
145
|
+
/* Cross-platform thin scrollbar utility (Firefox + WebKit). Used by ScrollArea
|
|
146
|
+
and any scroll region in the SolidJS-component build (web components also get
|
|
147
|
+
this globally inside their shadow root — see src/elements/styles.css). */
|
|
148
|
+
.scrollbar-thin {
|
|
149
|
+
scrollbar-width: thin;
|
|
150
|
+
scrollbar-color: var(--color-scrollbar-thumb) transparent;
|
|
151
|
+
}
|
|
152
|
+
.scrollbar-thin::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
153
|
+
.scrollbar-thin::-webkit-scrollbar-track { background: transparent; }
|
|
154
|
+
.scrollbar-thin::-webkit-scrollbar-thumb {
|
|
155
|
+
background-color: var(--color-scrollbar-thumb);
|
|
156
|
+
border-radius: 9999px;
|
|
157
|
+
border: 2px solid transparent;
|
|
158
|
+
background-clip: padding-box;
|
|
159
|
+
}
|
|
160
|
+
.scrollbar-thin::-webkit-scrollbar-thumb:hover { background-color: var(--color-scrollbar-thumb-hover); }
|
|
161
|
+
.scrollbar-thin::-webkit-scrollbar-corner { background: transparent; }
|
|
@@ -111,6 +111,60 @@ export const KitnChatScopePicker = createKitnComponent<KitnChatScopePickerProps>
|
|
|
111
111
|
{ onScopechange: 'scopechange' },
|
|
112
112
|
);
|
|
113
113
|
|
|
114
|
+
export interface KitnChatWorkspaceProps extends KitnBaseProps {
|
|
115
|
+
/** Pre-bucketed conversation groups for the sidebar. Set as a JS property. */
|
|
116
|
+
groups: { id: string; userId?: undefined | string; teamId?: undefined | string; name: string; sortOrder: number; createdAt: string }[];
|
|
117
|
+
/** Flat conversation list (auto-bucketed if `groups` is empty). Set as a JS property. */
|
|
118
|
+
conversations: { id: string; title: string; groupId?: undefined | string; scope: { type: "document" | "collection"; documentId?: undefined | string; filters?: undefined | { tags?: undefined | string[]; authors?: undefined | string[]; contentType?: undefined | "transcript" | "markdown"; dateRange?: undefined | { from: string; to: string } } }; messageCount: number; lastMessageAt: string; updatedAt: string }[];
|
|
119
|
+
/** Id of the open conversation, highlighted in the sidebar. */
|
|
120
|
+
activeId?: string;
|
|
121
|
+
/** The active conversation's message thread, newest last. Set as a JS property. */
|
|
122
|
+
messages: { id: string; role: "user" | "assistant"; content: string; reasoning?: undefined | { text: string; label?: undefined | string }; tools?: undefined | { type: string; state: "input-streaming" | "input-available" | "output-available" | "output-error"; input?: undefined | Record<string, unknown>; output?: undefined | Record<string, unknown>; toolCallId?: undefined | string; errorText?: undefined | string }[]; attachments?: undefined | { id: string; type: "file" | "source-document"; filename?: undefined | string; mediaType?: undefined | string; url?: undefined | string; title?: undefined | string }[]; actions?: undefined | ("copy" | "like" | "dislike" | "regenerate" | "edit")[] }[];
|
|
123
|
+
value?: string;
|
|
124
|
+
placeholder?: string;
|
|
125
|
+
loading?: boolean;
|
|
126
|
+
suggestions?: string[];
|
|
127
|
+
suggestionMode?: "submit" | "fill";
|
|
128
|
+
proseSize?: "xs" | "sm" | "base" | "lg";
|
|
129
|
+
codeTheme?: string;
|
|
130
|
+
codeHighlight?: boolean;
|
|
131
|
+
chatTitle?: string;
|
|
132
|
+
models?: { id: string; name: string; provider?: string }[];
|
|
133
|
+
currentModel?: string;
|
|
134
|
+
context?: { usedTokens: number; maxTokens: number; inputTokens?: number; outputTokens?: number; estimatedCost?: number };
|
|
135
|
+
scrollButton?: boolean;
|
|
136
|
+
search?: boolean;
|
|
137
|
+
voice?: boolean;
|
|
138
|
+
slashCommands?: { id: string; label: string; description?: string; category?: string }[];
|
|
139
|
+
slashActiveIds?: string[];
|
|
140
|
+
slashCompact?: boolean;
|
|
141
|
+
/** Sidebar default width as a percent of the workspace (default 22). */
|
|
142
|
+
sidebarWidth?: number;
|
|
143
|
+
/** Sidebar min width in px (default 200). */
|
|
144
|
+
sidebarMinWidth?: number;
|
|
145
|
+
/** Sidebar max width in px (default 420). */
|
|
146
|
+
sidebarMaxWidth?: number;
|
|
147
|
+
/** Initial collapsed state of the sidebar (default false). */
|
|
148
|
+
sidebarCollapsed?: boolean;
|
|
149
|
+
onConversationselect?: (event: CustomEvent<unknown>) => void;
|
|
150
|
+
onMessageaction?: (event: CustomEvent<unknown>) => void;
|
|
151
|
+
onModelchange?: (event: CustomEvent<unknown>) => void;
|
|
152
|
+
onNewchat?: (event: CustomEvent<unknown>) => void;
|
|
153
|
+
onSearch?: (event: CustomEvent<unknown>) => void;
|
|
154
|
+
onSidebartoggle?: (event: CustomEvent<unknown>) => void;
|
|
155
|
+
onSlashselect?: (event: CustomEvent<unknown>) => void;
|
|
156
|
+
onSubmit?: (event: CustomEvent<unknown>) => void;
|
|
157
|
+
onSuggestionclick?: (event: CustomEvent<unknown>) => void;
|
|
158
|
+
onValuechange?: (event: CustomEvent<unknown>) => void;
|
|
159
|
+
onVoice?: (event: CustomEvent<unknown>) => void;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const KitnChatWorkspace = createKitnComponent<KitnChatWorkspaceProps>(
|
|
163
|
+
'kitn-chat-workspace',
|
|
164
|
+
["theme","groups","conversations","activeId","messages","value","placeholder","loading","suggestions","suggestionMode","proseSize","codeTheme","codeHighlight","chatTitle","models","currentModel","context","scrollButton","search","voice","slashCommands","slashActiveIds","slashCompact","sidebarWidth","sidebarMinWidth","sidebarMaxWidth","sidebarCollapsed"],
|
|
165
|
+
{ onConversationselect: 'conversationselect', onMessageaction: 'messageaction', onModelchange: 'modelchange', onNewchat: 'newchat', onSearch: 'search', onSidebartoggle: 'sidebartoggle', onSlashselect: 'slashselect', onSubmit: 'submit', onSuggestionclick: 'suggestionclick', onValuechange: 'valuechange', onVoice: 'voice' },
|
|
166
|
+
);
|
|
167
|
+
|
|
114
168
|
export interface KitnCheckpointProps extends KitnBaseProps {
|
|
115
169
|
/** Optional text beside the icon. */
|
|
116
170
|
label?: string;
|
package/llms-full.txt
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @kitnai/chat
|
|
5
5
|
|
|
6
|
-
>
|
|
6
|
+
> Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. 28 `kitn-*` custom elements: streaming responses, markdown + code rendering, reasoning/tool panels, attachments, conversation sidebar, voice input. Zero framework dependency for consumers; the SolidJS runtime it is authored in is bundled in, so the host needs nothing.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
@@ -37,7 +37,7 @@ Drop an element into any framework (React, Vue, plain HTML). Data in via JS prop
|
|
|
37
37
|
- `<kitn-prompt-input>` — standalone composer with send button.
|
|
38
38
|
|
|
39
39
|
**Layer 2 — composable primitives** (`import { … } from '@kitnai/chat'`):
|
|
40
|
-
All
|
|
40
|
+
All 28 elements are also exported individually. Use them for custom layouts or features `<kitn-chat>` does not expose (ChainOfThought, FeedbackBar, ThinkingBar, VoiceInput, …). Your bundler tree-shakes the rest.
|
|
41
41
|
|
|
42
42
|
## Key rules for the web components
|
|
43
43
|
|
|
@@ -99,7 +99,7 @@ For Tailwind builds: `@import "@kitnai/chat/theme.css"` in your CSS.
|
|
|
99
99
|
|
|
100
100
|
## Docs
|
|
101
101
|
|
|
102
|
-
- Full element reference (all
|
|
102
|
+
- Full element reference (all 28 elements, every prop/event): ./llms-full.txt — https://kitn.dev/llms-full.txt
|
|
103
103
|
- Machine-readable Custom Elements Manifest: https://unpkg.com/@kitnai/chat/dist/custom-elements.json
|
|
104
104
|
- Working examples: https://github.com/kitn-ai/chat/tree/main/examples
|
|
105
105
|
- Storybook: https://storybook.kitn.dev
|
|
@@ -173,7 +173,7 @@ The same rule applies to every array/object property (`models`, `context`, `sugg
|
|
|
173
173
|
|
|
174
174
|
---
|
|
175
175
|
|
|
176
|
-
## Element reference (
|
|
176
|
+
## Element reference (28 elements, generated from custom-elements.json)
|
|
177
177
|
|
|
178
178
|
Every element also accepts the `theme` attribute. Array/object properties are marked with a `—` attribute: they must be set as JS properties.
|
|
179
179
|
|
|
@@ -269,6 +269,57 @@ _No events._
|
|
|
269
269
|
|
|
270
270
|
---
|
|
271
271
|
|
|
272
|
+
### `kitn-chat-workspace` / `KitnChatWorkspace`
|
|
273
|
+
|
|
274
|
+
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
|
275
|
+
|
|
276
|
+
| Property | Attribute | Type | Description |
|
|
277
|
+
|---|---|---|---|
|
|
278
|
+
| `groups` | — | `{ id: string; userId?: undefined \| string; teamId?: undefined \| string; name: string; sortOrder: number; createdAt: string }[]` | Pre-bucketed conversation groups for the sidebar. Set as a JS property. |
|
|
279
|
+
| `conversations` | — | `{ id: string; title: string; groupId?: undefined \| string; scope: { type: "document" \| "collection"; documentId?: undefined \| string; filters?: undefined \| { tags?: undefined \| string[]; authors?: undefined \| string[]; contentType?: undefined \| "transcript" \| "markdown"; dateRange?: undefined \| { from: string; to: string } } }; messageCount: number; lastMessageAt: string; updatedAt: string }[]` | Flat conversation list (auto-bucketed if `groups` is empty). Set as a JS property. |
|
|
280
|
+
| `activeId` | `active-id` | `undefined \| string` | Id of the open conversation, highlighted in the sidebar. |
|
|
281
|
+
| `messages` | — | `{ id: string; role: "user" \| "assistant"; content: string; reasoning?: undefined \| { text: string; label?: undefined \| string }; tools?: undefined \| { type: string; state: "input-streaming" \| "input-available" \| "output-available" \| "output-error"; input?: undefined \| Record<string, unknown>; output?: undefined \| Record<string, unknown>; toolCallId?: undefined \| string; errorText?: undefined \| string }[]; attachments?: undefined \| { id: string; type: "file" \| "source-document"; filename?: undefined \| string; mediaType?: undefined \| string; url?: undefined \| string; title?: undefined \| string }[]; actions?: undefined \| ("copy" \| "like" \| "dislike" \| "regenerate" \| "edit")[] }[]` | The active conversation's message thread, newest last. Set as a JS property. |
|
|
282
|
+
| `value` | `value` | `undefined \| string` | |
|
|
283
|
+
| `placeholder` | `placeholder` | `undefined \| string` | |
|
|
284
|
+
| `loading` | `loading` | `undefined \| false \| true` | |
|
|
285
|
+
| `suggestions` | — | `undefined \| string[]` | |
|
|
286
|
+
| `suggestionMode` | `suggestion-mode` | `undefined \| "submit" \| "fill"` | |
|
|
287
|
+
| `proseSize` | `prose-size` | `undefined \| "xs" \| "sm" \| "base" \| "lg"` | |
|
|
288
|
+
| `codeTheme` | `code-theme` | `undefined \| string` | |
|
|
289
|
+
| `codeHighlight` | `code-highlight` | `undefined \| false \| true` | |
|
|
290
|
+
| `chatTitle` | `chat-title` | `undefined \| string` | |
|
|
291
|
+
| `models` | — | `undefined \| { id: string; name: string; provider?: undefined \| string }[]` | |
|
|
292
|
+
| `currentModel` | `current-model` | `undefined \| string` | |
|
|
293
|
+
| `context` | — | `undefined \| { usedTokens: number; maxTokens: number; inputTokens?: undefined \| number; outputTokens?: undefined \| number; estimatedCost?: undefined \| number }` | |
|
|
294
|
+
| `scrollButton` | `scroll-button` | `undefined \| false \| true` | |
|
|
295
|
+
| `search` | `search` | `undefined \| false \| true` | |
|
|
296
|
+
| `voice` | `voice` | `undefined \| false \| true` | |
|
|
297
|
+
| `slashCommands` | — | `undefined \| { id: string; label: string; description?: undefined \| string; category?: undefined \| string }[]` | |
|
|
298
|
+
| `slashActiveIds` | — | `undefined \| string[]` | |
|
|
299
|
+
| `slashCompact` | `slash-compact` | `undefined \| false \| true` | |
|
|
300
|
+
| `sidebarWidth` | `sidebar-width` | `undefined \| number` | Sidebar default width as a percent of the workspace (default 22). |
|
|
301
|
+
| `sidebarMinWidth` | `sidebar-min-width` | `undefined \| number` | Sidebar min width in px (default 200). |
|
|
302
|
+
| `sidebarMaxWidth` | `sidebar-max-width` | `undefined \| number` | Sidebar max width in px (default 420). |
|
|
303
|
+
| `sidebarCollapsed` | `sidebar-collapsed` | `undefined \| false \| true` | Initial collapsed state of the sidebar (default false). |
|
|
304
|
+
|
|
305
|
+
**Events** (non-bubbling `CustomEvent`s — listen directly on the element):
|
|
306
|
+
|
|
307
|
+
| Event | `detail` type | Description |
|
|
308
|
+
|---|---|---|
|
|
309
|
+
| `conversationselect` | `CustomEvent<unknown>` | |
|
|
310
|
+
| `messageaction` | `CustomEvent<unknown>` | |
|
|
311
|
+
| `modelchange` | `CustomEvent<unknown>` | |
|
|
312
|
+
| `newchat` | `CustomEvent<unknown>` | |
|
|
313
|
+
| `search` | `CustomEvent<unknown>` | |
|
|
314
|
+
| `sidebartoggle` | `CustomEvent<unknown>` | |
|
|
315
|
+
| `slashselect` | `CustomEvent<unknown>` | |
|
|
316
|
+
| `submit` | `CustomEvent<unknown>` | |
|
|
317
|
+
| `suggestionclick` | `CustomEvent<unknown>` | |
|
|
318
|
+
| `valuechange` | `CustomEvent<unknown>` | |
|
|
319
|
+
| `voice` | `CustomEvent<unknown>` | |
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
272
323
|
### `kitn-checkpoint` / `KitnCheckpoint`
|
|
273
324
|
|
|
274
325
|
**Properties** (every element also accepts `theme="light|dark|auto"`; only scalar props work as HTML attributes):
|
package/llms.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- AUTO-GENERATED by scripts/gen-llms.mjs — do not edit by hand. Run `npm run build`. -->
|
|
2
2
|
# @kitnai/chat
|
|
3
3
|
|
|
4
|
-
>
|
|
4
|
+
> Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. 28 `kitn-*` custom elements: streaming responses, markdown + code rendering, reasoning/tool panels, attachments, conversation sidebar, voice input. Zero framework dependency for consumers; the SolidJS runtime it is authored in is bundled in, so the host needs nothing.
|
|
5
5
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
@@ -35,7 +35,7 @@ Drop an element into any framework (React, Vue, plain HTML). Data in via JS prop
|
|
|
35
35
|
- `<kitn-prompt-input>` — standalone composer with send button.
|
|
36
36
|
|
|
37
37
|
**Layer 2 — composable primitives** (`import { … } from '@kitnai/chat'`):
|
|
38
|
-
All
|
|
38
|
+
All 28 elements are also exported individually. Use them for custom layouts or features `<kitn-chat>` does not expose (ChainOfThought, FeedbackBar, ThinkingBar, VoiceInput, …). Your bundler tree-shakes the rest.
|
|
39
39
|
|
|
40
40
|
## Key rules for the web components
|
|
41
41
|
|
|
@@ -97,7 +97,7 @@ For Tailwind builds: `@import "@kitnai/chat/theme.css"` in your CSS.
|
|
|
97
97
|
|
|
98
98
|
## Docs
|
|
99
99
|
|
|
100
|
-
- Full element reference (all
|
|
100
|
+
- Full element reference (all 28 elements, every prop/event): ./llms-full.txt — https://kitn.dev/llms-full.txt
|
|
101
101
|
- Machine-readable Custom Elements Manifest: https://unpkg.com/@kitnai/chat/dist/custom-elements.json
|
|
102
102
|
- Working examples: https://github.com/kitn-ai/chat/tree/main/examples
|
|
103
103
|
- Storybook: https://storybook.kitn.dev
|
package/package.json
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitnai/chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"web-components",
|
|
8
|
+
"custom-elements",
|
|
9
|
+
"shadow-dom",
|
|
10
|
+
"ai",
|
|
11
|
+
"chat",
|
|
12
|
+
"chatbot",
|
|
13
|
+
"llm",
|
|
14
|
+
"chat-ui",
|
|
15
|
+
"streaming",
|
|
16
|
+
"markdown",
|
|
17
|
+
"framework-agnostic",
|
|
18
|
+
"react",
|
|
19
|
+
"vue",
|
|
20
|
+
"angular",
|
|
21
|
+
"svelte",
|
|
22
|
+
"solid-js"
|
|
23
|
+
],
|
|
6
24
|
"license": "MIT",
|
|
7
25
|
"homepage": "https://github.com/kitn-ai/chat#readme",
|
|
8
26
|
"repository": {
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { createSignal, For, Show } from 'solid-js';
|
|
2
|
+
import { ChatConfig, useChatConfig } from '../primitives/chat-config';
|
|
3
|
+
import { ChatContainer, ChatContainerContent, ChatContainerScrollAnchor } from './chat-container';
|
|
4
|
+
import { Message, MessageContent, MessageActions } from './message';
|
|
5
|
+
import { Reasoning, ReasoningTrigger, ReasoningContent } from './reasoning';
|
|
6
|
+
import { Tool } from './tool';
|
|
7
|
+
import { Attachments, Attachment, AttachmentPreview, AttachmentInfo, type AttachmentData } from './attachments';
|
|
8
|
+
import { ModelSwitcher } from './model-switcher';
|
|
9
|
+
import { ScrollButton } from './scroll-button';
|
|
10
|
+
import {
|
|
11
|
+
Context, ContextTrigger, ContextContent, ContextContentHeader,
|
|
12
|
+
ContextContentBody, ContextContentFooter, ContextInputUsage, ContextOutputUsage,
|
|
13
|
+
} from './context';
|
|
14
|
+
import { Button } from '../ui/button';
|
|
15
|
+
import { Copy, ThumbsUp, ThumbsDown, RefreshCw, Pencil } from 'lucide-solid';
|
|
16
|
+
import type { Component } from 'solid-js';
|
|
17
|
+
import { DefaultPromptInput } from '../elements/default-input';
|
|
18
|
+
import type { SlashCommandItem } from './slash-command';
|
|
19
|
+
import type { ChatMessage, ChatMessageAction } from '../elements/chat-types';
|
|
20
|
+
import type { ProseSize } from '../primitives/chat-config';
|
|
21
|
+
import type { ModelOption } from '../types';
|
|
22
|
+
|
|
23
|
+
export interface ChatThreadContextUsage {
|
|
24
|
+
usedTokens: number;
|
|
25
|
+
maxTokens: number;
|
|
26
|
+
inputTokens?: number;
|
|
27
|
+
outputTokens?: number;
|
|
28
|
+
estimatedCost?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ChatThreadProps {
|
|
32
|
+
/** Extra classes for the thread root (e.g. `h-full`). */
|
|
33
|
+
class?: string;
|
|
34
|
+
/** The full message thread to render, newest last. Each entry carries its role,
|
|
35
|
+
* content, and optional reasoning/tools/attachments/actions. Set as a JS
|
|
36
|
+
* property (`el.messages = [...]`). */
|
|
37
|
+
messages: ChatMessage[];
|
|
38
|
+
/** Controlled value of the input. When set, the host owns the input text and
|
|
39
|
+
* must update it on `valuechange`; leave unset for uncontrolled behavior. */
|
|
40
|
+
value?: string;
|
|
41
|
+
/** Placeholder text shown in the empty input. */
|
|
42
|
+
placeholder?: string;
|
|
43
|
+
/** When true, shows the loading/streaming state and disables submit (use while
|
|
44
|
+
* awaiting the assistant's reply). */
|
|
45
|
+
loading?: boolean;
|
|
46
|
+
/** Starter prompts shown above the input when the thread is empty. Clicking one
|
|
47
|
+
* follows `suggestionMode`. Set as a JS property. */
|
|
48
|
+
suggestions?: string[];
|
|
49
|
+
/** What clicking a suggestion does: `'submit'` (default) sends it immediately
|
|
50
|
+
* as if typed and submitted; `'fill'` just places it in the input. */
|
|
51
|
+
suggestionMode?: 'submit' | 'fill';
|
|
52
|
+
/** Body/prose font scale for rendered markdown (`'xs' | 'sm' | 'base' | 'lg'`).
|
|
53
|
+
* Defaults to `'sm'`. */
|
|
54
|
+
proseSize?: ProseSize;
|
|
55
|
+
/** Shiki theme name for syntax-highlighted code blocks (e.g.
|
|
56
|
+
* `'github-dark-dimmed'`). */
|
|
57
|
+
codeTheme?: string;
|
|
58
|
+
/** Enable Shiki syntax highlighting in code blocks. Turn off to render plain
|
|
59
|
+
* `<pre>` blocks (lighter, no highlighter load). Default true. */
|
|
60
|
+
codeHighlight?: boolean;
|
|
61
|
+
/** Optional header title shown on the left of the header. */
|
|
62
|
+
chatTitle?: string;
|
|
63
|
+
/** Optional model list. When set (>1 model) a ModelSwitcher is shown in the
|
|
64
|
+
* header and a `modelchange` event fires on selection. */
|
|
65
|
+
models?: ModelOption[];
|
|
66
|
+
/** The currently selected model id (pairs with `models`). */
|
|
67
|
+
currentModel?: string;
|
|
68
|
+
/** Optional context-window token usage. When set, a Context token meter is
|
|
69
|
+
* shown in the header. */
|
|
70
|
+
context?: ChatThreadContextUsage;
|
|
71
|
+
/** Show the scroll-to-bottom button inside the scroll area. Default true. */
|
|
72
|
+
scrollButton?: boolean;
|
|
73
|
+
/** Show a Search (Globe) button in the input toolbar; fires a `search` event. */
|
|
74
|
+
search?: boolean;
|
|
75
|
+
/** Show a Voice (Mic) button in the input toolbar; fires a `voice` event. */
|
|
76
|
+
voice?: boolean;
|
|
77
|
+
/** Slash commands — when set, typing `/` in the input opens the command
|
|
78
|
+
* palette and fires `slashselect`. Set as a JS property. */
|
|
79
|
+
slashCommands?: SlashCommandItem[];
|
|
80
|
+
/** Command ids to highlight as active in the palette. */
|
|
81
|
+
slashActiveIds?: string[];
|
|
82
|
+
/** Single-line palette rows. */
|
|
83
|
+
slashCompact?: boolean;
|
|
84
|
+
// callbacks (the facade maps these to dispatch())
|
|
85
|
+
onValueChange?: (value: string) => void;
|
|
86
|
+
onSubmit?: (detail: { value: string; attachments: AttachmentData[] }) => void;
|
|
87
|
+
onSuggestionClick?: (value: string) => void;
|
|
88
|
+
onModelChange?: (modelId: string) => void;
|
|
89
|
+
onMessageAction?: (detail: { messageId: string; action: ChatMessageAction }) => void;
|
|
90
|
+
onSearch?: () => void;
|
|
91
|
+
onVoice?: () => void;
|
|
92
|
+
onSlashSelect?: (command: SlashCommandItem) => void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const ACTION_LABEL: Record<ChatMessageAction, string> = {
|
|
96
|
+
copy: 'Copy', like: 'Like', dislike: 'Dislike', regenerate: 'Regenerate', edit: 'Edit',
|
|
97
|
+
};
|
|
98
|
+
const ACTION_ICON: Record<ChatMessageAction, Component<{ class?: string }>> = {
|
|
99
|
+
copy: Copy, like: ThumbsUp, dislike: ThumbsDown, regenerate: RefreshCw, edit: Pencil,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export function ChatThread(props: ChatThreadProps) {
|
|
103
|
+
const outer = useChatConfig();
|
|
104
|
+
const [internal, setInternal] = createSignal(props.value ?? '');
|
|
105
|
+
const [attachments, setAttachments] = createSignal<AttachmentData[]>([]);
|
|
106
|
+
const current = () => props.value ?? internal();
|
|
107
|
+
const handleChange = (v: string) => { setInternal(v); props.onValueChange?.(v); };
|
|
108
|
+
const handleSubmit = () => { props.onSubmit?.({ value: current(), attachments: attachments() }); setAttachments([]); };
|
|
109
|
+
const handleSuggestionClick = (v: string) => {
|
|
110
|
+
if ((props.suggestionMode ?? 'submit') === 'fill') { handleChange(v); props.onSuggestionClick?.(v); }
|
|
111
|
+
else { props.onSubmit?.({ value: v, attachments: attachments() }); setAttachments([]); }
|
|
112
|
+
};
|
|
113
|
+
const showHeader = () => !!(props.chatTitle || props.models || props.context);
|
|
114
|
+
const showScrollButton = () => props.scrollButton !== false;
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<ChatConfig proseSize={props.proseSize} codeTheme={props.codeTheme} codeHighlight={props.codeHighlight !== false} portalMount={outer.portalMount()}>
|
|
118
|
+
<div class={`flex h-full flex-col bg-background ${props.class ?? ''}`}>
|
|
119
|
+
<Show when={showHeader()}>
|
|
120
|
+
<header class="flex h-14 shrink-0 items-center justify-between border-b border-border px-5">
|
|
121
|
+
<div class="text-sm font-semibold text-foreground">{props.chatTitle}</div>
|
|
122
|
+
<div class="flex items-center gap-2">
|
|
123
|
+
<Show when={props.models}>
|
|
124
|
+
<ModelSwitcher
|
|
125
|
+
models={props.models!}
|
|
126
|
+
currentModelId={props.currentModel ?? props.models![0]?.id ?? ''}
|
|
127
|
+
onModelChange={(modelId) => props.onModelChange?.(modelId)}
|
|
128
|
+
/>
|
|
129
|
+
</Show>
|
|
130
|
+
<Show when={props.context}>
|
|
131
|
+
<Context
|
|
132
|
+
usedTokens={props.context!.usedTokens} maxTokens={props.context!.maxTokens}
|
|
133
|
+
inputTokens={props.context!.inputTokens} outputTokens={props.context!.outputTokens}
|
|
134
|
+
estimatedCost={props.context!.estimatedCost}
|
|
135
|
+
>
|
|
136
|
+
<ContextTrigger />
|
|
137
|
+
<ContextContent>
|
|
138
|
+
<ContextContentHeader />
|
|
139
|
+
<ContextContentBody><div class="space-y-1.5"><ContextInputUsage /><ContextOutputUsage /></div></ContextContentBody>
|
|
140
|
+
<ContextContentFooter />
|
|
141
|
+
</ContextContent>
|
|
142
|
+
</Context>
|
|
143
|
+
</Show>
|
|
144
|
+
</div>
|
|
145
|
+
</header>
|
|
146
|
+
</Show>
|
|
147
|
+
<div class="relative flex-1 overflow-hidden">
|
|
148
|
+
<ChatContainer class="h-full px-4 py-3">
|
|
149
|
+
<ChatContainerContent class="mx-auto w-full max-w-3xl space-y-4">
|
|
150
|
+
<For each={props.messages}>
|
|
151
|
+
{(m) => (
|
|
152
|
+
<Message class={m.role === 'user' ? 'flex-col items-end' : 'flex-col items-start'}>
|
|
153
|
+
<Show when={m.reasoning}>
|
|
154
|
+
<Reasoning class="mb-2 w-full">
|
|
155
|
+
<ReasoningTrigger>{m.reasoning!.label ?? 'Reasoning'}</ReasoningTrigger>
|
|
156
|
+
<ReasoningContent markdown>{m.reasoning!.text}</ReasoningContent>
|
|
157
|
+
</Reasoning>
|
|
158
|
+
</Show>
|
|
159
|
+
<For each={m.tools ?? []}>{(tp) => <Tool toolPart={tp} class="mb-2 w-full" />}</For>
|
|
160
|
+
<Show when={m.attachments?.length}>
|
|
161
|
+
<Attachments variant="inline" class={m.role === 'user' ? 'mb-2 justify-end' : 'mb-2'}>
|
|
162
|
+
<For each={m.attachments!}>
|
|
163
|
+
{(att) => (<Attachment data={att}><AttachmentPreview /><AttachmentInfo /></Attachment>)}
|
|
164
|
+
</For>
|
|
165
|
+
</Attachments>
|
|
166
|
+
</Show>
|
|
167
|
+
<MessageContent
|
|
168
|
+
markdown={m.role === 'assistant'}
|
|
169
|
+
class={m.role === 'user' ? 'bg-muted text-primary max-w-[85%] rounded-2xl px-4 py-2' : 'bg-transparent p-0'}
|
|
170
|
+
>
|
|
171
|
+
{m.content}
|
|
172
|
+
</MessageContent>
|
|
173
|
+
<Show when={m.actions?.length}>
|
|
174
|
+
<MessageActions class="mt-1 flex gap-0">
|
|
175
|
+
<For each={m.actions!}>
|
|
176
|
+
{(a) => (
|
|
177
|
+
<Button
|
|
178
|
+
variant="ghost" size="icon-sm" class="rounded-full"
|
|
179
|
+
data-action={a} aria-label={ACTION_LABEL[a]}
|
|
180
|
+
onClick={() => props.onMessageAction?.({ messageId: m.id, action: a })}
|
|
181
|
+
>
|
|
182
|
+
{(() => { const Icon = ACTION_ICON[a]; return <Icon class="size-3.5" />; })()}
|
|
183
|
+
</Button>
|
|
184
|
+
)}
|
|
185
|
+
</For>
|
|
186
|
+
</MessageActions>
|
|
187
|
+
</Show>
|
|
188
|
+
</Message>
|
|
189
|
+
)}
|
|
190
|
+
</For>
|
|
191
|
+
<ChatContainerScrollAnchor />
|
|
192
|
+
</ChatContainerContent>
|
|
193
|
+
<Show when={showScrollButton()}>
|
|
194
|
+
<div class="absolute bottom-4 left-1/2 flex w-full max-w-3xl -translate-x-1/2 justify-center px-5">
|
|
195
|
+
<ScrollButton class="shadow-sm" />
|
|
196
|
+
</div>
|
|
197
|
+
</Show>
|
|
198
|
+
</ChatContainer>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="shrink-0 px-4 pb-4">
|
|
201
|
+
<div class="mx-auto max-w-3xl">
|
|
202
|
+
<DefaultPromptInput
|
|
203
|
+
value={current()} placeholder={props.placeholder} loading={props.loading === true}
|
|
204
|
+
suggestions={props.suggestions} attachments={attachments()}
|
|
205
|
+
search={props.search === true} voice={props.voice === true}
|
|
206
|
+
slashCommands={props.slashCommands} slashActiveIds={props.slashActiveIds} slashCompact={props.slashCompact === true}
|
|
207
|
+
onValueChange={handleChange} onSubmit={handleSubmit} onSuggestionClick={handleSuggestionClick}
|
|
208
|
+
onAttachmentsChange={setAttachments}
|
|
209
|
+
onSearch={() => props.onSearch?.()} onVoice={() => props.onVoice?.()}
|
|
210
|
+
onSlashSelect={(command) => props.onSlashSelect?.(command)}
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
</ChatConfig>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
@@ -72,7 +72,12 @@ function PromptInput(props: PromptInputProps) {
|
|
|
72
72
|
<div
|
|
73
73
|
onClick={handleClick}
|
|
74
74
|
class={cn(
|
|
75
|
+
// The inner textarea neutralizes its own ring (focus-visible:ring-0),
|
|
76
|
+
// so the FRAME owns the focus affordance: a blue ring whenever a
|
|
77
|
+
// control inside it is focused. Without this the composer had no
|
|
78
|
+
// visible keyboard-focus state.
|
|
75
79
|
'bg-muted/40 cursor-text rounded-xl p-2 shadow-xs',
|
|
80
|
+
'focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-0',
|
|
76
81
|
local.disabled && 'cursor-not-allowed opacity-60',
|
|
77
82
|
local.class
|
|
78
83
|
)}
|