From 6abf7642caa66239f3f3e75454811f95aaf50a26 Mon Sep 17 00:00:00 2001
From: xiangpei <xiangpei@timesnew.cn>
Date: 星期三, 16 四月 2025 17:49:48 +0800
Subject: [PATCH] 聊天对接实现打字机效果

---
 src/components/AiChat.vue |  141 ++++++++++++++++++++++++++--------------------
 1 files changed, 79 insertions(+), 62 deletions(-)

diff --git a/src/components/AiChat.vue b/src/components/AiChat.vue
index 6a3f58e..547eda8 100644
--- a/src/components/AiChat.vue
+++ b/src/components/AiChat.vue
@@ -95,16 +95,21 @@
         top_k: 3,
         score_threshold: 2,
         history: [
+
         ],
         stream: true,
         model: "Qwen25-32B-Instruct",
-        temperature: 0.7,
-        max_tokens: 64,
+        temperature: 1.15,
+        max_tokens: 512,
         prompt_name: "default",
         return_direct: false
       },
       msgIndex: null,
-      msgRole: null
+      msgRole: null,
+      chunkRef: null, // ai鍥炵瓟涓存椂鏁版嵁
+      haveThinkStart: false, // ai鍥炵瓟鍝嶅簲鏁版嵁鏄惁鏈塼hink鏍囩
+      haveThinkEnd: false, // ai鍥炵瓟鍝嶅簲鏁版嵁鏄惁鏈塼hink鏍囩
+      controller: null, // ai鍥炵瓟璇锋眰鎺у埗鍣紝鍙互鏍规嵁涓氬姟涓柇璇锋眰
     };
   },
   methods: {
@@ -156,77 +161,89 @@
       return roleNames[role];
     },
 
-    // 鍙戦�佹秷鎭�
     async sendMessage() {
       if (this.inputMessage.trim() === '') return;
 
-      // 娣诲姞鐢ㄦ埛娑堟伅
-      this.messages.push({
-        role: 'user',
-        content: this.inputMessage,
-      });
-      this.messages.push({
-        role: 'assistant',
-        content: ''
-      })
-      this.sendMsgForm.query = this.inputMessage
-      // 娓呯┖杈撳叆妗�
+      // 鍒濆鍖栫姸鎬�
+      this.chunkRef = '';
+      this.haveThinkStart = false;
+      this.haveThinkEnd = false;
+      this.controller = new AbortController();
+
+      // 娣诲姞鐢ㄦ埛娑堟伅鍜屽崰浣嶅姪鐞嗘秷鎭�
+      this.messages.push(
+          { role: 'user', content: this.inputMessage },
+          { role: 'assistant', content: '' }
+      );
+      this.sendMsgForm.query = this.inputMessage;
       this.inputMessage = '';
+      const assistantIndex = this.messages.length - 1;
+      try {
+        const response = await fetch('/api/chat/kb_chat', {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+            "Accept": 'text/event-stream',
+            'Cache-Control': 'no-cache, no-transform'
+          },
+          signal: this.controller.signal,
+          body: JSON.stringify(this.sendMsgForm)
+        });
 
-      const response = await fetch('/api/chat/kb_chat', {
-        method: 'POST',
-        headers: {
-          'Content-Type': 'application/json',
-        },
-        body: JSON.stringify(this.sendMsgForm)
-      });
-      const reader = response.body.getReader();
-      const decoder = new TextDecoder();
+        if (!response.ok) throw new Error(`HTTP ${response.status}`);
 
-      // eslint-disable-next-line no-constant-condition
-      while (true) {
-        const { done, value } = await reader.read();
-        if (done) break;
-        console.log("缁撴潫寰幆")
-        const chunk = decoder.decode(value, { stream: false });
-        const dataList = chunk.split('\r\n\r\n')
-        console.log("鑾峰彇鍒颁簡娴佸紡鍝嶅簲鏁版嵁", dataList)
-        if (dataList && dataList.length > 0) {
-          for (const data of dataList) {
-            if (data.startsWith("data: ")) {
-              const jsonStr = data.slice(6).trim();
-              if (jsonStr) {
-                const json = JSON.parse(jsonStr);
-                if (json.docs && json.docs.length > 0) {
-                  console.log("1", json)
-                  json.docs.forEach(str => {
-                    this.messages[this.messages.length - 1].content += str
-                    // 寮哄埗Vue鏇存柊瑙嗗浘
-                    this.$forceUpdate();
-                  })
-                } else if (json.choices && json.choices.length > 0) {
-                  console.log("2", json)
-                  json.choices.forEach(choice => {
-                    this.messages[this.messages.length - 1].content += choice.delta.content
-                    // 寮哄埗Vue鏇存柊瑙嗗浘
-                    this.$forceUpdate();
-                  })
+        const reader = response.body.getReader();
+        const decoder = new TextDecoder();
+        let jsonBuffer = '';
+
+        // eslint-disable-next-line no-constant-condition
+        while (true) {
+          const { done, value } = await reader.read();
+          if (done) {
+            this.sendMsgForm.history.push({ role: 'user', content: this.inputMessage })
+            this.sendMsgForm.history.push({ role: 'assistant', content: this.messages[assistantIndex].content })
+            break;
+          }
+
+          const chunk = decoder.decode(value, { stream: true });
+          const lines = chunk.split('\n');
+
+          for (const line of lines) {
+            if (!line.startsWith('data:')) continue;
+
+            const rawData = line.slice(5).trim();
+            if (rawData === '[DONE]') continue;
+
+            try {
+              jsonBuffer += rawData;
+              const data = JSON.parse(jsonBuffer);
+              jsonBuffer = '';
+              let content = '';
+              if (data.docs) {
+                for (let i = 0; i < data.docs.length; i++) {
+                  content += data.docs[i]
                 }
+              } else {
+                content = data?.choices?.[0]?.delta?.content || '';
               }
-
+              if (!content) continue;
+              // 瀹炴椂鏇存柊
+              this.messages[assistantIndex].content += content;
+              this.messages[assistantIndex].content = this.messages[assistantIndex].content.replace(/<\/?think>/g, '');
+              await this.$nextTick();
+            } catch {
+              // JSON涓嶅畬鏁达紝绛夊緟涓嬫鏁版嵁
+              continue;
             }
           }
         }
-
+      } catch (error) {
+        if (error.name !== 'AbortError') {
+          console.error('Request failed:', error);
+          this.messages[assistantIndex].content = '璇锋眰寮傚父';
+        }
       }
-      // sendKbMsg(this.sendMsgForm).then(res => {
-      //   console.log(res, "鎷垮埌鍥炲浜嗭紒锛�")
-      //   this.sendMsgForm.history.push({
-      //     role: 'assistant',
-      //     content: JSON.parse(res.data).choices[0].message.content,
-      //   });
-      // })
-    },
+    }
 
   },
 };

--
Gitblit v1.8.0