mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 15:36:49 +08:00
fix(agent): recover pre-header stream disconnects (#673)
This commit is contained in:
@@ -1908,12 +1908,13 @@ async function streamAgentMessage(
|
||||
}
|
||||
|
||||
if (isRecoverableStreamDisconnect(error)) {
|
||||
if (!assistantMessage) return
|
||||
shouldSaveClientSnapshot = false
|
||||
invalidateProtectedDeliveries()
|
||||
beginStreamRecovery(sessionId.value, streamStartedAt)
|
||||
assistantMessage.status = 'streaming'
|
||||
refreshMessageList()
|
||||
if (assistantMessage) {
|
||||
assistantMessage.status = 'streaming'
|
||||
refreshMessageList()
|
||||
}
|
||||
if (document.visibilityState === 'visible') scheduleStreamRecovery(1200)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1022,6 +1022,73 @@ describe('AgentAssistantPanel stream recovery', () => {
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('recovers an ordinary request when the connection fails before response classification', async () => {
|
||||
const userText = '检查下载任务'
|
||||
const assistantText = '下载任务检查完成'
|
||||
let requestedSessionId = ''
|
||||
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = String(input)
|
||||
if (url.endsWith('/message/agent/stream') && init?.method === 'POST') {
|
||||
requestedSessionId = JSON.parse(String(init.body)).session_id
|
||||
throw new TypeError('Failed to fetch')
|
||||
}
|
||||
if (requestedSessionId && url.includes(`/sessions/${encodeURIComponent(requestedSessionId)}`)) {
|
||||
return createAgentResponse({
|
||||
session_id: 'web-agent:preheader-recovery',
|
||||
client_session_id: requestedSessionId,
|
||||
updated_at: new Date(Date.now() + 1000).toISOString(),
|
||||
is_processing: false,
|
||||
messages: [
|
||||
{
|
||||
id: 'user-preheader-recovery',
|
||||
role: 'user',
|
||||
content: userText,
|
||||
createdAt: Date.now(),
|
||||
status: 'done',
|
||||
attachments: [],
|
||||
choices: [],
|
||||
tools: [],
|
||||
},
|
||||
{
|
||||
id: 'assistant-preheader-recovery',
|
||||
role: 'assistant',
|
||||
content: assistantText,
|
||||
createdAt: Date.now() + 1000,
|
||||
status: 'done',
|
||||
attachments: [],
|
||||
choices: [],
|
||||
tools: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
return createAgentResponse([])
|
||||
})
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
const wrapper = mountPanel()
|
||||
await wrapper.find('textarea').setValue(userText)
|
||||
await wrapper.find('textarea').trigger('keydown', { key: 'Enter' })
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.find('.agent-assistant-message--user').exists()).toBe(false)
|
||||
expect(localStorage.getItem('moviepilot-agent-assistant-state') || '').not.toContain(userText)
|
||||
expect(JSON.parse(localStorage.getItem('moviepilot-agent-assistant-state') || '{}').streamRecovery).toMatchObject({
|
||||
sessionId: requestedSessionId,
|
||||
attempts: 0,
|
||||
})
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1200)
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain(userText)
|
||||
expect(wrapper.text()).toContain(assistantText)
|
||||
expect(JSON.parse(localStorage.getItem('moviepilot-agent-assistant-state') || '{}').streamRecovery).toBeNull()
|
||||
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('commits ordinary messages in user and assistant order after response classification', async () => {
|
||||
const userText = '检查下载任务'
|
||||
const assistantText = '检查完成'
|
||||
|
||||
Reference in New Issue
Block a user