Browse Source

处理解析提示bug添加聊天弹窗拖拽

yangg 8 months ago
parent
commit
a85f2f145c
37 changed files with 1033 additions and 652 deletions
  1. 1 1
      .env.development
  2. 0 0
      dist/index.html
  3. 0 0
      dist/static/css/app.fce89cef.css
  4. 0 0
      dist/static/css/chunk-013c991d.6d459198.css
  5. 0 0
      dist/static/css/chunk-27283076.775a21e3.css
  6. 1 0
      dist/static/css/chunk-386d37cb.86b22fbc.css
  7. 1 0
      dist/static/css/chunk-43013872.97cc4264.css
  8. 0 1
      dist/static/css/chunk-6262d025.8f453586.css
  9. 0 1
      dist/static/css/chunk-ad361d30.ba3e57b5.css
  10. 0 0
      dist/static/css/chunk-b580a2ac.271f9712.css
  11. 0 0
      dist/static/css/chunk-cd009444.0f785ade.css
  12. 0 0
      dist/static/css/chunk-e61fad7e.ee22606a.css
  13. 0 0
      dist/static/js/app.524b079f.js
  14. 0 0
      dist/static/js/app.bf7f79cb.js
  15. 0 0
      dist/static/js/chunk-013c991d.7b5257d7.js
  16. 1 1
      dist/static/js/chunk-27283076.657f1d56.js
  17. 0 1
      dist/static/js/chunk-2d0d3705.838b020d.js
  18. 1 0
      dist/static/js/chunk-2d0d3705.ba054746.js
  19. 0 0
      dist/static/js/chunk-386d37cb.a644209c.js
  20. 0 0
      dist/static/js/chunk-3bf9b75a.353a51e8.js
  21. 0 0
      dist/static/js/chunk-43013872.f0617c10.js
  22. 0 0
      dist/static/js/chunk-57d45b95.ee9f6e14.js
  23. 0 0
      dist/static/js/chunk-ad361d30.87217e77.js
  24. 0 0
      dist/static/js/chunk-b580a2ac.fb270316.js
  25. 0 0
      dist/static/js/chunk-cd009444.4182e197.js
  26. 0 0
      dist/static/js/chunk-e61fad7e.9d892b96.js
  27. 46 37
      src/components/FilePreview/index.vue
  28. 1 1
      src/components/LoginModal/index.vue
  29. 56 23
      src/components/ai.vue
  30. 110 13
      src/components/chartIcon/index.vue
  31. 38 11
      src/components/webAi/js/ChatBox.js
  32. 1 1
      src/layout/components/Navbar.vue
  33. 126 45
      src/views/appConfig/index.vue
  34. 94 11
      src/views/knowledgeMenu/category/infoList.vue
  35. 69 64
      src/views/knowledgeMenu/category/knowledgeSet.vue
  36. 6 2
      src/views/login/Preview.vue
  37. 481 439
      src/views/login/aiIndex.vue

+ 1 - 1
.env.development

@@ -2,5 +2,5 @@
 ENV = 'development'
 port = 8080
 # base api
-VUE_APP_BASE_API = 'http://192.168.1.200:8084'
+VUE_APP_BASE_API = 'http://58.246.234.210:8084'
 #192.168.1.200

File diff suppressed because it is too large
+ 0 - 0
dist/index.html


File diff suppressed because it is too large
+ 0 - 0
dist/static/css/app.fce89cef.css


File diff suppressed because it is too large
+ 0 - 0
dist/static/css/chunk-013c991d.6d459198.css


+ 0 - 0
dist/static/css/chunk-05d6d838.775a21e3.css → dist/static/css/chunk-27283076.775a21e3.css


+ 1 - 0
dist/static/css/chunk-386d37cb.86b22fbc.css

@@ -0,0 +1 @@
+.project-search[data-v-568e32be]{display:-webkit-box;display:-ms-flexbox;display:flex;padding:10px;font-size:12px}.listClass[data-v-568e32be]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.excel-table[data-v-568e32be]{width:100%;overflow-y:auto}.excel-table table[data-v-568e32be]{border-collapse:collapse;width:100%;margin-bottom:20px}.excel-table td[data-v-568e32be],.excel-table th[data-v-568e32be]{border:1px solid #ddd;padding:8px;text-align:left;white-space:nowrap}.excel-table th[data-v-568e32be]{background-color:#f2f2f2;font-weight:700;position:sticky;top:0;z-index:1}.excel-table tr[data-v-568e32be]:nth-child(2n){background-color:#f9f9f9}.no-data[data-v-568e32be]{font-size:18px;color:#909399;text-align:center;margin-top:50px}.pagination[data-v-568e32be]{position:fixed;bottom:0;right:51%}.movable-dialog[data-v-568e32be]{position:absolute!important}[data-v-568e32be] .el-dialog__header{cursor:move}

+ 1 - 0
dist/static/css/chunk-43013872.97cc4264.css

@@ -0,0 +1 @@
+.header[data-v-74a68649]{margin-left:20px}.header h2[data-v-74a68649]{margin-bottom:0}.center[data-v-74a68649]{margin:10px}.center[data-v-74a68649] .el-form--inline .el-form-item{display:block}.center[data-v-74a68649] .el-input-number--medium{width:150px}.footer[data-v-74a68649]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin:0 100px 20px}[data-v-74a68649] .avatar-uploader .el-upload{border:1px dashed #d9d9d9;border-radius:6px;cursor:pointer;position:relative;overflow:hidden}.avatar-uploader .el-upload[data-v-74a68649]:hover{border-color:#409eff}.avatar-uploader-icon[data-v-74a68649]{font-size:28px;color:#8c939d;width:100px;height:100px;line-height:100px;text-align:center}.avatar[data-v-74a68649]{width:100px;height:100px;display:block}.hint[data-v-74a68649]{font-size:12px;color:#c0c4cc}

+ 0 - 1
dist/static/css/chunk-6262d025.8f453586.css

@@ -1 +0,0 @@
-.header[data-v-1b38362d]{margin-left:20px}.header h2[data-v-1b38362d]{margin-bottom:0}.center[data-v-1b38362d]{margin:10px}.center[data-v-1b38362d] .el-form--inline .el-form-item{display:block}.center[data-v-1b38362d] .el-input-number--medium{width:150px}.footer[data-v-1b38362d]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin:0 100px 20px}[data-v-1b38362d] .avatar-uploader .el-upload{border:1px dashed #d9d9d9;border-radius:6px;cursor:pointer;position:relative;overflow:hidden}.avatar-uploader .el-upload[data-v-1b38362d]:hover{border-color:#409eff}.avatar-uploader-icon[data-v-1b38362d]{font-size:28px;color:#8c939d;width:100px;height:100px;line-height:100px;text-align:center}.avatar[data-v-1b38362d]{width:100px;height:100px;display:block}

+ 0 - 1
dist/static/css/chunk-ad361d30.ba3e57b5.css

@@ -1 +0,0 @@
-.project-search[data-v-26a33ed4]{display:-webkit-box;display:-ms-flexbox;display:flex;padding:10px;font-size:12px}.listClass[data-v-26a33ed4]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.excel-table[data-v-26a33ed4]{width:100%;overflow-y:auto}.excel-table table[data-v-26a33ed4]{border-collapse:collapse;width:100%;margin-bottom:20px}.excel-table td[data-v-26a33ed4],.excel-table th[data-v-26a33ed4]{border:1px solid #ddd;padding:8px;text-align:left;white-space:nowrap}.excel-table th[data-v-26a33ed4]{background-color:#f2f2f2;font-weight:700;position:sticky;top:0;z-index:1}.excel-table tr[data-v-26a33ed4]:nth-child(2n){background-color:#f9f9f9}.no-data[data-v-26a33ed4]{font-size:18px;color:#909399;text-align:center;margin-top:50px}.pagination[data-v-26a33ed4]{position:fixed;bottom:0;right:51%}

+ 0 - 0
dist/static/css/chunk-6e20448e.271f9712.css → dist/static/css/chunk-b580a2ac.271f9712.css


File diff suppressed because it is too large
+ 0 - 0
dist/static/css/chunk-cd009444.0f785ade.css


File diff suppressed because it is too large
+ 0 - 0
dist/static/css/chunk-e61fad7e.ee22606a.css


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/app.524b079f.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/app.bf7f79cb.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-013c991d.7b5257d7.js


File diff suppressed because it is too large
+ 1 - 1
dist/static/js/chunk-27283076.657f1d56.js


+ 0 - 1
dist/static/js/chunk-2d0d3705.838b020d.js

@@ -1 +0,0 @@
-(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0d3705"],{"5d88":function(e,t,o){"use strict";o.r(t);var l=function(){var e=this,t=e.$createElement,o=e._self._c||t;return o("div",[o("file-preview",{attrs:{"file-url":e.url,"file-type":"docx"}})],1)},n=[],r=o("3be9"),u={props:{},components:{FilePreview:r["default"]},data:function(){return{url:""}},computed:{},watch:{},mounted:function(){this.url=localStorage.getItem("href"),console.log(localStorage.getItem("href"))},methods:{}},c=u,a=o("2877"),i=Object(a["a"])(c,l,n,!1,null,"247a9e55",null);t["default"]=i.exports}}]);

+ 1 - 0
dist/static/js/chunk-2d0d3705.ba054746.js

@@ -0,0 +1 @@
+(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0d3705"],{"5d88":function(e,t,l){"use strict";l.r(t);var i=function(){var e=this,t=e.$createElement,l=e._self._c||t;return l("div",[l("file-preview",{attrs:{"file-url":e.url,"file-type":e.fileName,type:"ai"}})],1)},n=[],o=l("3be9"),r={props:{},components:{FilePreview:o["default"]},data:function(){return{url:"",fileName:""}},computed:{},watch:{},mounted:function(){this.fileName=this.$route.query.fileExtension,this.url=localStorage.getItem("href"),console.log(localStorage.getItem("href"))},methods:{}},u=r,a=l("2877"),c=Object(a["a"])(u,i,n,!1,null,"449cb8e0",null);t["default"]=c.exports}}]);

File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-386d37cb.a644209c.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-3bf9b75a.353a51e8.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-43013872.f0617c10.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-57d45b95.ee9f6e14.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-ad361d30.87217e77.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-b580a2ac.fb270316.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-cd009444.4182e197.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-e61fad7e.9d892b96.js


+ 46 - 37
src/components/FilePreview/index.vue

@@ -6,7 +6,7 @@
         v-if="fileType === 'pdf'"
         :src="fileUrl"
         width="100%"
-        height="822px"
+        height="830px"
       ></iframe>
 
       <!-- 图片预览 -->
@@ -49,7 +49,7 @@
 </template>
   
   <script>
-  import { searchTaskInfo } from "@/api/knowledge";
+import { searchTaskInfo } from "@/api/knowledge";
 import mammoth from "mammoth";
 export default {
   props: {
@@ -61,6 +61,10 @@ export default {
       type: String,
       required: true,
     },
+    type: {
+      type: String,
+      required: true,
+    },
   },
   data() {
     return {
@@ -82,7 +86,7 @@ export default {
       ],
       wordLoaded: false,
       wordLoadingError: false,
-      internalFileUrl: '', // 新增:内部使用的 URL
+      internalFileUrl: "", // 新增:内部使用的 URL
     };
   },
   watch: {
@@ -119,42 +123,47 @@ export default {
   },
   methods: {
     async loadWordContent() {
-    this.wordLoaded = false;
-    this.wordLoadingError = false;
-    try {
-      // 获取文档 URL
-      const res = await searchTaskInfo({
-        page: 1,
-        page_size: 10,
-        document_id: this.$route.query.id,
-      });
-      this.internalFileUrl = res.data.documentUrl;
+      this.wordLoaded = false;
+      this.wordLoadingError = false;
+      try {
+        // 获取文档 URL
+        if (this.type !== "ai") {
+          const res = await searchTaskInfo({
+            page: 1,
+            page_size: 10,
+            document_id: this.$route.query.id,
+          });
+          this.internalFileUrl = res.data.documentUrl;
+        } else {
+          this.internalFileUrl = localStorage.getItem("href");
+        }
 
-      // 使用 fetch 获取文件内容,指定 responseType 为 'arraybuffer'
-      const response = await fetch(this.internalFileUrl, {
-        method: 'GET',
-        headers: {
-          'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
-        },
-      });
+        // 使用 fetch 获取文件内容,指定 responseType 为 'arraybuffer'
+        const response = await fetch(this.internalFileUrl, {
+          method: "GET",
+          headers: {
+            "Content-Type":
+              "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+          },
+        });
 
-      if (!response.ok) {
-        throw new Error(`HTTP error! status: ${response.status}`);
-      }
+        if (!response.ok) {
+          throw new Error(`HTTP error! status: ${response.status}`);
+        }
 
-      const arrayBuffer = await response.arrayBuffer();
-      const result = await mammoth.convertToHtml({ arrayBuffer });
-      this.wordContent = result.value;
-      this.wordLoaded = true;
-    } catch (error) {
-      console.error("加载Word文档内容时出错:", error);
-      this.wordContent = `无法加载Word文档内容: ${error.message}`;
-      this.wordLoadingError = true;
-      this.$message.error(`文档加载失败: ${error.message}`);
-    } finally {
-      this.wordLoaded = true;
-    }
-  },
+        const arrayBuffer = await response.arrayBuffer();
+        const result = await mammoth.convertToHtml({ arrayBuffer });
+        this.wordContent = result.value;
+        this.wordLoaded = true;
+      } catch (error) {
+        console.error("加载Word文档内容时出错:", error);
+        this.wordContent = `无法加载Word文档内容: ${error.message}`;
+        this.wordLoadingError = true;
+        this.$message.error(`文档加载失败: ${error.message}`);
+      } finally {
+        this.wordLoaded = true;
+      }
+    },
     blobToArrayBuffer(blob) {
       return new Promise((resolve, reject) => {
         const reader = new FileReader();
@@ -170,7 +179,7 @@ export default {
   <style scoped>
 .file-preview {
   width: 100%;
-  height: 822px;
+  height: 830px;
   overflow: auto;
 }
 .unsupported-file {

+ 1 - 1
src/components/LoginModal/index.vue

@@ -132,7 +132,7 @@ export default {
     },
     saveUserData(userData) {
       // 将用户数据保存到本地存储
-      sessionStorage.setItem("AIData", JSON.stringify(userData));
+      localStorage.setItem("AIData", JSON.stringify(userData));
       // 如果有token,单独保存token
     },
   },

+ 56 - 23
src/components/ai.vue

@@ -67,7 +67,7 @@ import ChatBox from "@/components/webAi/index.vue";
 import LoginModal from "@/components/LoginModal";
 import MarkdownIt from "markdown-it";
 import { pcInnerAi, getMinioURl } from "@/api/api";
-import { modelList,get_default } from "@/api/knowledge";
+import { modelList, get_default } from "@/api/knowledge";
 import axios from "axios";
 const md = new MarkdownIt();
 
@@ -111,7 +111,7 @@ export default {
         documentDirectory: "",
         document: "",
       },
-      currentChat_id:'',
+      currentChat_id: "",
     };
   },
   mounted() {
@@ -144,7 +144,7 @@ export default {
         this.tweaks = res.data.tweaks;
       });
     }
-    this.init()
+    this.init();
   },
   methods: {
     checkAIData() {
@@ -172,13 +172,41 @@ export default {
     },
 
     handleLinkClick(event) {
-      if (event.target.tagName === "A") {
-        localStorage.setItem("href", event.target.href);
-        let _this = this;
-        let a = document.createElement("a");
-        a.href = "#/preview"; // 使用 hash
-        a.target = "_blank";
-        a.click();
+      if (event.target.tagName.toLowerCase() === "a") {
+        event.preventDefault();
+
+        // 获取文件名
+        const fullFileName = event.target.textContent.trim();
+
+        // 分离文件名和后缀
+        const lastDotIndex = fullFileName.lastIndexOf(".");
+        const fileName =
+          lastDotIndex > -1
+            ? fullFileName.slice(0, lastDotIndex)
+            : fullFileName;
+        const fileExtension =
+          lastDotIndex > -1 ? fullFileName.slice(lastDotIndex + 1) : "";
+
+        // 编码文件名和后缀以便于URL传参
+        const encodedFileName = encodeURIComponent(fileName);
+        const encodedFileExtension = encodeURIComponent(fileExtension);
+
+        // 构建新的URL,包含文件名和后缀作为查询参数
+        const newUrl = `#/preview?fileName=${encodedFileName}&fileExtension=${encodedFileExtension}`;
+
+        try {
+          localStorage.setItem("href", event.target.href);
+        } catch (e) {
+          console.warn("无法存储 URL 到 localStorage:", e);
+        }
+
+        // 使用新的URL打开窗口
+        const newWindow = window.open(newUrl, "_blank");
+        if (newWindow) {
+          newWindow.focus();
+        } else {
+          alert("弹出窗口被阻止,请允许此网站的弹出窗口。");
+        }
       }
     },
 
@@ -293,7 +321,7 @@ export default {
               message: message,
               /* output_type: "chat",
               input_type: "chat", */
-              chat_config_id:this.currentChat_id,
+              chat_config_id: this.currentChat_id,
               user_id: this.$store.state.user.id,
               session_id: this.session_id || "",
               /* tweaks:this.tweaks  {
@@ -409,17 +437,22 @@ export default {
     },
 
     async handleResponse(value, existingMessage) {
-      const data = value.data.answer//outputs[0].outputs[0].results.message;
-      console.log("this.messages",this.messages);
+      const data = value.data.answer; //outputs[0].outputs[0].results.message;
+      console.log("this.messages", this.messages);
       existingMessage.messageType = data;
       /* existingMessage.time = data.timestamp; */
-      existingMessage.message = '';
-      
-      let mainText = data;
-      let sourceText = '';
+      existingMessage.message = "";
 
-      if (this.$route.name !== 'ai' && this.minioUrls && this.minioUrls.length > 0) {
-        sourceText = "\n\n<div class='source-section'><h3>相关资料来源:</h3><ol>";
+      let mainText = data;
+      let sourceText = "";
+
+      if (
+        this.$route.name !== "ai" &&
+        this.minioUrls &&
+        this.minioUrls.length > 0
+      ) {
+        sourceText =
+          "\n\n<div class='source-section'><h3>相关资料来源:</h3><ol>";
         this.minioUrls.forEach((url, index) => {
           sourceText += `<li><a href="${url.url}" target="_blank" class="source-link">${url.object_name}</a></li>`;
         });
@@ -519,10 +552,10 @@ export default {
       /* modelList().then((res) => {
         console.log(res);
       }); */
-      get_default().then(res=>{
-        if(res.status!==200) return
-        this.currentChat_id=res.data.id
-      })
+      get_default().then((res) => {
+        if (res.status !== 200) return;
+        this.currentChat_id = res.data.id;
+      });
     },
   },
   updated() {

+ 110 - 13
src/components/chartIcon/index.vue

@@ -1,11 +1,14 @@
 <template>
   <div class="chat-icon" @click="toggleChat">
     <i class="el-icon-chat-dot-round animate-pulse"></i>
-    <div v-if="$store.state.app.showChat" class="chat-window">
+    <div v-if="$store.state.app.showChat" class="chat-window" :style="chatWindowStyle" ref="chatWindow" @click.stop>
       <div class="chat-header">
+        <div class="resize-handle" @mousedown="startResize">
+          <i class="el-icon-d-caret"></i>
+        </div>
         <i class="el-icon-minus close-icon" @click.stop="closeChat"></i>
       </div>
-      <ChatBox />
+      <ChatBox  ref="chatBox"/>
     </div>
   </div>
 </template>
@@ -27,10 +30,42 @@ export default {
   data() {
     return {
       showChat: false,
+      chatWidth: 500,
+      chatHeight: 625,
+      isResizing: false,
+      startX: 0,
+      startY: 0,
+      startWidth: 0,
+      startHeight: 0,
     };
   },
+  computed: {
+    chatWindowStyle() {
+      return {
+        width: `${this.chatWidth}px`,
+        height: `${this.chatHeight}px`,
+      };
+    },
+    messagesHeight() {
+      // 减去其他元素的高度(如头部、输入框等)
+      return `calc(${this.chatHeight}px - 195px)`;
+    },
+  },
+  mounted() {
+    window.addEventListener('resize', this.updateMessagesHeight);
+    document.addEventListener('click', this.handleOutsideClick);
+  },
 
+  beforeDestroy() {
+    window.removeEventListener('resize', this.updateMessagesHeight);
+    document.removeEventListener('click', this.handleOutsideClick);
+  },
   methods: {
+    handleOutsideClick(event) {
+      if (this.$store.state.app.showChat && !this.$el.contains(event.target)) {
+        this.closeChat();
+      }
+    },
     toggleChat() {
       this.$store.state.app.showChat= true;
       this.showChat = true;
@@ -39,6 +74,52 @@ export default {
       this.$store.state.app.showChat= false;
       this.showChat = false;
     },
+    startResize(event) {
+      this.isResizing = true;
+      this.startX = event.clientX;
+      this.startY = event.clientY;
+      this.startWidth = this.$refs.chatWindow.offsetWidth;
+      this.startHeight = this.$refs.chatWindow.offsetHeight;
+      document.addEventListener('mousemove', this.resize);
+      document.addEventListener('mouseup', this.stopResize);
+    },
+    // Add this method
+    stopResize() {
+      this.isResizing = false;
+      document.removeEventListener('mousemove', this.resize);
+      document.removeEventListener('mouseup', this.stopResize);
+    },
+    updateMessagesHeight() {
+      const chatBox = this.$refs.chatBox;
+      if (chatBox && chatBox.$el) {
+        const messagesEl = chatBox.$el.querySelector('.messages');
+        if (messagesEl) {
+          const chatWindowHeight = this.$refs.chatWindow.offsetHeight;
+          const otherElementsHeight = 195; // Adjust this value as needed
+          messagesEl.style.height = `${chatWindowHeight - otherElementsHeight}px`;
+        }
+      }
+    },
+
+    resize(event) {
+      if (this.isResizing) {
+        const dx = this.startX - event.clientX;
+        const dy = this.startY - event.clientY;
+        this.chatWidth = Math.max(300, this.startWidth + dx);
+        this.chatHeight = Math.max(400, this.startHeight + dy);
+        this.$nextTick(() => {
+          this.updateMessagesHeight();
+        });
+      }
+    },
+
+    toggleChat() {
+      this.$store.state.app.showChat = true;
+      this.showChat = true;
+      this.$nextTick(() => {
+        this.updateMessagesHeight();
+      });
+    },
   },
 
 };
@@ -76,11 +157,9 @@ export default {
   animation: pulse 1.5s infinite ease-in-out;
 }
 .chat-window {
-  position: absolute;
+  position: fixed;
   bottom: 60px;
   right: 0;
-  width: 500px;
-  height: 625px;
   background-color: white;
   border: 1px solid #dcdfe6;
   border-radius: 4px;
@@ -88,19 +167,34 @@ export default {
   box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
   display: flex;
   flex-direction: column;
+  overflow: auto;
 }
+
 .chat-header {
   display: flex;
   justify-content: flex-end;
   align-items: center;
   padding-bottom: 10px;
-  /*  border-bottom: 1px solid #dcdfe6; */
 }
 
-.chat-messages {
-  flex-grow: 1;
-  overflow-y: auto;
-  padding: 10px 0;
+
+.resize-handle {
+  width: 20px;
+  height: 20px;
+  cursor: nwse-resize;
+  position: absolute;
+  top: -4px;
+  left: -4px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: #409eff;
+  font-size: 16px;
+  transform: rotate(140deg);
+}
+
+.resize-handle i {
+  pointer-events: none;
 }
 
 .message {
@@ -128,9 +222,9 @@ export default {
 ::v-deep .chat-container .content{
     flex-direction: column;
   }
-  ::v-deep .messages{
-      height:445px; /* calc(100vh - 500px); */
-  }
+  ::v-deep .messages {
+  overflow-y: auto;
+}
   ::v-deep .input-container{
     margin: 0 auto;
     position: unset;
@@ -140,4 +234,7 @@ export default {
   ::v-deep .sender-name{
       font-size: 14px;
   }
+  ::v-deep.chat-container{
+    margin-bottom: 0;
+  }
 </style>

+ 38 - 11
src/components/webAi/js/ChatBox.js

@@ -710,17 +710,44 @@ newChat() {
   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');
-      }
-    },
+handleLinkClick(event) {
+  if (event.target.tagName.toLowerCase() === "a") {
+    event.preventDefault();
+
+    // 获取文件名
+    const fullFileName = event.target.textContent.trim();
+
+    // 分离文件名和后缀
+    const lastDotIndex = fullFileName.lastIndexOf(".");
+    const fileName =
+      lastDotIndex > -1
+        ? fullFileName.slice(0, lastDotIndex)
+        : fullFileName;
+    const fileExtension =
+      lastDotIndex > -1 ? fullFileName.slice(lastDotIndex + 1) : "";
+
+    // 编码文件名和后缀以便于URL传参
+    const encodedFileName = encodeURIComponent(fileName);
+    const encodedFileExtension = encodeURIComponent(fileExtension);
+
+    // 构建新的URL,包含文件名和后缀作为查询参数
+    const newUrl = `#/preview?fileName=${encodedFileName}&fileExtension=${encodedFileExtension}`;
+
+    try {
+      localStorage.setItem("href", event.target.href);
+    } catch (e) {
+      console.warn("无法存储 URL 到 localStorage:", e);
+    }
+
+    // 使用新的URL打开窗口
+    const newWindow = window.open(newUrl, "_blank");
+    if (newWindow) {
+      newWindow.focus();
+    } else {
+      alert("弹出窗口被阻止,请允许此网站的弹出窗口。");
+    }
+  }
+},
 
     handleLoginSuccess() {
       this.showLoginModal = false;

+ 1 - 1
src/layout/components/Navbar.vue

@@ -12,7 +12,7 @@
     <div class="right-menu">
       <template v-if="device !== 'mobile'">
         <div class="right-menu-item">
-          <img src="../../assets/images/人工智能.png" alt="" style="cursor: pointer; margin-top: 14px;width: 25px;height: 25px" @click="openChatDialog">
+          <img src="../../assets/images/人工智能.png" alt="" style="cursor: pointer; margin-top: 14px;width: 25px;height: 25px" @click.stop="openChatDialog">
         </div>
         <search id="header-search" class="right-menu-item" />
         <error-log class="errLog-container right-menu-item hover-effect" />

+ 126 - 45
src/views/appConfig/index.vue

@@ -1018,7 +1018,7 @@
             <el-form-item label="应用图标:">
               <!-- aiagent_icon -->
               <el-upload
-              disabled
+                disabled
                 class="avatar-uploader"
                 :action="uploadUrl"
                 method="post"
@@ -1398,8 +1398,9 @@ export default {
         timeout: 30,
         role_name: "Admin",
         role_description: "",
-        role_permissions: "",
-        custom_variables: "",
+        role_permissions: '["回答产品问题", "处理退换货请求", "升级复杂问题"]',
+        custom_variables:
+          '{"custom_greeting": "很高兴为您服务!", "company_name": "ABC公司"}',
         custom_prompt:
           "使用以下上下文来回答问题。如果你不知道答案,就说你不知道,不要试图编造答案。上下文: {context} 人类: {question}AI助手: 让我根据提供的上下文来回答你的问题。{custom_greeting}",
         application_type: "",
@@ -1440,6 +1441,34 @@ export default {
         documents: [
           { required: true, message: "请选择至少一个文档", trigger: "change" },
         ],
+        role_permissions: [
+        { required: true, message: "请输入角色权限", trigger: "blur" },
+        { 
+          validator: (rule, value, callback) => {
+            try {
+              JSON.parse(value);
+              callback();
+            } catch (error) {
+              callback(new Error("请输入有效的 JSON 数组"));
+            }
+          },
+          trigger: "blur"
+        }
+      ],
+      custom_variables: [
+        { required: true, message: "请输入自定义变量", trigger: "blur" },
+        { 
+          validator: (rule, value, callback) => {
+            try {
+              JSON.parse(value);
+              callback();
+            } catch (error) {
+              callback(new Error("请输入有效的 JSON 对象"));
+            }
+          },
+          trigger: "blur"
+        }
+      ],
       },
       /* 配置类型列表 */
       appTypeList: [],
@@ -1466,8 +1495,9 @@ export default {
         timeout: 30,
         role_name: "Admin",
         role_description: "",
-        role_permissions: "",
-        custom_variables: "",
+        role_permissions: '["回答产品问题", "处理退换货请求", "升级复杂问题"]',
+        custom_variables:
+          '{"custom_greeting": "很高兴为您服务!", "company_name": "ABC公司"}',
         custom_prompt:
           "使用以下上下文来回答问题。如果你不知道答案,就说你不知道,不要试图编造答案。上下文: {context} 人类: {question}AI助手: 让我根据提供的上下文来回答你的问题。{custom_greeting}",
         application_type: "",
@@ -1596,6 +1626,21 @@ export default {
         if (valid) {
           try {
             const convertedForm = { ...this.editForm };
+            // 处理 role_permissions
+          const parsedPermissions = this.validateAndParseJSON(
+            'role_permissions',
+            '角色权限格式无效'
+          );
+          if (!parsedPermissions) return;
+          convertedForm.role_permissions = parsedPermissions;
+
+          // 处理 custom_variables
+          const parsedVariables = this.validateAndParseJSON(
+            'custom_variables',
+            '自定义变量格式无效'
+          );
+          if (!parsedVariables) return;
+          convertedForm.custom_variables = parsedVariables;
             // 转换知识库、文档目录和文档的ID为名称
             /*  convertedForm.knowledge_base_names = this.safeGetNamesByIds(this.editForm.knowledge_base_names, this.kneList); */
             /*  convertedForm.documents = this.safeGetNamesByIds(this.editForm.documents, this.editDocumentList); */
@@ -1780,8 +1825,9 @@ export default {
         timeout: 30,
         role_name: "Admin",
         role_description: "",
-        role_permissions: "",
-        custom_variables: "",
+        role_permissions: '["回答产品问题", "处理退换货请求", "升级复杂问题"]',
+        custom_variables:
+          '{"custom_greeting": "很高兴为您服务!", "company_name": "ABC公司"}',
         custom_prompt:
           "使用以下上下文来回答问题。如果你不知道答案,就说你不知道,不要试图编造答案。上下文: {context} 人类: {question}AI助手: 让我根据提供的上下文来回答你的问题。{custom_greeting}",
         application_type: "",
@@ -1820,6 +1866,14 @@ export default {
         }
       }
     },
+    validateAndParseJSON(field, errorMessage) {
+    try {
+      return JSON.parse(this.AIform[field]);
+    } catch (error) {
+      this.$message.error(`${errorMessage}: ${error.message}`);
+      return null;
+    }
+  },
     /* 新增聊天应用 */
     generateApplication() {
       this.$refs.AIformRef.validate(async (valid) => {
@@ -1827,7 +1881,21 @@ export default {
           try {
             // 创建一个新对象来存储转换后的数据
             const convertedForm = { ...this.AIform };
+            // 处理 role_permissions
+            const parsedPermissions = this.validateAndParseJSON(
+              "role_permissions",
+              "角色权限格式无效"
+            );
+            if (!parsedPermissions) return;
+            convertedForm.role_permissions = parsedPermissions;
 
+            // 处理 custom_variables
+            const parsedVariables = this.validateAndParseJSON(
+              "custom_variables",
+              "自定义变量格式无效"
+            );
+            if (!parsedVariables) return;
+            convertedForm.custom_variables = parsedVariables;
             // 将知识库 ID 转换为名称
             /* convertedForm.knowledge_base_names = this.safeGetNamesByIds(
               this.AIform.knowledge_base_names,
@@ -2023,51 +2091,60 @@ export default {
       });
     },
     /* 编辑查看 */
-    initEdit() {
+    async initEdit() {
       // 深拷贝以避免直接修改原对象
       this.editForm = JSON.parse(JSON.stringify(this.$route.query.card));
 
       // 处理 role_permissions 和 custom_variables
       this.handleJsonFields();
 
-      // 加载知识库对应的目录列表
-      this.loadEditDirectoryList(this.editForm.knowledge_base_names).then(
-        () => {
-          // 在目录列表加载完成后,设置选中的目录
-          if (
-            this.editForm.document_directories &&
-            this.editForm.document_directories.length > 0
-          ) {
-            const dirId = this.editDirectoryList.find(
-              (dir) => dir.name === this.editForm.document_directories[0]
-            )?.id;
-            if (dirId) {
-              this.editForm.document_directories = dirId;
-              // 加载文档列表
-              this.loadEditDocumentList(dirId).then(() => {
-                this.$nextTick(() => {
-                  // 在文档列表加载完成且DOM更新后,设置选中的文档
-                  if (
-                    !this.editForm.documents ||
-                    this.editForm.documents.length === 0
-                  ) {
-                    // 如果 documents 为空,设置为 "全部"
-                    this.editForm.documents = ["all"];
-                  } else if (this.editForm.documents.includes("全部")) {
-                    this.editForm.documents = ["all"];
-                  } else {
-                    this.editForm.documents = this.editDocumentList
-                      .filter((doc) =>
-                        this.editForm.documents.includes(doc.name)
-                      )
-                      .map((doc) => doc.id);
-                  }
-                });
-              });
-            }
-          }
-        }
+      // 并行加载知识库目录列表
+      const directoryListPromise = this.loadEditDirectoryList(
+        this.editForm.knowledge_base_names
       );
+
+      // 等待目录列表加载完成
+      await directoryListPromise;
+
+      // 处理目录和文档选择
+      this.handleDirectoryAndDocumentSelection();
+    },
+
+    async handleDirectoryAndDocumentSelection() {
+      if (
+        !this.editForm.document_directories ||
+        this.editForm.document_directories.length === 0
+      ) {
+        return;
+      }
+
+      const dirName = this.editForm.document_directories[0];
+      const dir = this.editDirectoryList.find((dir) => dir.name === dirName);
+
+      if (!dir) {
+        return;
+      }
+
+      this.editForm.document_directories = dir.id;
+
+      // 延迟加载文档列表
+      await this.$nextTick();
+      await this.loadEditDocumentList(dir.id);
+
+      // 处理文档选择
+      this.handleDocumentSelection();
+    },
+
+    handleDocumentSelection() {
+      if (!this.editForm.documents || this.editForm.documents.length === 0) {
+        this.editForm.documents = ["all"];
+      } else if (this.editForm.documents.includes("全部")) {
+        this.editForm.documents = ["all"];
+      } else {
+        this.editForm.documents = this.editDocumentList
+          .filter((doc) => this.editForm.documents.includes(doc.name))
+          .map((doc) => doc.id);
+      }
     },
 
     handleJsonFields() {
@@ -2207,4 +2284,8 @@ export default {
   height: 100px;
   display: block;
 }
+.hint {
+  font-size: 12px;
+  color: #c0c4cc;
+}
 </style>

+ 94 - 11
src/views/knowledgeMenu/category/infoList.vue

@@ -1,14 +1,34 @@
 <template>
   <div class="project-search" v-if="type !== 'xls'">
-    <div style="width: 50%;height: calc(100vh - 30px);;overflow: auto;position: relative;" v-if="dataList.length !== 0">
+    <div
+      style="
+        width: 50%;
+        height: calc(100vh - 30px);
+        overflow: auto;
+        position: relative;
+        
+      "
+      v-if="dataList.length !== 0"
+    >
       <div v-for="item in dataList" :key="item.id" class="listClass">
         <img
           :src="item.url"
           alt=""
-          style="width: 150px; height: 150px; padding: 10px 10px 10px 0"
+          @click="openImagePreview(item.url)"
+          style="width: 150px; height: 150px; padding: 10px 10px 10px 0;cursor: pointer;"
         />
         <div v-html="item.content"></div>
       </div>
+      <el-dialog
+        :visible.sync="imagePreviewVisible"
+        append-to-body
+        :modal="false"
+        :close-on-click-modal="false"
+        custom-class="movable-dialog"
+        :title="'图片预览'"
+      >
+        <img :src="previewImageUrl" style="width: 100%" alt="Preview" />
+      </el-dialog>
       <!-- 分页控件 -->
       <div class="pagination">
         <el-pagination
@@ -93,8 +113,8 @@ export default {
   data() {
     return {
       queryForm: {
-        page:1,
-        page_size:10,
+        page: 1,
+        page_size: 10,
         document_id: "",
       },
       dataList: [],
@@ -117,7 +137,9 @@ export default {
         "png",
         "gif",
       ],
-      total:0
+      total: 0,
+      imagePreviewVisible: false,
+      previewImageUrl: "",
     };
   },
   mounted() {
@@ -125,12 +147,66 @@ export default {
     this.type = this.$route.query.type;
     this.checkFileType();
     this.search();
+    this.$nextTick(this.makeDialogDraggable);
   },
   methods: {
+    makeDialogDraggable() {
+      const dialogHeaderEl = document.querySelector(
+        ".movable-dialog .el-dialog__header"
+      );
+      const dragDom = document.querySelector(".movable-dialog");
+
+      dialogHeaderEl.style.cursor = "move";
+      dragDom.style.position = "absolute";
+
+      // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
+      const sty =
+        dragDom.currentStyle || window.getComputedStyle(dragDom, null);
+
+      dialogHeaderEl.onmousedown = (e) => {
+        // 鼠标按下,计算当前元素距离可视区的距离
+        const disX = e.clientX - dialogHeaderEl.offsetLeft;
+        const disY = e.clientY - dialogHeaderEl.offsetTop;
+
+        // 获取到的值带px 正则匹配替换
+        let styL, styT;
+
+        // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
+        if (sty.left.includes("%")) {
+          styL =
+            +document.body.clientWidth * (+sty.left.replace(/\%/g, "") / 100);
+          styT =
+            +document.body.clientHeight * (+sty.top.replace(/\%/g, "") / 100);
+        } else {
+          styL = +sty.left.replace(/\px/g, "");
+          styT = +sty.top.replace(/\px/g, "");
+        }
+
+        document.onmousemove = function (e) {
+          // 通过事件委托,计算移动的距离
+          const l = e.clientX - disX;
+          const t = e.clientY - disY;
+
+          // 移动当前元素
+          dragDom.style.left = `${l + styL}px`;
+          dragDom.style.top = `${t + styT}px`;
+        };
+
+        document.onmouseup = function (e) {
+          document.onmousemove = null;
+          document.onmouseup = null;
+        };
+      };
+    },
+    openImagePreview(url) {
+      this.previewImageUrl = url;
+      this.imagePreviewVisible = true;
+      this.$nextTick(this.makeDialogDraggable);
+    },
     /* 分页 */
     handleCurrentChange(page) {
       this.queryForm.page = page;
-      this.search(); 
+      this.search();
     },
     checkAuth(path) {
       if (this.roleInfo.is_admin == 1) {
@@ -150,7 +226,7 @@ export default {
       searchTaskInfo(this.queryForm)
         .then((res) => {
           this.dataList = res.data.tasks;
-          this.total=res.data.pagination.total_count
+          this.total = res.data.pagination.total_count;
           if (this.isFileViewable) {
             this.documentUrl = res.data.documentUrl;
           } else {
@@ -255,11 +331,18 @@ export default {
   text-align: center;
   margin-top: 50px;
 }
-.pagination{
+.pagination {
   position: fixed;
-    bottom: 0;
-    right: 51%;
-    /* right: 0; */
+  bottom: 0;
+  right: 51%;
+  /* right: 0; */
+}
+.movable-dialog {
+  position: absolute !important;
+}
+
+::v-deep .el-dialog__header {
+  cursor: move;
 }
 </style>
     

+ 69 - 64
src/views/knowledgeMenu/category/knowledgeSet.vue

@@ -15,9 +15,9 @@
         highlight-current
       >
         <span class="custom-tree-node" slot-scope="{ node, data }">
-          <span>
+          <span class="tree_title">
             <i class="el-icon-folder"></i>
-            {{ node.label }}
+            <span class="folder-name">{{ node.label }}</span>
           </span>
           <span v-if="data.id !== '001'" :class="getFileCountClass(data.id)"
             >{{ data.document_count }} files</span
@@ -127,7 +127,7 @@
                     circle
                     icon="el-icon-view"
                     v-if="checkAuth('/document/update')"
-                    style="font-size: 15px;"
+                    style="font-size: 15px"
                     @click="getName(scope.row)"
                   ></el-button
                 ></el-tooltip>
@@ -145,7 +145,7 @@
                     icon="el-icon-caret-right"
                     circle
                     @click="analysis(scope.row)"
-                    style="font-size: 15px;"
+                    style="font-size: 15px"
                   >
                   </el-button>
                   <el-button
@@ -154,7 +154,7 @@
                     icon="el-icon-loading"
                     circle
                     :disabled="true"
-                    style="font-size: 15px;"
+                    style="font-size: 15px"
                   >
                   </el-button>
                 </el-tooltip>
@@ -170,7 +170,7 @@
                     circle
                     icon="el-icon-tickets"
                     v-if="checkAuth('/document/update')"
-                    style="font-size: 15px;"
+                    style="font-size: 15px"
                     @click="Analytical(scope.row)"
                   ></el-button
                 ></el-tooltip>
@@ -186,7 +186,7 @@
                     circle
                     icon="el-icon-edit"
                     v-if="checkAuth('/document/update')"
-                    style="font-size: 15px;"
+                    style="font-size: 15px"
                     @click="btnEdit(scope.row)"
                   ></el-button
                 ></el-tooltip>
@@ -203,7 +203,7 @@
                     icon="el-icon-download"
                     @click="btnDown(scope.row)"
                     :loading="scope.row.downloading"
-                    style="font-size: 15px;"
+                    style="font-size: 15px"
                   ></el-button
                 ></el-tooltip>
                 <el-tooltip
@@ -219,7 +219,7 @@
                     icon="el-icon-delete"
                     v-if="checkAuth('/document/delete')"
                     @click="btnDelete(scope.row.id)"
-                    style="font-size: 15px;"
+                    style="font-size: 15px"
                   ></el-button
                 ></el-tooltip>
               </div>
@@ -781,7 +781,7 @@ export default {
       }); */
       let _this = this;
       let a = document.createElement("a");
-      a.href = `#/infoList?id=${e.id}&type=${e.type}`; // 使用 hash
+      a.href = `#/infoList?id=${e.id}&type=${e.type.toLowerCase()}`; // 使用 hash
       a.target = "_blank";
       a.click();
     },
@@ -858,18 +858,23 @@ export default {
       checkStatus();
     },
 
-    // 添加新方法来刷新当前列表
+    // 修改 refreshCurrentList 方法
     refreshCurrentList() {
       // 保存当前页码和每页显示数量
       const currentPage = this.queryForm.page;
       const currentPageSize = this.queryForm.pageSize;
 
       // 重新获取数据
-      this.search().then(() => {
-        // 恢复之前的页码和每页显示数量
-        this.queryForm.page = currentPage;
-        this.queryForm.pageSize = currentPageSize;
-      });
+      return this.search()
+        .then(() => {
+          // 恢复之前的页码和每页显示数量
+          this.queryForm.page = currentPage;
+          this.queryForm.pageSize = currentPageSize;
+        })
+        .catch((error) => {
+          console.error("刷新列表时出错:", error);
+          this.$message.error("刷新列表失败,请稍后重试");
+        });
     },
 
     handleAnalClose() {
@@ -1019,57 +1024,38 @@ export default {
     search() {
       let _this = this;
       _this.loading = true;
-      getBucketContents(_this.queryForm)
-        .then((res) => {
-          if (res.status !== 200) return;
-          _this.dataList = res.data.documents.map((el) => ({
-            ...el,
-            token_num: 256,
-            start_page: _this.analForm.start_page,
-            end_page: _this.analForm.end_page,
-          }));
-          _this.pageTotal = res.data.pagination.total_count;
-          _this.recordCount = res.data.pagination.total_count;
-          _this.queryForm.pageSize = res.data.pagination.total_size;
-          _this.loading = false;
-
-          /*    // Count documents with doc_type_id === 0 or null
-          const unclassifiedCount = res.data.documents.filter(
-            (doc) => !doc.doc_type_id || doc.doc_type_id === '0'
-          ).length;
-
-          // Update "其他" folder count
-          const otherFolder = _this.folders.find((f) => f.id === 0);
-          if (otherFolder) {
-            otherFolder.document_count = unclassifiedCount;
-          }
-
-          // Update "全部" folder count
-          const allFolder = _this.folders.find((f) => f.id === "001");
-          if (allFolder) {
-            allFolder.document_count = res.data.pagination.total_count;
-          }
-
-          // Update other folder counts
-          _this.folders.forEach((folder) => {
-            if (folder.id !== "001" && folder.id !== 0) {
-              const folderCount = res.data.documents.filter(
-                (doc) => doc.doc_type_id === folder.id
-              ).length;
-              folder.document_count = folderCount;
+      return new Promise((resolve, reject) => {
+        getBucketContents(_this.queryForm)
+          .then((res) => {
+            if (res.status !== 200) {
+              reject(new Error("Invalid response status"));
+              return;
             }
-          }); */
+            _this.dataList = res.data.documents.map((el) => ({
+              ...el,
+              token_num: 256,
+              start_page: _this.analForm.start_page,
+              end_page: _this.analForm.end_page,
+            }));
+            _this.pageTotal = res.data.pagination.total_count;
+            _this.recordCount = res.data.pagination.total_count;
+            _this.queryForm.pageSize = res.data.pagination.total_size;
+            _this.loading = false;
+
+            // Initialize the analysis status for each row
+            _this.dataList.forEach((row) => {
+              if (row.run == 1 || row.run == 5) {
+                _this.startAnalysisStatusChecker(row);
+              }
+            });
 
-          // Initialize the analysis status for each row
-          _this.dataList.forEach((row) => {
-            if (row.run == 1 || row.run == 5) {
-              _this.startAnalysisStatusChecker(row);
-            }
+            resolve();
+          })
+          .catch((error) => {
+            _this.loading = false;
+            reject(error);
           });
-        })
-        .catch(() => {
-          _this.loading = false;
-        });
+      });
     },
 
     //修改分页
@@ -1184,6 +1170,7 @@ export default {
   }
 
   .custom-tree-node {
+    font-size: 14px;
     width: 100%;
     display: flex;
     align-items: center;
@@ -1202,8 +1189,26 @@ export default {
       padding: 2px 6px;
       border-radius: 10px;
     }
+    .tree_title {
+      display: flex;
+      align-items: center;
+      width: 140px;
+
+      .folder-name {
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        max-width: calc(100% - 24px); // 减去图标的宽度
+      }
+    }
   }
 }
+.el-tree {
+  margin-left: 10px;
+}
+::v-deep .is-leaf {
+  display: none;
+}
 @import "./components/dataList.scss";
 </style>
     

+ 6 - 2
src/views/login/Preview.vue

@@ -1,7 +1,7 @@
 <template>
    <div>
     <!-- 文件预览组件 -->
-    <file-preview :file-url="url" file-type="docx" />
+    <file-preview :file-url="url" :file-type="fileName" type="ai" />
    </div>
 </template>
 
@@ -9,7 +9,9 @@
 import FilePreview from "@/components/FilePreview/index.vue"; // 新增文件预览组件
 export default {
   /*组件参数 接收来自父组件的数据*/
-  props: {},
+  props: {
+    
+  },
   /*局部注册的组件*/
   components: {
     FilePreview, // 注册文件预览组件
@@ -18,6 +20,7 @@ export default {
    data () {
       return {
         url:'',
+        fileName:''
       };
    },
 
@@ -32,6 +35,7 @@ export default {
   * 如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$ el 也在文档内。
   */
   mounted () {
+    this.fileName=this.$route.query.fileExtension
     this.url=localStorage.getItem('href')
     console.log(localStorage.getItem('href'));
   },

+ 481 - 439
src/views/login/aiIndex.vue

@@ -66,9 +66,9 @@
 import ChatBox from "@/components/webAi/index.vue";
 import LoginModal from "@/components/LoginModal";
 import MarkdownIt from "markdown-it";
-import { pcInnerAi,getMinioURl } from "@/api/api";
-import {modelList} from "@/api/knowledge"
-import axios from 'axios';
+import { pcInnerAi, getMinioURl } from "@/api/api";
+import { modelList,get_default } from "@/api/knowledge";
+import axios from "axios";
 const md = new MarkdownIt();
 
 export default {
@@ -80,7 +80,7 @@ export default {
     return {
       showLoginModal: false,
       isThinking: false,
-      thinkingDots: '',
+      thinkingDots: "",
       messages: [
         {
           user: "bot",
@@ -94,57 +94,59 @@ export default {
       generating: false,
       userInput: "",
       websocket: null,
-      wsUrl:'http://58.246.234.210:7860/api/v1/run/3ef7369e-b617-40d2-9e2e-54f3ee76ed2e?stream=false',
-      tweaks:{},
+      wsUrl:
+        "http://58.246.234.210:7860/api/v1/run/3ef7369e-b617-40d2-9e2e-54f3ee76ed2e?stream=false",
+      tweaks: {},
       isLoggedIn: false, // 添加登录状态标志
       idArray: [],
       minioUrls: [], // 新增:用于存储 Minio URL
-      AImodel:'1',//模型
-      AIknowledgeBase:"1",//知识库
-      AIFile:'1',//文件
-      AIform:{
-        modelLibrary: 'ollama',
-        modelType: '',
-        modelName: '',
-        knowledgeBase: '',
-        documentDirectory: '',
-        document: '',
-      }
+      AImodel: "1", //模型
+      AIknowledgeBase: "1", //知识库
+      AIFile: "1", //文件
+      AIform: {
+        modelLibrary: "ollama",
+        modelType: "",
+        modelName: "",
+        knowledgeBase: "",
+        documentDirectory: "",
+        document: "",
+      },
+      currentChat_id:'',
     };
   },
   mounted() {
     this.checkAIData();
-    if(this.$route.name=='ai'){
+    if (this.$route.name == "ai") {
       /* 外部知识库 */
-       /* 外部知识库 */
-       const AIDataString = localStorage.getItem("AIData");
-        if (!AIDataString) {
-          this.showLoginModal = true;
-          return;
-        }
-        try {
-          const AIData = JSON.parse(AIDataString);
-          if (AIData && AIData.data) {
-            this.wsUrl = AIData.data.url || '';
-            this.tweaks = AIData.data.tweaks || {};
-          } else {
-            console.error('Invalid AIData structure');
-            this.showLoginModal = true;
-          }
-        } catch (error) {
-          console.error('Error parsing AIData:', error);
+      /* 外部知识库 */
+      const AIDataString = localStorage.getItem("AIData");
+      if (!AIDataString) {
+        this.showLoginModal = true;
+        return;
+      }
+      try {
+        const AIData = JSON.parse(AIDataString);
+        if (AIData && AIData.data) {
+          console.log(AIData);
+          this.wsUrl = AIData.data.url || "";
+          this.tweaks = AIData.data.tweaks || {};
+        } else {
+          console.error("Invalid AIData structure");
           this.showLoginModal = true;
         }
-    }else{
+      } catch (error) {
+        console.error("Error parsing AIData:", error);
+        this.showLoginModal = true;
+      }
+    } else {
       /* 内部知识库 */
-      pcInnerAi().then(res=>{
-        if(res.status!==200) return
-        this.wsUrl=res.data.url
-        this.tweaks=res.data.tweaks
-        
-      })
-      
+      pcInnerAi().then((res) => {
+        if (res.status !== 200) return;
+        this.wsUrl = res.data.url;
+        this.tweaks = res.data.tweaks;
+      });
     }
+    this.init()
   },
   methods: {
     checkAIData() {
@@ -155,27 +157,58 @@ export default {
       this.showLoginModal = false;
       // 处理登录成功后的逻辑,例如刷新数据或更新状态
     },
-/* 聊天记录 */
-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 = [];
-},
+    /* 聊天记录 */
+    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') {
-        localStorage.setItem('href',event.target.href)
-        let _this = this;
-        let a = document.createElement("a");
-        a.href = "#/preview"; // 使用 hash
-        a.target = "_blank";
-        a.click();
+      if (event.target.tagName.toLowerCase() === "a") {
+        event.preventDefault();
+
+        // 获取文件名
+        const fullFileName = event.target.textContent.trim();
+
+        // 分离文件名和后缀
+        const lastDotIndex = fullFileName.lastIndexOf(".");
+        const fileName =
+          lastDotIndex > -1
+            ? fullFileName.slice(0, lastDotIndex)
+            : fullFileName;
+        const fileExtension =
+          lastDotIndex > -1 ? fullFileName.slice(lastDotIndex + 1) : "";
+
+        // 编码文件名和后缀以便于URL传参
+        const encodedFileName = encodeURIComponent(fileName);
+        const encodedFileExtension = encodeURIComponent(fileExtension);
+
+        // 构建新的URL,包含文件名和后缀作为查询参数
+        const newUrl = `#/preview?fileName=${encodedFileName}&fileExtension=${encodedFileExtension}`;
+
+        try {
+          localStorage.setItem("href", event.target.href);
+        } catch (e) {
+          console.warn("无法存储 URL 到 localStorage:", e);
+        }
+
+        // 使用新的URL打开窗口
+        const newWindow = window.open(newUrl, "_blank");
+        if (newWindow) {
+          newWindow.focus();
+        } else {
+          alert("弹出窗口被阻止,请允许此网站的弹出窗口。");
+        }
       }
     },
 
@@ -196,19 +229,20 @@ newChat() {
       );
     },
     connectWebSocket() {
-      const wsUrl = 'http://58.246.234.210:7860/api/v1/run/2f1faff2-99df-4821-87d6-43d693084c4b?stream=false'
+      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) => {
+
+      /*  this.websocket.onerror = (event) => {
         console.error("WebSocket 连接错误:", event);
       };
      */
       this.websocket.onclose = (event) => {
         console.log("WebSocket 连接关闭:", event);
       };
-    
+
       this.websocket.onopen = (event) => {
         console.log("WebSocket 连接已建立:", event);
       };
@@ -216,8 +250,6 @@ newChat() {
         // 解析收到的消息
         const result = JSON.parse(event.data);
 
-       
-
         // 检查消息是否完结
         if (result.done) {
           this.messages[this.messages.length - 1].done = true;
@@ -242,14 +274,14 @@ newChat() {
       };
     },
     async sendMessage() {
-      if(this.$route.name=='ai'){
-        if(!localStorage.getItem('AIData')){
-          this.showLoginModal=true
-          return
+      if (this.$route.name == "ai") {
+        if (!localStorage.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'
+      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'
       let message = this.userInput.trim();
       if (message) {
         // Markdown换行:在每个换行符之前添加两个空格
@@ -263,19 +295,19 @@ newChat() {
           done: true,
         });
         this.userInput = "";
-       // 添加机器人的响应消息(初始为空)
-       const botMessage = {
-        user: "bot",
-        messageType: "TEXT",
-        message: "",
-        html: "",
-        time: Date.now(),
-        done: false,
-      };
-      this.messages.push(botMessage);
+        // 添加机器人的响应消息(初始为空)
+        const botMessage = {
+          user: "bot",
+          messageType: "TEXT",
+          message: "",
+          html: "",
+          time: Date.now(),
+          done: false,
+        };
+        this.messages.push(botMessage);
 
-      // 开始模拟思考
-      const thinkingPromise = this.simulateThinking(botMessage);
+        // 开始模拟思考
+        const thinkingPromise = this.simulateThinking(botMessage);
         // 通过 HTTP 发送消息
         try {
           ///send-message
@@ -285,11 +317,13 @@ newChat() {
               "Content-Type": "application/json",
             },
             body: JSON.stringify({
-              chatId: chatId,
-              input_value: message,
-              output_type: "chat",
-              input_type: "chat",
-              tweaks:this.tweaks /* {
+              /*  chatId: chatId, */
+              message: message,
+              /* output_type: "chat",
+              input_type: "chat", */
+              chat_config_id:'3',//this.currentChat_id,
+              user_id: this.tweaks['Memory-KtRT7'].session_id,//this.tweaks.Memory-KtRT7.session_id,
+              session_id: this.session_id || ""/* {
                 "ChatInput-U82Vu": {},
                 "Prompt-b8Z1E": {},
                 "OllamaModel-z9SVx": {},
@@ -304,42 +338,44 @@ newChat() {
             }),
           });
 
-          if (!response.ok) {
+          const result = await response.json();
+          this.session_id = result.data.session_id;
+          console.log(result);
+          if (result.code !== 200) {
             const errorText = await response.text(); // 获取错误文本
             throw new Error(
               `HTTP error! status: ${response.status}, message: ${errorText}`
             );
           }
+          // 等待思考动画完成
+          await thinkingPromise;
+          if (this.$route.name !== "ai") {
+            // 提取 additional_input 数据
+            const additionalInput = result.data.milvus_ids;
+
+            // 处理字符串,提取 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);
+          }
 
-          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 = "抱歉,发生了错误,请稍后再试。";
-       }
+          this.handleResponse(result, botMessage);
+        } catch (error) {
+          console.error("Error sending message:", error);
+          // 如果发生错误,停止思考动画
+          botMessage.done = true;
+          botMessage.message = "抱歉,发生了错误,请稍后再试。";
+        }
       }
     },
     async getMinioUrls(idObject) {
@@ -348,28 +384,32 @@ newChat() {
       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'
+        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');
+          console.error("Failed to get Minio URLs");
         }
       } catch (error) {
-        console.error('Error fetching Minio URLs:', 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);
+          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);
+          console.error("No response received:", error.request);
         } else {
-          console.error('Error message:', error.message);
+          console.error("Error message:", error.message);
         }
       }
     },
@@ -377,37 +417,42 @@ newChat() {
       this.isThinking = true;
       const thinkingTime = Math.random() * 1000 + 500; // 思考时间为 0.5-1.5 秒
       const dotInterval = 200; // 每 200ms 更新一次点
-      
+
       const updateDots = () => {
-        this.thinkingDots += '.';
+        this.thinkingDots += ".";
         if (this.thinkingDots.length > 3) {
-          this.thinkingDots = '';
+          this.thinkingDots = "";
         }
-        message.message =  this.thinkingDots;
+        message.message = this.thinkingDots;
       };
-  
+
       const intervalId = setInterval(updateDots, dotInterval);
-  
-      await new Promise(resolve => setTimeout(resolve, thinkingTime));
-      
+
+      await new Promise((resolve) => setTimeout(resolve, thinkingTime));
+
       clearInterval(intervalId);
       this.isThinking = false;
-      this.thinkingDots = '';
-      message.message = '';
+      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;
+      const data = value.data.answer
+
+      existingMessage.messageType = data;
+      /* 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<div class='source-section'><h3>相关资料来源:</h3><ol>";
+      let mainText = data;
+      let sourceText = "";
+
+      if (
+        this.$route.name !== "ai" &&
+        this.minioUrls &&
+        this.minioUrls.length > 0
+      ) {
+        sourceText =
+          "\n\n<div class='source-section'><h3>相关资料来源:</h3><ol>";
         this.minioUrls.forEach((url, index) => {
           sourceText += `<li><a href="${url.url}" target="_blank" class="source-link">${url.object_name}</a></li>`;
         });
@@ -425,7 +470,7 @@ newChat() {
     },
 
     typeMessage(message, fullText) {
-      return new Promise(resolve => {
+      return new Promise((resolve) => {
         const delay = 30;
         let i = 0;
         const typeChar = () => {
@@ -439,7 +484,7 @@ newChat() {
             resolve();
           }
         };
-        
+
         typeChar();
       });
     },
@@ -447,29 +492,29 @@ newChat() {
       const md = new MarkdownIt({
         html: true,
         breaks: true,
-        linkify: true
+        linkify: true,
       });
 
       // 自定义链接渲染规则
       md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
         const token = tokens[idx];
-        const hrefIndex = token.attrIndex('href');
+        const hrefIndex = token.attrIndex("href");
         const href = token.attrs[hrefIndex][1];
         return `<span class="custom-link" data-href="${href}">`;
       };
 
-      md.renderer.rules.link_close = () => '</span>';
+      md.renderer.rules.link_close = () => "</span>";
 
       const renderedHtml = md.render(rawMarkdown);
 
       // 使用 setTimeout 来确保 DOM 更新后再添加事件监听器
       setTimeout(() => {
-        const links = document.querySelectorAll('.custom-link');
-        links.forEach(link => {
-          link.addEventListener('click', (e) => {
+        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');
+            const href = link.getAttribute("data-href");
+            window.open(href, "_blank");
           });
         });
       }, 0);
@@ -502,12 +547,16 @@ newChat() {
         this.websocket.close();
       }
     },
-      /* 获取列表 */
-  init(){
-    modelList().then(res=>{
-      console.log(res);
-    })
-  }
+    /* 获取列表 */
+    init() {
+      /* modelList().then((res) => {
+        console.log(res);
+      }); */
+      get_default().then((res) => {
+        if (res.status !== 200) return;
+        this.currentChat_id = res.data.id;
+      });
+    },
   },
   updated() {
     const messagesContainer = this.$el.querySelector(".messages");
@@ -516,89 +565,87 @@ newChat() {
 };
 </script>
 <style scoped>
-
 .chat-container {
-    position: relative;
-    display: flex;
-    flex-direction: column;
-    margin-bottom: 150px;
-     /* 或者按照实际需要调整高度 */
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 150px;
+  /* 或者按照实际需要调整高度 */
 }
-.chat-container .header{
+.chat-container .header {
   width: 1200px;
   margin: 10px auto 0;
   border: 1px solid #d7d7d7;
   border-radius: 10px;
 }
 .form-container {
-max-width: 1200px; /* 或者其他适合你布局的宽度 */
-margin: 0 auto; /* 这会使容器水平居中 */
-padding: 0 15px; /* 添加一些左右内边距 */
+  max-width: 1200px; /* 或者其他适合你布局的宽度 */
+  margin: 0 auto; /* 这会使容器水平居中 */
+  padding: 0 15px; /* 添加一些左右内边距 */
 }
 
 .el-form-item {
-margin-bottom: 15px;
+  margin-bottom: 15px;
 }
 
 .el-select {
-width: 100%;
+  width: 100%;
 }
 .generate-button {
-margin-top: 10px; /* 在小屏幕上给按钮一些上边距 */
-margin-right: 25px;
+  margin-top: 10px; /* 在小屏幕上给按钮一些上边距 */
+  margin-right: 25px;
 }
 
 @media (max-width: 768px) {
-.selection-summary {
-  flex-direction: column;
-}
+  .selection-summary {
+    flex-direction: column;
+  }
 
-.generate-button {
-  margin-left: 0;
-  margin-top: 15px;
-  align-self: flex-end; /* 在小屏幕上将按钮右对齐 */
-}
+  .generate-button {
+    margin-left: 0;
+    margin-top: 15px;
+    align-self: flex-end; /* 在小屏幕上将按钮右对齐 */
+  }
 }
 .selection-summary {
-background-color: #f2f2f2;
-border-radius: 4px;
-padding: 15px;
-margin: 15px;
-display: flex;
-justify-content: space-between;
-align-items: center;
-margin-right: 85px;
-border-radius:10px;
+  background-color: #f2f2f2;
+  border-radius: 4px;
+  padding: 15px;
+  margin: 15px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-right: 85px;
+  border-radius: 10px;
 }
 
 .summary-content {
-flex-grow: 1;
+  flex-grow: 1;
 }
 
 .summary-label {
-font-weight: bold;
-color: #7f7f7f;
-margin-right: 10px;
+  font-weight: bold;
+  color: #7f7f7f;
+  margin-right: 10px;
 }
 
 .summary-text {
-color: #606266;
-margin: 0;
-margin-top: 3px;
-font-size: 14px;
+  color: #606266;
+  margin: 0;
+  margin-top: 3px;
+  font-size: 14px;
 }
-.title{
-width: 1200px;
-margin: 0 auto 15px;
-display: flex;
-align-items: center;
-
+.title {
+  width: 1200px;
+  margin: 0 auto 15px;
+  display: flex;
+  align-items: center;
 }
-.title_info{
-cursor: pointer;
-color: #1296db;
-font-size: 14px;
-margin-left: 10px;
+.title_info {
+  cursor: pointer;
+  color: #1296db;
+  font-size: 14px;
+  margin-left: 10px;
 }
 /* .chat-container .header ::v-deep .el-select .el-input--medium .el-input__inner{
   border: none;
@@ -607,285 +654,280 @@ margin-left: 10px;
   display: none;
 } */
 .chat-container .content {
-    display: flex;
-    justify-content: flex-start;
+  display: flex;
+  justify-content: flex-start;
 }
 
-.chat-container .content .left{
-    margin-left: 10px;
-    width: 330px;
-    height: 87vh;
+.chat-container .content .left {
+  margin-left: 10px;
+  width: 330px;
+  height: 87vh;
 }
 .chat-item {
-    border-radius: 10px;
-    padding: 10px;
-    border-bottom: 1px solid #e8edf2;
-    background-color: #f5f5fa;
-    margin-bottom: 10px;
-  }
-  
-  .chat-header {
-    display: flex;
-    align-items: center;
-    margin-bottom: 5px;
-  }
-  
-  .avatar {
-    width: 40px;
-    height: 40px;
-    border-radius: 50%;
-    margin-right: 10px;
-  }
-  
-  .user-info {
-    flex-grow: 1;
-  }
-  .user-info .info_header{
-    display: flex;
-    align-items: flex-end;
-  }
-  
-  .username {
-    font-weight: bold;
-  }
-  
-  .timestamp {
-    margin-left:13px;
-    font-size: 0.8em;
-    color: #888;
-  }
-  
-  .more-options {
-    cursor: pointer;
-  }
-  
-  .chat-message {
-    font-weight: bold;
-    margin-top: 8px;
-    margin-bottom: 10px;
-  }
-  
-  .chat-preview {
-    font-size: 0.9em;
-    color: #666;
-  }
-  
-  /* Adjust existing styles */
-  .left {
-    width: 300px; /* Increased width to accommodate the new layout */
-  }
-  
-  .chat-history {
-    width: 330px;
-    height: 100%;
-    overflow-y: auto;
-  }
+  border-radius: 10px;
+  padding: 10px;
+  border-bottom: 1px solid #e8edf2;
+  background-color: #f5f5fa;
+  margin-bottom: 10px;
+}
+
+.chat-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 5px;
+}
+
+.avatar {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  margin-right: 10px;
+}
+
+.user-info {
+  flex-grow: 1;
+}
+.user-info .info_header {
+  display: flex;
+  align-items: flex-end;
+}
+
+.username {
+  font-weight: bold;
+}
+
+.timestamp {
+  margin-left: 13px;
+  font-size: 0.8em;
+  color: #888;
+}
+
+.more-options {
+  cursor: pointer;
+}
+
+.chat-message {
+  font-weight: bold;
+  margin-top: 8px;
+  margin-bottom: 10px;
+}
+
+.chat-preview {
+  font-size: 0.9em;
+  color: #666;
+}
+
+/* Adjust existing styles */
+.left {
+  width: 300px; /* Increased width to accommodate the new layout */
+}
+
+.chat-history {
+  width: 330px;
+  height: 100%;
+  overflow-y: auto;
+}
 
 .messages {
-    width: 1180px; /* 设置消息列表宽度为屏幕宽度的 80% */
-    max-width: 90%; /* 限制最大宽度,避免超出视口 */
-    margin: 0 auto; /* 上下保持原有的 margin,左右自动调整以居中 */
-    overflow-y: auto; /* 如果消息太多,允许滚动 */
-    height: calc(100vh - 230px); /* 高度需要根据输入框和其他元素的高度调整 */
-    padding: 10px; /* 或你希望的内边距大小 */
-    margin-bottom: 120px;
+  width: 1180px; /* 设置消息列表宽度为屏幕宽度的 80% */
+  max-width: 90%; /* 限制最大宽度,避免超出视口 */
+  margin: 0 auto; /* 上下保持原有的 margin,左右自动调整以居中 */
+  overflow-y: auto; /* 如果消息太多,允许滚动 */
+  height: calc(100vh - 230px); /* 高度需要根据输入框和其他元素的高度调整 */
+  padding: 10px; /* 或你希望的内边距大小 */
+  margin-bottom: 120px;
 }
 /* 针对手机端的媒体查询 */
 @media screen and (max-width: 768px) {
-    .messages {
-        width: 100%;
-        max-width: 100%;
-        margin: 0;
-        margin-bottom: 120px;
-    }
+  .messages {
+    width: 100%;
+    max-width: 100%;
+    margin: 0;
+    margin-bottom: 120px;
+  }
 }
 .user-message {
-    display: flex;
-    flex-direction: column !important;
-    align-items: flex-end !important;
-  }
-  
-  .user-message .message-content {
-    order: 1;
-    text-align: right;
-  }
-  
-  .user-message .avatar {
-    order: 2;
-    margin-left: 10px;
-    margin-right: 0;
-  }
-  
-  .message-text {
-    margin-top: 5px;
-  }
+  display: flex;
+  flex-direction: column !important;
+  align-items: flex-end !important;
+}
+
+.user-message .message-content {
+  order: 1;
+  text-align: right;
+}
+
+.user-message .avatar {
+  order: 2;
+  margin-left: 10px;
+  margin-right: 0;
+}
+
+.message-text {
+  margin-top: 5px;
+}
 .messages li {
-    display: flex;
-    flex-direction: column;
+  display: flex;
+  flex-direction: column;
 
-    align-items: flex-start; /* 这将确保所有子元素对齐到容器的顶部 */
-   /*  margin-bottom: 10px; */
+  align-items: flex-start; /* 这将确保所有子元素对齐到容器的顶部 */
+  /*  margin-bottom: 10px; */
 }
 
 .avatar {
-    width: 40px; /* 头像的尺寸 */
-    height: 40px;
-    border-radius: 50%; /* 圆形头像 */
-    /*background-color: #e9e0e0;  暂时用灰色代替,你可以用图片替换 */
-    align-self: start; /* 这将覆盖 flex 容器的 align-items 并将头像对齐到顶部 */
-    margin-right: 10px; /* 按需调整以在头像和消息之间提供空间 */
+  width: 40px; /* 头像的尺寸 */
+  height: 40px;
+  border-radius: 50%; /* 圆形头像 */
+  /*background-color: #e9e0e0;  暂时用灰色代替,你可以用图片替换 */
+  align-self: start; /* 这将覆盖 flex 容器的 align-items 并将头像对齐到顶部 */
+  margin-right: 10px; /* 按需调整以在头像和消息之间提供空间 */
 }
 
-
 .user-avatar {
-
-    background-image: url('../../components/webAi/assets/用户.png') ;
-    background-position: center;
-    background-repeat: no-repeat;
-    background-size: cover;
-   /*  background-color: #93939d; */ /* 用户头像颜色 */
+  background-image: url("../../components/webAi/assets/用户.png");
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: cover;
+  /*  background-color: #93939d; */ /* 用户头像颜色 */
 }
 
 .bot-avatar {
-  
-    background-image: url('../../components/webAi/assets/机器人.png');
-    background-position: center;
-    background-repeat: no-repeat;
-    background-size: cover;
-    /* background-color: green; */ /* AI头像颜色 */
+  background-image: url("../../components/webAi/assets/机器人.png");
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: cover;
+  /* background-color: green; */ /* AI头像颜色 */
 }
 /* 针对手机端的媒体查询 */
 @media screen and (max-width: 768px) {
-    .user-avatar, .bot-avatar {
-        width: 30px;  /* 或其他更小的尺寸 */
-        height: 30px;
-    }
+  .user-avatar,
+  .bot-avatar {
+    width: 30px; /* 或其他更小的尺寸 */
+    height: 30px;
+  }
 }
 
-.message_name{
-    display: flex;
-    align-items: center;
-    padding-top: 5px;
-    padding-bottom: 5px;
+.message_name {
+  display: flex;
+  align-items: center;
+  padding-top: 5px;
+  padding-bottom: 5px;
 }
-.name{
-    margin-left: 8px;
-    /* margin-bottom: 5px; */
+.name {
+  margin-left: 8px;
+  /* margin-bottom: 5px; */
 }
 
 .message-content {
-    background-color: #f0f0f0; /* 消息背景 */
-    padding-left: 20px; /* 上下左右的内边距 */
-    padding-right: 20px; /* 上下左右的内边距 */
-    padding-top: 10px; /* 上下左右的内边距 */
-    padding-bottom: 0px; /* 上下左右的内边距 */
-    max-width: calc(90% - 10px); /* 计算头像和间距 */
-    border-radius: 7px; /* 边框圆角 */
-    /* margin-bottom: 10px; 和下一条消息之间的间距 */
-    /* 消息内容样式 */
-    display: flex;
-    flex-direction: column; /* 排列文本 */
-    margin-top: 0;
+  background-color: #f0f0f0; /* 消息背景 */
+  padding-left: 20px; /* 上下左右的内边距 */
+  padding-right: 20px; /* 上下左右的内边距 */
+  padding-top: 10px; /* 上下左右的内边距 */
+  padding-bottom: 0px; /* 上下左右的内边距 */
+  max-width: calc(90% - 10px); /* 计算头像和间距 */
+  border-radius: 7px; /* 边框圆角 */
+  /* margin-bottom: 10px; 和下一条消息之间的间距 */
+  /* 消息内容样式 */
+  display: flex;
+  flex-direction: column; /* 排列文本 */
+  margin-top: 0;
 }
 
-
 .message-text {
-    margin: 0;
-    white-space: normal; /* 确保文本会换行 */
-    word-wrap: break-word; /* 长单词或 URL 会被断开以适应容器宽度 */
-    overflow: auto;
+  margin: 0;
+  white-space: normal; /* 确保文本会换行 */
+  word-wrap: break-word; /* 长单词或 URL 会被断开以适应容器宽度 */
+  overflow: auto;
+  font-size: 14px;
 }
 .message-text ::v-deep p {
-    font-size: 14px !important;
-    margin: 5px 0 10px;
-    padding: 0;
-    font-size: inherit;
-    line-height: 22px;
-    font-weight: inherit;
-  }
-  .message-text ::v-deep ol{
-    padding-bottom: 10px ;
-
-  }
-  .message-text ::v-deep .source-section h3 {
-    margin: 20px 0 3px;
-    color: #333;
-    font-size: 1.1em;
-  }
-  .message-text ::v-deep ol li{
-    padding-top:3px ;
-  }
-  .message-text ::v-deep ol li a{
-    color: #1296db;
-  }
+  font-size: 14px !important;
+  margin: 5px 0 10px;
+  padding: 0;
+  font-size: inherit;
+  line-height: 22px;
+  font-weight: inherit;
+}
+.message-text ::v-deep ol {
+  padding-bottom: 10px;
+}
+.message-text ::v-deep .source-section h3 {
+  margin: 20px 0 3px;
+  color: #333;
+  font-size: 16px;
+}
+.message-text ::v-deep ol li {
+  padding-top: 3px;
+}
+.message-text ::v-deep ol li a {
+  font-size: 14px;
+  color: #1296db;
+}
 .input-container {
-    position: absolute; /* 改为绝对定位 */
-    bottom: -115px; /* 距离底部的距离 */
-    left: 50%;
-    transform: translateX(-50%);
-    width: 90%; /* 设置宽度为弹窗宽度的90% */
-    max-width: 800px; /* 设置最大宽度 */
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 10px;
-    background: white;
-    border-radius: 10px;
-    box-shadow: 0px 2px 5px rgba(0,0,0,0.1);
+  position: absolute; /* 改为绝对定位 */
+  bottom: -115px; /* 距离底部的距离 */
+  left: 50%;
+  transform: translateX(-50%);
+  width: 90%; /* 设置宽度为弹窗宽度的90% */
+  max-width: 800px; /* 设置最大宽度 */
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 10px;
+  background: white;
+  border-radius: 10px;
+  box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
 }
 
 textarea {
-    font-size: 16px;
-    width: 100%; /* 可以根据需要调整 */
-    padding: 10px;
-    border: 1px solid #ccc;
-    border-radius: 4px;
-    border: none;
+  font-size: 16px;
+  width: 100%; /* 可以根据需要调整 */
+  padding: 10px;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  border: none;
   outline: none;
   resize: none;
 }
 
 .input-container textarea {
-    width: 100%; /* 输入框占据所有可用空间 */
-    padding: 10px;
-    margin-right: 10px; /* 与按钮的间隔 */
-    box-sizing: border-box;
+  width: 100%; /* 输入框占据所有可用空间 */
+  padding: 10px;
+  margin-right: 10px; /* 与按钮的间隔 */
+  box-sizing: border-box;
 }
 
 .input-container button {
-    min-width: 80px; /* 例如,按钮的最小宽度为 100px */
-    white-space: nowrap; /* 防止文字折行 */
-    padding: 10px 20px;
-    cursor: pointer;
+  min-width: 80px; /* 例如,按钮的最小宽度为 100px */
+  white-space: nowrap; /* 防止文字折行 */
+  padding: 10px 20px;
+  cursor: pointer;
 }
 
-
-
-
 .sender-name {
-    font-weight: bold;
-    color: #333;
-    margin-left: -5px;
+  font-weight: bold;
+  color: #333;
+  margin-left: -5px;
 }
 
 .btn {
-    font-size: 16px;
-    color: #ffffff;
-    border-radius: 5px;
-    border: none;
-    background-color: #3cbade;
-    padding: 10px 20px;  /* 添加内边距 */
-    width: auto;  /* 让宽度自适应内容 */
+  font-size: 16px;
+  color: #ffffff;
+  border-radius: 5px;
+  border: none;
+  background-color: #3cbade;
+  padding: 10px 20px; /* 添加内边距 */
+  width: auto; /* 让宽度自适应内容 */
 }
 
 /* 针对手机端的媒体查询 */
 @media screen and (max-width: 768px) {
-    .btn {
-        font-size: 14px;  /* 稍微减小字体大小 */
-        padding: 8px 16px;  /* 减小内边距 */
-        
-        max-width: 80px;  /* 限制最大宽度 */
-        margin: 0 auto;  /* 居中显示 */
-    }
+  .btn {
+    font-size: 14px; /* 稍微减小字体大小 */
+    padding: 8px 16px; /* 减小内边距 */
+
+    max-width: 80px; /* 限制最大宽度 */
+    margin: 0 auto; /* 居中显示 */
+  }
 }
 </style>

Some files were not shown because too many files changed in this diff