From 1455e8a5dcea2209b4d1baf4d513aa8fbfb2b39b Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期五, 08 五月 2026 17:44:45 +0800
Subject: [PATCH] 隐藏新财务模块

---
 src/components/AIChatSidebar/index.vue |  182 +++++++++++++++++++++++++++------------------
 1 files changed, 109 insertions(+), 73 deletions(-)

diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
index 257e4fe..a5dcf0b 100644
--- a/src/components/AIChatSidebar/index.vue
+++ b/src/components/AIChatSidebar/index.vue
@@ -191,7 +191,8 @@
                   <div
                       v-for="(file, fileIndex) in message.localUploadFiles"
                       :key="`${file.previewId || file.name}-${fileIndex}`"
-                      class="message-local-file-item"
+                      :class="['message-local-file-item', { clickable: !!file.accessUrl && !file.isImage }]"
+                      @click="handleMessageFileClick(file)"
                   >
                     <el-image
                         v-if="file.isImage && file.previewUrl"
@@ -205,8 +206,14 @@
                     />
                     <el-icon v-else class="message-local-file-icon"><Document /></el-icon>
                     <div class="message-local-file-meta">
-                      <span class="message-local-file-name">{{ file.name }}</span>
-                      <small class="message-local-file-size">{{ formatFileSize(file.size) }}</small>
+                      <span
+                          :class="['message-local-file-name', { clickable: !!file.accessUrl }]"
+                          :title="file.name"
+                          @click.stop="openMessageAttachment(file)"
+                      >
+                        {{ file.name }}
+                      </span>
+                      <small v-if="Number(file.size) > 0" class="message-local-file-size">{{ formatFileSize(file.size) }}</small>
                     </div>
                   </div>
                 </div>
@@ -515,8 +522,9 @@
 import { ref, onMounted, onUnmounted, nextTick, watch, computed } from 'vue'
 import request from '@/utils/request'
 import * as echarts from 'echarts'
-import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, ShoppingCart, Promotion, RefreshRight } from '@element-plus/icons-vue'
+import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, Promotion, RefreshRight } from '@element-plus/icons-vue'
 import { ElMessage } from 'element-plus'
+import { builtInAssistants, generalAssistant } from './assistants'
 
 const props = defineProps({
   assistants: {
@@ -529,69 +537,10 @@
   }
 })
 
-const builtInAssistants = [
-  {
-    key: 'general',
-    label: '寰呭姙鍔╃悊',
-    title: '寰呭姙鏅鸿兘鍔╃悊',
-    tooltip: '寰呭姙鍔╂墜',
-    icon: Cpu,
-    apiBase: '/xiaozhi',
-    storageKey: 'ai_chat_uuid',
-    placeholder: '璇疯緭鍏ユ偍鐨勯棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
-    welcomeMessage: '浣犲ソ',
-    description: '鎴戝彲浠ュ洖绛斾綘鐨勯棶棰橈紝涓轰綘鎻愪緵涓氬姟鏁版嵁瑙h淇℃伅銆佸鐞嗗缓璁拰杈呭姪鍐崇瓥鏀寔銆�',
-    allowFileUpload: true,
-    emptySessionText: '鏆傛棤鍘嗗彶浼氳瘽'
-  },
-  {
-    key: 'purchase',
-    label: '閲囪喘鍔╃悊',
-    title: '閲囪喘鏅鸿兘鍔╃悊',
-    tooltip: '閲囪喘鏅鸿兘鍔╃悊',
-    icon: ShoppingCart,
-    apiBase: '/purchase-ai',
-    storageKey: 'purchase_ai_chat_uuid',
-    placeholder: '璇疯緭鍏ラ噰璐棶棰�... (Enter 鍙戦��, Shift+Enter 鎹㈣)',
-    welcomeMessage: '浣犲ソ',
-    description: '鎴戝彲浠ュ崗鍔╀綘鍒嗘瀽閲囪喘璁㈠崟銆佸埌璐ц繘搴︺�佷緵搴斿晢琛ㄧ幇鍜屼粯娆炬儏鍐碉紝甯姪浣犲揩閫熷畾浣嶉噰璐紓甯搞��',
-    allowFileUpload: true,
-    allowMultipleFileUpload: true,
-    fileAnalyzeUrl: '/purchase-ai/analyze-files',
-    emptySessionText: '鏆傛棤閲囪喘浼氳瘽'
-  }
-]
-
 const assistants = computed(() => props.assistants?.length ? props.assistants : builtInAssistants)
 const selectedAssistantKey = ref(props.defaultAssistant || assistants.value[0]?.key || 'general')
 const currentAssistant = computed(() => assistants.value.find(item => item.key === selectedAssistantKey.value) || assistants.value[0] || builtInAssistants[0])
 const showAssistantSwitch = computed(() => assistants.value.length > 1)
-const assistantQuickPromptMap = {
-  general: [
-    '鎴戝綋鍓嶆湁鍝簺瀹℃壒寰呭姙闇�瑕佸鐞嗭紵',
-    '甯垜鍒楀嚭浠婂ぉ鏂板鐨勫鎵瑰緟鍔炪��',
-    '褰撳墠寰呮垜瀹℃壒鐨勫崟鎹紝鎸夋椂闂村�掑簭鍒楀嚭鏉ャ��',
-    '鎴戝彂璧风殑瀹℃壒閲岋紝鍝簺杩樺湪澶勭悊涓紵',
-    '鏌ヨ娴佺▼缂栧彿 XXX 鐨勫鎵硅鎯呫��',
-    '娴佺▼缂栧彿 XXX 鐜板湪鍗″湪鍝釜瀹℃壒鑺傜偣锛熷綋鍓嶅鎵逛汉鏄皝锛�',
-    '甯垜鏌ョ湅娴佺▼缂栧彿 XXX 鐨勫鎵规祦杞褰曘��',
-    '杩�7澶╂垜鐨勫鎵瑰緟鍔炵粺璁℃儏鍐垫�庝箞鏍凤紵',
-    '鏈湀鎴戠殑瀹℃壒涓紝閫氳繃銆侀┏鍥炪�佸鐞嗕腑鍚勬湁澶氬皯锛�',
-    '杩�30澶╁悇绫诲瀷瀹℃壒鏁伴噺鍒嗗竷鏄粈涔堬紵',
-    '甯垜瀹℃壒閫氳繃娴佺▼缂栧彿 XXX锛屽娉ㄢ�滃悓鎰忊�濄��',
-    '甯垜椹冲洖娴佺▼缂栧彿 XXX锛屽娉ㄢ�滆琛ュ厖璇存槑鈥濄��',
-    '鎾ら攢鎴戝垰鍒氬娴佺▼缂栧彿 XXX 鐨勫鎵规搷浣溿��',
-    '甯垜淇敼娴佺▼缂栧彿 XXX 鐨勫娉ㄤ负鈥滃凡琛ュ厖闄勪欢鈥濄��',
-    '鍒犻櫎鎴戝彂璧风殑娴佺▼缂栧彿 XXX銆�'
-  ],
-  purchase: [
-    '鏈湀閲囪喘閲戦鎺掑悕鍓嶅崄鐨勭墿鏂欐湁鍝簺锛�',
-    '鍝簺閲囪喘璁㈠崟杩樻湭鍏ュ簱锛�',
-    '鏈�杩�7澶╀緵搴斿晢鍒拌揣寮傚父鏈夊摢浜涳紵',
-    '甯垜缁熻寰呬粯娆鹃噰璐崟',
-    '鍒楀嚭鏈湀閲囪喘閫�璐ф儏鍐�'
-  ]
-}
 const quickPromptLimit = 3
 const quickPromptStart = ref(0)
 const quickPrompts = computed(() => {
@@ -599,7 +548,7 @@
   if (Array.isArray(assistant.quickPrompts) && assistant.quickPrompts.length) {
     return assistant.quickPrompts
   }
-  return assistantQuickPromptMap[assistant.key] || assistantQuickPromptMap.general
+  return generalAssistant.quickPrompts || []
 })
 const displayedQuickPrompts = computed(() => {
   const prompts = quickPrompts.value || []
@@ -831,6 +780,34 @@
 const loadingSessions = ref(false)
 
 const isImageFileType = (fileType = '') => String(fileType || '').toLowerCase().startsWith('image/')
+const imageFilePathPattern = /\.(png|jpe?g|gif|webp|bmp|svg)$/i
+
+const getPathnameFromFilePath = (filePath = '') => {
+  const rawPath = String(filePath || '').trim()
+  if (!rawPath) return ''
+  try {
+    const baseOrigin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost'
+    return new URL(rawPath, baseOrigin).pathname || ''
+  } catch (err) {
+    return rawPath.split('?')[0]
+  }
+}
+
+const isImageFilePath = (filePath = '') => {
+  const pathname = getPathnameFromFilePath(filePath).toLowerCase()
+  return imageFilePathPattern.test(pathname)
+}
+
+const getHistoryFileName = (filePath = '', index = 0) => {
+  const pathname = getPathnameFromFilePath(filePath)
+  const fileName = pathname.split('/').filter(Boolean).pop()
+  if (!fileName) return `file-${index + 1}`
+  try {
+    return decodeURIComponent(fileName)
+  } catch (err) {
+    return fileName
+  }
+}
 
 const getImagePreviewList = (files = []) => {
   if (!Array.isArray(files)) return []
@@ -858,14 +835,34 @@
   const fileType = rawFile?.type || ''
   const isImage = isImageFileType(fileType)
   const canCreateObjectURL = typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function'
+  const previewUrl = isImage && rawFile && canCreateObjectURL ? URL.createObjectURL(rawFile) : ''
   return {
     previewId: `${rawFile?.name || 'file'}-${rawFile?.size || 0}-${rawFile?.lastModified || Date.now()}-${index}`,
     name: rawFile?.name || `file-${index + 1}`,
     size: rawFile?.size || 0,
     type: fileType,
     isImage,
-    previewUrl: isImage && rawFile && canCreateObjectURL ? URL.createObjectURL(rawFile) : '',
-    rawFile
+    previewUrl,
+    accessUrl: '',
+    rawFile,
+    isObjectUrl: !!previewUrl
+  }
+}
+
+const createHistoryFileSnapshot = (filePath, memoryId = '', messageIndex = 0, fileIndex = 0) => {
+  const normalizedPath = String(filePath || '').trim()
+  if (!normalizedPath) return null
+  const isImage = isImageFilePath(normalizedPath)
+  return {
+    previewId: `${memoryId || 'history'}-${messageIndex}-${fileIndex}`,
+    name: getHistoryFileName(normalizedPath, fileIndex),
+    size: 0,
+    type: '',
+    isImage,
+    previewUrl: isImage ? normalizedPath : '',
+    accessUrl: normalizedPath,
+    rawFile: null,
+    isObjectUrl: false
   }
 }
 
@@ -874,10 +871,29 @@
   if (!canRevokeObjectURL) return
   if (!Array.isArray(snapshots)) return
   snapshots.forEach((snapshot) => {
-    if (snapshot?.previewUrl) {
+    if (snapshot?.isObjectUrl && snapshot?.previewUrl) {
       URL.revokeObjectURL(snapshot.previewUrl)
     }
   })
+}
+
+const mapHistoryFilePathsToSnapshots = (filePaths = [], memoryId = '', messageIndex = 0) => {
+  if (!Array.isArray(filePaths)) return []
+  return filePaths
+    .map((filePath, fileIndex) => createHistoryFileSnapshot(filePath, memoryId, messageIndex, fileIndex))
+    .filter(Boolean)
+}
+
+const openMessageAttachment = (file) => {
+  const accessUrl = String(file?.accessUrl || '').trim()
+  if (!accessUrl) return
+  if (typeof window === 'undefined' || typeof window.open !== 'function') return
+  window.open(accessUrl, '_blank', 'noopener,noreferrer')
+}
+
+const handleMessageFileClick = (file) => {
+  if (!file?.accessUrl || file?.isImage) return
+  openMessageAttachment(file)
 }
 
 const revokeMessageLocalFileSnapshots = (messageList = []) => {
@@ -962,7 +978,7 @@
 
         const messageObj = {
           isUser,
-          content: msg.content,
+          content: msg.content || '',
           htmlContent: '',
           isTyping: false,
           chartOptions: null,
@@ -970,7 +986,8 @@
           type: '',
           tableData: null,
           payloadTreeData: null,
-          payloadHiddenData: null
+          payloadHiddenData: null,
+          localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : []
         }
 
         messages.value.push(messageObj)
@@ -985,15 +1002,15 @@
           }
 
           // 瑙f瀽鍘嗗彶娑堟伅涓殑 JSON
-          const extracted = extractEmbeddedSuccessJson(msg.content)
+          const extracted = extractEmbeddedSuccessJson(msg.content || '')
           if (extracted) {
             applyStructuredMessageData(messageObj, extracted.data, botMsgIndex)
           }
 
-          updateOutputState(msg.content, botMsgIndex)
-          messageObj.htmlContent = convertStreamOutput(msg.content, botMsgIndex)
+          updateOutputState(msg.content || '', botMsgIndex)
+          messageObj.htmlContent = convertStreamOutput(msg.content || '', botMsgIndex)
         } else {
-          messageObj.htmlContent = convertTextToHtml(msg.content)
+          messageObj.htmlContent = convertTextToHtml(msg.content || '')
         }
       })
       scrollToBottom()
@@ -3509,6 +3526,16 @@
       border: 1px solid rgba(88, 117, 255, 0.2);
       background: rgba(255, 255, 255, 0.9);
       max-width: 100%;
+
+      &.clickable {
+        cursor: pointer;
+        transition: all 0.2s ease;
+
+        &:hover {
+          border-color: rgba(44, 109, 255, 0.38);
+          background: rgba(243, 247, 255, 0.96);
+        }
+      }
     }
 
     .message-local-file-thumb {
@@ -3547,6 +3574,15 @@
       white-space: nowrap;
       overflow: hidden;
       text-overflow: ellipsis;
+
+      &.clickable {
+        color: $primary-blue;
+        cursor: pointer;
+
+        &:hover {
+          text-decoration: underline;
+        }
+      }
     }
 
     .message-local-file-size {

--
Gitblit v1.9.3