diff --git a/.gitignore b/.gitignore index ae6f9bf..920d437 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,8 @@ Thumbs.db *.aps wcdb/ +!resources/wcdb/ +!resources/wcdb/** xkey/ server/ *info @@ -73,4 +75,4 @@ pnpm-lock.yaml wechat-research-site .codex weflow-web-offical -Insight \ No newline at end of file +Insight diff --git a/electron/main.ts b/electron/main.ts index ce8f0ac..72cfb45 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -2558,7 +2558,13 @@ function registerIpcHandlers() { ipcMain.handle('image:decrypt', async (_, payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; force?: boolean }) => { return imageDecryptService.decryptImage(payload) }) - ipcMain.handle('image:resolveCache', async (_, payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; disableUpdateCheck?: boolean }) => { + ipcMain.handle('image:resolveCache', async (_, payload: { + sessionId?: string + imageMd5?: string + imageDatName?: string + disableUpdateCheck?: boolean + allowCacheIndex?: boolean + }) => { return imageDecryptService.resolveCachedImage(payload) }) ipcMain.handle( @@ -2566,13 +2572,14 @@ function registerIpcHandlers() { async ( _, payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>, - options?: { disableUpdateCheck?: boolean } + options?: { disableUpdateCheck?: boolean; allowCacheIndex?: boolean } ) => { const list = Array.isArray(payloads) ? payloads : [] const rows = await Promise.all(list.map(async (payload) => { return imageDecryptService.resolveCachedImage({ ...payload, - disableUpdateCheck: options?.disableUpdateCheck === true + disableUpdateCheck: options?.disableUpdateCheck === true, + allowCacheIndex: options?.allowCacheIndex !== false }) })) return { success: true, rows } @@ -2583,7 +2590,7 @@ function registerIpcHandlers() { async ( _, payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>, - options?: { allowDecrypt?: boolean } + options?: { allowDecrypt?: boolean; allowCacheIndex?: boolean } ) => { imagePreloadService.enqueue(payloads || [], options) return true diff --git a/electron/preload.ts b/electron/preload.ts index 89575b6..ed918b7 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -266,15 +266,21 @@ contextBridge.exposeInMainWorld('electronAPI', { image: { decrypt: (payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; force?: boolean }) => ipcRenderer.invoke('image:decrypt', payload), - resolveCache: (payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; disableUpdateCheck?: boolean }) => + resolveCache: (payload: { + sessionId?: string + imageMd5?: string + imageDatName?: string + disableUpdateCheck?: boolean + allowCacheIndex?: boolean + }) => ipcRenderer.invoke('image:resolveCache', payload), resolveCacheBatch: ( payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>, - options?: { disableUpdateCheck?: boolean } + options?: { disableUpdateCheck?: boolean; allowCacheIndex?: boolean } ) => ipcRenderer.invoke('image:resolveCacheBatch', payloads, options), preload: ( payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>, - options?: { allowDecrypt?: boolean } + options?: { allowDecrypt?: boolean; allowCacheIndex?: boolean } ) => ipcRenderer.invoke('image:preload', payloads, options), onUpdateAvailable: (callback: (payload: { cacheKey: string; imageMd5?: string; imageDatName?: string }) => void) => { const listener = (_: unknown, payload: { cacheKey: string; imageMd5?: string; imageDatName?: string }) => callback(payload) diff --git a/electron/services/imageDecryptService.ts b/electron/services/imageDecryptService.ts index 37b41f2..4d40843 100644 --- a/electron/services/imageDecryptService.ts +++ b/electron/services/imageDecryptService.ts @@ -63,6 +63,7 @@ type CachedImagePayload = { imageDatName?: string preferFilePath?: boolean disableUpdateCheck?: boolean + allowCacheIndex?: boolean } type DecryptImagePayload = CachedImagePayload & { @@ -116,7 +117,9 @@ export class ImageDecryptService { } async resolveCachedImage(payload: CachedImagePayload): Promise { - await this.ensureCacheIndexed() + if (payload.allowCacheIndex !== false) { + await this.ensureCacheIndexed() + } const cacheKeys = this.getCacheKeys(payload) const cacheKey = cacheKeys[0] if (!cacheKey) { diff --git a/electron/services/imagePreloadService.ts b/electron/services/imagePreloadService.ts index 2916bfe..05a772a 100644 --- a/electron/services/imagePreloadService.ts +++ b/electron/services/imagePreloadService.ts @@ -8,11 +8,13 @@ type PreloadImagePayload = { type PreloadOptions = { allowDecrypt?: boolean + allowCacheIndex?: boolean } type PreloadTask = PreloadImagePayload & { key: string allowDecrypt: boolean + allowCacheIndex: boolean } export class ImagePreloadService { @@ -27,6 +29,7 @@ export class ImagePreloadService { enqueue(payloads: PreloadImagePayload[], options?: PreloadOptions): void { if (!Array.isArray(payloads) || payloads.length === 0) return const allowDecrypt = options?.allowDecrypt !== false + const allowCacheIndex = options?.allowCacheIndex !== false for (const payload of payloads) { if (!allowDecrypt && this.queue.length >= this.maxQueueSize) break const cacheKey = payload.imageMd5 || payload.imageDatName @@ -34,7 +37,7 @@ export class ImagePreloadService { const key = `${payload.sessionId || 'unknown'}|${cacheKey}` if (this.pending.has(key)) continue this.pending.add(key) - this.queue.push({ ...payload, key, allowDecrypt }) + this.queue.push({ ...payload, key, allowDecrypt, allowCacheIndex }) } this.processQueue() } @@ -71,7 +74,8 @@ export class ImagePreloadService { sessionId: task.sessionId, imageMd5: task.imageMd5, imageDatName: task.imageDatName, - disableUpdateCheck: !task.allowDecrypt + disableUpdateCheck: !task.allowDecrypt, + allowCacheIndex: task.allowCacheIndex }) if (cached.success) return if (!task.allowDecrypt) return diff --git a/electron/services/keyService.ts b/electron/services/keyService.ts index 4b25c88..72c827c 100644 --- a/electron/services/keyService.ts +++ b/electron/services/keyService.ts @@ -61,6 +61,7 @@ export class KeyService { private getDllPath(): string { const isPackaged = typeof app !== 'undefined' && app ? app.isPackaged : process.env.NODE_ENV === 'production' + const archDir = process.arch === 'arm64' ? 'arm64' : 'x64' const candidates: string[] = [] if (process.env.WX_KEY_DLL_PATH) { @@ -68,11 +69,20 @@ export class KeyService { } if (isPackaged) { + candidates.push(join(process.resourcesPath, 'resources', 'key', 'win32', archDir, 'wx_key.dll')) + candidates.push(join(process.resourcesPath, 'resources', 'key', 'win32', 'x64', 'wx_key.dll')) + candidates.push(join(process.resourcesPath, 'resources', 'key', 'win32', 'wx_key.dll')) candidates.push(join(process.resourcesPath, 'resources', 'wx_key.dll')) candidates.push(join(process.resourcesPath, 'wx_key.dll')) } else { const cwd = process.cwd() + candidates.push(join(cwd, 'resources', 'key', 'win32', archDir, 'wx_key.dll')) + candidates.push(join(cwd, 'resources', 'key', 'win32', 'x64', 'wx_key.dll')) + candidates.push(join(cwd, 'resources', 'key', 'win32', 'wx_key.dll')) candidates.push(join(cwd, 'resources', 'wx_key.dll')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'win32', archDir, 'wx_key.dll')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'win32', 'x64', 'wx_key.dll')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'win32', 'wx_key.dll')) candidates.push(join(app.getAppPath(), 'resources', 'wx_key.dll')) } diff --git a/electron/services/keyServiceLinux.ts b/electron/services/keyServiceLinux.ts index 2c8aef9..85d5a36 100644 --- a/electron/services/keyServiceLinux.ts +++ b/electron/services/keyServiceLinux.ts @@ -25,13 +25,23 @@ export class KeyServiceLinux { private getHelperPath(): string { const isPackaged = app.isPackaged + const archDir = process.arch === 'arm64' ? 'arm64' : 'x64' const candidates: string[] = [] if (process.env.WX_KEY_HELPER_PATH) candidates.push(process.env.WX_KEY_HELPER_PATH) if (isPackaged) { + candidates.push(join(process.resourcesPath, 'resources', 'key', 'linux', archDir, 'xkey_helper_linux')) + candidates.push(join(process.resourcesPath, 'resources', 'key', 'linux', 'x64', 'xkey_helper_linux')) + candidates.push(join(process.resourcesPath, 'resources', 'key', 'linux', 'xkey_helper_linux')) candidates.push(join(process.resourcesPath, 'resources', 'xkey_helper_linux')) candidates.push(join(process.resourcesPath, 'xkey_helper_linux')) } else { + candidates.push(join(app.getAppPath(), 'resources', 'key', 'linux', archDir, 'xkey_helper_linux')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'linux', 'x64', 'xkey_helper_linux')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'linux', 'xkey_helper_linux')) candidates.push(join(app.getAppPath(), 'resources', 'xkey_helper_linux')) + candidates.push(join(process.cwd(), 'resources', 'key', 'linux', archDir, 'xkey_helper_linux')) + candidates.push(join(process.cwd(), 'resources', 'key', 'linux', 'x64', 'xkey_helper_linux')) + candidates.push(join(process.cwd(), 'resources', 'key', 'linux', 'xkey_helper_linux')) candidates.push(join(app.getAppPath(), '..', 'Xkey', 'build', 'xkey_helper_linux')) } for (const p of candidates) { diff --git a/electron/services/keyServiceMac.ts b/electron/services/keyServiceMac.ts index e7642a9..c350eb1 100644 --- a/electron/services/keyServiceMac.ts +++ b/electron/services/keyServiceMac.ts @@ -27,6 +27,7 @@ export class KeyServiceMac { private getHelperPath(): string { const isPackaged = app.isPackaged + const archDir = process.arch === 'arm64' ? 'arm64' : 'x64' const candidates: string[] = [] if (process.env.WX_KEY_HELPER_PATH) { @@ -34,12 +35,21 @@ export class KeyServiceMac { } if (isPackaged) { + candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', archDir, 'xkey_helper')) + candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', 'universal', 'xkey_helper')) + candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', 'xkey_helper')) candidates.push(join(process.resourcesPath, 'resources', 'xkey_helper')) candidates.push(join(process.resourcesPath, 'xkey_helper')) } else { const cwd = process.cwd() + candidates.push(join(cwd, 'resources', 'key', 'macos', archDir, 'xkey_helper')) + candidates.push(join(cwd, 'resources', 'key', 'macos', 'universal', 'xkey_helper')) + candidates.push(join(cwd, 'resources', 'key', 'macos', 'xkey_helper')) candidates.push(join(cwd, 'resources', 'xkey_helper')) candidates.push(join(cwd, 'Xkey', 'build', 'xkey_helper')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', archDir, 'xkey_helper')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', 'universal', 'xkey_helper')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', 'xkey_helper')) candidates.push(join(app.getAppPath(), 'resources', 'xkey_helper')) } @@ -52,14 +62,24 @@ export class KeyServiceMac { private getImageScanHelperPath(): string { const isPackaged = app.isPackaged + const archDir = process.arch === 'arm64' ? 'arm64' : 'x64' const candidates: string[] = [] if (isPackaged) { + candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', archDir, 'image_scan_helper')) + candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', 'universal', 'image_scan_helper')) + candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', 'image_scan_helper')) candidates.push(join(process.resourcesPath, 'resources', 'image_scan_helper')) candidates.push(join(process.resourcesPath, 'image_scan_helper')) } else { const cwd = process.cwd() + candidates.push(join(cwd, 'resources', 'key', 'macos', archDir, 'image_scan_helper')) + candidates.push(join(cwd, 'resources', 'key', 'macos', 'universal', 'image_scan_helper')) + candidates.push(join(cwd, 'resources', 'key', 'macos', 'image_scan_helper')) candidates.push(join(cwd, 'resources', 'image_scan_helper')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', archDir, 'image_scan_helper')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', 'universal', 'image_scan_helper')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', 'image_scan_helper')) candidates.push(join(app.getAppPath(), 'resources', 'image_scan_helper')) } @@ -72,6 +92,7 @@ export class KeyServiceMac { private getDylibPath(): string { const isPackaged = app.isPackaged + const archDir = process.arch === 'arm64' ? 'arm64' : 'x64' const candidates: string[] = [] if (process.env.WX_KEY_DYLIB_PATH) { @@ -79,11 +100,20 @@ export class KeyServiceMac { } if (isPackaged) { + candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', archDir, 'libwx_key.dylib')) + candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', 'universal', 'libwx_key.dylib')) + candidates.push(join(process.resourcesPath, 'resources', 'key', 'macos', 'libwx_key.dylib')) candidates.push(join(process.resourcesPath, 'resources', 'libwx_key.dylib')) candidates.push(join(process.resourcesPath, 'libwx_key.dylib')) } else { const cwd = process.cwd() + candidates.push(join(cwd, 'resources', 'key', 'macos', archDir, 'libwx_key.dylib')) + candidates.push(join(cwd, 'resources', 'key', 'macos', 'universal', 'libwx_key.dylib')) + candidates.push(join(cwd, 'resources', 'key', 'macos', 'libwx_key.dylib')) candidates.push(join(cwd, 'resources', 'libwx_key.dylib')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', archDir, 'libwx_key.dylib')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', 'universal', 'libwx_key.dylib')) + candidates.push(join(app.getAppPath(), 'resources', 'key', 'macos', 'libwx_key.dylib')) candidates.push(join(app.getAppPath(), 'resources', 'libwx_key.dylib')) } diff --git a/electron/services/wcdbCore.ts b/electron/services/wcdbCore.ts index dcf6dee..fde2ca7 100644 --- a/electron/services/wcdbCore.ts +++ b/electron/services/wcdbCore.ts @@ -121,6 +121,9 @@ export class WcdbCore { private videoHardlinkCache: Map = new Map() private readonly hardlinkCacheTtlMs = 10 * 60 * 1000 private readonly hardlinkCacheMaxEntries = 20000 + private mediaStreamSessionCache: Array<{ sessionId: string; displayName: string; sortTimestamp: number }> | null = null + private mediaStreamSessionCacheAt = 0 + private readonly mediaStreamSessionCacheTtlMs = 12 * 1000 private logTimer: NodeJS.Timeout | null = null private lastLogTail: string | null = null private lastResolvedLogPath: string | null = null @@ -277,7 +280,9 @@ export class WcdbCore { const isLinux = process.platform === 'linux' const isArm64 = process.arch === 'arm64' const libName = isMac ? 'libwcdb_api.dylib' : isLinux ? 'libwcdb_api.so' : 'wcdb_api.dll' - const subDir = isMac ? 'macos' : isLinux ? 'linux' : (isArm64 ? 'arm64' : '') + const legacySubDir = isMac ? 'macos' : isLinux ? 'linux' : (isArm64 ? 'arm64' : '') + const platformDir = isMac ? 'macos' : (isLinux ? 'linux' : 'win32') + const archDir = isMac ? 'universal' : (isArm64 ? 'arm64' : 'x64') const envDllPath = process.env.WCDB_DLL_PATH if (envDllPath && envDllPath.length > 0) { @@ -287,20 +292,33 @@ export class WcdbCore { // 基础路径探测 const isPackaged = typeof process['resourcesPath'] !== 'undefined' const resourcesPath = isPackaged ? process.resourcesPath : join(process.cwd(), 'resources') - - const candidates = [ - // 环境变量指定 resource 目录 - process.env.WCDB_RESOURCES_PATH ? join(process.env.WCDB_RESOURCES_PATH, subDir, libName) : null, - // 显式 setPaths 设置的路径 - this.resourcesPath ? join(this.resourcesPath, subDir, libName) : null, - // resources/macos/libwcdb_api.dylib 或 resources/wcdb_api.dll - join(resourcesPath, 'resources', subDir, libName), - // resources/libwcdb_api.dylib 或 resources/wcdb_api.dll (扁平结构) - join(resourcesPath, subDir, libName), - // CWD fallback - join(process.cwd(), 'resources', subDir, libName) + const roots = [ + process.env.WCDB_RESOURCES_PATH || null, + this.resourcesPath || null, + join(resourcesPath, 'resources'), + resourcesPath, + join(process.cwd(), 'resources') ].filter(Boolean) as string[] + const normalizedArch = process.arch === 'arm64' ? 'arm64' : 'x64' + const relativeCandidates = [ + join('wcdb', platformDir, archDir, libName), + join('wcdb', platformDir, normalizedArch, libName), + join('wcdb', platformDir, 'x64', libName), + join('wcdb', platformDir, 'universal', libName), + join('wcdb', platformDir, libName) + ] + + const candidates: string[] = [] + for (const root of roots) { + for (const relativePath of relativeCandidates) { + candidates.push(join(root, relativePath)) + } + // 兼容旧目录:resources/macos/libwcdb_api.dylib 或 resources/wcdb_api.dll + candidates.push(join(root, legacySubDir, libName)) + candidates.push(join(root, libName)) + } + for (const path of candidates) { if (existsSync(path)) return path } @@ -1465,6 +1483,11 @@ export class WcdbCore { this.videoHardlinkCache.clear() } + private clearMediaStreamSessionCache(): void { + this.mediaStreamSessionCache = null + this.mediaStreamSessionCacheAt = 0 + } + isReady(): boolean { return this.ensureReady() } @@ -1580,6 +1603,7 @@ export class WcdbCore { this.currentDbStoragePath = null this.initialized = false this.clearHardlinkCaches() + this.clearMediaStreamSessionCache() this.stopLogPolling() } } @@ -1957,7 +1981,7 @@ export class WcdbCore { error?: string }> { if (!this.ensureReady()) return { success: false, error: 'WCDB 未连接' } - if (!this.wcdbScanMediaStream) return { success: false, error: '当前数据服务版本不支持媒体流扫描,请先更新 wcdb 数据服务' } + if (!this.wcdbScanMediaStream) return { success: false, error: '当前数据服务版本不支持资源扫描,请先更新 wcdb 数据服务' } try { const toInt = (value: unknown): number => { const n = Number(value || 0) @@ -2168,37 +2192,64 @@ export class WcdbCore { const offset = Math.max(0, toInt(options?.offset)) const limit = Math.min(1200, Math.max(40, toInt(options?.limit) || 240)) - const sessionsRes = await this.getSessions() - if (!sessionsRes.success || !Array.isArray(sessionsRes.sessions)) { - return { success: false, error: sessionsRes.error || '读取会话失败' } + const getSessionRows = async (): Promise<{ + success: boolean + rows?: Array<{ sessionId: string; displayName: string; sortTimestamp: number }> + error?: string + }> => { + const now = Date.now() + const cachedRows = this.mediaStreamSessionCache + if ( + cachedRows && + now - this.mediaStreamSessionCacheAt <= this.mediaStreamSessionCacheTtlMs + ) { + return { success: true, rows: cachedRows } + } + + const sessionsRes = await this.getSessions() + if (!sessionsRes.success || !Array.isArray(sessionsRes.sessions)) { + return { success: false, error: sessionsRes.error || '读取会话失败' } + } + + const rows = (sessionsRes.sessions || []) + .map((row: any) => ({ + sessionId: String( + row.username || + row.user_name || + row.userName || + row.usrName || + row.UsrName || + row.talker || + '' + ).trim(), + displayName: String(row.displayName || row.display_name || row.remark || '').trim(), + sortTimestamp: toInt( + row.sort_timestamp || + row.sortTimestamp || + row.last_timestamp || + row.lastTimestamp || + 0 + ) + })) + .filter((row) => Boolean(row.sessionId)) + .sort((a, b) => b.sortTimestamp - a.sortTimestamp) + + this.mediaStreamSessionCache = rows + this.mediaStreamSessionCacheAt = now + return { success: true, rows } } - const sessions = (sessionsRes.sessions || []) - .map((row: any) => ({ - sessionId: String( - row.username || - row.user_name || - row.userName || - row.usrName || - row.UsrName || - row.talker || - '' - ).trim(), - displayName: String(row.displayName || row.display_name || row.remark || '').trim(), - sortTimestamp: toInt( - row.sort_timestamp || - row.sortTimestamp || - row.last_timestamp || - row.lastTimestamp || - 0 - ) - })) - .filter((row) => Boolean(row.sessionId)) - .sort((a, b) => b.sortTimestamp - a.sortTimestamp) + let sessionRows: Array<{ sessionId: string; displayName: string; sortTimestamp: number }> = [] + if (requestedSessionId) { + sessionRows = [{ sessionId: requestedSessionId, displayName: requestedSessionId, sortTimestamp: 0 }] + } else { + const sessionsRowsRes = await getSessionRows() + if (!sessionsRowsRes.success || !Array.isArray(sessionsRowsRes.rows)) { + return { success: false, error: sessionsRowsRes.error || '读取会话失败' } + } + sessionRows = sessionsRowsRes.rows + } - const sessionRows = requestedSessionId - ? sessions.filter((row) => row.sessionId === requestedSessionId) - : sessions if (sessionRows.length === 0) { return { success: true, items: [], hasMore: false, nextOffset: offset } } @@ -2219,10 +2270,10 @@ export class WcdbCore { outHasMore ) if (result !== 0 || !outPtr[0]) { - return { success: false, error: `扫描媒体流失败: ${result}` } + return { success: false, error: `扫描资源失败: ${result}` } } const jsonStr = this.decodeJsonPtr(outPtr[0]) - if (!jsonStr) return { success: false, error: '解析媒体流失败' } + if (!jsonStr) return { success: false, error: '解析资源失败' } const rows = JSON.parse(jsonStr) const list = Array.isArray(rows) ? rows as Array> : [] @@ -2254,19 +2305,39 @@ export class WcdbCore { rawMessageContent && (rawMessageContent.includes('<') || rawMessageContent.includes('md5') || rawMessageContent.includes('videomsg')) ) - const content = useRawMessageContent - ? rawMessageContent - : decodeMessageContent(rawMessageContent, rawCompressContent) + const decodeContentIfNeeded = (): string => { + if (useRawMessageContent) return rawMessageContent + if (!rawMessageContent && !rawCompressContent) return '' + return decodeMessageContent(rawMessageContent, rawCompressContent) + } const packedPayload = extractPackedPayload(row) const imageMd5ByColumn = pickString(row, ['image_md5', 'imageMd5']) - const imageMd5 = localType === 3 - ? (imageMd5ByColumn || extractImageMd5(content) || extractHexMd5(packedPayload) || undefined) - : undefined - const imageDatName = localType === 3 ? (extractImageDatName(row, content) || undefined) : undefined const videoMd5ByColumn = pickString(row, ['video_md5', 'videoMd5', 'raw_md5', 'rawMd5']) - const videoMd5 = localType === 43 - ? (videoMd5ByColumn || extractVideoMd5(content) || extractHexMd5(packedPayload) || undefined) - : undefined + + let content = '' + let imageMd5: string | undefined + let imageDatName: string | undefined + let videoMd5: string | undefined + + if (localType === 3) { + imageMd5 = imageMd5ByColumn || extractHexMd5(packedPayload) || undefined + imageDatName = extractImageDatName(row, '') || undefined + if (!imageMd5 || !imageDatName) { + content = decodeContentIfNeeded() + if (!imageMd5) imageMd5 = extractImageMd5(content) || extractHexMd5(packedPayload) || undefined + if (!imageDatName) imageDatName = extractImageDatName(row, content) || undefined + } + } else if (localType === 43) { + videoMd5 = videoMd5ByColumn || extractHexMd5(packedPayload) || undefined + if (!videoMd5) { + content = decodeContentIfNeeded() + videoMd5 = extractVideoMd5(content) || extractHexMd5(packedPayload) || undefined + } else if (useRawMessageContent) { + // 占位态标题只依赖简单 XML,已带 md5 时不做额外解压 + content = rawMessageContent + } + } + return { sessionId, sessionDisplayName: sessionNameMap.get(sessionId) || sessionId, @@ -2280,7 +2351,7 @@ export class WcdbCore { imageMd5, imageDatName, videoMd5, - content: content || undefined + content: localType === 43 ? (content || undefined) : undefined } }) diff --git a/package.json b/package.json index c9811cd..1da944d 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "gatekeeperAssess": false, "entitlements": "electron/entitlements.mac.plist", "entitlementsInherit": "electron/entitlements.mac.plist", - "icon": "resources/icon.icns" + "icon": "resources/icons/macos/icon.icns" }, "win": { "target": [ @@ -107,19 +107,19 @@ "icon": "public/icon.ico", "extraFiles": [ { - "from": "resources/msvcp140.dll", + "from": "resources/runtime/win32/msvcp140.dll", "to": "." }, { - "from": "resources/msvcp140_1.dll", + "from": "resources/runtime/win32/msvcp140_1.dll", "to": "." }, { - "from": "resources/vcruntime140.dll", + "from": "resources/runtime/win32/vcruntime140.dll", "to": "." }, { - "from": "resources/vcruntime140_1.dll", + "from": "resources/runtime/win32/vcruntime140_1.dll", "to": "." } ] @@ -135,7 +135,7 @@ "synopsis": "WeFlow for Linux", "extraFiles": [ { - "from": "resources/linux/install.sh", + "from": "resources/installer/linux/install.sh", "to": "install.sh" } ] @@ -190,7 +190,7 @@ "node_modules/sherpa-onnx-*/**/*", "node_modules/ffmpeg-static/**/*" ], - "icon": "resources/icon.icns" + "icon": "resources/icons/macos/icon.icns" }, "overrides": { "picomatch": "^4.0.4", diff --git a/resources/arm64/wcdb_api.dll b/resources/arm64/wcdb_api.dll deleted file mode 100644 index 78747ac..0000000 Binary files a/resources/arm64/wcdb_api.dll and /dev/null differ diff --git a/resources/icon.icns b/resources/icons/macos/icon.icns similarity index 100% rename from resources/icon.icns rename to resources/icons/macos/icon.icns diff --git a/resources/linux/install.sh b/resources/installer/linux/install.sh similarity index 100% rename from resources/linux/install.sh rename to resources/installer/linux/install.sh diff --git a/resources/xkey_helper_linux b/resources/key/linux/x64/xkey_helper_linux old mode 100755 new mode 100644 similarity index 100% rename from resources/xkey_helper_linux rename to resources/key/linux/x64/xkey_helper_linux diff --git a/resources/image_scan_entitlements.plist b/resources/key/macos/source/image_scan_entitlements.plist similarity index 100% rename from resources/image_scan_entitlements.plist rename to resources/key/macos/source/image_scan_entitlements.plist diff --git a/resources/image_scan_helper.c b/resources/key/macos/source/image_scan_helper.c similarity index 100% rename from resources/image_scan_helper.c rename to resources/key/macos/source/image_scan_helper.c diff --git a/resources/image_scan_helper b/resources/key/macos/universal/image_scan_helper old mode 100755 new mode 100644 similarity index 100% rename from resources/image_scan_helper rename to resources/key/macos/universal/image_scan_helper diff --git a/resources/libwx_key.dylib b/resources/key/macos/universal/libwx_key.dylib old mode 100755 new mode 100644 similarity index 100% rename from resources/libwx_key.dylib rename to resources/key/macos/universal/libwx_key.dylib diff --git a/resources/xkey_helper b/resources/key/macos/universal/xkey_helper old mode 100755 new mode 100644 similarity index 100% rename from resources/xkey_helper rename to resources/key/macos/universal/xkey_helper diff --git a/resources/xkey_helper_macos b/resources/key/macos/universal/xkey_helper_macos similarity index 100% rename from resources/xkey_helper_macos rename to resources/key/macos/universal/xkey_helper_macos diff --git a/resources/wx_key.dll b/resources/key/win32/x64/wx_key.dll similarity index 100% rename from resources/wx_key.dll rename to resources/key/win32/x64/wx_key.dll diff --git a/resources/libwcdb_api.dylib b/resources/libwcdb_api.dylib deleted file mode 100755 index d185cfc..0000000 Binary files a/resources/libwcdb_api.dylib and /dev/null differ diff --git a/resources/libwcdb_api.so b/resources/libwcdb_api.so deleted file mode 100755 index d3c686a..0000000 Binary files a/resources/libwcdb_api.so and /dev/null differ diff --git a/resources/macos/libwcdb_api.dylib b/resources/macos/libwcdb_api.dylib deleted file mode 100755 index 26b44d2..0000000 Binary files a/resources/macos/libwcdb_api.dylib and /dev/null differ diff --git a/resources/msvcp140.dll b/resources/runtime/win32/msvcp140.dll similarity index 100% rename from resources/msvcp140.dll rename to resources/runtime/win32/msvcp140.dll diff --git a/resources/msvcp140_1.dll b/resources/runtime/win32/msvcp140_1.dll similarity index 100% rename from resources/msvcp140_1.dll rename to resources/runtime/win32/msvcp140_1.dll diff --git a/resources/vcruntime140.dll b/resources/runtime/win32/vcruntime140.dll similarity index 100% rename from resources/vcruntime140.dll rename to resources/runtime/win32/vcruntime140.dll diff --git a/resources/vcruntime140_1.dll b/resources/runtime/win32/vcruntime140_1.dll similarity index 100% rename from resources/vcruntime140_1.dll rename to resources/runtime/win32/vcruntime140_1.dll diff --git a/resources/linux/libwcdb_api.so b/resources/wcdb/linux/x64/libwcdb_api.so old mode 100755 new mode 100644 similarity index 66% rename from resources/linux/libwcdb_api.so rename to resources/wcdb/linux/x64/libwcdb_api.so index 0fa218c..8f698f3 Binary files a/resources/linux/libwcdb_api.so and b/resources/wcdb/linux/x64/libwcdb_api.so differ diff --git a/resources/macos/libWCDB.dylib b/resources/wcdb/macos/universal/libWCDB.dylib old mode 100755 new mode 100644 similarity index 100% rename from resources/macos/libWCDB.dylib rename to resources/wcdb/macos/universal/libWCDB.dylib diff --git a/resources/wcdb/macos/universal/libwcdb_api.dylib b/resources/wcdb/macos/universal/libwcdb_api.dylib new file mode 100644 index 0000000..5a81c68 Binary files /dev/null and b/resources/wcdb/macos/universal/libwcdb_api.dylib differ diff --git a/resources/arm64/WCDB.dll b/resources/wcdb/win32/arm64/WCDB.dll similarity index 100% rename from resources/arm64/WCDB.dll rename to resources/wcdb/win32/arm64/WCDB.dll diff --git a/resources/wcdb/win32/arm64/wcdb_api.dll b/resources/wcdb/win32/arm64/wcdb_api.dll new file mode 100644 index 0000000..5f144d8 Binary files /dev/null and b/resources/wcdb/win32/arm64/wcdb_api.dll differ diff --git a/resources/SDL2.dll b/resources/wcdb/win32/x64/SDL2.dll similarity index 100% rename from resources/SDL2.dll rename to resources/wcdb/win32/x64/SDL2.dll diff --git a/resources/WCDB.dll b/resources/wcdb/win32/x64/WCDB.dll similarity index 100% rename from resources/WCDB.dll rename to resources/wcdb/win32/x64/WCDB.dll diff --git a/resources/wcdb_api.dll b/resources/wcdb/win32/x64/wcdb_api.dll similarity index 100% rename from resources/wcdb_api.dll rename to resources/wcdb/win32/x64/wcdb_api.dll diff --git a/src/pages/ChatPage.scss b/src/pages/ChatPage.scss index 8190a19..22d2e56 100644 --- a/src/pages/ChatPage.scss +++ b/src/pages/ChatPage.scss @@ -1965,6 +1965,10 @@ color: var(--on-primary); border-radius: 18px 18px 4px 18px; } + + .bubble-body { + align-items: flex-end; + } } // 对方发送的消息 - 左侧白色 @@ -1974,6 +1978,10 @@ color: var(--text-primary); border-radius: 18px 18px 18px 4px; } + + .bubble-body { + align-items: flex-start; + } } &.system { @@ -2038,6 +2046,12 @@ white-space: pre-wrap; } +// 让文字气泡按内容收缩,不被群昵称行宽度牵连 +.message-bubble:not(.system) .bubble-content { + width: fit-content; + max-width: 100%; +} + // 表情包消息 .message-bubble.emoji { .bubble-content { diff --git a/src/pages/ResourcesPage.tsx b/src/pages/ResourcesPage.tsx index f58c729..7518647 100644 --- a/src/pages/ResourcesPage.tsx +++ b/src/pages/ResourcesPage.tsx @@ -1,6 +1,7 @@ import { forwardRef, memo, useCallback, useEffect, useMemo, useRef, useState, type HTMLAttributes } from 'react' import { Calendar, Image as ImageIcon, Loader2, PlayCircle, RefreshCw, Trash2, UserRound } from 'lucide-react' import { VirtuosoGrid } from 'react-virtuoso' +import { finishBackgroundTask, registerBackgroundTask, updateBackgroundTask } from '../services/backgroundTaskMonitor' import './ResourcesPage.scss' type MediaTab = 'image' | 'video' @@ -35,10 +36,14 @@ type DialogState = { onConfirm?: (() => void) | null } -const PAGE_SIZE = 120 -const MAX_IMAGE_CACHE_RESOLVE_PER_TICK = 18 -const MAX_IMAGE_CACHE_PRELOAD_PER_TICK = 36 -const MAX_VIDEO_POSTER_RESOLVE_PER_TICK = 4 +const PAGE_SIZE = 96 +const MAX_IMAGE_CACHE_RESOLVE_PER_TICK = 12 +const MAX_IMAGE_CACHE_PRELOAD_PER_TICK = 24 +const MAX_VIDEO_POSTER_RESOLVE_PER_TICK = 3 +const INITIAL_IMAGE_PRELOAD_END = 48 +const INITIAL_IMAGE_RESOLVE_END = 12 +const TASK_PROGRESS_UPDATE_MIN_INTERVAL_MS = 250 +const TASK_PROGRESS_UPDATE_MAX_STEPS = 100 const GridList = forwardRef>(function GridList(props, ref) { const { className = '', ...rest } = props @@ -409,7 +414,13 @@ function ResourcesPage() { } try { - await window.electronAPI.chat.connect() + if (reset) { + const connectResult = await window.electronAPI.chat.connect() + if (!connectResult.success) { + setError(connectResult.error || '连接数据库失败') + return + } + } const requestOffset = reset ? 0 : nextOffset const streamResult = await window.electronAPI.chat.getMediaStream({ sessionId: selectedContact === 'all' ? undefined : selectedContact, @@ -524,7 +535,6 @@ function ResourcesPage() { let cancelled = false const run = async () => { try { - await window.electronAPI.chat.connect() const sessionResult = await window.electronAPI.chat.getSessions() if (!cancelled && sessionResult.success && Array.isArray(sessionResult.sessions)) { const initialNameMap: Record = {} @@ -674,7 +684,10 @@ function ResourcesPage() { resolvingImageCacheBatchRef.current = true void (async () => { try { - const result = await window.electronAPI.image.resolveCacheBatch(payloads, { disableUpdateCheck: true }) + const result = await window.electronAPI.image.resolveCacheBatch(payloads, { + disableUpdateCheck: true, + allowCacheIndex: false + }) const rows = Array.isArray(result?.rows) ? result.rows : [] const pathPatch: Record = {} const updatePatch: Record = {} @@ -741,7 +754,10 @@ function ResourcesPage() { if (payloads.length >= MAX_IMAGE_CACHE_PRELOAD_PER_TICK) break } if (payloads.length === 0) return - void window.electronAPI.image.preload(payloads, { allowDecrypt: false }) + void window.electronAPI.image.preload(payloads, { + allowDecrypt: false, + allowCacheIndex: false + }) }, [displayItems]) const resolveItemVideoMd5 = useCallback(async (item: MediaStreamItem): Promise => { @@ -813,14 +829,18 @@ function ResourcesPage() { if (!pending) return pendingRangeRef.current = null if (tab === 'image') { - preloadImageCacheRange(pending.start - 8, pending.end + 32) - resolveImageCacheRange(pending.start - 2, pending.end + 8) + preloadImageCacheRange(pending.start - 4, pending.end + 20) + resolveImageCacheRange(pending.start - 1, pending.end + 6) return } resolvePosterRange(pending.start, pending.end) }, [preloadImageCacheRange, resolveImageCacheRange, resolvePosterRange, tab]) const scheduleRangeResolve = useCallback((start: number, end: number) => { + const previous = pendingRangeRef.current + if (previous && start >= previous.start && end <= previous.end) { + return + } pendingRangeRef.current = { start, end } if (rangeTimerRef.current !== null) { window.clearTimeout(rangeTimerRef.current) @@ -832,8 +852,8 @@ function ResourcesPage() { useEffect(() => { if (displayItems.length === 0) return if (tab === 'image') { - preloadImageCacheRange(0, Math.min(displayItems.length - 1, 80)) - resolveImageCacheRange(0, Math.min(displayItems.length - 1, 20)) + preloadImageCacheRange(0, Math.min(displayItems.length - 1, INITIAL_IMAGE_PRELOAD_END)) + resolveImageCacheRange(0, Math.min(displayItems.length - 1, INITIAL_IMAGE_RESOLVE_END)) return } resolvePosterRange(0, Math.min(displayItems.length - 1, 12)) @@ -1057,25 +1077,61 @@ function ResourcesPage() { setBatchBusy(true) let success = 0 + let failed = 0 const previewPatch: Record = {} const updatePatch: Record = {} + const taskId = registerBackgroundTask({ + sourcePage: 'other', + title: '资源页图片批量解密', + detail: `正在解密图片(0/${imageItems.length})`, + progressText: `0 / ${imageItems.length}`, + cancelable: false + }) try { + let completed = 0 + const progressStep = Math.max(1, Math.floor(imageItems.length / TASK_PROGRESS_UPDATE_MAX_STEPS)) + let lastProgressBucket = 0 + let lastProgressUpdateAt = Date.now() + const updateTaskProgress = (force: boolean = false) => { + const now = Date.now() + const bucket = Math.floor(completed / progressStep) + const crossedBucket = bucket !== lastProgressBucket + const intervalReached = now - lastProgressUpdateAt >= TASK_PROGRESS_UPDATE_MIN_INTERVAL_MS + if (!force && !crossedBucket && !intervalReached) return + updateBackgroundTask(taskId, { + detail: `正在解密图片(${completed}/${imageItems.length})`, + progressText: `${completed} / ${imageItems.length}` + }) + lastProgressBucket = bucket + lastProgressUpdateAt = now + } for (const item of imageItems) { - if (!item.imageMd5 && !item.imageDatName) continue + if (!item.imageMd5 && !item.imageDatName) { + failed += 1 + completed += 1 + updateTaskProgress() + continue + } const result = await window.electronAPI.image.decrypt({ sessionId: item.sessionId, imageMd5: item.imageMd5 || undefined, imageDatName: item.imageDatName || undefined, force: true }) - if (!result?.success) continue - success += 1 - if (result.localPath) { - const key = getItemKey(item) - previewPatch[key] = result.localPath - updatePatch[key] = isLikelyThumbnailPreview(result.localPath) + if (!result?.success) { + failed += 1 + } else { + success += 1 + if (result.localPath) { + const key = getItemKey(item) + previewPatch[key] = result.localPath + updatePatch[key] = isLikelyThumbnailPreview(result.localPath) + } } + completed += 1 + updateTaskProgress() } + updateTaskProgress(true) if (Object.keys(previewPatch).length > 0) { setPreviewPathMap((prev) => ({ ...prev, ...previewPatch })) @@ -1083,8 +1139,17 @@ function ResourcesPage() { if (Object.keys(updatePatch).length > 0) { setPreviewUpdateMap((prev) => ({ ...prev, ...updatePatch })) } - setActionMessage(`批量解密完成:成功 ${success},失败 ${imageItems.length - success}`) - showAlert(`批量解密完成:成功 ${success},失败 ${imageItems.length - success}`, '批量解密完成') + setActionMessage(`批量解密完成:成功 ${success},失败 ${failed}`) + showAlert(`批量解密完成:成功 ${success},失败 ${failed}`, '批量解密完成') + finishBackgroundTask(taskId, success > 0 || failed === 0 ? 'completed' : 'failed', { + detail: `资源页图片批量解密完成:成功 ${success},失败 ${failed}`, + progressText: `成功 ${success} / 失败 ${failed}` + }) + } catch (e) { + finishBackgroundTask(taskId, 'failed', { + detail: `资源页图片批量解密失败:${String(e)}` + }) + showAlert(`批量解密失败:${String(e)}`, '批量解密失败') } finally { setBatchBusy(false) } diff --git a/src/stores/batchImageDecryptStore.ts b/src/stores/batchImageDecryptStore.ts index d074362..8e162fb 100644 --- a/src/stores/batchImageDecryptStore.ts +++ b/src/stores/batchImageDecryptStore.ts @@ -1,4 +1,10 @@ import { create } from 'zustand' +import { + finishBackgroundTask, + registerBackgroundTask, + updateBackgroundTask +} from '../services/backgroundTaskMonitor' +import type { BackgroundTaskSourcePage } from '../types/backgroundTask' export interface BatchImageDecryptState { isBatchDecrypting: boolean @@ -8,8 +14,9 @@ export interface BatchImageDecryptState { result: { success: number; fail: number } startTime: number sessionName: string + taskId: string | null - startDecrypt: (total: number, sessionName: string) => void + startDecrypt: (total: number, sessionName: string, sourcePage?: BackgroundTaskSourcePage) => void updateProgress: (current: number, total: number) => void finishDecrypt: (success: number, fail: number) => void setShowToast: (show: boolean) => void @@ -17,7 +24,26 @@ export interface BatchImageDecryptState { reset: () => void } -export const useBatchImageDecryptStore = create((set) => ({ +const clampProgress = (current: number, total: number): { current: number; total: number } => { + const normalizedTotal = Number.isFinite(total) ? Math.max(0, Math.floor(total)) : 0 + const normalizedCurrentRaw = Number.isFinite(current) ? Math.max(0, Math.floor(current)) : 0 + const normalizedCurrent = normalizedTotal > 0 + ? Math.min(normalizedCurrentRaw, normalizedTotal) + : normalizedCurrentRaw + return { current: normalizedCurrent, total: normalizedTotal } +} + +const TASK_PROGRESS_UPDATE_MIN_INTERVAL_MS = 250 +const TASK_PROGRESS_UPDATE_MAX_STEPS = 100 + +const taskProgressUpdateMeta = new Map() + +const calcProgressStep = (total: number): number => { + if (total <= 0) return 1 + return Math.max(1, Math.floor(total / TASK_PROGRESS_UPDATE_MAX_STEPS)) +} + +export const useBatchImageDecryptStore = create((set, get) => ({ isBatchDecrypting: false, progress: { current: 0, total: 0 }, showToast: false, @@ -25,40 +51,127 @@ export const useBatchImageDecryptStore = create((set) => result: { success: 0, fail: 0 }, startTime: 0, sessionName: '', + taskId: null, - startDecrypt: (total, sessionName) => set({ - isBatchDecrypting: true, - progress: { current: 0, total }, - showToast: true, - showResultToast: false, - result: { success: 0, fail: 0 }, - startTime: Date.now(), - sessionName - }), + startDecrypt: (total, sessionName, sourcePage = 'chat') => { + const previousTaskId = get().taskId + if (previousTaskId) { + taskProgressUpdateMeta.delete(previousTaskId) + finishBackgroundTask(previousTaskId, 'canceled', { + detail: '已被新的批量解密任务替换', + progressText: '已替换' + }) + } - updateProgress: (current, total) => set({ - progress: { current, total } - }), + const normalizedProgress = clampProgress(0, total) + const normalizedSessionName = String(sessionName || '').trim() + const title = normalizedSessionName + ? `图片批量解密(${normalizedSessionName})` + : '图片批量解密' + const taskId = registerBackgroundTask({ + sourcePage, + title, + detail: `正在解密图片(${normalizedProgress.current}/${normalizedProgress.total})`, + progressText: `${normalizedProgress.current} / ${normalizedProgress.total}`, + cancelable: false + }) + taskProgressUpdateMeta.set(taskId, { + lastAt: Date.now(), + lastBucket: 0, + step: calcProgressStep(normalizedProgress.total) + }) - finishDecrypt: (success, fail) => set({ - isBatchDecrypting: false, - showToast: false, - showResultToast: true, - result: { success, fail }, - startTime: 0 - }), + set({ + isBatchDecrypting: true, + progress: normalizedProgress, + showToast: true, + showResultToast: false, + result: { success: 0, fail: 0 }, + startTime: Date.now(), + sessionName: normalizedSessionName, + taskId + }) + }, + + updateProgress: (current, total) => { + const previousProgress = get().progress + const normalizedProgress = clampProgress(current, total) + const taskId = get().taskId + if (taskId) { + const now = Date.now() + const meta = taskProgressUpdateMeta.get(taskId) + const step = meta?.step || calcProgressStep(normalizedProgress.total) + const bucket = Math.floor(normalizedProgress.current / step) + const intervalReached = !meta || (now - meta.lastAt >= TASK_PROGRESS_UPDATE_MIN_INTERVAL_MS) + const crossedBucket = !meta || bucket !== meta.lastBucket + const isFinal = normalizedProgress.total > 0 && normalizedProgress.current >= normalizedProgress.total + if (crossedBucket || intervalReached || isFinal) { + updateBackgroundTask(taskId, { + detail: `正在解密图片(${normalizedProgress.current}/${normalizedProgress.total})`, + progressText: `${normalizedProgress.current} / ${normalizedProgress.total}` + }) + taskProgressUpdateMeta.set(taskId, { + lastAt: now, + lastBucket: bucket, + step + }) + } + } + if ( + previousProgress.current !== normalizedProgress.current || + previousProgress.total !== normalizedProgress.total + ) { + set({ + progress: normalizedProgress + }) + } + }, + + finishDecrypt: (success, fail) => { + const taskId = get().taskId + const normalizedSuccess = Number.isFinite(success) ? Math.max(0, Math.floor(success)) : 0 + const normalizedFail = Number.isFinite(fail) ? Math.max(0, Math.floor(fail)) : 0 + if (taskId) { + taskProgressUpdateMeta.delete(taskId) + const status = normalizedSuccess > 0 || normalizedFail === 0 ? 'completed' : 'failed' + finishBackgroundTask(taskId, status, { + detail: `图片批量解密完成:成功 ${normalizedSuccess},失败 ${normalizedFail}`, + progressText: `成功 ${normalizedSuccess} / 失败 ${normalizedFail}` + }) + } + + set({ + isBatchDecrypting: false, + showToast: false, + showResultToast: true, + result: { success: normalizedSuccess, fail: normalizedFail }, + startTime: 0, + taskId: null + }) + }, setShowToast: (show) => set({ showToast: show }), setShowResultToast: (show) => set({ showResultToast: show }), - reset: () => set({ - isBatchDecrypting: false, - progress: { current: 0, total: 0 }, - showToast: false, - showResultToast: false, - result: { success: 0, fail: 0 }, - startTime: 0, - sessionName: '' - }) -})) + reset: () => { + const taskId = get().taskId + if (taskId) { + taskProgressUpdateMeta.delete(taskId) + finishBackgroundTask(taskId, 'canceled', { + detail: '批量解密任务已重置', + progressText: '已停止' + }) + } + set({ + isBatchDecrypting: false, + progress: { current: 0, total: 0 }, + showToast: false, + showResultToast: false, + result: { success: 0, fail: 0 }, + startTime: 0, + sessionName: '', + taskId: null + }) + } +})) diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index 3319b95..9de0e7b 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -403,10 +403,16 @@ export interface ElectronAPI { image: { decrypt: (payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; force?: boolean }) => Promise<{ success: boolean; localPath?: string; liveVideoPath?: string; error?: string }> - resolveCache: (payload: { sessionId?: string; imageMd5?: string; imageDatName?: string; disableUpdateCheck?: boolean }) => Promise<{ success: boolean; localPath?: string; hasUpdate?: boolean; liveVideoPath?: string; error?: string }> + resolveCache: (payload: { + sessionId?: string + imageMd5?: string + imageDatName?: string + disableUpdateCheck?: boolean + allowCacheIndex?: boolean + }) => Promise<{ success: boolean; localPath?: string; hasUpdate?: boolean; liveVideoPath?: string; error?: string }> resolveCacheBatch: ( payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>, - options?: { disableUpdateCheck?: boolean } + options?: { disableUpdateCheck?: boolean; allowCacheIndex?: boolean } ) => Promise<{ success: boolean rows?: Array<{ success: boolean; localPath?: string; hasUpdate?: boolean; error?: string }> @@ -414,7 +420,7 @@ export interface ElectronAPI { }> preload: ( payloads: Array<{ sessionId?: string; imageMd5?: string; imageDatName?: string }>, - options?: { allowDecrypt?: boolean } + options?: { allowDecrypt?: boolean; allowCacheIndex?: boolean } ) => Promise onUpdateAvailable: (callback: (payload: { cacheKey: string; imageMd5?: string; imageDatName?: string }) => void) => () => void onCacheResolved: (callback: (payload: { cacheKey: string; imageMd5?: string; imageDatName?: string; localPath: string }) => void) => () => void