fix: smooth scroll when unlocking
This commit is contained in:
parent
b42cb3b032
commit
9ae43e89ff
@ -4,6 +4,7 @@ type ScrollContext = {
|
|||||||
getMessagesAreaElement?: () => HTMLElement | null;
|
getMessagesAreaElement?: () => HTMLElement | null;
|
||||||
getThinkingContentElement?: (blockId: string) => HTMLElement | null;
|
getThinkingContentElement?: (blockId: string) => HTMLElement | null;
|
||||||
chatToggleScrollLockState?: () => boolean;
|
chatToggleScrollLockState?: () => boolean;
|
||||||
|
chatSetScrollState?: (payload: { autoScrollEnabled?: boolean; userScrolling?: boolean }) => void;
|
||||||
thinkingScrollLocks?: Map<string, boolean>;
|
thinkingScrollLocks?: Map<string, boolean>;
|
||||||
_setScrollingFlag?: (value: boolean) => void;
|
_setScrollingFlag?: (value: boolean) => void;
|
||||||
autoScrollEnabled?: boolean;
|
autoScrollEnabled?: boolean;
|
||||||
@ -11,7 +12,20 @@ type ScrollContext = {
|
|||||||
isOutputActive?: () => boolean;
|
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?.();
|
const messagesArea = ctx.getMessagesAreaElement?.();
|
||||||
if (!messagesArea) {
|
if (!messagesArea) {
|
||||||
return;
|
return;
|
||||||
@ -19,10 +33,13 @@ export function scrollToBottom(ctx: ScrollContext) {
|
|||||||
|
|
||||||
const attempts = [0, 16, 60, 150, 320, 520]; // 多次尝试覆盖布局抖动/异步伸缩
|
const attempts = [0, 16, 60, 150, 320, 520]; // 多次尝试覆盖布局抖动/异步伸缩
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
const useSmooth =
|
||||||
|
options?.behavior === 'smooth' &&
|
||||||
|
typeof (messagesArea as HTMLElement).scrollTo === 'function';
|
||||||
|
|
||||||
const perform = (idx: number) => {
|
const perform = (idx: number) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
if (ctx.userScrolling) {
|
if (ctx.userScrolling && !options?.ignoreUserScrolling) {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
if (typeof ctx._setScrollingFlag === 'function') {
|
if (typeof ctx._setScrollingFlag === 'function') {
|
||||||
ctx._setScrollingFlag(false);
|
ctx._setScrollingFlag(false);
|
||||||
@ -34,12 +51,32 @@ export function scrollToBottom(ctx: ScrollContext) {
|
|||||||
ctx._setScrollingFlag(true);
|
ctx._setScrollingFlag(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (useSmooth) {
|
||||||
|
(messagesArea as HTMLElement).scrollTo({
|
||||||
|
top: messagesArea.scrollHeight,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
messagesArea.scrollTop = messagesArea.scrollHeight;
|
messagesArea.scrollTop = messagesArea.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
if (idx < attempts.length - 1) {
|
if (idx < attempts.length - 1) {
|
||||||
setTimeout(() => perform(idx + 1), attempts[idx + 1] - attempts[idx]);
|
setTimeout(() => perform(idx + 1), attempts[idx + 1] - attempts[idx]);
|
||||||
} else if (typeof ctx._setScrollingFlag === 'function') {
|
} else if (typeof ctx._setScrollingFlag === 'function') {
|
||||||
setTimeout(() => ctx._setScrollingFlag && ctx._setScrollingFlag(false), 40);
|
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) {
|
if (!active) {
|
||||||
scrollToBottom(ctx);
|
scrollToBottom(ctx, { ignoreUserScrolling: true, resetUserScrolling: true, behavior: 'smooth' });
|
||||||
return ctx.autoScrollEnabled ?? false;
|
return ctx.autoScrollEnabled ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user