diff --git a/src/components/AiChat.vue b/src/components/AiChat.vue index ba4d2ab..91030f2 100644 --- a/src/components/AiChat.vue +++ b/src/components/AiChat.vue @@ -1743,6 +1743,10 @@ function isNearBottom(): boolean { // 回到底部按钮:内容溢出且不在底部时显示 const showBackToBottom = ref(false) +// UX-260616-03: 跟随阅读位置自动收起 —— 用户滚近底部(在读最新内容)时, +// 收起不再活跃的旧卡(视为已读)。用 wasNearBottom 边沿检测避免每个 scroll tick 都触发: +// 仅在"远离底部 → 滚近底部"的转换瞬间收起一次,已在底部继续滚动不重复触发。 +let wasNearBottom = true function refreshBackToBottom() { const el = messagesContainer.value if (!el) { showBackToBottom.value = false; return } @@ -1751,6 +1755,12 @@ function refreshBackToBottom() { } function onMessagesScroll() { refreshBackToBottom() + // UX-260616-03: 滚近底部 → 收起已读旧卡(边沿触发,防抖) + const near = isNearBottom() + if (near && !wasNearBottom) { + collapseAllToolLists(buildActiveToolIds(store.state.messages)) + } + wasNearBottom = near } function scrollToBottom(smooth = false) { @@ -1789,7 +1799,15 @@ watch(mdReady, (ready) => { }) watch(() => store.state.streaming, (s) => { if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null } - if (!s) { + if (s) { + // UX-260616-03: 新一轮生成开始 → 收起上一轮已完成的旧工具卡,聚焦当前生成内容。 + // 仅当前视图正在生成时触发(generatingConvId 对齐 activeConversationId), + // 切走后台生成/恢复会话(streaming 初值已 true 的 onMounted 路径)不误触。 + // buildActiveToolIds/collapseAllToolLists 是 function 声明,提升可用。 + if (store.state.generatingConvId === store.state.activeConversationId) { + collapseAllToolLists(buildActiveToolIds(store.state.messages)) + } + } else { // UX-2025-01: 流式→完成切换,模板从 v-for 分块切到 v-html 整段,DOM 重建 saveSelection() streamingBlocks.value = [] diff --git a/src/components/ToolCard.vue b/src/components/ToolCard.vue index f05df9c..0b152ef 100644 --- a/src/components/ToolCard.vue +++ b/src/components/ToolCard.vue @@ -1,7 +1,10 @@