fix: smooth scroll when unlocking

This commit is contained in:
JOJO 2026-01-01 20:17:21 +08:00
parent b42cb3b032
commit 9ae43e89ff

View File

@ -4,6 +4,7 @@ type ScrollContext = {
getMessagesAreaElement?: () => HTMLElement | null;
getThinkingContentElement?: (blockId: string) => HTMLElement | null;
chatToggleScrollLockState?: () => boolean;
chatSetScrollState?: (payload: { autoScrollEnabled?: boolean; userScrolling?: boolean }) => void;
thinkingScrollLocks?: Map<string, boolean>;
_setScrollingFlag?: (value: boolean) => void;
autoScrollEnabled?: boolean;
@ -11,7 +12,20 @@ type ScrollContext = {
isOutputActive?: () => boolean;
};
export function scrollToBottom(ctx: ScrollContext) {
type ScrollOptions = {
ignoreUserScrolling?: boolean;
/**
* When true, force userScrolling=false after the programmatic scroll so that
* later自动滚动不会被
*/
resetUserScrolling?: boolean;
/**
* Control scroll behavior; 'smooth'
*/
behavior?: ScrollBehavior;
};
export function scrollToBottom(ctx: ScrollContext, options?: ScrollOptions) {
const messagesArea = ctx.getMessagesAreaElement?.();
if (!messagesArea) {
return;
@ -19,10 +33,13 @@ export function scrollToBottom(ctx: ScrollContext) {
const attempts = [0, 16, 60, 150, 320, 520]; // 多次尝试覆盖布局抖动/异步伸缩
let cancelled = false;
const useSmooth =
options?.behavior === 'smooth' &&
typeof (messagesArea as HTMLElement).scrollTo === 'function';
const perform = (idx: number) => {
if (cancelled) return;
if (ctx.userScrolling) {
if (ctx.userScrolling && !options?.ignoreUserScrolling) {
cancelled = true;
if (typeof ctx._setScrollingFlag === 'function') {
ctx._setScrollingFlag(false);
@ -34,12 +51,32 @@ export function scrollToBottom(ctx: ScrollContext) {
ctx._setScrollingFlag(true);
}
messagesArea.scrollTop = messagesArea.scrollHeight;
if (useSmooth) {
(messagesArea as HTMLElement).scrollTo({
top: messagesArea.scrollHeight,
behavior: 'smooth'
});
} else {
messagesArea.scrollTop = messagesArea.scrollHeight;
}
if (idx < attempts.length - 1) {
setTimeout(() => perform(idx + 1), attempts[idx + 1] - attempts[idx]);
} else if (typeof ctx._setScrollingFlag === 'function') {
setTimeout(() => ctx._setScrollingFlag && ctx._setScrollingFlag(false), 40);
if (options?.resetUserScrolling) {
if (typeof ctx.chatSetScrollState === 'function') {
ctx.chatSetScrollState({ userScrolling: false });
} else if ('userScrolling' in ctx) {
ctx.userScrolling = false;
}
}
} else if (options?.resetUserScrolling) {
if (typeof ctx.chatSetScrollState === 'function') {
ctx.chatSetScrollState({ userScrolling: false });
} else if ('userScrolling' in ctx) {
ctx.userScrolling = false;
}
}
};
@ -58,7 +95,7 @@ export function toggleScrollLock(ctx: ScrollContext) {
// 没有模型输出时:允许点击,但不切换锁定,仅单次滚动到底部
if (!active) {
scrollToBottom(ctx);
scrollToBottom(ctx, { ignoreUserScrolling: true, resetUserScrolling: true, behavior: 'smooth' });
return ctx.autoScrollEnabled ?? false;
}