mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-01-23 09:57:32 +00:00
Merge pull request #288 from siteboon/fix/move-to-correct-scroll-position-in-long-messages-chat
fix: normalize file path handling and improve scroll position restoration
This commit is contained in:
@@ -177,7 +177,9 @@ function AppContent() {
|
|||||||
// If so, and the session is not active, trigger a message reload in ChatInterface
|
// If so, and the session is not active, trigger a message reload in ChatInterface
|
||||||
if (latestMessage.changedFile && selectedSession && selectedProject) {
|
if (latestMessage.changedFile && selectedSession && selectedProject) {
|
||||||
// Extract session ID from changedFile (format: "project-name/session-id.jsonl")
|
// Extract session ID from changedFile (format: "project-name/session-id.jsonl")
|
||||||
const changedFileParts = latestMessage.changedFile.split('/');
|
const normalized = latestMessage.changedFile.replace(/\\/g, '/');
|
||||||
|
const changedFileParts = normalized.split('/');
|
||||||
|
|
||||||
if (changedFileParts.length >= 2) {
|
if (changedFileParts.length >= 2) {
|
||||||
const filename = changedFileParts[changedFileParts.length - 1];
|
const filename = changedFileParts[changedFileParts.length - 1];
|
||||||
const changedSessionId = filename.replace('.jsonl', '');
|
const changedSessionId = filename.replace('.jsonl', '');
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* This ensures uninterrupted chat experience by coordinating with App.jsx to pause sidebar updates.
|
* This ensures uninterrupted chat experience by coordinating with App.jsx to pause sidebar updates.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef, useMemo, useCallback, memo } from 'react';
|
import React, { useState, useEffect, useRef, useMemo, useCallback, useLayoutEffect, memo } from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import remarkMath from 'remark-math';
|
import remarkMath from 'remark-math';
|
||||||
@@ -1696,6 +1696,9 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
|||||||
const inputContainerRef = useRef(null);
|
const inputContainerRef = useRef(null);
|
||||||
const scrollContainerRef = useRef(null);
|
const scrollContainerRef = useRef(null);
|
||||||
const isLoadingSessionRef = useRef(false); // Track session loading to prevent multiple scrolls
|
const isLoadingSessionRef = useRef(false); // Track session loading to prevent multiple scrolls
|
||||||
|
const isLoadingMoreRef = useRef(false);
|
||||||
|
const topLoadLockRef = useRef(false);
|
||||||
|
const pendingScrollRestoreRef = useRef(null);
|
||||||
// Streaming throttle buffers
|
// Streaming throttle buffers
|
||||||
const streamBufferRef = useRef('');
|
const streamBufferRef = useRef('');
|
||||||
const streamTimerRef = useRef(null);
|
const streamTimerRef = useRef(null);
|
||||||
@@ -2710,6 +2713,39 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
|||||||
return scrollHeight - scrollTop - clientHeight < 50;
|
return scrollHeight - scrollTop - clientHeight < 50;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const loadOlderMessages = useCallback(async (container) => {
|
||||||
|
if (!container || isLoadingMoreRef.current || isLoadingMoreMessages) return false;
|
||||||
|
if (!hasMoreMessages || !selectedSession || !selectedProject) return false;
|
||||||
|
|
||||||
|
const sessionProvider = selectedSession.__provider || 'claude';
|
||||||
|
if (sessionProvider === 'cursor') return false;
|
||||||
|
|
||||||
|
isLoadingMoreRef.current = true;
|
||||||
|
const previousScrollHeight = container.scrollHeight;
|
||||||
|
const previousScrollTop = container.scrollTop;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const moreMessages = await loadSessionMessages(
|
||||||
|
selectedProject.name,
|
||||||
|
selectedSession.id,
|
||||||
|
true,
|
||||||
|
sessionProvider
|
||||||
|
);
|
||||||
|
|
||||||
|
if (moreMessages.length > 0) {
|
||||||
|
pendingScrollRestoreRef.current = {
|
||||||
|
height: previousScrollHeight,
|
||||||
|
top: previousScrollTop
|
||||||
|
};
|
||||||
|
// Prepend new messages to the existing ones
|
||||||
|
setSessionMessages(prev => [...moreMessages, ...prev]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
isLoadingMoreRef.current = false;
|
||||||
|
}
|
||||||
|
}, [hasMoreMessages, isLoadingMoreMessages, selectedSession, selectedProject, loadSessionMessages]);
|
||||||
|
|
||||||
// Handle scroll events to detect when user manually scrolls up and load more messages
|
// Handle scroll events to detect when user manually scrolls up and load more messages
|
||||||
const handleScroll = useCallback(async () => {
|
const handleScroll = useCallback(async () => {
|
||||||
if (scrollContainerRef.current) {
|
if (scrollContainerRef.current) {
|
||||||
@@ -2719,32 +2755,29 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
|||||||
|
|
||||||
// Check if we should load more messages (scrolled near top)
|
// Check if we should load more messages (scrolled near top)
|
||||||
const scrolledNearTop = container.scrollTop < 100;
|
const scrolledNearTop = container.scrollTop < 100;
|
||||||
const provider = localStorage.getItem('selected-provider') || 'claude';
|
if (!scrolledNearTop) {
|
||||||
|
topLoadLockRef.current = false;
|
||||||
if (scrolledNearTop && hasMoreMessages && !isLoadingMoreMessages && selectedSession && selectedProject && provider !== 'cursor') {
|
} else if (!topLoadLockRef.current) {
|
||||||
// Save current scroll position
|
const didLoad = await loadOlderMessages(container);
|
||||||
const previousScrollHeight = container.scrollHeight;
|
if (didLoad) {
|
||||||
const previousScrollTop = container.scrollTop;
|
topLoadLockRef.current = true;
|
||||||
|
|
||||||
// Load more messages
|
|
||||||
const moreMessages = await loadSessionMessages(selectedProject.name, selectedSession.id, true, selectedSession.__provider || 'claude');
|
|
||||||
|
|
||||||
if (moreMessages.length > 0) {
|
|
||||||
// Prepend new messages to the existing ones
|
|
||||||
setSessionMessages(prev => [...moreMessages, ...prev]);
|
|
||||||
|
|
||||||
// Restore scroll position after DOM update
|
|
||||||
setTimeout(() => {
|
|
||||||
if (scrollContainerRef.current) {
|
|
||||||
const newScrollHeight = scrollContainerRef.current.scrollHeight;
|
|
||||||
const scrollDiff = newScrollHeight - previousScrollHeight;
|
|
||||||
scrollContainerRef.current.scrollTop = previousScrollTop + scrollDiff;
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isNearBottom, hasMoreMessages, isLoadingMoreMessages, selectedSession, selectedProject, loadSessionMessages]);
|
}, [isNearBottom, loadOlderMessages]);
|
||||||
|
|
||||||
|
// Restore scroll position after paginated messages render
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!pendingScrollRestoreRef.current || !scrollContainerRef.current) return;
|
||||||
|
|
||||||
|
const { height, top } = pendingScrollRestoreRef.current;
|
||||||
|
const container = scrollContainerRef.current;
|
||||||
|
const newScrollHeight = container.scrollHeight;
|
||||||
|
const scrollDiff = newScrollHeight - height;
|
||||||
|
|
||||||
|
container.scrollTop = top + Math.max(scrollDiff, 0);
|
||||||
|
pendingScrollRestoreRef.current = null;
|
||||||
|
}, [chatMessages.length]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Load session messages when session changes
|
// Load session messages when session changes
|
||||||
@@ -2873,7 +2906,8 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
|||||||
// convertedMessages will be automatically updated via useMemo
|
// convertedMessages will be automatically updated via useMemo
|
||||||
|
|
||||||
// Smart scroll behavior: only auto-scroll if user is near bottom
|
// Smart scroll behavior: only auto-scroll if user is near bottom
|
||||||
if (isNearBottom && autoScrollToBottom) {
|
const shouldAutoScroll = autoScrollToBottom && isNearBottom();
|
||||||
|
if (shouldAutoScroll) {
|
||||||
setTimeout(() => scrollToBottom(), 200);
|
setTimeout(() => scrollToBottom(), 200);
|
||||||
}
|
}
|
||||||
// If user scrolled up, preserve their position (they're reading history)
|
// If user scrolled up, preserve their position (they're reading history)
|
||||||
@@ -4418,6 +4452,8 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
|||||||
{/* Messages Area - Scrollable Middle Section */}
|
{/* Messages Area - Scrollable Middle Section */}
|
||||||
<div
|
<div
|
||||||
ref={scrollContainerRef}
|
ref={scrollContainerRef}
|
||||||
|
onWheel={handleScroll}
|
||||||
|
onTouchMove={handleScroll}
|
||||||
className="flex-1 overflow-y-auto overflow-x-hidden px-0 py-3 sm:p-4 space-y-3 sm:space-y-4 relative"
|
className="flex-1 overflow-y-auto overflow-x-hidden px-0 py-3 sm:p-4 space-y-3 sm:space-y-4 relative"
|
||||||
>
|
>
|
||||||
{isLoadingSessionMessages && chatMessages.length === 0 ? (
|
{isLoadingSessionMessages && chatMessages.length === 0 ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user