@@ -64,6 +64,8 @@ export const RemixUiRemixAiAssistant = React.forwardRef<
6464 const contextBtnRef = useRef ( null )
6565 const textareaRef = useRef < HTMLTextAreaElement > ( null )
6666 const aiChatRef = useRef < HTMLDivElement > ( null )
67+ const userHasScrolledRef = useRef ( false )
68+ const lastMessageCountRef = useRef ( 0 )
6769
6870 useOnClickOutside ( [ modelBtnRef , contextBtnRef ] , ( ) => setShowAssistantOptions ( false ) )
6971 useOnClickOutside ( [ modelBtnRef , contextBtnRef ] , ( ) => setShowContextOptions ( false ) )
@@ -224,13 +226,25 @@ export const RemixUiRemixAiAssistant = React.forwardRef<
224226 props . onMessagesChange ?.( messages )
225227 } , [ messages , props . onMessagesChange ] )
226228
227- // always scroll to bottom when messages change
229+ // Smart auto- scroll: only scroll to bottom if:
228230 useEffect ( ( ) => {
229231 const node = historyRef . current
230- if ( node && messages . length > 0 ) {
232+ if ( ! node || messages . length === 0 ) return
233+
234+ const isAtBottom = node . scrollHeight - node . scrollTop - node . clientHeight < 100
235+ const userSentNewMessage = messages . length > lastMessageCountRef . current &&
236+ messages [ messages . length - 1 ] ?. role === 'user'
237+ // Auto-scroll conditions:
238+ // - User sent a new message (always scroll)
239+ // - User hasn't manually scrolled up (userHasScrolledRef is false)
240+ // - Currently streaming and user is near bottom
241+ if ( userSentNewMessage || ! userHasScrolledRef . current || ( isStreaming && isAtBottom ) ) {
231242 node . scrollTop = node . scrollHeight
243+ userHasScrolledRef . current = false
232244 }
233- } , [ messages ] )
245+
246+ lastMessageCountRef . current = messages . length
247+ } , [ messages , isStreaming ] )
234248
235249 useEffect ( ( ) => {
236250 if ( textareaRef . current ) {
@@ -662,11 +676,24 @@ export const RemixUiRemixAiAssistant = React.forwardRef<
662676 )
663677 const chatHistoryRef = useRef < HTMLElement | null > ( null )
664678
679+ // Detect manual user scrolling
665680 useEffect ( ( ) => {
666- if ( chatHistoryRef . current ) {
667- chatHistoryRef . current . scrollTop = chatHistoryRef . current . scrollHeight
681+ const node = historyRef . current
682+ if ( ! node ) return
683+
684+ const handleScroll = ( ) => {
685+ const isAtBottom = node . scrollHeight - node . scrollTop - node . clientHeight < 100
686+
687+ if ( ! isAtBottom ) {
688+ userHasScrolledRef . current = true
689+ } else {
690+ userHasScrolledRef . current = false
691+ }
668692 }
669- } , [ messages ] )
693+
694+ node . addEventListener ( 'scroll' , handleScroll )
695+ return ( ) => node . removeEventListener ( 'scroll' , handleScroll )
696+ } , [ ] )
670697
671698 const maximizePanel = async ( ) => {
672699 await props . plugin . call ( 'layout' , 'maximisePinnedPanel' )
0 commit comments