perf(chat): restore session window from cache on switch back

This commit is contained in:
tisonhuang
2026-03-04 18:13:00 +08:00
parent 7a7e54ea5b
commit 54d6cded53

View File

@@ -152,6 +152,9 @@ const CHAT_SESSION_LIST_CACHE_TTL_MS = 24 * 60 * 60 * 1000
const CHAT_SESSION_PREVIEW_CACHE_TTL_MS = 24 * 60 * 60 * 1000 const CHAT_SESSION_PREVIEW_CACHE_TTL_MS = 24 * 60 * 60 * 1000
const CHAT_SESSION_PREVIEW_LIMIT_PER_SESSION = 30 const CHAT_SESSION_PREVIEW_LIMIT_PER_SESSION = 30
const CHAT_SESSION_PREVIEW_MAX_SESSIONS = 18 const CHAT_SESSION_PREVIEW_MAX_SESSIONS = 18
const CHAT_SESSION_WINDOW_CACHE_TTL_MS = 12 * 60 * 60 * 1000
const CHAT_SESSION_WINDOW_CACHE_MAX_SESSIONS = 30
const CHAT_SESSION_WINDOW_CACHE_MAX_MESSAGES = 300
const GROUP_MEMBERS_PANEL_CACHE_TTL_MS = 10 * 60 * 1000 const GROUP_MEMBERS_PANEL_CACHE_TTL_MS = 10 * 60 * 1000
function buildChatSessionListCacheKey(scope: string): string { function buildChatSessionListCacheKey(scope: string): string {
@@ -290,6 +293,16 @@ interface GroupMembersPanelCacheEntry {
includeMessageCounts: boolean includeMessageCounts: boolean
} }
interface SessionWindowCacheEntry {
updatedAt: number
messages: Message[]
currentOffset: number
hasMoreMessages: boolean
hasMoreLater: boolean
jumpStartTime: number
jumpEndTime: number
}
interface LoadMessagesOptions { interface LoadMessagesOptions {
preferLatestPath?: boolean preferLatestPath?: boolean
deferGroupSenderWarmup?: boolean deferGroupSenderWarmup?: boolean
@@ -544,6 +557,7 @@ function ChatPage(_props: ChatPageProps) {
const hasInitializedGroupMembersRef = useRef(false) const hasInitializedGroupMembersRef = useRef(false)
const chatCacheScopeRef = useRef('default') const chatCacheScopeRef = useRef('default')
const previewCacheRef = useRef<Record<string, SessionPreviewCacheEntry>>({}) const previewCacheRef = useRef<Record<string, SessionPreviewCacheEntry>>({})
const sessionWindowCacheRef = useRef<Map<string, SessionWindowCacheEntry>>(new Map())
const previewPersistTimerRef = useRef<number | null>(null) const previewPersistTimerRef = useRef<number | null>(null)
const sessionListPersistTimerRef = useRef<number | null>(null) const sessionListPersistTimerRef = useRef<number | null>(null)
const pendingExportRequestIdRef = useRef<string | null>(null) const pendingExportRequestIdRef = useRef<string | null>(null)
@@ -749,6 +763,76 @@ function ChatPage(_props: ChatPageProps) {
} }
}, [persistSessionPreviewCache, setMessages]) }, [persistSessionPreviewCache, setMessages])
const saveSessionWindowCache = useCallback((sessionId: string, entry: Omit<SessionWindowCacheEntry, 'updatedAt'>) => {
const id = String(sessionId || '').trim()
if (!id || !Array.isArray(entry.messages) || entry.messages.length === 0) return
const trimmedMessages = entry.messages.length > CHAT_SESSION_WINDOW_CACHE_MAX_MESSAGES
? entry.messages.slice(-CHAT_SESSION_WINDOW_CACHE_MAX_MESSAGES)
: entry.messages.slice()
const cache = sessionWindowCacheRef.current
cache.set(id, {
updatedAt: Date.now(),
...entry,
messages: trimmedMessages,
currentOffset: trimmedMessages.length
})
if (cache.size <= CHAT_SESSION_WINDOW_CACHE_MAX_SESSIONS) return
const sortedByTime = [...cache.entries()]
.sort((a, b) => (a[1].updatedAt || 0) - (b[1].updatedAt || 0))
for (const [key] of sortedByTime) {
if (cache.size <= CHAT_SESSION_WINDOW_CACHE_MAX_SESSIONS) break
cache.delete(key)
}
}, [])
const restoreSessionWindowCache = useCallback((sessionId: string): boolean => {
const id = String(sessionId || '').trim()
if (!id) return false
const cache = sessionWindowCacheRef.current
const entry = cache.get(id)
if (!entry) return false
if (Date.now() - entry.updatedAt > CHAT_SESSION_WINDOW_CACHE_TTL_MS) {
cache.delete(id)
return false
}
if (!Array.isArray(entry.messages) || entry.messages.length === 0) {
cache.delete(id)
return false
}
// LRU: 命中后更新时间
cache.set(id, {
...entry,
updatedAt: Date.now(),
messages: entry.messages.slice()
})
setMessages(entry.messages.slice())
setCurrentOffset(entry.messages.length)
setHasMoreMessages(entry.hasMoreMessages !== false)
setHasMoreLater(entry.hasMoreLater === true)
setJumpStartTime(entry.jumpStartTime || 0)
setJumpEndTime(entry.jumpEndTime || 0)
setNoMessageTable(false)
setHasInitialMessages(true)
return true
}, [
setMessages,
setHasMoreMessages,
setHasMoreLater,
setCurrentOffset,
setJumpStartTime,
setJumpEndTime,
setNoMessageTable,
setHasInitialMessages
])
const hydrateSessionListCache = useCallback((scope: string): boolean => { const hydrateSessionListCache = useCallback((scope: string): boolean => {
try { try {
const cacheKey = buildChatSessionListCacheKey(scope) const cacheKey = buildChatSessionListCacheKey(scope)
@@ -1435,6 +1519,7 @@ function ChatPage(_props: ChatPageProps) {
pendingSessionLoadRef.current = null pendingSessionLoadRef.current = null
initialLoadRequestedSessionRef.current = null initialLoadRequestedSessionRef.current = null
sessionSwitchRequestSeqRef.current += 1 sessionSwitchRequestSeqRef.current += 1
sessionWindowCacheRef.current.clear()
setIsSessionSwitching(false) setIsSessionSwitching(false)
setSessionDetail(null) setSessionDetail(null)
setIsRefreshingDetailStats(false) setIsRefreshingDetailStats(false)
@@ -2110,6 +2195,33 @@ function ChatPage(_props: ChatPageProps) {
} }
}, [currentSessionId, isLoadingMore, isLoadingMessages, messages, getMessageKey, appendMessages, setHasMoreLater, setLoadingMore]) }, [currentSessionId, isLoadingMore, isLoadingMessages, messages, getMessageKey, appendMessages, setHasMoreLater, setLoadingMore])
const refreshSessionIncrementally = useCallback(async (sessionId: string, switchRequestSeq?: number) => {
const currentMessages = useChatStore.getState().messages || []
const lastMsg = currentMessages[currentMessages.length - 1]
const minTime = lastMsg?.createTime || 0
if (!sessionId || minTime <= 0) return
try {
const result = await window.electronAPI.chat.getNewMessages(sessionId, minTime, 120) as {
success: boolean
messages?: Message[]
error?: string
}
if (switchRequestSeq && switchRequestSeq !== sessionSwitchRequestSeqRef.current) return
if (currentSessionRef.current !== sessionId) return
if (!result.success || !Array.isArray(result.messages) || result.messages.length === 0) return
const latestMessages = useChatStore.getState().messages || []
const existing = new Set(latestMessages.map(getMessageKey))
const newMessages = result.messages.filter((msg) => !existing.has(getMessageKey(msg)))
if (newMessages.length > 0) {
appendMessages(newMessages, false)
}
} catch (error) {
console.warn('[SessionCache] 增量刷新失败:', error)
}
}, [appendMessages, getMessageKey])
// 选择会话 // 选择会话
const handleSelectSession = (session: ChatSession) => { const handleSelectSession = (session: ChatSession) => {
// 点击折叠群入口,切换到折叠群视图 // 点击折叠群入口,切换到折叠群视图
@@ -2120,21 +2232,31 @@ function ChatPage(_props: ChatPageProps) {
if (session.username === currentSessionId) return if (session.username === currentSessionId) return
const switchRequestSeq = sessionSwitchRequestSeqRef.current + 1 const switchRequestSeq = sessionSwitchRequestSeqRef.current + 1
sessionSwitchRequestSeqRef.current = switchRequestSeq sessionSwitchRequestSeqRef.current = switchRequestSeq
setCurrentSession(session.username, { preserveMessages: false })
setNoMessageTable(false)
const restoredFromWindowCache = restoreSessionWindowCache(session.username)
if (restoredFromWindowCache) {
pendingSessionLoadRef.current = null
initialLoadRequestedSessionRef.current = null
setIsSessionSwitching(false)
void refreshSessionIncrementally(session.username, switchRequestSeq)
} else {
pendingSessionLoadRef.current = session.username pendingSessionLoadRef.current = session.username
initialLoadRequestedSessionRef.current = session.username initialLoadRequestedSessionRef.current = session.username
setIsSessionSwitching(true) setIsSessionSwitching(true)
setCurrentSession(session.username, { preserveMessages: false })
void hydrateSessionPreview(session.username) void hydrateSessionPreview(session.username)
setCurrentOffset(0) setCurrentOffset(0)
setJumpStartTime(0) setJumpStartTime(0)
setJumpEndTime(0) setJumpEndTime(0)
setNoMessageTable(false)
void loadMessages(session.username, 0, 0, 0, false, { void loadMessages(session.username, 0, 0, 0, false, {
preferLatestPath: true, preferLatestPath: true,
deferGroupSenderWarmup: true, deferGroupSenderWarmup: true,
forceInitialLimit: 30, forceInitialLimit: 30,
switchRequestSeq switchRequestSeq
}) })
}
// 切换会话后回到正常聊天窗口:收起详情侧栏,详情需手动再次展开 // 切换会话后回到正常聊天窗口:收起详情侧栏,详情需手动再次展开
setShowDetailPanel(false) setShowDetailPanel(false)
setShowGroupMembersPanel(false) setShowGroupMembersPanel(false)
@@ -2446,7 +2568,25 @@ function ChatPage(_props: ChatPageProps) {
useEffect(() => { useEffect(() => {
if (!currentSessionId || !Array.isArray(messages) || messages.length === 0) return if (!currentSessionId || !Array.isArray(messages) || messages.length === 0) return
persistSessionPreviewCache(currentSessionId, messages) persistSessionPreviewCache(currentSessionId, messages)
}, [currentSessionId, messages, persistSessionPreviewCache]) saveSessionWindowCache(currentSessionId, {
messages,
currentOffset,
hasMoreMessages,
hasMoreLater,
jumpStartTime,
jumpEndTime
})
}, [
currentSessionId,
messages,
currentOffset,
hasMoreMessages,
hasMoreLater,
jumpStartTime,
jumpEndTime,
persistSessionPreviewCache,
saveSessionWindowCache
])
useEffect(() => { useEffect(() => {
if (!Array.isArray(sessions) || sessions.length === 0) return if (!Array.isArray(sessions) || sessions.length === 0) return