核工业西南物理研究院知识库AI客户端
xiangpei
2025-04-16 689aced342a8c6b41c1f7dcb7594e9fce387112b
聊天对接
7个文件已修改
1个文件已添加
203 ■■■■ 已修改文件
package-lock.json 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/knowledge.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/request.js 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/AiChat.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Login.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
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",
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"
src/api/knowledge.js
New file
@@ -0,0 +1,9 @@
import axios from "./request";
// 获取知识库列表
export const getKbList = () => {
    return axios({
        url: "knowledge_base/list_knowledge_bases",
        method: "GET"
    })
}
src/api/request.js
@@ -4,7 +4,7 @@
const instance = axios.create({
    baseURL: '/api/',
    timeout: 50000,
    timeout: 500000,
    // 不携带cookie
    withCredentials: false,
    headers: {
@@ -26,19 +26,19 @@
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
    console.log("正常响应结果",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;
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渲染为html
    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;
src/components/Login.vue
@@ -39,7 +39,7 @@
  name: "LoginView",
  data() {
    return {
      open: true,
      open: false,
      loginForm: {
        username: '',
        pwd: ''
src/views/Index.vue
@@ -261,6 +261,9 @@
}
.right {
  width: 1600px;
  height: 100vh;
  overflow-y: scroll;
  position: relative;
}
.logo {
  width: 100%;
vue.config.js
@@ -4,7 +4,7 @@
  devServer: {
    proxy: {
      "/api": {
        target: 'http://i-1.gpushare.com:10695/',//代理地址 凡是使用/api
        target: 'http://i-1.gpushare.com:52574/',//代理地址 凡是使用/api
        changeOrigin: true,//允许跨域请求
        secure: false,
        pathRewrite: { //重写路径 替换请求地址中的指定路径