diff --git a/electron/services/exportService.ts b/electron/services/exportService.ts index 61f38a8..6f72ca9 100644 --- a/electron/services/exportService.ts +++ b/electron/services/exportService.ts @@ -2722,128 +2722,6 @@ class ExportService { return result } - private async collectPrivateMutualGroupStats( - privateWxid: string, - myWxid: string - ): Promise<{ - totalGroups: number - totalMessagesByMe: number - totalMessagesByPeer: number - totalMessagesCombined: number - groups: Array<{ - wxid: string - displayName: string - myMessageCount: number - peerMessageCount: number - totalMessageCount: number - }> - }> { - const normalizedPrivateWxid = String(privateWxid || '').trim() - const normalizedMyWxid = String(myWxid || '').trim() - if (!normalizedPrivateWxid || !normalizedMyWxid) { - return { - totalGroups: 0, - totalMessagesByMe: 0, - totalMessagesByPeer: 0, - totalMessagesCombined: 0, - groups: [] - } - } - - const sessionsResult = await wcdbService.getSessions() - if (!sessionsResult.success || !sessionsResult.sessions) { - return { - totalGroups: 0, - totalMessagesByMe: 0, - totalMessagesByPeer: 0, - totalMessagesCombined: 0, - groups: [] - } - } - - const groupIds = Array.from( - new Set( - (sessionsResult.sessions as Array>) - .map((row) => String(row.username || row.user_name || row.userName || '').trim()) - .filter((username) => username.endsWith('@chatroom')) - ) - ) - if (groupIds.length === 0) { - return { - totalGroups: 0, - totalMessagesByMe: 0, - totalMessagesByPeer: 0, - totalMessagesCombined: 0, - groups: [] - } - } - - const mutualGroups = await parallelLimit(groupIds, 4, async (groupId) => { - const membersResult = await wcdbService.getGroupMembers(groupId) - if (!membersResult.success || !membersResult.members || membersResult.members.length === 0) { - return null - } - - let hasMe = false - let hasPeer = false - for (const member of membersResult.members) { - const memberWxid = this.extractGroupMemberUsername(member) - if (!memberWxid) continue - if (!hasMe && this.isSameWxid(memberWxid, normalizedMyWxid)) { - hasMe = true - } - if (!hasPeer && this.isSameWxid(memberWxid, normalizedPrivateWxid)) { - hasPeer = true - } - if (hasMe && hasPeer) break - } - if (!hasMe || !hasPeer) return null - - const [groupInfo, groupStatsResult] = await Promise.all([ - this.getContactInfo(groupId), - wcdbService.getGroupStats(groupId, 0, 0) - ]) - const senderCountMap = groupStatsResult.success && groupStatsResult.data - ? this.extractGroupSenderCountMap(groupStatsResult.data, groupId) - : new Map() - const myMessageCount = this.sumSenderCountsByIdentity(senderCountMap, normalizedMyWxid) - const peerMessageCount = this.sumSenderCountsByIdentity(senderCountMap, normalizedPrivateWxid) - const totalMessageCount = myMessageCount + peerMessageCount - - return { - wxid: groupId, - displayName: groupInfo.displayName || groupId, - myMessageCount, - peerMessageCount, - totalMessageCount - } - }) - - const groups = mutualGroups - .filter((item): item is { - wxid: string - displayName: string - myMessageCount: number - peerMessageCount: number - totalMessageCount: number - } => Boolean(item)) - .sort((a, b) => { - if (b.totalMessageCount !== a.totalMessageCount) return b.totalMessageCount - a.totalMessageCount - return a.displayName.localeCompare(b.displayName, 'zh-CN') - }) - - const totalMessagesByMe = groups.reduce((sum, item) => sum + item.myMessageCount, 0) - const totalMessagesByPeer = groups.reduce((sum, item) => sum + item.peerMessageCount, 0) - - return { - totalGroups: groups.length, - totalMessagesByMe, - totalMessagesByPeer, - totalMessagesCombined: totalMessagesByMe + totalMessagesByPeer, - groups - } - } - private resolveAvatarFile(avatarUrl?: string): { data?: Buffer; sourcePath?: string; sourceUrl?: string; ext: string; mime?: string } | null { if (!avatarUrl) return null if (avatarUrl.startsWith('data:')) { @@ -3998,19 +3876,6 @@ class ExportService { const arkmeSession: any = { ...sessionPayload } - let privateMutualGroups: { - totalGroups: number - totalMessagesByMe: number - totalMessagesByPeer: number - totalMessagesCombined: number - groups: Array<{ - wxid: string - displayName: string - myMessageCount: number - peerMessageCount: number - totalMessageCount: number - }> - } | undefined let groupMembers: Array<{ wxid: string displayName: string @@ -4082,8 +3947,6 @@ class ExportService { if (b.messageCount !== a.messageCount) return b.messageCount - a.messageCount return String(a.displayName || a.wxid).localeCompare(String(b.displayName || b.wxid), 'zh-CN') }) - } else if (!sessionId.startsWith('gh_')) { - privateMutualGroups = await this.collectPrivateMutualGroupStats(sessionId, cleanedMyWxid) } const arkmeExport: any = { @@ -4095,9 +3958,6 @@ class ExportService { senders, messages: compactMessages } - if (privateMutualGroups) { - arkmeExport.privateMutualGroups = privateMutualGroups - } if (groupMembers) { arkmeExport.groupMembers = groupMembers } diff --git a/src/pages/ExportPage.tsx b/src/pages/ExportPage.tsx index 7d9ff07..01e4b08 100644 --- a/src/pages/ExportPage.tsx +++ b/src/pages/ExportPage.tsx @@ -863,12 +863,6 @@ function ExportPage() { const [contactsLoadIssue, setContactsLoadIssue] = useState(null) const [showContactsDiagnostics, setShowContactsDiagnostics] = useState(false) const [contactsDiagnosticTick, setContactsDiagnosticTick] = useState(Date.now()) - const [contactsAvatarEnrichProgress, setContactsAvatarEnrichProgress] = useState({ - loaded: 0, - total: 0, - running: false, - tab: null as ConversationTab | null - }) const [showSessionDetailPanel, setShowSessionDetailPanel] = useState(false) const [sessionDetail, setSessionDetail] = useState(null) const [isLoadingSessionDetail, setIsLoadingSessionDetail] = useState(false) @@ -941,8 +935,6 @@ function ExportPage() { const preselectAppliedRef = useRef(false) const exportCacheScopeRef = useRef('default') const exportCacheScopeReadyRef = useRef(false) - const activeTabRef = useRef('private') - const contactsDataRef = useRef([]) const contactsLoadVersionRef = useRef(0) const contactsLoadAttemptRef = useRef(0) const contactsLoadTimeoutTimerRef = useRef(null) @@ -1059,128 +1051,8 @@ function ExportPage() { contactsLoadTimeoutMsRef.current = contactsLoadTimeoutMs }, [contactsLoadTimeoutMs]) - useEffect(() => { - activeTabRef.current = activeTab - }, [activeTab]) - - useEffect(() => { - contactsDataRef.current = contactsList - }, [contactsList]) - - const applyEnrichedContactsToList = useCallback((enrichedMap: Record) => { - if (!enrichedMap || Object.keys(enrichedMap).length === 0) return - setContactsList(prev => { - let changed = false - const next = prev.map(contact => { - const enriched = enrichedMap[contact.username] - if (!enriched) return contact - const displayName = enriched.displayName || contact.displayName - const avatarUrl = enriched.avatarUrl || contact.avatarUrl - if (displayName === contact.displayName && avatarUrl === contact.avatarUrl) { - return contact - } - changed = true - return { - ...contact, - displayName, - avatarUrl - } - }) - return changed ? next : prev - }) - }, []) - - const enrichContactsListInBackground = useCallback(async ( - sourceContacts: ContactInfo[], - loadVersion: number, - scopeKey: string, - targetTab: ConversationTab - ) => { - const sourceByUsername = new Map() - for (const contact of sourceContacts) { - if (!contact.username) continue - sourceByUsername.set(contact.username, contact) - } - - const usernames = Array.from(new Set(sourceContacts - .filter(contact => matchesContactTab(contact, targetTab)) - .map(contact => contact.username) - .filter(Boolean) - .filter((username) => { - const currentContact = sourceByUsername.get(username) - return Boolean(currentContact && !currentContact.avatarUrl) - }))) - - const total = usernames.length - setContactsAvatarEnrichProgress({ - loaded: 0, - total, - running: total > 0, - tab: targetTab - }) - if (total === 0) return - - for (let i = 0; i < total; i += EXPORT_AVATAR_ENRICH_BATCH_SIZE) { - if (contactsLoadVersionRef.current !== loadVersion) return - const batch = usernames.slice(i, i + EXPORT_AVATAR_ENRICH_BATCH_SIZE) - if (batch.length === 0) continue - - try { - const avatarResult = await withTimeout( - window.electronAPI.chat.enrichSessionsContactInfo(batch, { - skipDisplayName: true, - onlyMissingAvatar: true - }), - CONTACT_ENRICH_TIMEOUT_MS - ) - if (contactsLoadVersionRef.current !== loadVersion) return - if (avatarResult?.success && avatarResult.contacts) { - applyEnrichedContactsToList(avatarResult.contacts) - for (const [username, enriched] of Object.entries(avatarResult.contacts)) { - const prev = sourceByUsername.get(username) - if (!prev) continue - sourceByUsername.set(username, { - ...prev, - displayName: enriched.displayName || prev.displayName, - avatarUrl: enriched.avatarUrl || prev.avatarUrl - }) - } - } - - const batchContacts = batch - .map(username => sourceByUsername.get(username)) - .filter((contact): contact is ContactInfo => Boolean(contact)) - const upsertResult = upsertAvatarCacheFromContacts( - contactsAvatarCacheRef.current, - batchContacts, - { markCheckedUsernames: batch } - ) - contactsAvatarCacheRef.current = upsertResult.avatarEntries - if (upsertResult.updatedAt) { - setAvatarCacheUpdatedAt(upsertResult.updatedAt) - } - } catch (error) { - console.error('导出页分批补全头像失败:', error) - } - - const loaded = Math.min(i + batch.length, total) - setContactsAvatarEnrichProgress({ - loaded, - total, - running: loaded < total, - tab: targetTab - }) - await new Promise(resolve => setTimeout(resolve, 0)) - } - - void configService.setContactsAvatarCache(scopeKey, contactsAvatarCacheRef.current).catch((error) => { - console.error('写入导出页头像缓存失败:', error) - }) - }, [applyEnrichedContactsToList]) - const loadContactsList = useCallback(async (options?: { scopeKey?: string }) => { const scopeKey = options?.scopeKey || await ensureExportCacheScope() - const targetTab = activeTabRef.current const loadVersion = contactsLoadVersionRef.current + 1 contactsLoadVersionRef.current = loadVersion contactsLoadAttemptRef.current += 1 @@ -1214,13 +1086,6 @@ function ExportPage() { contactsLoadTimeoutTimerRef.current = timeoutTimerId setIsContactsListLoading(true) - setContactsAvatarEnrichProgress({ - loaded: 0, - total: 0, - running: false, - tab: null - }) - try { const contactsResult = await window.electronAPI.chat.getContacts() if (contactsLoadVersionRef.current !== loadVersion) return @@ -1266,7 +1131,6 @@ function ExportPage() { ).catch((error) => { console.error('写入导出页通讯录缓存失败:', error) }) - void enrichContactsListInBackground(contactsWithAvatarCache, loadVersion, scopeKey, targetTab) return } @@ -1301,7 +1165,7 @@ function ExportPage() { setIsContactsListLoading(false) } } - }, [ensureExportCacheScope, enrichContactsListInBackground, syncContactTypeCounts]) + }, [ensureExportCacheScope, syncContactTypeCounts]) useEffect(() => { if (!isExportRoute) return @@ -1341,29 +1205,9 @@ function ExportPage() { } }, [isExportRoute, ensureExportCacheScope, loadContactsList, syncContactTypeCounts]) - useEffect(() => { - if (!isExportRoute || isContactsListLoading || contactsDataRef.current.length === 0) return - let cancelled = false - const loadVersion = contactsLoadVersionRef.current - void (async () => { - const scopeKey = await ensureExportCacheScope() - if (cancelled || contactsLoadVersionRef.current !== loadVersion) return - await enrichContactsListInBackground(contactsDataRef.current, loadVersion, scopeKey, activeTab) - })() - return () => { - cancelled = true - } - }, [activeTab, ensureExportCacheScope, enrichContactsListInBackground, isContactsListLoading, isExportRoute]) - useEffect(() => { if (isExportRoute) return contactsLoadVersionRef.current += 1 - setContactsAvatarEnrichProgress({ - loaded: 0, - total: 0, - running: false, - tab: null - }) }, [isExportRoute]) useEffect(() => { @@ -3203,8 +3047,6 @@ function ExportPage() { contact.avatarUrl ? count + 1 : count ), 0) }, [contactsList]) - const isCurrentTabAvatarEnrichRunning = contactsAvatarEnrichProgress.running && contactsAvatarEnrichProgress.tab === activeTab - useEffect(() => { if (!contactsListRef.current) return contactsListRef.current.scrollTop = 0 @@ -3938,20 +3780,15 @@ function ExportPage() { {avatarCacheUpdatedAtLabel ? ` · 更新于 ${avatarCacheUpdatedAtLabel}` : ''} )} - {(isContactsListLoading || isCurrentTabAvatarEnrichRunning) && contactsList.length > 0 && ( + {isContactsListLoading && contactsList.length > 0 && ( 后台同步中... )} - {isCurrentTabAvatarEnrichRunning && ( - - 头像补全中 {contactsAvatarEnrichProgress.loaded}/{contactsAvatarEnrichProgress.total} - - )} - {contactsList.length > 0 && (isContactsListLoading || isCurrentTabAvatarEnrichRunning) && ( + {contactsList.length > 0 && isContactsListLoading && (
- {isContactsListLoading ? '联系人列表同步中…' : '正在补充头像…'} + 联系人列表同步中…
)}