@kitnai/chat 0.1.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/LICENSE +21 -0
- package/README.md +314 -0
- package/dist/bash-InADTalH.js +6 -0
- package/dist/core-AYMC6_lb.js +5874 -0
- package/dist/engine-javascript-vq0WuIJl.js +2643 -0
- package/dist/github-dark-dimmed-DUshB20C.js +4 -0
- package/dist/github-light-JYsPkUQd.js +4 -0
- package/dist/javascript-C25yR2R2.js +6 -0
- package/dist/json-DxJze_jm.js +6 -0
- package/dist/kitn-chat.es.js +6632 -0
- package/dist/tsx-B8rCNbgL.js +6 -0
- package/dist/typescript-RycA9KXf.js +6 -0
- package/package.json +80 -0
- package/src/components/attachments.stories.tsx +304 -0
- package/src/components/attachments.tsx +394 -0
- package/src/components/chain-of-thought.stories.tsx +212 -0
- package/src/components/chain-of-thought.tsx +139 -0
- package/src/components/chat-container.stories.tsx +188 -0
- package/src/components/chat-container.tsx +78 -0
- package/src/components/chat-scope-picker.tsx +47 -0
- package/src/components/checkpoint.stories.tsx +103 -0
- package/src/components/checkpoint.tsx +81 -0
- package/src/components/code-block.stories.tsx +151 -0
- package/src/components/code-block.tsx +99 -0
- package/src/components/context.stories.tsx +180 -0
- package/src/components/context.tsx +323 -0
- package/src/components/conversation-item.stories.tsx +126 -0
- package/src/components/conversation-item.tsx +18 -0
- package/src/components/conversation-list.stories.tsx +134 -0
- package/src/components/conversation-list.tsx +100 -0
- package/src/components/empty.stories.tsx +435 -0
- package/src/components/empty.tsx +166 -0
- package/src/components/feedback-bar.stories.tsx +101 -0
- package/src/components/feedback-bar.tsx +58 -0
- package/src/components/file-upload.stories.tsx +157 -0
- package/src/components/file-upload.tsx +161 -0
- package/src/components/image.stories.tsx +90 -0
- package/src/components/image.tsx +67 -0
- package/src/components/loader.stories.tsx +182 -0
- package/src/components/loader.tsx +333 -0
- package/src/components/markdown.stories.tsx +181 -0
- package/src/components/markdown.tsx +81 -0
- package/src/components/message-narrow.stories.tsx +330 -0
- package/src/components/message-skills.stories.tsx +212 -0
- package/src/components/message-skills.tsx +36 -0
- package/src/components/message.stories.tsx +282 -0
- package/src/components/message.tsx +149 -0
- package/src/components/model-switcher.stories.tsx +98 -0
- package/src/components/model-switcher.tsx +36 -0
- package/src/components/prompt-input.stories.tsx +223 -0
- package/src/components/prompt-input.tsx +190 -0
- package/src/components/prompt-suggestion.stories.tsx +143 -0
- package/src/components/prompt-suggestion.tsx +115 -0
- package/src/components/reasoning.stories.tsx +141 -0
- package/src/components/reasoning.tsx +157 -0
- package/src/components/response-stream.tsx +103 -0
- package/src/components/scroll-button.stories.tsx +101 -0
- package/src/components/scroll-button.tsx +33 -0
- package/src/components/slash-command.stories.tsx +164 -0
- package/src/components/slash-command.tsx +223 -0
- package/src/components/source.stories.tsx +125 -0
- package/src/components/source.tsx +129 -0
- package/src/components/text-shimmer.stories.tsx +88 -0
- package/src/components/text-shimmer.tsx +37 -0
- package/src/components/thinking-bar.stories.tsx +88 -0
- package/src/components/thinking-bar.tsx +50 -0
- package/src/components/tool.stories.tsx +154 -0
- package/src/components/tool.tsx +173 -0
- package/src/components/voice-input.stories.tsx +84 -0
- package/src/components/voice-input.tsx +103 -0
- package/src/elements/chat-types.ts +14 -0
- package/src/elements/chat.tsx +111 -0
- package/src/elements/compiled.css +2 -0
- package/src/elements/conversation-list.tsx +26 -0
- package/src/elements/css.ts +5 -0
- package/src/elements/default-input.tsx +53 -0
- package/src/elements/define.tsx +54 -0
- package/src/elements/kitn-chat.stories.tsx +105 -0
- package/src/elements/kitn-conversation-list.stories.tsx +177 -0
- package/src/elements/kitn-prompt-input.stories.tsx +123 -0
- package/src/elements/prompt-input.tsx +39 -0
- package/src/elements/register.ts +9 -0
- package/src/elements/styles.css +12 -0
- package/src/index.ts +128 -0
- package/src/primitives/chat-config.tsx +76 -0
- package/src/primitives/highlighter.ts +150 -0
- package/src/primitives/use-auto-resize.ts +31 -0
- package/src/primitives/use-stick-to-bottom.ts +43 -0
- package/src/primitives/use-text-stream.ts +112 -0
- package/src/primitives/use-voice-recorder.ts +50 -0
- package/src/stories/chat-panel-layout.stories.tsx +144 -0
- package/src/stories/chat-scene.tsx +570 -0
- package/src/stories/checkpoint-restore.stories.tsx +224 -0
- package/src/stories/context-usage.stories.tsx +155 -0
- package/src/stories/conversation-with-reasoning.stories.tsx +151 -0
- package/src/stories/conversation-with-sources.stories.tsx +165 -0
- package/src/stories/docs/GettingStarted.mdx +76 -0
- package/src/stories/docs/Installation.mdx +48 -0
- package/src/stories/docs/Integrations.mdx +110 -0
- package/src/stories/docs/Introduction.mdx +29 -0
- package/src/stories/docs/Theming.mdx +87 -0
- package/src/stories/docs/theme-editor/canvas.tsx +32 -0
- package/src/stories/docs/theme-editor/inspector.tsx +66 -0
- package/src/stories/docs/theme-editor/presets.test.ts +32 -0
- package/src/stories/docs/theme-editor/presets.ts +64 -0
- package/src/stories/docs/theme-editor/theme-css.test.ts +19 -0
- package/src/stories/docs/theme-editor/theme-css.ts +15 -0
- package/src/stories/docs/theme-editor/theme-editor.tsx +145 -0
- package/src/stories/docs/theme-tokens.tsx +174 -0
- package/src/stories/full-chat.stories.tsx +18 -0
- package/src/stories/message-actions.stories.tsx +167 -0
- package/src/stories/prompt-input-variants.stories.tsx +179 -0
- package/src/stories/streaming-response.stories.tsx +234 -0
- package/src/stories/theme-editor.stories.tsx +16 -0
- package/src/stories/token-reference.stories.tsx +18 -0
- package/src/types.ts +41 -0
- package/src/ui/avatar.stories.tsx +104 -0
- package/src/ui/avatar.tsx +23 -0
- package/src/ui/badge.stories.tsx +87 -0
- package/src/ui/badge.tsx +21 -0
- package/src/ui/button.stories.tsx +146 -0
- package/src/ui/button.tsx +37 -0
- package/src/ui/collapsible.tsx +14 -0
- package/src/ui/dialog.tsx +21 -0
- package/src/ui/dropdown.tsx +26 -0
- package/src/ui/hover-card.tsx +48 -0
- package/src/ui/resizable.stories.tsx +171 -0
- package/src/ui/resizable.tsx +219 -0
- package/src/ui/scroll-area.tsx +13 -0
- package/src/ui/separator.stories.tsx +82 -0
- package/src/ui/separator.tsx +10 -0
- package/src/ui/skeleton.stories.tsx +338 -0
- package/src/ui/skeleton.tsx +16 -0
- package/src/ui/textarea.tsx +21 -0
- package/src/ui/tooltip.stories.tsx +75 -0
- package/src/ui/tooltip.tsx +22 -0
- package/src/utils/cn.ts +6 -0
- package/theme.css +115 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { createSignal, For } from 'solid-js';
|
|
3
|
+
import {
|
|
4
|
+
PromptInput, PromptInputTextarea, PromptInputActions,
|
|
5
|
+
PromptSuggestion, ModelSwitcher, Loader, Button,
|
|
6
|
+
} from '../index';
|
|
7
|
+
import type { ModelOption } from '../types';
|
|
8
|
+
import { ArrowUp, Paperclip, Globe, Mic, Square, Sparkles } from 'lucide-solid';
|
|
9
|
+
|
|
10
|
+
const meta: Meta = {
|
|
11
|
+
title: 'Examples/Prompt Input Variants',
|
|
12
|
+
parameters: { layout: 'padded' },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj;
|
|
17
|
+
|
|
18
|
+
export const BasicInput: Story = {
|
|
19
|
+
name: 'Basic Input',
|
|
20
|
+
render: () => {
|
|
21
|
+
const [value, setValue] = createSignal('');
|
|
22
|
+
return (
|
|
23
|
+
<div class="w-full max-w-2xl p-4">
|
|
24
|
+
<PromptInput value={value()} onValueChange={setValue} onSubmit={() => setValue('')}>
|
|
25
|
+
<PromptInputTextarea placeholder="Ask anything..." />
|
|
26
|
+
<PromptInputActions class="justify-end">
|
|
27
|
+
<Button variant="default" size="icon-sm" class="rounded-full" disabled={!value()}>
|
|
28
|
+
<ArrowUp class="size-4" />
|
|
29
|
+
</Button>
|
|
30
|
+
</PromptInputActions>
|
|
31
|
+
</PromptInput>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const WithSuggestions: Story = {
|
|
38
|
+
name: 'With Suggestion Chips',
|
|
39
|
+
render: () => {
|
|
40
|
+
const [value, setValue] = createSignal('');
|
|
41
|
+
|
|
42
|
+
const suggestionGroups = [
|
|
43
|
+
{
|
|
44
|
+
label: 'Get started',
|
|
45
|
+
items: ['Summarize this document', 'What are the key takeaways?', 'Create an outline'],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
label: 'Go deeper',
|
|
49
|
+
items: ['Compare with similar approaches', 'What are the tradeoffs?', 'Find contradictions'],
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div class="w-full max-w-2xl p-4 space-y-4">
|
|
55
|
+
<For each={suggestionGroups}>
|
|
56
|
+
{(group) => (
|
|
57
|
+
<div class="space-y-2">
|
|
58
|
+
<span class="text-xs font-medium text-muted-foreground uppercase tracking-wider">{group.label}</span>
|
|
59
|
+
<div class="flex flex-wrap gap-2">
|
|
60
|
+
<For each={group.items}>
|
|
61
|
+
{(item) => (
|
|
62
|
+
<PromptSuggestion onClick={() => setValue(item)}>
|
|
63
|
+
{item}
|
|
64
|
+
</PromptSuggestion>
|
|
65
|
+
)}
|
|
66
|
+
</For>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</For>
|
|
71
|
+
|
|
72
|
+
<PromptInput value={value()} onValueChange={setValue} onSubmit={() => setValue('')}>
|
|
73
|
+
<PromptInputTextarea placeholder="Ask about this document..." />
|
|
74
|
+
<PromptInputActions class="justify-end">
|
|
75
|
+
<Button variant="default" size="icon-sm" class="rounded-full" disabled={!value()}>
|
|
76
|
+
<ArrowUp class="size-4" />
|
|
77
|
+
</Button>
|
|
78
|
+
</PromptInputActions>
|
|
79
|
+
</PromptInput>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const WithActionButtons: Story = {
|
|
86
|
+
name: 'With Action Buttons',
|
|
87
|
+
render: () => {
|
|
88
|
+
const [value, setValue] = createSignal('');
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div class="w-full max-w-2xl p-4">
|
|
92
|
+
<PromptInput value={value()} onValueChange={setValue} onSubmit={() => setValue('')}>
|
|
93
|
+
<PromptInputTextarea placeholder="Message..." />
|
|
94
|
+
<PromptInputActions class="justify-between">
|
|
95
|
+
<div class="flex items-center gap-1">
|
|
96
|
+
<Button variant="ghost" size="icon-sm"><Paperclip class="size-4 text-muted-foreground" /></Button>
|
|
97
|
+
<Button variant="ghost" size="icon-sm"><Globe class="size-4 text-muted-foreground" /></Button>
|
|
98
|
+
<Button variant="ghost" size="icon-sm"><Mic class="size-4 text-muted-foreground" /></Button>
|
|
99
|
+
<Button variant="ghost" size="icon-sm"><Sparkles class="size-4 text-muted-foreground" /></Button>
|
|
100
|
+
</div>
|
|
101
|
+
<Button variant="default" size="icon-sm" class="rounded-full" disabled={!value()}>
|
|
102
|
+
<ArrowUp class="size-4" />
|
|
103
|
+
</Button>
|
|
104
|
+
</PromptInputActions>
|
|
105
|
+
</PromptInput>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export const StreamingState: Story = {
|
|
112
|
+
name: 'Streaming / Loading State',
|
|
113
|
+
render: () => (
|
|
114
|
+
<div class="w-full max-w-2xl p-4 space-y-6">
|
|
115
|
+
<div>
|
|
116
|
+
<p class="text-sm text-muted-foreground mb-2">Disabled while streaming (with stop button)</p>
|
|
117
|
+
<PromptInput disabled isLoading>
|
|
118
|
+
<PromptInputTextarea placeholder="Generating response..." />
|
|
119
|
+
<PromptInputActions class="justify-between">
|
|
120
|
+
<div class="flex items-center gap-2">
|
|
121
|
+
<Loader variant="typing" size="sm" />
|
|
122
|
+
<span class="text-xs text-muted-foreground">Generating...</span>
|
|
123
|
+
</div>
|
|
124
|
+
<Button variant="outline" size="icon-sm" class="rounded-full">
|
|
125
|
+
<Square class="size-3" />
|
|
126
|
+
</Button>
|
|
127
|
+
</PromptInputActions>
|
|
128
|
+
</PromptInput>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div>
|
|
132
|
+
<p class="text-sm text-muted-foreground mb-2">Disabled while waiting for first token</p>
|
|
133
|
+
<PromptInput disabled isLoading>
|
|
134
|
+
<PromptInputTextarea placeholder="Waiting for response..." />
|
|
135
|
+
<PromptInputActions class="justify-between">
|
|
136
|
+
<div class="flex items-center gap-2">
|
|
137
|
+
<Loader variant="dots" size="sm" />
|
|
138
|
+
<span class="text-xs text-muted-foreground">Thinking...</span>
|
|
139
|
+
</div>
|
|
140
|
+
<Button variant="outline" size="icon-sm" class="rounded-full">
|
|
141
|
+
<Square class="size-3" />
|
|
142
|
+
</Button>
|
|
143
|
+
</PromptInputActions>
|
|
144
|
+
</PromptInput>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const WithModelSelector: Story = {
|
|
151
|
+
name: 'With Model Selector',
|
|
152
|
+
render: () => {
|
|
153
|
+
const [value, setValue] = createSignal('');
|
|
154
|
+
const [modelId, setModelId] = createSignal('claude-4');
|
|
155
|
+
|
|
156
|
+
const models: ModelOption[] = [
|
|
157
|
+
{ id: 'claude-4', name: 'Claude 4 Opus', provider: 'Anthropic' },
|
|
158
|
+
{ id: 'claude-4-sonnet', name: 'Claude 4 Sonnet', provider: 'Anthropic' },
|
|
159
|
+
{ id: 'gemini-2', name: 'Gemini 2.5 Pro', provider: 'Google' },
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div class="w-full max-w-2xl p-4">
|
|
164
|
+
<PromptInput value={value()} onValueChange={setValue} onSubmit={() => setValue('')}>
|
|
165
|
+
<PromptInputTextarea placeholder="Ask anything..." />
|
|
166
|
+
<PromptInputActions class="justify-between">
|
|
167
|
+
<ModelSwitcher models={models} currentModelId={modelId()} onModelChange={setModelId} />
|
|
168
|
+
<div class="flex items-center gap-1">
|
|
169
|
+
<Button variant="ghost" size="icon-sm"><Paperclip class="size-4 text-muted-foreground" /></Button>
|
|
170
|
+
<Button variant="default" size="icon-sm" class="rounded-full" disabled={!value()}>
|
|
171
|
+
<ArrowUp class="size-4" />
|
|
172
|
+
</Button>
|
|
173
|
+
</div>
|
|
174
|
+
</PromptInputActions>
|
|
175
|
+
</PromptInput>
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
},
|
|
179
|
+
};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { createSignal, Show } from 'solid-js';
|
|
3
|
+
import {
|
|
4
|
+
ChatContainer, ChatContainerContent, ChatContainerScrollAnchor,
|
|
5
|
+
Message, MessageAvatar, MessageContent, MessageActions,
|
|
6
|
+
PromptInput, PromptInputTextarea, PromptInputActions,
|
|
7
|
+
ResponseStream, Loader, TextShimmer, Button, Separator,
|
|
8
|
+
} from '../index';
|
|
9
|
+
import { Square, ArrowUp } from 'lucide-solid';
|
|
10
|
+
|
|
11
|
+
const meta: Meta = {
|
|
12
|
+
title: 'Examples/Streaming Response',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj;
|
|
17
|
+
|
|
18
|
+
const streamedText = `**Server-Sent Events (SSE)** are a lightweight alternative to WebSockets for one-way server-to-client streaming.
|
|
19
|
+
|
|
20
|
+
### How SSE Works
|
|
21
|
+
|
|
22
|
+
The server sends a continuous stream of text data over a single HTTP connection. The browser's \`EventSource\` API handles reconnection automatically.
|
|
23
|
+
|
|
24
|
+
\`\`\`typescript
|
|
25
|
+
// Server (Node.js/Express)
|
|
26
|
+
app.get('/stream', (req, res) => {
|
|
27
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
28
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
29
|
+
res.setHeader('Connection', 'keep-alive');
|
|
30
|
+
|
|
31
|
+
const interval = setInterval(() => {
|
|
32
|
+
res.write(\`data: \${JSON.stringify({ time: Date.now() })}\\n\\n\`);
|
|
33
|
+
}, 1000);
|
|
34
|
+
|
|
35
|
+
req.on('close', () => clearInterval(interval));
|
|
36
|
+
});
|
|
37
|
+
\`\`\`
|
|
38
|
+
|
|
39
|
+
\`\`\`typescript
|
|
40
|
+
// Client
|
|
41
|
+
const source = new EventSource('/stream');
|
|
42
|
+
source.onmessage = (event) => {
|
|
43
|
+
const data = JSON.parse(event.data);
|
|
44
|
+
console.log('Received:', data);
|
|
45
|
+
};
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
### SSE vs WebSocket
|
|
49
|
+
|
|
50
|
+
| Feature | SSE | WebSocket |
|
|
51
|
+
|---------|-----|-----------|
|
|
52
|
+
| Direction | Server to client only | Bidirectional |
|
|
53
|
+
| Protocol | HTTP | WS |
|
|
54
|
+
| Reconnection | Automatic | Manual |
|
|
55
|
+
| Binary data | No | Yes |
|
|
56
|
+
|
|
57
|
+
Use SSE when you only need server push -- it's simpler to implement, works through proxies, and the browser handles reconnection for you.`;
|
|
58
|
+
|
|
59
|
+
export const TypewriterStream: Story = {
|
|
60
|
+
name: 'Typewriter Streaming',
|
|
61
|
+
render: () => {
|
|
62
|
+
const [isStreaming, setIsStreaming] = createSignal(false);
|
|
63
|
+
|
|
64
|
+
const startStream = () => {
|
|
65
|
+
setIsStreaming(true);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div class="flex flex-col h-[600px] w-full max-w-2xl bg-background rounded-xl shadow-lg overflow-hidden">
|
|
70
|
+
<div class="flex items-center px-4 py-3">
|
|
71
|
+
<h2 class="text-sm font-semibold text-foreground">Streaming Demo</h2>
|
|
72
|
+
</div>
|
|
73
|
+
<Separator />
|
|
74
|
+
|
|
75
|
+
<ChatContainer class="flex-1 p-4">
|
|
76
|
+
<ChatContainerContent class="space-y-6 py-4">
|
|
77
|
+
<Message>
|
|
78
|
+
<MessageAvatar src="" fallback="U" alt="User" />
|
|
79
|
+
<MessageContent>
|
|
80
|
+
Explain Server-Sent Events and when to use them over WebSockets.
|
|
81
|
+
</MessageContent>
|
|
82
|
+
</Message>
|
|
83
|
+
|
|
84
|
+
<Show when={isStreaming()}>
|
|
85
|
+
<Message>
|
|
86
|
+
<MessageAvatar src="" fallback="AI" alt="Assistant" />
|
|
87
|
+
<div class="flex-1 rounded-lg p-2 bg-secondary">
|
|
88
|
+
<ResponseStream
|
|
89
|
+
textStream={streamedText}
|
|
90
|
+
mode="typewriter"
|
|
91
|
+
speed={40}
|
|
92
|
+
onComplete={() => setIsStreaming(false)}
|
|
93
|
+
class="prose dark:prose-invert prose-sm max-w-none"
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
</Message>
|
|
97
|
+
</Show>
|
|
98
|
+
|
|
99
|
+
<ChatContainerScrollAnchor />
|
|
100
|
+
</ChatContainerContent>
|
|
101
|
+
</ChatContainer>
|
|
102
|
+
|
|
103
|
+
<div class="px-4 pb-4">
|
|
104
|
+
<Show
|
|
105
|
+
when={isStreaming()}
|
|
106
|
+
fallback={
|
|
107
|
+
<PromptInput onSubmit={startStream}>
|
|
108
|
+
<PromptInputTextarea placeholder="Click send to start streaming..." />
|
|
109
|
+
<PromptInputActions class="justify-end">
|
|
110
|
+
<Button variant="default" size="icon-sm" class="rounded-full" onClick={startStream}>
|
|
111
|
+
<ArrowUp class="size-4" />
|
|
112
|
+
</Button>
|
|
113
|
+
</PromptInputActions>
|
|
114
|
+
</PromptInput>
|
|
115
|
+
}
|
|
116
|
+
>
|
|
117
|
+
<PromptInput disabled isLoading>
|
|
118
|
+
<PromptInputTextarea placeholder="Generating..." />
|
|
119
|
+
<PromptInputActions class="justify-between">
|
|
120
|
+
<div class="flex items-center gap-2">
|
|
121
|
+
<Loader variant="typing" size="sm" />
|
|
122
|
+
<span class="text-xs text-muted-foreground">Streaming response...</span>
|
|
123
|
+
</div>
|
|
124
|
+
<Button variant="outline" size="icon-sm" class="rounded-full" onClick={() => setIsStreaming(false)}>
|
|
125
|
+
<Square class="size-3" />
|
|
126
|
+
</Button>
|
|
127
|
+
</PromptInputActions>
|
|
128
|
+
</PromptInput>
|
|
129
|
+
</Show>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const WaitingForFirstToken: Story = {
|
|
137
|
+
name: 'Waiting for First Token',
|
|
138
|
+
render: () => (
|
|
139
|
+
<div class="flex flex-col h-[400px] w-full max-w-2xl bg-background rounded-xl shadow-lg overflow-hidden">
|
|
140
|
+
<div class="flex items-center px-4 py-3">
|
|
141
|
+
<h2 class="text-sm font-semibold text-foreground">Processing Query</h2>
|
|
142
|
+
</div>
|
|
143
|
+
<Separator />
|
|
144
|
+
|
|
145
|
+
<ChatContainer class="flex-1 p-4">
|
|
146
|
+
<ChatContainerContent class="space-y-6 py-4">
|
|
147
|
+
<Message>
|
|
148
|
+
<MessageAvatar src="" fallback="U" alt="User" />
|
|
149
|
+
<MessageContent>
|
|
150
|
+
Analyze the performance characteristics of B-tree vs LSM-tree storage engines for write-heavy workloads.
|
|
151
|
+
</MessageContent>
|
|
152
|
+
</Message>
|
|
153
|
+
|
|
154
|
+
<Message>
|
|
155
|
+
<MessageAvatar src="" fallback="AI" alt="Assistant" />
|
|
156
|
+
<div class="flex-1 flex items-center gap-3 rounded-lg p-3 bg-secondary">
|
|
157
|
+
<Loader variant="dots" size="sm" />
|
|
158
|
+
<TextShimmer class="text-sm">Thinking...</TextShimmer>
|
|
159
|
+
</div>
|
|
160
|
+
</Message>
|
|
161
|
+
|
|
162
|
+
<ChatContainerScrollAnchor />
|
|
163
|
+
</ChatContainerContent>
|
|
164
|
+
</ChatContainer>
|
|
165
|
+
|
|
166
|
+
<div class="px-4 pb-4">
|
|
167
|
+
<PromptInput disabled isLoading>
|
|
168
|
+
<PromptInputTextarea placeholder="Waiting..." />
|
|
169
|
+
<PromptInputActions class="justify-between">
|
|
170
|
+
<span class="text-xs text-muted-foreground">Waiting for response...</span>
|
|
171
|
+
<Button variant="outline" size="icon-sm" class="rounded-full">
|
|
172
|
+
<Square class="size-3" />
|
|
173
|
+
</Button>
|
|
174
|
+
</PromptInputActions>
|
|
175
|
+
</PromptInput>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
),
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const FadeStream: Story = {
|
|
182
|
+
name: 'Fade-in Streaming',
|
|
183
|
+
render: () => {
|
|
184
|
+
const [isStreaming, setIsStreaming] = createSignal(false);
|
|
185
|
+
|
|
186
|
+
const startStream = () => {
|
|
187
|
+
setIsStreaming(true);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const shortText = `The **event loop** in JavaScript processes tasks in phases:
|
|
191
|
+
|
|
192
|
+
1. **Microtasks** (Promise callbacks, queueMicrotask) run first
|
|
193
|
+
2. **Macrotasks** (setTimeout, setInterval, I/O) run one per iteration
|
|
194
|
+
3. **Render steps** (requestAnimationFrame, layout, paint) happen between macrotasks
|
|
195
|
+
|
|
196
|
+
This is why \`Promise.resolve().then()\` always runs before \`setTimeout(cb, 0)\`.`;
|
|
197
|
+
|
|
198
|
+
const [showMessage, setShowMessage] = createSignal(false);
|
|
199
|
+
|
|
200
|
+
const handleStart = () => {
|
|
201
|
+
setShowMessage(false);
|
|
202
|
+
// Reset then show to remount the component
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
setShowMessage(true);
|
|
205
|
+
setIsStreaming(true);
|
|
206
|
+
}, 50);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<div class="w-full max-w-2xl p-4 space-y-4">
|
|
211
|
+
<p class="text-sm text-muted-foreground">Words fade in instead of appearing character by character.</p>
|
|
212
|
+
|
|
213
|
+
<Show when={showMessage()}>
|
|
214
|
+
<Message>
|
|
215
|
+
<MessageAvatar src="" fallback="AI" alt="Assistant" />
|
|
216
|
+
<div class="flex-1 rounded-lg p-2 bg-secondary">
|
|
217
|
+
<ResponseStream
|
|
218
|
+
textStream={shortText}
|
|
219
|
+
mode="fade"
|
|
220
|
+
speed={30}
|
|
221
|
+
onComplete={() => setIsStreaming(false)}
|
|
222
|
+
class="prose dark:prose-invert prose-sm max-w-none"
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
</Message>
|
|
226
|
+
</Show>
|
|
227
|
+
|
|
228
|
+
<Button onClick={handleStart} disabled={isStreaming()}>
|
|
229
|
+
{isStreaming() ? 'Streaming...' : 'Start Fade Stream'}
|
|
230
|
+
</Button>
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
},
|
|
234
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/stories/theme-editor.stories.tsx
|
|
2
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
3
|
+
import { ThemeEditor } from './docs/theme-editor/theme-editor';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Theming/Editor',
|
|
7
|
+
parameters: { layout: 'fullscreen' },
|
|
8
|
+
} satisfies Meta;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
type Story = StoryObj;
|
|
12
|
+
|
|
13
|
+
/** Full-screen live theme editor. */
|
|
14
|
+
export const Editor: Story = {
|
|
15
|
+
render: () => <ThemeEditor />,
|
|
16
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { TokenTable } from './docs/theme-tokens';
|
|
3
|
+
|
|
4
|
+
// Renders in the Solid preview (unlike MDX, which is React) and is embedded
|
|
5
|
+
// into the Theming docs page via <Canvas of={...}>.
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Theming/Token Reference',
|
|
8
|
+
parameters: { layout: 'padded' },
|
|
9
|
+
} satisfies Meta;
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj;
|
|
13
|
+
|
|
14
|
+
/** Auto-generated reference of every overridable token. */
|
|
15
|
+
export const TokenReference: Story = {
|
|
16
|
+
render: () => <TokenTable />,
|
|
17
|
+
parameters: { docs: { source: { code: '/* override any of these on :root or a scoped parent */', language: 'css' } } },
|
|
18
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Types the chat kit components reference. Extracted from the kit's origin
|
|
2
|
+
// project (@tab-zen/shared) so the kit is self-contained. Only the types the
|
|
3
|
+
// components actually import are kept; the RAG/document/adapter types are dropped.
|
|
4
|
+
|
|
5
|
+
export interface ModelOption {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
provider?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SearchFilters {
|
|
12
|
+
tags?: string[];
|
|
13
|
+
authors?: string[];
|
|
14
|
+
contentType?: 'transcript' | 'markdown';
|
|
15
|
+
dateRange?: { from: string; to: string };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ConversationScope {
|
|
19
|
+
type: 'document' | 'collection';
|
|
20
|
+
documentId?: string;
|
|
21
|
+
filters?: SearchFilters;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ConversationSummary {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
groupId?: string;
|
|
28
|
+
scope: ConversationScope;
|
|
29
|
+
messageCount: number;
|
|
30
|
+
lastMessageAt: string;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ConversationGroup {
|
|
35
|
+
id: string;
|
|
36
|
+
userId?: string;
|
|
37
|
+
teamId?: string;
|
|
38
|
+
name: string;
|
|
39
|
+
sortOrder: number;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { Avatar } from './avatar';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'UI/Avatar',
|
|
6
|
+
component: Avatar,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: 'padded',
|
|
10
|
+
docs: {
|
|
11
|
+
controls: { exclude: ['use:eventListener'] },
|
|
12
|
+
description: {
|
|
13
|
+
component: [
|
|
14
|
+
'A small, rounded **avatar** that renders an image when `src` is provided and falls back to short initials otherwise.',
|
|
15
|
+
'**When to use:** to identify the author of a message, a conversation participant, or the current user in a header or list.',
|
|
16
|
+
'**How to use:** always pass a `fallback` (1–2 initials) and a `size`. Provide `src`/`alt` when you have an image; if the image is missing or fails, the `fallback` text shows instead.',
|
|
17
|
+
'**Placement:** message rows (next to assistant/user content), conversation list items, account menus, and headers.',
|
|
18
|
+
].join('\n\n'),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
argTypes: {
|
|
23
|
+
src: {
|
|
24
|
+
control: 'text',
|
|
25
|
+
description: 'Image URL. When omitted, the `fallback` initials are shown.',
|
|
26
|
+
},
|
|
27
|
+
alt: {
|
|
28
|
+
control: 'text',
|
|
29
|
+
description: 'Alt text for the image. Defaults to `fallback` when not set.',
|
|
30
|
+
},
|
|
31
|
+
fallback: {
|
|
32
|
+
control: 'text',
|
|
33
|
+
description: 'Initials shown when there is no image (required).',
|
|
34
|
+
},
|
|
35
|
+
size: {
|
|
36
|
+
control: 'select',
|
|
37
|
+
options: ['sm', 'md', 'lg'],
|
|
38
|
+
description: 'Avatar diameter preset.',
|
|
39
|
+
table: { defaultValue: { summary: 'md' } },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
args: {
|
|
43
|
+
fallback: 'JD',
|
|
44
|
+
size: 'md',
|
|
45
|
+
},
|
|
46
|
+
render: (args) => <Avatar {...args} />,
|
|
47
|
+
} satisfies Meta<typeof Avatar>;
|
|
48
|
+
|
|
49
|
+
export default meta;
|
|
50
|
+
type Story = StoryObj<typeof meta>;
|
|
51
|
+
|
|
52
|
+
const IMPORT = `import { Avatar } from '@kitnai/chat';`;
|
|
53
|
+
const src = (code: string) => ({
|
|
54
|
+
parameters: { docs: { source: { code: `${IMPORT}\n\n${code}`, language: 'tsx' } } },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/** Interactive playground — tweak the controls to explore image vs. fallback and sizes. */
|
|
58
|
+
export const Playground: Story = {
|
|
59
|
+
...src(`<Avatar fallback="JD" size="md" />`),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const WithFallback: Story = {
|
|
63
|
+
args: { fallback: 'JD', size: 'md' },
|
|
64
|
+
...src(`<Avatar fallback="JD" size="md" />`),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const WithImage: Story = {
|
|
68
|
+
args: {
|
|
69
|
+
src: 'https://api.dicebear.com/7.x/initials/svg?seed=JD',
|
|
70
|
+
alt: 'John Doe',
|
|
71
|
+
fallback: 'JD',
|
|
72
|
+
},
|
|
73
|
+
...src(`<Avatar
|
|
74
|
+
src="https://api.dicebear.com/7.x/initials/svg?seed=JD"
|
|
75
|
+
alt="John Doe"
|
|
76
|
+
fallback="JD"
|
|
77
|
+
/>`),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const Small: Story = {
|
|
81
|
+
args: { size: 'sm', fallback: 'S' },
|
|
82
|
+
...src(`<Avatar size="sm" fallback="S" />`),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const Large: Story = {
|
|
86
|
+
args: { size: 'lg', fallback: 'LG' },
|
|
87
|
+
...src(`<Avatar size="lg" fallback="LG" />`),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** Every size side by side (showcase — not driven by controls). */
|
|
91
|
+
export const AllSizes: Story = {
|
|
92
|
+
render: () => (
|
|
93
|
+
<div class="flex items-center gap-3">
|
|
94
|
+
<Avatar size="sm" fallback="SM" />
|
|
95
|
+
<Avatar size="md" fallback="MD" />
|
|
96
|
+
<Avatar size="lg" fallback="LG" />
|
|
97
|
+
</div>
|
|
98
|
+
),
|
|
99
|
+
...src(`<div class="flex items-center gap-3">
|
|
100
|
+
<Avatar size="sm" fallback="SM" />
|
|
101
|
+
<Avatar size="md" fallback="MD" />
|
|
102
|
+
<Avatar size="lg" fallback="LG" />
|
|
103
|
+
</div>`),
|
|
104
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type JSX, splitProps, Show } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export interface AvatarProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
src?: string;
|
|
6
|
+
alt?: string;
|
|
7
|
+
fallback: string;
|
|
8
|
+
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const sizeClasses = { sm: 'h-6 w-6 text-[10px]', md: 'h-8 w-8 text-xs', lg: 'h-10 w-10 text-sm' };
|
|
12
|
+
|
|
13
|
+
export function Avatar(props: AvatarProps) {
|
|
14
|
+
const [local, rest] = splitProps(props, ['src', 'alt', 'fallback', 'size', 'class']);
|
|
15
|
+
const size = () => local.size ?? 'md';
|
|
16
|
+
return (
|
|
17
|
+
<div class={cn('inline-flex items-center justify-center rounded-md bg-accent font-semibold text-accent-foreground flex-shrink-0', sizeClasses[size()], local.class)} {...rest}>
|
|
18
|
+
<Show when={local.src} fallback={<span>{local.fallback}</span>}>
|
|
19
|
+
<img src={local.src} alt={local.alt ?? local.fallback} class="h-full w-full rounded-md object-cover" />
|
|
20
|
+
</Show>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|