ChatBox.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071
  1. import LoginModal from "@/components/LoginModal";
  2. import MarkdownIt from "markdown-it";
  3. import { pcInnerAi,getMinioURl } from "@/api/api";
  4. import {modelList,listBuckets,selectTypeList,getBucketContents,configSave,configList,configDelete,application_types,set_default} from "@/api/knowledge"
  5. import axios from 'axios';
  6. const md = new MarkdownIt();
  7. export default {
  8. components: {
  9. LoginModal,
  10. },
  11. data() {
  12. return {
  13. applicationDialogVisible: false,
  14. isThinking: false,
  15. thinkingDots: '',
  16. messages: [
  17. {
  18. user: "bot",
  19. messageType: "TEXT",
  20. message: "欢迎使用轻良智能AI助理",
  21. html: "",
  22. time: "",
  23. done: true,
  24. },
  25. ],
  26. generating: false,
  27. userInput: "",
  28. websocket: null,
  29. wsUrl:'http://58.246.234.210:7860/api/v1/run/3ef7369e-b617-40d2-9e2e-54f3ee76ed2e?stream=false',
  30. tweaks:{},
  31. showLoginModal: false,
  32. isLoggedIn: false, // 添加登录状态标志
  33. idArray: [],
  34. minioUrls: [], // 新增:用于存储 Minio URL
  35. AImodel:'1',//模型
  36. AIknowledgeBase:"1",//知识库
  37. AIFile:'1',//文件
  38. AIform:{
  39. chat_name:'',
  40. modelLibrary: 'ollama',
  41. model_type: 'chat',
  42. model_name: '',
  43. knowledge_base_names: [],
  44. document_directories: [],
  45. documents: [],
  46. temperature: 0.7,
  47. max_tokens: 150,
  48. top_p: 1.0,
  49. frequency_penalty: 0.0,
  50. presence_penalty: 0.0,
  51. response_format: "text",
  52. context_window: 2048,
  53. user_id: "user123",
  54. session_id: "session456",
  55. language: "en",
  56. timeout: 30,
  57. role_name: "Admin",
  58. role_description: "",
  59. role_permissions: "",
  60. custom_variables:"",
  61. custom_prompt:'',
  62. application_type: "",
  63. is_default: false,
  64. generate_new_api_key: true
  65. },
  66. /* 模型库 */
  67. modelList:[],
  68. /* 模型类型 */
  69. modelTypeList:[],
  70. /* 模型名称 */
  71. modelNameList:[],
  72. /* 知识库 */
  73. kneList:[],
  74. /* 文档目录 */
  75. directoryList: [],
  76. /* 文档 */
  77. documentList: [],
  78. bucket_id:'',
  79. rules: {
  80. chat_name: [
  81. { required: true, message: '请填写应名称', trigger: 'blur' }
  82. ],
  83. model_name: [
  84. { required: true, message: '请选择模型名称', trigger: 'change' }
  85. ],
  86. knowledge_base_names: [
  87. { required: true, message: '请选择至少一个知识库', trigger: 'change' }
  88. ],
  89. /* document_directories: [
  90. { required: true, message: '请选择文档目录', trigger: 'change' }
  91. ], */
  92. documents: [
  93. { required: true, message: '请选择至少一个文档', trigger: 'change' }
  94. ]
  95. },
  96. chatDialogVisible: false,
  97. /* 应用列表 */
  98. knowledgeBases: [],
  99. currentChat: {},
  100. /* 修改弹窗 */
  101. editDialogVisible: false,
  102. editForm: {
  103. // 复制 AIform 的结构,但初始值为空
  104. chat_name: '',
  105. modelLibrary: 'ollama',
  106. model_type: 'chat',
  107. model_name: '',
  108. knowledge_base_names: [],
  109. document_directories: [],
  110. documents: [],
  111. temperature: 0.7,
  112. max_tokens: 150,
  113. top_p: 1.0,
  114. frequency_penalty: 0.0,
  115. presence_penalty: 0.0,
  116. response_format: "text",
  117. context_window: 2048,
  118. user_id: "user123",
  119. session_id: "session456",
  120. language: "en",
  121. timeout: 30,
  122. role_name: "Admin",
  123. role_description: "",
  124. role_permissions:"['read', 'write', 'delete']",
  125. custom_variables:"",
  126. custom_prompt:'',
  127. application_type: "",
  128. is_default: false,
  129. generate_new_api_key: true
  130. },
  131. editDirectoryList: [],
  132. editDocumentList: [],
  133. /* 查看 */
  134. viewDialogVisible: false,
  135. viewForm: null,
  136. session_id:"",
  137. /* 配置类型列表 */
  138. appTypeList:[]
  139. };
  140. },
  141. created() {
  142. // this.connectWebSocket();
  143. },
  144. mounted() {
  145. /* */
  146. if(this.$route.name=='ai'){
  147. /* 外部知识库 */
  148. const AIData=JSON.parse(sessionStorage.getItem("AIData"))
  149. this.wsUrl=AIData.data.url
  150. this.tweaks=AIData.data.tweaks
  151. }else{
  152. /* 内部知识库 */
  153. pcInnerAi().then(res=>{
  154. if(res.status!==200) return
  155. this.wsUrl=res.data.url
  156. this.tweaks=res.data.tweaks
  157. })
  158. }
  159. /* 获取模型列表 */
  160. this.init()
  161. },
  162. computed: {
  163. getKnowledgeBaseNames() {
  164. const names = this.AIform.knowledge_base_names.map(id =>
  165. this.kneList.find(item => item.id === id)?.name || id
  166. );
  167. if (names.length <= 2) {
  168. return names.join(', ');
  169. } else {
  170. return `${names[0]}, ${names[1]} 等${names.length}个`;
  171. }
  172. },
  173. getDocumentNames() {
  174. const names = this.AIform.documents.map(id =>
  175. this.documentList.find(item => item.id === id)?.name || id
  176. );
  177. if (names.length <= 2) {
  178. return names.join(', ');
  179. } else {
  180. return `${names[0]}, ${names[1]} 等${names.length}个`;
  181. }
  182. },
  183. isDocumentSelectDisabled() {
  184. return !this.AIform.knowledge_base_names.length ||this.AIform.document_directories=='';
  185. }
  186. },
  187. methods: {
  188. /* 检索知识库名称 */
  189. getKnowledgeBaseNamesForView(knowledgeBaseIds) {
  190. return knowledgeBaseIds.map(id => {
  191. const knowledgeBase = this.kneList.find(kb => kb.id === id);
  192. return knowledgeBase ? knowledgeBase.name : id;
  193. }).join(", ");
  194. },
  195. /* 设为默认 */
  196. setDefault(val){
  197. set_default({id:val.id}).then(res=>{
  198. })
  199. },
  200. /* 重置表单 */
  201. resetForm() {
  202. if (this.$refs.AIformRef) {
  203. this.$refs.AIformRef.resetFields();
  204. }
  205. this.AIform = {
  206. chat_name: '',
  207. modelLibrary: 'ollama',
  208. model_type: 'chat',
  209. model_name: '',
  210. knowledge_base_names: [],
  211. document_directories: [],
  212. documents: [],
  213. temperature: 0.7,
  214. max_tokens: 150,
  215. top_p: 1.0,
  216. frequency_penalty: 0.0,
  217. presence_penalty: 0.0,
  218. response_format: "",
  219. context_window: 2048,
  220. user_id: "",
  221. session_id: "",
  222. language: "en",
  223. timeout: 30,
  224. role_name: "Admin",
  225. role_description: "",
  226. role_permissions: "['read', 'write', 'delete']",
  227. custom_variables:"",
  228. custom_prompt:'',
  229. application_type: "",
  230. is_default: false,
  231. generate_new_api_key: true
  232. };
  233. },
  234. /* 新增聊天应用 */
  235. addApplication() {
  236. this.resetForm();
  237. /*console.log(this.AIform); */
  238. this.$router.push({
  239. path: "/knowledge/appConfig/index",
  240. });
  241. /* this.applicationDialogVisible = true; */
  242. },
  243. handleCloseApplicationDialog(done) {
  244. this.$confirm('确认关闭?未保存的更改将会丢失。')
  245. .then(_ => {
  246. this.resetForm();
  247. done();
  248. })
  249. .catch(_ => {});
  250. },
  251. cancelApplication() {
  252. this.$confirm('确认取消?未保存的更改将会丢失。')
  253. .then(_ => {
  254. this.resetForm();
  255. this.applicationDialogVisible = false;
  256. })
  257. .catch(_ => {});
  258. },
  259. /* 查看 */
  260. viewApplication(card) {
  261. console.log(card);
  262. this.viewForm = { ...card };
  263. this.viewDialogVisible = true;
  264. },
  265. /* 删除 */
  266. deleteApplication(card) {
  267. this.$confirm('确定要删除这个应用吗?', '提示', {
  268. confirmButtonText: '确定',
  269. cancelButtonText: '取消',
  270. type: 'warning'
  271. }).then(() => {
  272. // 调用删除 API
  273. configDelete({id:card.id}).then(response => {
  274. if (response.status==200) {
  275. this.$message({
  276. type: 'success',
  277. message: '删除成功!'
  278. });
  279. // 从列表中移除已删除的应用
  280. /* const index = this.knowledgeBases.findIndex(kb => kb.id === card.id);
  281. if (index > -1) {
  282. this.knowledgeBases.splice(index, 1);
  283. } */
  284. this.fetchApplicationList();
  285. } else {
  286. this.$message.error('删除失败: ' + response.message);
  287. }
  288. }).catch(error => {
  289. console.error('删除应用时出错:', error);
  290. this.$message.error('删除失败,请稍后重试');
  291. });
  292. }).catch(() => {
  293. this.$message({
  294. type: 'info',
  295. message: '已取消删除'
  296. });
  297. });
  298. },
  299. handleEditKnowledgeBaseChange(val) {
  300. // 重置目录和文档选择
  301. this.editForm.document_directories = [];
  302. this.editForm.documents = [];
  303. this.editDocumentList = [];
  304. // 加载新选择的知识库对应的目录列表
  305. this.loadEditDirectoryList(val);
  306. },
  307. handleEditDirectoryChange(val) {
  308. // 重置文档选择
  309. this.editForm.documents = [];
  310. // 加载选中目录对应的文档列表
  311. this.loadEditDocumentList(val);
  312. },
  313. /* 编辑 */
  314. editApplication(card) {
  315. // 根据 card 的数据填充 editForm
  316. this.editForm = JSON.parse(JSON.stringify(card)); // 深拷贝以避免直接修改原对象
  317. // 处理 knowledge_base_names
  318. this.editForm.knowledge_base_names = this.editForm.knowledge_base_names.map(name => {
  319. const kb = this.kneList.find(kb => kb.name === name);
  320. return kb ? kb.id : name; // 如果找不到对应的知识库,保留原名称
  321. });
  322. // 处理 role_permissions
  323. if (this.editForm.role_permissions) {
  324. if (typeof this.editForm.role_permissions !== 'string') {
  325. try {
  326. this.editForm.role_permissions = JSON.stringify(this.editForm.role_permissions);
  327. } catch (error) {
  328. console.error('Error stringifying role_permissions:', error);
  329. this.editForm.role_permissions = '';
  330. }
  331. }
  332. } else {
  333. this.editForm.role_permissions = '';
  334. }
  335. // 处理 custom_variables
  336. if (this.editForm.custom_variables) {
  337. this.editForm.custom_variables = JSON.stringify(this.editForm.custom_variables);
  338. } else {
  339. this.editForm.custom_variables = "";
  340. }
  341. this.$router.push({
  342. path: "/knowledge/appConfig/index",
  343. query: { id: card.id,card:card },
  344. });
  345. /* this.editDialogVisible = true; */
  346. console.log(this.editForm);
  347. // 加载知识库对应的目录列表
  348. this.loadEditDirectoryList(this.editForm.knowledge_base_names);
  349. },
  350. handleEditDialogClose() {
  351. this.$refs.editFormRef.resetFields();
  352. this.editDirectoryList = [];
  353. this.editDocumentList = [];
  354. },
  355. async loadEditDirectoryList(val) {
  356. this.editDirectoryList = []; // 清空现有目录列表
  357. let totalDocuments = 0;
  358. let otherFolderCount = 0;
  359. for (const kbName of val) {
  360. // 根据知识库名称找到对应的 ID
  361. const kbId = this.kneList.find(kb => kb.name === kbName)?.id;
  362. /* if (!kbId) {
  363. console.error(`No matching knowledge base found for name: ${kbName}`);
  364. continue;
  365. } */
  366. const typeForm = {
  367. page: 1,
  368. pageSize: 9999,
  369. kb_id: kbName,
  370. };
  371. try {
  372. const res = await selectTypeList(typeForm);
  373. if (res.data) {
  374. this.editDirectoryList = [...new Set([...this.editDirectoryList, ...res.data.dataList])];
  375. res.data.dataList.forEach(folder => {
  376. if (folder.id === "other") {
  377. otherFolderCount += folder.document_count || 0;
  378. } else {
  379. totalDocuments += folder.document_count || 0;
  380. }
  381. });
  382. }
  383. } catch (error) {
  384. console.error(`Error loading directory list for kb_id ${kbId}:`, error);
  385. }
  386. }
  387. this.editDirectoryList.unshift({
  388. id: "001",
  389. name: "全部",
  390. document_count: totalDocuments + otherFolderCount,
  391. });
  392. },
  393. loadEditDocumentList(val) {
  394. // 找到选中目录的名称
  395. const selectedDirectoryName = val;
  396. const id= this.directoryList.find(el=>el.id==val)
  397. // 从 kneList 中找到对应的知识库
  398. const selectedKnowledgeBase = this.kneList.find(kb =>
  399. kb.id === this.editForm.knowledge_base_names[0]
  400. );
  401. /* if (!selectedKnowledgeBase) {
  402. console.error('No matching knowledge base found');
  403. return;
  404. } */
  405. let queryForm = {
  406. page: 1,
  407. pageSize: 9999,
  408. bucket_id:selectedKnowledgeBase.id,
  409. doc_type_id: selectedDirectoryName === '001' ? '' : this.getDirectoryIdByName(selectedDirectoryName),
  410. };
  411. getBucketContents(queryForm).then(res => {
  412. this.editDocumentList = res.data.documents;
  413. });
  414. },
  415. // 添加一个辅助方法来根据目录名称获取目录ID
  416. getDirectoryIdByName(directoryName) {
  417. const directory = this.editDirectoryList.find(dir => dir.name === directoryName);
  418. return directory ? directory.id : null;
  419. },
  420. submitEdit() {
  421. this.$refs.editFormRef.validate(async (valid) => {
  422. if (valid) {
  423. try {
  424. const convertedForm = { ...this.editForm };
  425. console.log(this.editForm.documents);
  426. // 转换知识库、文档目录和文档的ID为名称
  427. convertedForm.knowledge_base_names = this.safeGetNamesByIds(this.editForm.knowledge_base_names, this.kneList);
  428. /* convertedForm.documents = this.safeGetNamesByIds(this.editForm.documents, this.editDocumentList); */
  429. convertedForm.document_directories = this.safeGetNamesByIds(this.editForm.document_directories, this.editDirectoryList);
  430. if (convertedForm.document_directories.length === 0) {
  431. convertedForm.document_directories = ['全部'];
  432. }
  433. console.log('Converted form for edit:', convertedForm);
  434. const response = await axios.post(`${process.env.VUE_APP_BASE_API}/chatbot/configuration/update/`, convertedForm, {
  435. headers: {
  436. 'Content-Type': 'application/json'
  437. }
  438. });
  439. if (response.status === 200) {
  440. this.$message.success('应用更新成功');
  441. this.editDialogVisible = false;
  442. this.fetchApplicationList();
  443. } else {
  444. this.$message.error(response.data.message || '应用更新失败');
  445. }
  446. } catch (error) {
  447. console.error('Error updating application:', error);
  448. this.$message.error('应用更新失败,请稍后重试');
  449. }
  450. } else {
  451. this.$message.error('请填写所有必填字段');
  452. return false;
  453. }
  454. });
  455. },
  456. // 添加一个方法来获取完整的应用配置
  457. async fetchFullApplicationConfig(appId) {
  458. try {
  459. const response = await axios.get(`${process.env.VUE_APP_BASE_API}/chatbot/configDetail/${appId}`);
  460. if (response.status === 200 && response.data.code === 200) {
  461. return response.data.data;
  462. } else {
  463. throw new Error(response.data.message || '获取应用配置失败');
  464. }
  465. } catch (error) {
  466. console.error('Error fetching application config:', error);
  467. this.$message.error('获取应用配置失败,请稍后重试');
  468. return null;
  469. }
  470. },
  471. // 实现获取应用列表的方法
  472. async fetchApplicationList() {
  473. try {
  474. const response = await configList();
  475. if (response.status === 200) {
  476. this.knowledgeBases = response.data; // 假设返回的数据结构符合要求
  477. } else {
  478. throw new Error(response.data.message || '获取应用列表失败');
  479. }
  480. } catch (error) {
  481. console.error('Error fetching application list:', error);
  482. this.$message.error('获取应用列表失败,请稍后重试');
  483. }
  484. },
  485. /* */
  486. openChatDialog(card) {
  487. this.currentChat = card;
  488. this.chatDialogVisible = true;
  489. // 可能需要在这里初始化聊天记录或执行其他操作
  490. this.messages = []; // 清空之前的聊天记录
  491. // 可以添加一个欢迎消息
  492. this.messages.push({
  493. user: "bot",
  494. messageType: "TEXT",
  495. message: `欢迎使用 ${card.chat_name} 聊天应用`,
  496. html: "",
  497. time: "",
  498. done: true,
  499. });
  500. },
  501. /* 关闭 */
  502. handleCloseDialog(done) {
  503. // 在这里可以添加关闭前的确认逻辑
  504. this.$confirm('确认关闭?')
  505. .then(_ => {
  506. done();
  507. })
  508. .catch(_ => {});
  509. },
  510. /* 点击应用 */
  511. getFileTypeIcon(type) {
  512. const iconMap = {
  513. word: 'el-icon-document',
  514. excel: 'el-icon-tickets',
  515. pdf: 'el-icon-document-copy'
  516. };
  517. return iconMap[type] || 'el-icon-document';
  518. },
  519. /* 生成引用 */
  520. generateApplication() {
  521. this.$refs.AIformRef.validate(async (valid) => {
  522. if (valid) {
  523. try {
  524. // 创建一个新对象来存储转换后的数据
  525. const convertedForm = { ...this.AIform };
  526. // 将知识库 ID 转换为名称
  527. convertedForm.knowledge_base_names = this.safeGetNamesByIds(this.AIform.knowledge_base_names, this.kneList);
  528. // 将文档 ID 转换为名称
  529. convertedForm.documents = this.safeGetNamesByIds(this.AIform.documents, this.documentList);
  530. // 将文档目录 ID 转换为名称,如果为空则传递 '全部'
  531. convertedForm.document_directories = this.safeGetNamesByIds(this.AIform.document_directories, this.directoryList);
  532. if (convertedForm.document_directories.length === 0) {
  533. convertedForm.document_directories = ['全部'];
  534. }
  535. console.log('Converted form:', convertedForm);
  536. // 使用 axios 发送 POST 请求
  537. const response = await axios.post(`${process.env.VUE_APP_BASE_API}/chatbot/configCreart/`, convertedForm, {
  538. headers: {
  539. 'Content-Type': 'application/json'
  540. }
  541. });
  542. if (response.status === 200) {
  543. this.$message.success('应用生成成功');
  544. this.resetForm();
  545. this.fetchApplicationList();
  546. this.applicationDialogVisible = false; // 关闭弹窗
  547. // 可选:重定向到新应用或更新 UI
  548. } else {
  549. this.$message.error(response.data.message || '应用生成失败');
  550. }
  551. } catch (error) {
  552. console.error('Error generating application:', error);
  553. this.$message.error('应用生成失败,请稍后重试');
  554. }
  555. } else {
  556. this.$message.error('请填写所有必填字段');
  557. return false;
  558. }
  559. });
  560. },
  561. // 安全的辅助方法:通过 ID 数组获取名称数组
  562. safeGetNamesByIds(ids, list) {
  563. if (!Array.isArray(ids)) {
  564. console.warn('Expected an array of ids, but received:', ids);
  565. return [];
  566. }
  567. return ids.map(id => {
  568. if (id === '001') {
  569. return '全部';
  570. }
  571. const item = list.find(item => item.id === id);
  572. return item ? item.name : '';
  573. }).filter(name => name !== '');
  574. },
  575. /* 监听联动 */
  576. handleKnowledgeBaseChange(val) {
  577. // 重置目录和文档选择
  578. this.AIform.document_directories = [];
  579. this.AIform.documents = [];
  580. this.documentList = [];
  581. this.bucket_id=val[0]
  582. // 根据选中的知识库加载目录列表
  583. // 这里需要调用后端 API 来获取目录列表
  584. this.loadDirectoryList(val);
  585. },
  586. handleDirectoryChange(val) {
  587. // 重置文档选择
  588. this.AIform.documents = [];
  589. // 根据选中的目录加载文档列表
  590. // 这里需要调用后端 API 来获取文档列表
  591. this.loadDocumentList(val);
  592. },
  593. async loadDirectoryList(val) {
  594. this.directoryList = []; // 清空现有目录列表
  595. let totalDocuments = 0;
  596. let otherFolderCount = 0;
  597. for (const kbId of val) {
  598. const typeForm = {
  599. page: 1,
  600. pageSize: 9999,
  601. kb_id: kbId,
  602. };
  603. try {
  604. const res = await selectTypeList(typeForm);
  605. // 假设 res.data 包含目录列表
  606. if (res.data) {
  607. // 将新的目录添加到列表中,避免重复
  608. this.directoryList = [...new Set([...this.directoryList, ...res.data.dataList])];
  609. console.log(res.data.dataList);
  610. // 计算总文档数和其他文件夹数量
  611. res.data.dataList.forEach(folder => {
  612. if (folder.id === "other") {
  613. otherFolderCount += folder.document_count || 0;
  614. } else {
  615. totalDocuments += folder.document_count || 0;
  616. }
  617. });
  618. }
  619. } catch (error) {
  620. console.error(`Error loading directory list for kb_id ${kbId}:`, error);
  621. // 可以在这里添加错误处理,比如显示一个错误提示
  622. }
  623. }
  624. // 在列表开头插入"全部"选项
  625. this.directoryList.unshift({
  626. id: "001",
  627. name: "全部",
  628. /* document_count: totalDocuments + otherFolderCount, */
  629. });
  630. },
  631. loadDocumentList(val) {
  632. const id= this.directoryList.find(el=>el.id==val)
  633. let queryForm={
  634. page: 1,
  635. pageSize: 9999,
  636. bucket_id:val=='001'?this.bucket_id : id.kb_id,//this.bucket_id,
  637. doc_type_id: val=='001'?'' :val,
  638. }
  639. getBucketContents(queryForm).then(res=>{
  640. this.documentList=res.data.documents
  641. })
  642. },
  643. /* 聊天记录 */
  644. selectChat(index) {
  645. this.currentChatIndex = index;
  646. // Load the selected chat messages
  647. // This is where you would typically load the messages for the selected chat
  648. },
  649. newChat() {
  650. this.chatHistory.push({ title: `聊天 ${this.chatHistory.length + 1}`, preview: "新的聊天..." });
  651. this.currentChatIndex = this.chatHistory.length - 1;
  652. // Clear the current messages and start a new chat
  653. this.messages = [];
  654. },
  655. handleLinkClick(event) {
  656. if (event.target.tagName === 'A') {
  657. event.preventDefault(); // 阻止默认行为
  658. const href = event.target.href;
  659. localStorage.setItem('href', href);
  660. // 使用 window.open 打开新标签页
  661. window.open('#/preview', '_blank');
  662. }
  663. },
  664. handleLoginSuccess() {
  665. this.showLoginModal = false;
  666. this.isLoggedIn = true;
  667. // 登录成功后,可以继续发送消息
  668. this.sendMessage();
  669. },
  670. getUuid() {
  671. return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
  672. /[xy]/g,
  673. function (c) {
  674. var r = (Math.random() * 16) | 0,
  675. v = c == "x" ? r : (r & 0x3) | 0x8;
  676. return v.toString(16);
  677. }
  678. );
  679. },
  680. connectWebSocket() {
  681. const wsUrl = 'http://58.246.234.210:7860/api/v1/run/2f1faff2-99df-4821-87d6-43d693084c4b?stream=false'
  682. // 这里做一个简单的鉴权,只有符合条件的鉴权才能握手成功
  683. // 移除这里的fetch调用,改用WebSocket
  684. this.websocket = new WebSocket(wsUrl);
  685. /* this.websocket.onerror = (event) => {
  686. console.error("WebSocket 连接错误:", event);
  687. };
  688. */
  689. this.websocket.onclose = (event) => {
  690. console.log("WebSocket 连接关闭:", event);
  691. };
  692. this.websocket.onopen = (event) => {
  693. console.log("WebSocket 连接已建立:", event);
  694. };
  695. this.websocket.onmessage = (event) => {
  696. // 解析收到的消息
  697. const result = JSON.parse(event.data);
  698. // 检查消息是否完结
  699. if (result.done) {
  700. this.messages[this.messages.length - 1].done = true;
  701. return;
  702. }
  703. if (this.messages[this.messages.length - 1].done) {
  704. // 添加新的消息
  705. this.messages.push({
  706. time: Date.now(),
  707. message: result.content,
  708. messageType: "TEXT",
  709. user: "bot",
  710. done: false,
  711. });
  712. } else {
  713. // 更新最后一条消息
  714. let lastMessage = this.messages[this.messages.length - 1];
  715. lastMessage.message += result.content;
  716. this.messages[this.messages.length - 1] = lastMessage;
  717. }
  718. };
  719. },
  720. async sendMessage() {
  721. if(this.$route.name=='ai'){
  722. if(!sessionStorage.getItem('AIData')){
  723. this.showLoginModal=true
  724. return
  725. }
  726. }
  727. const chatId = this.getUuid();
  728. const wsUrl =`${process.env.VUE_APP_BASE_API}/chatbot/chat`//this.wsUrl//'http://58.246.234.210:7860/api/v1/run/3ef7369e-b617-40d2-9e2e-54f3ee76ed2e?stream=false'
  729. let message = this.userInput.trim();
  730. if (message) {
  731. // Markdown换行:在每个换行符之前添加两个空格
  732. message = message.replace(/(\r\n|\r|\n)/g, " \n");
  733. this.messages.push({
  734. time: Date.now(),
  735. message: message,
  736. messageType: "TEXT",
  737. user: "user",
  738. done: true,
  739. });
  740. this.userInput = "";
  741. // 添加机器人的响应消息(初始为空)
  742. const botMessage = {
  743. user: "bot",
  744. messageType: "TEXT",
  745. message: "",
  746. html: "",
  747. time: Date.now(),
  748. done: false,
  749. };
  750. this.messages.push(botMessage);
  751. // 开始模拟思考
  752. const thinkingPromise = this.simulateThinking(botMessage);
  753. // 通过 HTTP 发送消息
  754. try {
  755. ///send-message
  756. const response = await fetch(`${wsUrl}`, {
  757. method: "POST",
  758. headers: {
  759. "Content-Type": "application/json"
  760. },
  761. /* mode: 'no-cors', */
  762. /* credentials: "include", */
  763. body: JSON.stringify({
  764. /* chatId: chatId, */
  765. message: message,
  766. /* output_type: "chat",
  767. input_type: "chat", */
  768. chat_config_id:this.currentChat.id,
  769. user_id:this.$store.state.user.id,
  770. session_id:this.session_id||''
  771. /* tweaks:this.tweaks {
  772. "ChatInput-U82Vu": {},
  773. "Prompt-b8Z1E": {},
  774. "OllamaModel-z9SVx": {},
  775. "Milvus-l0vvr": {},
  776. "OllamaEmbeddings-4Ml5q": {},
  777. "ParseData-LM8yW": {},
  778. "ParseData-jVoZg": {},
  779. "APIRequest-OnZHl": {},
  780. "ParseData-fH1vY": {},
  781. "ChatOutput-ybzb9": {}
  782. } */,
  783. }),
  784. });
  785. const result = await response.json();
  786. this.session_id=result.data.session_id
  787. console.log(result.status);
  788. if (result.status!=='success') {
  789. const errorText = await response.text(); // 获取错误文本
  790. throw new Error(
  791. `HTTP error! status: ${response.status}, message: ${errorText}`
  792. );
  793. }
  794. // 等待思考动画完成
  795. await thinkingPromise;
  796. if(this.$route.name !== 'ai'){
  797. // 提取 additional_input 数据
  798. const additionalInput = result.data.milvus_ids//outputs?.[0]?.outputs?.[0]?.results?.message?.data?.additional_input;
  799. // 处理字符串,提取 ID 值
  800. this.idArray = additionalInput/* .split('\n') // 按换行符分割
  801. .map(line => line.trim()) // 去除每行首尾空白 */
  802. /* .filter(line => line.startsWith('ID:')) // 只保留以 'ID:' 开头的行
  803. .map(line => line.substring(3).trim()); // 提取 'ID:' 后面的内容并去除空白 */
  804. // 创建符合要求格式的对象
  805. const idObject = { ids: this.idArray };
  806. console.log("ID Object:", idObject);
  807. // 调用方法来获取 Minio URL,传递新的 idObject
  808. await this.getMinioUrls(idObject);
  809. }
  810. this.handleResponse(result, botMessage);
  811. } catch (error) {
  812. console.error("Error sending message:", error);
  813. // 如果发生错误,停止思考动画
  814. botMessage.done = true;
  815. botMessage.message = "抱歉,发生了错误,请稍后再试。";
  816. }
  817. }
  818. },
  819. async getMinioUrls(idObject) {
  820. this.minioUrls = []; // 清空之前的 URL
  821. try {
  822. console.log("Sending to backend:", JSON.stringify(idObject));
  823. const response = await axios.post(`${process.env.VUE_APP_BASE_API}/milvus/getMinioURl`, idObject, {
  824. headers: {
  825. 'Content-Type': 'application/json'
  826. }
  827. });
  828. if (response.status === 200 && response.data) {
  829. this.minioUrls = response.data.data; // 假设返回的是 URL 数组
  830. console.log("Received Minio URLs:", this.minioUrls);
  831. } else {
  832. console.error('Failed to get Minio URLs');
  833. }
  834. } catch (error) {
  835. console.error('Error fetching Minio URLs:', error);
  836. if (error.response) {
  837. console.error('Response data:', error.response.data);
  838. console.error('Response status:', error.response.status);
  839. console.error('Response headers:', error.response.headers);
  840. } else if (error.request) {
  841. console.error('No response received:', error.request);
  842. } else {
  843. console.error('Error message:', error.message);
  844. }
  845. }
  846. },
  847. async simulateThinking(message) {
  848. this.isThinking = true;
  849. const thinkingTime = Math.random() * 1000 + 500; // 思考时间为 0.5-1.5 秒
  850. const dotInterval = 200; // 每 200ms 更新一次点
  851. const updateDots = () => {
  852. this.thinkingDots += '.';
  853. if (this.thinkingDots.length > 3) {
  854. this.thinkingDots = '';
  855. }
  856. message.message = this.thinkingDots;
  857. };
  858. const intervalId = setInterval(updateDots, dotInterval);
  859. await new Promise(resolve => setTimeout(resolve, thinkingTime));
  860. clearInterval(intervalId);
  861. this.isThinking = false;
  862. this.thinkingDots = '';
  863. message.message = '';
  864. },
  865. async handleResponse(value, existingMessage) {
  866. const data = value.data.answer//outputs[0].outputs[0].results.message;
  867. existingMessage.messageType = data;
  868. /* existingMessage.time = data.timestamp; */
  869. existingMessage.message = '';
  870. let mainText = data;
  871. let sourceText = '';
  872. if (this.$route.name !== 'ai' && this.minioUrls && this.minioUrls.length > 0) {
  873. sourceText = "\n\n<div class='source-section'><h3>相关资料来源:</h3><ol>";
  874. this.minioUrls.forEach((url, index) => {
  875. sourceText += `<li><a href="${url.url}" target="_blank" class="source-link">${url.object_name}</a></li>`;
  876. });
  877. sourceText += "</ol></div>";
  878. }
  879. console.log(existingMessage, mainText);
  880. // 先进行主要内容的打字效果
  881. await this.typeMessage(existingMessage, mainText);
  882. // 直接添加资料来源部分
  883. if (sourceText) {
  884. existingMessage.message += sourceText;
  885. existingMessage.html = this.renderMarkdown(existingMessage.message);
  886. }
  887. },
  888. typeMessage(message, fullText) {
  889. return new Promise(resolve => {
  890. const delay = 30;
  891. let i = 0;
  892. const typeChar = () => {
  893. if (i < fullText.length) {
  894. message.message += fullText[i];
  895. i++;
  896. setTimeout(typeChar, delay);
  897. } else {
  898. message.done = true;
  899. message.html = this.renderMarkdown(message.message);
  900. resolve();
  901. }
  902. };
  903. typeChar();
  904. });
  905. },
  906. renderMarkdown(rawMarkdown) {
  907. const md = new MarkdownIt({
  908. html: true,
  909. breaks: true,
  910. linkify: true
  911. });
  912. // 自定义链接渲染规则
  913. md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
  914. const token = tokens[idx];
  915. const hrefIndex = token.attrIndex('href');
  916. const href = token.attrs[hrefIndex][1];
  917. return `<span class="custom-link" data-href="${href}">`;
  918. };
  919. md.renderer.rules.link_close = () => '</span>';
  920. const renderedHtml = md.render(rawMarkdown);
  921. // 使用 setTimeout 来确保 DOM 更新后再添加事件监听器
  922. setTimeout(() => {
  923. const links = document.querySelectorAll('.custom-link');
  924. links.forEach(link => {
  925. link.addEventListener('click', (e) => {
  926. e.preventDefault();
  927. const href = link.getAttribute('data-href');
  928. window.open(href, '_blank');
  929. });
  930. });
  931. }, 0);
  932. return renderedHtml;
  933. },
  934. handleKeydown(event) {
  935. // Check if 'Enter' is pressed without the 'Alt' key
  936. if (event.key === "Enter" && !(event.shiftKey || event.altKey)) {
  937. event.preventDefault(); // Prevent the default action to avoid line break in textarea
  938. this.sendMessage();
  939. } else if (event.key === "Enter" && event.altKey) {
  940. // Allow 'Alt + Enter' to insert a newline
  941. const cursorPosition = event.target.selectionStart;
  942. const textBeforeCursor = this.userInput.slice(0, cursorPosition);
  943. const textAfterCursor = this.userInput.slice(cursorPosition);
  944. // Insert the newline character at the cursor position
  945. this.userInput = textBeforeCursor + "\n" + textAfterCursor;
  946. // Move the cursor to the right after the inserted newline
  947. this.$nextTick(() => {
  948. event.target.selectionStart = cursorPosition + 1;
  949. event.target.selectionEnd = cursorPosition + 1;
  950. });
  951. }
  952. },
  953. beforeDestroy() {
  954. if (this.websocket) {
  955. this.websocket.close();
  956. }
  957. },
  958. /* 获取列表 */
  959. init(){
  960. /* 模型库 */
  961. modelList({model_type:'model'}).then(res=>{
  962. this.modelNameList=res.data
  963. })
  964. /* 知识库 */
  965. listBuckets({ user_id: this.$store.state.user.id }).then(res=>{
  966. this.kneList=res.data
  967. })
  968. /* 应用列表 */
  969. configList().then(res=>{
  970. this.knowledgeBases=res.data
  971. console.log(res);
  972. })
  973. /* 获取应用类型 */
  974. application_types().then(res=>{
  975. if(res.status!==200) return
  976. this.appTypeList=res.data.application_types
  977. })
  978. }
  979. },
  980. updated() {
  981. const messagesContainer = this.$el.querySelector(".messages");
  982. if(messagesContainer){
  983. messagesContainer.scrollTop = messagesContainer.scrollHeight;
  984. }
  985. },
  986. };