From 689aced342a8c6b41c1f7dcb7594e9fce387112b Mon Sep 17 00:00:00 2001
From: xiangpei <xiangpei@timesnew.cn>
Date: 星期三, 16 四月 2025 10:49:02 +0800
Subject: [PATCH] 聊天对接

---
 src/api/request.js        |   16 ++--
 vue.config.js             |    2 
 package-lock.json         |   55 +++++++++++++
 package.json              |    1 
 src/views/Index.vue       |    3 
 src/api/knowledge.js      |    9 ++
 src/components/AiChat.vue |  115 +++++++++++++++++++---------
 src/components/Login.vue  |    2 
 8 files changed, 153 insertions(+), 50 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 63ad5f1..4308e26 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
         "clipboard": "^2.0.11",
         "core-js": "^3.8.3",
         "element-ui": "^2.15.14",
+        "markdown-it": "^14.1.0",
         "vue": "^2.6.14",
         "vue-router": "^3.5.1",
         "vuex": "^3.6.2"
@@ -5405,7 +5406,6 @@
       "version": "4.5.0",
       "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
       "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
-      "dev": true,
       "license": "BSD-2-Clause",
       "engines": {
         "node": ">=0.12"
@@ -7773,6 +7773,15 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/linkify-it": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.0.tgz",
+      "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
+      "license": "MIT",
+      "dependencies": {
+        "uc.micro": "^2.0.0"
+      }
+    },
     "node_modules/loader-runner": {
       "version": "4.3.0",
       "resolved": "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.0.tgz",
@@ -8093,6 +8102,29 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/markdown-it": {
+      "version": "14.1.0",
+      "resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-14.1.0.tgz",
+      "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+      "license": "MIT",
+      "dependencies": {
+        "argparse": "^2.0.1",
+        "entities": "^4.4.0",
+        "linkify-it": "^5.0.0",
+        "mdurl": "^2.0.0",
+        "punycode.js": "^2.3.1",
+        "uc.micro": "^2.1.0"
+      },
+      "bin": {
+        "markdown-it": "bin/markdown-it.mjs"
+      }
+    },
+    "node_modules/markdown-it/node_modules/argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "license": "Python-2.0"
+    },
     "node_modules/math-intrinsics": {
       "version": "1.1.0",
       "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -8108,6 +8140,12 @@
       "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
       "dev": true,
       "license": "CC0-1.0"
+    },
+    "node_modules/mdurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-2.0.0.tgz",
+      "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
+      "license": "MIT"
     },
     "node_modules/media-typer": {
       "version": "0.3.0",
@@ -9916,6 +9954,15 @@
         "node": ">=6"
       }
     },
+    "node_modules/punycode.js": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz",
+      "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/qs": {
       "version": "6.13.0",
       "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz",
@@ -11461,6 +11508,12 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/uc.micro": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz",
+      "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
+      "license": "MIT"
+    },
     "node_modules/undici-types": {
       "version": "6.20.0",
       "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.20.0.tgz",
diff --git a/package.json b/package.json
index 9362f0d..bdf6baa 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
     "clipboard": "^2.0.11",
     "core-js": "^3.8.3",
     "element-ui": "^2.15.14",
+    "markdown-it": "^14.1.0",
     "vue": "^2.6.14",
     "vue-router": "^3.5.1",
     "vuex": "^3.6.2"
diff --git a/src/api/knowledge.js b/src/api/knowledge.js
new file mode 100644
index 0000000..a80d658
--- /dev/null
+++ b/src/api/knowledge.js
@@ -0,0 +1,9 @@
+import axios from "./request";
+
+// 鑾峰彇鐭ヨ瘑搴撳垪琛�
+export const getKbList = () => {
+    return axios({
+        url: "knowledge_base/list_knowledge_bases",
+        method: "GET"
+    })
+}
diff --git a/src/api/request.js b/src/api/request.js
index b6d9702..b6b202c 100644
--- a/src/api/request.js
+++ b/src/api/request.js
@@ -4,7 +4,7 @@
 
 const instance = axios.create({
     baseURL: '/api/',
-    timeout: 50000,
+    timeout: 500000,
     // 涓嶆惡甯ookie
     withCredentials: false,
     headers: {
@@ -26,19 +26,19 @@
 
 // 娣诲姞鍝嶅簲鎷︽埅鍣�
 instance.interceptors.response.use(function (response) {
+    console.log("姝e父鍝嶅簲缁撴灉",response)
     // 澶勭悊鑷畾涔夌姸鎬佺爜
-    if(response.data.code === 200) {
+    if(response.status === 200) {
         return response;
         // 楠岃瘉鐮侀敊璇斁琛岋紝浠ヤ究鍒锋柊楠岃瘉鐮�
-    } else if(response.data.code === 404) {
-        Message.error(response.data.msg);
-    } else if (response.data.code === 1998) {
-        return response;
+    } else if(response.status === 404) {
+        Message.error(response.statusText);
     } else {
-      Message.error(response.data.msg);
-      return Promise.reject(response.data.msg);
+      Message.error(response.statusText);
+      return Promise.reject(response.statusText);
     }
   }, function (error) {
+    console.log("閿欒鍝嶅簲缁撴灉",error)
     // 澶勭悊http鐘舵�佺爜
     if(error.response.data) {
       error.message = error.response.data.msg;
diff --git a/src/components/AiChat.vue b/src/components/AiChat.vue
index e17ef15..6a3f58e 100644
--- a/src/components/AiChat.vue
+++ b/src/components/AiChat.vue
@@ -1,5 +1,5 @@
 <template>
-  <div style="position: relative;height:calc(100vh - 20px);width: 100%;display: flex;justify-content: center">
+  <div style="height:calc(100vh - 20px);width: 100%;display: flex;justify-content: center">
     <!-- 鑱婂ぉ娑堟伅鍒楄〃 -->
     <div class="chat-messages">
       <div
@@ -11,8 +11,11 @@
         <div class="avatar">
           <img :src="getAvatar(message.role)" alt="avatar" />
         </div>
-        <div class="content">
-          <div class="text" :ref="'msg' + index">{{ message.content }}</div>
+        <div v-if="!message.content && message.role == 'assistant'" v-loading="!message.content && message.role == 'assistant'">
+
+        </div>
+        <div v-else class="content">
+          <div class="text" :ref="'msg' + index" v-html="getHtml(message.content)"></div>
           <div v-if="message.role === 'assistant'">
             <div v-show="msgIndex === message" class="msg-op-list">
               <el-button class="copy" v-show="false"/>
@@ -37,7 +40,7 @@
 
     <!-- 杈撳叆妗� -->
     <div class="chat-input">
-      <div style="position: relative;width: 100%;box-sizing: border-box;">
+      <div style="position: relative;width: 800px;box-sizing: border-box;">
         <el-input
             v-model="inputMessage"
             type="textarea"
@@ -74,45 +77,29 @@
 
 <script>
 import ClipboardJS from 'clipboard';
-import {sendKbMsg} from "@/api/chat";
+// import {sendKbMsg} from "@/api/chat";
 import {Message} from "element-ui";
+const markdownIt = require('markdown-it')();
 
 export default {
   name: 'AiChat',
   data() {
     return {
+      messages: [], // 鐢ㄤ簬椤甸潰灞曠ず鐨勫璇濆垪琛�
       netSearchEnable: false,
-      messages: [
-        {
-          role: 'assistant',
-          content: '浣犲ソ锛佹垜鏄綘鐨� AI 鍔╂墜锛屾湁浠�涔堝彲浠ュ府浣犵殑鍚楋紵',
-        },
-        {
-          role: 'user',
-          content: '浣犲ソ锛佹垜鏄綘鐨� AI 鍔╂墜锛屾湁浠�涔堝彲浠ュ府浣犵殑鍚楋紵',
-        },
-      ],
       inputMessage: '',
       sendMsgForm: {
         query: "",
         mode: "local_kb",
-        kb_name: "samples",
+        kb_name: "SouthWest_Neclear_Develepment_KB",
         top_k: 3,
         score_threshold: 2,
         history: [
-          {
-            "content": "鎴戜滑鏉ョ帺鎴愯鎺ラ緳锛屾垜鍏堟潵锛岀敓榫欐椿铏�",
-            "role": "user"
-          },
-          {
-            "content": "铏庡ご铏庤剳",
-            "role": "assistant"
-          }
         ],
         stream: true,
-        model: "qwen:7b",
+        model: "Qwen25-32B-Instruct",
         temperature: 0.7,
-        max_tokens: 0,
+        max_tokens: 64,
         prompt_name: "default",
         return_direct: false
       },
@@ -121,6 +108,10 @@
     };
   },
   methods: {
+    // md娓叉煋涓篽tml
+    getHtml(md) {
+      return markdownIt.render(md)
+    },
     changeNetEnable() {
       this.netSearchEnable = ! this.netSearchEnable
     },
@@ -129,11 +120,9 @@
       e.preventDefault(); // 闃绘榛樿鎹㈣琛屼负
     },
     handleMouseEnter(msgIndex) {
-      console.log("榧犳爣绉诲叆", msgIndex)
       this.msgIndex = msgIndex
     },
     handleMouseLeave() {
-      console.log("榧犳爣绉婚櫎")
       this.msgIndex = null
     },
     // 閲嶆柊鐢熸垚
@@ -176,18 +165,69 @@
         role: 'user',
         content: this.inputMessage,
       });
+      this.messages.push({
+        role: 'assistant',
+        content: ''
+      })
       this.sendMsgForm.query = this.inputMessage
       // 娓呯┖杈撳叆妗�
       this.inputMessage = '';
 
-      sendKbMsg(this.sendMsgForm).then(res => {
-        console.log(res, "鎷垮埌鍥炲浜嗭紒锛�")
-        this.messages.push({
-          role: 'assistant',
-          content: '杩欐槸涓�鏉� AI 鍥炲銆�',
-        });
-      })
+      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();
+
+      // 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();
+                  })
+                }
+              }
+
+            }
+          }
+        }
+
+      }
+      // sendKbMsg(this.sendMsgForm).then(res => {
+      //   console.log(res, "鎷垮埌鍥炲浜嗭紒锛�")
+      //   this.sendMsgForm.history.push({
+      //     role: 'assistant',
+      //     content: JSON.parse(res.data).choices[0].message.content,
+      //   });
+      // })
     },
+
   },
 };
 </script>
@@ -229,9 +269,7 @@
 .chat-messages {
   padding: 16px;
   margin-top: 14px;
-  overflow-y: auto;
   width: 800px;
-  height: 680px;
 }
 
 .message {
@@ -280,9 +318,8 @@
 }
 
 .chat-input {
-  padding: 16px;
   background-color: #fff;
-  width: 800px;
+  width: 100%;
   display: flex;
   flex-direction: row;
   justify-content: center;
diff --git a/src/components/Login.vue b/src/components/Login.vue
index fcdaea1..48f24c9 100644
--- a/src/components/Login.vue
+++ b/src/components/Login.vue
@@ -39,7 +39,7 @@
   name: "LoginView",
   data() {
     return {
-      open: true,
+      open: false,
       loginForm: {
         username: '',
         pwd: ''
diff --git a/src/views/Index.vue b/src/views/Index.vue
index d88fddf..670d93d 100644
--- a/src/views/Index.vue
+++ b/src/views/Index.vue
@@ -261,6 +261,9 @@
 }
 .right {
   width: 1600px;
+  height: 100vh;
+  overflow-y: scroll;
+  position: relative;
 }
 .logo {
   width: 100%;
diff --git a/vue.config.js b/vue.config.js
index e42232b..c78c8d6 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -4,7 +4,7 @@
   devServer: {
     proxy: {
       "/api": {
-        target: 'http://i-1.gpushare.com:10695/',//浠g悊鍦板潃 鍑℃槸浣跨敤/api
+        target: 'http://i-1.gpushare.com:52574/',//浠g悊鍦板潃 鍑℃槸浣跨敤/api
         changeOrigin: true,//鍏佽璺ㄥ煙璇锋眰
         secure: false,
         pathRewrite: { //閲嶅啓璺緞 鏇挎崲璇锋眰鍦板潃涓殑鎸囧畾璺緞

--
Gitblit v1.8.0