feat(export): improve count accuracy and include pending updates

This commit is contained in:
tisonhuang
2026-03-02 19:26:29 +08:00
parent b6878aefd6
commit 91f630209c
8 changed files with 526 additions and 31 deletions

View File

@@ -848,6 +848,26 @@ function ChatPage(_props: ChatPageProps) {
setIsLoadingDetail(true)
setIsLoadingDetailExtra(true)
if (normalizedSessionId.includes('@chatroom')) {
void (async () => {
try {
const hintResult = await window.electronAPI.chat.getGroupMyMessageCountHint(normalizedSessionId)
if (requestSeq !== detailRequestSeqRef.current) return
if (!hintResult.success || !Number.isFinite(hintResult.count)) return
const hintedMyCount = Math.max(0, Math.floor(hintResult.count as number))
setSessionDetail((prev) => {
if (!prev || prev.wxid !== normalizedSessionId) return prev
return {
...prev,
groupMyMessages: hintedMyCount
}
})
} catch {
// ignore hint errors
}
})()
}
try {
const result = await window.electronAPI.chat.getSessionDetailFast(normalizedSessionId)
if (requestSeq !== detailRequestSeqRef.current) return
@@ -1074,6 +1094,57 @@ function ChatPage(_props: ChatPageProps) {
})
}, [])
const normalizeWxidLikeIdentity = useCallback((value?: string): string => {
const trimmed = String(value || '').trim()
if (!trimmed) return ''
const lowered = trimmed.toLowerCase()
if (lowered.startsWith('wxid_')) {
const matched = lowered.match(/^(wxid_[^_]+)/i)
return matched ? matched[1].toLowerCase() : lowered
}
const suffixMatch = lowered.match(/^(.+)_([a-z0-9]{4})$/i)
return suffixMatch ? suffixMatch[1].toLowerCase() : lowered
}, [])
const isSelfGroupMember = useCallback((memberUsername?: string): boolean => {
const selfRaw = String(myWxid || '').trim().toLowerCase()
const selfNormalized = normalizeWxidLikeIdentity(myWxid)
if (!selfRaw && !selfNormalized) return false
const memberRaw = String(memberUsername || '').trim().toLowerCase()
const memberNormalized = normalizeWxidLikeIdentity(memberUsername)
return Boolean(
(selfRaw && memberRaw && selfRaw === memberRaw) ||
(selfNormalized && memberNormalized && selfNormalized === memberNormalized)
)
}, [myWxid, normalizeWxidLikeIdentity])
const resolveMyGroupMessageCountFromMembers = useCallback((members: GroupPanelMember[]): number | undefined => {
if (!myWxid) return undefined
for (const member of members) {
if (!isSelfGroupMember(member.username)) continue
if (Number.isFinite(member.messageCount)) {
return Math.max(0, Math.floor(member.messageCount))
}
return 0
}
return undefined
}, [isSelfGroupMember, myWxid])
const syncGroupMyMessagesFromMembers = useCallback((chatroomId: string, members: GroupPanelMember[]) => {
const myMessageCount = resolveMyGroupMessageCountFromMembers(members)
if (!Number.isFinite(myMessageCount)) return
setSessionDetail((prev) => {
if (!prev || prev.wxid !== chatroomId || !prev.wxid.includes('@chatroom')) return prev
return {
...prev,
groupMyMessages: myMessageCount as number
}
})
}, [resolveMyGroupMessageCountFromMembers])
const updateGroupMembersPanelCache = useCallback((
chatroomId: string,
members: GroupPanelMember[],
@@ -1093,6 +1164,47 @@ function ChatPage(_props: ChatPageProps) {
}
}, [])
const syncGroupMembersMyCountFromDetail = useCallback((chatroomId: string, myMessageCount: number) => {
if (!chatroomId || !chatroomId.includes('@chatroom')) return
const normalizedCount = Number.isFinite(myMessageCount) ? Math.max(0, Math.floor(myMessageCount)) : 0
const patchMembers = (members: GroupPanelMember[]): { changed: boolean; members: GroupPanelMember[] } => {
if (!Array.isArray(members) || members.length === 0) {
return { changed: false, members }
}
let changed = false
const patched = members.map((member) => {
if (!isSelfGroupMember(member.username)) return member
if (member.messageCount === normalizedCount) return member
changed = true
return {
...member,
messageCount: normalizedCount
}
})
if (!changed) return { changed: false, members }
return { changed: true, members: normalizeGroupPanelMembers(patched) }
}
const cached = groupMembersPanelCacheRef.current.get(chatroomId)
if (cached && cached.members.length > 0) {
const patchedCache = patchMembers(cached.members)
if (patchedCache.changed) {
updateGroupMembersPanelCache(chatroomId, patchedCache.members, true)
}
}
setGroupPanelMembers((prev) => {
const patched = patchMembers(prev)
if (!patched.changed) return prev
return patched.members
})
}, [
isSelfGroupMember,
normalizeGroupPanelMembers,
updateGroupMembersPanelCache
])
const getGroupMembersPanelDataWithTimeout = useCallback(async (
chatroomId: string,
options: { forceRefresh?: boolean; includeMessageCounts?: boolean },
@@ -1145,6 +1257,7 @@ function ChatPage(_props: ChatPageProps) {
const membersWithCounts = normalizeGroupPanelMembers(countsResult.data as GroupPanelMember[])
setGroupPanelMembers(membersWithCounts)
syncGroupMyMessagesFromMembers(chatroomId, membersWithCounts)
setGroupMembersError(null)
updateGroupMembersPanelCache(chatroomId, membersWithCounts, true)
hasInitializedGroupMembersRef.current = true
@@ -1161,6 +1274,9 @@ function ChatPage(_props: ChatPageProps) {
if (cacheFresh && cached) {
setGroupPanelMembers(cached.members)
if (cached.includeMessageCounts) {
syncGroupMyMessagesFromMembers(chatroomId, cached.members)
}
setGroupMembersError(null)
setGroupMembersLoadingHint('')
setIsLoadingGroupMembers(false)
@@ -1176,6 +1292,9 @@ function ChatPage(_props: ChatPageProps) {
setGroupMembersError(null)
if (hasCachedMembers && cached) {
setGroupPanelMembers(cached.members)
if (cached.includeMessageCounts) {
syncGroupMyMessagesFromMembers(chatroomId, cached.members)
}
setIsRefreshingGroupMembers(true)
setGroupMembersLoadingHint('')
setIsLoadingGroupMembers(false)
@@ -1230,6 +1349,7 @@ function ChatPage(_props: ChatPageProps) {
}, [
getGroupMembersPanelDataWithTimeout,
isGroupChatSession,
syncGroupMyMessagesFromMembers,
normalizeGroupPanelMembers,
updateGroupMembersPanelCache
])
@@ -1267,6 +1387,13 @@ function ChatPage(_props: ChatPageProps) {
void loadGroupMembersPanel(currentSessionId)
}, [showGroupMembersPanel, currentSessionId, loadGroupMembersPanel, isGroupChatSession])
useEffect(() => {
const chatroomId = String(sessionDetail?.wxid || '').trim()
if (!chatroomId || !chatroomId.includes('@chatroom')) return
if (!Number.isFinite(sessionDetail?.groupMyMessages)) return
syncGroupMembersMyCountFromDetail(chatroomId, sessionDetail!.groupMyMessages as number)
}, [sessionDetail?.groupMyMessages, sessionDetail?.wxid, syncGroupMembersMyCountFromDetail])
// 复制字段值到剪贴板
const handleCopyField = useCallback(async (text: string, field: string) => {
try {

View File

@@ -876,7 +876,6 @@ function ExportPage() {
totalFriends: 0
})
const [contentSessionCounts, setContentSessionCounts] = useState<ExportContentSessionCountsSummary>(defaultContentSessionCounts)
const [isContentSessionCountsLoading, setIsContentSessionCountsLoading] = useState(true)
const [hasSeededContentSessionCounts, setHasSeededContentSessionCounts] = useState(false)
const [hasSeededSnsStats, setHasSeededSnsStats] = useState(false)
const [nowTick, setNowTick] = useState(Date.now())
@@ -904,6 +903,7 @@ function ExportPage() {
const inProgressSessionIdsRef = useRef<string[]>([])
const activeTaskCountRef = useRef(0)
const hasBaseConfigReadyRef = useRef(false)
const contentSessionCountsForceRetryRef = useRef(0)
const ensureExportCacheScope = useCallback(async (): Promise<string> => {
if (exportCacheScopeReadyRef.current) {
@@ -1413,9 +1413,6 @@ function ExportPage() {
}, [])
const loadContentSessionCounts = useCallback(async (options?: { silent?: boolean; forceRefresh?: boolean }) => {
if (!options?.silent) {
setIsContentSessionCountsLoading(true)
}
try {
const result = await withTimeout(
window.electronAPI.chat.getExportContentSessionCounts({
@@ -1437,14 +1434,23 @@ function ExportPage() {
refreshing: result.data.refreshing === true
}
setContentSessionCounts(next)
setHasSeededContentSessionCounts(true)
const looksLikeAllZero = next.totalSessions > 0 &&
next.textSessions === 0 &&
next.voiceSessions === 0 &&
next.imageSessions === 0 &&
next.videoSessions === 0 &&
next.emojiSessions === 0
if (looksLikeAllZero && contentSessionCountsForceRetryRef.current < 3) {
contentSessionCountsForceRetryRef.current += 1
void window.electronAPI.chat.refreshExportContentSessionCounts({ forceRefresh: true })
} else {
contentSessionCountsForceRetryRef.current = 0
setHasSeededContentSessionCounts(true)
}
}
} catch (error) {
console.error('加载导出内容会话统计失败:', error)
} finally {
if (!options?.silent) {
setIsContentSessionCountsLoading(false)
}
}
}, [])
@@ -3205,7 +3211,7 @@ function ExportPage() {
const shouldShowFormatSection = !isContentScopeDialog || isContentTextDialog
const shouldShowMediaSection = !isContentScopeDialog
const isTabCountComputing = isSharedTabCountsLoading && !isSharedTabCountsReady
const isSessionCardStatsLoading = isBaseConfigLoading || (isContentSessionCountsLoading && !hasSeededContentSessionCounts)
const isSessionCardStatsLoading = isBaseConfigLoading || !hasSeededContentSessionCounts
const isSessionCardStatsRefreshing = contentSessionCounts.refreshing || contentSessionCounts.pendingMediaSessions > 0
const isSnsCardStatsLoading = !hasSeededSnsStats
const taskRunningCount = tasks.filter(task => task.status === 'running').length