Add context info to server error (#17663)

* fix: Add context info to server error

* chore: update webui build output
This commit is contained in:
Aleksander Grygier 2025-12-02 09:20:57 +01:00 committed by GitHub
parent ed32089927
commit cee92af553
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 148 additions and 44 deletions

Binary file not shown.

View File

@ -575,6 +575,7 @@
<DialogChatError <DialogChatError
message={activeErrorDialog?.message ?? ''} message={activeErrorDialog?.message ?? ''}
contextInfo={activeErrorDialog?.contextInfo}
onOpenChange={handleErrorDialogOpenChange} onOpenChange={handleErrorDialogOpenChange}
open={Boolean(activeErrorDialog)} open={Boolean(activeErrorDialog)}
type={activeErrorDialog?.type ?? 'server'} type={activeErrorDialog?.type ?? 'server'}

View File

@ -6,10 +6,11 @@
open: boolean; open: boolean;
type: 'timeout' | 'server'; type: 'timeout' | 'server';
message: string; message: string;
contextInfo?: { n_prompt_tokens: number; n_ctx: number };
onOpenChange?: (open: boolean) => void; onOpenChange?: (open: boolean) => void;
} }
let { open = $bindable(), type, message, onOpenChange }: Props = $props(); let { open = $bindable(), type, message, contextInfo, onOpenChange }: Props = $props();
const isTimeout = $derived(type === 'timeout'); const isTimeout = $derived(type === 'timeout');
const title = $derived(isTimeout ? 'TCP Timeout' : 'Server Error'); const title = $derived(isTimeout ? 'TCP Timeout' : 'Server Error');
@ -51,6 +52,15 @@
<div class={`rounded-lg border px-4 py-3 text-sm ${badgeClass}`}> <div class={`rounded-lg border px-4 py-3 text-sm ${badgeClass}`}>
<p class="font-medium">{message}</p> <p class="font-medium">{message}</p>
{#if contextInfo}
<div class="mt-2 space-y-1 text-xs opacity-80">
<p>
<span class="font-medium">Prompt tokens:</span>
{contextInfo.n_prompt_tokens.toLocaleString()}
</p>
<p><span class="font-medium">Context size:</span> {contextInfo.n_ctx.toLocaleString()}</p>
</div>
{/if}
</div> </div>
<AlertDialog.Footer> <AlertDialog.Footer>

View File

@ -764,18 +764,33 @@ export class ChatService {
* @param response - HTTP response object * @param response - HTTP response object
* @returns Promise<Error> - Parsed error with context info if available * @returns Promise<Error> - Parsed error with context info if available
*/ */
private static async parseErrorResponse(response: Response): Promise<Error> { private static async parseErrorResponse(
response: Response
): Promise<Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } }> {
try { try {
const errorText = await response.text(); const errorText = await response.text();
const errorData: ApiErrorResponse = JSON.parse(errorText); const errorData: ApiErrorResponse = JSON.parse(errorText);
const message = errorData.error?.message || 'Unknown server error'; const message = errorData.error?.message || 'Unknown server error';
const error = new Error(message); const error = new Error(message) as Error & {
contextInfo?: { n_prompt_tokens: number; n_ctx: number };
};
error.name = response.status === 400 ? 'ServerError' : 'HttpError'; error.name = response.status === 400 ? 'ServerError' : 'HttpError';
if (errorData.error && 'n_prompt_tokens' in errorData.error && 'n_ctx' in errorData.error) {
error.contextInfo = {
n_prompt_tokens: errorData.error.n_prompt_tokens,
n_ctx: errorData.error.n_ctx
};
}
return error; return error;
} catch { } catch {
const fallback = new Error(`Server error (${response.status}): ${response.statusText}`); const fallback = new Error(
`Server error (${response.status}): ${response.statusText}`
) as Error & {
contextInfo?: { n_prompt_tokens: number; n_ctx: number };
};
fallback.name = 'HttpError'; fallback.name = 'HttpError';
return fallback; return fallback;
} }

View File

@ -58,7 +58,11 @@ class ChatStore {
activeProcessingState = $state<ApiProcessingState | null>(null); activeProcessingState = $state<ApiProcessingState | null>(null);
currentResponse = $state(''); currentResponse = $state('');
errorDialogState = $state<{ type: 'timeout' | 'server'; message: string } | null>(null); errorDialogState = $state<{
type: 'timeout' | 'server';
message: string;
contextInfo?: { n_prompt_tokens: number; n_ctx: number };
} | null>(null);
isLoading = $state(false); isLoading = $state(false);
chatLoadingStates = new SvelteMap<string, boolean>(); chatLoadingStates = new SvelteMap<string, boolean>();
chatStreamingStates = new SvelteMap<string, { response: string; messageId: string }>(); chatStreamingStates = new SvelteMap<string, { response: string; messageId: string }>();
@ -335,8 +339,12 @@ class ChatStore {
return error instanceof Error && (error.name === 'AbortError' || error instanceof DOMException); return error instanceof Error && (error.name === 'AbortError' || error instanceof DOMException);
} }
private showErrorDialog(type: 'timeout' | 'server', message: string): void { private showErrorDialog(
this.errorDialogState = { type, message }; type: 'timeout' | 'server',
message: string,
contextInfo?: { n_prompt_tokens: number; n_ctx: number }
): void {
this.errorDialogState = { type, message, contextInfo };
} }
dismissErrorDialog(): void { dismissErrorDialog(): void {
@ -347,6 +355,23 @@ class ChatStore {
// Message Operations // Message Operations
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
/**
* Finds a message by ID and optionally validates its role.
* Returns message and index, or null if not found or role doesn't match.
*/
private getMessageByIdWithRole(
messageId: string,
expectedRole?: ChatRole
): { message: DatabaseMessage; index: number } | null {
const index = conversationsStore.findMessageIndex(messageId);
if (index === -1) return null;
const message = conversationsStore.activeMessages[index];
if (expectedRole && message.role !== expectedRole) return null;
return { message, index };
}
async addMessage( async addMessage(
role: ChatRole, role: ChatRole,
content: string, content: string,
@ -508,7 +533,6 @@ class ChatStore {
) => { ) => {
this.stopStreaming(); this.stopStreaming();
// Build update data - only include model if not already persisted
const updateData: Record<string, unknown> = { const updateData: Record<string, unknown> = {
content: finalContent || streamedContent, content: finalContent || streamedContent,
thinking: reasoningContent || streamedReasoningContent, thinking: reasoningContent || streamedReasoningContent,
@ -520,7 +544,6 @@ class ChatStore {
} }
await DatabaseService.updateMessage(assistantMessage.id, updateData); await DatabaseService.updateMessage(assistantMessage.id, updateData);
// Update UI state - always include model and timings if available
const idx = conversationsStore.findMessageIndex(assistantMessage.id); const idx = conversationsStore.findMessageIndex(assistantMessage.id);
const uiUpdate: Partial<DatabaseMessage> = { const uiUpdate: Partial<DatabaseMessage> = {
content: updateData.content as string, content: updateData.content as string,
@ -543,22 +566,38 @@ class ChatStore {
}, },
onError: (error: Error) => { onError: (error: Error) => {
this.stopStreaming(); this.stopStreaming();
if (this.isAbortError(error)) { if (this.isAbortError(error)) {
this.setChatLoading(assistantMessage.convId, false); this.setChatLoading(assistantMessage.convId, false);
this.clearChatStreaming(assistantMessage.convId); this.clearChatStreaming(assistantMessage.convId);
this.clearProcessingState(assistantMessage.convId); this.clearProcessingState(assistantMessage.convId);
return; return;
} }
console.error('Streaming error:', error); console.error('Streaming error:', error);
this.setChatLoading(assistantMessage.convId, false); this.setChatLoading(assistantMessage.convId, false);
this.clearChatStreaming(assistantMessage.convId); this.clearChatStreaming(assistantMessage.convId);
this.clearProcessingState(assistantMessage.convId); this.clearProcessingState(assistantMessage.convId);
const idx = conversationsStore.findMessageIndex(assistantMessage.id); const idx = conversationsStore.findMessageIndex(assistantMessage.id);
if (idx !== -1) { if (idx !== -1) {
const failedMessage = conversationsStore.removeMessageAtIndex(idx); const failedMessage = conversationsStore.removeMessageAtIndex(idx);
if (failedMessage) DatabaseService.deleteMessage(failedMessage.id).catch(console.error); if (failedMessage) DatabaseService.deleteMessage(failedMessage.id).catch(console.error);
} }
this.showErrorDialog(error.name === 'TimeoutError' ? 'timeout' : 'server', error.message);
const contextInfo = (
error as Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } }
).contextInfo;
this.showErrorDialog(
error.name === 'TimeoutError' ? 'timeout' : 'server',
error.message,
contextInfo
);
if (onError) onError(error); if (onError) onError(error);
} }
}, },
@ -591,7 +630,9 @@ class ChatStore {
await conversationsStore.updateConversationName(currentConv.id, content.trim()); await conversationsStore.updateConversationName(currentConv.id, content.trim());
const assistantMessage = await this.createAssistantMessage(userMessage.id); const assistantMessage = await this.createAssistantMessage(userMessage.id);
if (!assistantMessage) throw new Error('Failed to create assistant message'); if (!assistantMessage) throw new Error('Failed to create assistant message');
conversationsStore.addMessageToActive(assistantMessage); conversationsStore.addMessageToActive(assistantMessage);
await this.streamChatCompletion( await this.streamChatCompletion(
conversationsStore.activeMessages.slice(0, -1), conversationsStore.activeMessages.slice(0, -1),
@ -607,15 +648,26 @@ class ChatStore {
if (!this.errorDialogState) { if (!this.errorDialogState) {
const dialogType = const dialogType =
error instanceof Error && error.name === 'TimeoutError' ? 'timeout' : 'server'; error instanceof Error && error.name === 'TimeoutError' ? 'timeout' : 'server';
this.showErrorDialog(dialogType, error instanceof Error ? error.message : 'Unknown error'); const contextInfo = (
error as Error & { contextInfo?: { n_prompt_tokens: number; n_ctx: number } }
).contextInfo;
this.showErrorDialog(
dialogType,
error instanceof Error ? error.message : 'Unknown error',
contextInfo
);
} }
} }
} }
async stopGeneration(): Promise<void> { async stopGeneration(): Promise<void> {
const activeConv = conversationsStore.activeConversation; const activeConv = conversationsStore.activeConversation;
if (!activeConv) return; if (!activeConv) return;
await this.savePartialResponseIfNeeded(activeConv.id); await this.savePartialResponseIfNeeded(activeConv.id);
this.stopStreaming(); this.stopStreaming();
this.abortRequest(activeConv.id); this.abortRequest(activeConv.id);
this.setChatLoading(activeConv.id, false); this.setChatLoading(activeConv.id, false);
@ -655,17 +707,22 @@ class ChatStore {
private async savePartialResponseIfNeeded(convId?: string): Promise<void> { private async savePartialResponseIfNeeded(convId?: string): Promise<void> {
const conversationId = convId || conversationsStore.activeConversation?.id; const conversationId = convId || conversationsStore.activeConversation?.id;
if (!conversationId) return; if (!conversationId) return;
const streamingState = this.chatStreamingStates.get(conversationId); const streamingState = this.chatStreamingStates.get(conversationId);
if (!streamingState || !streamingState.response.trim()) return; if (!streamingState || !streamingState.response.trim()) return;
const messages = const messages =
conversationId === conversationsStore.activeConversation?.id conversationId === conversationsStore.activeConversation?.id
? conversationsStore.activeMessages ? conversationsStore.activeMessages
: await conversationsStore.getConversationMessages(conversationId); : await conversationsStore.getConversationMessages(conversationId);
if (!messages.length) return; if (!messages.length) return;
const lastMessage = messages[messages.length - 1]; const lastMessage = messages[messages.length - 1];
if (lastMessage?.role === 'assistant') { if (lastMessage?.role === 'assistant') {
try { try {
const updateData: { content: string; thinking?: string; timings?: ChatMessageTimings } = { const updateData: { content: string; thinking?: string; timings?: ChatMessageTimings } = {
@ -684,9 +741,13 @@ class ChatStore {
: undefined : undefined
}; };
} }
await DatabaseService.updateMessage(lastMessage.id, updateData); await DatabaseService.updateMessage(lastMessage.id, updateData);
lastMessage.content = this.currentResponse; lastMessage.content = this.currentResponse;
if (updateData.thinking) lastMessage.thinking = updateData.thinking; if (updateData.thinking) lastMessage.thinking = updateData.thinking;
if (updateData.timings) lastMessage.timings = updateData.timings; if (updateData.timings) lastMessage.timings = updateData.timings;
} catch (error) { } catch (error) {
lastMessage.content = this.currentResponse; lastMessage.content = this.currentResponse;
@ -700,14 +761,12 @@ class ChatStore {
if (!activeConv) return; if (!activeConv) return;
if (this.isLoading) this.stopGeneration(); if (this.isLoading) this.stopGeneration();
try { const result = this.getMessageByIdWithRole(messageId, 'user');
const messageIndex = conversationsStore.findMessageIndex(messageId); if (!result) return;
if (messageIndex === -1) return; const { message: messageToUpdate, index: messageIndex } = result;
const messageToUpdate = conversationsStore.activeMessages[messageIndex];
const originalContent = messageToUpdate.content; const originalContent = messageToUpdate.content;
if (messageToUpdate.role !== 'user') return;
try {
const allMessages = await conversationsStore.getConversationMessages(activeConv.id); const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null); const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
const isFirstUserMessage = rootMessage && messageToUpdate.parent === rootMessage.id; const isFirstUserMessage = rootMessage && messageToUpdate.parent === rootMessage.id;
@ -724,7 +783,9 @@ class ChatStore {
} }
const messagesToRemove = conversationsStore.activeMessages.slice(messageIndex + 1); const messagesToRemove = conversationsStore.activeMessages.slice(messageIndex + 1);
for (const message of messagesToRemove) await DatabaseService.deleteMessage(message.id); for (const message of messagesToRemove) await DatabaseService.deleteMessage(message.id);
conversationsStore.sliceActiveMessages(messageIndex + 1); conversationsStore.sliceActiveMessages(messageIndex + 1);
conversationsStore.updateConversationTimestamp(); conversationsStore.updateConversationTimestamp();
@ -732,8 +793,11 @@ class ChatStore {
this.clearChatStreaming(activeConv.id); this.clearChatStreaming(activeConv.id);
const assistantMessage = await this.createAssistantMessage(); const assistantMessage = await this.createAssistantMessage();
if (!assistantMessage) throw new Error('Failed to create assistant message'); if (!assistantMessage) throw new Error('Failed to create assistant message');
conversationsStore.addMessageToActive(assistantMessage); conversationsStore.addMessageToActive(assistantMessage);
await conversationsStore.updateCurrentNode(assistantMessage.id); await conversationsStore.updateCurrentNode(assistantMessage.id);
await this.streamChatCompletion( await this.streamChatCompletion(
conversationsStore.activeMessages.slice(0, -1), conversationsStore.activeMessages.slice(0, -1),
@ -758,12 +822,11 @@ class ChatStore {
const activeConv = conversationsStore.activeConversation; const activeConv = conversationsStore.activeConversation;
if (!activeConv || this.isLoading) return; if (!activeConv || this.isLoading) return;
try { const result = this.getMessageByIdWithRole(messageId, 'assistant');
const messageIndex = conversationsStore.findMessageIndex(messageId); if (!result) return;
if (messageIndex === -1) return; const { index: messageIndex } = result;
const messageToRegenerate = conversationsStore.activeMessages[messageIndex];
if (messageToRegenerate.role !== 'assistant') return;
try {
const messagesToRemove = conversationsStore.activeMessages.slice(messageIndex); const messagesToRemove = conversationsStore.activeMessages.slice(messageIndex);
for (const message of messagesToRemove) await DatabaseService.deleteMessage(message.id); for (const message of messagesToRemove) await DatabaseService.deleteMessage(message.id);
conversationsStore.sliceActiveMessages(messageIndex); conversationsStore.sliceActiveMessages(messageIndex);
@ -832,6 +895,7 @@ class ChatStore {
const siblings = allMessages.filter( const siblings = allMessages.filter(
(m) => m.parent === messageToDelete.parent && m.id !== messageId (m) => m.parent === messageToDelete.parent && m.id !== messageId
); );
if (siblings.length > 0) { if (siblings.length > 0) {
const latestSibling = siblings.reduce((latest, sibling) => const latestSibling = siblings.reduce((latest, sibling) =>
sibling.timestamp > latest.timestamp ? sibling : latest sibling.timestamp > latest.timestamp ? sibling : latest
@ -845,6 +909,7 @@ class ChatStore {
} }
await DatabaseService.deleteMessageCascading(activeConv.id, messageId); await DatabaseService.deleteMessageCascading(activeConv.id, messageId);
await conversationsStore.refreshActiveMessages(); await conversationsStore.refreshActiveMessages();
conversationsStore.updateConversationTimestamp(); conversationsStore.updateConversationTimestamp();
} catch (error) { } catch (error) {
console.error('Failed to delete message:', error); console.error('Failed to delete message:', error);
@ -862,12 +927,12 @@ class ChatStore {
): Promise<void> { ): Promise<void> {
const activeConv = conversationsStore.activeConversation; const activeConv = conversationsStore.activeConversation;
if (!activeConv || this.isLoading) return; if (!activeConv || this.isLoading) return;
try {
const idx = conversationsStore.findMessageIndex(messageId);
if (idx === -1) return;
const msg = conversationsStore.activeMessages[idx];
if (msg.role !== 'assistant') return;
const result = this.getMessageByIdWithRole(messageId, 'assistant');
if (!result) return;
const { message: msg, index: idx } = result;
try {
if (shouldBranch) { if (shouldBranch) {
const newMessage = await DatabaseService.createMessageBranch( const newMessage = await DatabaseService.createMessageBranch(
{ {
@ -902,12 +967,12 @@ class ChatStore {
async editUserMessagePreserveResponses(messageId: string, newContent: string): Promise<void> { async editUserMessagePreserveResponses(messageId: string, newContent: string): Promise<void> {
const activeConv = conversationsStore.activeConversation; const activeConv = conversationsStore.activeConversation;
if (!activeConv) return; if (!activeConv) return;
try {
const idx = conversationsStore.findMessageIndex(messageId);
if (idx === -1) return;
const msg = conversationsStore.activeMessages[idx];
if (msg.role !== 'user') return;
const result = this.getMessageByIdWithRole(messageId, 'user');
if (!result) return;
const { message: msg, index: idx } = result;
try {
await DatabaseService.updateMessage(messageId, { await DatabaseService.updateMessage(messageId, {
content: newContent, content: newContent,
timestamp: Date.now() timestamp: Date.now()
@ -916,6 +981,7 @@ class ChatStore {
const allMessages = await conversationsStore.getConversationMessages(activeConv.id); const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null); const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
if (rootMessage && msg.parent === rootMessage.id && newContent.trim()) { if (rootMessage && msg.parent === rootMessage.id && newContent.trim()) {
await conversationsStore.updateConversationTitleWithConfirmation( await conversationsStore.updateConversationTitleWithConfirmation(
activeConv.id, activeConv.id,
@ -932,15 +998,16 @@ class ChatStore {
async editMessageWithBranching(messageId: string, newContent: string): Promise<void> { async editMessageWithBranching(messageId: string, newContent: string): Promise<void> {
const activeConv = conversationsStore.activeConversation; const activeConv = conversationsStore.activeConversation;
if (!activeConv || this.isLoading) return; if (!activeConv || this.isLoading) return;
try {
const idx = conversationsStore.findMessageIndex(messageId);
if (idx === -1) return;
const msg = conversationsStore.activeMessages[idx];
if (msg.role !== 'user') return;
const result = this.getMessageByIdWithRole(messageId, 'user');
if (!result) return;
const { message: msg } = result;
try {
const allMessages = await conversationsStore.getConversationMessages(activeConv.id); const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null); const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
const isFirstUserMessage = rootMessage && msg.parent === rootMessage.id; const isFirstUserMessage = rootMessage && msg.parent === rootMessage.id;
const parentId = msg.parent || rootMessage?.id; const parentId = msg.parent || rootMessage?.id;
if (!parentId) return; if (!parentId) return;
@ -1034,7 +1101,9 @@ class ChatStore {
private async generateResponseForMessage(userMessageId: string): Promise<void> { private async generateResponseForMessage(userMessageId: string): Promise<void> {
const activeConv = conversationsStore.activeConversation; const activeConv = conversationsStore.activeConversation;
if (!activeConv) return; if (!activeConv) return;
this.errorDialogState = null; this.errorDialogState = null;
this.setChatLoading(activeConv.id, true); this.setChatLoading(activeConv.id, true);
this.clearChatStreaming(activeConv.id); this.clearChatStreaming(activeConv.id);
@ -1071,26 +1140,30 @@ class ChatStore {
async continueAssistantMessage(messageId: string): Promise<void> { async continueAssistantMessage(messageId: string): Promise<void> {
const activeConv = conversationsStore.activeConversation; const activeConv = conversationsStore.activeConversation;
if (!activeConv || this.isLoading) return; if (!activeConv || this.isLoading) return;
try {
const idx = conversationsStore.findMessageIndex(messageId); const result = this.getMessageByIdWithRole(messageId, 'assistant');
if (idx === -1) return; if (!result) return;
const msg = conversationsStore.activeMessages[idx]; const { message: msg, index: idx } = result;
if (msg.role !== 'assistant') return;
if (this.isChatLoading(activeConv.id)) return; if (this.isChatLoading(activeConv.id)) return;
try {
this.errorDialogState = null; this.errorDialogState = null;
this.setChatLoading(activeConv.id, true); this.setChatLoading(activeConv.id, true);
this.clearChatStreaming(activeConv.id); this.clearChatStreaming(activeConv.id);
const allMessages = await conversationsStore.getConversationMessages(activeConv.id); const allMessages = await conversationsStore.getConversationMessages(activeConv.id);
const dbMessage = allMessages.find((m) => m.id === messageId); const dbMessage = allMessages.find((m) => m.id === messageId);
if (!dbMessage) { if (!dbMessage) {
this.setChatLoading(activeConv.id, false); this.setChatLoading(activeConv.id, false);
return; return;
} }
const originalContent = dbMessage.content; const originalContent = dbMessage.content;
const originalThinking = dbMessage.thinking || ''; const originalThinking = dbMessage.thinking || '';
const conversationContext = conversationsStore.activeMessages.slice(0, idx); const conversationContext = conversationsStore.activeMessages.slice(0, idx);
const contextWithContinue = [ const contextWithContinue = [
...conversationContext, ...conversationContext,
@ -1107,6 +1180,7 @@ class ChatStore {
contextWithContinue, contextWithContinue,
{ {
...this.getApiOptions(), ...this.getApiOptions(),
onChunk: (chunk: string) => { onChunk: (chunk: string) => {
hasReceivedContent = true; hasReceivedContent = true;
appendedContent += chunk; appendedContent += chunk;
@ -1114,6 +1188,7 @@ class ChatStore {
this.setChatStreaming(msg.convId, fullContent, msg.id); this.setChatStreaming(msg.convId, fullContent, msg.id);
conversationsStore.updateMessageAtIndex(idx, { content: fullContent }); conversationsStore.updateMessageAtIndex(idx, { content: fullContent });
}, },
onReasoningChunk: (reasoningChunk: string) => { onReasoningChunk: (reasoningChunk: string) => {
hasReceivedContent = true; hasReceivedContent = true;
appendedThinking += reasoningChunk; appendedThinking += reasoningChunk;
@ -1121,6 +1196,7 @@ class ChatStore {
thinking: originalThinking + appendedThinking thinking: originalThinking + appendedThinking
}); });
}, },
onTimings: (timings: ChatMessageTimings, promptProgress?: ChatMessagePromptProgress) => { onTimings: (timings: ChatMessageTimings, promptProgress?: ChatMessagePromptProgress) => {
const tokensPerSecond = const tokensPerSecond =
timings?.predicted_ms && timings?.predicted_n timings?.predicted_ms && timings?.predicted_n
@ -1137,6 +1213,7 @@ class ChatStore {
msg.convId msg.convId
); );
}, },
onComplete: async ( onComplete: async (
finalContent?: string, finalContent?: string,
reasoningContent?: string, reasoningContent?: string,
@ -1161,6 +1238,7 @@ class ChatStore {
this.clearChatStreaming(msg.convId); this.clearChatStreaming(msg.convId);
this.clearProcessingState(msg.convId); this.clearProcessingState(msg.convId);
}, },
onError: async (error: Error) => { onError: async (error: Error) => {
if (this.isAbortError(error)) { if (this.isAbortError(error)) {
if (hasReceivedContent && appendedContent) { if (hasReceivedContent && appendedContent) {