Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions frontend/app/aipanel/aipanelmessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,59 @@ export const AIPanelMessages = memo(({ messages, status, onContextMenu }: AIPane
const messagesEndRef = useRef<HTMLDivElement>(null);
const messagesContainerRef = useRef<HTMLDivElement>(null);
const prevStatusRef = useRef<string>(status);
const userHasScrolledUp = useRef<boolean>(false);
const isAutoScrolling = useRef<boolean>(false);

const scrollToBottom = () => {
const container = messagesContainerRef.current;
if (container) {
isAutoScrolling.current = true;
container.scrollTop = container.scrollHeight;
container.scrollLeft = 0;
userHasScrolledUp.current = false;
setTimeout(() => {
isAutoScrolling.current = false;
}, 100);
}
};

// Detect if user has manually scrolled up
useEffect(() => {
const container = messagesContainerRef.current;
if (!container) return;

const handleScroll = () => {
// Ignore scroll events triggered by our auto-scroll
if (isAutoScrolling.current) return;
Comment on lines +44 to +46

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Scroll lock ignored while streaming updates

During streaming, scrollToBottom marks isAutoScrolling true for 100ms on every new message; because this handler exits early when that flag is set, user scroll events occurring while tokens stream faster than that interval are never recorded as userHasScrolledUp. In practice the panel keeps auto-scrolling to the bottom even if the user scrolls up mid-stream, defeating the regression fix whenever messages arrive more often than 100ms.

Useful? React with 👍 / 👎.


const { scrollTop, scrollHeight, clientHeight } = container;
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;

// If user is more than 50px from the bottom, they've scrolled up
if (distanceFromBottom > 50) {
userHasScrolledUp.current = true;
} else {
userHasScrolledUp.current = false;
}
};

container.addEventListener("scroll", handleScroll);
return () => container.removeEventListener("scroll", handleScroll);
}, []);

useEffect(() => {
model.registerScrollToBottom(scrollToBottom);
}, [model]);

useEffect(() => {
scrollToBottom();
// Only auto-scroll if user hasn't manually scrolled up
if (!userHasScrolledUp.current) {
scrollToBottom();
}
}, [messages]);

useEffect(() => {
if (isPanelOpen) {
if (isPanelOpen && !userHasScrolledUp.current) {
scrollToBottom();
}
}, [isPanelOpen]);
Expand All @@ -47,7 +81,7 @@ export const AIPanelMessages = memo(({ messages, status, onContextMenu }: AIPane
const wasStreaming = prevStatusRef.current === "streaming";
const isNowNotStreaming = status !== "streaming";

if (wasStreaming && isNowNotStreaming) {
if (wasStreaming && isNowNotStreaming && !userHasScrolledUp.current) {
requestAnimationFrame(() => {
scrollToBottom();
});
Expand Down