fix(chat): keep sent messages in view

This commit is contained in:
2026-07-15 19:14:36 +08:00
parent 47876484fc
commit cf239a51de
8 changed files with 122 additions and 1 deletions
+31 -1
View File
@@ -47,6 +47,7 @@ const ReplyingAnimation = lazy(() =>
export interface ChatAreaProps {
messages: readonly UiMessage[];
isReplyingAI: boolean;
scrollToBottomSignal?: number;
initialScrollReady?: boolean;
canLoadMoreHistory?: boolean;
isLoadingMoreHistory?: boolean;
@@ -62,6 +63,7 @@ export interface ChatAreaProps {
export function ChatArea({
messages,
isReplyingAI,
scrollToBottomSignal = 0,
initialScrollReady = true,
canLoadMoreHistory = false,
isLoadingMoreHistory = false,
@@ -77,6 +79,7 @@ export function ChatArea({
const contentRef = useRef<HTMLDivElement>(null);
const initialScrollSettledRef = useRef(false);
const shouldStickToBottomRef = useRef(true);
const previousScrollToBottomSignalRef = useRef(scrollToBottomSignal);
const loadMoreAnchorRef = useRef<{
scrollHeight: number;
scrollTop: number;
@@ -115,12 +118,33 @@ export function ChatArea({
useLayoutEffect(() => {
if (!initialScrollReady) {
initialScrollSettledRef.current = false;
previousScrollToBottomSignalRef.current = scrollToBottomSignal;
return;
}
const scrollNode = scrollRef.current;
if (!scrollNode) return;
const shouldForceScrollToBottom =
previousScrollToBottomSignalRef.current !== scrollToBottomSignal;
previousScrollToBottomSignalRef.current = scrollToBottomSignal;
if (shouldForceScrollToBottom) {
loadMoreAnchorRef.current = null;
initialScrollSettledRef.current = true;
shouldStickToBottomRef.current = true;
scrollToBottom(scrollNode);
const frameId = requestAnimationFrame(() => {
if (
scrollRef.current === scrollNode &&
shouldStickToBottomRef.current
) {
scrollToBottom(scrollNode);
}
});
return () => cancelAnimationFrame(frameId);
}
if (loadMoreAnchorRef.current && !isLoadingMoreHistory) {
const anchor = loadMoreAnchorRef.current;
loadMoreAnchorRef.current = null;
@@ -149,7 +173,13 @@ export function ChatArea({
});
return () => cancelAnimationFrame(frameId);
}, [initialScrollReady, isLoadingMoreHistory, isReplyingAI, messages]);
}, [
initialScrollReady,
isLoadingMoreHistory,
isReplyingAI,
messages,
scrollToBottomSignal,
]);
useEffect(() => {
const scrollNode = scrollRef.current;