chore: snapshot before collapse fix

This commit is contained in:
JOJO 2026-01-01 14:23:39 +08:00
parent 2a67e10e9b
commit eeb3db084b
15 changed files with 39 additions and 80 deletions

Binary file not shown.

View File

@ -1,18 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 8V4H8" />
<rect width="16" height="12" x="4" y="8" rx="2" />
<path d="M2 14h2" />
<path d="M20 14h2" />
<path d="M15 13v2" />
<path d="M9 13v2" />
</svg>

Before

Width:  |  Height:  |  Size: 380 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:1;"><path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"/><path d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"/><path d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"/></svg>

Before

Width:  |  Height:  |  Size: 491 B

View File

@ -1,15 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M4 5h16" />
<path d="M4 12h16" />
<path d="M4 19h16" />
</svg>

Before

Width:  |  Height:  |  Size: 279 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:1;"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11zm7.318-19.539l-10.94 10.939"/></svg>

Before

Width:  |  Height:  |  Size: 372 B

View File

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M6 7c0-2 1.6-3.6 3.6-3.6h13c2 0 3.6 1.6 3.6 3.6v10c0 2-1.6 3.6-3.6 3.6h-6.2L12.4 25l0.7-4.4H9.6c-2 0-3.6-1.6-3.6-3.6V7Z" />
<path d="M10.4 11h11.2" />
<path d="M10.4 15h7.2" />
</svg>

Before

Width:  |  Height:  |  Size: 360 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:1;"><path d="m21 21l-4.34-4.34"/><circle cx="11" cy="11" r="8"/></svg>

Before

Width:  |  Height:  |  Size: 261 B

View File

@ -1,14 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
<path d="m15 5 4 4" />
</svg>

Before

Width:  |  Height:  |  Size: 377 B

View File

@ -1,14 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>

Before

Width:  |  Height:  |  Size: 299 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:1;"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3m.08 4h.01"/></svg>

Before

Width:  |  Height:  |  Size: 291 B

View File

@ -135,11 +135,11 @@
<div
v-if="chatDisplayMode === 'chat'"
class="scroll-lock-toggle"
:class="{ locked: autoScrollEnabled && !userScrolling }"
:class="{ locked: autoScrollEnabled && !userScrolling && isOutputActive() }"
>
<button @click="toggleScrollLock" class="scroll-lock-btn">
<svg
v-if="autoScrollEnabled && !userScrolling"
v-if="autoScrollEnabled && !userScrolling && isOutputActive()"
viewBox="0 0 24 24"
aria-hidden="true"
stroke-linecap="round"

View File

@ -709,6 +709,9 @@ const appOptions = {
}
return elRef || null;
},
isOutputActive() {
return !!(this.streamingMessage || this.taskInProgress || this.hasPendingToolActions());
},
logMessageState(action, extra = {}) {
const count = Array.isArray(this.messages) ? this.messages.length : 'N/A';
debugLog('[Messages]', {
@ -999,11 +1002,26 @@ const appOptions = {
const clientHeight = messagesArea.clientHeight;
const isAtBottom = scrollHeight - scrollTop - clientHeight < bottomThreshold;
if (isAtBottom) {
this.chatSetScrollState({ userScrolling: false, autoScrollEnabled: true });
} else {
this.chatSetScrollState({ userScrolling: true, autoScrollEnabled: false });
const activeLock = this.autoScrollEnabled && this.isOutputActive();
// 锁定且当前有输出时,强制贴底
if (activeLock) {
if (!isAtBottom) {
if (typeof this._setScrollingFlag === 'function') {
this._setScrollingFlag(true);
}
messagesArea.scrollTop = messagesArea.scrollHeight;
if (typeof this._setScrollingFlag === 'function') {
setTimeout(() => this._setScrollingFlag && this._setScrollingFlag(false), 16);
}
}
// 保持锁定状态下 userScrolling 为 false
this.chatSetScrollState({ userScrolling: false });
return;
}
// 未锁定或无输出:允许自由滚动,仅记录位置
this.chatSetScrollState({ userScrolling: !isAtBottom });
});
},

View File

@ -8,6 +8,7 @@ type ScrollContext = {
_setScrollingFlag?: (value: boolean) => void;
autoScrollEnabled?: boolean;
userScrolling?: boolean;
isOutputActive?: () => boolean;
};
export function scrollToBottom(ctx: ScrollContext) {
@ -46,14 +47,16 @@ export function scrollToBottom(ctx: ScrollContext) {
}
export function conditionalScrollToBottom(ctx: ScrollContext) {
if (ctx.autoScrollEnabled === true && ctx.userScrolling === false) {
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 nextState = ctx.chatToggleScrollLockState?.() ?? false;
if (nextState) {
const active = typeof ctx.isOutputActive === 'function' ? ctx.isOutputActive() : true;
if (nextState && active) {
scrollToBottom(ctx);
}
return nextState;

View File

@ -144,10 +144,11 @@ export const useChatStore = defineStore('chat', {
this.setScrollState({ autoScrollEnabled: true, userScrolling: false });
},
disableAutoScroll() {
this.setScrollState({ autoScrollEnabled: false, userScrolling: true });
this.setScrollState({ autoScrollEnabled: false, userScrolling: false });
},
toggleScrollLockState() {
if (this.isScrollLocked) {
const locked = this.isScrollLocked;
if (locked) {
this.disableAutoScroll();
return false;
}

View File

@ -268,6 +268,13 @@
transition:
max-height 0.26s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.26s cubic-bezier(0.4, 0, 0.2, 1);
/* 始终隐藏滚动条,避免堆叠模式展开时闪现 */
scrollbar-width: none;
-ms-overflow-style: none;
}
.collapsible-content::-webkit-scrollbar {
display: none;
}
.collapsible-block.expanded .collapsible-content {