Skip to content

Commit a89abc8

Browse files
authored
Improve databunny (#112)
* feat: improve databunny * refactor: remove unused AI elements components
1 parent bed48fd commit a89abc8

File tree

18 files changed

+486
-2383
lines changed

18 files changed

+486
-2383
lines changed
Lines changed: 195 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
11
'use client';
22

33
import {
4-
BrainIcon,
54
ChartBarIcon,
5+
ChatIcon,
66
ClockCounterClockwiseIcon,
77
HashIcon,
88
LightningIcon,
9+
PaperPlaneRightIcon,
910
SparkleIcon,
1011
TrendUpIcon,
1112
} from '@phosphor-icons/react';
1213
import { useAtom } from 'jotai';
13-
import { useEffect, useRef } from 'react';
14-
import { Action, Actions } from '@/components/ai-elements/actions';
14+
import { useEffect, useRef, useState } from 'react';
1515
import {
1616
Conversation,
1717
ConversationContent,
1818
ConversationScrollButton,
1919
} from '@/components/ai-elements/conversation';
20-
21-
import {
22-
PromptInput,
23-
PromptInputSubmit,
24-
PromptInputTextarea,
25-
PromptInputToolbar,
26-
} from '@/components/ai-elements/prompt-input';
27-
import { Suggestion, Suggestions } from '@/components/ai-elements/suggestion';
20+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
21+
import { Button } from '@/components/ui/button';
22+
import { Input } from '@/components/ui/input';
2823
import { Skeleton } from '@/components/ui/skeleton';
29-
30-
import { modelAtom, websiteDataAtom } from '@/stores/jotai/assistantAtoms';
24+
import { cn } from '@/lib/utils';
25+
import {
26+
isRateLimitedAtom,
27+
modelAtom,
28+
websiteDataAtom,
29+
} from '@/stores/jotai/assistantAtoms';
3130
import { useChat } from '../hooks/use-chat';
31+
import { ChatHistorySheet } from './chat-history-sheet';
3232
import { MessageBubble } from './message-bubble';
3333
import { ModelSelector } from './model-selector';
3434

@@ -73,29 +73,49 @@ export function ChatSkeleton() {
7373
}
7474

7575
const quickQuestions = [
76-
{ text: 'Show me page views over the last 7 days', icon: TrendUpIcon },
77-
{ text: 'How many visitors yesterday?', icon: HashIcon },
78-
{ text: 'Top traffic sources breakdown', icon: ChartBarIcon },
79-
{ text: "What's my bounce rate?", icon: HashIcon },
76+
{
77+
text: 'Show me page views over the last 7 days',
78+
icon: TrendUpIcon,
79+
type: 'chart',
80+
},
81+
{ text: 'How many visitors yesterday?', icon: HashIcon, type: 'metric' },
82+
{
83+
text: 'Top traffic sources breakdown',
84+
icon: ChartBarIcon,
85+
type: 'chart',
86+
},
87+
{ text: "What's my bounce rate?", icon: HashIcon, type: 'metric' },
8088
];
8189

8290
export default function ChatSection() {
8391
const [selectedModel] = useAtom(modelAtom);
8492
const [websiteData] = useAtom(websiteDataAtom);
93+
const [isRateLimited] = useAtom(isRateLimitedAtom);
94+
95+
const bottomRef = useRef<HTMLDivElement>(null);
96+
const inputRef = useRef<HTMLInputElement>(null);
8597

86-
const inputRef = useRef<HTMLTextAreaElement>(null);
8798
const {
8899
messages,
89100
inputValue,
90101
setInputValue,
91102
isLoading,
92103
sendMessage,
93-
scrollToBottom,
104+
handleKeyPress,
94105
resetChat,
95106
handleVote,
96107
handleFeedbackComment,
97108
} = useChat();
98109

110+
const [showChatHistory, setShowChatHistory] = useState(false);
111+
112+
const messageStats = {
113+
total: messages.length,
114+
charts: messages.filter((m) => m.responseType === 'chart').length,
115+
metrics: messages.filter((m) => m.responseType === 'metric').length,
116+
text: messages.filter((m) => m.responseType === 'text').length,
117+
};
118+
99119
const hasMessages = messages.length > 1;
100120

101121
useEffect(() => {
@@ -107,91 +127,125 @@ export default function ChatSection() {
107127
const handleSend = () => {
108128
if (!isLoading && inputValue.trim()) {
109129
sendMessage(inputValue.trim());
110-
scrollToBottom();
111130
setTimeout(() => inputRef.current?.focus(), 100);
112131
}
113132
};
114133

115134
return (
116-
<div className="flex h-full min-h-0 flex-col rounded border bg-background">
117-
{/* Header */}
118-
<div className="flex flex-shrink-0 items-center justify-between border-b p-4">
119-
<div className="flex min-w-0 flex-1 items-center gap-3">
120-
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded bg-primary/10">
121-
<BrainIcon className="h-4 w-4 text-primary" weight="duotone" />
122-
</div>
123-
<div className="min-w-0 flex-1">
124-
<h2 className="truncate font-medium text-sm">Databunny</h2>
125-
<p className="truncate text-muted-foreground text-xs">
135+
<div className="flex h-full flex-col overflow-hidden rounded border bg-gradient-to-br from-background to-muted/10 shadow-lg backdrop-blur-sm">
136+
{/* Enhanced Header */}
137+
<div className="flex flex-shrink-0 items-center justify-between border-b bg-gradient-to-r from-primary/5 to-accent/5 p-4">
138+
<div className="flex flex-1 items-center gap-3">
139+
<Avatar>
140+
<AvatarImage src="/databunny.webp" />
141+
<AvatarFallback>DB</AvatarFallback>
142+
</Avatar>
143+
<div className="flex-1">
144+
<div className="flex items-center gap-2">
145+
<h2 className="truncate font-semibold text-lg">Databunny</h2>
146+
{hasMessages && (
147+
<span className="px-2 py-0.5 text-muted-foreground text-xs">
148+
{messageStats.total}{' '}
149+
{messageStats.total === 1 ? 'query' : 'queries'}
150+
</span>
151+
)}
152+
</div>
153+
<p className="truncate text-muted-foreground text-sm">
126154
{isLoading
127-
? 'Analyzing your data...'
128-
: `Data analyst for ${websiteData?.name || 'your website'}`}
155+
? 'Databunny is analyzing your data...'
156+
: `Your data analyst for ${websiteData?.name || 'your website'}`}
129157
</p>
130158
</div>
131159
</div>
132-
<Actions>
160+
<div className="flex items-center gap-2">
133161
<ModelSelector disabled={isLoading} selectedModel={selectedModel} />
134-
<Action
135-
className="hover:bg-destructive/10 hover:text-destructive"
162+
<Button
163+
onClick={() => setShowChatHistory(true)}
164+
title="Open chat history"
165+
variant="ghost"
166+
>
167+
<ChatIcon className="h-5 w-5" weight="duotone" />
168+
</Button>
169+
<Button
170+
className="flex-shrink-0 hover:bg-destructive/10 hover:text-destructive"
136171
disabled={isLoading}
137172
onClick={resetChat}
138-
tooltip="Reset chat"
173+
size="icon"
174+
title="Reset chat"
175+
variant="ghost"
139176
>
140177
<ClockCounterClockwiseIcon className="h-4 w-4" />
141-
</Action>
142-
</Actions>
178+
</Button>
179+
</div>
143180
</div>
144181

145182
{/* Messages Area */}
146183
<Conversation>
147-
<ConversationContent className="!p-4">
148-
<ConversationScrollButton />
184+
<ConversationContent>
185+
{/* Welcome State */}
149186
{!(hasMessages || isLoading) && (
150-
<div className="h-full space-y-6">
187+
<div className="fade-in-0 slide-in-from-bottom-4 h-full animate-in space-y-6 duration-500">
151188
<div className="flex h-full flex-col justify-between">
152-
<div className="space-y-4 py-8 text-center">
153-
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded bg-primary/10">
189+
<div className="space-y-2 py-4 text-center">
190+
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-primary/10 to-accent/10">
154191
<SparkleIcon
155-
className="h-4 w-4 text-primary"
192+
className="h-8 w-8 text-primary"
156193
weight="duotone"
157194
/>
158195
</div>
159-
<h3 className="font-medium text-sm">Welcome to Databunny</h3>
160-
<p className="mx-auto max-w-md text-muted-foreground text-xs">
161-
Your data analyst. Ask me about your website analytics,
162-
metrics, and trends.
196+
<h3 className="font-semibold text-lg">
197+
Welcome to Databunny
198+
</h3>
199+
<p className="mx-auto max-w-md text-muted-foreground text-sm">
200+
I'm Databunny, your data analyst. I can help you understand
201+
your website data through charts, metrics, and insights.
202+
Just ask me anything!
163203
</p>
164204
</div>
205+
165206
<div className="space-y-3">
166-
<div className="flex items-center gap-2 text-muted-foreground text-xs">
207+
<div className="flex items-center gap-2 text-muted-foreground text-sm">
167208
<LightningIcon className="h-4 w-4" weight="duotone" />
168209
<span>Try these examples:</span>
169210
</div>
170-
<Suggestions>
171-
{quickQuestions.map((question) => (
172-
<Suggestion
173-
disabled={isLoading}
211+
<div className="grid grid-cols-1 gap-2 lg:grid-cols-2">
212+
{quickQuestions.map((question, index) => (
213+
<Button
214+
className={cn(
215+
'h-auto px-4 py-3 text-left font-normal text-sm',
216+
'hover:bg-gradient-to-r hover:from-primary/5 hover:to-accent/5',
217+
'border-dashed transition-all duration-300 hover:border-solid',
218+
'fade-in-0 slide-in-from-left-2 animate-in'
219+
)}
220+
disabled={isLoading || isRateLimited}
174221
key={question.text}
175-
onClick={(suggestion) => {
176-
if (!isLoading) {
177-
sendMessage(suggestion);
178-
scrollToBottom();
222+
onClick={() => {
223+
if (!(isLoading || isRateLimited)) {
224+
sendMessage(question.text);
179225
}
180226
}}
181-
suggestion={question.text}
227+
size="sm"
228+
style={{ animationDelay: `${index * 100}ms` }}
229+
variant="outline"
182230
>
183-
<question.icon className="mr-2 h-4 w-4 text-primary" />
184-
{question.text}
185-
</Suggestion>
231+
<question.icon className="mr-3 h-4 w-4 flex-shrink-0 text-primary/70" />
232+
<div className="flex-1">
233+
<div className="font-medium">{question.text}</div>
234+
<div className="text-muted-foreground text-xs capitalize">
235+
{question.type} response
236+
</div>
237+
</div>
238+
</Button>
186239
))}
187-
</Suggestions>
240+
</div>
188241
</div>
189242
</div>
190243
</div>
191244
)}
192245

246+
{/* Messages */}
193247
{hasMessages && (
194-
<div className="space-y-4">
248+
<div className="space-y-3">
195249
{messages.map((message, index) => (
196250
<MessageBubble
197251
handleFeedbackComment={handleFeedbackComment}
@@ -201,37 +255,97 @@ export default function ChatSection() {
201255
message={message}
202256
/>
203257
))}
258+
<div ref={bottomRef} />
204259
</div>
205260
)}
206261
</ConversationContent>
262+
<ConversationScrollButton />
207263
</Conversation>
208264

209-
{/* Input Area */}
210-
<div className="border-t p-4">
211-
<PromptInput
212-
onSubmit={(e) => {
213-
e.preventDefault();
214-
handleSend();
215-
}}
216-
>
217-
<PromptInputTextarea
218-
disabled={isLoading}
265+
{/* Enhanced Input Area */}
266+
<div className="flex-shrink-0 border-t bg-gradient-to-r from-muted/10 to-muted/5 p-4">
267+
<div className="flex gap-3">
268+
<Input
269+
className={cn(
270+
'h-11 flex-1 rounded-xl border-2 bg-background/50 backdrop-blur-sm',
271+
'placeholder:text-muted-foreground/60',
272+
'focus:border-primary/30 focus:bg-background/80',
273+
'transition-all duration-200'
274+
)}
275+
disabled={isLoading || isRateLimited}
219276
onChange={(e) => setInputValue(e.target.value)}
277+
onKeyDown={(e) => {
278+
if (e.key === 'Enter' && !e.shiftKey) {
279+
e.preventDefault();
280+
handleSend();
281+
} else {
282+
handleKeyPress(e);
283+
}
284+
}}
220285
placeholder={
221-
isLoading ? 'Analyzing...' : 'Ask about your analytics data...'
286+
isLoading
287+
? 'Databunny is thinking...'
288+
: isRateLimited
289+
? 'Rate limited - please wait...'
290+
: 'Ask Databunny about your analytics data...'
222291
}
223292
ref={inputRef}
224293
value={inputValue}
225294
/>
226-
<PromptInputToolbar>
227-
<div />
228-
<PromptInputSubmit
229-
disabled={!inputValue.trim() || isLoading}
230-
status={isLoading ? 'submitted' : undefined}
295+
<Button
296+
className={cn(
297+
'h-11 w-11 flex-shrink-0 rounded-xl',
298+
'bg-gradient-to-r from-primary to-primary/80',
299+
'hover:from-primary/90 hover:to-primary/70',
300+
'shadow-lg transition-all duration-200',
301+
(!inputValue.trim() || isRateLimited) &&
302+
!isLoading &&
303+
'opacity-50'
304+
)}
305+
disabled={!inputValue.trim() || isLoading || isRateLimited}
306+
onClick={handleSend}
307+
size="icon"
308+
title="Send message"
309+
>
310+
<PaperPlaneRightIcon
311+
className={cn(
312+
'h-4 w-4',
313+
inputValue.trim() && !isLoading && !isRateLimited && 'scale-110'
314+
)}
315+
weight="duotone"
231316
/>
232-
</PromptInputToolbar>
233-
</PromptInput>
317+
</Button>
318+
</div>
319+
320+
{/* Helper text */}
321+
<div className="mt-3 flex flex-wrap items-center justify-between gap-2 text-xs">
322+
<div className="flex items-center gap-2 text-muted-foreground">
323+
<SparkleIcon className="h-3 w-3 flex-shrink-0" weight="duotone" />
324+
<span>Ask about trends, comparisons, or specific metrics</span>
325+
</div>
326+
{hasMessages && (
327+
<div className="flex items-center gap-3 text-muted-foreground">
328+
{messageStats.charts > 0 && (
329+
<span className="flex items-center gap-1">
330+
<ChartBarIcon className="h-3 w-3" weight="duotone" />
331+
{messageStats.charts}
332+
</span>
333+
)}
334+
{messageStats.metrics > 0 && (
335+
<span className="flex items-center gap-1">
336+
<HashIcon className="h-3 w-3" weight="duotone" />
337+
{messageStats.metrics}
338+
</span>
339+
)}
340+
</div>
341+
)}
342+
</div>
234343
</div>
344+
345+
<ChatHistorySheet
346+
isOpen={showChatHistory}
347+
onClose={() => setShowChatHistory(false)}
348+
/>
235349
</div>
236350
);
237351
}

0 commit comments

Comments
 (0)