fix(滚动控制): 优化滚动逻辑并修复流式消息时的滚动问题
This commit is contained in:
parent
04b0f92c2e
commit
e51d0dccdd
@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header__center" @mouseenter="uiState.showRenameButton = true" @mouseleave="uiState.showRenameButton = false">
|
<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 }}
|
{{ agentStore.currentThread?.title }}
|
||||||
</div>
|
</div>
|
||||||
<div class="rename-button" v-if="currentChatId" :class="{ 'visible': uiState.showRenameButton }" @click="handleRenameChat">
|
<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;
|
if (!AgentValidator.validateAgentIdWithError(agentStore.selectedAgentId, '选择对话', handleValidationError)) return;
|
||||||
agentStore.selectThread(chatId);
|
agentStore.selectThread(chatId);
|
||||||
await agentStore.fetchThreadMessages(chatId);
|
await agentStore.fetchThreadMessages(chatId);
|
||||||
|
await nextTick();
|
||||||
|
scrollController.scrollToBottomStaticForce();
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteChat = async (chatId) => {
|
const deleteChat = async (chatId) => {
|
||||||
@ -262,7 +264,6 @@ const handleSendMessage = async () => {
|
|||||||
|
|
||||||
userInput.value = '';
|
userInput.value = '';
|
||||||
// Enable auto scroll before sending message to ensure proper scrolling during streaming
|
// Enable auto scroll before sending message to ensure proper scrolling during streaming
|
||||||
scrollController.enableAutoScroll();
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
await scrollController.scrollToBottom(true);
|
await scrollController.scrollToBottom(true);
|
||||||
|
|
||||||
@ -328,6 +329,27 @@ const toggleSidebar = () => {
|
|||||||
|
|
||||||
const openAgentModal = () => emit('open-agent-modal');
|
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 ====================
|
// ==================== HELPER FUNCTIONS ====================
|
||||||
|
|
||||||
const getLastMessage = (conv) => {
|
const getLastMessage = (conv) => {
|
||||||
@ -375,14 +397,19 @@ const loadChatsList = async () => {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await initAll();
|
await initAll();
|
||||||
|
scrollController.enableAutoScroll();
|
||||||
watch(() => agentStore.selectedAgentId, (newAgentId, oldAgentId) => {
|
watch(() => agentStore.selectedAgentId, (newAgentId, oldAgentId) => {
|
||||||
if (newAgentId && newAgentId !== oldAgentId) {
|
if (newAgentId && newAgentId !== oldAgentId) {
|
||||||
initAll();
|
initAll();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 只监听streaming状态,用于流式消息的智能滚动
|
||||||
watch(conversations, () => {
|
watch(conversations, () => {
|
||||||
|
if (isProcessing.value) {
|
||||||
scrollController.scrollToBottom();
|
scrollController.scrollToBottom();
|
||||||
}, { deep: true });
|
}
|
||||||
|
}, { deep: true, flush: 'post' });
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -546,6 +546,19 @@ const toggleToolCall = (toolCallId) => {
|
|||||||
color: var(--main-700);
|
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 {
|
code {
|
||||||
font-size: 13px;
|
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;
|
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;
|
letter-spacing: 0.025em;
|
||||||
tab-size: 4;
|
tab-size: 4;
|
||||||
-moz-tab-size: 4;
|
-moz-tab-size: 4;
|
||||||
background-color: var(--gray-100);
|
background-color: var(--gray-25);
|
||||||
}
|
}
|
||||||
|
|
||||||
p:last-child {
|
p:last-child {
|
||||||
|
|||||||
@ -241,7 +241,7 @@ watch(inputValue, () => {
|
|||||||
|
|
||||||
// Wait for component to mount before setting up onStartTyping
|
// Wait for component to mount before setting up onStartTyping
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('Component mounted');
|
// console.log('Component mounted');
|
||||||
checkOptionsLeft();
|
checkOptionsLeft();
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (inputRef.value) {
|
if (inputRef.value) {
|
||||||
|
|||||||
@ -8,14 +8,15 @@ export class ScrollController {
|
|||||||
this.containerSelector = containerSelector;
|
this.containerSelector = containerSelector;
|
||||||
this.options = {
|
this.options = {
|
||||||
threshold: 100,
|
threshold: 100,
|
||||||
scrollDelay: 150,
|
scrollDelay: 100,
|
||||||
retryDelays: [50, 150, 300],
|
retryDelays: [50, 150],
|
||||||
...options
|
...options
|
||||||
};
|
};
|
||||||
|
|
||||||
this.scrollTimer = null;
|
this.scrollTimer = null;
|
||||||
this.isUserScrolling = false;
|
this.isUserScrolling = false;
|
||||||
this.shouldAutoScroll = true;
|
this.shouldAutoScroll = true;
|
||||||
|
this.isProgrammaticScroll = false;
|
||||||
|
|
||||||
// Bind the context of 'this' for the event handler
|
// Bind the context of 'this' for the event handler
|
||||||
this.handleScroll = this.handleScroll.bind(this);
|
this.handleScroll = this.handleScroll.bind(this);
|
||||||
@ -49,12 +50,17 @@ export class ScrollController {
|
|||||||
clearTimeout(this.scrollTimer);
|
clearTimeout(this.scrollTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是程序性滚动,忽略此次事件
|
||||||
|
if (this.isProgrammaticScroll) {
|
||||||
|
this.isProgrammaticScroll = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 标记用户正在滚动
|
// 标记用户正在滚动
|
||||||
this.isUserScrolling = true;
|
this.isUserScrolling = true;
|
||||||
|
|
||||||
// 检查是否在底部
|
// 检查是否在底部
|
||||||
const atBottom = this.isAtBottom();
|
this.shouldAutoScroll = this.isAtBottom();
|
||||||
this.shouldAutoScroll = atBottom;
|
|
||||||
|
|
||||||
// 滚动结束后一段时间重置用户滚动状态
|
// 滚动结束后一段时间重置用户滚动状态
|
||||||
this.scrollTimer = setTimeout(() => {
|
this.scrollTimer = setTimeout(() => {
|
||||||
@ -75,6 +81,9 @@ export class ScrollController {
|
|||||||
const container = this.getContainer();
|
const container = this.getContainer();
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
|
// 标记为程序性滚动
|
||||||
|
this.isProgrammaticScroll = true;
|
||||||
|
|
||||||
const scrollOptions = {
|
const scrollOptions = {
|
||||||
top: container.scrollHeight,
|
top: container.scrollHeight,
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
@ -87,6 +96,7 @@ export class ScrollController {
|
|||||||
this.options.retryDelays.forEach((delay, index) => {
|
this.options.retryDelays.forEach((delay, index) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (force || this.shouldAutoScroll) {
|
if (force || this.shouldAutoScroll) {
|
||||||
|
this.isProgrammaticScroll = true;
|
||||||
const behavior = index === this.options.retryDelays.length - 1 ? 'auto' : 'smooth';
|
const behavior = index === this.options.retryDelays.length - 1 ? 'auto' : 'smooth';
|
||||||
container.scrollTo({
|
container.scrollTo({
|
||||||
top: container.scrollHeight,
|
top: container.scrollHeight,
|
||||||
@ -97,6 +107,21 @@ export class ScrollController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.cleanup();
|
||||||
this.isUserScrolling = false;
|
this.isUserScrolling = false;
|
||||||
this.shouldAutoScroll = true;
|
this.shouldAutoScroll = true;
|
||||||
|
this.isProgrammaticScroll = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user