@@ -78,16 +78,57 @@ function Chat() {
7878 const [ isLoading , setIsLoading ] = useState ( false ) ;
7979 const [ isSettingsOpen , setIsSettingsOpen ] = useState ( false ) ;
8080 const messagesEndRef = useRef < HTMLDivElement > ( null ) ;
81+ const chatContainerRef = useRef < HTMLDivElement > ( null ) ;
82+ const [ autoScroll , setAutoScroll ] = useState ( true ) ;
83+ const isStreamingRef = useRef ( false ) ;
84+ const scrollTimeoutRef = useRef < NodeJS . Timeout | null > ( null ) ;
8185
8286 // Add effect to log model changes
8387 useEffect ( ( ) => {
8488 console . log ( "Model changed to:" , selectedModel ) ;
8589 } , [ selectedModel ] ) ;
8690
91+ // Improved scrollToBottom with debouncing during streaming
8792 const scrollToBottom = ( ) => {
88- messagesEndRef . current ?. scrollIntoView ( { behavior : "smooth" } ) ;
93+ if ( ! autoScroll ) return ;
94+
95+ // Clear any pending scroll timeout
96+ if ( scrollTimeoutRef . current ) {
97+ clearTimeout ( scrollTimeoutRef . current ) ;
98+ }
99+
100+ // If streaming, debounce the scroll to avoid too many scroll events
101+ if ( isStreamingRef . current ) {
102+ scrollTimeoutRef . current = setTimeout ( ( ) => {
103+ if ( messagesEndRef . current ) {
104+ messagesEndRef . current . scrollIntoView ( { behavior : "auto" } ) ;
105+ }
106+ } , 300 ) ; // Adjust this timeout as needed
107+ } else {
108+ // For non-streaming updates, scroll immediately
109+ if ( messagesEndRef . current ) {
110+ messagesEndRef . current . scrollIntoView ( { behavior : "smooth" } ) ;
111+ }
112+ }
89113 } ;
90114
115+ useEffect ( ( ) => {
116+ const chatContainer = chatContainerRef . current ;
117+
118+ const handleScroll = ( ) => {
119+ if ( ! chatContainer ) return ;
120+
121+ const { scrollTop, scrollHeight, clientHeight } = chatContainer ;
122+ // If user has scrolled up more than 100px from bottom, disable auto-scroll
123+ // When they scroll back to bottom, re-enable it
124+ const isNearBottom = scrollHeight - scrollTop - clientHeight < 100 ;
125+ setAutoScroll ( isNearBottom ) ;
126+ } ;
127+
128+ chatContainer ?. addEventListener ( "scroll" , handleScroll ) ;
129+ return ( ) => chatContainer ?. removeEventListener ( "scroll" , handleScroll ) ;
130+ } , [ ] ) ;
131+
91132 useEffect ( ( ) => {
92133 scrollToBottom ( ) ;
93134 } , [ chatHistory ] ) ;
@@ -137,9 +178,17 @@ function Chat() {
137178
138179 setChatHistory ( ( prev ) => [ ...prev , assistantMessage ] ) ;
139180
181+ // Set streaming flag to true
182+ isStreamingRef . current = true ;
183+
140184 while ( true ) {
141185 const { done, value } = await reader . read ( ) ;
142- if ( done ) break ;
186+ if ( done ) {
187+ // Final scroll when streaming is complete
188+ isStreamingRef . current = false ;
189+ setTimeout ( scrollToBottom , 100 ) ;
190+ break ;
191+ }
143192
144193 const chunk = decoder . decode ( value ) ;
145194 assistantMessage . content += chunk ;
@@ -149,15 +198,22 @@ function Chat() {
149198 newHistory [ newHistory . length - 1 ] = { ...assistantMessage } ;
150199 return newHistory ;
151200 } ) ;
201+
202+ // Only scroll occasionally during streaming, not on every chunk
203+ if ( chunk . length > 50 ) {
204+ scrollToBottom ( ) ;
205+ }
152206 }
153207 } catch ( error ) {
154208 console . error ( "Error:" , error ) ;
155209 setChatHistory ( ( prev ) => [
156210 ...prev ,
157211 { role : "assistant" , content : "Error: Could not connect to server" } ,
158212 ] ) ;
213+ isStreamingRef . current = false ;
159214 } finally {
160215 setIsLoading ( false ) ;
216+ isStreamingRef . current = false ;
161217 }
162218 } ;
163219
@@ -252,7 +308,10 @@ function Chat() {
252308 </ button >
253309 </ div >
254310
255- < div className = "flex-1 overflow-y-auto scrollbar-custom p-6 space-y-8" >
311+ < div
312+ ref = { chatContainerRef }
313+ className = "flex-1 overflow-y-auto scrollbar-custom p-6 space-y-8"
314+ >
256315 { chatHistory . length === 0 ? (
257316 < div className = "text-center text-gray-500 mt-20" >
258317 < Welcome />
0 commit comments