| | |
| | | "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" |
| | |
| | | "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" |
| | |
| | | "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", |
| | |
| | | "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", |
| | |
| | | "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", |
| | |
| | | "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", |
| | |
| | | "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", |
| | |
| | | "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" |
New file |
| | |
| | | import axios from "./request"; |
| | | |
| | | // 获取知识库列表 |
| | | export const getKbList = () => { |
| | | return axios({ |
| | | url: "knowledge_base/list_knowledge_bases", |
| | | method: "GET" |
| | | }) |
| | | } |
| | |
| | | |
| | | const instance = axios.create({ |
| | | baseURL: '/api/', |
| | | timeout: 50000, |
| | | timeout: 500000, |
| | | // 不携带cookie |
| | | withCredentials: false, |
| | | headers: { |
| | |
| | | |
| | | // 添加响应拦截器 |
| | | 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; |
| | |
| | | <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 |
| | |
| | | <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"/> |
| | |
| | | |
| | | <!-- 输入框 --> |
| | | <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" |
| | |
| | | |
| | | <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 |
| | | }, |
| | |
| | | }; |
| | | }, |
| | | methods: { |
| | | // md渲染为html |
| | | getHtml(md) { |
| | | return markdownIt.render(md) |
| | | }, |
| | | changeNetEnable() { |
| | | this.netSearchEnable = ! this.netSearchEnable |
| | | }, |
| | |
| | | e.preventDefault(); // 阻止默认换行行为 |
| | | }, |
| | | handleMouseEnter(msgIndex) { |
| | | console.log("鼠标移入", msgIndex) |
| | | this.msgIndex = msgIndex |
| | | }, |
| | | handleMouseLeave() { |
| | | console.log("鼠标移除") |
| | | this.msgIndex = null |
| | | }, |
| | | // 重新生成 |
| | |
| | | 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> |
| | |
| | | .chat-messages { |
| | | padding: 16px; |
| | | margin-top: 14px; |
| | | overflow-y: auto; |
| | | width: 800px; |
| | | height: 680px; |
| | | } |
| | | |
| | | .message { |
| | |
| | | } |
| | | |
| | | .chat-input { |
| | | padding: 16px; |
| | | background-color: #fff; |
| | | width: 800px; |
| | | width: 100%; |
| | | display: flex; |
| | | flex-direction: row; |
| | | justify-content: center; |
| | |
| | | name: "LoginView", |
| | | data() { |
| | | return { |
| | | open: true, |
| | | open: false, |
| | | loginForm: { |
| | | username: '', |
| | | pwd: '' |
| | |
| | | } |
| | | .right { |
| | | width: 1600px; |
| | | height: 100vh; |
| | | overflow-y: scroll; |
| | | position: relative; |
| | | } |
| | | .logo { |
| | | width: 100%; |
| | |
| | | devServer: { |
| | | proxy: { |
| | | "/api": { |
| | | target: 'http://i-1.gpushare.com:10695/',//代理地址 凡是使用/api |
| | | target: 'http://i-1.gpushare.com:52574/',//代理地址 凡是使用/api |
| | | changeOrigin: true,//允许跨域请求 |
| | | secure: false, |
| | | pathRewrite: { //重写路径 替换请求地址中的指定路径 |