1
1
"use client" ;
2
2
3
- import { useMutation } from "@tanstack/react-query" ;
3
+ import {
4
+ QueryClient ,
5
+ QueryClientProvider ,
6
+ useMutation ,
7
+ } from "@tanstack/react-query" ;
4
8
import {
5
9
ArrowUpIcon ,
6
10
ThumbsDownIcon ,
@@ -41,6 +45,8 @@ const predefinedPrompts = [
41
45
"How do I send a transaction in Unity?" ,
42
46
] ;
43
47
48
+ const queryClient = new QueryClient ( ) ;
49
+
44
50
// Empty State Component
45
51
function ChatEmptyState ( {
46
52
onPromptClick,
@@ -156,13 +162,16 @@ export function Chat() {
156
162
[ conversationId , posthog ] ,
157
163
) ;
158
164
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
159
168
useEffect ( ( ) => {
160
169
if ( scrollAnchorRef . current && messages . length > 0 ) {
161
170
scrollAnchorRef . current . scrollIntoView ( {
162
171
behavior : "smooth" ,
163
172
} ) ;
164
173
}
165
- } , [ messages . length ] ) ;
174
+ } , [ messages . length , lastMessageLength ] ) ;
166
175
167
176
const handleInputChange = ( e : ChangeEvent < HTMLTextAreaElement > ) => {
168
177
setInput ( e . target . value ) ;
@@ -178,57 +187,59 @@ export function Chat() {
178
187
} ;
179
188
180
189
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 >
206
216
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 >
230
241
</ div >
231
- </ div >
242
+ </ QueryClientProvider >
232
243
) ;
233
244
}
234
245
@@ -272,7 +283,7 @@ function RenderAIResponse(props: {
272
283
return (
273
284
< div className = "flex items-start gap-3.5" >
274
285
{ 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 " >
276
287
< StyledMarkdownRenderer
277
288
text = { props . message . content }
278
289
type = "assistant"
@@ -334,7 +345,7 @@ function RenderMessage(props: {
334
345
return (
335
346
< div className = "flex items-start gap-3.5" >
336
347
{ 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 " >
338
349
< StyledMarkdownRenderer
339
350
text = { props . message . content }
340
351
type = "user"
@@ -349,7 +360,9 @@ function RenderMessage(props: {
349
360
return (
350
361
< div className = "flex items-center gap-3.5" >
351
362
{ 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 >
353
366
</ div >
354
367
) ;
355
368
}
@@ -371,7 +384,7 @@ function StyledMarkdownRenderer(props: {
371
384
} ) {
372
385
return (
373
386
< 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"
375
388
code = { {
376
389
className : "bg-card" ,
377
390
ignoreFormattingErrors : true ,
0 commit comments