feat: auto-scroll thinking block with per-block lock

This commit is contained in:
JOJO 2025-11-18 22:27:56 +08:00
parent e7a81f00e7
commit 05b404687a
4 changed files with 61 additions and 4 deletions

View File

@ -202,6 +202,8 @@ async function bootstrapApp() {
// 设置菜单状态 // 设置菜单状态
settingsOpen: false, settingsOpen: false,
// 思考块滚动锁
thinkingScrollLocks: new Map(),
// 工具控制菜单 // 工具控制菜单
toolMenuOpen: false, toolMenuOpen: false,
@ -732,6 +734,7 @@ async function bootstrapApp() {
this.autoScrollEnabled = true; this.autoScrollEnabled = true;
this.userScrolling = false; this.userScrolling = false;
this.scrollToBottom(); this.scrollToBottom();
this.thinkingScrollLocks.set(blockId, true);
this.$forceUpdate(); this.$forceUpdate();
} }
}); });
@ -747,8 +750,12 @@ async function bootstrapApp() {
lastAction.content += data.content; lastAction.content += data.content;
} }
this.$forceUpdate(); this.$forceUpdate();
if (lastAction && lastAction.blockId) {
this.$nextTick(() => this.scrollThinkingToBottom(lastAction.blockId));
} else {
this.conditionalScrollToBottom(); this.conditionalScrollToBottom();
} }
}
}); });
// 思考结束 // 思考结束
@ -765,6 +772,7 @@ async function bootstrapApp() {
this.expandedBlocks.delete(lastAction.blockId); this.expandedBlocks.delete(lastAction.blockId);
this.$forceUpdate(); this.$forceUpdate();
}, 1000); }, 1000);
this.$nextTick(() => this.scrollThinkingToBottom(lastAction.blockId));
} }
} }
msg.streamingThinking = ''; msg.streamingThinking = '';
@ -1295,6 +1303,7 @@ async function bootstrapApp() {
if (this.markdownCache) { if (this.markdownCache) {
this.markdownCache.clear(); this.markdownCache.clear();
} }
this.thinkingScrollLocks.clear();
// 强制更新视图 // 强制更新视图
this.$forceUpdate(); this.$forceUpdate();
@ -3001,6 +3010,23 @@ async function bootstrapApp() {
} }
}, },
scrollThinkingToBottom(blockId) {
if (!this.thinkingScrollLocks.get(blockId)) return;
const refName = `thinkingContent-${blockId}`;
const elRef = this.$refs[refName];
const el = Array.isArray(elRef) ? elRef[0] : elRef;
if (el) {
el.scrollTop = el.scrollHeight;
}
},
handleThinkingScroll(blockId, event) {
const el = event.target;
const threshold = 12;
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
this.thinkingScrollLocks.set(blockId, atBottom);
},
// 面板调整方法 // 面板调整方法
startResize(panel, event) { startResize(panel, event) {
this.isResizing = true; this.isResizing = true;

View File

@ -298,7 +298,10 @@
<span class="status-text">{{ action.streaming ? '正在思考...' : '思考过程' }}</span> <span class="status-text">{{ action.streaming ? '正在思考...' : '思考过程' }}</span>
</div> </div>
<div class="collapsible-content"> <div class="collapsible-content">
<div class="content-inner thinking-content"> <div class="content-inner thinking-content"
:ref="`thinkingContent-${index}-thinking-${actionIndex}`"
@scroll="handleThinkingScroll(`${index}-thinking-${actionIndex}`, $event)"
style="max-height: 240px; overflow-y: auto;">
{{ action.content }} {{ action.content }}
</div> </div>
</div> </div>

View File

@ -239,6 +239,7 @@ async function bootstrapApp() {
// 设置菜单状态 // 设置菜单状态
settingsOpen: false, settingsOpen: false,
thinkingScrollLocks: new Map(),
// 工具控制菜单 // 工具控制菜单
toolMenuOpen: false, toolMenuOpen: false,
@ -1089,6 +1090,7 @@ async function bootstrapApp() {
this.autoScrollEnabled = true; this.autoScrollEnabled = true;
this.userScrolling = false; this.userScrolling = false;
this.scrollToBottom(); this.scrollToBottom();
this.thinkingScrollLocks.set(blockId, true);
this.$forceUpdate(); this.$forceUpdate();
} }
}); });
@ -1104,8 +1106,12 @@ async function bootstrapApp() {
lastAction.content += data.content; lastAction.content += data.content;
} }
this.$forceUpdate(); this.$forceUpdate();
if (lastAction && lastAction.blockId) {
this.$nextTick(() => this.scrollThinkingToBottom(lastAction.blockId));
} else {
this.conditionalScrollToBottom(); this.conditionalScrollToBottom();
} }
}
}); });
// 思考结束 // 思考结束
@ -1122,6 +1128,7 @@ async function bootstrapApp() {
this.expandedBlocks.delete(lastAction.blockId); this.expandedBlocks.delete(lastAction.blockId);
this.$forceUpdate(); this.$forceUpdate();
}, 1000); }, 1000);
this.$nextTick(() => this.scrollThinkingToBottom(lastAction.blockId));
} }
} }
msg.streamingThinking = ''; msg.streamingThinking = '';
@ -1645,6 +1652,7 @@ async function bootstrapApp() {
if (this.markdownCache) { if (this.markdownCache) {
this.markdownCache.clear(); this.markdownCache.clear();
} }
this.thinkingScrollLocks.clear();
// 强制更新视图 // 强制更新视图
this.$forceUpdate(); this.$forceUpdate();
@ -3204,6 +3212,23 @@ async function bootstrapApp() {
} }
}, },
scrollThinkingToBottom(blockId) {
if (!this.thinkingScrollLocks.get(blockId)) return;
const refName = `thinkingContent-${blockId}`;
const elRef = this.$refs[refName];
const el = Array.isArray(elRef) ? elRef[0] : elRef;
if (el) {
el.scrollTop = el.scrollHeight;
}
},
handleThinkingScroll(blockId, event) {
const el = event.target;
const threshold = 12;
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
this.thinkingScrollLocks.set(blockId, atBottom);
},
// 面板调整方法 // 面板调整方法
startResize(panel, event) { startResize(panel, event) {
this.isResizing = true; this.isResizing = true;

View File

@ -270,7 +270,10 @@
<span class="status-text">{{ action.streaming ? '正在思考...' : '思考过程' }}</span> <span class="status-text">{{ action.streaming ? '正在思考...' : '思考过程' }}</span>
</div> </div>
<div class="collapsible-content"> <div class="collapsible-content">
<div class="content-inner thinking-content"> <div class="content-inner thinking-content"
:ref="`thinkingContent-${index}-thinking-${actionIndex}`"
@scroll="handleThinkingScroll(`${index}-thinking-${actionIndex}`, $event)"
style="max-height: 240px; overflow-y: auto;">
{{ action.content }} {{ action.content }}
</div> </div>
</div> </div>