@@ -12,15 +12,31 @@ import { AcpInput } from './AcpInput';
1212import { AcpMessages } from './AcpMessages' ;
1313import { AcpIcons } from './AcpIcons' ;
1414
15- const useAutoScroll = ( messages : AcpMessage [ ] ) => {
15+ const useAutoScroll = ( messages : AcpMessage [ ] , isProcessing : boolean ) => {
1616 const contentRef = useRef < HTMLDivElement > ( null ) ;
17- const isScrolledToBottomRef = useRef ( true ) ;
17+ const autoScrollEnabledRef = useRef ( true ) ;
1818 const lastScrollTopRef = useRef < number > ( 0 ) ;
19- const userScrolledUpRef = useRef ( false ) ;
19+ const isProgrammaticScrollRef = useRef ( false ) ;
20+ const [ autoScrollEnabled , setAutoScrollEnabled ] = useState ( true ) ;
2021
2122 const checkIfScrolledToBottom = ( element : HTMLElement ) : boolean => {
22- const threshold = 70 ;
23- return element . scrollHeight - element . scrollTop - element . clientHeight <= threshold ;
23+ return element . scrollHeight - element . scrollTop - element . clientHeight <= 48 ;
24+ } ;
25+
26+ const setAutoScroll = ( enabled : boolean ) => {
27+ autoScrollEnabledRef . current = enabled ;
28+ setAutoScrollEnabled ( prev => ( prev === enabled ? prev : enabled ) ) ;
29+ } ;
30+
31+ const scrollToBottom = ( behavior : ScrollBehavior = 'auto' ) => {
32+ const element = contentRef . current ;
33+ if ( ! element ) return ;
34+
35+ isProgrammaticScrollRef . current = true ;
36+ element . scrollTo ( {
37+ top : element . scrollHeight ,
38+ behavior,
39+ } ) ;
2440 } ;
2541
2642 useEffect ( ( ) => {
@@ -29,33 +45,48 @@ const useAutoScroll = (messages: AcpMessage[]) => {
2945
3046 const handleScroll = ( ) => {
3147 const currentScrollTop = contentElement . scrollTop ;
32- const scrollDirection = currentScrollTop < lastScrollTopRef . current ? 'up' : 'down' ;
33-
48+ const delta = currentScrollTop - lastScrollTopRef . current ;
49+ const scrollDirection = delta < - 1 ? 'up' : delta > 1 ? 'down' : 'none' ;
50+
51+ if ( isProgrammaticScrollRef . current ) {
52+ if ( checkIfScrolledToBottom ( contentElement ) ) {
53+ isProgrammaticScrollRef . current = false ;
54+ }
55+ lastScrollTopRef . current = currentScrollTop ;
56+ return ;
57+ }
58+
3459 if ( scrollDirection === 'up' ) {
35- userScrolledUpRef . current = true ;
36- } else if ( scrollDirection === 'down' && checkIfScrolledToBottom ( contentElement ) ) {
37- userScrolledUpRef . current = false ;
60+ setAutoScroll ( false ) ;
3861 }
39-
40- isScrolledToBottomRef . current = checkIfScrolledToBottom ( contentElement ) ;
62+
4163 lastScrollTopRef . current = currentScrollTop ;
4264 } ;
4365
4466 contentElement . addEventListener ( 'scroll' , handleScroll ) ;
67+ lastScrollTopRef . current = contentElement . scrollTop ;
4568 return ( ) => contentElement . removeEventListener ( 'scroll' , handleScroll ) ;
4669 } , [ ] ) ;
4770
4871 useEffect ( ( ) => {
49- if ( contentRef . current && isScrolledToBottomRef . current && ! userScrolledUpRef . current ) {
72+ if ( ! isProcessing ) return ;
73+
74+ if ( autoScrollEnabledRef . current && contentRef . current ) {
5075 requestAnimationFrame ( ( ) => {
51- if ( contentRef . current ) {
52- contentRef . current . scrollTop = contentRef . current . scrollHeight ;
53- }
76+ scrollToBottom ( 'auto' ) ;
5477 } ) ;
5578 }
56- } , [ messages ] ) ;
79+ } , [ messages , isProcessing ] ) ;
80+
81+ const enableAutoScroll = ( ) => {
82+ setAutoScroll ( true ) ;
83+
84+ requestAnimationFrame ( ( ) => {
85+ scrollToBottom ( 'auto' ) ;
86+ } ) ;
87+ } ;
5788
58- return contentRef ;
89+ return { contentRef, autoScrollEnabled , enableAutoScroll } ;
5990} ;
6091
6192const useExpandableItems = ( ) => {
@@ -133,7 +164,7 @@ export const AcpDialog: React.FC<AcpDialogProps> = ({
133164 const { expanded : expandedToolResults , toggle : toggleToolResult } = useExpandableItems ( ) ;
134165 const { expanded : expandedThoughts , toggle : toggleThought } = useExpandableItems ( ) ;
135166 const { expanded : expandedPermissions , toggle : togglePermission } = useExpandableItems ( ) ;
136- const contentRef = useAutoScroll ( messages ) ;
167+ const { contentRef, autoScrollEnabled , enableAutoScroll } = useAutoScroll ( messages , isProcessing ) ;
137168
138169 if ( ! isOpen ) return null ;
139170
@@ -203,23 +234,35 @@ export const AcpDialog: React.FC<AcpDialogProps> = ({
203234 </ div >
204235 </ div >
205236
206- < div className = "acp-dialog-content" ref = { contentRef } >
207- < div className = "acp-dialog-messages" >
208- < AcpMessages
209- messages = { messages }
210- toolCalls = { toolCalls }
211- expandedToolCalls = { expandedToolCalls }
212- expandedToolResults = { expandedToolResults }
213- expandedThoughts = { expandedThoughts }
214- expandedPermissions = { expandedPermissions }
215- onToggleToolCall = { toggleToolCall }
216- onToggleToolResult = { toggleToolResult }
217- onToggleThought = { toggleThought }
218- onTogglePermission = { togglePermission }
219- onPermissionResponse = { ( permissionId , optionId ) => onPermissionResponse ( agentId , permissionId , optionId ) }
220- onUndoMessage = { ( message ) => onUndoPrompt ( agentId , message . checkpoint_id , message . content ) }
221- />
237+ < div className = "acp-dialog-content" >
238+ < div className = "acp-dialog-messages" ref = { contentRef } >
239+ < div className = "acp-dialog-messages-inner" >
240+ < AcpMessages
241+ messages = { messages }
242+ toolCalls = { toolCalls }
243+ expandedToolCalls = { expandedToolCalls }
244+ expandedToolResults = { expandedToolResults }
245+ expandedThoughts = { expandedThoughts }
246+ expandedPermissions = { expandedPermissions }
247+ onToggleToolCall = { toggleToolCall }
248+ onToggleToolResult = { toggleToolResult }
249+ onToggleThought = { toggleThought }
250+ onTogglePermission = { togglePermission }
251+ onPermissionResponse = { ( permissionId , optionId ) => onPermissionResponse ( agentId , permissionId , optionId ) }
252+ onUndoMessage = { ( message ) => onUndoPrompt ( agentId , message . checkpoint_id , message . content ) }
253+ />
254+ </ div >
222255 </ div >
256+ { ! autoScrollEnabled && (
257+ < button
258+ className = "acp-scroll-to-bottom-btn"
259+ onClick = { enableAutoScroll }
260+ title = "Enable auto-scroll"
261+ aria-label = "Enable auto-scroll"
262+ >
263+ < AcpIcons . ScrollDown />
264+ </ button >
265+ ) }
223266 </ div >
224267
225268 < AcpInput
0 commit comments