84 lines
2.4 KiB
TypeScript
84 lines
2.4 KiB
TypeScript
// 负责聊天区域滚动控制的通用方法,解耦 App.vue 中的 DOM 操作
|
|
|
|
type ScrollContext = {
|
|
getMessagesAreaElement?: () => HTMLElement | null;
|
|
getThinkingContentElement?: (blockId: string) => HTMLElement | null;
|
|
chatToggleScrollLockState?: () => boolean;
|
|
thinkingScrollLocks?: Map<string, boolean>;
|
|
_setScrollingFlag?: (value: boolean) => void;
|
|
autoScrollEnabled?: boolean;
|
|
userScrolling?: boolean;
|
|
isOutputActive?: () => boolean;
|
|
};
|
|
|
|
export function scrollToBottom(ctx: ScrollContext) {
|
|
const messagesArea = ctx.getMessagesAreaElement?.();
|
|
if (!messagesArea) {
|
|
return;
|
|
}
|
|
|
|
const attempts = [0, 16, 60, 150, 320, 520]; // 多次尝试覆盖布局抖动/异步伸缩
|
|
let cancelled = false;
|
|
|
|
const perform = (idx: number) => {
|
|
if (cancelled) return;
|
|
if (ctx.userScrolling) {
|
|
cancelled = true;
|
|
if (typeof ctx._setScrollingFlag === 'function') {
|
|
ctx._setScrollingFlag(false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (idx === 0 && typeof ctx._setScrollingFlag === 'function') {
|
|
ctx._setScrollingFlag(true);
|
|
}
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
perform(0);
|
|
}
|
|
|
|
export function conditionalScrollToBottom(ctx: ScrollContext) {
|
|
const active = typeof ctx.isOutputActive === 'function' ? ctx.isOutputActive() : true;
|
|
if (ctx.autoScrollEnabled === true && ctx.userScrolling === false && active) {
|
|
scrollToBottom(ctx);
|
|
}
|
|
}
|
|
|
|
export function toggleScrollLock(ctx: ScrollContext) {
|
|
const active = typeof ctx.isOutputActive === 'function' ? ctx.isOutputActive() : true;
|
|
|
|
// 没有模型输出时:允许点击,但不切换锁定,仅单次滚动到底部
|
|
if (!active) {
|
|
scrollToBottom(ctx);
|
|
return ctx.autoScrollEnabled ?? false;
|
|
}
|
|
|
|
const nextState = ctx.chatToggleScrollLockState?.() ?? false;
|
|
if (nextState) {
|
|
scrollToBottom(ctx);
|
|
}
|
|
return nextState;
|
|
}
|
|
|
|
export function scrollThinkingToBottom(ctx: ScrollContext, blockId: string) {
|
|
if (!blockId) {
|
|
return;
|
|
}
|
|
if (!ctx.thinkingScrollLocks?.get(blockId)) {
|
|
return;
|
|
}
|
|
const el = ctx.getThinkingContentElement?.(blockId);
|
|
if (el) {
|
|
el.scrollTop = el.scrollHeight;
|
|
}
|
|
}
|