11"use client" ;
22
3- import { useLayoutEffect , useRef } from "react" ;
3+ import { useLayoutEffect , useRef , useEffect , useCallback } from "react" ;
44
55interface Message {
66 role : string ;
@@ -27,18 +27,69 @@ export default function MessageList({ messages }: MessageListProps) {
2727 // the content, and use that as the min height of the scroll area.
2828 const contentMinHeight = useRef ( 0 ) ;
2929
30+ // Track if user is at bottom - default to true for initial scroll
31+ const isAtBottomRef = useRef ( true ) ;
32+ // Track the last known scroll height to detect new content
33+ const lastScrollHeightRef = useRef ( 0 ) ;
34+
35+ const checkIfAtBottom = useCallback ( ( ) => {
36+ if ( ! scrollAreaRef . current ) return false ;
37+ const { scrollTop, scrollHeight, clientHeight } = scrollAreaRef . current ;
38+ return scrollTop + clientHeight >= scrollHeight - 10 ; // 10px tolerance
39+ } , [ ] ) ;
40+
41+ // Update isAtBottom on scroll
42+ useEffect ( ( ) => {
43+ const scrollContainer = scrollAreaRef . current ;
44+ if ( ! scrollContainer ) return ;
45+
46+ const handleScroll = ( ) => {
47+ isAtBottomRef . current = checkIfAtBottom ( ) ;
48+ } ;
49+
50+ // Initial check
51+ handleScroll ( ) ;
52+
53+ scrollContainer . addEventListener ( "scroll" , handleScroll ) ;
54+ return ( ) => scrollContainer . removeEventListener ( "scroll" , handleScroll ) ;
55+ } , [ checkIfAtBottom ] ) ;
56+
57+ // Handle auto-scrolling when messages change
3058 useLayoutEffect ( ( ) => {
59+ if ( ! scrollAreaRef . current ) return ;
60+
61+ const scrollContainer = scrollAreaRef . current ;
62+ const currentScrollHeight = scrollContainer . scrollHeight ;
63+
64+ // Check if this is new content (scroll height increased)
65+ const hasNewContent = currentScrollHeight > lastScrollHeightRef . current ;
66+ const isFirstRender = lastScrollHeightRef . current === 0 ;
67+ const isNewUserMessage =
68+ messages . length > 0 && messages [ messages . length - 1 ] . role === "user" ;
69+
70+ // Update content min height if needed
71+ if ( currentScrollHeight > contentMinHeight . current ) {
72+ contentMinHeight . current = currentScrollHeight ;
73+ }
74+
75+ // Auto-scroll only if:
76+ // 1. It's the first render, OR
77+ // 2. There's new content AND user was at the bottom, OR
78+ // 3. The user sent a new message
3179 if (
32- scrollAreaRef . current &&
33- scrollAreaRef . current . scrollHeight > contentMinHeight . current
80+ hasNewContent &&
81+ ( isFirstRender || isAtBottomRef . current || isNewUserMessage )
3482 ) {
35- const isFirstScroll = contentMinHeight . current === 0 ;
36- scrollAreaRef . current . scrollTo ( {
37- top : scrollAreaRef . current . scrollHeight ,
38- behavior : isFirstScroll ? "instant" : "smooth" ,
83+ scrollContainer . scrollTo ( {
84+ top : currentScrollHeight ,
85+ behavior : isFirstRender ? "instant" : "smooth" ,
3986 } ) ;
40- contentMinHeight . current = scrollAreaRef . current . scrollHeight ;
87+ // After scrolling, we're at the bottom
88+ isAtBottomRef . current = true ;
4189 }
90+
91+ // Update the last known scroll height
92+ lastScrollHeightRef . current = currentScrollHeight ;
4293 } , [ messages ] ) ;
4394
4495 // If no messages, show a placeholder
0 commit comments