diff --git a/src/components/layout/ChatWorkspace/index.tsx b/src/components/layout/ChatWorkspace/index.tsx index dc60dea..bb53bab 100644 --- a/src/components/layout/ChatWorkspace/index.tsx +++ b/src/components/layout/ChatWorkspace/index.tsx @@ -1571,13 +1571,35 @@ export function ChatWorkspace() { } }, onError: (err: Error) => { - // Clean up streaming placeholder if the stream errored + // Clean up streaming placeholder if the stream errored. + // If the streaming message already has partial content (model streamed + // most of the answer before the connection dropped), preserve it and + // finalize it instead of discarding — losing a 90% complete response + // is worse than showing it with an error note appended. streamCancelRef.current = null const streamMsgId = streamingMsgIdRef.current streamingMsgIdRef.current = null clearBridgeDraft(sessionId) if (streamMsgId) { - setMessages(prev => prev.filter(m => m.id !== streamMsgId)) + let preserved = false + setMessages(prev => { + const streamMsg = prev.find(m => m.id === streamMsgId) + if (streamMsg?.content && streamMsg.content.trim().length > 0) { + // Preserve partial content — finalize the streaming message + preserved = true + return prev.map(m => + m.id === streamMsgId + ? { ...m, isStreaming: false, content: m.content + '\n\n---\n*Response interrupted — connection dropped before completion.*' } + : m, + ) + } + // No useful content — remove the empty placeholder + return prev.filter(m => m.id !== streamMsgId) + }) + if (preserved) { + if (err.name === 'AbortError') return + // Still show the error as a separate message so the user knows what happened + } } if (err.name === 'AbortError') return const isTimeout = err.name === 'TimeoutError'