Skip to content

Commit 0365859

Browse files
committed
chat: loading indicator for user message being sent, scroll to bottom when a new user message appears
1 parent 5f130fa commit 0365859

File tree

2 files changed

+74
-18
lines changed

2 files changed

+74
-18
lines changed

chat/src/components/ChatInterface.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ export default function ChatInterface() {
281281
</div>
282282
)}
283283

284-
<MessageList messages={messages} />
284+
<MessageList messages={messages} loading={loading} />
285285

286286
<MessageInput onSendMessage={sendMessage} disabled={loading} />
287287
</div>

chat/src/components/MessageList.tsx

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,55 @@ interface Message {
1010

1111
interface MessageListProps {
1212
messages: Message[];
13+
loading?: boolean;
1314
}
1415

15-
export default function MessageList({ messages }: MessageListProps) {
16+
export default function MessageList({
17+
messages,
18+
loading = false,
19+
}: MessageListProps) {
1620
const messagesEndRef = useRef<HTMLDivElement>(null);
21+
const prevMessagesLengthRef = useRef<number>(0);
22+
const prevLoadingRef = useRef<boolean>(false);
1723

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)
1928
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" });
3455
}
3556
}
36-
}, [messages]);
57+
58+
// Update references for next comparison
59+
prevMessagesLengthRef.current = messages.length;
60+
prevLoadingRef.current = loading;
61+
}, [messages, loading]);
3762

3863
// If no messages, show a placeholder
3964
if (messages.length === 0) {
@@ -44,6 +69,24 @@ export default function MessageList({ messages }: MessageListProps) {
4469
);
4570
}
4671

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+
4790
return (
4891
<div className="flex-1 overflow-y-auto p-4 bg-white">
4992
{messages.map((message) => (
@@ -71,6 +114,19 @@ export default function MessageList({ messages }: MessageListProps) {
71114
</div>
72115
</div>
73116
))}
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+
74130
<div ref={messagesEndRef} />
75131
</div>
76132
);

0 commit comments

Comments
 (0)