11'use client' ;
22
33import {
4- BrainIcon ,
54 ChartBarIcon ,
5+ ChatIcon ,
66 ClockCounterClockwiseIcon ,
77 HashIcon ,
88 LightningIcon ,
9+ PaperPlaneRightIcon ,
910 SparkleIcon ,
1011 TrendUpIcon ,
1112} from '@phosphor-icons/react' ;
1213import { 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' ;
1515import {
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' ;
2823import { 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' ;
3130import { useChat } from '../hooks/use-chat' ;
31+ import { ChatHistorySheet } from './chat-history-sheet' ;
3232import { MessageBubble } from './message-bubble' ;
3333import { ModelSelector } from './model-selector' ;
3434
@@ -73,29 +73,49 @@ export function ChatSkeleton() {
7373}
7474
7575const 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
8290export 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