1.优化AI聊天页面效果
This commit is contained in:
@@ -168,6 +168,13 @@ export default {
|
|||||||
// 组件销毁标志
|
// 组件销毁标志
|
||||||
isDestroyed: false,
|
isDestroyed: false,
|
||||||
|
|
||||||
|
// 性能优化相关
|
||||||
|
scrollThrottleTimer: null, // 滚动节流定时器
|
||||||
|
contentUpdateTimer: null, // 内容更新节流定时器
|
||||||
|
isUserAtBottom: true, // 用户是否在底部
|
||||||
|
lastContentLength: 0, // 上次内容长度
|
||||||
|
scrollPending: false, // 滚动待处理标志
|
||||||
|
|
||||||
// 性能优化和错误边界
|
// 性能优化和错误边界
|
||||||
requestQueue: [],
|
requestQueue: [],
|
||||||
maxRetries: 2,
|
maxRetries: 2,
|
||||||
@@ -283,6 +290,14 @@ export default {
|
|||||||
clearTimeout(this.loadDebounceTimer)
|
clearTimeout(this.loadDebounceTimer)
|
||||||
this.loadDebounceTimer = null
|
this.loadDebounceTimer = null
|
||||||
}
|
}
|
||||||
|
if (this.scrollThrottleTimer) {
|
||||||
|
clearTimeout(this.scrollThrottleTimer)
|
||||||
|
this.scrollThrottleTimer = null
|
||||||
|
}
|
||||||
|
if (this.contentUpdateTimer) {
|
||||||
|
clearTimeout(this.contentUpdateTimer)
|
||||||
|
this.contentUpdateTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
// 取消正在进行的请求
|
// 取消正在进行的请求
|
||||||
if (this.currentCancel) {
|
if (this.currentCancel) {
|
||||||
@@ -809,8 +824,8 @@ export default {
|
|||||||
if (data.event === 'message' && data.answer) {
|
if (data.event === 'message' && data.answer) {
|
||||||
const currentContent = aiMsg.content
|
const currentContent = aiMsg.content
|
||||||
const newContent = (currentContent === '正在思考...' ? '' : currentContent) + data.answer
|
const newContent = (currentContent === '正在思考...' ? '' : currentContent) + data.answer
|
||||||
aiMsg.content = newContent
|
// 使用节流更新方法,减少频繁的DOM更新和滚动
|
||||||
this.scrollToBottom(true) // 流式响应时使用平滑滚动
|
this.throttledContentUpdate(aiMsg, newContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理结束消息
|
// 处理结束消息
|
||||||
@@ -825,12 +840,19 @@ export default {
|
|||||||
userMsg.messageId = data.message_id
|
userMsg.messageId = data.message_id
|
||||||
aiMsg.messageId = 'ai-' + data.message_id
|
aiMsg.messageId = 'ai-' + data.message_id
|
||||||
}
|
}
|
||||||
if (data.retriever_resources) {
|
if (data.metadata && data.metadata.retriever_resources) {
|
||||||
aiMsg.retrieverResources = data.retriever_resources
|
// 使用Vue的响应式方法确保引用信息正确更新
|
||||||
aiMsg.groupedReferences = this.getGroupedReferences(data.retriever_resources) // 预计算分组引用
|
this.$set(aiMsg, 'retrieverResources', data.metadata.retriever_resources)
|
||||||
|
this.$set(aiMsg, 'groupedReferences', this.getGroupedReferences(data.metadata.retriever_resources))
|
||||||
}
|
}
|
||||||
// 标记流输出完成
|
// 标记流输出完成并触发最终滚动
|
||||||
aiMsg.streamCompleted = true
|
aiMsg.streamCompleted = true
|
||||||
|
// 重置内容长度计数器
|
||||||
|
this.lastContentLength = 0
|
||||||
|
// 流式响应结束后强制滚动到底部
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom(true, true) // 使用平滑滚动并强制执行
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理错误事件
|
// 处理错误事件
|
||||||
@@ -933,45 +955,48 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 滚动到底部
|
* 滚动到底部(优化版本)
|
||||||
*/
|
*/
|
||||||
scrollToBottom(smooth = false) {
|
scrollToBottom(smooth = false, force = false) {
|
||||||
this.$nextTick(() => {
|
// 如果用户不在底部且不是强制滚动,则跳过
|
||||||
if (this.$refs.messageList) {
|
if (!force && !this.isUserAtBottom) {
|
||||||
const targetTop = this.$refs.messageList.scrollHeight
|
return
|
||||||
const messageList = this.$refs.messageList
|
}
|
||||||
|
|
||||||
if (smooth && messageList.scrollTo && !this.isLoadingHistory) {
|
// 节流处理,避免频繁滚动
|
||||||
// 只在非加载状态时使用平滑滚动
|
if (this.scrollThrottleTimer) {
|
||||||
messageList.scrollTo({
|
clearTimeout(this.scrollThrottleTimer)
|
||||||
top: messageList.scrollHeight,
|
}
|
||||||
behavior: 'smooth'
|
|
||||||
})
|
this.scrollThrottleTimer = setTimeout(() => {
|
||||||
} else {
|
this.$nextTick(() => {
|
||||||
// 强制立即滚动到底部
|
if (this.$refs.messageList && !this.isDestroyed) {
|
||||||
const forceScrollToBottom = () => {
|
const messageList = this.$refs.messageList
|
||||||
messageList.scrollTop = messageList.scrollHeight
|
const scrollHeight = messageList.scrollHeight
|
||||||
|
|
||||||
|
if (smooth && messageList.scrollTo && !this.isLoadingHistory) {
|
||||||
|
// 只在非加载状态时使用平滑滚动
|
||||||
|
messageList.scrollTo({
|
||||||
|
top: scrollHeight,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 立即滚动到底部
|
||||||
|
messageList.scrollTop = scrollHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
// 立即执行一次
|
// 更新用户位置状态
|
||||||
forceScrollToBottom()
|
this.isUserAtBottom = true
|
||||||
|
|
||||||
// 再次确保滚动到底部
|
// 延迟更新lastScrollTop
|
||||||
this.$nextTick(() => {
|
setTimeout(() => {
|
||||||
forceScrollToBottom()
|
if (!this.isDestroyed) {
|
||||||
// 第三次确保
|
this.lastScrollTop = scrollHeight
|
||||||
setTimeout(() => {
|
}
|
||||||
forceScrollToBottom()
|
}, smooth ? 300 : 50)
|
||||||
}, 10)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
// 延迟更新lastScrollTop,避免干扰滚动检测
|
}, smooth ? 0 : 16) // 非平滑滚动时使用16ms节流(约60fps)
|
||||||
setTimeout(() => {
|
|
||||||
this.lastScrollTop = this.$refs.messageList.scrollHeight
|
|
||||||
}, smooth ? 300 : 50) // 非平滑滚动时更快更新
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -979,6 +1004,11 @@ export default {
|
|||||||
*/
|
*/
|
||||||
onScroll(e) {
|
onScroll(e) {
|
||||||
const scrollTop = e.target.scrollTop
|
const scrollTop = e.target.scrollTop
|
||||||
|
const scrollHeight = e.target.scrollHeight
|
||||||
|
const clientHeight = e.target.clientHeight
|
||||||
|
|
||||||
|
// 检测用户是否在底部(允许10px的误差)
|
||||||
|
this.isUserAtBottom = (scrollTop + clientHeight >= scrollHeight - 10)
|
||||||
|
|
||||||
// 检查是否需要加载历史记录
|
// 检查是否需要加载历史记录
|
||||||
// 当滚动到距离顶部阈值范围内且向上滚动时触发加载
|
// 当滚动到距离顶部阈值范围内且向上滚动时触发加载
|
||||||
@@ -1036,6 +1066,37 @@ export default {
|
|||||||
return sanitizedHtml
|
return sanitizedHtml
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节流更新内容
|
||||||
|
*/
|
||||||
|
throttledContentUpdate(aiMsg, newContent) {
|
||||||
|
// 检查内容是否真的有变化
|
||||||
|
if (aiMsg.content === newContent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 立即更新内容(保证响应性)
|
||||||
|
aiMsg.content = newContent
|
||||||
|
|
||||||
|
// 只有当内容长度显著增加时才滚动
|
||||||
|
const contentLengthDiff = newContent.length - this.lastContentLength
|
||||||
|
if (contentLengthDiff > 20) { // 内容增加超过20个字符才滚动
|
||||||
|
this.lastContentLength = newContent.length
|
||||||
|
|
||||||
|
// 清除之前的定时器
|
||||||
|
if (this.contentUpdateTimer) {
|
||||||
|
clearTimeout(this.contentUpdateTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节流滚动更新
|
||||||
|
this.contentUpdateTimer = setTimeout(() => {
|
||||||
|
if (this.isUserAtBottom && !this.isDestroyed) {
|
||||||
|
this.scrollToBottom(false)
|
||||||
|
}
|
||||||
|
}, 100) // 100ms节流,减少滚动频率
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理点赞
|
* 处理点赞
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user