Просмотр исходного кода

[Feat 0000]智能问答 文件预览功能 获取历史功能优化

bobo04052021@163.com 5 месяцев назад
Родитель
Сommit
761f35f86d

+ 15 - 0
package-lock.json

@@ -4162,6 +4162,21 @@
         "path-browserify": "^1.0.1"
       }
     },
+    "@vue-office/docx": {
+      "version": "1.6.3",
+      "resolved": "https://registry.npmmirror.com/@vue-office/docx/-/docx-1.6.3.tgz",
+      "integrity": "sha512-Cs+3CAaRBOWOiW4XAhTwwxJ0dy8cPIf6DqfNvYcD3YACiLwO4kuawLF2IAXxyijhbuOeoFsfvoVbOc16A/4bZA=="
+    },
+    "@vue-office/excel": {
+      "version": "1.7.14",
+      "resolved": "https://registry.npmmirror.com/@vue-office/excel/-/excel-1.7.14.tgz",
+      "integrity": "sha512-pVUgt+emDQUnW7q22CfnQ+jl43mM/7IFwYzOg7lwOwPEbiVB4K4qEQf+y/bc4xGXz75w1/e3Kz3G6wAafmFBFg=="
+    },
+    "@vue-office/pdf": {
+      "version": "2.0.10",
+      "resolved": "https://registry.npmmirror.com/@vue-office/pdf/-/pdf-2.0.10.tgz",
+      "integrity": "sha512-yHVLrMAKpMPBkhBwofFyGEtEeJF0Zd7oGmf56Pe5aj/xObdRq3E1CIZqTqhWJNgHV8oLQqaX0vs4p5T1zq+GIA=="
+    },
     "@vue/babel-helper-vue-transform-on": {
       "version": "1.4.0",
       "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.4.0.tgz",

+ 3 - 0
package.json

@@ -30,6 +30,9 @@
     "@logicflow/core": "^1.2.12",
     "@logicflow/extension": "^1.2.13",
     "@qiaoqiaoyun/drag-free": "^1.1.4",
+    "@vue-office/docx": "^1.6.3",
+    "@vue-office/excel": "^1.7.14",
+    "@vue-office/pdf": "^2.0.10",
     "@vue/runtime-core": "^3.3.4",
     "@vue/shared": "^3.3.4",
     "@vueuse/core": "^10.4.1",

+ 7 - 0
src/assets/icons/Fold1.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16.761" height="16.761" viewBox="0 0 16.761 16.761">
+  <g id="展开" transform="translate(0 16.761) rotate(-90)">
+    <path id="路径_57023" data-name="路径 57023" d="M2.514,15.084a.838.838,0,0,1-.839-.838V2.514a.839.839,0,0,1,.839-.838H14.246a.839.839,0,0,1,.839.838V14.247a.838.838,0,0,1-.839.838H2.514ZM0,14.247a2.514,2.514,0,0,0,2.514,2.514H14.246a2.514,2.514,0,0,0,2.514-2.514V2.514A2.514,2.514,0,0,0,14.246,0H2.514A2.514,2.514,0,0,0,0,2.514V14.247Z" transform="translate(0 0)" fill="#fff"/>
+    <path id="路径_57024" data-name="路径 57024" d="M0,.839a.838.838,0,0,0,.838.838H15.923a.838.838,0,1,0,0-1.676H.838A.838.838,0,0,0,0,.839ZM6.111,5.274a.838.838,0,0,1,1.186,0L8.38,6.357,9.463,5.274a.838.838,0,1,1,1.186,1.186L8.972,8.135a.838.838,0,0,1-1.185,0L6.111,6.459a.838.838,0,0,1,0-1.186Z" transform="translate(0 4.19)" fill="#fff"/>
+    <path id="路径_57025" data-name="路径 57025" d="M.838,6.7a.838.838,0,0,0,.838-.838V.838A.838.838,0,0,0,0,.838V5.866A.838.838,0,0,0,.838,6.7Zm15.085,0a.838.838,0,0,0,.838-.838V.838a.838.838,0,0,0-1.675,0V5.866A.838.838,0,0,0,15.923,6.7Z" transform="translate(0 1.677)" fill="#fff"/>
+  </g>
+</svg>

+ 3 - 0
src/assets/icons/addB.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="15.978" height="15.978" viewBox="0 0 15.978 15.978">
+  <path id="路径_57020" data-name="路径 57020" d="M70.415,62.7A7.989,7.989,0,1,0,78.4,70.685,7.989,7.989,0,0,0,70.415,62.7Zm4.788,8.9H71.3V75.5H69.48V71.594H65.575V69.776H69.48V65.871H71.3v3.905H75.2Z" transform="translate(-62.426 -62.696)" fill="#fff"/>
+</svg>

+ 3 - 0
src/assets/icons/history.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="13.03" height="13" viewBox="0 0 13.03 13">
+  <path id="历史对话" d="M115.055,185.072a5.7,5.7,0,0,0-.13,1.042,4.532,4.532,0,0,0,4.56,4.56,3.935,3.935,0,0,0,1.954-.456v2.476a1.752,1.752,0,0,1-1.759,1.759H117.01l-.977,1.173a.557.557,0,0,1-.847.065.064.064,0,0,1-.065-.065l-.977-1.173h-2.671a1.752,1.752,0,0,1-1.759-1.759v-5.863a1.752,1.752,0,0,1,1.759-1.759Zm4.43,4.3a3.257,3.257,0,1,1,3.257-3.257A3.226,3.226,0,0,1,119.485,189.372Zm.391-3.648v-1.368a.4.4,0,1,0-.782,0v1.694a.239.239,0,0,0,.065.2.358.358,0,0,0,.326.2h1.173a.4.4,0,1,0,0-.782h-.782Z" transform="translate(-109.713 -182.857)" fill="#fff"/>
+</svg>

+ 7 - 0
src/assets/icons/unfold.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16.761" height="16.761" viewBox="0 0 16.761 16.761">
+  <g id="收起" transform="translate(-85.351 102.11) rotate(-90)">
+    <path id="路径_57023" data-name="路径 57023" d="M87.864,87.027a.838.838,0,0,0-.839.838V99.6a.839.839,0,0,0,.839.838H99.6a.839.839,0,0,0,.839-.838V87.865a.838.838,0,0,0-.839-.838H87.864Zm-2.514.838a2.514,2.514,0,0,1,2.514-2.514H99.6a2.514,2.514,0,0,1,2.514,2.514V99.6a2.514,2.514,0,0,1-2.514,2.514H87.864A2.514,2.514,0,0,1,85.35,99.6V87.865Z" transform="translate(0 0)" fill="#fff"/>
+    <path id="路径_57024" data-name="路径 57024" d="M85.35,306.244a.838.838,0,0,1,.838-.838h15.084a.838.838,0,0,1,0,1.676H86.188a.838.838,0,0,1-.838-.839Zm6.111-4.435a.838.838,0,0,0,1.186,0l1.083-1.083,1.083,1.083A.838.838,0,1,0,96,300.623l-1.676-1.675a.838.838,0,0,0-1.185,0l-1.676,1.675a.838.838,0,0,0,0,1.186Z" transform="translate(0 -209.162)" fill="#fff"/>
+    <path id="路径_57025" data-name="路径 57025" d="M86.188,512a.838.838,0,0,1,.838.838v5.028a.838.838,0,0,1-1.675,0v-5.028A.838.838,0,0,1,86.188,512Zm15.084,0a.838.838,0,0,1,.838.838v5.028a.838.838,0,0,1-1.675,0v-5.028A.838.838,0,0,1,101.273,512Z" transform="translate(0 -418.27)" fill="#fff"/>
+  </g>
+</svg>

+ 406 - 173
src/components/AIChat/MiniChat.vue

@@ -5,162 +5,193 @@
       <img src="@/assets/images/vent/home/wakeBtn.png" />
     </div>
   </div>
-  <div v-if="isShowChatBroad" class="mini-chat">
-    <!-- 左侧折叠区域 -->
-    <div class="left-side" :class="{ collapsed: isFold }" id="leftSide">
-      <div
-        class="addBtn"
-        :style="{
-          backgroundColor: isFold ? '' : '#2cb6ff',
-          width: isFold ? '20px' : 'auto',
-        }"
-        @click="addNew"
-      >
-        <SvgIcon v-if="isFold" name="add" size="20" :color="''" />
-        <span v-if="!isFold" class="btn-text">添加新对话</span>
-      </div>
-      <div
-        v-if="isFold"
-        class="historyBtn"
-        :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/history.svg' : '/src/assets/images/vent/home/history.svg'})` }"
-        @click="addNew"
-      ></div>
-      <div v-else class="historyBtn1">
-        <span
-          class="btn-text-bg"
+  <div class="container">
+    <div v-if="isShowChatBroad" class="mini-chat">
+      <!-- 左侧折叠区域 -->
+      <div class="left-side" :class="{ collapsed: isFold }" id="leftSide">
+        <div
+          class="addBtn"
           :style="{
-            backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/history.svg' : ''})`,
+            backgroundColor: isFold ? '' : '#2cb6ff',
+            width: isFold ? '20px' : 'auto',
           }"
-        ></span>
-        <span v-if="!isFold" class="btn-text">历史对话</span>
-        <a-list style="width: 130px" :split="false" :data-source="sessionHistory" :scroll="200" class="custom-list">
-          <template #renderItem="{ item }">
-            <a-list-item
-              class="session-item"
-              :style="{
-                padding: '8px 10px 0 8px',
-                color: '#5e7081',
-                fontSize: '10px',
-                position: 'relative', // 新增定位
-              }"
-            >
-              <!-- 新增flex布局容器 -->
-              <div style="width: 100%">
-                <div v-if="editingId !== item.id" class="text-container">
-                  <span class="edit-text" @click="sessionsHistory(item.id)">{{ item.name }}</span>
-                  <div class="btn-container">
-                    <EditOutlined class="edit-icon" @click="startEditing(item)" />
-                    <DeleteOutlined class="delete-icon" @click="startDelete(item)" />
+          @click="addNew"
+        >
+          <SvgIcon v-if="isFold" name="add" size="20" :color="''" />
+          <span v-if="!isFold" class="btn-text">添加新对话</span>
+        </div>
+        <div v-if="isFold" class="historyBtn" @click="addNew">
+          <SvgIcon v-if="isFold" name="history" size="20" :color="''" />
+        </div>
+        <div v-else class="historyBtn1">
+          <span v-if="!isFold" class="btn-text">历史对话</span>
+          <a-list style="width: 136px" :split="false" :data-source="sessionHistory" :scroll="190" class="custom-list">
+            <template #renderItem="{ item }">
+              <a-list-item
+                class="session-item"
+                :style="{
+                  padding: '8px 10px 0 8px',
+                  color: '#5e7081',
+                  fontSize: '10px',
+                  position: 'relative', // 新增定位
+                }"
+              >
+                <!-- 新增flex布局容器 -->
+                <div style="width: 100%">
+                  <div v-if="editingId !== item.id" class="text-container">
+                    <span class="edit-text" @click="sessionsHistory(item.id)">{{ item.name }}</span>
+                    <div class="btn-container">
+                      <EditOutlined class="edit-icon" @click="startEditing(item)" />
+                      <DeleteOutlined class="delete-icon" @click="startDelete(item)" />
+                    </div>
                   </div>
+                  <!-- 输入框 -->
+                  <a-input
+                    size="small"
+                    v-else
+                    v-model:value="editText"
+                    v-focus
+                    @blur="handleSave(item)"
+                    @keyup.enter="handleSave(item)"
+                    class="edit-input"
+                  />
                 </div>
-                <!-- 输入框 -->
-                <a-input
-                  size="small"
-                  v-else
-                  v-model:value="editText"
-                  v-focus
-                  @blur="handleSave(item)"
-                  @keyup.enter="handleSave(item)"
-                  class="edit-input"
-                />
-              </div>
-            </a-list-item>
-          </template>
-        </a-list>
+              </a-list-item>
+            </template>
+          </a-list>
+        </div>
+        <div class="foldBtn" @click="fold">
+          <SvgIcon v-if="isFold" name="Fold1" size="20" :color="''" />
+          <SvgIcon v-else name="unfold" size="20" :color="''" />
+        </div>
       </div>
-      <div
-        class="foldBtn"
-        :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/Fold.svg' : '/src/assets/images/vent/home/unfold.svg'})` }"
-        @click="fold"
-      ></div>
-    </div>
-    <!-- 右侧对话框 -->
-    <div class="right-side">
-      <div class="title"> 智能问答 </div>
-      <!-- 对话区域 -->
-      <div class="dialog-area" ref="dialogAreaRef">
-        <div
-          v-for="message in messageHistory"
-          :key="message.id"
-          class="flex items-center w-100%"
-          :style="{ alignSelf: message.type === 'user' ? 'flex-end' : 'flex-start' }"
-        >
-          <template v-if="message.type === 'user'">
-            <div class="flex-grow-1"></div>
-            <div class="message-wrapper user-message-wrapper">
-              <div class="ask-message">{{ message.parsedContent }}</div>
-              <div class="copy-icon-container">
-                <CopyOutlined class="copy-icon" @click="copyToClipboard(message.parsedContent)" title="复制消息" />
-                <EditOutlined class="copy-icon" @click="editAsk(message.parsedContent)" title="重新编辑" />
+      <!-- 右侧对话框 -->
+      <div class="right-side">
+        <div class="title"> 智能问答 </div>
+        <!-- 对话区域 -->
+        <div class="dialog-area" ref="dialogAreaRef">
+          <div
+            v-for="message in messageHistory"
+            :key="message.id"
+            class="flex items-center w-100%"
+            :style="{ alignSelf: message.type === 'user' ? 'flex-end' : 'flex-start' }"
+          >
+            <template v-if="message.type === 'user'">
+              <div class="flex-grow-1"></div>
+              <div class="message-wrapper user-message-wrapper">
+                <div class="ask-message">{{ message.parsedContent }}</div>
+                <div class="copy-icon-container">
+                  <CopyOutlined class="copy-icon" @click="copyToClipboard(message.parsedContent)" title="复制消息" />
+                  <EditOutlined class="copy-icon" @click="editAsk(message.parsedContent)" title="重新编辑" />
+                </div>
               </div>
-            </div>
-          </template>
-          <template v-else>
-            <SvgIcon size="40" class="answerIcon" name="ai-logo" />
-            <div class="message-wrapper ai-message-wrapper">
-              <div class="answer-message">
-                <div v-if="message.parsedContentR1" class="thinking-section">
-                  <div class="thinking-header" @click="isShow(message)">
-                    <span class="thinking-title"
-                      >思考过程:<RightOutlined v-if="!message.isShowThink" /> <DownOutlined v-if="message.isShowThink"
-                    /></span>
+            </template>
+            <template v-else>
+              <SvgIcon size="40" class="answerIcon" name="ai-logo" />
+              <div class="message-wrapper ai-message-wrapper">
+                <div class="answer-message">
+                  <div v-if="message.parsedContentR1" class="thinking-section">
+                    <div class="thinking-header" @click="isShow(message)">
+                      <span class="thinking-title"
+                        >思考过程:<RightOutlined v-if="!message.isShowThink" /> <DownOutlined v-if="message.isShowThink"
+                      /></span>
+                    </div>
+                    <div v-show="message.isShowThink" class="color-gray font-size-12px" v-html="message.parsedContentR1"></div>
                   </div>
-                  <div v-show="message.isShowThink" class="color-gray font-size-12px" v-html="message.parsedContentR1"></div>
+                  <div v-if="message.parsedContent" v-html="message.parsedContent"> </div>
+                </div>
+                <div class="copy-icon-container">
+                  <CopyOutlined class="copy-icon" @click="copyToClipboard(message.parsedContent)" title="复制消息" />
+                  <RedoOutlined class="copy-icon" @click="refresh()" title="重新生成" />
                 </div>
-                <div v-if="message.parsedContent" v-html="formatMessage(message.parsedContent)"> </div>
-              </div>
-              <div class="copy-icon-container">
-                <CopyOutlined class="copy-icon" @click="copyToClipboard(message.parsedContent)" title="复制消息" />
-                <RedoOutlined class="copy-icon" @click="refresh()" title="重新生成" />
               </div>
-            </div>
-          </template>
+            </template>
+          </div>
+          <!-- 建议信息 -->
+          <div v-for="(item, index) in suggestList" :key="index" class="suggestion-item" @click="handleSuggestClick(item)">
+            <span class="suggestion-text">{{ item }}</span>
+            <a-icon type="right" class="suggestion-arrow" />
+          </div>
         </div>
-        <!-- 建议信息 -->
-        <div v-for="(item, index) in suggestList" :key="index" class="suggestion-item" @click="handleSuggestClick(item)">
-          <span class="suggestion-text">{{ item }}</span>
-          <a-icon type="right" class="suggestion-arrow" />
+        <!-- 底部输入区 -->
+        <div class="input-area">
+          <a-textarea v-model:value="inputText" placeholder="请输入你的问题" @keyup.enter="handleSend(inputText)" class="ant-input" auto-size />
+          <div class="ctrl-btn">
+            <div class="input-controls">
+              <button class="control-btn" :class="{ active: isThinking }" @click="toggleThinking">深度思考</button>
+              <button class="control-btn" @click="stopReq()">停止响应</button>
+            </div>
+            <div class="action-bar">
+              <Space>
+                <Button class="control-btn1" size="small" @mouseenter="showModal(true)">
+                  <template #icon>
+                    <SvgIcon name="send-file" />
+                  </template>
+                </Button>
+                <Button class="control-btn2" size="small" @click="handleSend(inputText)">
+                  <template #icon>
+                    <SvgIcon name="send" />
+                  </template>
+                </Button>
+              </Space>
+            </div>
+          </div>
+          <!-- 右侧文件上传区 -->
+          <div v-if="open" @mouseenter="showModal(true)" @mouseleave="showModal(false)" class="file-upload">
+            <!-- 上传按钮 -->
+            <a-upload
+              class="custom-upload"
+              name="file"
+              :multiple="false"
+              :before-upload="handleBeforeUpload"
+              :file-list="fileList"
+              :remove="handleRemove"
+              accept=".pdf,.docx,.xlsx,.xls"
+            >
+              <a-button class="upload-btn">
+                <UploadOutlined></UploadOutlined>
+                从本地上传
+              </a-button>
+            </a-upload>
+          </div>
         </div>
       </div>
-      <!-- 底部输入区 -->
-      <div class="input-area">
-        <a-textarea v-model:value="inputText" placeholder="请输入你的问题" @keyup.enter="handleSend(inputText)" class="ant-input" auto-size />
-        <div class="ctrl-btn">
-          <div class="input-controls">
-            <button class="control-btn" :class="{ active: isThinking }" @click="toggleThinking">深度学习</button>
-            <button class="control-btn" @click="stopReq()">停止响应</button>
+    </div>
+    <div class="doc" v-if="isShowDoc">
+      <div class="close"> <button class="closeBtn" @click="close">关闭</button></div>
+      <!-- 已上传文件列表 -->
+      <div class="file-list" v-if="uploadedFiles.length">
+        <div class="file-item" v-for="file in uploadedFiles" :key="file.id">
+          <div class="file-info">
+            <div class="file-name" :title="file.name">{{ file.name }}</div>
           </div>
-          <div class="action-bar">
-            <Space>
-              <Button class="control-btn1" size="small" @click="showModal()">
-                <template #icon>
-                  <SvgIcon name="send-file" />
-                </template>
-              </Button>
-              <Button class="control-btn2" size="small" @click="handleSend(inputText)">
-                <template #icon>
-                  <SvgIcon name="send" />
-                </template>
-              </Button>
-            </Space>
+          <div class="file-actions">
+            <button class="btn btn-preview" @click="previewFile(file)"> <i class="fas fa-eye"></i> 预览 </button>
+            <button class="btn btn-delete" @click="deleteFile(file.id)"> <i class="fas fa-trash"></i> 删除 </button>
           </div>
         </div>
-        <!-- 右侧文件上传区 -->
-        <div v-if="open" class="file-upload">
-          <!-- 上传按钮 -->
-          <a-upload
-            class="custom-upload"
-            name="file"
-            :multiple="false"
-            :before-upload="handleBeforeUpload"
-            :file-list="fileList"
-            :remove="handleRemove"
-          >
-            <a-button class="upload-btn">
-              <UploadOutlined></UploadOutlined>
-              从本地上传
-            </a-button>
-          </a-upload>
+      </div>
+      <!-- 预览内容 -->
+      <div class="pre-container">
+        <!-- PDF预览 -->
+        <VueOfficePdf v-if="fileType === 'pdf'" :src="fileUrl" @rendered="handleRendered" @error="handleError" />
+        <!-- Word文档预览 -->
+        <VueOfficeDocx v-else-if="fileType === 'docx'" :src="fileUrl" @rendered="handleRendered" @error="handleError" />
+        <!-- Excel预览 -->
+        <VueOfficeExcel
+          v-else-if="fileType === 'xlsx' || fileType === 'xls'"
+          :options="{ xls: true }"
+          :src="fileUrl"
+          @rendered="handleRendered"
+          @error="handleError"
+        />
+        <!-- 不支持的文件类型 -->
+        <div v-else class="unsupported">
+          <p>不支持预览该文件类型: {{ fileType }}</p>
+        </div>
+        <!-- 加载状态 -->
+        <div v-if="loading" class="loading">
+          <p>文件加载中...</p>
         </div>
       </div>
     </div>
@@ -168,6 +199,9 @@
 </template>
 
 <script lang="ts" setup>
+import VueOfficePdf from '@vue-office/pdf';
+import VueOfficeDocx from '@vue-office/docx';
+import VueOfficeExcel from '@vue-office/excel';
 import { ref, onMounted, nextTick } from 'vue';
 import { SvgIcon } from '../Icon';
 import { Space, Button, Modal, Input, message } from 'ant-design-vue';
@@ -187,6 +221,8 @@ import { createVNode } from 'vue';
 import { marked } from 'marked';
 import katex from 'katex';
 import 'katex/dist/katex.min.css';
+import '@vue-office/excel/lib/index.css';
+import '@vue-office/docx/lib/index.css';
 const TextArea = Input.TextArea; // 直接导入TextArea组件使用时打包报错
 const inputText = ref(''); // 输入框内容
 const refreshText = ref(''); //重新生成文本
@@ -200,6 +236,9 @@ const messageID = ref('');
 const open = ref<boolean>(false);
 const isThinking = ref(false); //深度思考是否开启
 const Thinking = ref(false);
+const isShowDoc = ref(false);
+const fileType = ref('');
+const fileUrl = ref('');
 const APIKEY = ref('Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd');
 interface ListItem {
   id: number;
@@ -220,9 +259,30 @@ const messageHistory = ref<Message[]>([]);
 const isFold = ref(true); // 是否折叠
 const userid = useUserStore().getUserInfo.id as string;
 const filePath = ref(''); // 绑定输入框值
+const uploadedFiles = ref([]);
 const showConfirmBtn = ref(false); // 控制确认按钮显示状态
 const fileList = ref([]);
 const suggestList = ref([]); //建议列表
+const loading = ref(false);
+// 文件预览
+const handleRendered = () => {
+  loading.value = false;
+  console.log('文件渲染完成');
+};
+const handleError = (error) => {
+  loading.value = false;
+  console.error('文件预览错误:', error);
+};
+function previewFile(data) {
+  fileType.value = data.name.split('.').pop().toLowerCase();
+  fileUrl.value = data.source_url;
+}
+function deleteFile(fileId) {
+  // 确认删除
+  if (confirm('确定要删除这个文件吗?')) {
+    uploadedFiles.value = uploadedFiles.value.filter((file) => file.id !== fileId);
+  }
+}
 //启用深度思考
 const toggleThinking = () => {
   isThinking.value = !isThinking.value;
@@ -255,6 +315,9 @@ const handleSuggestClick = (text) => {
 };
 function showAIChat() {
   isShowChatBroad.value = !isShowChatBroad.value;
+  if (isShowChatBroad) {
+    isShowDoc.value = false;
+  }
 }
 //复制消息
 function copyToClipboard(text) {
@@ -299,7 +362,6 @@ const renderLatexInHtml = (html) => {
   const blockRegex = /\$\$(.*?)\$\$/gs;
   // 匹配行内公式($...$)
   const inlineRegex = /\$(.*?)\$/g;
-  let matchCount = 0; // 统计匹配次数
   // // 替换块级公式(居中显示)
   html = html.replace(blockRegex, (match, formula) => {
     matchCount++;
@@ -319,7 +381,6 @@ const renderLatexInHtml = (html) => {
       strict: false,
     });
   });
-  console.log(`最终匹配次数:${matchCount}`); // 输出总匹配次数
   return html;
 };
 // Markdown + LaTeX 解析
@@ -534,8 +595,11 @@ async function addNew() {
   messageID.value = '';
 }
 // 上传文件
-const showModal = () => {
-  open.value = !open.value;
+const showModal = (data) => {
+  open.value = data;
+};
+const close = () => {
+  isShowDoc.value = false;
 };
 // 上传文件
 const handleBeforeUpload = async (file) => {
@@ -554,9 +618,11 @@ const handleBeforeUpload = async (file) => {
 
     const result = await response.json();
     if (response.ok) {
-      message.success('上传成功');
       const linkText = `[${result.name}](${result.source_url})`;
-      inputText.value += `\n${linkText}`;
+      // inputText.value += `\n${linkText}`;
+      uploadedFiles.value.push(result);
+      isShowDoc.value = true;
+      previewFile(result);
     } else {
       message.error(`上传失败: ${result.message || '网络错误'}`);
     }
@@ -594,17 +660,22 @@ async function stopReq() {
   }
 }
 //获取具体会话记录
-async function sessionsHistory(id: string) {
+async function sessionsHistory(id: string, retryCount = 0) {
+  const API_KEYS = ['Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd', 'Bearer app-kprgsFKtySM4Wjxs0ZGzaNFN'];
+  // 最大重试次数
+  const maxRetries = API_KEYS.length - 1;
   try {
     let response = await fetch(`http://39.97.59.228:8000/v1/messages?conversation_id=${id}&user=${userid}`, {
       method: 'GET',
       headers: {
         'Content-Type': 'application/json',
-        Authorization: APIKEY.value,
+        Authorization: API_KEYS[retryCount],
       },
     });
+    if (!response.ok) {
+      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+    }
     const data = await response.json();
-    console.log(data, '123');
     if (data.data.length > 0) {
       messageHistory.value = [];
       data.data.forEach((item: any) => {
@@ -654,11 +725,15 @@ async function sessionsHistory(id: string) {
         });
       });
     }
-    if (!response.ok) {
-      throw new Error('Network response was not ok');
-    }
   } catch (error) {
-    console.error('保存失败:', error);
+    // 判断是否达到最大重试次数
+    if (retryCount < maxRetries) {
+      console.warn(`请求失败,正在尝试第 ${retryCount + 2} 个 API Key...`);
+      return sessionsHistory(id, retryCount + 1);
+    } else {
+      console.error('所有 API Key 均尝试失败:', error);
+      throw error;
+    }
   }
   editingId.value = null;
 }
@@ -753,18 +828,51 @@ const fold = () => {
 };
 // 获取历史会话列表
 async function getHistoryList() {
-  let response = await fetch(`http://39.97.59.228:8000/v1/conversations?user=${userid}`, {
-    method: 'get',
-    headers: {
-      'Content-Type': 'application/json',
-      Authorization: APIKEY.value,
-    },
-  });
-  const data = await response.json();
-  sessionHistory.value = data.data;
-  data.data.forEach((item) => {
-    currentSessionID.value = item.conversation_id;
-  });
+  sessionHistory.value = [];
+  try {
+    // 并行请求两个接口,提升效率
+    const [response1, response2] = await Promise.all([
+      // 第一个请求
+      fetch(`http://39.97.59.228:8000/v1/conversations?user=${userid}`, {
+        method: 'get',
+        headers: {
+          'Content-Type': 'application/json',
+          Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
+        },
+      }),
+      // 第二个请求
+      fetch(`http://39.97.59.228:8000/v1/conversations?user=${userid}`, {
+        method: 'get',
+        headers: {
+          'Content-Type': 'application/json',
+          Authorization: 'Bearer app-kprgsFKtySM4Wjxs0ZGzaNFN',
+        },
+      }),
+    ]);
+
+    // 检查响应是否成功
+    if (!response1.ok || !response2.ok) {
+      throw new Error('接口请求失败');
+    }
+
+    // 解析两个响应的JSON数据
+    const [data1, data2] = await Promise.all([response1.json(), response2.json()]);
+
+    // 合并两个数组(核心修改:用扩展运算符合并)
+    // 确保 data1.data 和 data2.data 都是数组(防止接口返回异常)
+    const list1 = Array.isArray(data1.data) ? data1.data : [];
+    const list2 = Array.isArray(data2.data) ? data2.data : [];
+
+    // 合并数组并赋值给 sessionHistory
+    sessionHistory.value = [...list1, ...list2];
+    sessionHistory.value.forEach((item) => {
+      currentSessionID.value = item.id;
+    });
+    console.log(sessionHistory.value, '历史数据');
+  } catch (error) {
+    console.error('获取历史数据失败:', error);
+    // 错误处理(比如提示用户)
+  }
 }
 //格式化消息
 const formatMessage = (text) => {
@@ -799,21 +907,70 @@ onMounted(() => {
   margin-right: 5px;
   margin-bottom: 5px;
 }
+.container {
+  display: flex;
+  flex-direction: row;
+}
 .mini-chat {
   display: flex;
-  width: 800px;
+  min-width: none;
+  width: 950px;
   height: 85%;
   border-radius: 4px;
   position: fixed;
-  top: 70px;
+  top: 50px;
   right: 20px;
-  background-color: rgb(255, 255, 255);
-  background: url('../../assets/images/warn-dialog-bg.png') no-repeat center;
-  background-size: 103% 124%;
+  padding: 10px;
+  background-color: #0a1a2f;
+  background-image: linear-gradient(to bottom, #0b69b6 1%, #0a1a2f 100%), linear-gradient(to left, #0b69b6, #0a1a2f),
+    linear-gradient(to top, #0b69b6, #0a1a2f), linear-gradient(to right, #0b69b6, #0a1a2f);
+  background-size: 100% 5px, 5px 100%, 100% 5px, 5px 100%;
+  background-position: top left, top right, bottom right, bottom left;
+  background-repeat: no-repeat;
   z-index: 9999999;
   color: #fff;
 }
+.doc {
+  display: flex;
+  flex-direction: column;
+  min-width: 600px;
+  height: 85%;
+  border-radius: 4px;
+  position: fixed;
+  top: 50px;
+  right: 51%;
+  padding: 10px;
+  background-color: #0a1a2f;
+  background-image: linear-gradient(to bottom, #0b69b6 1%, #0a1a2f 100%), linear-gradient(to left, #0b69b6, #0a1a2f),
+    linear-gradient(to top, #0b69b6, #0a1a2f), linear-gradient(to right, #0b69b6, #0a1a2f);
+  background-size: 100% 5px, 5px 100%, 100% 5px, 5px 100%;
+  background-position: top left, top right, bottom right, bottom left;
+  background-repeat: no-repeat;
+  z-index: 9999999;
+  color: #fff;
+}
+
+.close {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  width: 100%;
+  padding: 1px;
+  border-radius: 8px;
+}
+
+.closeBtn {
+  background-color: #007bff;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+}
 
+.closeBtn:hover {
+  background-color: #0056b3;
+}
 .left-side {
   background: #0c2842;
   transition: width 0.5s ease; /* 平滑过渡动画 */
@@ -838,7 +995,7 @@ onMounted(() => {
   cursor: pointer;
 }
 .custom-list {
-  height: 680px;
+  height: 650px;
   overflow-y: auto;
 }
 .text-container {
@@ -1262,7 +1419,6 @@ onMounted(() => {
 .copy-icon:hover {
   color: #1890ff;
 }
-
 ::v-deep table {
   border-collapse: collapse;
   width: 100%;
@@ -1281,3 +1437,80 @@ onMounted(() => {
   text-align: center;
 }
 </style>
+<style scoped>
+/* 已上传文件列表 */
+.file-list {
+  margin-top: 20px;
+}
+.pre-container {
+  flex: 1;
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+}
+.vue-office-excel {
+  background: #fff !important;
+}
+
+.file-item {
+  display: flex;
+  align-items: center;
+  padding: 12px 15px;
+  background-color: #ddd;
+  border-radius: 6px;
+  margin-bottom: 10px;
+  transition: background-color 0.2s ease;
+}
+
+.file-item:hover {
+  background-color: #f0f2f5;
+}
+
+.file-info {
+  flex: 1;
+  overflow: hidden;
+}
+
+.file-name {
+  font-size: 15px;
+  color: #1d2129;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  margin-bottom: 3px;
+}
+
+.file-actions {
+  display: flex;
+  gap: 10px;
+}
+
+.btn {
+  padding: 0px 15px;
+  margin-left: 10px;
+  border-radius: 4px;
+  border: none;
+  font-size: 14px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+
+.btn-preview {
+  background-color: #165dff;
+  color: white;
+}
+
+.btn-preview:hover {
+  background-color: #0d47a1;
+}
+
+.btn-delete {
+  background-color: #f2f3f5;
+  color: #4e5969;
+}
+
+.btn-delete:hover {
+  background-color: #e5e6eb;
+  color: #1d2129;
+}
+</style>