1
1
"use client" ;
2
2
3
- import { useEffect , useRef } from "react" ;
4
-
5
3
interface Message {
6
4
role : string ;
7
5
content : string ;
@@ -17,49 +15,6 @@ export default function MessageList({
17
15
messages,
18
16
loading = false ,
19
17
} : MessageListProps ) {
20
- const messagesEndRef = useRef < HTMLDivElement > ( null ) ;
21
- const prevMessagesLengthRef = useRef < number > ( 0 ) ;
22
- const prevLoadingRef = useRef < boolean > ( false ) ;
23
-
24
- // Enhanced scrolling behavior to handle:
25
- // 1. New messages being added
26
- // 2. Loading indicator appearing/disappearing
27
- // 3. New user messages (to ensure they're always visible)
28
- useEffect ( ( ) => {
29
- const lastMessage = messages [ messages . length - 1 ] ;
30
- const isNewUserMessage =
31
- messages . length > prevMessagesLengthRef . current &&
32
- lastMessage ?. role === "user" ;
33
-
34
- const loadingChanged = loading !== prevLoadingRef . current ;
35
-
36
- // Store current scroll position and total scroll height
37
- const messageContainer = messagesEndRef . current ?. parentElement ;
38
- if ( messagesEndRef . current && messageContainer ) {
39
- // Determine if we should force scroll
40
- const shouldForceScroll =
41
- isNewUserMessage || // New user message added
42
- loading || // Loading indicator is active
43
- loadingChanged ; // Loading state changed
44
-
45
- // Check if we're already near the bottom
46
- const isNearBottom =
47
- messageContainer . scrollHeight -
48
- messageContainer . scrollTop -
49
- messageContainer . clientHeight <
50
- 100 ;
51
-
52
- // Scroll if we're forced to or if we're already near the bottom
53
- if ( shouldForceScroll || isNearBottom ) {
54
- messagesEndRef . current . scrollIntoView ( { behavior : "smooth" } ) ;
55
- }
56
- }
57
-
58
- // Update references for next comparison
59
- prevMessagesLengthRef . current = messages . length ;
60
- prevLoadingRef . current = loading ;
61
- } , [ messages , loading ] ) ;
62
-
63
18
// If no messages, show a placeholder
64
19
if ( messages . length === 0 ) {
65
20
return (
@@ -89,7 +44,32 @@ export default function MessageList({
89
44
) ;
90
45
91
46
return (
92
- < div className = "overflow-y-auto" >
47
+ < div
48
+ className = "overflow-y-auto"
49
+ ref = { ( scrollAreaRef ) => {
50
+ if ( ! scrollAreaRef ) {
51
+ return ;
52
+ }
53
+
54
+ scrollAreaRef . scrollTo ( {
55
+ top : scrollAreaRef . scrollHeight ,
56
+ } ) ;
57
+
58
+ const callback : MutationCallback = ( mutationsList ) => {
59
+ for ( const mutation of mutationsList ) {
60
+ if ( mutation . type === "childList" ) {
61
+ scrollAreaRef . scrollTo ( {
62
+ top : scrollAreaRef . scrollHeight ,
63
+ behavior : "smooth" ,
64
+ } ) ;
65
+ }
66
+ }
67
+ } ;
68
+
69
+ const observer = new MutationObserver ( callback ) ;
70
+ observer . observe ( scrollAreaRef , { childList : true , subtree : false } ) ;
71
+ } }
72
+ >
93
73
< div className = "p-4 flex flex-col gap-4 max-w-4xl mx-auto" >
94
74
{ messages . map ( ( message ) => (
95
75
< div
@@ -124,8 +104,6 @@ export default function MessageList({
124
104
< LoadingDots />
125
105
</ div >
126
106
) }
127
-
128
- < div ref = { messagesEndRef } />
129
107
</ div >
130
108
</ div >
131
109
) ;
0 commit comments