@@ -10,30 +10,55 @@ interface Message {
10
10
11
11
interface MessageListProps {
12
12
messages : Message [ ] ;
13
+ loading ?: boolean ;
13
14
}
14
15
15
- export default function MessageList ( { messages } : MessageListProps ) {
16
+ export default function MessageList ( {
17
+ messages,
18
+ loading = false ,
19
+ } : MessageListProps ) {
16
20
const messagesEndRef = useRef < HTMLDivElement > ( null ) ;
21
+ const prevMessagesLengthRef = useRef < number > ( 0 ) ;
22
+ const prevLoadingRef = useRef < boolean > ( false ) ;
17
23
18
- // Only scroll to bottom when new messages are added or updated
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)
19
28
useEffect ( ( ) => {
20
- const shouldScroll = messagesEndRef . current && messages . length > 0 ;
21
- if ( shouldScroll ) {
22
- // Store current scroll position and total scroll height
23
- const messageContainer = messagesEndRef . current ?. parentElement ;
24
- if ( messageContainer ) {
25
- // Only scroll if we're already near the bottom or if messages length has changed
26
- const isNearBottom =
27
- messageContainer . scrollHeight -
28
- messageContainer . scrollTop -
29
- messageContainer . clientHeight <
30
- 100 ;
31
- if ( isNearBottom ) {
32
- messagesEndRef . current ?. scrollIntoView ( { behavior : "smooth" } ) ;
33
- }
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" } ) ;
34
55
}
35
56
}
36
- } , [ messages ] ) ;
57
+
58
+ // Update references for next comparison
59
+ prevMessagesLengthRef . current = messages . length ;
60
+ prevLoadingRef . current = loading ;
61
+ } , [ messages , loading ] ) ;
37
62
38
63
// If no messages, show a placeholder
39
64
if ( messages . length === 0 ) {
@@ -44,6 +69,24 @@ export default function MessageList({ messages }: MessageListProps) {
44
69
) ;
45
70
}
46
71
72
+ // Define a component for the animated dots
73
+ const LoadingDots = ( ) => (
74
+ < div className = "flex space-x-1" >
75
+ < div
76
+ className = "w-2 h-2 rounded-full bg-white animate-pulse"
77
+ style = { { animationDelay : "0ms" } }
78
+ > </ div >
79
+ < div
80
+ className = "w-2 h-2 rounded-full bg-white animate-pulse"
81
+ style = { { animationDelay : "300ms" } }
82
+ > </ div >
83
+ < div
84
+ className = "w-2 h-2 rounded-full bg-white animate-pulse"
85
+ style = { { animationDelay : "600ms" } }
86
+ > </ div >
87
+ </ div >
88
+ ) ;
89
+
47
90
return (
48
91
< div className = "flex-1 overflow-y-auto p-4 bg-white" >
49
92
{ messages . map ( ( message ) => (
@@ -71,6 +114,19 @@ export default function MessageList({ messages }: MessageListProps) {
71
114
</ div >
72
115
</ div >
73
116
) ) }
117
+
118
+ { /* Loading indicator for message being sent */ }
119
+ { loading && (
120
+ < div className = "mb-4 text-right" >
121
+ < div className = "inline-block px-4 py-2 rounded-lg bg-blue-500 text-white rounded-tr-none" >
122
+ < div className = "text-xs mb-1 font-bold text-left" > You</ div >
123
+ < div className = "h-6 flex items-center" >
124
+ < LoadingDots />
125
+ </ div >
126
+ </ div >
127
+ </ div >
128
+ ) }
129
+
74
130
< div ref = { messagesEndRef } />
75
131
</ div >
76
132
) ;
0 commit comments