agent-Specialization/static/src/composables/useScrollControl.ts

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;
}
}