From 7789aeaad9032763805da324d743bc664bddd2e8 Mon Sep 17 00:00:00 2001 From: xiangpei <xiangpei@timesnew.cn> Date: 星期五, 18 四月 2025 17:17:26 +0800 Subject: [PATCH] 改为对接java而不是直接调langchain --- src/components/AiChat.vue | 323 +++++++++++++++++++++++++++++++++++++++++++---------- 1 files changed, 258 insertions(+), 65 deletions(-) diff --git a/src/components/AiChat.vue b/src/components/AiChat.vue index 41a0fd7..486e8cf 100644 --- a/src/components/AiChat.vue +++ b/src/components/AiChat.vue @@ -1,80 +1,148 @@ <template> - <div style="position: relative;height: 800px;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 v-for="(message, index) in messages" - :key="index" + :key="message + index" :class="['message', message.role]" + @mouseover="handleMouseEnter(message)" > <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 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"/> + <el-tooltip class="item" effect="dark" content="澶嶅埗" placement="top"> + <i class="el-icon-copy-document msg-op msg-copy" @click="copyText(message.content)"/> + </el-tooltip> + <el-tooltip class="item" effect="dark" content="閲嶆柊鐢熸垚" placement="top"> + <i class="el-icon-refresh msg-op msg-re" @click="reAnswer"/> + </el-tooltip> + </div> + </div> + </div> + <div v-if="message.role === 'user'"> + <div v-show="msgIndex === message" style="display: flex"> + <el-tooltip class="item" effect="dark" content="澶嶅埗" placement="top"> + <i class="el-icon-copy-document msg-op msg-copy" @click="copyText(message.content)"/> + </el-tooltip> + </div> </div> </div> </div> <!-- 杈撳叆妗� --> <div class="chat-input"> + <div style="position: relative;width: 800px;box-sizing: border-box;"> <el-input v-model="inputMessage" + type="textarea" + resize="none" + :autofocus="true" + :autosize="true" placeholder="璇疯緭鍏ユ秷鎭紝鎸変笅鍥炶溅鍙戦��" @keyup.native.enter="sendMessage" + @keydown.enter.native.prevent="handleEnterKey" > - <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 class="send-op-list"> + <div class="send-warp"> + <div class="add-option"> + <div :class="{'add-but': true, 'add-but-active': netSearchEnable}" @click="changeNetEnable"> + <svg t="1743144032689" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2731" width="18" height="18"> + <path fill="#575757" d="M909.989 343.281c-21.76-51.446-52.904-97.643-92.568-137.307-39.663-39.664-85.86-70.808-137.306-92.568-53.28-22.536-109.858-33.962-168.164-33.962S397.067 90.87 343.787 113.406c-51.446 21.76-97.643 52.904-137.307 92.568-39.664 39.664-70.808 85.86-92.568 137.307-22.536 53.28-33.962 109.858-33.962 168.164s11.426 114.884 33.962 168.164c21.76 51.445 52.904 97.643 92.568 137.306 39.664 39.664 85.86 70.809 137.307 92.568 53.28 22.535 109.858 33.962 168.164 33.962s114.884-11.427 168.164-33.962c51.445-21.76 97.643-52.904 137.306-92.568 39.664-39.663 70.809-85.86 92.568-137.306 22.535-53.28 33.962-109.858 33.962-168.164s-11.427-114.884-33.962-168.164zM543.951 376.03c17.57-0.243 35.095-0.683 52.536-1.368a1990.762 1990.762 0 0 0 91.131-5.678c10.157 34.378 16.643 71.314 19.389 110.46H543.951V376.03z m0-64.069V163.41c17.617 12.176 39.372 29.845 61.074 54.197 23.42 26.28 43.109 56.138 58.784 89.019-41.937 3.147-82.117 4.783-119.858 5.335z m-64-144.763v144.705a2038.572 2038.572 0 0 1-103.41-4.309 1929.6 1929.6 0 0 1-11.183-0.786c15.195-31.915 34.175-60.991 56.676-86.711 20.284-23.185 40.722-40.49 57.917-52.899zM370.842 371.354a2124.301 2124.301 0 0 0 109.109 4.606v103.484H322.25c2.742-39.079 9.208-75.96 19.335-110.285 9.453 0.776 19.21 1.512 29.257 2.195z m-112.731 108.09H145.342c3.916-45.757 16.208-89.778 36.139-130.276 20.257 3.578 52.836 8.759 95.407 13.641-9.902 36.699-16.182 75.682-18.777 116.635z m-0.859 64c1.975 39.144 9.712 78.242 23.101 116.799-44.093 4.976-77.797 10.319-98.621 13.992-20.08-40.64-32.458-84.841-36.39-130.792h111.91z m64.098 0h158.601v104.048c-36.37 0.602-72.818 2.13-109.109 4.601-8.41 0.573-16.612 1.182-24.609 1.821-14.322-36.523-22.659-73.543-24.883-110.47z m158.601 168.103v131.712c-16.974-15.877-36.574-35.962-56.038-59.735-18.064-22.064-33.892-44.698-47.379-67.668a2038.38 2038.38 0 0 1 103.417-4.309z m64 136.665V711.487c34.387 0.503 70.782 1.903 108.68 4.527-13.467 22.915-29.263 45.496-47.286 67.509-21.547 26.32-43.262 48.119-61.394 64.689z m52.536-199.426a2063.187 2063.187 0 0 0-52.536-1.384V543.444h163.958c-2.227 36.975-10.582 74.045-24.939 110.615a1994.972 1994.972 0 0 0-86.483-5.273z m175.521-105.342H878.56c-3.928 45.898-16.283 90.051-36.322 130.652a1822.84 1822.84 0 0 0-93.476-13.439c13.478-38.691 21.264-77.929 23.246-117.213z m-0.859-64c-2.605-41.111-8.924-80.238-18.891-117.061a1821.491 1821.491 0 0 0 90.233-13.075c19.89 40.46 32.158 84.432 36.07 130.136H771.149z m1.017-228.215a373.188 373.188 0 0 1 34.046 39.155 1772.853 1772.853 0 0 1-75.117 9.98c-19.529-46.785-45.825-88.91-78.291-125.338a423.863 423.863 0 0 0-5.386-5.927c46.185 18.263 88.573 45.955 124.748 82.13zM384.81 165.923a420.788 420.788 0 0 0-8.355 9.102c-32.552 36.525-58.904 78.775-78.449 125.71-32.357-3.484-59.48-7.244-80.226-10.469a373.386 373.386 0 0 1 33.956-39.037c38.338-38.338 83.654-67.152 133.074-85.306zM251.736 771.659a373.444 373.444 0 0 1-33.584-38.548c22.526-3.498 52.529-7.614 88.626-11.328 18.229 35.542 41.31 70.356 68.867 103.808a678.068 678.068 0 0 0 35.727 39.995c-59.757-16.875-114.524-48.814-159.636-93.927z m368.249 91.745a677.032 677.032 0 0 0 33.629-37.814c27.469-33.344 50.487-68.043 68.689-103.466a1780.315 1780.315 0 0 1 83.528 10.88 373.34 373.34 0 0 1-33.665 38.654c-43.23 43.232-95.324 74.373-152.181 91.746z" p-id="2732"></path></svg> + 鑱旂綉鎼滅储 + </div> + </div> + <div class="send-but-warp"> + <div class="send-but" @click="sendMessage"> + <svg t="1743144485359" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1261" width="36" height="36"> + <path fill="#409eff" d="M512 85.333333c234.666667 0 426.666667 192 426.666667 426.666667s-192 426.666667-426.666667 426.666667S85.333333 746.666667 85.333333 512 277.333333 85.333333 512 85.333333z m-6.4 234.666667c-4.266667 2.133333-6.4 2.133333-12.8 8.533333l-153.6 153.6c-12.8 12.8-12.8 32 0 44.8 12.8 12.8 32 12.8 44.8 0l96-96V682.666667c0 17.066667 14.933333 32 32 32s32-14.933333 32-32V430.933333l96 96c12.8 12.8 32 12.8 44.8 0s12.8-32 0-44.8l-153.6-153.6c-6.4-6.4-8.533333-8.533333-12.8-8.533333s-8.533333-2.133333-12.8 0z" p-id="1262"></path> + </svg> + </div> + </div> + </div> + </div> + </div> </div> </div> </template> <script> -import {sendKbMsg} from "@/api/chat"; +import ClipboardJS from 'clipboard'; +import {Message} from "element-ui"; +const markdownIt = require('markdown-it')(); export default { name: 'AiChat', data() { return { - messages: [ - { - role: 'assistant', - content: '浣犲ソ锛佹垜鏄綘鐨� AI 鍔╂墜锛屾湁浠�涔堝彲浠ュ府浣犵殑鍚楋紵', - }, - ], + chatId: null, + messages: [], // 鐢ㄤ簬椤甸潰灞曠ず鐨勫璇濆垪琛� + netSearchEnable: false, inputMessage: '', sendMsgForm: { query: "", mode: "local_kb", - kb_name: "samples", - top_k: 3, - score_threshold: 2, + kbName: "SouthWest_Neclear_Develepment_KB", + topK: 3, + scoreThreshold: 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 - } + model: "Qwen25-32B-Instruct", + temperature: 1.15, + maxTokens: 512, + promptName: "default", + returnDirect: false + }, + msgIndex: null, + msgRole: null, + chunkRef: null, // ai鍥炵瓟涓存椂鏁版嵁 + haveThinkStart: false, // ai鍥炵瓟鍝嶅簲鏁版嵁鏄惁鏈塼hink鏍囩 + haveThinkEnd: false, // ai鍥炵瓟鍝嶅簲鏁版嵁鏄惁鏈塼hink鏍囩 + controller: null, // ai鍥炵瓟璇锋眰鎺у埗鍣紝鍙互鏍规嵁涓氬姟涓柇璇锋眰 }; }, methods: { + // md娓叉煋涓篽tml + getHtml(md) { + return markdownIt.render(md) + }, + changeNetEnable() { + this.netSearchEnable = ! this.netSearchEnable + }, + // 闃绘鍥炶溅鎹㈣ + handleEnterKey(e) { + e.preventDefault(); // 闃绘榛樿鎹㈣琛屼负 + }, + handleMouseEnter(msgIndex) { + this.msgIndex = msgIndex + }, + handleMouseLeave() { + this.msgIndex = null + }, + // 閲嶆柊鐢熸垚 + reAnswer() { + }, + // 澶嶅埗鍐呭 + copyText(content) { + const clipboard = new ClipboardJS('.copy', { + text: () => content + }); + // 瑙﹀彂澶嶅埗锛堥渶瑕佷竴涓殣钘忕殑鎸夐挳锛� + document.querySelector('.copy').click(); + clipboard.destroy(); + Message.success("澶嶅埗鎴愬姛") + }, // 鑾峰彇瑙掕壊澶村儚 getAvatar(role) { const avatars = { @@ -93,40 +161,117 @@ 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 = ''; + // 鍒濆鍖栫姸鎬� + this.chunkRef = ''; + this.haveThinkStart = false; + this.haveThinkEnd = false; + this.controller = new AbortController(); - sendKbMsg(this.sendMsgForm).then(res => { - console.log(res, "鎷垮埌鍥炲浜嗭紒锛�") - this.messages.push({ - role: 'assistant', - content: '杩欐槸涓�鏉� AI 鍥炲銆�', + // 娣诲姞鐢ㄦ埛娑堟伅鍜屽崰浣嶅姪鐞嗘秷鎭� + this.messages.push( + { role: 'user', content: this.inputMessage }, + { role: 'assistant', content: '' } + ); + this.sendMsgForm.query = this.inputMessage; + this.inputMessage = ''; + const assistantIndex = this.messages.length - 1; + try { + const response = await fetch('/api/chat/send/msg', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + "Accept": 'text/event-stream', + 'Cache-Control': 'no-cache, no-transform' + }, + signal: this.controller.signal, + body: JSON.stringify(this.sendMsgForm) }); - }) - }, + + if (!response.ok) throw new Error(`HTTP ${response.status}`); + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let jsonBuffer = ''; + + // eslint-disable-next-line no-constant-condition + while (true) { + const { done, value } = await reader.read(); + if (done) { + this.sendMsgForm.history.push({ role: 'user', content: this.inputMessage }) + this.sendMsgForm.history.push({ role: 'assistant', content: this.messages[assistantIndex].content }) + break; + } + + const chunk = decoder.decode(value, { stream: true }); + const lines = chunk.split('\n'); + + for (const line of lines) { + if (!line.startsWith('data:')) continue; + + const rawData = line.slice(5).trim(); + if (rawData === '[DONE]') continue; + + try { + jsonBuffer += rawData; + const data = JSON.parse(jsonBuffer); + jsonBuffer = ''; + let content = ''; + if (data.docs) { + for (let i = 0; i < data.docs.length; i++) { + content += data.docs[i] + } + } else { + content = data?.choices?.[0]?.delta?.content || ''; + } + if (!content) continue; + // 瀹炴椂鏇存柊 + this.messages[assistantIndex].content += content; + this.messages[assistantIndex].content = this.messages[assistantIndex].content.replace(/<\/?think>/g, ''); + await this.$nextTick(); + } catch { + // JSON涓嶅畬鏁达紝绛夊緟涓嬫鏁版嵁 + continue; + } + } + } + } catch (error) { + if (error.name !== 'AbortError') { + console.error('Request failed:', error); + this.messages[assistantIndex].content = '璇锋眰寮傚父'; + } + } + } + }, }; </script> <style scoped> -.send-but { +.add-but { + width: 90px; + height: 80%; + border: 1px solid #E4E7ED; display: flex; - justify-content: center; align-items: center; - width: 58px; - height: 54px; - background-color: #f6f6f6; - margin-left: 5px; + justify-content: center; + border-radius: 8px; + background-color: #DCDFE6; +} +.add-but:hover { + cursor: pointer; + background-color: #409EFF; +} +.add-but-active { + background-color: #409EFF; +} +.send-but-warp { + flex:1; + display: flex; + justify-content: flex-end; + align-items: center; } .send-but:hover { cursor: pointer; @@ -140,18 +285,18 @@ .chat-messages { padding: 16px; - margin-top: 54px; - overflow-y: auto; + margin-top: 14px; width: 800px; - height: 680px; } .message { display: flex; - margin-bottom: 16px; + margin-bottom: 18px; + min-height: 75px; } .message.user { + align-items: center; flex-direction: row-reverse; } @@ -167,7 +312,7 @@ padding: 8px 12px; border-radius: 8px; background-color: #fff; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + /*box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);*/ } .message.user .content { @@ -190,16 +335,64 @@ } .chat-input { - padding: 16px; background-color: #fff; - width: 800px; + width: 100%; display: flex; flex-direction: row; justify-content: center; align-items: center; position: absolute; - bottom: 70px; + bottom: 50px; left: 50%; /* 灏� div 鐨勫乏杈圭Щ鍔ㄥ埌鐖跺鍣ㄧ殑 50% 浣嶇疆 */ transform: translateX(-50%); /* 灏� div 鍚戝乏绉诲姩鑷韩瀹藉害鐨� 50% */ } + +.msg-op-list { + margin-top: 15px; +} +.msg-copy { + font-size: 18px; +} +.msg-re { + font-size: 20px; +} +.msg-op { + color: gray; + margin-right: 5px; +} +.msg-op:hover { + cursor: pointer; +} + +.send-op-list { + position: absolute; + bottom: 0px; + width: 100%; + height: 40px; + display: flex; + align-items: center; + user-select: none; + opacity: 1; + z-index: 2; +} + +.send-warp { + display: flex; + width: 100%; + height: 100%; + padding: 0 15px; + box-sizing: border-box +} + +.add-option { + flex:1; + display: flex; + justify-content: flex-start; +} + +::v-deep(.el-textarea__inner) { + min-height: 100px !important; + max-height: 400px !important; + padding-bottom: 85px !important; +} </style> -- Gitblit v1.8.0