mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
perf(chat): restore session window from cache on switch back
This commit is contained in:
@@ -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
|
||||||
pendingSessionLoadRef.current = session.username
|
|
||||||
initialLoadRequestedSessionRef.current = session.username
|
|
||||||
setIsSessionSwitching(true)
|
|
||||||
setCurrentSession(session.username, { preserveMessages: false })
|
setCurrentSession(session.username, { preserveMessages: false })
|
||||||
void hydrateSessionPreview(session.username)
|
|
||||||
setCurrentOffset(0)
|
|
||||||
setJumpStartTime(0)
|
|
||||||
setJumpEndTime(0)
|
|
||||||
setNoMessageTable(false)
|
setNoMessageTable(false)
|
||||||
void loadMessages(session.username, 0, 0, 0, false, {
|
|
||||||
preferLatestPath: true,
|
const restoredFromWindowCache = restoreSessionWindowCache(session.username)
|
||||||
deferGroupSenderWarmup: true,
|
if (restoredFromWindowCache) {
|
||||||
forceInitialLimit: 30,
|
pendingSessionLoadRef.current = null
|
||||||
switchRequestSeq
|
initialLoadRequestedSessionRef.current = null
|
||||||
})
|
setIsSessionSwitching(false)
|
||||||
|
void refreshSessionIncrementally(session.username, switchRequestSeq)
|
||||||
|
} else {
|
||||||
|
pendingSessionLoadRef.current = session.username
|
||||||
|
initialLoadRequestedSessionRef.current = session.username
|
||||||
|
setIsSessionSwitching(true)
|
||||||
|
void hydrateSessionPreview(session.username)
|
||||||
|
setCurrentOffset(0)
|
||||||
|
setJumpStartTime(0)
|
||||||
|
setJumpEndTime(0)
|
||||||
|
void loadMessages(session.username, 0, 0, 0, false, {
|
||||||
|
preferLatestPath: true,
|
||||||
|
deferGroupSenderWarmup: true,
|
||||||
|
forceInitialLimit: 30,
|
||||||
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user