Skip to content

Commit e0f7770

Browse files
stevenvoclaude
andcommitted
Prevent AI panel auto-scroll when user scrolls up to read
When the AI is streaming responses, the panel would continuously auto-scroll to the bottom, making it impossible to read earlier parts of the response. This change detects when the user has manually scrolled up (more than 50px from bottom) and stops auto-scrolling until they scroll back near the bottom. - Add scroll event listener to detect manual user scrolling - Track userHasScrolledUp state - Only auto-scroll if user is at/near bottom - Improves readability of long AI responses 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent fbb0c4d commit e0f7770

File tree

1 file changed

+37
-3
lines changed

1 file changed

+37
-3
lines changed

frontend/app/aipanel/aipanelmessages.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,59 @@ export const AIPanelMessages = memo(({ messages, status, onContextMenu }: AIPane
2020
const messagesEndRef = useRef<HTMLDivElement>(null);
2121
const messagesContainerRef = useRef<HTMLDivElement>(null);
2222
const prevStatusRef = useRef<string>(status);
23+
const userHasScrolledUp = useRef<boolean>(false);
24+
const isAutoScrolling = useRef<boolean>(false);
2325

2426
const scrollToBottom = () => {
2527
const container = messagesContainerRef.current;
2628
if (container) {
29+
isAutoScrolling.current = true;
2730
container.scrollTop = container.scrollHeight;
2831
container.scrollLeft = 0;
32+
userHasScrolledUp.current = false;
33+
setTimeout(() => {
34+
isAutoScrolling.current = false;
35+
}, 100);
2936
}
3037
};
3138

39+
// Detect if user has manually scrolled up
40+
useEffect(() => {
41+
const container = messagesContainerRef.current;
42+
if (!container) return;
43+
44+
const handleScroll = () => {
45+
// Ignore scroll events triggered by our auto-scroll
46+
if (isAutoScrolling.current) return;
47+
48+
const { scrollTop, scrollHeight, clientHeight } = container;
49+
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
50+
51+
// If user is more than 50px from the bottom, they've scrolled up
52+
if (distanceFromBottom > 50) {
53+
userHasScrolledUp.current = true;
54+
} else {
55+
userHasScrolledUp.current = false;
56+
}
57+
};
58+
59+
container.addEventListener("scroll", handleScroll);
60+
return () => container.removeEventListener("scroll", handleScroll);
61+
}, []);
62+
3263
useEffect(() => {
3364
model.registerScrollToBottom(scrollToBottom);
3465
}, [model]);
3566

3667
useEffect(() => {
37-
scrollToBottom();
68+
// Only auto-scroll if user hasn't manually scrolled up
69+
if (!userHasScrolledUp.current) {
70+
scrollToBottom();
71+
}
3872
}, [messages]);
3973

4074
useEffect(() => {
41-
if (isPanelOpen) {
75+
if (isPanelOpen && !userHasScrolledUp.current) {
4276
scrollToBottom();
4377
}
4478
}, [isPanelOpen]);
@@ -47,7 +81,7 @@ export const AIPanelMessages = memo(({ messages, status, onContextMenu }: AIPane
4781
const wasStreaming = prevStatusRef.current === "streaming";
4882
const isNowNotStreaming = status !== "streaming";
4983

50-
if (wasStreaming && isNowNotStreaming) {
84+
if (wasStreaming && isNowNotStreaming && !userHasScrolledUp.current) {
5185
requestAnimationFrame(() => {
5286
scrollToBottom();
5387
});

0 commit comments

Comments
 (0)