Skip to content

Commit 8b07327

Browse files
webui: remove client-side context pre-check and rely on backend for limits
Removed the client-side context window pre-check and now simply sends messages while keeping the dialog imports limited to core components, eliminating the maximum context alert path Simplified streaming and non-streaming chat error handling to surface a generic 'No response received from server' error whenever the backend returns no content Removed the obsolete maxContextError plumbing from the chat store so state management now focuses on the core message flow without special context-limit cases
1 parent 755e0c0 commit 8b07327

File tree

9 files changed

+130
-352
lines changed

9 files changed

+130
-352
lines changed

tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
ChatMessages,
88
ChatProcessingInfo,
99
EmptyFileAlertDialog,
10+
ChatErrorDialog,
1011
ServerErrorSplash,
1112
ServerInfo,
1213
ServerLoadingSplash,
@@ -22,10 +23,11 @@
2223
activeMessages,
2324
activeConversation,
2425
deleteConversation,
26+
dismissErrorDialog,
27+
errorDialog,
2528
isLoading,
2629
sendMessage,
27-
stopGeneration,
28-
setMaxContextError
30+
stopGeneration
2931
} from '$lib/stores/chat.svelte';
3032
import {
3133
supportsVision,
@@ -34,7 +36,6 @@
3436
serverWarning,
3537
serverStore
3638
} from '$lib/stores/server.svelte';
37-
import { contextService } from '$lib/services';
3839
import { parseFilesToMessageExtras } from '$lib/utils/convert-files-to-extra';
3940
import { isFileTypeSupported } from '$lib/utils/file-type';
4041
import { filterFilesByModalities } from '$lib/utils/modality-file-validation';
@@ -80,6 +81,7 @@
8081
);
8182
8283
let isServerLoading = $derived(serverLoading());
84+
let activeErrorDialog = $derived(errorDialog());
8385
8486
async function handleDeleteConfirm() {
8587
const conversation = activeConversation();
@@ -105,6 +107,12 @@
105107
}
106108
}
107109
110+
function handleErrorDialogOpenChange(open: boolean) {
111+
if (!open) {
112+
dismissErrorDialog();
113+
}
114+
}
115+
108116
function handleDragOver(event: DragEvent) {
109117
event.preventDefault();
110118
}
@@ -183,21 +191,6 @@
183191
184192
const extras = result?.extras;
185193
186-
// Check context limit using real-time slots data
187-
const contextCheck = await contextService.checkContextLimit();
188-
189-
if (contextCheck && contextCheck.wouldExceed) {
190-
const errorMessage = contextService.getContextErrorMessage(contextCheck);
191-
192-
setMaxContextError({
193-
message: errorMessage,
194-
estimatedTokens: contextCheck.currentUsage,
195-
maxContext: contextCheck.maxContext
196-
});
197-
198-
return false;
199-
}
200-
201194
// Enable autoscroll for user-initiated message sending
202195
userScrolledUp = false;
203196
autoScrollEnabled = true;
@@ -461,6 +454,13 @@
461454
}}
462455
/>
463456

457+
<ChatErrorDialog
458+
open={Boolean(activeErrorDialog)}
459+
type={activeErrorDialog?.type ?? 'server'}
460+
message={activeErrorDialog?.message ?? ''}
461+
onOpenChange={handleErrorDialogOpenChange}
462+
/>
463+
464464
<style>
465465
.conversation-chat-form {
466466
position: relative;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<script lang="ts">
2+
import * as AlertDialog from '$lib/components/ui/alert-dialog';
3+
import { AlertTriangle, TimerOff } from '@lucide/svelte';
4+
5+
interface Props {
6+
open: boolean;
7+
type: 'timeout' | 'server';
8+
message: string;
9+
onOpenChange?: (open: boolean) => void;
10+
}
11+
12+
let { open = $bindable(), type, message, onOpenChange }: Props = $props();
13+
14+
const isTimeout = $derived(type === 'timeout');
15+
const title = $derived(isTimeout ? 'TCP Timeout' : 'Server Error');
16+
const description = $derived(
17+
isTimeout
18+
? 'The request did not receive a response from the server before timing out.'
19+
: 'The server responded with an error message. Review the details below.'
20+
);
21+
const iconClass = $derived(isTimeout ? 'text-destructive' : 'text-amber-500');
22+
const badgeClass = $derived(
23+
isTimeout
24+
? 'border-destructive/40 bg-destructive/10 text-destructive'
25+
: 'border-amber-500/40 bg-amber-500/10 text-amber-600 dark:text-amber-400'
26+
);
27+
28+
function handleOpenChange(newOpen: boolean) {
29+
open = newOpen;
30+
onOpenChange?.(newOpen);
31+
}
32+
</script>
33+
34+
<AlertDialog.Root {open} onOpenChange={handleOpenChange}>
35+
<AlertDialog.Content>
36+
<AlertDialog.Header>
37+
<AlertDialog.Title class="flex items-center gap-2">
38+
{#if isTimeout}
39+
<TimerOff class={`h-5 w-5 ${iconClass}`} />
40+
{:else}
41+
<AlertTriangle class={`h-5 w-5 ${iconClass}`} />
42+
{/if}
43+
44+
{title}
45+
</AlertDialog.Title>
46+
47+
<AlertDialog.Description>
48+
{description}
49+
</AlertDialog.Description>
50+
</AlertDialog.Header>
51+
52+
<div class={`rounded-lg border px-4 py-3 text-sm ${badgeClass}`}>
53+
<p class="font-medium">{message}</p>
54+
</div>
55+
56+
<AlertDialog.Footer>
57+
<AlertDialog.Action onclick={() => handleOpenChange(false)}>Close</AlertDialog.Action>
58+
</AlertDialog.Footer>
59+
</AlertDialog.Content>
60+
</AlertDialog.Root>

tools/server/webui/src/lib/components/app/dialogs/MaximumContextAlertDialog.svelte

Lines changed: 0 additions & 66 deletions
This file was deleted.

tools/server/webui/src/lib/components/app/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,11 @@ export { default as ChatSidebar } from './chat/ChatSidebar/ChatSidebar.svelte';
3030
export { default as ChatSidebarConversationItem } from './chat/ChatSidebar/ChatSidebarConversationItem.svelte';
3131
export { default as ChatSidebarSearch } from './chat/ChatSidebar/ChatSidebarSearch.svelte';
3232

33+
export { default as ChatErrorDialog } from './dialogs/ChatErrorDialog.svelte';
3334
export { default as EmptyFileAlertDialog } from './dialogs/EmptyFileAlertDialog.svelte';
3435

3536
export { default as ConversationTitleUpdateDialog } from './dialogs/ConversationTitleUpdateDialog.svelte';
3637

37-
export { default as MaximumContextAlertDialog } from './dialogs/MaximumContextAlertDialog.svelte';
38-
3938
export { default as KeyboardShortcutInfo } from './misc/KeyboardShortcutInfo.svelte';
4039

4140
export { default as MarkdownContent } from './misc/MarkdownContent.svelte';

tools/server/webui/src/lib/services/chat.ts

Lines changed: 18 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { slotsService } from './slots';
1313
* - Manages streaming and non-streaming response parsing
1414
* - Provides request abortion capabilities
1515
* - Converts database messages to API format
16-
* - Handles error translation and context detection
16+
* - Handles error translation for server responses
1717
*
1818
* - **ChatStore**: Stateful orchestration and UI state management
1919
* - Uses ChatService for all AI model communication
@@ -26,7 +26,6 @@ import { slotsService } from './slots';
2626
* - Streaming response handling with real-time callbacks
2727
* - Reasoning content extraction and processing
2828
* - File attachment processing (images, PDFs, audio, text)
29-
* - Context error detection and reporting
3029
* - Request lifecycle management (abort, cleanup)
3130
*/
3231
export class ChatService {
@@ -209,10 +208,13 @@ export class ChatService {
209208
userFriendlyError = new Error(
210209
'Unable to connect to server - please check if the server is running'
211210
);
211+
userFriendlyError.name = 'NetworkError';
212212
} else if (error.message.includes('ECONNREFUSED')) {
213213
userFriendlyError = new Error('Connection refused - server may be offline');
214+
userFriendlyError.name = 'NetworkError';
214215
} else if (error.message.includes('ETIMEDOUT')) {
215-
userFriendlyError = new Error('Request timeout - server may be overloaded');
216+
userFriendlyError = new Error('TCP Timeout');
217+
userFriendlyError.name = 'TimeoutError';
216218
} else {
217219
userFriendlyError = error;
218220
}
@@ -319,12 +321,8 @@ export class ChatService {
319321

320322
if (streamFinished) {
321323
if (!hasReceivedData && aggregatedContent.length === 0) {
322-
const contextError = new Error(
323-
'The request exceeds the available context size. Try increasing the context size or enable context shift.'
324-
);
325-
contextError.name = 'ContextError';
326-
onError?.(contextError);
327-
return;
324+
const noResponseError = new Error('No response received from server. Please try again.');
325+
throw noResponseError;
328326
}
329327

330328
onComplete?.(aggregatedContent, fullReasoningContent || undefined, lastTimings);
@@ -363,12 +361,8 @@ export class ChatService {
363361
const responseText = await response.text();
364362

365363
if (!responseText.trim()) {
366-
const contextError = new Error(
367-
'The request exceeds the available context size. Try increasing the context size or enable context shift.'
368-
);
369-
contextError.name = 'ContextError';
370-
onError?.(contextError);
371-
throw contextError;
364+
const noResponseError = new Error('No response received from server. Please try again.');
365+
throw noResponseError;
372366
}
373367

374368
const data: ApiChatCompletionResponse = JSON.parse(responseText);
@@ -380,22 +374,14 @@ export class ChatService {
380374
}
381375

382376
if (!content.trim()) {
383-
const contextError = new Error(
384-
'The request exceeds the available context size. Try increasing the context size or enable context shift.'
385-
);
386-
contextError.name = 'ContextError';
387-
onError?.(contextError);
388-
throw contextError;
377+
const noResponseError = new Error('No response received from server. Please try again.');
378+
throw noResponseError;
389379
}
390380

391381
onComplete?.(content, reasoningContent);
392382

393383
return content;
394384
} catch (error) {
395-
if (error instanceof Error && error.name === 'ContextError') {
396-
throw error;
397-
}
398-
399385
const err = error instanceof Error ? error : new Error('Parse error');
400386

401387
onError?.(err);
@@ -589,37 +575,19 @@ export class ChatService {
589575
const errorText = await response.text();
590576
const errorData: ApiErrorResponse = JSON.parse(errorText);
591577

592-
if (errorData.error?.type === 'exceed_context_size_error') {
593-
const contextError = errorData.error as ApiContextSizeError;
594-
const error = new Error(contextError.message);
595-
error.name = 'ContextError';
596-
// Attach structured context information
597-
(
598-
error as Error & {
599-
contextInfo?: { promptTokens: number; maxContext: number; estimatedTokens: number };
600-
}
601-
).contextInfo = {
602-
promptTokens: contextError.n_prompt_tokens,
603-
maxContext: contextError.n_ctx,
604-
estimatedTokens: contextError.n_prompt_tokens
605-
};
606-
return error;
607-
}
608-
609-
// Fallback for other error types
610578
const message = errorData.error?.message || 'Unknown server error';
611-
return new Error(message);
579+
const error = new Error(message);
580+
error.name = response.status === 400 ? 'ServerError' : 'HttpError';
581+
582+
return error;
612583
} catch {
613584
// If we can't parse the error response, return a generic error
614-
return new Error(`Server error (${response.status}): ${response.statusText}`);
585+
const fallback = new Error(`Server error (${response.status}): ${response.statusText}`);
586+
fallback.name = 'HttpError';
587+
return fallback;
615588
}
616589
}
617590

618-
/**
619-
* Updates the processing state with timing information from the server response
620-
* @param timings - Timing data from the API response
621-
* @param promptProgress - Progress data from the API response
622-
*/
623591
private updateProcessingState(
624592
timings?: ChatMessageTimings,
625593
promptProgress?: ChatMessagePromptProgress

0 commit comments

Comments
 (0)