From d34fbe963a3b3b6025aebb75fe99f8d00234aad6 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Fri, 2 Jan 2026 12:39:23 +0800 Subject: [PATCH] fix: unify collapse animation and scroll lock reset --- static/src/app.ts | 5 +++- static/src/components/chat/ChatArea.vue | 26 ++++++++++++++++--- .../components/chat/actions/ToolAction.vue | 7 ++++- static/src/composables/useScrollControl.ts | 17 ++++++++++++ .../styles/components/chat/_chat-area.scss | 11 ++++---- 5 files changed, 56 insertions(+), 10 deletions(-) diff --git a/static/src/app.ts b/static/src/app.ts index a2c01fb..432b720 100644 --- a/static/src/app.ts +++ b/static/src/app.ts @@ -57,6 +57,7 @@ import { scrollToBottom as scrollToBottomHelper, conditionalScrollToBottom as conditionalScrollToBottomHelper, toggleScrollLock as toggleScrollLockHelper, + normalizeScrollLock, scrollThinkingToBottom as scrollThinkingToBottomHelper } from './composables/useScrollControl'; import { @@ -294,7 +295,7 @@ const appOptions = { }); }, - async mounted() { + async mounted() { debugLog('Vue应用已挂载'); if (window.ensureCsrfToken) { window.ensureCsrfToken().catch((err) => { @@ -308,6 +309,8 @@ const appOptions = { await socketPromise; this.$nextTick(() => { this.ensureScrollListener(); + // 刷新后若无输出,自动解锁滚动锁定 + normalizeScrollLock(this); }); setupShowImageObserver(); diff --git a/static/src/components/chat/ChatArea.vue b/static/src/components/chat/ChatArea.vue index 43b60a6..82fb6c4 100644 --- a/static/src/components/chat/ChatArea.vue +++ b/static/src/components/chat/ChatArea.vue @@ -79,7 +79,10 @@ {{ group.action.streaming ? '正在思考...' : '思考过程' }} -
+
{{ action.streaming ? '正在思考...' : '思考过程' }}
-
+
系统消息
-
+
{{ msg.content }}
@@ -359,6 +368,17 @@ const stackedBlocksEnabled = computed(() => personalization.experiments.stackedB const DEFAULT_GENERATING_TEXT = '生成中…'; const rootEl = ref(null); const thinkingRefs = new Map(); +const registerCollapseContent = (key: string, el: Element | null) => { + if (!(el instanceof HTMLElement)) { + return; + } + requestAnimationFrame(() => { + const h = el.scrollHeight || el.offsetHeight || 0; + if (h > 0) { + el.style.setProperty('--collapse-max', `${h}px`); + } + }); +}; function registerThinkingRef(key: string, el: Element | null) { if (el instanceof HTMLElement) { diff --git a/static/src/components/chat/actions/ToolAction.vue b/static/src/components/chat/actions/ToolAction.vue index a93518f..3d8c574 100644 --- a/static/src/components/chat/actions/ToolAction.vue +++ b/static/src/components/chat/actions/ToolAction.vue @@ -20,7 +20,10 @@ {{ getToolStatusText(action.tool) }} {{ getToolDescription(action.tool) }}
-
+
@@ -75,6 +78,8 @@ defineProps<{ formatSearchTopic: (filters: Record) => string; formatSearchTime: (filters: Record) => string; streamingMessage: boolean; + registerCollapseContent?: (key: string, el: Element | null) => void; + collapseKey?: string; }>(); defineEmits<{ (event: 'toggle'): void }>(); diff --git a/static/src/composables/useScrollControl.ts b/static/src/composables/useScrollControl.ts index 8820d5e..2791c1d 100644 --- a/static/src/composables/useScrollControl.ts +++ b/static/src/composables/useScrollControl.ts @@ -10,6 +10,8 @@ type ScrollContext = { autoScrollEnabled?: boolean; userScrolling?: boolean; isOutputActive?: () => boolean; + streamingMessage?: boolean; + hasPendingToolActions?: () => boolean; }; type ScrollOptions = { @@ -106,6 +108,21 @@ export function toggleScrollLock(ctx: ScrollContext) { return nextState; } +/** + * 在页面初始化/刷新后同步滚动锁定的默认状态: + * 仅当存在输出(流式中或有未完成工具)时才保持锁定,否则自动解锁。 + */ +export function normalizeScrollLock(ctx: ScrollContext) { + const active = + (typeof ctx.isOutputActive === 'function' ? ctx.isOutputActive() : false) || + !!ctx.streamingMessage || + (typeof ctx.hasPendingToolActions === 'function' ? ctx.hasPendingToolActions() : false); + + if (!active && ctx.autoScrollEnabled) { + ctx.chatSetScrollState?.({ autoScrollEnabled: false, userScrolling: false }); + } +} + export function scrollThinkingToBottom(ctx: ScrollContext, blockId: string) { if (!blockId) { return; diff --git a/static/src/styles/components/chat/_chat-area.scss b/static/src/styles/components/chat/_chat-area.scss index b5ae702..4d1ed23 100644 --- a/static/src/styles/components/chat/_chat-area.scss +++ b/static/src/styles/components/chat/_chat-area.scss @@ -266,11 +266,12 @@ overflow: hidden; opacity: 0; transition: - max-height 0.26s cubic-bezier(0.4, 0, 0.2, 1), - opacity 0.26s cubic-bezier(0.4, 0, 0.2, 1); + max-height 260ms cubic-bezier(0.4, 0, 0.2, 1), + opacity 220ms cubic-bezier(0.4, 0, 0.2, 1); /* 始终隐藏滚动条,避免堆叠模式展开时闪现 */ scrollbar-width: none; -ms-overflow-style: none; + will-change: max-height, opacity; } .collapsible-content::-webkit-scrollbar { @@ -278,7 +279,7 @@ } .collapsible-block.expanded .collapsible-content { - max-height: 600px; + max-height: var(--collapse-max, 600px); overflow-y: auto; opacity: 1; } @@ -439,8 +440,8 @@ .stacked-block .collapsible-content { transition: - max-height 280ms cubic-bezier(0.25, 0.9, 0.3, 1), - opacity 220ms ease; + max-height 260ms cubic-bezier(0.4, 0, 0.2, 1), + opacity 220ms cubic-bezier(0.4, 0, 0.2, 1); } .progress-indicator {