فهرست منبع

修改图片文档展示

yangg 6 ماه پیش
والد
کامیت
db13e83a5e

+ 1 - 1
.env.development

@@ -3,7 +3,7 @@ ENV = 'development'
 port = 8080
 # base api
 VUE_APP_BASE_AI_API='https://ylaiapi.raycos.com.cn'
-VUE_APP_BASE_API = 'http://192.168.66.163:8084'
+VUE_APP_BASE_API = 'http://183.195.216.54:8084'
 #192.168.66.187
 #朱:192.168.66.163
 #测试服务器:121.36.251.245

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
dist/index.html


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
dist/static/css/app.0426da3f.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
dist/static/css/chunk-15a824ae.c215501b.css


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
dist/static/js/app.600f4d0c.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
dist/static/js/app.f24048c5.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
dist/static/js/chunk-03ae2181.dac25b79.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
dist/static/js/chunk-15a824ae.eba486da.js


+ 358 - 15
src/components/ai.vue

@@ -34,7 +34,26 @@
             </div>
             <!-- 使用 v-html 来渲染 Markdown 文本 -->
             <div class="message-content">
-              <div
+              <!-- 添加文件预览区域 -->
+              <div class="message-files" v-if="message.files && message.files.length > 0">
+                <div 
+                  v-for="(file, index) in message.files" 
+                  :key="index"
+                  class="message-file-item"
+                >
+                  <img 
+                    v-if="file.type.startsWith('image/')" 
+                    :src="file.url" 
+                    class="message-image"
+                    alt="uploaded image"
+                  />
+                  <div v-else class="message-document">
+                    <i class="el-icon-document"></i>
+                    <span>{{ file.name }}</span>
+                  </div>
+                </div>
+              </div>
+              <div 
                 class="message-text"
                 v-html="renderMarkdown(message.message)"
                 @click="handleLinkClick"
@@ -43,7 +62,7 @@
           </li>
         </ul>
       </div>
-      <div class="input-container">
+      <!-- <div class="input-container">
         <textarea
           v-model="userInput"
           rows="4"
@@ -51,6 +70,79 @@
           placeholder="发送消息..."
         ></textarea>
         <button @click="sendMessage" class="btn">发送</button>
+      </div> -->
+      <div class="input-container">
+        <!-- 文件上传预览区 -->
+        <div
+          class="upload-preview"
+          v-if="uploadFiles.length > 0 || documents.length > 0"
+        >
+          <div
+            v-for="(file, index) in uploadFiles"
+            :key="'img-' + index"
+            class="preview-item"
+          >
+            <div class="preview-content">
+              <img
+                v-if="file.type.startsWith('image/')"
+                :src="file.preview"
+                class="preview-image"
+                alt="preview"
+              />
+              <div v-else class="file-info">
+                <i class="el-icon-document"></i>
+                <span>{{ file.name }}</span>
+              </div>
+            </div>
+            <i class="el-icon-close remove-file" @click="removeFile(index)"></i>
+          </div>
+
+          <div
+            v-for="(doc, index) in documents"
+            :key="'doc-' + index"
+            class="preview-item document-item"
+          >
+            <div class="preview-content">
+              <div class="file-info">
+                <i class="el-icon-document"></i>
+                <span>{{ doc.name }}</span>
+              </div>
+            </div>
+            <i
+              class="el-icon-close remove-file"
+              @click="removeDocument(index)"
+            ></i>
+          </div>
+        </div>
+
+        <!-- 输入区域 -->
+        <div class="input-area">
+          <textarea
+            v-model="userInput"
+            rows="4"
+            @keydown="handleKeydown"
+            placeholder="发送消息..."
+          ></textarea>
+
+          <!-- 工具栏 -->
+          <div class="toolbar">
+            <div class="left-tools">
+              <input
+                type="file"
+                ref="fileInput"
+                multiple
+                @change="handleFileUpload"
+                accept="image/*,.pdf,.doc,.docx,.txt"
+                style="display: none"
+              />
+              <button class="tool-btn" @click="$refs.fileInput.click()">
+                <i class="el-icon-upload2"></i>
+              </button>
+            </div>
+            <!--  <button @click="playAudio" class="btn">播放音频</button> -->
+            <button @click="sendMessage" class="btn">发送</button>
+          </div>
+        </div>
       </div>
       <!-- </div> -->
     </div>
@@ -68,7 +160,7 @@ import LoginModal from "@/components/LoginModal";
 import MarkdownIt from "markdown-it";
 import { pcInnerAi, getMinioURl } from "@/api/api";
 import { modelList, get_default } from "@/api/knowledge";
-import axios from 'axios';
+import axios from "axios";
 const md = new MarkdownIt();
 
 export default {
@@ -112,6 +204,8 @@ export default {
         document: "",
       },
       currentChat_id: "",
+      uploadFiles: [], // 存储上传的文件
+      documents: [], // 新增:专门存储文档类型文件的数组
     };
   },
   mounted() {
@@ -147,6 +241,65 @@ export default {
     this.init();
   },
   methods: {
+    /* 播放音频 */
+    playAudio(audioBase64) {
+      console.log(audioBase64);
+      // 创建音频元素
+      const audio = new Audio(audioBase64); // 播放音频
+      audio.play().catch((error) => {
+        console.error("Error playing audio:", error);
+      });
+    },
+    async handleFileUpload(event) {
+      const files = Array.from(event.target.files);
+
+      for (const file of files) {
+        try {
+          const formData = new FormData();
+          formData.append("file", file);
+
+          const response = await axios.post(
+            `${process.env.VUE_APP_BASE_AI_API}/upload/file`, //process.env.VUE_APP_BASE_API
+            formData,
+            {
+              headers: {
+                "Content-Type": "multipart/form-data",
+              },
+            }
+          );
+
+          const fileUrl = response.data.data.fileUrl;
+          const fileInfo = {
+            name: file.name,
+            type: file.type,
+            preview: fileUrl,
+            url: fileUrl,
+          };
+
+          // 根据文件类型分别存储
+          if (file.type.startsWith("image/")) {
+            this.uploadFiles.push(fileInfo);
+          } else {
+            // 文档类型:pdf, doc, docx, txt 等
+            this.documents.push(fileInfo);
+          }
+        } catch (error) {
+          console.error("文件上传失败:", error);
+          this.$message.error(`文件 ${file.name} 上传失败`);
+        }
+      }
+
+      event.target.value = "";
+    },
+
+    removeFile(index) {
+      this.uploadFiles.splice(index, 1);
+    },
+    removeDocument(index) {
+      this.documents.splice(index, 1);
+    },
+
+    /*  */
     checkAIData() {
       const aiData = localStorage.getItem("AIData");
       this.showLoginModal = !aiData;
@@ -280,8 +433,28 @@ export default {
       }
       const chatId = this.getUuid();
       let message = this.userInput.trim();
+      const imageUrls = this.uploadFiles
+        .filter((file) => file.type.startsWith("image/"))
+        .map((file) => file.url);
+
+      // 获取文档 URL
+      const documentUrls = this.documents.map((doc) => doc.url);
       if (!message) return;
 
+      // 添加文件信息到消息中
+      const files = [
+        ...this.uploadFiles.map(file => ({
+          type: file.type,
+          url: file.url,
+          name: file.name
+        })),
+        ...this.documents.map(doc => ({
+          type: 'document',
+          url: doc.url,
+          name: doc.name
+        }))
+      ];
+
       // 添加用户消息
       this.messages.push({
         time: Date.now(),
@@ -289,9 +462,11 @@ export default {
         messageType: "TEXT",
         user: "user",
         done: true,
+        files: files // 添加文件数组
       });
       this.userInput = "";
-
+      this.uploadFiles = [];
+      this.documents = [];
       // 添加机器人的响应消息
       const botMessage = {
         user: "bot",
@@ -312,13 +487,18 @@ export default {
 
       try {
         // 直接使用 axios 而不是 this.$axios
-        const response = await axios.post(`${process.env.VUE_APP_BASE_AI_API}/chatbot/chat`, {
-          message: message,
-          chat_config_id: this.currentChat_id,
-          user_id: this.$store.state.user.id,
-          session_id: this.session_id || "",
-          source: "pc",
-        });
+        const response = await axios.post(
+          `${process.env.VUE_APP_BASE_AI_API}/chatbot/multimodal`,
+          {
+            message: message,
+            chat_config_id: this.currentChat_id,
+            user_id: this.$store.state.user.id,
+            session_id: this.session_id || "",
+            source: "pc",
+            image_urls: imageUrls,
+            documents: documentUrls, // 添加文档 URLs
+          }
+        );
 
         // 停止思考动画
         thinkingController.abort();
@@ -327,14 +507,19 @@ export default {
         if (response.status === 200) {
           const result = response.data;
           this.session_id = result.data.session_id;
-          
+
           if (this.$route.name !== "ai") {
             const additionalInput = result.data.milvus_ids;
             this.idArray = additionalInput;
             const idObject = { ids: this.idArray };
             await this.getMinioUrls(idObject);
           }
-
+          if (
+            response.data.data.audio_info &&
+            response.data.data.audio_info.audio
+          ) {
+            this.playAudio(response.data.data.audio_info.audio);
+          }
           this.handleResponse(result, botMessage);
         } else {
           throw new Error(`HTTP error! status: ${response.status}`);
@@ -486,7 +671,7 @@ export default {
 
       const renderedHtml = md.render(rawMarkdown);
 
-      // 使用 setTimeout 确保 DOM 更新后再添加事件监听器
+      // 使用 setTimeout ���确保 DOM 更新后再添加事件监听器
       setTimeout(() => {
         const links = document.querySelectorAll(".custom-link");
         links.forEach((link) => {
@@ -851,7 +1036,8 @@ export default {
   max-width: 800px; /* 设置最大宽度 */
   display: flex;
   justify-content: space-between;
-  align-items: center;
+  align-items: flex-start;
+  flex-direction: column;
   padding: 10px;
   background: #fdfdfd;
   border-radius: 10px;
@@ -910,4 +1096,161 @@ textarea {
     margin: 0 auto; /* 居中显示 */
   }
 }
+
+/* 新输入框 */
+.upload-preview {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  max-height: 100px;
+  overflow-y: auto;
+}
+
+.preview-item {
+  position: relative;
+  width: 60px;
+  height: 20px;
+  border-radius: 4px;
+  overflow: hidden;
+  border: 1px solid #e0e0e0;
+}
+
+.preview-content {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.preview-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.file-info {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  padding: 5px;
+  text-align: center;
+}
+
+.file-info span {
+  font-size: 12px;
+  word-break: break-word;
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+.remove-file {
+  position: absolute;
+  top: 2px;
+  right: 2px;
+  background: rgba(0, 0, 0, 0.5);
+  color: white;
+  border-radius: 50%;
+  padding: 2px;
+  cursor: pointer;
+  font-size: 12px;
+}
+
+.input-area {
+  width: 100%;
+}
+
+.toolbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: 10px;
+}
+
+.left-tools {
+  display: flex;
+  gap: 10px;
+}
+
+.tool-btn {
+  background: none;
+  border: none;
+  cursor: pointer;
+  padding: 5px;
+  color: #666;
+  font-size: 20px;
+}
+
+.tool-btn:hover {
+  color: #3cbade;
+}
+
+.document-item {
+  width: 120px; /* 文档预览项可以设置得更宽一些 */
+  height: 60px;
+  background-color: #f5f5f5;
+}
+
+.document-item .file-info {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+}
+
+.document-item .file-info i {
+  font-size: 20px;
+  color: #666;
+}
+
+.document-item .file-info span {
+  font-size: 12px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.message-files {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  margin-bottom: 8px;
+}
+
+.message-file-item {
+  max-width: 200px;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.message-image {
+  width: 100%;
+  height: auto;
+  max-height: 200px;
+  object-fit: cover;
+  border-radius: 4px;
+}
+
+.message-document {
+  display: flex;
+  align-items: center;
+  padding: 8px;
+  background-color: #f5f5f5;
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+}
+
+.message-document i {
+  font-size: 20px;
+  color: #666;
+  margin-right: 8px;
+}
+
+.message-document span {
+  font-size: 14px;
+  color: #333;
+  word-break: break-all;
+}
 </style>

+ 3 - 3
src/components/chartIcon/index.vue

@@ -31,7 +31,7 @@ export default {
     return {
       showChat: false,
       chatWidth: 500,
-      chatHeight: 625,
+      chatHeight: 735,
       isResizing: false,
       startX: 0,
       startY: 0,
@@ -48,7 +48,7 @@ export default {
     },
     messagesHeight() {
       // 减去其他元素的高度(如头部、输入框等)
-      return `calc(${this.chatHeight}px - 195px)`;
+      return `calc(${this.chatHeight}px - 251px)`;
     },
   },
   watch: {
@@ -107,7 +107,7 @@ export default {
         const messagesEl = chatBox.$el.querySelector('.messages');
         if (messagesEl) {
           const chatWindowHeight = this.$refs.chatWindow.offsetHeight;
-          const otherElementsHeight = 195; // Adjust this value as needed
+          const otherElementsHeight = 251; // Adjust this value as needed
           messagesEl.style.height = `${chatWindowHeight - otherElementsHeight}px`;
         }
       }

+ 82 - 12
src/views/login/aiIndex.vue

@@ -34,6 +34,33 @@
             </div>
             <!-- 使用 v-html 来渲染 Markdown 文本 -->
             <div class="message-content">
+              <!-- 添加媒体预览区域 -->
+              <div v-if="message.files" class="message-media">
+                <!-- 图片预览 -->
+                <div v-if="message.files.images && message.files.images.length > 0" class="image-previews">
+                  <img 
+                    v-for="(img, index) in message.media.images" 
+                    :key="index"
+                    :src="img"
+                    class="message-image"
+                    alt="uploaded image"
+                    @click="previewImage(img)"
+                  />
+                </div>
+                <!-- 文档预览 -->
+                <div v-if="message.media.documents && message.media.documents.length > 0" class="document-previews">
+                  <div 
+                    v-for="(doc, index) in message.media.documents" 
+                    :key="index"
+                    class="document-preview"
+                    @click="openDocument(doc.url)"
+                  >
+                    <i class="el-icon-document"></i>
+                    <span class="document-name">{{ doc.name }}</span>
+                  </div>
+                </div>
+              </div>
+              <!-- 消息文本内容 -->
               <div
                 class="message-text"
                 v-html="renderMarkdown(message.message)"
@@ -376,7 +403,7 @@ export default {
         console.log("WebSocket 连接已建立:", event);
       };
       this.websocket.onmessage = (event) => {
-        // 解����收到的消息
+        // 解收到的消息
         const result = JSON.parse(event.data);
 
         // 检查消息是否完结
@@ -462,7 +489,7 @@ export default {
               session_id: this.session_id || "",
               source: "pc",
               image_urls: imageUrls,
-              documents: documentUrls, // 加文档 URLs
+              documents: documentUrls, // ��加文档 URLs
               //history_limit: 10
             },
           });
@@ -478,13 +505,12 @@ export default {
           thinkingController.abort();
           await thinkingPromise;
 
-          if (this.$route.name !== "ai") {
+          /* if (this.$route.name !== "ai") { */
             const additionalInput = response.data.data.milvus_ids;
             this.idArray = additionalInput;
             const idObject = { ids: this.idArray };
-            console.log("ID Object:", idObject);
             await this.getMinioUrls(idObject);
-          }
+         /*  } */
           // 如果有音频,播放音频
           if (
             response.data.data.audio_info &&
@@ -588,9 +614,9 @@ export default {
 
       let mainText = data;
       let sourceText = "";
-
+/*  this.$route.name !== "ai"&& */
       if (
-        this.$route.name !== "ai" &&
+        
         this.minioUrls &&
         this.minioUrls.length > 0
       ) {
@@ -602,7 +628,7 @@ export default {
         sourceText += "</ol></div>";
       }
       console.log(mainText);
-      // 先进主要内容的打字效果
+      // 先进主要内容的打字效果
       await this.typeMessage(existingMessage, mainText);
 
       // 直接添加资料来源部分
@@ -638,7 +664,7 @@ export default {
         linkify: true,
       });
 
-      // 自定义链接渲染规���
+      // 自定义链接渲染规
       md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
         const token = tokens[idx];
         const hrefIndex = token.attrIndex("href");
@@ -872,7 +898,7 @@ export default {
 }
 
 .messages {
-  width: 1180px; /* 设置消息列表宽度为屏宽度的 80% */
+  width: 1180px; /* 设置消息列表宽度为屏���宽度的 80% */
   max-width: 90%; /* 限制最大宽度,避免超出视口 */
   margin: 0 auto; /* 上下保持原有的 margin,左右自动调整以居中 */
   overflow-y: auto; /* 如果消息太多,允许滚动 */
@@ -979,7 +1005,7 @@ export default {
 .message-text {
   margin: 0;
   white-space: normal; /* 确保文本会换行 */
-  word-wrap: break-word; /* 长单词或 URL 会被断开���适应容器宽度 */
+  word-wrap: break-word; /* 长单词或 URL 会被断开适应容器宽度 */
   overflow: auto;
   font-size: 14px;
 }
@@ -1037,7 +1063,7 @@ textarea {
 .input-container textarea {
   width: 100%; /* 输入框占据所有可用空间 */
   padding: 10px;
-  margin-right: 10px; /* 与按钮的间�� */
+  margin-right: 10px; /* 与按钮的间 */
   box-sizing: border-box;
 }
 
@@ -1189,4 +1215,48 @@ textarea {
   text-overflow: ellipsis;
   white-space: nowrap;
 }
+
+/* 添加新的样式 */
+.message-media {
+  margin-bottom: 10px;
+}
+
+.image-previews {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  margin-bottom: 8px;
+}
+
+.message-image {
+  max-width: 200px;
+  max-height: 200px;
+  border-radius: 4px;
+  object-fit: cover;
+}
+
+.document-previews {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.document-preview {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+}
+
+.document-preview i {
+  font-size: 20px;
+  color: #666;
+}
+
+.document-name {
+  font-size: 14px;
+  color: #333;
+}
 </style>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است