Skip to content

Commit ae71fc0

Browse files
ServeurpersoComallozaur
authored andcommitted
webui: remove client-side context pre-check and rely on backend for limits (ggml-org#16506)
* fix: make SSE client robust to premature [DONE] in agentic proxy chains * 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 * webui: cosmetic rename of error messages * Update tools/server/webui/src/lib/stores/chat.svelte.ts Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com> * Update tools/server/webui/src/lib/stores/chat.svelte.ts Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com> * Update tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com> * Update tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com> * chore: update webui build output --------- Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>
1 parent bfb4912 commit ae71fc0

File tree

11 files changed

+141
-366
lines changed

11 files changed

+141
-366
lines changed

tools/server/public/index.html.gz

-195 Bytes
Binary file not shown.

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';
@@ -79,6 +80,7 @@
7980
showCenteredEmpty && !activeConversation() && activeMessages().length === 0 && !isLoading()
8081
);
8182
83+
let activeErrorDialog = $derived(errorDialog());
8284
let isServerLoading = $derived(serverLoading());
8385
8486
async function handleDeleteConfirm() {
@@ -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+
message={activeErrorDialog?.message ?? ''}
459+
onOpenChange={handleErrorDialogOpenChange}
460+
open={Boolean(activeErrorDialog)}
461+
type={activeErrorDialog?.type ?? 'server'}
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: 26 additions & 63 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('Request timed out - the server took too long to respond');
217+
userFriendlyError.name = 'TimeoutError';
216218
} else {
217219
userFriendlyError = error;
218220
}
@@ -262,6 +264,7 @@ export class ChatService {
262264
let fullReasoningContent = '';
263265
let hasReceivedData = false;
264266
let lastTimings: ChatMessageTimings | undefined;
267+
let streamFinished = false;
265268

266269
try {
267270
let chunk = '';
@@ -277,18 +280,8 @@ export class ChatService {
277280
if (line.startsWith('data: ')) {
278281
const data = line.slice(6);
279282
if (data === '[DONE]') {
280-
if (!hasReceivedData && aggregatedContent.length === 0) {
281-
const contextError = new Error(
282-
'The request exceeds the available context size. Try increasing the context size or enable context shift.'
283-
);
284-
contextError.name = 'ContextError';
285-
onError?.(contextError);
286-
return;
287-
}
288-
289-
onComplete?.(aggregatedContent, fullReasoningContent || undefined, lastTimings);
290-
291-
return;
283+
streamFinished = true;
284+
continue;
292285
}
293286

294287
try {
@@ -326,13 +319,13 @@ export class ChatService {
326319
}
327320
}
328321

329-
if (!hasReceivedData && aggregatedContent.length === 0) {
330-
const contextError = new Error(
331-
'The request exceeds the available context size. Try increasing the context size or enable context shift.'
332-
);
333-
contextError.name = 'ContextError';
334-
onError?.(contextError);
335-
return;
322+
if (streamFinished) {
323+
if (!hasReceivedData && aggregatedContent.length === 0) {
324+
const noResponseError = new Error('No response received from server. Please try again.');
325+
throw noResponseError;
326+
}
327+
328+
onComplete?.(aggregatedContent, fullReasoningContent || undefined, lastTimings);
336329
}
337330
} catch (error) {
338331
const err = error instanceof Error ? error : new Error('Stream error');
@@ -368,12 +361,8 @@ export class ChatService {
368361
const responseText = await response.text();
369362

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

379368
const data: ApiChatCompletionResponse = JSON.parse(responseText);
@@ -385,22 +374,14 @@ export class ChatService {
385374
}
386375

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

396381
onComplete?.(content, reasoningContent);
397382

398383
return content;
399384
} catch (error) {
400-
if (error instanceof Error && error.name === 'ContextError') {
401-
throw error;
402-
}
403-
404385
const err = error instanceof Error ? error : new Error('Parse error');
405386

406387
onError?.(err);
@@ -594,37 +575,19 @@ export class ChatService {
594575
const errorText = await response.text();
595576
const errorData: ApiErrorResponse = JSON.parse(errorText);
596577

597-
if (errorData.error?.type === 'exceed_context_size_error') {
598-
const contextError = errorData.error as ApiContextSizeError;
599-
const error = new Error(contextError.message);
600-
error.name = 'ContextError';
601-
// Attach structured context information
602-
(
603-
error as Error & {
604-
contextInfo?: { promptTokens: number; maxContext: number; estimatedTokens: number };
605-
}
606-
).contextInfo = {
607-
promptTokens: contextError.n_prompt_tokens,
608-
maxContext: contextError.n_ctx,
609-
estimatedTokens: contextError.n_prompt_tokens
610-
};
611-
return error;
612-
}
613-
614-
// Fallback for other error types
615578
const message = errorData.error?.message || 'Unknown server error';
616-
return new Error(message);
579+
const error = new Error(message);
580+
error.name = response.status === 400 ? 'ServerError' : 'HttpError';
581+
582+
return error;
617583
} catch {
618584
// If we can't parse the error response, return a generic error
619-
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;
620588
}
621589
}
622590

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

0 commit comments

Comments
 (0)