import LoginModal from "@/components/LoginModal"; import MarkdownIt from "markdown-it"; import { pcInnerAi,getMinioURl } from "@/api/api"; import {modelList,listBuckets,selectTypeList,getBucketContents,configSave,configList,configDelete} from "@/api/knowledge" 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',//文件 AIform:{ chat_name:'', modelLibrary: 'ollama', model_type: 'chat', model_name: '', knowledge_base_names: [], document_directories: [], documents: [], temperature: 0.7, max_tokens: 150, top_p: 1.0, frequency_penalty: 0.0, presence_penalty: 0.0, response_format: "text", context_window: 2048, user_id: "user123", session_id: "session456", language: "en", timeout: 30, role_name: "Admin", role_description: "", role_permissions: ["read", "write", "delete"] }, /* 模型库 */ modelList:[], /* 模型类型 */ modelTypeList:[], /* 模型名称 */ modelNameList:[], /* 知识库 */ kneList:[], /* 文档目录 */ directoryList: [], /* 文档 */ documentList: [], bucket_id:'', rules: { chat_name: [ { required: true, message: '请填写应名称', trigger: 'blur' } ], model_name: [ { required: true, message: '请选择模型名称', trigger: 'change' } ], knowledge_base_names: [ { required: true, message: '请选择至少一个知识库', trigger: 'change' } ], /* document_directories: [ { required: true, message: '请选择文档目录', trigger: 'change' } ], */ documents: [ { required: true, message: '请选择至少一个文档', trigger: 'change' } ] }, chatDialogVisible: false, /* 应用列表 */ knowledgeBases: [], currentChat: {}, /* 修改弹窗 */ editDialogVisible: false, editForm: { // 复制 AIform 的结构,但初始值为空 chat_name: '', modelLibrary: 'ollama', model_type: 'chat', model_name: '', knowledge_base_names: [], document_directories: [], documents: [], temperature: 0.7, max_tokens: 150, top_p: 1.0, frequency_penalty: 0.0, presence_penalty: 0.0, response_format: "text", context_window: 2048, user_id: "user123", session_id: "session456", language: "en", timeout: 30, role_name: "Admin", role_description: "", role_permissions: ["read", "write", "delete"] }, editDirectoryList: [], editDocumentList: [], }; }, 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 }) } /* 获取模型列表 */ this.init() }, computed: { getKnowledgeBaseNames() { const names = this.AIform.knowledge_base_names.map(id => this.kneList.find(item => item.id === id)?.name || id ); if (names.length <= 2) { return names.join(', '); } else { return `${names[0]}, ${names[1]} 等${names.length}个`; } }, getDocumentNames() { const names = this.AIform.documents.map(id => this.documentList.find(item => item.id === id)?.name || id ); if (names.length <= 2) { return names.join(', '); } else { return `${names[0]}, ${names[1]} 等${names.length}个`; } } }, methods: { /* 删除 */ deleteApplication(card) { this.$confirm('确定要删除这个应用吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { // 调用删除 API configDelete({id:card.id}).then(response => { if (response.status==200) { this.$message({ type: 'success', message: '删除成功!' }); // 从列表中移除已删除的应用 /* const index = this.knowledgeBases.findIndex(kb => kb.id === card.id); if (index > -1) { this.knowledgeBases.splice(index, 1); } */ this.fetchApplicationList(); } else { this.$message.error('删除失败: ' + response.message); } }).catch(error => { console.error('删除应用时出错:', error); this.$message.error('删除失败,请稍后重试'); }); }).catch(() => { this.$message({ type: 'info', message: '已取消删除' }); }); }, handleEditKnowledgeBaseChange(val) { // 重置目录和文档选择 this.editForm.document_directories = []; this.editForm.documents = []; this.editDocumentList = []; // 加载新选择的知识库对应的目录列表 this.loadEditDirectoryList(val); }, handleEditDirectoryChange(val) { // 重置文档选择 this.editForm.documents = []; // 加载选中目录对应的文档列表 this.loadEditDocumentList(val); }, /* 编辑 */ editApplication(card) { // 根据 card 的数据填充 editForm this.editForm = JSON.parse(JSON.stringify(card)); // 深拷贝以避免直接修改原对象 this.editForm.knowledge_base_names = this.editForm.knowledge_base_names.map(name => { const kb = this.kneList.find(kb => kb.name === name); return kb ? kb.id : name; // 如果找不到对应的知识库,保留原名称 }); this.editDialogVisible = true; console.log(this.editForm); // 加载知识库对应的目录列表 this.loadEditDirectoryList(this.editForm.knowledge_base_names); }, handleEditDialogClose() { this.$refs.editFormRef.resetFields(); this.editDirectoryList = []; this.editDocumentList = []; }, async loadEditDirectoryList(val) { this.editDirectoryList = []; // 清空现有目录列表 let totalDocuments = 0; let otherFolderCount = 0; for (const kbName of val) { // 根据知识库名称找到对应的 ID const kbId = this.kneList.find(kb => kb.name === kbName)?.id; /* if (!kbId) { console.error(`No matching knowledge base found for name: ${kbName}`); continue; } */ const typeForm = { page: 1, pageSize: 9999, kb_id: kbName, }; try { const res = await selectTypeList(typeForm); if (res.data) { this.editDirectoryList = [...new Set([...this.editDirectoryList, ...res.data.dataList])]; res.data.dataList.forEach(folder => { if (folder.id === "other") { otherFolderCount += folder.document_count || 0; } else { totalDocuments += folder.document_count || 0; } }); } } catch (error) { console.error(`Error loading directory list for kb_id ${kbId}:`, error); } } this.editDirectoryList.unshift({ id: "001", name: "全部", document_count: totalDocuments + otherFolderCount, }); }, loadEditDocumentList(val) { // 找到选中目录的名称 const selectedDirectoryName = val[0]; // 从 kneList 中找到对应的知识库 const selectedKnowledgeBase = this.kneList.find(kb => kb.name === this.editForm.knowledge_base_names[0] ); /* if (!selectedKnowledgeBase) { console.error('No matching knowledge base found'); return; } */ let queryForm = { page: 1, pageSize: 9999, bucket_id: this.editForm.knowledge_base_names[0], doc_type_id: selectedDirectoryName === '全部' ? 0 : this.getDirectoryIdByName(selectedDirectoryName), }; getBucketContents(queryForm).then(res => { console.log(res); this.editDocumentList = res.data.documents; }); }, // 添加一个辅助方法来根据目录名称获取目录ID getDirectoryIdByName(directoryName) { const directory = this.editDirectoryList.find(dir => dir.name === directoryName); return directory ? directory.id : null; }, submitEdit() { this.$refs.editFormRef.validate(async (valid) => { if (valid) { try { const convertedForm = { ...this.editForm }; console.log(this.editForm.documents); // 转换知识库、文档目录和文档的ID为名称 convertedForm.knowledge_base_names = this.safeGetNamesByIds(this.editForm.knowledge_base_names, this.kneList); /* convertedForm.documents = this.safeGetNamesByIds(this.editForm.documents, this.editDocumentList); */ convertedForm.document_directories = this.safeGetNamesByIds(this.editForm.document_directories, this.editDirectoryList); if (convertedForm.document_directories.length === 0) { convertedForm.document_directories = ['全部']; } console.log('Converted form for edit:', convertedForm); const response = await axios.post(`${process.env.VUE_APP_BASE_API}/chatbot/configuration/update/`, convertedForm, { headers: { 'Content-Type': 'application/json' } }); if (response.status === 200) { this.$message.success('应用更新成功'); this.editDialogVisible = false; this.fetchApplicationList(); } else { this.$message.error(response.data.message || '应用更新失败'); } } catch (error) { console.error('Error updating application:', error); this.$message.error('应用更新失败,请稍后重试'); } } else { this.$message.error('请填写所有必填字段'); return false; } }); }, // 添加一个方法来获取完整的应用配置 async fetchFullApplicationConfig(appId) { try { const response = await axios.get(`${process.env.VUE_APP_BASE_API}/chatbot/configDetail/${appId}`); if (response.status === 200 && response.data.code === 200) { return response.data.data; } else { throw new Error(response.data.message || '获取应用配置失败'); } } catch (error) { console.error('Error fetching application config:', error); this.$message.error('获取应用配置失败,请稍后重试'); return null; } }, // 实现获取应用列表的方法 async fetchApplicationList() { try { const response = await configList(); if (response.status === 200) { this.knowledgeBases = response.data; // 假设返回的数据结构符合要求 } else { throw new Error(response.data.message || '获取应用列表失败'); } } catch (error) { console.error('Error fetching application list:', error); this.$message.error('获取应用列表失败,请稍后重试'); } }, /* */ openChatDialog(card) { console.log(card); this.currentChat = card; this.chatDialogVisible = true; // 可能需要在这里初始化聊天记录或执行其他操作 this.messages = []; // 清空之前的聊天记录 // 可以添加一个欢迎消息 this.messages.push({ user: "bot", messageType: "TEXT", message: `欢迎使用 ${card.name} 聊天应用`, html: "", time: "", done: true, }); }, /* 关闭 */ handleCloseDialog(done) { // 在这里可以添加关闭前的确认逻辑 this.$confirm('确认关闭?') .then(_ => { done(); }) .catch(_ => {}); }, /* 点击应用 */ getFileTypeIcon(type) { const iconMap = { word: 'el-icon-document', excel: 'el-icon-tickets', pdf: 'el-icon-document-copy' }; return iconMap[type] || 'el-icon-document'; }, /* 生成引用 */ generateApplication() { this.$refs.AIformRef.validate(async (valid) => { if (valid) { try { // 创建一个新对象来存储转换后的数据 const convertedForm = { ...this.AIform }; // 将知识库 ID 转换为名称 convertedForm.knowledge_base_names = this.safeGetNamesByIds(this.AIform.knowledge_base_names, this.kneList); // 将文档 ID 转换为名称 convertedForm.documents = this.safeGetNamesByIds(this.AIform.documents, this.documentList); // 将文档目录 ID 转换为名称,如果为空则传递 '全部' convertedForm.document_directories = this.safeGetNamesByIds(this.AIform.document_directories, this.directoryList); if (convertedForm.document_directories.length === 0) { convertedForm.document_directories = ['全部']; } console.log('Converted form:', convertedForm); // 使用 axios 发送 POST 请求 const response = await axios.post(`${process.env.VUE_APP_BASE_API}/chatbot/configSave/`, convertedForm, { headers: { 'Content-Type': 'application/json' } }); if (response.status === 200) { this.$message.success('应用生成成功'); this.AIform={} this.fetchApplicationList(); // 可选:重定向到新应用或更新 UI } else { this.$message.error(response.data.message || '应用生成失败'); } } catch (error) { console.error('Error generating application:', error); this.$message.error('应用生成失败,请稍后重试'); } } else { this.$message.error('请填写所有必填字段'); return false; } }); }, // 安全的辅助方法:通过 ID 数组获取名称数组 safeGetNamesByIds(ids, list) { if (!Array.isArray(ids)) { console.warn('Expected an array of ids, but received:', ids); return []; } return ids.map(id => { if (id === '001') { return '全部'; } const item = list.find(item => item.id === id); return item ? item.name : ''; }).filter(name => name !== ''); }, /* 监听联动 */ handleKnowledgeBaseChange(val) { // 重置目录和文档选择 this.AIform.document_directories = []; this.AIform.documents = []; this.documentList = []; this.bucket_id=val[0] // 根据选中的知识库加载目录列表 // 这里需要调用后端 API 来获取目录列表 this.loadDirectoryList(val); }, handleDirectoryChange(val) { // 重置文档选择 this.AIform.documents = []; // 根据选中的目录加载文档列表 // 这里需要调用后端 API 来获取文档列表 this.loadDocumentList(val); }, async loadDirectoryList(val) { this.directoryList = []; // 清空现有目录列表 let totalDocuments = 0; let otherFolderCount = 0; for (const kbId of val) { const typeForm = { page: 1, pageSize: 9999, kb_id: kbId, }; try { const res = await selectTypeList(typeForm); // 假设 res.data 包含目录列表 if (res.data) { // 将新的目录添加到列表中,避免重复 this.directoryList = [...new Set([...this.directoryList, ...res.data.dataList])]; console.log(res.data.dataList); // 计算总文档数和其他文件夹数量 res.data.dataList.forEach(folder => { if (folder.id === "other") { otherFolderCount += folder.document_count || 0; } else { totalDocuments += folder.document_count || 0; } }); } } catch (error) { console.error(`Error loading directory list for kb_id ${kbId}:`, error); // 可以在这里添加错误处理,比如显示一个错误提示 } } // 在列表开头插入"全部"选项 this.directoryList.unshift({ id: "001", name: "全部", /* document_count: totalDocuments + otherFolderCount, */ }); }, loadDocumentList(val) { console.log(val); let queryForm={ page: 1, pageSize: 9999, bucket_id: this.bucket_id, doc_type_id: val=='001'?0 :val, } getBucketContents(queryForm).then(res=>{ this.documentList=res.data.documents }) }, /* 聊天记录 */ 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') { event.preventDefault(); // 阻止默认行为 const href = event.target.href; localStorage.setItem('href', href); // 使用 window.open 打开新标签页 window.open('#/preview', '_blank'); } }, 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