|
|
@@ -111,6 +111,7 @@ export const useChatStore = defineStore('chat', () => {
|
|
|
let nextSessionCursor: string | undefined
|
|
|
let modelLoadGeneration = 0
|
|
|
let unreadLoadGeneration = 0
|
|
|
+ let historyLoadGeneration = 0
|
|
|
let reconnectResumePromise: Promise<unknown> | undefined
|
|
|
let persistedRecoveryPromise: Promise<void> | undefined
|
|
|
const runtimePromises = new Map<string, Promise<ChatRouteState>>()
|
|
|
@@ -304,9 +305,13 @@ export const useChatStore = defineStore('chat', () => {
|
|
|
if (event.type === 'gateway.ready') return
|
|
|
const payload = record(event.payload)
|
|
|
if (event.type === 'sessions.changed') {
|
|
|
- if(payload.reason==='cache.synced'){
|
|
|
- const state=activeRouteState.value
|
|
|
- if(state && state.route.profile===payload.profile && state.route.sessionId===payload.session_id && !state.isLoadingHistory)
|
|
|
+ if(payload.reason==='cache.synced' && typeof payload.profile==='string' && typeof payload.session_id==='string'){
|
|
|
+ const state=routes[routeKey(payload.profile,payload.session_id)]
|
|
|
+ if(state){
|
|
|
+ state.historySyncRevision=(state.historySyncRevision??0)+1
|
|
|
+ state.historySynced=false
|
|
|
+ }
|
|
|
+ if(state && state===activeRouteState.value && !state.isLoadingHistory)
|
|
|
void loadHistory(state).catch(()=>{})
|
|
|
}
|
|
|
scheduleSessionListRefresh()
|
|
|
@@ -486,40 +491,54 @@ export const useChatStore = defineStore('chat', () => {
|
|
|
}
|
|
|
|
|
|
async function loadHistory(state: ChatRouteState, forceRefresh = false): Promise<void> {
|
|
|
- const generation = ++state.generation
|
|
|
+ const key = routeKey(state.route.profile, state.route.sessionId)
|
|
|
+ const generation = ++historyLoadGeneration
|
|
|
+ state.generation = generation
|
|
|
+ const syncRevision = state.historySyncRevision ?? 0
|
|
|
+ // SSE replaces a route state object. Resolve the current object after each
|
|
|
+ // await so history merges into its live tail instead of a detached copy.
|
|
|
+ const current = () => routes[key]?.generation === generation ? routes[key] : undefined
|
|
|
state.isLoadingHistory = true
|
|
|
state.error = undefined
|
|
|
const scope = cacheScope(state.route.profile)
|
|
|
const cached = await historyCache.get(scope, state.route.sessionId)
|
|
|
- if (generation !== state.generation) return
|
|
|
+ let latest = current()
|
|
|
+ if (!latest) return
|
|
|
if (cached?.messages.length) {
|
|
|
- state.messages = mergeChatMessages(state.messages, cached.messages, 'snapshot')
|
|
|
- state.messageTotal = cached.total
|
|
|
- state.loadedMessageCount = cached.messages.length
|
|
|
+ latest.messages = mergeChatMessages(latest.messages, cached.messages, 'snapshot')
|
|
|
+ latest.messageTotal = cached.total
|
|
|
+ latest.loadedMessageCount = cached.messages.length
|
|
|
}
|
|
|
try {
|
|
|
const page = await getMessages(state.route.sessionId, 0, 150, state.route.profile, false, sessionView)
|
|
|
- if (generation !== state.generation) return
|
|
|
- if (forceRefresh && routes[routeKey(state.route.profile, state.route.sessionId)]?.isStreaming) {
|
|
|
+ latest = current()
|
|
|
+ if (!latest || (latest.historySyncRevision ?? 0) !== syncRevision) return
|
|
|
+ if (forceRefresh && latest.isStreaming) {
|
|
|
throw new Error('正在回复,请结束后再刷新历史')
|
|
|
}
|
|
|
- const retained = forceRefresh ? state.messages.filter(message => message.role === 'user' && message.stage !== 'settled') : state.messages
|
|
|
- state.messages = reconcileChatHistory(retained, page.messages, state.isStreaming)
|
|
|
- state.messageTotal = page.total
|
|
|
- state.loadedMessageCount = page.returned
|
|
|
- state.hasMoreBefore = page.hasMore
|
|
|
- state.historySynced = true
|
|
|
+ const retained = forceRefresh ? latest.messages.filter(message => message.role === 'user' && message.stage !== 'settled') : latest.messages
|
|
|
+ latest.messages = reconcileChatHistory(retained, page.messages, latest.isStreaming)
|
|
|
+ latest.messageTotal = page.total
|
|
|
+ latest.loadedMessageCount = page.returned
|
|
|
+ latest.hasMoreBefore = page.hasMore
|
|
|
+ latest.historySynced = true
|
|
|
if (page.session) {
|
|
|
const index = sessions.value.findIndex(session => session.id === page.session!.id && session.profile === page.session!.profile)
|
|
|
if (index >= 0) sessions.value.splice(index, 1, page.session)
|
|
|
}
|
|
|
- await historyCache.set(scope, state.route.sessionId, { messages: state.messages, total: page.total, savedAt: Date.now() })
|
|
|
+ await historyCache.set(scope, state.route.sessionId, { messages: latest.messages, total: page.total, savedAt: Date.now() })
|
|
|
} catch (cause) {
|
|
|
- if (generation === state.generation) state.error = errorMessage(cause)
|
|
|
+ latest = current()
|
|
|
+ if (latest) latest.error = errorMessage(cause)
|
|
|
if(cause instanceof ApiError && cause.code==='CHAT_HISTORY_SYNC_PENDING')return
|
|
|
throw cause
|
|
|
} finally {
|
|
|
- if (generation === state.generation) state.isLoadingHistory = false
|
|
|
+ latest = current()
|
|
|
+ if (latest) {
|
|
|
+ latest.isLoadingHistory = false
|
|
|
+ if ((latest.historySyncRevision ?? 0) !== syncRevision && latest === activeRouteState.value)
|
|
|
+ void loadHistory(latest).catch(() => {})
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -538,7 +557,7 @@ export const useChatStore = defineStore('chat', () => {
|
|
|
syncSelectedModel(refreshed?.model, refreshed?.provider)
|
|
|
if (refreshed?.owned === true && !sessionId.startsWith('draft-')
|
|
|
&& activeSessionId.value === sessionId && activeProfileName.value === selectedProfile) {
|
|
|
- await ensureRuntime(state).catch(cause => {
|
|
|
+ await ensureRuntime(routes[routeKey(selectedProfile, sessionId)] ?? state).catch(cause => {
|
|
|
const current = routes[routeKey(selectedProfile, sessionId)]
|
|
|
if (current) current.error = errorMessage(cause)
|
|
|
})
|
|
|
@@ -556,20 +575,31 @@ export const useChatStore = defineStore('chat', () => {
|
|
|
async function loadOlder(): Promise<void> {
|
|
|
const state = activeRouteState.value
|
|
|
if (!state || state.isLoadingHistory || !state.hasMoreBefore) return
|
|
|
+ const key = routeKey(state.route.profile, state.route.sessionId)
|
|
|
const generation = state.generation
|
|
|
+ const syncRevision = state.historySyncRevision ?? 0
|
|
|
+ const current = () => routes[key]?.generation === generation ? routes[key] : undefined
|
|
|
state.isLoadingHistory = true
|
|
|
try {
|
|
|
const page = await getMessages(state.route.sessionId, state.loadedMessageCount, 150, state.route.profile, false, sessionView)
|
|
|
- if (generation !== state.generation) return
|
|
|
- state.messages = mergeChatMessages(state.messages, page.messages, 'prepend')
|
|
|
- state.loadedMessageCount += page.returned
|
|
|
- state.messageTotal = Math.max(state.messageTotal, page.total)
|
|
|
- state.hasMoreBefore = page.hasMore
|
|
|
+ const latest = current()
|
|
|
+ if (!latest) return
|
|
|
+ latest.messages = mergeChatMessages(latest.messages, page.messages, 'prepend')
|
|
|
+ latest.loadedMessageCount += page.returned
|
|
|
+ latest.messageTotal = Math.max(latest.messageTotal, page.total)
|
|
|
+ latest.hasMoreBefore = page.hasMore
|
|
|
await historyCache.set(cacheScope(state.route.profile), state.route.sessionId, {
|
|
|
- messages: state.messages, total: state.messageTotal, savedAt: Date.now(),
|
|
|
+ messages: latest.messages, total: latest.messageTotal, savedAt: Date.now(),
|
|
|
})
|
|
|
- } catch (cause) { state.error = errorMessage(cause) }
|
|
|
- finally { if (generation === state.generation) state.isLoadingHistory = false }
|
|
|
+ } catch (cause) { const latest = current(); if (latest) latest.error = errorMessage(cause) }
|
|
|
+ finally {
|
|
|
+ const latest = current()
|
|
|
+ if (latest) {
|
|
|
+ latest.isLoadingHistory = false
|
|
|
+ if ((latest.historySyncRevision ?? 0) !== syncRevision && latest === activeRouteState.value)
|
|
|
+ void loadHistory(latest).catch(() => {})
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
function createSession(profile = auth.activeProfile?.name ?? 'default'): string {
|