核工业西南物理研究院知识库AI客户端
xiangpei
2025-03-20 5d929ee64eed9b9e54ec84a7ce531afe444f29df
初始化对话框
1个文件已修改
19个文件已添加
13080 ■■■■■ 已修改文件
.browserslistrc 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintrc.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
babel.config.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jsconfig.json 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json 12542 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/favicon.ico 补丁 | 查看 | 原始文档 | blame | 历史
public/index.html 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/chat.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/request.js 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/logo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/AiChat.vue 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/index.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/HomeView.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.browserslistrc
New file
@@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead
.eslintrc.js
New file
@@ -0,0 +1,17 @@
module.exports = {
  root: true,
  env: {
    node: true
  },
  'extends': [
    'plugin:vue/essential',
    'eslint:recommended'
  ],
  parserOptions: {
    parser: '@babel/eslint-parser'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
  }
}
.gitignore
New file
@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
README.md
@@ -1,4 +1,24 @@
## knowledge-base-ai-client
# ai-client
核工业西南物理研究院知识库AI客户端
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
babel.config.js
New file
@@ -0,0 +1,5 @@
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ]
}
jsconfig.json
New file
@@ -0,0 +1,19 @@
{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "baseUrl": "./",
    "moduleResolution": "node",
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  }
}
package-lock.json
New file
Diff too large
package.json
New file
@@ -0,0 +1,32 @@
{
  "name": "ai-client",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "axios": "^1.8.4",
    "core-js": "^3.8.3",
    "element-ui": "^2.15.14",
    "vue": "^2.6.14",
    "vue-router": "^3.5.1",
    "vuex": "^3.6.2"
  },
  "devDependencies": {
    "@babel/core": "^7.12.16",
    "@babel/eslint-parser": "^7.12.16",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-plugin-router": "~5.0.0",
    "@vue/cli-plugin-vuex": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^8.0.3",
    "less": "^4.0.0",
    "less-loader": "^8.0.0",
    "vue-template-compiler": "^2.6.14"
  }
}
public/favicon.ico
public/index.html
New file
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
src/App.vue
New file
@@ -0,0 +1,28 @@
<template>
  <div id="app">
    <router-view/>
  </div>
</template>
<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
nav {
  padding: 30px;
  a {
    font-weight: bold;
    color: #2c3e50;
    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>
src/api/chat.js
New file
@@ -0,0 +1,10 @@
import axios from "./request";
// 发送知识库问题
export const sendKbMsg = (data) => {
    return axios({
        url: "chat/kb_chat",
        method: "POST",
        data: data
    })
}
src/api/request.js
New file
@@ -0,0 +1,62 @@
import axios from "axios";
import router from "../router";
import { Message } from 'element-ui';
const instance = axios.create({
    baseURL: '/api/',
    timeout: 50000,
    // 不携带cookie
    withCredentials: false,
    headers: {
      "Content-Type": "application/json"
    }
  });
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
    // 带上token
    if (sessionStorage.getItem('token') !== null) {
        config.headers['Authentication'] = sessionStorage.getItem("token");
    }
    return config;
  }, function (error) {
      Message.error("请求存在问题,请检查")
    return Promise.reject(error);
  });
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
    // 处理自定义状态码
    if(response.data.code === 200) {
        return response;
        // 验证码错误放行,以便刷新验证码
    } else if(response.data.code === 404) {
        Message.error(response.data.msg);
    } else if (response.data.code === 1998) {
        return response;
    } else {
      Message.error(response.data.msg);
      return Promise.reject(response.data.msg);
    }
  }, function (error) {
    // 处理http状态码
    if(error.response.data) {
      error.message = error.response.data.msg;
    }
    if(error.response.status === 401) {
      error.message = "登录已过期,请重新登录";
      // 删掉sessionStorage中过期token
      sessionStorage.clear();
      router.push("/");
    }
    if(error.response.status === 403) {
      error.message = "权限不足";
    }
    if(error.response.status === 404) {
        error.message = "请求地址不存在";
    }
    Message.error(error.message);
    return Promise.reject(error);
  });
export default instance;
src/assets/logo.png
src/components/AiChat.vue
New file
@@ -0,0 +1,203 @@
<template>
  <div class="ai-chat-dialog">
    <!-- 聊天消息列表 -->
    <div class="chat-messages">
      <div
          v-for="(message, index) in messages"
          :key="index"
          :class="['message', message.role]"
      >
        <div class="avatar">
          <img :src="getAvatar(message.role)" alt="avatar" />
        </div>
        <div class="content">
<!--          <div class="name">{{ getRoleName(message.role) }}</div>-->
          <div class="text">{{ message.content }}</div>
        </div>
      </div>
    </div>
    <!-- 输入框 -->
    <div class="chat-input">
        <el-input
            style="width: 50%"
            v-model="inputMessage"
            placeholder="请输入消息"
            @keyup.native.enter="sendMessage"
        >
          <div slot="append">
            <el-button type="primary" @click="sendMessage" icon="el-icon-thumb"></el-button>
          </div>
        </el-input>
<!--      <div class="send-but" @click="sendMessage">-->
<!--        <svg t="1742455190051" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1160" width="32" height="32"><path d="M417.016025 991.681197h266.763504l286.972859-930.353884z" fill="#6E6E96" opacity=".2" p-id="1161"></path><path d="M318.168022 667.620127l106.879202 307.873376L985.008068 34.974312z" fill="#E5C0A8" p-id="1162"></path><path d="M425.051265 993.681924a18.180337 18.180337 0 0 1-17.186036-12.222619l-106.87516-307.873376a18.188421 18.188421 0 0 1 4.664319-19.15847L972.498476 21.777602a18.184379 18.184379 0 0 1 28.14355 22.497056L440.67714 984.797891a18.192463 18.192463 0 0 1-15.625875 8.884033z m-85.849345-320.940746l90.210525 259.868071L902.064828 138.741273 339.20192 672.741178z" fill="#6E6E96" p-id="1163"></path><path d="M51.287304 548.991206L985.008068 34.974312 318.168022 667.620127z" fill="#FFDAC1" p-id="1164"></path><path d="M318.16398 685.808548c-2.497876 0-5.01192-0.513318-7.384499-1.568246l-266.876675-118.628922a18.188421 18.188421 0 0 1-10.783713-15.848177 18.204588 18.204588 0 0 1 9.401393-16.713137L976.237207 19.041256a18.188421 18.188421 0 1 1 21.292578 29.129766L330.689739 680.816837a18.184379 18.184379 0 0 1-12.525759 4.991711z m-226.005273-138.555347l222.302919 98.811627L869.915784 119.097779 92.158707 547.253201z" fill="#6E6E96" p-id="1165"></path><path d="M985.008068 34.974312L425.281652 711.656315l429.48924 166.755482z" fill="#FFDAC1" p-id="1166"></path><path d="M854.770892 896.60426a18.196504 18.196504 0 0 1-6.584208-1.232771l-429.48924-166.759524a18.176295 18.176295 0 0 1-7.42896-28.551779L970.998942 23.382225a18.196504 18.196504 0 0 1 31.995453 14.368853l-130.245259 843.445569a18.224798 18.224798 0 0 1-8.87595 12.978448 18.325844 18.325844 0 0 1-9.102294 2.429165z m-399.409634-192.781092l384.887191 149.44819L956.969607 97.417181 455.361258 703.823168z" fill="#6E6E96" p-id="1167"></path><path d="M425.281652 993.681924a18.192463 18.192463 0 0 1-18.188421-18.188421v-263.837188a18.188421 18.188421 0 0 1 36.376842 0v263.837188a18.188421 18.188421 0 0 1-18.188421 18.188421z" fill="#6E6E96" p-id="1168"></path><path d="M985.008068 34.974312L518.147686 728.203736l-62.786428-24.380568zM417.016025 711.656315l78.937746 109.18711 32.504729-54.593555z" fill="#6E6E96" opacity=".2" p-id="1169"></path></svg>-->
<!--      </div>-->
    </div>
  </div>
</template>
<script>
import {sendKbMsg} from "@/api/chat";
export default {
  name: 'AiChat',
  data() {
    return {
      messages: [
        {
          role: 'assistant',
          content: '你好!我是你的 AI 助手,有什么可以帮你的吗?',
        },
      ],
      inputMessage: '',
      sendMsgForm: {
        query: "",
        mode: "local_kb",
        kb_name: "samples",
        top_k: 3,
        score_threshold: 2,
        history: [
          {
            "content": "我们来玩成语接龙,我先来,生龙活虎",
            "role": "user"
          },
          {
            "content": "虎头虎脑",
            "role": "assistant"
          }
        ],
        stream: true,
        model: "qwen:7b",
        temperature: 0.7,
        max_tokens: 0,
        prompt_name: "default",
        return_direct: false
      }
    };
  },
  methods: {
    // 获取角色头像
    getAvatar(role) {
      const avatars = {
        user: 'https://picsum.photos/200',
        assistant: 'https://picsum.photos/200',
      };
      return avatars[role];
    },
    // 获取角色名称
    getRoleName(role) {
      const roleNames = {
        user: '用户',
        assistant: 'AI 助手',
      };
      return roleNames[role];
    },
    // 发送消息
    async sendMessage() {
      if (this.inputMessage.trim() === '') return;
      // 添加用户消息
      this.messages.push({
        role: 'user',
        content: this.inputMessage,
      });
      this.sendMsgForm.query = this.inputMessage
      // 清空输入框
      this.inputMessage = '';
      sendKbMsg(this.sendMsgForm).then(res => {
        console.log(res, "拿到回复了!!")
        this.messages.push({
          role: 'assistant',
          content: '这是一条 AI 回复。',
        });
      })
    },
  },
};
</script>
<style scoped>
.send-but {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 58px;
  height: 54px;
  background-color: #f6f6f6;
  margin-left: 5px;
}
.send-but:hover {
  cursor: pointer;
}
.ai-chat-dialog {
  display: flex;
  flex-direction: column;
  border-radius: 8px;
  overflow: hidden;
}
.chat-messages {
  flex: 1;
  padding: 16px;
  overflow-y: auto;
}
.message {
  display: flex;
  margin-bottom: 16px;
}
.message.user {
  flex-direction: row-reverse;
}
.avatar img {
  width: 40px;
  height: 40px;
  border-radius: 50%;
}
.content {
  max-width: 70%;
  margin: 0 12px;
  padding: 8px 12px;
  border-radius: 8px;
  background-color: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.message.user .content {
  background-color: #409eff;
  color: #fff;
}
.name {
  font-size: 12px;
  color: #666;
  margin-bottom: 4px;
}
.message.user .name {
  color: #fff;
}
.text {
  font-size: 14px;
}
.chat-input {
  padding: 16px;
  background-color: #fff;
  border-top: 1px solid #ddd;
  display: flex;
  width: 100%;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  position: absolute;
  bottom: 70px;
}
</style>
src/main.js
New file
@@ -0,0 +1,17 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.config.productionTip = false
Vue.use(ElementUI);
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')
src/router/index.js
New file
@@ -0,0 +1,28 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import HomeView from '../views/HomeView.vue'
Vue.use(VueRouter)
const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  // {
  //   path: '/about',
  //   name: 'about',
  //   // route level code-splitting
  //   // this generates a separate chunk (about.[hash].js) for this route
  //   // which is lazy-loaded when the route is visited.
  //   component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
  // }
]
const router = new VueRouter({
  mode: 'history',
  routes
})
export default router
src/store/index.js
New file
@@ -0,0 +1,17 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})
src/views/HomeView.vue
New file
@@ -0,0 +1,17 @@
<template>
  <div class="home">
    <ai-chat/>
  </div>
</template>
<script>
import AiChat from "@/components/AiChat";
export default {
  name: 'HomeView',
  components: {
    AiChat
  }
}
</script>
vue.config.js
New file
@@ -0,0 +1,16 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    proxy: {
      "/api": {
        target: 'http://i-1.gpushare.com:10695/',//代理地址 凡是使用/api
        changeOrigin: true,//允许跨域请求
        secure: false,
        pathRewrite: { //重写路径 替换请求地址中的指定路径
          ['^/api']: '/' //将请求地址中的api替换为空
        }
      }
    }
  }
})