| | |
| | | <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; |