ソースを参照

优化AI回复效果

jiuling 4 ヶ月 前
コミット
d07dac1f25
1 ファイル変更179 行追加82 行削除
  1. 179 82
      src/views/ai-chat/index.vue

+ 179 - 82
src/views/ai-chat/index.vue

@@ -15,7 +15,8 @@
               <div class="message-avatar ai-avatar" v-else-if="message.role === 'assistant'">
                 <i class="el-icon-s-opportunity"></i>
               </div>
-              <div class="message-text" v-html="message.content">
+              <div class="message-text">
+                <div v-html="formatMessageContent(message.content)"></div>
               </div>
               <div class="message-completed" v-if="message.role === 'assistant' && message.completed">
                 <i class="el-icon-check"></i>
@@ -51,7 +52,7 @@
                 <i class="el-icon-s-opportunity"></i>
               </div>
               <div class="message-text">
-                <div v-html="currentStreamingText"></div>
+                <div v-html="formatMessageContent(currentStreamingText)"></div>
               </div>
             </div>
           </div>
@@ -97,7 +98,8 @@
               <!-- <i class="el-icon-s-opportunity"></i> -->
               <img src="@/assets/icons/ai.png" alt="Custom Icon" class="custom-icon">
             </div>
-            <div class="message-text" v-html="message.content">
+            <div class="message-text">
+              <div v-html="formatMessageContent(message.content)"></div>
             </div>
             <div class="message-completed" v-if="message.role === 'assistant' && message.completed">
               <i class="el-icon-check"></i>
@@ -133,7 +135,7 @@
               <img src="@/assets/icons/ai.png" alt="Custom Icon" class="custom-icon">
             </div>
             <div class="message-text">
-              <div v-html="currentStreamingText"></div>
+              <div v-html="formatMessageContent(currentStreamingText)"></div>
             </div>
           </div>
         </div>
@@ -294,49 +296,109 @@ export default {
               const lines = buffer.split('\n');
               buffer = lines.pop() || '';  // 最后一行可能不完整,保存到buffer
               
-              // 变量来跟踪当前事件类型和数据
-              let currentEvent = 'agent_message';  // 默认为agent_message事件类型
-              let currentData = '';
-              
-              for (let i = 0; i < lines.length; i++) {
-                const line = lines[i].trim();
+              for (const line of lines) {
+                // 跳过完全空的行(SSE 协议中的分隔符)
+                if (line === '') continue;
                 
-                // 空行表示一个事件的结束
-                if (line === '') {
-                  if (currentData) {
-                    // 如果有数据,处理当前事件
-                    this.handleEvent(currentEvent, currentData);
-                    currentData = '';
-                  }
-                  continue;
-                }
-                
-                // 事件类型行
+                // 解析 SSE 格式
                 if (line.startsWith('event:')) {
-                  currentEvent = line.slice(6).trim();
+                  // event: message - 忽略,只处理 data 行
                   continue;
                 }
                 
-                // 数据行
                 if (line.startsWith('data:')) {
-                  const dataContent = line.slice(5).trim();
-                  if (dataContent) {
-                    // 初始化或追加数据
-                    if (currentData) {
-                      currentData += '\n' + dataContent;
-                    } else {
-                      currentData = dataContent;
+                  // 提取 data: 后面的内容
+                  let data = line.substring(5).trim();
+                  
+                  // 跳过 ping 事件
+                  if (data === 'ping' || data.includes('"ping"')) continue;
+                  
+                  // 尝试解析 JSON 格式的数据
+                  if (data.startsWith('{')) {
+                    try {
+                      const jsonData = JSON.parse(data);
+                      
+                      // 调试日志:简化输出
+                      console.log('解析JSON - event:', jsonData.event, 'answer长度:', jsonData.answer ? jsonData.answer.length : 0);
+                      
+                      // 处理 MESSAGE_END 事件
+                      if (jsonData.event === 'message_end' || jsonData.eventType === 'MESSAGE_END') {
+                        console.log('收到 MESSAGE_END 事件,流式结束');
+                        this.messageId = jsonData.message_id || jsonData.id;
+                        this.finishStreaming();
+                        continue;
+                      }
+                      
+                      // 处理包含 answer 字段的消息
+                      if (jsonData.answer !== undefined && jsonData.answer !== null) {
+                        this.messageId = jsonData.message_id || jsonData.id;
+                        this.taskId = jsonData.task_id;
+                        
+                        let answer = jsonData.answer.toString();
+                        
+                        // 调试日志:查看 answer 内容
+                        console.log('收到answer,长度:', answer.length, '前50字符:', answer.substring(0, Math.min(50, answer.length)));
+                        
+                        // 检查是否包含完整的 Thinking 块(从开始到结束)
+                        if (answer.includes('<details') && answer.includes('</details>')) {
+                          console.log('检测到完整的Thinking块,提取后续内容');
+                          const detailsEndIndex = answer.indexOf('</details>');
+                          answer = answer.substring(detailsEndIndex + '</details>'.length);
+                          this.isThinking = false;
+                        }
+                        // 检查是否是 Thinking 开始
+                        else if (answer.includes('<details') && answer.includes('<summary>')) {
+                          console.log('检测到Thinking开始,跳过此块');
+                          this.isThinking = true;
+                          continue;
+                        }
+                        // 检查是否是 Thinking 结束
+                        else if (answer.includes('</details>')) {
+                          console.log('检测到Thinking结束,提取后续内容');
+                          const detailsEndIndex = answer.indexOf('</details>');
+                          answer = answer.substring(detailsEndIndex + '</details>'.length);
+                          this.isThinking = false;
+                        }
+                        
+                        // 如果在 Thinking 模式中,跳过
+                        if (this.isThinking) {
+                          console.log('当前在Thinking模式,跳过');
+                          continue;
+                        }
+                        
+                        // 普通消息内容,添加到队列
+                        if (answer) {
+                          console.log('添加到队列,长度:', answer.length);
+                          this.messageQueue.push(answer);
+                          
+                          if (!this.isProcessingQueue) {
+                            this.processMessageQueue();
+                          }
+                        } else {
+                          console.log('answer为空,跳过');
+                        }
+                      }
+                      
+                    } catch (e) {
+                      console.error('JSON解析错误:', e.message);
+                    }
+                  } else {
+                    // 非 JSON 格式的纯文本数据
+                    console.log('纯文本数据,长度:', data.length);
+                    
+                    if (!this.isThinking) {
+                      this.messageQueue.push(data);
+                      
+                      if (!this.isProcessingQueue) {
+                        this.processMessageQueue();
+                      }
                     }
                   }
-                  continue;
+                }else{
+                  this.handleMessageData(line)
                 }
               }
               
-              // 处理缓冲区中可能剩余的完整事件
-              if (currentData) {
-                this.handleEvent(currentEvent, currentData);
-              }
-              
               // 继续读取流
               readStream();
             } catch (error) {
@@ -360,29 +422,48 @@ export default {
         this.finishStreaming();
       });
     },
-    
+    	// 处理消息数据(添加到队列)
+		handleMessageData(text) {
+				if (!text) return;        
+				// 将文本按字符添加到队列
+				for (let char of text) { 
+					this.messageQueue.push(char);
+				}
+				if (!this.isProcessingQueue) {
+            this.processMessageQueue();
+        }
+		},
+
     processMessageQueue() {
       this.isProcessingQueue = true;
       
       const processNextChunk = () => {
         if (this.messageQueue.length > 0) {
-          // 每次只处理一小部分文本,以实现打字机效果
+          // 取出队列中的文本块
           const chunk = this.messageQueue.shift();
           
-          // 处理内容 - 不需要类型检查,直接添加内容到当前流式文本
+          // 调试日志:查看队列中的数据
+          // console.log('处理队列数据,长度:', chunk.length, '内容预览:', chunk.substring(0, 50));
+          
+          // 直接添加整个文本块到当前流式文本(保留换行符)
           this.currentStreamingText += chunk;
           
           // 滚动到底部
           this.scrollToBottom();
           
-          // 使用setTimeout延迟处理下一个块,创造打字机效果
-          setTimeout(() => {
-            processNextChunk();
-          }, 10); // 调整延迟时间可以控制打字速度
+          // 立即处理下一个块(不需要打字机效果的延迟)
+          if (this.messageQueue.length > 0) {
+            setTimeout(() => {
+              processNextChunk();
+            }, 5); // 很短的延迟,只是为了不阻塞UI
+          } else {
+            this.isProcessingQueue = false;
+          }
         } else {
           this.isProcessingQueue = false;
+          // 检查是否有新数据到达
           setTimeout(() => {
-            if (this.messageQueue.length > 0) {
+            if (this.messageQueue.length > 0 && !this.isProcessingQueue) {
               this.processMessageQueue();
             }
           }, 20);
@@ -397,9 +478,21 @@ export default {
       this.isStreaming = false;
       this.isThinking = false;
       
+      console.log('finishStreaming 被调用,队列长度:', this.messageQueue.length, '当前文本长度:', this.currentStreamingText.length);
+      
+      // 先处理完队列中剩余的所有消息
+      if (this.messageQueue.length > 0) {
+        console.log('队列中还有数据,先处理完...');
+        const remainingText = this.messageQueue.join('');
+        this.currentStreamingText += remainingText;
+        this.messageQueue = [];
+      }
+      
       // 等待所有消息处理完毕
       setTimeout(() => {
         if (this.currentStreamingText) {
+          console.log('保存消息到历史记录,总长度:', this.currentStreamingText.length);
+          
           // 创建消息对象
           const assistantMessage = {
             role: "assistant",
@@ -420,11 +513,12 @@ export default {
         this.isLoading = false;
         this.messageQueue = [];
         this.aiThought = "";
+        this.isProcessingQueue = false;
         console.log("this.sessionId:结束",this.sessionId);
         
         // 最终滚动到底部确保内容可见
         this.scrollToBottom();
-      }, 200); // 短暂延迟确保所有内容已处理完毕
+      }, 100); // 缩短延迟时间
     },
     
     // 获取建议问题
@@ -511,10 +605,23 @@ export default {
         return;
       }
       
+      // 处理空数据
+      if (!eventData || !eventData.trim()) {
+        return;
+      }
+      
       try {
         // 尝试解析JSON数据
         const data = JSON.parse(eventData);
         
+        // 过滤掉 MESSAGE_END 等元数据事件
+        if (data.eventType === 'MESSAGE_END' || data.event === 'message_end') {
+          console.log('收到 MESSAGE_END 事件,流式结束');
+          this.messageId = data.id;
+          this.finishStreaming();
+          return;
+        }
+        
         switch (eventName) {
           case 'agent_message':
             // 处理agent_message事件
@@ -523,7 +630,7 @@ export default {
               this.messageId = data.id; // 保存消息ID
               this.taskId = data.task_id; // 保存任务ID用于停止响应
               const answer = data.answer.toString();
-              if (answer.trim()) {
+              if (answer) {
                 this.isThinking = false; // 收到回答,关闭思考状态
                 this.messageQueue.push(answer);
                 
@@ -538,7 +645,7 @@ export default {
             // 处理message事件
             if (data && data.answer !== undefined) {
               const answer = data.answer.toString();
-              if (answer.trim()) {
+              if (answer) {
                 this.isThinking = false; // 收到回答,关闭思考状态
                 this.messageQueue.push(answer);
                 
@@ -546,37 +653,18 @@ export default {
                   this.processMessageQueue();
                 }
               }
-            } else if (data && data.eventType === 'MESSAGE_END') {
-              console.log("Message end event received");
-              this.finishStreaming();
             }
             break;
             
           case 'message_end':
             // 消息结束事件,完成流式处理
-            // console.log("Message end event received:", data);
             this.finishStreaming();
             break;
             
           case 'agent_thought':
             // 处理思考过程
             if (data && data.thought && data.thought.trim() !== '') {
-              // console.log("AI思考:", data.thought);
-              
-              // 格式化思考内容,添加前缀
-              // const formattedThought = `<strong>AI思考:</strong><br>${data.thought.replace(/\n/g, '<br>')}`;
-              
-              // 更新思考内容并显示思考状态
-              // this.aiThought = formattedThought;
               this.isThinking = true;
-              
-              // 确保滚动到可见区域
-              // this.$nextTick(() => {
-              //   const thinkingElement = document.querySelector('.thinking-message');
-              //   if (thinkingElement) {
-              //     thinkingElement.scrollIntoView({ behavior: 'smooth', block: 'end' });
-              //   }
-              // });
             }
             break;
             
@@ -596,7 +684,7 @@ export default {
             // 其他事件类型,尝试提取answer字段
             if (data && data.answer !== undefined) {
               const answer = data.answer.toString();
-              if (answer.trim()) {
+              if (answer) {
                 this.isThinking = false; // 收到回答,关闭思考状态
                 this.messageQueue.push(answer);
                 
@@ -607,26 +695,28 @@ export default {
             }
         }
       } catch (error) {
-        // 如果解析JSON失败,尝试直接使用原始数据
-        console.error("Error parsing event data:", error);
+        // 如果解析JSON失败,说明是纯文本数据
+        console.log("非JSON数据,直接处理:", eventData);
+        
         if (eventName === 'message_end') {
           this.finishStreaming();
         } else if (eventName === 'agent_thought') {
-          // 尝试处理可能的非JSON格式的思考内容
-          if (typeof eventData === 'string' && eventData.trim()) {
-            this.aiThought = eventData;
+          // 思考内容,跳过显示
+          this.isThinking = true;
+        } else if (eventName !== 'error' && eventName !== 'ping') {
+          // 检查是否是 Thinking 内容(包含 <details> 标签)
+          if (eventData.includes('<details') && eventData.includes('<summary>')) {
+            // 跳过 Thinking 内容,不显示
             this.isThinking = true;
+            return;
           }
-        } else if (eventName !== 'error') {
-          // 非控制类事件,直接添加到消息队列
-          if (typeof eventData === 'string' && eventData.trim()) {
-            this.isThinking = false; // 收到回答,关闭思考状态
-            // 将原始文本添加到队列
-            this.messageQueue.push(eventData);
-            
-            if (!this.isProcessingQueue) {
-              this.processMessageQueue();
-            }
+          
+          // 普通消息内容,直接添加到队列
+          this.isThinking = false;
+          this.messageQueue.push(eventData);
+          
+          if (!this.isProcessingQueue) {
+            this.processMessageQueue();
           }
         }
       }
@@ -661,6 +751,13 @@ export default {
     handleSuggestionClick(suggestion) {
       this.userInput = suggestion;
       // this.sendMessage();
+    },
+    
+    // 格式化消息内容,将换行符转换为 <br> 标签
+    formatMessageContent(content) {
+      if (!content) return '';
+      // 将换行符转换为 <br> 标签
+      return content.replace(/\n/g, '<br>');
     }
   }
 };