11"use client" ;
22
3- import React , { useLayoutEffect , useRef , useEffect , useCallback , useMemo } from "react" ;
3+ import React , { useLayoutEffect , useRef , useEffect , useCallback , useMemo , useState } from "react" ;
44
55interface Message {
66 role : string ;
@@ -24,18 +24,20 @@ interface ProcessedMessageProps {
2424}
2525
2626export default function MessageList ( { messages} : MessageListProps ) {
27- const scrollAreaRef = useRef < HTMLDivElement > ( null ) ;
27+ const [ scrollAreaRef , setScrollAreaRef ] = useState < HTMLDivElement | null > ( null ) ;
2828
2929 // Track if user is at bottom - default to true for initial scroll
3030 const isAtBottomRef = useRef ( true ) ;
3131 // Track the last known scroll height to detect new content
3232 const lastScrollHeightRef = useRef ( 0 ) ;
33+ // Track if we're currently doing a programmatic scroll
34+ const isProgrammaticScrollRef = useRef ( false ) ;
3335
3436 const checkIfAtBottom = useCallback ( ( ) => {
35- if ( ! scrollAreaRef . current ) return false ;
36- const { scrollTop, scrollHeight, clientHeight } = scrollAreaRef . current ;
37+ if ( ! scrollAreaRef ) return false ;
38+ const { scrollTop, scrollHeight, clientHeight } = scrollAreaRef ;
3739 return scrollTop + clientHeight >= scrollHeight - 10 ; // 10px tolerance
38- } , [ ] ) ;
40+ } , [ scrollAreaRef ] ) ;
3941
4042 // Track Ctrl (Windows/Linux) or Cmd (Mac) key state
4143 // This is so that underline is only visible when hover + cmd/ctrl
@@ -60,26 +62,26 @@ export default function MessageList({messages}: MessageListProps) {
6062
6163 // Update isAtBottom on scroll
6264 useEffect ( ( ) => {
63- const scrollContainer = scrollAreaRef . current ;
64- if ( ! scrollContainer ) return ;
65+ if ( ! scrollAreaRef ) return ;
6566
6667 const handleScroll = ( ) => {
68+ if ( isProgrammaticScrollRef . current ) return ;
6769 isAtBottomRef . current = checkIfAtBottom ( ) ;
6870 } ;
6971
7072 // Initial check
7173 handleScroll ( ) ;
7274
73- scrollContainer . addEventListener ( "scroll" , handleScroll ) ;
74- return ( ) => scrollContainer . removeEventListener ( "scroll" , handleScroll ) ;
75- } , [ checkIfAtBottom ] ) ;
75+ scrollAreaRef . addEventListener ( "scroll" , handleScroll ) ;
76+ scrollAreaRef . addEventListener ( "scrollend" , ( ) => isProgrammaticScrollRef . current = false ) ;
77+ return ( ) => scrollAreaRef . removeEventListener ( "scroll" , handleScroll ) ;
78+ } , [ checkIfAtBottom , scrollAreaRef ] ) ;
7679
7780 // Handle auto-scrolling when messages change
7881 useLayoutEffect ( ( ) => {
79- if ( ! scrollAreaRef . current ) return ;
82+ if ( ! scrollAreaRef ) return ;
8083
81- const scrollContainer = scrollAreaRef . current ;
82- const currentScrollHeight = scrollContainer . scrollHeight ;
84+ const currentScrollHeight = scrollAreaRef . scrollHeight ;
8385
8486 // Check if this is new content (scroll height increased)
8587 const hasNewContent = currentScrollHeight > lastScrollHeightRef . current ;
@@ -95,7 +97,8 @@ export default function MessageList({messages}: MessageListProps) {
9597 hasNewContent &&
9698 ( isFirstRender || isAtBottomRef . current || isNewUserMessage )
9799 ) {
98- scrollContainer . scrollTo ( {
100+ isProgrammaticScrollRef . current = true ;
101+ scrollAreaRef . scrollTo ( {
99102 top : currentScrollHeight ,
100103 behavior : isFirstRender ? "instant" : "smooth" ,
101104 } ) ;
@@ -105,7 +108,7 @@ export default function MessageList({messages}: MessageListProps) {
105108
106109 // Update the last known scroll height
107110 lastScrollHeightRef . current = currentScrollHeight ;
108- } , [ messages ] ) ;
111+ } , [ messages , scrollAreaRef ] ) ;
109112
110113 // If no messages, show a placeholder
111114 if ( messages . length === 0 ) {
@@ -117,7 +120,7 @@ export default function MessageList({messages}: MessageListProps) {
117120 }
118121
119122 return (
120- < div className = "overflow-y-auto flex-1" ref = { scrollAreaRef } >
123+ < div className = "overflow-y-auto flex-1" ref = { setScrollAreaRef } >
121124 < div
122125 className = "p-4 flex flex-col gap-4 max-w-4xl mx-auto transition-all duration-300 ease-in-out min-h-0" >
123126 { messages . map ( ( message , index ) => (
@@ -191,7 +194,6 @@ const ProcessedMessage = React.memo(function ProcessedMessage({
191194
192195 const linkedContent = useMemo ( ( ) => {
193196 return messageContent . split ( urlRegex ) . map ( ( content , idx ) => {
194- console . log ( content )
195197 if ( urlRegex . test ( content ) ) {
196198 return (
197199 < a
0 commit comments