webui: preserve partial response on streaming error (#23090)

This commit is contained in:
Pascal
2026-05-15 11:18:11 +02:00
committed by GitHub
parent 91e84fed64
commit d528444580
3 changed files with 17 additions and 21 deletions
@@ -4,9 +4,6 @@ export const ATTACHMENT_SAVED_REGEX = /\[Attachment saved: ([^\]]+)\]/;
export const NEWLINE_SEPARATOR = '\n'; export const NEWLINE_SEPARATOR = '\n';
export const LLM_ERROR_BLOCK_START = '\n\n```\nUpstream LLM error:\n';
export const LLM_ERROR_BLOCK_END = '\n```\n';
export const DEFAULT_AGENTIC_CONFIG: AgenticConfig = { export const DEFAULT_AGENTIC_CONFIG: AgenticConfig = {
enabled: true, enabled: true,
maxTurns: 100, maxTurns: 100,
@@ -30,12 +30,7 @@ import { ToolSource, ToolPermissionDecision } from '$lib/enums';
import { SvelteMap } from 'svelte/reactivity'; import { SvelteMap } from 'svelte/reactivity';
import { ToolsService } from '$lib/services/tools.service'; import { ToolsService } from '$lib/services/tools.service';
import { isAbortError } from '$lib/utils'; import { isAbortError } from '$lib/utils';
import { import { DEFAULT_AGENTIC_CONFIG, NEWLINE_SEPARATOR } from '$lib/constants';
DEFAULT_AGENTIC_CONFIG,
NEWLINE_SEPARATOR,
LLM_ERROR_BLOCK_START,
LLM_ERROR_BLOCK_END
} from '$lib/constants';
import { import {
IMAGE_MIME_TO_EXTENSION, IMAGE_MIME_TO_EXTENSION,
DATA_URI_BASE64_REGEX, DATA_URI_BASE64_REGEX,
@@ -640,10 +635,9 @@ class AgenticStore {
return; return;
} }
const normalizedError = error instanceof Error ? error : new Error('LLM stream error'); const normalizedError = error instanceof Error ? error : new Error('LLM stream error');
// Save error as content in the current turn // preserve partial output as is, the outer error dialog informs the user separately
onChunk?.(`${LLM_ERROR_BLOCK_START}${normalizedError.message}${LLM_ERROR_BLOCK_END}`);
await onAssistantTurnComplete?.( await onAssistantTurnComplete?.(
turnContent + `${LLM_ERROR_BLOCK_START}${normalizedError.message}${LLM_ERROR_BLOCK_END}`, turnContent,
turnReasoningContent || undefined, turnReasoningContent || undefined,
this.buildFinalTimings(capturedTimings, agenticTimings), this.buildFinalTimings(capturedTimings, agenticTimings),
undefined undefined
@@ -814,7 +814,7 @@ class ChatStore {
); );
} }
}, },
onError: (error: Error) => { onError: async (error: Error) => {
this.setStreamingActive(false); this.setStreamingActive(false);
if (isAbortError(error)) { if (isAbortError(error)) {
cleanupStreamingState(); cleanupStreamingState();
@@ -826,13 +826,10 @@ class ChatStore {
return; return;
} }
console.error('Streaming error:', error); console.error('Streaming error:', error);
// keep whatever was streamed so far, the message stays in memory and in DB
await this.savePartialResponseIfNeeded(convId);
cleanupStreamingState(); cleanupStreamingState();
this.clearPendingMessage(convId); this.clearPendingMessage(convId);
const idx = conversationsStore.findMessageIndex(assistantMessage.id);
if (idx !== -1) {
const failedMessage = conversationsStore.removeMessageAtIndex(idx);
if (failedMessage) DatabaseService.deleteMessage(failedMessage.id).catch(console.error);
}
const contextInfo = ( const contextInfo = (
error as Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } } error as Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } }
).contextInfo; ).contextInfo;
@@ -1389,9 +1386,17 @@ class ChatStore {
} }
console.error('Continue generation error:', error); console.error('Continue generation error:', error);
conversationsStore.updateMessageAtIndex(idx, { content: originalContent }); // keep whatever was appended so far, the message stays in memory and in DB
await DatabaseService.updateMessage(msg.id, {
await DatabaseService.updateMessage(msg.id, { content: originalContent }); content: originalContent + appendedContent,
reasoningContent: originalReasoning + appendedReasoning || undefined,
timestamp: Date.now()
});
conversationsStore.updateMessageAtIndex(idx, {
content: originalContent + appendedContent,
reasoningContent: originalReasoning + appendedReasoning || undefined,
timestamp: Date.now()
});
this.setChatLoading(msg.convId, false); this.setChatLoading(msg.convId, false);
this.clearChatStreaming(msg.convId); this.clearChatStreaming(msg.convId);