import LoginModal from "@/components/LoginModal"; import MarkdownIt from "markdown-it"; import { pcInnerAi,getMinioURl } from "@/api/api"; import axios from 'axios'; const md = new MarkdownIt(); export default { components: { LoginModal, }, data() { return { isThinking: false, thinkingDots: '', messages: [ { user: "bot", messageType: "TEXT", message: "欢迎使用轻良智能AI助理", html: "", time: "", done: true, }, ], generating: false, userInput: "", websocket: null, wsUrl:'http://58.246.234.210:7860/api/v1/run/3ef7369e-b617-40d2-9e2e-54f3ee76ed2e?stream=false', tweaks:{}, showLoginModal: false, isLoggedIn: false, // 添加登录状态标志 idArray: [], minioUrls: [], // 新增:用于存储 Minio URL AImodel:'1',//模型 AIknowledgeBase:"1",//知识库 AIFile:'1',//文件 chatHistory: [ { username: "Theresa Rose", avatar: "/path/to/avatar1.jpg", timestamp: "08:33AM", message: "I hope I can finish Frox soon", preview: "Hi, what's new with Frox? Please upload the latest code, and I will check today" }, { username: "Theresa Rose", avatar: "/path/to/avatar2.jpg", timestamp: "08:33AM", message: "I hope I can finish Frox soon", preview: "Hi, what's new with Frox? Please upload the latest code, and I will check today" }, // Add more chat history items as needed ], currentChatIndex: 0, }; }, created() { // this.connectWebSocket(); }, mounted() { if(this.$route.name=='ai'){ /* 外部知识库 */ const AIData=JSON.parse(sessionStorage.getItem("AIData")) this.wsUrl=AIData.data.url this.tweaks=AIData.data.tweaks }else{ /* 内部知识库 */ pcInnerAi().then(res=>{ if(res.status!==200) return this.wsUrl=res.data.url this.tweaks=res.data.tweaks }) } }, methods: { /* 聊天记录 */ selectChat(index) { this.currentChatIndex = index; // Load the selected chat messages // This is where you would typically load the messages for the selected chat }, newChat() { this.chatHistory.push({ title: `聊天 ${this.chatHistory.length + 1}`, preview: "新的聊天..." }); this.currentChatIndex = this.chatHistory.length - 1; // Clear the current messages and start a new chat this.messages = []; }, handleLinkClick(event) { if (event.target.tagName === 'A') { sessionStorage.setItem('href',event.target.href) let _this = this; let a = document.createElement("a"); a.href = "#/preview"; // 使用 hash a.target = "_blank"; a.click(); } }, handleLoginSuccess() { this.showLoginModal = false; this.isLoggedIn = true; // 登录成功后,可以继续发送消息 this.sendMessage(); }, getUuid() { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function (c) { var r = (Math.random() * 16) | 0, v = c == "x" ? r : (r & 0x3) | 0x8; return v.toString(16); } ); }, connectWebSocket() { const wsUrl = 'http://58.246.234.210:7860/api/v1/run/2f1faff2-99df-4821-87d6-43d693084c4b?stream=false' // 这里做一个简单的鉴权,只有符合条件的鉴权才能握手成功 // 移除这里的fetch调用,改用WebSocket this.websocket = new WebSocket(wsUrl); /* this.websocket.onerror = (event) => { console.error("WebSocket 连接错误:", event); }; */ this.websocket.onclose = (event) => { console.log("WebSocket 连接关闭:", event); }; this.websocket.onopen = (event) => { console.log("WebSocket 连接已建立:", event); }; this.websocket.onmessage = (event) => { // 解析收到的消息 const result = JSON.parse(event.data); // 检查消息是否完结 if (result.done) { this.messages[this.messages.length - 1].done = true; return; } if (this.messages[this.messages.length - 1].done) { // 添加新的消息 this.messages.push({ time: Date.now(), message: result.content, messageType: "TEXT", user: "bot", done: false, }); } else { // 更新最后一条消息 let lastMessage = this.messages[this.messages.length - 1]; lastMessage.message += result.content; this.messages[this.messages.length - 1] = lastMessage; } }; }, async sendMessage() { if(this.$route.name=='ai'){ if(!sessionStorage.getItem('AIData')){ this.showLoginModal=true return } } const chatId = this.getUuid(); const wsUrl =this.wsUrl//'http://58.246.234.210:7860/api/v1/run/3ef7369e-b617-40d2-9e2e-54f3ee76ed2e?stream=false' let message = this.userInput.trim(); if (message) { // Markdown换行:在每个换行符之前添加两个空格 message = message.replace(/(\r\n|\r|\n)/g, " \n"); this.messages.push({ time: Date.now(), message: message, messageType: "TEXT", user: "user", done: true, }); this.userInput = ""; // 添加机器人的响应消息(初始为空) const botMessage = { user: "bot", messageType: "TEXT", message: "", html: "", time: Date.now(), done: false, }; this.messages.push(botMessage); // 开始模拟思考 const thinkingPromise = this.simulateThinking(botMessage); // 通过 HTTP 发送消息 try { ///send-message const response = await fetch(`${wsUrl}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ chatId: chatId, input_value: message, output_type: "chat", input_type: "chat", tweaks:this.tweaks /* { "ChatInput-U82Vu": {}, "Prompt-b8Z1E": {}, "OllamaModel-z9SVx": {}, "Milvus-l0vvr": {}, "OllamaEmbeddings-4Ml5q": {}, "ParseData-LM8yW": {}, "ParseData-jVoZg": {}, "APIRequest-OnZHl": {}, "ParseData-fH1vY": {}, "ChatOutput-ybzb9": {} } */, }), }); if (!response.ok) { const errorText = await response.text(); // 获取错误文本 throw new Error( `HTTP error! status: ${response.status}, message: ${errorText}` ); } const result = await response.json(); // 等待思考动画完成 await thinkingPromise; if(this.$route.name !== 'ai'){ // 提取 additional_input 数据 const additionalInput = result.outputs?.[0]?.outputs?.[0]?.results?.message?.data?.additional_input; // 处理字符串,提取 ID 值 this.idArray = additionalInput.split('\n') // 按换行符分割 .map(line => line.trim()) // 去除每行首尾空白 .filter(line => line.startsWith('ID:')) // 只保留以 'ID:' 开头的行 .map(line => line.substring(3).trim()); // 提取 'ID:' 后面的内容并去除空白 // 创建符合要求格式的对象 const idObject = { ids: this.idArray }; console.log("ID Object:", idObject); // 调用方法来获取 Minio URL,传递新的 idObject await this.getMinioUrls(idObject); } this.handleResponse(result, botMessage); } catch (error) { console.error("Error sending message:", error); // 如果发生错误,停止思考动画 botMessage.done = true; botMessage.message = "抱歉,发生了错误,请稍后再试。"; } } }, async getMinioUrls(idObject) { this.minioUrls = []; // 清空之前的 URL try { console.log("Sending to backend:", JSON.stringify(idObject)); const response = await axios.post('http://58.246.234.210:8084/milvus/getMinioURl', idObject, { headers: { 'Content-Type': 'application/json' } }); if (response.status === 200 && response.data) { this.minioUrls = response.data.data; // 假设返回的是 URL 数组 console.log("Received Minio URLs:", this.minioUrls); } else { console.error('Failed to get Minio URLs'); } } catch (error) { console.error('Error fetching Minio URLs:', error); if (error.response) { console.error('Response data:', error.response.data); console.error('Response status:', error.response.status); console.error('Response headers:', error.response.headers); } else if (error.request) { console.error('No response received:', error.request); } else { console.error('Error message:', error.message); } } }, async simulateThinking(message) { this.isThinking = true; const thinkingTime = Math.random() * 1000 + 500; // 思考时间为 0.5-1.5 秒 const dotInterval = 200; // 每 200ms 更新一次点 const updateDots = () => { this.thinkingDots += '.'; if (this.thinkingDots.length > 3) { this.thinkingDots = ''; } message.message = this.thinkingDots; }; const intervalId = setInterval(updateDots, dotInterval); await new Promise(resolve => setTimeout(resolve, thinkingTime)); clearInterval(intervalId); this.isThinking = false; this.thinkingDots = ''; message.message = ''; }, async handleResponse(value, existingMessage) { const data = value.outputs[0].outputs[0].results.message; existingMessage.messageType = data.text_key; existingMessage.time = data.timestamp; existingMessage.message = ''; let mainText = data.text; let sourceText = ''; if (this.$route.name !== 'ai' && this.minioUrls && this.minioUrls.length > 0) { sourceText = "\n\n

相关资料来源:

    "; this.minioUrls.forEach((url, index) => { sourceText += `
  1. ${url.object_name}
  2. `; }); sourceText += "
"; } // 先进行主要内容的打字效果 await this.typeMessage(existingMessage, mainText); // 直接添加资料来源部分 if (sourceText) { existingMessage.message += sourceText; existingMessage.html = this.renderMarkdown(existingMessage.message); } }, typeMessage(message, fullText) { return new Promise(resolve => { const delay = 30; let i = 0; const typeChar = () => { if (i < fullText.length) { message.message += fullText[i]; i++; setTimeout(typeChar, delay); } else { message.done = true; message.html = this.renderMarkdown(message.message); resolve(); } }; typeChar(); }); }, renderMarkdown(rawMarkdown) { const md = new MarkdownIt({ html: true, breaks: true, linkify: true }); // 自定义链接渲染规则 md.renderer.rules.link_open = (tokens, idx, options, env, self) => { const token = tokens[idx]; const hrefIndex = token.attrIndex('href'); const href = token.attrs[hrefIndex][1]; return ``; }; md.renderer.rules.link_close = () => ''; const renderedHtml = md.render(rawMarkdown); // 使用 setTimeout 来确保 DOM 更新后再添加事件监听器 setTimeout(() => { const links = document.querySelectorAll('.custom-link'); links.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const href = link.getAttribute('data-href'); window.open(href, '_blank'); }); }); }, 0); return renderedHtml; }, handleKeydown(event) { // Check if 'Enter' is pressed without the 'Alt' key if (event.key === "Enter" && !(event.shiftKey || event.altKey)) { event.preventDefault(); // Prevent the default action to avoid line break in textarea this.sendMessage(); } else if (event.key === "Enter" && event.altKey) { // Allow 'Alt + Enter' to insert a newline const cursorPosition = event.target.selectionStart; const textBeforeCursor = this.userInput.slice(0, cursorPosition); const textAfterCursor = this.userInput.slice(cursorPosition); // Insert the newline character at the cursor position this.userInput = textBeforeCursor + "\n" + textAfterCursor; // Move the cursor to the right after the inserted newline this.$nextTick(() => { event.target.selectionStart = cursorPosition + 1; event.target.selectionEnd = cursorPosition + 1; }); } }, beforeDestroy() { if (this.websocket) { this.websocket.close(); } }, }, updated() { const messagesContainer = this.$el.querySelector(".messages"); messagesContainer.scrollTop = messagesContainer.scrollHeight; }, };