diff --git a/electron/electron-down.js b/electron/electron-down.js index 81b56443d..67d617923 100644 --- a/electron/electron-down.js +++ b/electron/electron-down.js @@ -63,7 +63,7 @@ function initialize(onStarted = null, onChanged = null) { }); // IPC - ipcMain.handle('downloadManager', async (event, {action, path, msgId}) => { + ipcMain.handle('downloadManager', async (event, {action, path, msgId, file, files}) => { switch (action) { case "get": { return { @@ -93,12 +93,14 @@ function initialize(onStarted = null, onChanged = null) { case "remove": { downloadManager.remove(path); syncDownloadItems(); + notifyChanged(); return true; } case "removeAll": { downloadManager.removeAll(); syncDownloadItems(); + notifyChanged(); return true; } @@ -130,6 +132,22 @@ function initialize(onStarted = null, onChanged = null) { shell.showItemInFolder(file.path); return true; } + + case "fileStatuses": { + if (!Array.isArray(files)) { + return {}; + } + return downloadManager.getFileStatuses(files.slice(0, 500)); + } + + case "showFile": { + const localFile = downloadManager.getFileStatus(file); + if (localFile.status !== 'available') { + return false; + } + shell.showItemInFolder(localFile.path); + return true; + } } }); } diff --git a/electron/lib/download-manager.js b/electron/lib/download-manager.js index f9404fb9c..efb60cc4d 100644 --- a/electron/lib/download-manager.js +++ b/electron/lib/download-manager.js @@ -103,12 +103,27 @@ class DownloadManager { * @returns {{status: 'available'|'downloading'|'missing', path?: string}} */ getMessageFileStatus(msgId) { - const targetId = parseInt(msgId, 10); - if (!targetId) { + return this.getFileStatus({msgId}); + } + + /** + * 获取会话附件对应的下载状态。 + * + * @param {{msgId?: number|string, attachmentId?: number|string}} reference + * @returns {{status: 'available'|'downloading'|'missing', path?: string}} + */ + getFileStatus(reference = {}) { + const msgId = parseInt(reference.msgId, 10) || 0; + const attachmentId = parseInt(reference.attachmentId, 10) || 0; + if (!msgId && !attachmentId) { return {status: 'missing'}; } - const items = this.downloadHistory.filter(item => this.getMessageFileId(item) === targetId); + const items = this.downloadHistory.filter(item => { + const file = this.getFileReference(item); + return (msgId > 0 && file.msgId === msgId) + || (attachmentId > 0 && file.attachmentId === attachmentId); + }); const downloading = items.some(item => item.state === 'progressing' && !item.paused); if (downloading) { return {status: 'downloading'}; @@ -118,6 +133,58 @@ class DownloadManager { return available ? {status: 'available', path: available.path} : {status: 'missing'}; } + /** + * 批量获取会话附件的下载状态,只扫描一次下载历史。 + * + * @param {Array<{key: string, msgId?: number|string, attachmentId?: number|string}>} references + * @returns {Object} + */ + getFileStatuses(references = []) { + const statuses = {}; + const msgKeys = new Map(); + const attachmentKeys = new Map(); + const addKey = (map, id, key) => { + if (!id) return; + if (!map.has(id)) map.set(id, []); + map.get(id).push(key); + }; + + references.forEach(reference => { + if (!reference || typeof reference !== 'object') return; + const key = `${reference.key || ''}`; + if (!key) return; + const msgId = parseInt(reference.msgId, 10) || 0; + const attachmentId = parseInt(reference.attachmentId, 10) || 0; + statuses[key] = 'missing'; + addKey(msgKeys, msgId, key); + addKey(attachmentKeys, attachmentId, key); + }); + + this.downloadHistory.forEach(item => { + const file = this.getFileReference(item); + const keys = new Set([ + ...(msgKeys.get(file.msgId) || []), + ...(attachmentKeys.get(file.attachmentId) || []), + ]); + if (!keys.size) return; + + let status = ''; + if (item.state === 'progressing' && !item.paused) { + status = 'downloading'; + } else if (item.state === 'completed' && item.path && fs.existsSync(item.path)) { + status = 'available'; + } + if (!status) return; + + keys.forEach(key => { + if (status === 'downloading' || statuses[key] === 'missing') { + statuses[key] = status; + } + }); + }); + return statuses; + } + /** * 从下载地址中识别聊天文件消息 ID。 * @@ -125,6 +192,17 @@ class DownloadManager { * @returns {number} */ getMessageFileId(item) { + return this.getFileReference(item).msgId; + } + + /** + * 从下载地址中识别会话消息或协作附件 ID。 + * + * @param {Object} item + * @returns {{msgId: number, attachmentId: number}} + */ + getFileReference(item) { + const reference = {msgId: 0, attachmentId: 0}; const urls = [...(Array.isArray(item.urls) ? item.urls : []), item.url].filter(Boolean); for (const value of urls) { try { @@ -132,14 +210,19 @@ class DownloadManager { if (url.pathname.endsWith('/api/dialog/msg/download')) { const msgId = parseInt(url.searchParams.get('msg_id'), 10); if (msgId > 0) { - return msgId; + reference.msgId = msgId; + } + } else if (url.pathname.endsWith('/api/file/collaboration/download')) { + const attachmentId = parseInt(url.searchParams.get('attachment_id'), 10); + if (attachmentId > 0) { + reference.attachmentId = attachmentId; } } } catch { // Ignore malformed history URLs. } } - return 0; + return reference; } /** diff --git a/resources/ai-kb/zh/howto/file/collaboration.md b/resources/ai-kb/zh/howto/file/collaboration.md index e62a6a3df..52f211f39 100644 --- a/resources/ai-kb/zh/howto/file/collaboration.md +++ b/resources/ai-kb/zh/howto/file/collaboration.md @@ -34,6 +34,8 @@ last_verified: v1.8.89 文件消息和粘贴、插入到聊天正文中的图片都会汇总到协作文件。一条聊天消息包含多张图片时,每张图片会作为一条独立记录展示。图片会在列表和宫格中直接显示缩略图;缩略图不可用时显示对应的文件类型图标。 +Electron 桌面客户端会同步会话文件与协作文件的本地下载状态:未下载时显示下载图标,下载中显示加载图标;下载完成且本地文件仍存在时显示文件夹图标,点击可在资源管理器或 Finder 中定位文件。清除下载历史,或移动、改名、删除本地文件后,操作会恢复为下载图标。正文图片等独立附件也支持相同操作。 + ## 权限与范围 - 只展示当前用户仍有权访问的来源;退出会话、项目或任务权限变化后,对应文件不再显示。 - 已撤回或删除的文件消息、正文图片不显示;编辑消息移除图片后,对应图片也不再显示。 diff --git a/resources/assets/js/pages/manage/components/CollaborationFileList.vue b/resources/assets/js/pages/manage/components/CollaborationFileList.vue index 875303f3c..11c2f22d5 100644 --- a/resources/assets/js/pages/manage/components/CollaborationFileList.vue +++ b/resources/assets/js/pages/manage/components/CollaborationFileList.vue @@ -99,8 +99,14 @@ {{formatTime(item.created_at)}} {{$A.bytesToSize(item.size)}}
- - + + + +
@@ -123,8 +129,13 @@
{{item.sender.nickname || $L('未知成员')}} · {{formatTime(item.created_at)}}
- - + +
@@ -142,11 +153,14 @@ diff --git a/resources/assets/js/pages/manage/components/DialogView/LocalFileStatusIcon.vue b/resources/assets/js/pages/manage/components/DialogView/LocalFileStatusIcon.vue new file mode 100644 index 000000000..3af0bd66f --- /dev/null +++ b/resources/assets/js/pages/manage/components/DialogView/LocalFileStatusIcon.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/resources/assets/js/pages/manage/components/DialogView/file.vue b/resources/assets/js/pages/manage/components/DialogView/file.vue index e84faccb0..21020c5ef 100644 --- a/resources/assets/js/pages/manage/components/DialogView/file.vue +++ b/resources/assets/js/pages/manage/components/DialogView/file.vue @@ -16,7 +16,7 @@
-
{{ msg.name }}
+
{{ msg.name }}
{{ $A.bytesToSize(msg.size) }}
@@ -40,7 +38,10 @@