@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.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +314 -0
  3. package/dist/bash-InADTalH.js +6 -0
  4. package/dist/core-AYMC6_lb.js +5874 -0
  5. package/dist/engine-javascript-vq0WuIJl.js +2643 -0
  6. package/dist/github-dark-dimmed-DUshB20C.js +4 -0
  7. package/dist/github-light-JYsPkUQd.js +4 -0
  8. package/dist/javascript-C25yR2R2.js +6 -0
  9. package/dist/json-DxJze_jm.js +6 -0
  10. package/dist/kitn-chat.es.js +6632 -0
  11. package/dist/tsx-B8rCNbgL.js +6 -0
  12. package/dist/typescript-RycA9KXf.js +6 -0
  13. package/package.json +80 -0
  14. package/src/components/attachments.stories.tsx +304 -0
  15. package/src/components/attachments.tsx +394 -0
  16. package/src/components/chain-of-thought.stories.tsx +212 -0
  17. package/src/components/chain-of-thought.tsx +139 -0
  18. package/src/components/chat-container.stories.tsx +188 -0
  19. package/src/components/chat-container.tsx +78 -0
  20. package/src/components/chat-scope-picker.tsx +47 -0
  21. package/src/components/checkpoint.stories.tsx +103 -0
  22. package/src/components/checkpoint.tsx +81 -0
  23. package/src/components/code-block.stories.tsx +151 -0
  24. package/src/components/code-block.tsx +99 -0
  25. package/src/components/context.stories.tsx +180 -0
  26. package/src/components/context.tsx +323 -0
  27. package/src/components/conversation-item.stories.tsx +126 -0
  28. package/src/components/conversation-item.tsx +18 -0
  29. package/src/components/conversation-list.stories.tsx +134 -0
  30. package/src/components/conversation-list.tsx +100 -0
  31. package/src/components/empty.stories.tsx +435 -0
  32. package/src/components/empty.tsx +166 -0
  33. package/src/components/feedback-bar.stories.tsx +101 -0
  34. package/src/components/feedback-bar.tsx +58 -0
  35. package/src/components/file-upload.stories.tsx +157 -0
  36. package/src/components/file-upload.tsx +161 -0
  37. package/src/components/image.stories.tsx +90 -0
  38. package/src/components/image.tsx +67 -0
  39. package/src/components/loader.stories.tsx +182 -0
  40. package/src/components/loader.tsx +333 -0
  41. package/src/components/markdown.stories.tsx +181 -0
  42. package/src/components/markdown.tsx +81 -0
  43. package/src/components/message-narrow.stories.tsx +330 -0
  44. package/src/components/message-skills.stories.tsx +212 -0
  45. package/src/components/message-skills.tsx +36 -0
  46. package/src/components/message.stories.tsx +282 -0
  47. package/src/components/message.tsx +149 -0
  48. package/src/components/model-switcher.stories.tsx +98 -0
  49. package/src/components/model-switcher.tsx +36 -0
  50. package/src/components/prompt-input.stories.tsx +223 -0
  51. package/src/components/prompt-input.tsx +190 -0
  52. package/src/components/prompt-suggestion.stories.tsx +143 -0
  53. package/src/components/prompt-suggestion.tsx +115 -0
  54. package/src/components/reasoning.stories.tsx +141 -0
  55. package/src/components/reasoning.tsx +157 -0
  56. package/src/components/response-stream.tsx +103 -0
  57. package/src/components/scroll-button.stories.tsx +101 -0
  58. package/src/components/scroll-button.tsx +33 -0
  59. package/src/components/slash-command.stories.tsx +164 -0
  60. package/src/components/slash-command.tsx +223 -0
  61. package/src/components/source.stories.tsx +125 -0
  62. package/src/components/source.tsx +129 -0
  63. package/src/components/text-shimmer.stories.tsx +88 -0
  64. package/src/components/text-shimmer.tsx +37 -0
  65. package/src/components/thinking-bar.stories.tsx +88 -0
  66. package/src/components/thinking-bar.tsx +50 -0
  67. package/src/components/tool.stories.tsx +154 -0
  68. package/src/components/tool.tsx +173 -0
  69. package/src/components/voice-input.stories.tsx +84 -0
  70. package/src/components/voice-input.tsx +103 -0
  71. package/src/elements/chat-types.ts +14 -0
  72. package/src/elements/chat.tsx +111 -0
  73. package/src/elements/compiled.css +2 -0
  74. package/src/elements/conversation-list.tsx +26 -0
  75. package/src/elements/css.ts +5 -0
  76. package/src/elements/default-input.tsx +53 -0
  77. package/src/elements/define.tsx +54 -0
  78. package/src/elements/kitn-chat.stories.tsx +105 -0
  79. package/src/elements/kitn-conversation-list.stories.tsx +177 -0
  80. package/src/elements/kitn-prompt-input.stories.tsx +123 -0
  81. package/src/elements/prompt-input.tsx +39 -0
  82. package/src/elements/register.ts +9 -0
  83. package/src/elements/styles.css +12 -0
  84. package/src/index.ts +128 -0
  85. package/src/primitives/chat-config.tsx +76 -0
  86. package/src/primitives/highlighter.ts +150 -0
  87. package/src/primitives/use-auto-resize.ts +31 -0
  88. package/src/primitives/use-stick-to-bottom.ts +43 -0
  89. package/src/primitives/use-text-stream.ts +112 -0
  90. package/src/primitives/use-voice-recorder.ts +50 -0
  91. package/src/stories/chat-panel-layout.stories.tsx +144 -0
  92. package/src/stories/chat-scene.tsx +570 -0
  93. package/src/stories/checkpoint-restore.stories.tsx +224 -0
  94. package/src/stories/context-usage.stories.tsx +155 -0
  95. package/src/stories/conversation-with-reasoning.stories.tsx +151 -0
  96. package/src/stories/conversation-with-sources.stories.tsx +165 -0
  97. package/src/stories/docs/GettingStarted.mdx +76 -0
  98. package/src/stories/docs/Installation.mdx +48 -0
  99. package/src/stories/docs/Integrations.mdx +110 -0
  100. package/src/stories/docs/Introduction.mdx +29 -0
  101. package/src/stories/docs/Theming.mdx +87 -0
  102. package/src/stories/docs/theme-editor/canvas.tsx +32 -0
  103. package/src/stories/docs/theme-editor/inspector.tsx +66 -0
  104. package/src/stories/docs/theme-editor/presets.test.ts +32 -0
  105. package/src/stories/docs/theme-editor/presets.ts +64 -0
  106. package/src/stories/docs/theme-editor/theme-css.test.ts +19 -0
  107. package/src/stories/docs/theme-editor/theme-css.ts +15 -0
  108. package/src/stories/docs/theme-editor/theme-editor.tsx +145 -0
  109. package/src/stories/docs/theme-tokens.tsx +174 -0
  110. package/src/stories/full-chat.stories.tsx +18 -0
  111. package/src/stories/message-actions.stories.tsx +167 -0
  112. package/src/stories/prompt-input-variants.stories.tsx +179 -0
  113. package/src/stories/streaming-response.stories.tsx +234 -0
  114. package/src/stories/theme-editor.stories.tsx +16 -0
  115. package/src/stories/token-reference.stories.tsx +18 -0
  116. package/src/types.ts +41 -0
  117. package/src/ui/avatar.stories.tsx +104 -0
  118. package/src/ui/avatar.tsx +23 -0
  119. package/src/ui/badge.stories.tsx +87 -0
  120. package/src/ui/badge.tsx +21 -0
  121. package/src/ui/button.stories.tsx +146 -0
  122. package/src/ui/button.tsx +37 -0
  123. package/src/ui/collapsible.tsx +14 -0
  124. package/src/ui/dialog.tsx +21 -0
  125. package/src/ui/dropdown.tsx +26 -0
  126. package/src/ui/hover-card.tsx +48 -0
  127. package/src/ui/resizable.stories.tsx +171 -0
  128. package/src/ui/resizable.tsx +219 -0
  129. package/src/ui/scroll-area.tsx +13 -0
  130. package/src/ui/separator.stories.tsx +82 -0
  131. package/src/ui/separator.tsx +10 -0
  132. package/src/ui/skeleton.stories.tsx +338 -0
  133. package/src/ui/skeleton.tsx +16 -0
  134. package/src/ui/textarea.tsx +21 -0
  135. package/src/ui/tooltip.stories.tsx +75 -0
  136. package/src/ui/tooltip.tsx +22 -0
  137. package/src/utils/cn.ts +6 -0
  138. package/theme.css +115 -0
@@ -0,0 +1,224 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import { createSignal } from 'solid-js';
3
+ import {
4
+ ChatContainer, ChatContainerContent, ChatContainerScrollAnchor,
5
+ Message, MessageAvatar, MessageContent, MessageActions,
6
+ PromptInput, PromptInputTextarea, PromptInputActions,
7
+ Checkpoint, CheckpointIcon, CheckpointTrigger,
8
+ Button, Separator,
9
+ } from '../index';
10
+ import { Copy, ThumbsUp, ThumbsDown, ArrowUp, RotateCcw } from 'lucide-solid';
11
+
12
+ const meta: Meta = {
13
+ title: 'Examples/Checkpoint & Restore',
14
+ };
15
+
16
+ export default meta;
17
+ type Story = StoryObj;
18
+
19
+ export const Default: Story = {
20
+ render: () => {
21
+ const [restoredTo, setRestoredTo] = createSignal<string | null>(null);
22
+
23
+ return (
24
+ <div class="flex flex-col h-[700px] w-full max-w-2xl bg-background rounded-xl shadow-lg overflow-hidden">
25
+ <div class="flex items-center justify-between px-4 py-3">
26
+ <h2 class="text-sm font-semibold text-foreground">API Design Session</h2>
27
+ {restoredTo() && (
28
+ <span class="text-xs text-muted-foreground bg-muted px-2 py-1 rounded-md">
29
+ Restored to checkpoint: {restoredTo()}
30
+ </span>
31
+ )}
32
+ </div>
33
+ <Separator />
34
+
35
+ <ChatContainer class="flex-1 p-4">
36
+ <ChatContainerContent class="space-y-6 py-4">
37
+
38
+ {/* Initial exchange */}
39
+ <Message>
40
+ <MessageAvatar src="" fallback="U" alt="User" />
41
+ <MessageContent>
42
+ I'm designing a REST API for a task management app. What should the core endpoints look like?
43
+ </MessageContent>
44
+ </Message>
45
+
46
+ <Message>
47
+ <MessageAvatar src="" fallback="AI" alt="Assistant" />
48
+ <div class="flex-1 space-y-2">
49
+ <MessageContent markdown>
50
+ {`Here's a clean REST resource design for task management:
51
+
52
+ \`\`\`
53
+ GET /api/tasks # List tasks (with filters)
54
+ POST /api/tasks # Create task
55
+ GET /api/tasks/:id # Get single task
56
+ PATCH /api/tasks/:id # Update task
57
+ DELETE /api/tasks/:id # Delete task
58
+
59
+ GET /api/projects # List projects
60
+ POST /api/projects # Create project
61
+ GET /api/projects/:id/tasks # Tasks within a project
62
+ \`\`\`
63
+
64
+ Key design decisions:
65
+ - Use **PATCH** over PUT for partial updates
66
+ - Nest tasks under projects for scoped queries
67
+ - Support filtering via query params: \`?status=active&assignee=me\``}
68
+ </MessageContent>
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>
73
+ </MessageActions>
74
+ </div>
75
+ </Message>
76
+
77
+ {/* First checkpoint */}
78
+ <Checkpoint>
79
+ <CheckpointIcon />
80
+ <CheckpointTrigger
81
+ tooltip="Restore to this point"
82
+ onClick={() => setRestoredTo('API structure defined')}
83
+ >
84
+ <div class="flex items-center gap-1.5 text-xs">
85
+ <RotateCcw class="size-3" />
86
+ <span>API structure defined</span>
87
+ </div>
88
+ </CheckpointTrigger>
89
+ </Checkpoint>
90
+
91
+ {/* Second exchange */}
92
+ <Message>
93
+ <MessageAvatar src="" fallback="U" alt="User" />
94
+ <MessageContent>
95
+ What about authentication? Should I use JWT or session cookies?
96
+ </MessageContent>
97
+ </Message>
98
+
99
+ <Message>
100
+ <MessageAvatar src="" fallback="AI" alt="Assistant" />
101
+ <div class="flex-1 space-y-2">
102
+ <MessageContent markdown>
103
+ {`For a task management API, I'd recommend **JWT with refresh tokens**:
104
+
105
+ \`\`\`typescript
106
+ // Auth endpoints
107
+ POST /api/auth/login // Returns access + refresh tokens
108
+ POST /api/auth/refresh // Exchanges refresh for new access token
109
+ POST /api/auth/logout // Invalidates refresh token
110
+
111
+ // Middleware
112
+ function authMiddleware(req, res, next) {
113
+ const token = req.headers.authorization?.split(' ')[1];
114
+ try {
115
+ req.user = jwt.verify(token, SECRET);
116
+ next();
117
+ } catch {
118
+ res.status(401).json({ error: 'Invalid token' });
119
+ }
120
+ }
121
+ \`\`\`
122
+
123
+ **Why JWT over sessions:**
124
+ - Stateless -- no server-side session store needed
125
+ - Works across subdomains and mobile apps
126
+ - Access tokens expire in 15min, limiting damage from theft
127
+
128
+ **The tradeoff:** you can't instantly revoke JWTs. Mitigate this with short expiry + a blocklist for critical cases (password change, account compromise).`}
129
+ </MessageContent>
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>
134
+ </MessageActions>
135
+ </div>
136
+ </Message>
137
+
138
+ {/* Second checkpoint */}
139
+ <Checkpoint>
140
+ <CheckpointIcon />
141
+ <CheckpointTrigger
142
+ tooltip="Restore to this point"
143
+ onClick={() => setRestoredTo('Auth strategy chosen')}
144
+ >
145
+ <div class="flex items-center gap-1.5 text-xs">
146
+ <RotateCcw class="size-3" />
147
+ <span>Auth strategy chosen</span>
148
+ </div>
149
+ </CheckpointTrigger>
150
+ </Checkpoint>
151
+
152
+ {/* Third exchange after checkpoint */}
153
+ <Message>
154
+ <MessageAvatar src="" fallback="U" alt="User" />
155
+ <MessageContent>
156
+ Now help me think about error handling. What's a good error response format?
157
+ </MessageContent>
158
+ </Message>
159
+
160
+ <Message>
161
+ <MessageAvatar src="" fallback="AI" alt="Assistant" />
162
+ <div class="flex-1 space-y-2">
163
+ <MessageContent markdown>
164
+ {`Use a consistent error envelope inspired by the **RFC 7807 Problem Details** spec:
165
+
166
+ \`\`\`typescript
167
+ interface ApiError {
168
+ status: number; // HTTP status code
169
+ code: string; // Machine-readable error code
170
+ message: string; // Human-readable description
171
+ details?: unknown[]; // Validation errors, field-level info
172
+ requestId: string; // For debugging/support
173
+ }
174
+
175
+ // Example responses
176
+ // 400 Bad Request
177
+ {
178
+ "status": 400,
179
+ "code": "VALIDATION_ERROR",
180
+ "message": "Invalid task data",
181
+ "details": [
182
+ { "field": "title", "error": "Required field missing" },
183
+ { "field": "dueDate", "error": "Must be a future date" }
184
+ ],
185
+ "requestId": "req_abc123"
186
+ }
187
+
188
+ // 404 Not Found
189
+ {
190
+ "status": 404,
191
+ "code": "RESOURCE_NOT_FOUND",
192
+ "message": "Task not found",
193
+ "requestId": "req_def456"
194
+ }
195
+ \`\`\`
196
+
197
+ Always include the \`requestId\` -- it's invaluable for correlating client errors with server logs.`}
198
+ </MessageContent>
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>
203
+ </MessageActions>
204
+ </div>
205
+ </Message>
206
+
207
+ <ChatContainerScrollAnchor />
208
+ </ChatContainerContent>
209
+ </ChatContainer>
210
+
211
+ <div class="px-4 pb-4">
212
+ <PromptInput>
213
+ <PromptInputTextarea placeholder="Continue the API design..." />
214
+ <PromptInputActions class="justify-end">
215
+ <Button variant="default" size="icon-sm" class="rounded-full">
216
+ <ArrowUp class="size-4" />
217
+ </Button>
218
+ </PromptInputActions>
219
+ </PromptInput>
220
+ </div>
221
+ </div>
222
+ );
223
+ },
224
+ };
@@ -0,0 +1,155 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import {
3
+ Context, ContextTrigger, ContextContent,
4
+ ContextContentHeader, ContextContentBody, ContextContentFooter,
5
+ ContextInputUsage, ContextOutputUsage, ContextReasoningUsage, ContextCacheUsage,
6
+ ModelSwitcher, Button, Separator,
7
+ } from '../index';
8
+ import type { ModelOption } from '../types';
9
+ import { createSignal } from 'solid-js';
10
+
11
+ const meta: Meta = {
12
+ title: 'Examples/Context & Token Usage',
13
+ parameters: { layout: 'padded' },
14
+ };
15
+
16
+ export default meta;
17
+ type Story = StoryObj;
18
+
19
+ export const LowUsage: Story = {
20
+ name: 'Low Usage (Green)',
21
+ render: () => (
22
+ <div class="p-8">
23
+ <p class="text-sm text-muted-foreground mb-4">Early in a conversation -- minimal token usage.</p>
24
+ <Context usedTokens={4200} maxTokens={200000} inputTokens={2800} outputTokens={1400} estimatedCost={0.012}>
25
+ <ContextTrigger />
26
+ <ContextContent>
27
+ <ContextContentHeader />
28
+ <ContextContentBody>
29
+ <div class="space-y-1.5">
30
+ <ContextInputUsage />
31
+ <ContextOutputUsage />
32
+ </div>
33
+ </ContextContentBody>
34
+ <ContextContentFooter />
35
+ </ContextContent>
36
+ </Context>
37
+ </div>
38
+ ),
39
+ };
40
+
41
+ export const MediumUsage: Story = {
42
+ name: 'Medium Usage (Yellow)',
43
+ render: () => (
44
+ <div class="p-8">
45
+ <p class="text-sm text-muted-foreground mb-4">Extended conversation with reasoning -- approaching 75% usage.</p>
46
+ <Context usedTokens={150000} maxTokens={200000} inputTokens={85000} outputTokens={42000} reasoningTokens={23000} estimatedCost={0.89}>
47
+ <ContextTrigger />
48
+ <ContextContent>
49
+ <ContextContentHeader />
50
+ <ContextContentBody>
51
+ <div class="space-y-1.5">
52
+ <ContextInputUsage />
53
+ <ContextOutputUsage />
54
+ <ContextReasoningUsage />
55
+ </div>
56
+ </ContextContentBody>
57
+ <ContextContentFooter />
58
+ </ContextContent>
59
+ </Context>
60
+ </div>
61
+ ),
62
+ };
63
+
64
+ export const HighUsage: Story = {
65
+ name: 'High Usage (Red)',
66
+ render: () => (
67
+ <div class="p-8">
68
+ <p class="text-sm text-muted-foreground mb-4">Near the context limit -- user should consider starting a new conversation.</p>
69
+ <Context usedTokens={189000} maxTokens={200000} inputTokens={110000} outputTokens={54000} reasoningTokens={25000} estimatedCost={1.42}>
70
+ <ContextTrigger />
71
+ <ContextContent>
72
+ <ContextContentHeader />
73
+ <ContextContentBody>
74
+ <div class="space-y-1.5">
75
+ <ContextInputUsage />
76
+ <ContextOutputUsage />
77
+ <ContextReasoningUsage />
78
+ </div>
79
+ </ContextContentBody>
80
+ <ContextContentFooter />
81
+ </ContextContent>
82
+ </Context>
83
+ </div>
84
+ ),
85
+ };
86
+
87
+ export const WithCacheBreakdown: Story = {
88
+ name: 'Full Breakdown with Cache',
89
+ render: () => (
90
+ <div class="p-8">
91
+ <p class="text-sm text-muted-foreground mb-4">Detailed usage including cache hit tokens.</p>
92
+ <Context usedTokens={82000} maxTokens={200000} inputTokens={45000} outputTokens={22000} reasoningTokens={15000} cacheTokens={32000} estimatedCost={0.38}>
93
+ <ContextTrigger />
94
+ <ContextContent>
95
+ <ContextContentHeader />
96
+ <ContextContentBody>
97
+ <div class="space-y-1.5">
98
+ <ContextInputUsage />
99
+ <ContextOutputUsage />
100
+ <ContextReasoningUsage />
101
+ <ContextCacheUsage />
102
+ </div>
103
+ </ContextContentBody>
104
+ <ContextContentFooter />
105
+ </ContextContent>
106
+ </Context>
107
+ </div>
108
+ ),
109
+ };
110
+
111
+ export const InHeaderBar: Story = {
112
+ name: 'In a Header Bar',
113
+ render: () => {
114
+ const [modelId, setModelId] = createSignal('claude-4');
115
+ const models: ModelOption[] = [
116
+ { id: 'claude-4', name: 'Claude 4 Opus', provider: 'Anthropic' },
117
+ { id: 'claude-4-sonnet', name: 'Claude 4 Sonnet', provider: 'Anthropic' },
118
+ ];
119
+
120
+ return (
121
+ <div class="w-full max-w-3xl">
122
+ <p class="text-sm text-muted-foreground mb-4 px-4">Context usage as it appears in the app header, alongside model switcher.</p>
123
+
124
+ <div class="bg-background rounded-xl shadow-lg overflow-hidden">
125
+ <div class="flex items-center justify-between px-4 py-2.5">
126
+ <div class="flex items-center gap-3">
127
+ <h2 class="text-sm font-semibold text-foreground">Database indexing strategies</h2>
128
+ <span class="text-xs text-muted-foreground">24 messages</span>
129
+ </div>
130
+ <div class="flex items-center gap-2">
131
+ <ModelSwitcher models={models} currentModelId={modelId()} onModelChange={setModelId} />
132
+ <Context usedTokens={67000} maxTokens={200000} inputTokens={38000} outputTokens={29000} estimatedCost={0.31}>
133
+ <ContextTrigger />
134
+ <ContextContent>
135
+ <ContextContentHeader />
136
+ <ContextContentBody>
137
+ <div class="space-y-1.5">
138
+ <ContextInputUsage />
139
+ <ContextOutputUsage />
140
+ </div>
141
+ </ContextContentBody>
142
+ <ContextContentFooter />
143
+ </ContextContent>
144
+ </Context>
145
+ </div>
146
+ </div>
147
+ <Separator />
148
+ <div class="h-48 flex items-center justify-center text-muted-foreground text-sm">
149
+ Chat content area
150
+ </div>
151
+ </div>
152
+ </div>
153
+ );
154
+ },
155
+ };
@@ -0,0 +1,151 @@
1
+ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
+ import {
3
+ ChatContainer, ChatContainerContent, ChatContainerScrollAnchor,
4
+ Message, MessageAvatar, MessageContent, MessageActions,
5
+ PromptInput, PromptInputTextarea, PromptInputActions,
6
+ ChainOfThought, ChainOfThoughtStep, ChainOfThoughtTrigger, ChainOfThoughtContent, ChainOfThoughtItem,
7
+ Button, Separator,
8
+ } from '../index';
9
+ import { Copy, ThumbsUp, ThumbsDown, ArrowUp, Lightbulb, Search, Calculator } from 'lucide-solid';
10
+
11
+ const meta: Meta = {
12
+ title: 'Examples/Conversation with Reasoning',
13
+ parameters: { layout: 'padded' },
14
+ };
15
+
16
+ export default meta;
17
+ type Story = StoryObj;
18
+
19
+ export const Default: Story = {
20
+ render: () => (
21
+ <div class="flex flex-col h-[700px] w-full max-w-2xl bg-background rounded-xl shadow-lg overflow-hidden">
22
+ <div class="flex items-center px-4 py-3">
23
+ <h2 class="text-sm font-semibold text-foreground">Architecture Decision</h2>
24
+ </div>
25
+ <Separator />
26
+
27
+ <ChatContainer class="flex-1 p-4">
28
+ <ChatContainerContent class="space-y-6 py-4">
29
+
30
+ <Message>
31
+ <MessageAvatar src="" fallback="U" alt="User" />
32
+ <MessageContent>
33
+ I have a Chrome extension that needs to sync user data across devices. Should I use Chrome's built-in storage.sync API or implement my own sync layer with a backend? The data is about 50KB per user, updated frequently (every few minutes), and includes nested objects with arrays.
34
+ </MessageContent>
35
+ </Message>
36
+
37
+ <Message>
38
+ <MessageAvatar src="" fallback="AI" alt="Assistant" />
39
+ <div class="flex-1 space-y-3">
40
+
41
+ {/* Chain of thought reasoning */}
42
+ <ChainOfThought>
43
+ <ChainOfThoughtStep>
44
+ <ChainOfThoughtTrigger leftIcon={<Search class="size-3.5" />}>
45
+ Analyzing data size and sync limits
46
+ </ChainOfThoughtTrigger>
47
+ <ChainOfThoughtContent>
48
+ <ChainOfThoughtItem>
49
+ chrome.storage.sync has a total limit of 102,400 bytes and 512 items max. At 50KB per user, that's within the total limit but leaves little headroom. Each item is capped at 8,192 bytes, so nested objects would need splitting. Write operations are throttled to 120/minute in normal mode, 1,800/hour.
50
+ </ChainOfThoughtItem>
51
+ </ChainOfThoughtContent>
52
+ </ChainOfThoughtStep>
53
+
54
+ <ChainOfThoughtStep>
55
+ <ChainOfThoughtTrigger leftIcon={<Calculator class="size-3.5" />}>
56
+ Evaluating update frequency constraints
57
+ </ChainOfThoughtTrigger>
58
+ <ChainOfThoughtContent>
59
+ <ChainOfThoughtItem>
60
+ "Updated every few minutes" means roughly 20-30 writes per hour. That's well within the 1,800/hour limit. But if the data has nested objects with arrays, each update might need to write multiple storage items (due to the 8KB per-item limit), multiplying the write count. 50KB / 8KB = ~7 items minimum, so 7 * 30 = 210 writes/hour. Still within limits.
61
+ </ChainOfThoughtItem>
62
+ </ChainOfThoughtContent>
63
+ </ChainOfThoughtStep>
64
+
65
+ <ChainOfThoughtStep>
66
+ <ChainOfThoughtTrigger leftIcon={<Lightbulb class="size-3.5" />}>
67
+ Considering conflict resolution
68
+ </ChainOfThoughtTrigger>
69
+ <ChainOfThoughtContent>
70
+ <ChainOfThoughtItem>
71
+ Chrome storage.sync uses last-write-wins. For nested objects updated from multiple devices simultaneously, this could cause data loss. If a user has the extension open on laptop and desktop, and both modify different array elements, the last write overwrites the other's changes. A custom backend could use CRDTs or operational transforms for proper merging.
72
+ </ChainOfThoughtItem>
73
+ </ChainOfThoughtContent>
74
+ </ChainOfThoughtStep>
75
+
76
+ <ChainOfThoughtStep isLast>
77
+ <ChainOfThoughtTrigger leftIcon={<Lightbulb class="size-3.5" />}>
78
+ Weighing implementation complexity
79
+ </ChainOfThoughtTrigger>
80
+ <ChainOfThoughtContent>
81
+ <ChainOfThoughtItem>
82
+ Custom backend means: server costs, auth, API design, offline handling, retry logic, and maintenance. Chrome storage.sync is free, built-in, handles offline automatically, and requires zero infrastructure. For a personal tool, the operational burden of a backend is significant.
83
+ </ChainOfThoughtItem>
84
+ </ChainOfThoughtContent>
85
+ </ChainOfThoughtStep>
86
+ </ChainOfThought>
87
+
88
+ {/* Final answer */}
89
+ <MessageContent markdown>
90
+ {`Given your constraints, I'd recommend a **hybrid approach**:
91
+
92
+ ### Use \`chrome.storage.sync\` as primary, with a chunking strategy
93
+
94
+ 1. **Split your 50KB into logical chunks** -- group related data into objects under 8KB each. Use a manifest key that tracks which chunks exist and their versions.
95
+
96
+ 2. **Implement delta syncing** -- instead of writing the full dataset every time, only write the chunks that changed. This keeps you well under rate limits.
97
+
98
+ 3. **Add a simple conflict guard** -- store a \`lastModifiedBy\` device ID and timestamp in each chunk. On read, if you detect a conflict, merge arrays by union rather than overwrite.
99
+
100
+ \`\`\`typescript
101
+ interface SyncChunk {
102
+ version: number;
103
+ deviceId: string;
104
+ updatedAt: number;
105
+ data: Record<string, unknown>;
106
+ }
107
+
108
+ // Write only changed chunks
109
+ async function syncChunks(changes: Map<string, SyncChunk>) {
110
+ const batch: Record<string, SyncChunk> = {};
111
+ for (const [key, chunk] of changes) {
112
+ batch[\`chunk_\${key}\`] = { ...chunk, version: chunk.version + 1 };
113
+ }
114
+ await chrome.storage.sync.set(batch);
115
+ }
116
+ \`\`\`
117
+
118
+ ### When to upgrade to a backend
119
+
120
+ Move to a custom sync layer if:
121
+ - Data grows beyond **80KB** (hitting limits)
122
+ - You need **real-time collaboration** (multiple devices editing simultaneously)
123
+ - You want **version history** or undo across devices
124
+ - You add team/sharing features`}
125
+ </MessageContent>
126
+
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>
131
+ </MessageActions>
132
+ </div>
133
+ </Message>
134
+
135
+ <ChatContainerScrollAnchor />
136
+ </ChatContainerContent>
137
+ </ChatContainer>
138
+
139
+ <div class="px-4 pb-4">
140
+ <PromptInput>
141
+ <PromptInputTextarea placeholder="Ask a follow-up..." />
142
+ <PromptInputActions class="justify-end">
143
+ <Button variant="default" size="icon-sm" class="rounded-full">
144
+ <ArrowUp class="size-4" />
145
+ </Button>
146
+ </PromptInputActions>
147
+ </PromptInput>
148
+ </div>
149
+ </div>
150
+ ),
151
+ };