11"use client" ;
22
3- import { useMutation } from "@tanstack/react-query" ;
3+ import {
4+ QueryClient ,
5+ QueryClientProvider ,
6+ useMutation ,
7+ } from "@tanstack/react-query" ;
48import {
59 ArrowUpIcon ,
610 ThumbsDownIcon ,
@@ -41,6 +45,8 @@ const predefinedPrompts = [
4145 "How do I send a transaction in Unity?" ,
4246] ;
4347
48+ const queryClient = new QueryClient ( ) ;
49+
4450// Empty State Component
4551function ChatEmptyState ( {
4652 onPromptClick,
@@ -156,13 +162,16 @@ export function Chat() {
156162 [ conversationId , posthog ] ,
157163 ) ;
158164
165+ const lastMessageLength = messages [ messages . length - 1 ] ?. content . length ?? 0 ;
166+
167+ // biome-ignore lint/correctness/useExhaustiveDependencies: need both the number of messages and the last message length to trigger the scroll
159168 useEffect ( ( ) => {
160169 if ( scrollAnchorRef . current && messages . length > 0 ) {
161170 scrollAnchorRef . current . scrollIntoView ( {
162171 behavior : "smooth" ,
163172 } ) ;
164173 }
165- } , [ messages . length ] ) ;
174+ } , [ messages . length , lastMessageLength ] ) ;
166175
167176 const handleInputChange = ( e : ChangeEvent < HTMLTextAreaElement > ) => {
168177 setInput ( e . target . value ) ;
@@ -178,57 +187,59 @@ export function Chat() {
178187 } ;
179188
180189 return (
181- < div className = "container flex max-h-full flex-col grow overflow-hidden lg:max-w-4xl pb-6" >
182- < Toaster richColors />
183- < div className = "relative flex max-h-full flex-1 flex-col overflow-hidden" >
184- { messages . length === 0 ? (
185- < ChatEmptyState onPromptClick = { handleSendMessage } />
186- ) : (
187- < ScrollShadow
188- className = "flex-1"
189- scrollableClassName = "max-h-full overscroll-contain"
190- shadowColor = "hsl(var(--background))"
191- shadowClassName = "z-[1]"
192- >
193- < div className = "space-y-8 pt-10 pb-16" >
194- { messages . map ( ( message ) => (
195- < RenderMessage
196- conversationId = { conversationId }
197- message = { message }
198- key = { message . id }
199- />
200- ) ) }
201- </ div >
202- < div ref = { scrollAnchorRef } />
203- </ ScrollShadow >
204- ) }
205- </ div >
190+ < QueryClientProvider client = { queryClient } >
191+ < div className = "flex max-h-full flex-col grow overflow-hidden" >
192+ < Toaster richColors />
193+ < div className = "relative flex max-h-full flex-1 flex-col overflow-hidden px-4" >
194+ { messages . length === 0 ? (
195+ < ChatEmptyState onPromptClick = { handleSendMessage } />
196+ ) : (
197+ < ScrollShadow
198+ className = "flex-1"
199+ scrollableClassName = "max-h-full overscroll-contain"
200+ shadowColor = "hsl(var(--background))"
201+ shadowClassName = "z-[1]"
202+ >
203+ < div className = "space-y-8 pt-6 pb-16" >
204+ { messages . map ( ( message ) => (
205+ < RenderMessage
206+ conversationId = { conversationId }
207+ message = { message }
208+ key = { message . id }
209+ />
210+ ) ) }
211+ </ div >
212+ < div ref = { scrollAnchorRef } />
213+ </ ScrollShadow >
214+ ) }
215+ </ div >
206216
207- < div className = "relative z-stickyTop" >
208- < AutoResizeTextarea
209- className = "min-h-[120px] rounded-xl bg-card"
210- onChange = { handleInputChange }
211- onKeyDown = { handleKeyDown }
212- placeholder = "Ask AI Assistant..."
213- rows = { 2 }
214- value = { input }
215- />
216- < Button
217- className = "absolute bottom-3 right-3 disabled:opacity-100 !h-auto w-auto shrink-0 gap-2 p-2"
218- disabled = { ! input . trim ( ) }
219- onClick = { ( ) => {
220- const currentInput = input ;
221- setInput ( "" ) ;
222- handleSendMessage ( currentInput ) ;
223- } }
224- type = "submit"
225- size = "sm"
226- variant = "default"
227- >
228- < ArrowUpIcon className = "size-4" />
229- </ Button >
217+ < div className = "relative z-stickyTop" >
218+ < AutoResizeTextarea
219+ className = "min-h-[120px] rounded-xl border-x-0 border-b-0 rounded-t-none bg-card focus-visible:ring-0 focus-visible:ring-offset-0"
220+ onChange = { handleInputChange }
221+ onKeyDown = { handleKeyDown }
222+ placeholder = "Ask AI Assistant..."
223+ rows = { 2 }
224+ value = { input }
225+ />
226+ < Button
227+ className = "absolute bottom-3 right-3 disabled:opacity-100 !h-auto w-auto shrink-0 gap-2 p-2"
228+ disabled = { ! input . trim ( ) }
229+ onClick = { ( ) => {
230+ const currentInput = input ;
231+ setInput ( "" ) ;
232+ handleSendMessage ( currentInput ) ;
233+ } }
234+ type = "submit"
235+ size = "sm"
236+ variant = "default"
237+ >
238+ < ArrowUpIcon className = "size-4" />
239+ </ Button >
240+ </ div >
230241 </ div >
231- </ div >
242+ </ QueryClientProvider >
232243 ) ;
233244}
234245
@@ -272,7 +283,7 @@ function RenderAIResponse(props: {
272283 return (
273284 < div className = "flex items-start gap-3.5" >
274285 { aiIcon }
275- < div className = "flex-1 min-w-0 overflow-hidden" >
286+ < div className = "flex-1 min-w-0 overflow-hidden fade-in-0 duration-300 animate-in " >
276287 < StyledMarkdownRenderer
277288 text = { props . message . content }
278289 type = "assistant"
@@ -334,7 +345,7 @@ function RenderMessage(props: {
334345 return (
335346 < div className = "flex items-start gap-3.5" >
336347 { userIcon }
337- < div className = "px-3.5 py-2 rounded-xl border bg-card relative" >
348+ < div className = "px-3.5 py-2 rounded-xl border bg-card relative fade-in-0 duration-300 animate-in " >
338349 < StyledMarkdownRenderer
339350 text = { props . message . content }
340351 type = "user"
@@ -349,7 +360,9 @@ function RenderMessage(props: {
349360 return (
350361 < div className = "flex items-center gap-3.5" >
351362 { aiIcon }
352- < TextShimmer text = "Thinking..." className = "text-sm md:text-base" />
363+ < div className = "fade-in-0 duration-300 animate-in" >
364+ < TextShimmer text = "Thinking..." className = "text-sm" />
365+ </ div >
353366 </ div >
354367 ) ;
355368 }
@@ -371,7 +384,7 @@ function StyledMarkdownRenderer(props: {
371384} ) {
372385 return (
373386 < MarkdownRenderer
374- className = "text-sm md:text-base text-foreground [&>*:first-child]:mt-0 [&>*:first-child]:border-none [&>*:first-child]:pb-0 [&>*:last-child]:mb-0 leading-relaxed"
387+ className = "text-sm text-foreground [&>*:first-child]:mt-0 [&>*:first-child]:border-none [&>*:first-child]:pb-0 [&>*:last-child]:mb-0 leading-relaxed"
375388 code = { {
376389 className : "bg-card" ,
377390 ignoreFormattingErrors : true ,
0 commit comments