From 35abfea922ab52dbb6ebe9b69a833088281e79a6 Mon Sep 17 00:00:00 2001 From: zoujing Date: Tue, 9 Sep 2025 23:13:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=20=E5=81=9C=E6=AD=A2=E7=9A=84=E6=97=B6?= =?UTF-8?q?=E5=80=99=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/chat/ChatMainList.vue | 44 +++++++++++++++++------------- utils/TypewriterManager.js | 53 +++++++++++++++++++++++++------------ 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/pages/chat/ChatMainList.vue b/pages/chat/ChatMainList.vue index f77f1fa..dc0c017 100644 --- a/pages/chat/ChatMainList.vue +++ b/pages/chat/ChatMainList.vue @@ -256,8 +256,9 @@ const handleScrollToLower = () => {}; // 滚动到底部 - 优化版本,确保打字机效果始终可见 const scrollToBottom = () => { nextTick(() => { + // 使用更大的值确保滚动到真正的底部 scrollTop.value = 99999; - // 强制触发滚动更新 + // 强制触发滚动更新,增加延迟确保DOM更新完成 setTimeout(() => { scrollTop.value = scrollTop.value + Math.random(); }, 10); @@ -538,7 +539,6 @@ const initTypewriterManager = () => { typewriterManager.setCallbacks({ // 每个字符打字时的回调 onCharacterTyped: (displayedContent) => { - // 只有在用户没有滚动时才自动滚动到底部 scrollToBottom(); }, // 内容更新时的回调 @@ -671,30 +671,38 @@ const stopRequest = () => { // 发送中断消息给服务器 (messageType=2) sendWebSocketMessage(2, "stop_request", { silent: true }); - // 停止打字机效果 + // 停止打字机效果并保留当前内容 if (typewriterManager) { - typewriterManager.stopTypewriter(); - } + // 获取当前已显示的内容 + const currentStatus = typewriterManager.getStatus(); + const currentDisplayedContent = currentStatus.displayedContent; - // 重置会话状态和消息状态 - isSessionActive.value = false; - resetMessageState(); + // 使用新的方法停止并保留当前内容 + typewriterManager.stopAndKeepCurrent(); - // 更新最后一条AI消息的状态 - const aiMsgIndex = chatMsgList.value.length - 1; - if ( - chatMsgList.value[aiMsgIndex] && - chatMsgList.value[aiMsgIndex].msgType === MessageRole.AI - ) { - chatMsgList.value[aiMsgIndex].isLoading = false; + // 更新最后一条AI消息的状态 + const aiMsgIndex = chatMsgList.value.length - 1; if ( - !chatMsgList.value[aiMsgIndex].msg || - chatMsgList.value[aiMsgIndex].msg.startsWith("加载中") + chatMsgList.value[aiMsgIndex] && + chatMsgList.value[aiMsgIndex].msgType === MessageRole.AI ) { - chatMsgList.value[aiMsgIndex].msg = "请求已停止"; + chatMsgList.value[aiMsgIndex].isLoading = false; + // 如果有已显示的内容,使用已显示的内容,否则显示停止消息 + if ( + currentDisplayedContent && + currentDisplayedContent.trim() && + !currentDisplayedContent.startsWith("加载中") + ) { + chatMsgList.value[aiMsgIndex].msg = currentDisplayedContent; + } else { + chatMsgList.value[aiMsgIndex].msg = "请求已停止"; + } } } + // 重置会话状态(但不重置消息状态,保留已显示内容) + isSessionActive.value = false; + console.log("请求已停止,状态已重置"); setTimeoutScrollToBottom(); diff --git a/utils/TypewriterManager.js b/utils/TypewriterManager.js index 894c9dc..b183a50 100644 --- a/utils/TypewriterManager.js +++ b/utils/TypewriterManager.js @@ -7,8 +7,8 @@ class TypewriterManager { // 配置选项 this.options = { typingSpeed: 50, // 打字速度(毫秒) - cursorText: '', // 光标样式 - ...options + cursorText: "", // 光标样式 + ...options, }; // 状态变量 @@ -41,11 +41,11 @@ class TypewriterManager { * @param {string} content - 要添加的内容 */ addContent(content) { - if (typeof content !== 'string') { + if (typeof content !== "string") { content = String(content); } this.currentMessageContent += content; - + // 如果没有在打字,启动打字机效果 if (!this.isTyping) { this.startTypewriter(); @@ -70,18 +70,22 @@ class TypewriterManager { _typeNextChar() { // 如果已显示内容长度小于完整内容长度,继续打字 if (this.displayedContent.length < this.currentMessageContent.length) { + const nextLength = Math.min( + this.displayedContent.length + 1, + this.currentMessageContent.length + ); this.displayedContent = this.currentMessageContent.substring( 0, - this.displayedContent.length + 1 + nextLength ); - + const displayContent = this.displayedContent; - + // 调用内容更新回调 if (this.onContentUpdate) { this.onContentUpdate(displayContent); } - + // 调用字符打字回调 if (this.onCharacterTyped) { this.onCharacterTyped(this.displayedContent); @@ -92,18 +96,17 @@ class TypewriterManager { this.typewriterTimer = setTimeout(() => { this._typeNextChar(); }, delay); - } else { // 打字完成,移除光标 if (this.onContentUpdate) { this.onContentUpdate(this.currentMessageContent); } - + // 调用打字完成回调 if (this.onTypingComplete) { this.onTypingComplete(this.currentMessageContent); } - + this.stopTypewriter(); } } @@ -119,6 +122,21 @@ class TypewriterManager { this.isTyping = false; } + /** + * 停止打字机效果并保留当前显示的内容 + * 与stopTypewriter不同,这个方法会将当前显示的内容设为最终内容 + */ + stopAndKeepCurrent() { + this.stopTypewriter(); + // 将当前显示的内容设为完整内容,避免后续添加更多内容 + this.currentMessageContent = this.displayedContent; + + // 调用完成回调 + if (this.onTypingComplete) { + this.onTypingComplete(this.displayedContent); + } + } + /** * 重置打字机状态 */ @@ -137,9 +155,10 @@ class TypewriterManager { isTyping: this.isTyping, currentContent: this.currentMessageContent, displayedContent: this.displayedContent, - progress: this.currentMessageContent.length > 0 - ? this.displayedContent.length / this.currentMessageContent.length - : 0 + progress: + this.currentMessageContent.length > 0 + ? this.displayedContent.length / this.currentMessageContent.length + : 0, }; } @@ -149,11 +168,11 @@ class TypewriterManager { completeImmediately() { this.stopTypewriter(); this.displayedContent = this.currentMessageContent; - + if (this.onContentUpdate) { this.onContentUpdate(this.currentMessageContent); } - + if (this.onTypingComplete) { this.onTypingComplete(this.currentMessageContent); } @@ -171,4 +190,4 @@ class TypewriterManager { } } -export default TypewriterManager; \ No newline at end of file +export default TypewriterManager;