mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
perf(export): prioritize visible content stats loading
This commit is contained in:
@@ -471,6 +471,9 @@ const EXPORT_CARD_DIAG_POLL_INTERVAL_MS = 1200
|
|||||||
const EXPORT_REENTER_SESSION_SOFT_REFRESH_MS = 5 * 60 * 1000
|
const EXPORT_REENTER_SESSION_SOFT_REFRESH_MS = 5 * 60 * 1000
|
||||||
const EXPORT_REENTER_CONTACTS_SOFT_REFRESH_MS = 5 * 60 * 1000
|
const EXPORT_REENTER_CONTACTS_SOFT_REFRESH_MS = 5 * 60 * 1000
|
||||||
const EXPORT_REENTER_SNS_SOFT_REFRESH_MS = 3 * 60 * 1000
|
const EXPORT_REENTER_SNS_SOFT_REFRESH_MS = 3 * 60 * 1000
|
||||||
|
const EXPORT_CONTENT_STATS_FIRST_SCREEN_LIMIT = 120
|
||||||
|
const EXPORT_CONTENT_STATS_CHUNK_SIZE = 80
|
||||||
|
const EXPORT_CONTENT_STATS_CHUNK_CONCURRENCY = 2
|
||||||
type SessionDataSource = 'cache' | 'network' | null
|
type SessionDataSource = 'cache' | 'network' | null
|
||||||
type ContactsDataSource = 'cache' | 'network' | null
|
type ContactsDataSource = 'cache' | 'network' | null
|
||||||
|
|
||||||
@@ -539,6 +542,11 @@ interface SessionContentMetric {
|
|||||||
emojiMessages?: number
|
emojiMessages?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SessionContentStatsProgress {
|
||||||
|
completed: number
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
|
||||||
interface SessionExportCacheMeta {
|
interface SessionExportCacheMeta {
|
||||||
updatedAt: number
|
updatedAt: number
|
||||||
stale: boolean
|
stale: boolean
|
||||||
@@ -869,6 +877,7 @@ function ExportPage() {
|
|||||||
const [isLoadingSessionCounts, setIsLoadingSessionCounts] = useState(false)
|
const [isLoadingSessionCounts, setIsLoadingSessionCounts] = useState(false)
|
||||||
const [sessionContentMetrics, setSessionContentMetrics] = useState<Record<string, SessionContentMetric>>({})
|
const [sessionContentMetrics, setSessionContentMetrics] = useState<Record<string, SessionContentMetric>>({})
|
||||||
const [isLoadingSessionContentStats, setIsLoadingSessionContentStats] = useState(false)
|
const [isLoadingSessionContentStats, setIsLoadingSessionContentStats] = useState(false)
|
||||||
|
const [sessionContentStatsProgress, setSessionContentStatsProgress] = useState<SessionContentStatsProgress>({ completed: 0, total: 0 })
|
||||||
const [contactsListScrollTop, setContactsListScrollTop] = useState(0)
|
const [contactsListScrollTop, setContactsListScrollTop] = useState(0)
|
||||||
const [contactsListViewportHeight, setContactsListViewportHeight] = useState(480)
|
const [contactsListViewportHeight, setContactsListViewportHeight] = useState(480)
|
||||||
const [contactsLoadTimeoutMs, setContactsLoadTimeoutMs] = useState(DEFAULT_CONTACTS_LOAD_TIMEOUT_MS)
|
const [contactsLoadTimeoutMs, setContactsLoadTimeoutMs] = useState(DEFAULT_CONTACTS_LOAD_TIMEOUT_MS)
|
||||||
@@ -960,6 +969,8 @@ function ExportPage() {
|
|||||||
const contactsUpdatedAtRef = useRef<number | null>(null)
|
const contactsUpdatedAtRef = useRef<number | null>(null)
|
||||||
const sessionsHydratedAtRef = useRef(0)
|
const sessionsHydratedAtRef = useRef(0)
|
||||||
const snsStatsHydratedAtRef = useRef(0)
|
const snsStatsHydratedAtRef = useRef(0)
|
||||||
|
const filteredContactUsernamesRef = useRef<string[]>([])
|
||||||
|
const visibleContactUsernamesRef = useRef<string[]>([])
|
||||||
const inProgressSessionIdsRef = useRef<string[]>([])
|
const inProgressSessionIdsRef = useRef<string[]>([])
|
||||||
const activeTaskCountRef = useRef(0)
|
const activeTaskCountRef = useRef(0)
|
||||||
const hasBaseConfigReadyRef = useRef(false)
|
const hasBaseConfigReadyRef = useRef(false)
|
||||||
@@ -1526,9 +1537,16 @@ function ExportPage() {
|
|||||||
const exportableSessions = sourceSessions.filter(session => session.hasSession)
|
const exportableSessions = sourceSessions.filter(session => session.hasSession)
|
||||||
if (exportableSessions.length === 0) {
|
if (exportableSessions.length === 0) {
|
||||||
setIsLoadingSessionContentStats(false)
|
setIsLoadingSessionContentStats(false)
|
||||||
|
setSessionContentStatsProgress({ completed: 0, total: 0 })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const exportableSessionIdSet = new Set(exportableSessions.map(session => session.username))
|
||||||
|
const visiblePrioritySessionIds = visibleContactUsernamesRef.current
|
||||||
|
.filter((sessionId) => exportableSessionIdSet.has(sessionId))
|
||||||
|
const firstScreenPrioritySessionIds = filteredContactUsernamesRef.current
|
||||||
|
.filter((sessionId) => exportableSessionIdSet.has(sessionId))
|
||||||
|
.slice(0, EXPORT_CONTENT_STATS_FIRST_SCREEN_LIMIT)
|
||||||
const prioritizedSessionIds = exportableSessions
|
const prioritizedSessionIds = exportableSessions
|
||||||
.filter(session => session.kind === priorityTab)
|
.filter(session => session.kind === priorityTab)
|
||||||
.map(session => session.username)
|
.map(session => session.username)
|
||||||
@@ -1536,32 +1554,84 @@ function ExportPage() {
|
|||||||
const remainingSessionIds = exportableSessions
|
const remainingSessionIds = exportableSessions
|
||||||
.filter(session => !prioritizedSet.has(session.username))
|
.filter(session => !prioritizedSet.has(session.username))
|
||||||
.map(session => session.username)
|
.map(session => session.username)
|
||||||
const orderedSessionIds = [...prioritizedSessionIds, ...remainingSessionIds]
|
const orderedSessionIds = Array.from(new Set([
|
||||||
|
...visiblePrioritySessionIds,
|
||||||
|
...firstScreenPrioritySessionIds,
|
||||||
|
...prioritizedSessionIds,
|
||||||
|
...remainingSessionIds
|
||||||
|
]))
|
||||||
|
|
||||||
if (orderedSessionIds.length === 0) {
|
if (orderedSessionIds.length === 0) {
|
||||||
setIsLoadingSessionContentStats(false)
|
setIsLoadingSessionContentStats(false)
|
||||||
|
setSessionContentStatsProgress({ completed: 0, total: 0 })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoadingSessionContentStats(true)
|
const total = orderedSessionIds.length
|
||||||
try {
|
const processedSessionIds = new Set<string>()
|
||||||
const chunkSize = 80
|
const markChunkProcessed = (chunk: string[]) => {
|
||||||
for (let i = 0; i < orderedSessionIds.length; i += chunkSize) {
|
for (const sessionId of chunk) {
|
||||||
const chunk = orderedSessionIds.slice(i, i + chunkSize)
|
processedSessionIds.add(sessionId)
|
||||||
if (chunk.length === 0) continue
|
}
|
||||||
const result = await window.electronAPI.chat.getExportSessionStats(
|
if (!isStale()) {
|
||||||
|
setSessionContentStatsProgress({ completed: processedSessionIds.size, total })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const runChunk = async (chunk: string[]) => {
|
||||||
|
if (chunk.length === 0) return
|
||||||
|
const result = await withTimeout(
|
||||||
|
window.electronAPI.chat.getExportSessionStats(
|
||||||
chunk,
|
chunk,
|
||||||
{ includeRelations: false, allowStaleCache: true }
|
{ includeRelations: false, allowStaleCache: true }
|
||||||
)
|
),
|
||||||
if (isStale()) return
|
25000
|
||||||
if (result.success && result.data) {
|
)
|
||||||
mergeSessionContentMetrics(result.data as Record<string, SessionExportMetric | undefined>)
|
if (isStale()) return
|
||||||
}
|
if (result?.success && result.data) {
|
||||||
|
mergeSessionContentMetrics(result.data as Record<string, SessionExportMetric | undefined>)
|
||||||
}
|
}
|
||||||
|
markChunkProcessed(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoadingSessionContentStats(true)
|
||||||
|
setSessionContentStatsProgress({ completed: 0, total })
|
||||||
|
try {
|
||||||
|
const immediateSessionIds = Array.from(new Set([
|
||||||
|
...visiblePrioritySessionIds,
|
||||||
|
...firstScreenPrioritySessionIds
|
||||||
|
])).slice(0, EXPORT_CONTENT_STATS_FIRST_SCREEN_LIMIT)
|
||||||
|
|
||||||
|
for (let i = 0; i < immediateSessionIds.length; i += EXPORT_CONTENT_STATS_CHUNK_SIZE) {
|
||||||
|
const chunk = immediateSessionIds.slice(i, i + EXPORT_CONTENT_STATS_CHUNK_SIZE)
|
||||||
|
await runChunk(chunk)
|
||||||
|
if (isStale()) return
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingIds = orderedSessionIds.filter((sessionId) => !processedSessionIds.has(sessionId))
|
||||||
|
const remainingChunks: string[][] = []
|
||||||
|
for (let i = 0; i < remainingIds.length; i += EXPORT_CONTENT_STATS_CHUNK_SIZE) {
|
||||||
|
const chunk = remainingIds.slice(i, i + EXPORT_CONTENT_STATS_CHUNK_SIZE)
|
||||||
|
if (chunk.length === 0) continue
|
||||||
|
remainingChunks.push(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextChunkIndex = 0
|
||||||
|
const workerCount = Math.min(EXPORT_CONTENT_STATS_CHUNK_CONCURRENCY, remainingChunks.length)
|
||||||
|
await Promise.all(Array.from({ length: workerCount }, async () => {
|
||||||
|
while (true) {
|
||||||
|
if (isStale()) return
|
||||||
|
const index = nextChunkIndex
|
||||||
|
nextChunkIndex += 1
|
||||||
|
if (index >= remainingChunks.length) return
|
||||||
|
await runChunk(remainingChunks[index])
|
||||||
|
}
|
||||||
|
}))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('导出页加载会话内容统计失败:', error)
|
console.error('导出页加载会话内容统计失败:', error)
|
||||||
} finally {
|
} finally {
|
||||||
if (!isStale()) {
|
if (!isStale()) {
|
||||||
|
setSessionContentStatsProgress({ completed: processedSessionIds.size, total })
|
||||||
setIsLoadingSessionContentStats(false)
|
setIsLoadingSessionContentStats(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1663,6 +1733,7 @@ function ExportPage() {
|
|||||||
setSessionContentMetrics({})
|
setSessionContentMetrics({})
|
||||||
setIsLoadingSessionCounts(false)
|
setIsLoadingSessionCounts(false)
|
||||||
setIsLoadingSessionContentStats(false)
|
setIsLoadingSessionContentStats(false)
|
||||||
|
setSessionContentStatsProgress({ completed: 0, total: 0 })
|
||||||
|
|
||||||
const isStale = () => sessionLoadTokenRef.current !== loadToken
|
const isStale = () => sessionLoadTokenRef.current !== loadToken
|
||||||
|
|
||||||
@@ -1943,6 +2014,7 @@ function ExportPage() {
|
|||||||
setIsSessionEnriching(false)
|
setIsSessionEnriching(false)
|
||||||
setIsLoadingSessionCounts(false)
|
setIsLoadingSessionCounts(false)
|
||||||
setIsLoadingSessionContentStats(false)
|
setIsLoadingSessionContentStats(false)
|
||||||
|
setSessionContentStatsProgress({ completed: 0, total: 0 })
|
||||||
}, [isExportRoute])
|
}, [isExportRoute])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -3425,6 +3497,14 @@ function ExportPage() {
|
|||||||
return filteredContacts.slice(contactStartIndex, contactEndIndex)
|
return filteredContacts.slice(contactStartIndex, contactEndIndex)
|
||||||
}, [filteredContacts, contactStartIndex, contactEndIndex])
|
}, [filteredContacts, contactStartIndex, contactEndIndex])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
filteredContactUsernamesRef.current = filteredContacts.map(contact => contact.username)
|
||||||
|
}, [filteredContacts])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
visibleContactUsernamesRef.current = visibleContacts.map(contact => contact.username)
|
||||||
|
}, [visibleContacts])
|
||||||
|
|
||||||
const onContactsListScroll = useCallback((event: UIEvent<HTMLDivElement>) => {
|
const onContactsListScroll = useCallback((event: UIEvent<HTMLDivElement>) => {
|
||||||
setContactsListScrollTop(event.currentTarget.scrollTop)
|
setContactsListScrollTop(event.currentTarget.scrollTop)
|
||||||
}, [])
|
}, [])
|
||||||
@@ -4134,7 +4214,7 @@ function ExportPage() {
|
|||||||
{isLoadingSessionContentStats && (
|
{isLoadingSessionContentStats && (
|
||||||
<span className="meta-item syncing">
|
<span className="meta-item syncing">
|
||||||
<Loader2 size={12} className="spin" />
|
<Loader2 size={12} className="spin" />
|
||||||
图片/语音/表情包/视频统计中…
|
图片/语音/表情包/视频统计中…({sessionContentStatsProgress.completed}/{sessionContentStatsProgress.total})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user