Skip to content

Commit 53d88d3

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 95f3f6b commit 53d88d3

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
@@ -31,12 +31,11 @@ export { default as ChatSidebarConversationItem } from './chat/ChatSidebar/ChatS
3131
export { default as ChatSidebarSearch } from './chat/ChatSidebar/ChatSidebarSearch.svelte';
3232
export { default as ChatSidebarModelSelector } from './chat/ChatSidebar/ModelSelector.svelte';
3333

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

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

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

4241
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
@@ -14,7 +14,7 @@ import { slotsService } from './slots';
1414
* - Manages streaming and non-streaming response parsing
1515
* - Provides request abortion capabilities
1616
* - Converts database messages to API format
17-
* - Handles error translation and context detection
17+
* - Handles error translation for server responses
1818
*
1919
* - **ChatStore**: Stateful orchestration and UI state management
2020
* - Uses ChatService for all AI model communication
@@ -27,7 +27,6 @@ import { slotsService } from './slots';
2727
* - Streaming response handling with real-time callbacks
2828
* - Reasoning content extraction and processing
2929
* - File attachment processing (images, PDFs, audio, text)
30-
* - Context error detection and reporting
3130
* - Request lifecycle management (abort, cleanup)
3231
*/
3332
export class ChatService {
@@ -215,10 +214,13 @@ export class ChatService {
215214
userFriendlyError = new Error(
216215
'Unable to connect to server - please check if the server is running'
217216
);
217+
userFriendlyError.name = 'NetworkError';
218218
} else if (error.message.includes('ECONNREFUSED')) {
219219
userFriendlyError = new Error('Connection refused - server may be offline');
220+
userFriendlyError.name = 'NetworkError';
220221
} else if (error.message.includes('ETIMEDOUT')) {
221-
userFriendlyError = new Error('Request timeout - server may be overloaded');
222+
userFriendlyError = new Error('TCP Timeout');
223+
userFriendlyError.name = 'TimeoutError';
222224
} else {
223225
userFriendlyError = error;
224226
}
@@ -325,12 +327,8 @@ export class ChatService {
325327

326328
if (streamFinished) {
327329
if (!hasReceivedData && aggregatedContent.length === 0) {
328-
const contextError = new Error(
329-
'The request exceeds the available context size. Try increasing the context size or enable context shift.'
330-
);
331-
contextError.name = 'ContextError';
332-
onError?.(contextError);
333-
return;
330+
const noResponseError = new Error('No response received from server. Please try again.');
331+
throw noResponseError;
334332
}
335333

336334
onComplete?.(aggregatedContent, fullReasoningContent || undefined, lastTimings);
@@ -369,12 +367,8 @@ export class ChatService {
369367
const responseText = await response.text();
370368

371369
if (!responseText.trim()) {
372-
const contextError = new Error(
373-
'The request exceeds the available context size. Try increasing the context size or enable context shift.'
374-
);
375-
contextError.name = 'ContextError';
376-
onError?.(contextError);
377-
throw contextError;
370+
const noResponseError = new Error('No response received from server. Please try again.');
371+
throw noResponseError;
378372
}
379373

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

388382
if (!content.trim()) {
389-
const contextError = new Error(
390-
'The request exceeds the available context size. Try increasing the context size or enable context shift.'
391-
);
392-
contextError.name = 'ContextError';
393-
onError?.(contextError);
394-
throw contextError;
383+
const noResponseError = new Error('No response received from server. Please try again.');
384+
throw noResponseError;
395385
}
396386

397387
onComplete?.(content, reasoningContent);
398388

399389
return content;
400390
} catch (error) {
401-
if (error instanceof Error && error.name === 'ContextError') {
402-
throw error;
403-
}
404-
405391
const err = error instanceof Error ? error : new Error('Parse error');
406392

407393
onError?.(err);
@@ -595,37 +581,19 @@ export class ChatService {
595581
const errorText = await response.text();
596582
const errorData: ApiErrorResponse = JSON.parse(errorText);
597583

598-
if (errorData.error?.type === 'exceed_context_size_error') {
599-
const contextError = errorData.error as ApiContextSizeError;
600-
const error = new Error(contextError.message);
601-
error.name = 'ContextError';
602-
// Attach structured context information
603-
(
604-
error as Error & {
605-
contextInfo?: { promptTokens: number; maxContext: number; estimatedTokens: number };
606-
}
607-
).contextInfo = {
608-
promptTokens: contextError.n_prompt_tokens,
609-
maxContext: contextError.n_ctx,
610-
estimatedTokens: contextError.n_prompt_tokens
611-
};
612-
return error;
613-
}
614-
615-
// Fallback for other error types
616584
const message = errorData.error?.message || 'Unknown server error';
617-
return new Error(message);
585+
const error = new Error(message);
586+
error.name = response.status === 400 ? 'ServerError' : 'HttpError';
587+
588+
return error;
618589
} catch {
619590
// If we can't parse the error response, return a generic error
620-
return new Error(`Server error (${response.status}): ${response.statusText}`);
591+
const fallback = new Error(`Server error (${response.status}): ${response.statusText}`);
592+
fallback.name = 'HttpError';
593+
return fallback;
621594
}
622595
}
623596

624-
/**
625-
* Updates the processing state with timing information from the server response
626-
* @param timings - Timing data from the API response
627-
* @param promptProgress - Progress data from the API response
628-
*/
629597
private updateProcessingState(
630598
timings?: ChatMessageTimings,
631599
promptProgress?: ChatMessagePromptProgress

0 commit comments

Comments
 (0)