fix(滚动控制): 优化滚动逻辑并修复流式消息时的滚动问题

This commit is contained in:
Wenjie Zhang 2025-08-27 11:52:49 +08:00
parent 04b0f92c2e
commit e51d0dccdd
4 changed files with 83 additions and 17 deletions

View File

@ -34,7 +34,7 @@
</div>
</div>
<div class="header__center" @mouseenter="uiState.showRenameButton = true" @mouseleave="uiState.showRenameButton = false">
<div @click="console.log(agentStore.currentThread)" class="center-title">
<div @click="logConversationInfo" class="center-title">
{{ agentStore.currentThread?.title }}
</div>
<div class="rename-button" v-if="currentChatId" :class="{ 'visible': uiState.showRenameButton }" @click="handleRenameChat">
@ -234,6 +234,8 @@ const selectChat = async (chatId) => {
if (!AgentValidator.validateAgentIdWithError(agentStore.selectedAgentId, '选择对话', handleValidationError)) return;
agentStore.selectThread(chatId);
await agentStore.fetchThreadMessages(chatId);
await nextTick();
scrollController.scrollToBottomStaticForce();
};
const deleteChat = async (chatId) => {
@ -262,7 +264,6 @@ const handleSendMessage = async () => {
userInput.value = '';
// Enable auto scroll before sending message to ensure proper scrolling during streaming
scrollController.enableAutoScroll();
await nextTick();
await scrollController.scrollToBottom(true);
@ -328,6 +329,27 @@ const toggleSidebar = () => {
const openAgentModal = () => emit('open-agent-modal');
// ==================== CONVERSATION INFO LOGGING ====================
const logConversationInfo = () => {
console.log(agentStore.currentThread);
//
console.group('📜 对话历史消息');
console.log('原始消息数组:', agentStore.currentThreadMessages);
console.log('消息总数:', agentStore.currentThreadMessages.length);
console.groupEnd();
//
if (agentStore.isStreaming || agentStore.onGoingConvMessages.length > 0) {
console.log('进行中的消息:', agentStore.onGoingConvMessages);
console.log('消息块:', agentStore.onGoingConv.msgChunks);
console.groupEnd();
}
console.groupEnd();
};
// ==================== HELPER FUNCTIONS ====================
const getLastMessage = (conv) => {
@ -375,14 +397,19 @@ const loadChatsList = async () => {
onMounted(async () => {
await initAll();
scrollController.enableAutoScroll();
watch(() => agentStore.selectedAgentId, (newAgentId, oldAgentId) => {
if (newAgentId && newAgentId !== oldAgentId) {
initAll();
}
});
// streaming
watch(conversations, () => {
scrollController.scrollToBottom();
}, { deep: true });
if (isProcessing.value) {
scrollController.scrollToBottom();
}
}, { deep: true, flush: 'post' });
});
</script>

View File

@ -546,6 +546,19 @@ const toggleToolCall = (toolCallId) => {
color: var(--main-700);
}
.md-editor-code {
border: var(--gray-50);
border-radius: 8px;
.md-editor-code-head {
background-color: var(--gray-50);
.md-editor-collapse-tips {
color: var(--gray-400);
}
}
}
code {
font-size: 13px;
font-family: 'Menlo', 'Monaco', 'Consolas', 'PingFang SC', 'Noto Sans SC', 'Microsoft YaHei', 'Hiragino Sans GB', 'Source Han Sans CN', 'Courier New', monospace;
@ -553,7 +566,7 @@ const toggleToolCall = (toolCallId) => {
letter-spacing: 0.025em;
tab-size: 4;
-moz-tab-size: 4;
background-color: var(--gray-100);
background-color: var(--gray-25);
}
p:last-child {

View File

@ -241,7 +241,7 @@ watch(inputValue, () => {
// Wait for component to mount before setting up onStartTyping
onMounted(() => {
console.log('Component mounted');
// console.log('Component mounted');
checkOptionsLeft();
nextTick(() => {
if (inputRef.value) {

View File

@ -8,14 +8,15 @@ export class ScrollController {
this.containerSelector = containerSelector;
this.options = {
threshold: 100,
scrollDelay: 150,
retryDelays: [50, 150, 300],
scrollDelay: 100,
retryDelays: [50, 150],
...options
};
this.scrollTimer = null;
this.isUserScrolling = false;
this.shouldAutoScroll = true;
this.isProgrammaticScroll = false;
// Bind the context of 'this' for the event handler
this.handleScroll = this.handleScroll.bind(this);
@ -49,12 +50,17 @@ export class ScrollController {
clearTimeout(this.scrollTimer);
}
// 如果是程序性滚动,忽略此次事件
if (this.isProgrammaticScroll) {
this.isProgrammaticScroll = false;
return;
}
// 标记用户正在滚动
this.isUserScrolling = true;
// 检查是否在底部
const atBottom = this.isAtBottom();
this.shouldAutoScroll = atBottom;
this.shouldAutoScroll = this.isAtBottom();
// 滚动结束后一段时间重置用户滚动状态
this.scrollTimer = setTimeout(() => {
@ -75,9 +81,12 @@ export class ScrollController {
const container = this.getContainer();
if (!container) return;
const scrollOptions = {
top: container.scrollHeight,
behavior: 'smooth'
// 标记为程序性滚动
this.isProgrammaticScroll = true;
const scrollOptions = {
top: container.scrollHeight,
behavior: 'smooth'
};
// 立即滚动
@ -87,16 +96,32 @@ export class ScrollController {
this.options.retryDelays.forEach((delay, index) => {
setTimeout(() => {
if (force || this.shouldAutoScroll) {
this.isProgrammaticScroll = true;
const behavior = index === this.options.retryDelays.length - 1 ? 'auto' : 'smooth';
container.scrollTo({
top: container.scrollHeight,
behavior
container.scrollTo({
top: container.scrollHeight,
behavior
});
}
}, delay);
});
}
async scrollToBottomStaticForce() {
const container = this.getContainer();
if (!container) return;
// 标记为程序性滚动
this.isProgrammaticScroll = true;
const scrollOptions = {
top: container.scrollHeight,
behavior: 'auto'
};
container.scrollTo(scrollOptions);
}
/**
* 启用自动滚动
*/
@ -139,6 +164,7 @@ export class ScrollController {
this.cleanup();
this.isUserScrolling = false;
this.shouldAutoScroll = true;
this.isProgrammaticScroll = false;
}
}