mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-26 02:04:37 +08:00
fix(ai): cancel stale conversation restoration
Carry one selection generation across the complete session restoration flow so delayed list or history requests cannot override a newer restoration, user selection, or draft.
This commit is contained in:
1 parent
982965b44a
commit
7d32e1d7ed
2 files changed
+53
-9
No files matched your search
+22
-7
@@ -558,10 +558,13 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
return true
|
||||
}
|
||||
|
||||
async function loadAIChat(chatKey: string, aiChatId: string): Promise<boolean> {
|
||||
async function loadAIChatForSelection(
|
||||
chatKey: string,
|
||||
aiChatId: string,
|
||||
selectionGeneration: number
|
||||
): Promise<boolean> {
|
||||
const state = getSessionState(chatKey)
|
||||
if (!state) return false
|
||||
const selectionGeneration = advanceAIChatSelection(chatKey)
|
||||
if (!state || !isLatestAIChatSelection(chatKey, selectionGeneration)) return false
|
||||
|
||||
try {
|
||||
const conversation = await useAIService().getAIChat(aiChatId)
|
||||
@@ -599,6 +602,11 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAIChat(chatKey: string, aiChatId: string): Promise<boolean> {
|
||||
if (!getSessionState(chatKey)) return false
|
||||
return loadAIChatForSelection(chatKey, aiChatId, advanceAIChatSelection(chatKey))
|
||||
}
|
||||
|
||||
function focusAIChat(chatKey: string, aiChatId: string | null): boolean {
|
||||
const state = getSessionState(chatKey)
|
||||
if (!state) return false
|
||||
@@ -635,24 +643,31 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
}
|
||||
const state = getSessionState(chatKey)
|
||||
if (!state || state.isAIThinking) return
|
||||
const selectionGeneration = advanceAIChatSelection(chatKey)
|
||||
|
||||
if (!assistantStore.isLoaded) {
|
||||
await assistantStore.loadAssistants()
|
||||
if (!isLatestAIChatSelection(chatKey, selectionGeneration)) return
|
||||
}
|
||||
|
||||
if (preferredAIChatId && (await loadAIChat(chatKey, preferredAIChatId))) {
|
||||
return
|
||||
if (preferredAIChatId) {
|
||||
if (await loadAIChatForSelection(chatKey, preferredAIChatId, selectionGeneration)) return
|
||||
if (!isLatestAIChatSelection(chatKey, selectionGeneration)) return
|
||||
}
|
||||
|
||||
try {
|
||||
const latestAIChat = (await useAIService().getAIChats(state.sessionId))[0]
|
||||
if (latestAIChat && latestAIChat.id !== preferredAIChatId && (await loadAIChat(chatKey, latestAIChat.id))) {
|
||||
return
|
||||
if (!isLatestAIChatSelection(chatKey, selectionGeneration)) return
|
||||
if (latestAIChat && latestAIChat.id !== preferredAIChatId) {
|
||||
if (await loadAIChatForSelection(chatKey, latestAIChat.id, selectionGeneration)) return
|
||||
if (!isLatestAIChatSelection(chatKey, selectionGeneration)) return
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isLatestAIChatSelection(chatKey, selectionGeneration)) return
|
||||
console.error('[AI] Failed to load the latest conversation:', error)
|
||||
}
|
||||
|
||||
if (!isLatestAIChatSelection(chatKey, selectionGeneration)) return
|
||||
if (!state.selectedAssistantId) {
|
||||
const defaultId = getDefaultGeneralAssistantId(state.locale)
|
||||
selectAssistantForSession(chatKey, defaultId)
|
||||
|
||||
@@ -17,14 +17,27 @@ test('restores the latest valid AI chat without leaking stale navigation state',
|
||||
const slowConversation = new Promise<ReturnType<typeof makeConversation>>((resolve) => {
|
||||
resolveSlowConversation = resolve
|
||||
})
|
||||
let resolveStaleList!: (conversations: ReturnType<typeof makeConversation>[]) => void
|
||||
const staleList = new Promise<ReturnType<typeof makeConversation>[]>((resolve) => {
|
||||
resolveStaleList = resolve
|
||||
})
|
||||
let staleListRequestCount = 0
|
||||
const aiService = {
|
||||
getAIChat: async (id: string) => {
|
||||
if (id === 'chat-slow') return slowConversation
|
||||
if (id === 'chat-one' || id === 'chat-newest') return makeConversation(id)
|
||||
if (id === 'chat-restored' || id === 'chat-user-choice') return makeConversation(id, 'session-four')
|
||||
return id === 'chat-latest' ? makeConversation(id, 'session-three') : null
|
||||
},
|
||||
getAIChats: async (sessionId: string) =>
|
||||
sessionId === 'session-three' ? [makeConversation('chat-latest', sessionId)] : [],
|
||||
getAIChats: async (sessionId: string) => {
|
||||
if (sessionId === 'session-three') return [makeConversation('chat-latest', sessionId)]
|
||||
if (sessionId === 'session-four') {
|
||||
staleListRequestCount++
|
||||
if (staleListRequestCount === 1) return staleList
|
||||
return [makeConversation('chat-restored', sessionId)]
|
||||
}
|
||||
return []
|
||||
},
|
||||
getMessages: async (aiChatId: string) => [
|
||||
{
|
||||
id: `message-${aiChatId}`,
|
||||
@@ -120,6 +133,22 @@ test('restores the latest valid AI chat without leaking stale navigation state',
|
||||
assert.equal(third.state.currentAIChatId, 'chat-latest')
|
||||
assert.equal(third.state.messages[0]?.content, 'Saved answer for chat-latest')
|
||||
|
||||
const fourth = store.ensureSessionState({
|
||||
sessionId: 'session-four',
|
||||
sessionName: 'Fourth session',
|
||||
chatType: 'private',
|
||||
locale: 'zh-CN',
|
||||
})
|
||||
const staleRestore = store.resetToSelectorOnEnter(fourth.chatKey)
|
||||
await store.resetToSelectorOnEnter(fourth.chatKey)
|
||||
assert.equal(await store.loadAIChat(fourth.chatKey, 'chat-user-choice'), true)
|
||||
|
||||
resolveStaleList([makeConversation('chat-restored', 'session-four')])
|
||||
await staleRestore
|
||||
|
||||
assert.equal(fourth.state.currentAIChatId, 'chat-user-choice')
|
||||
assert.equal(fourth.state.messages[0]?.content, 'Saved answer for chat-user-choice')
|
||||
|
||||
const global = store.ensureGlobalState('zh-CN')
|
||||
store.activeTask = {
|
||||
requestId: 'global-request',
|
||||
|
||||
Reference in new issue
Block a user