diff --git a/tools/server/public/index.html.gz b/tools/server/public/index.html.gz
index f12ff3e62e4aa..53c6a9b5cfb52 100644
Binary files a/tools/server/public/index.html.gz and b/tools/server/public/index.html.gz differ
diff --git a/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte b/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte
index 17215b713a5cb..666febf0d28d6 100644
--- a/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte
+++ b/tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte
@@ -3,9 +3,11 @@
import {
ChatForm,
ChatScreenHeader,
+ ChatScreenWarning,
ChatMessages,
ChatProcessingInfo,
EmptyFileAlertDialog,
+ ServerErrorSplash,
ServerInfo,
ServerLoadingSplash,
ConfirmationDialog
@@ -29,6 +31,7 @@
supportsVision,
supportsAudio,
serverLoading,
+ serverWarning,
serverStore
} from '$lib/stores/server.svelte';
import { contextService } from '$lib/services';
@@ -303,6 +306,10 @@
>
+ {#if serverWarning()}
+
+ {/if}
+
+ {#if serverWarning()}
+
+ {/if}
+
+ import { AlertTriangle, RefreshCw } from '@lucide/svelte';
+ import { serverLoading, serverStore } from '$lib/stores/server.svelte';
+ import { fly } from 'svelte/transition';
+
+ interface Props {
+ class?: string;
+ }
+
+ let { class: className = '' }: Props = $props();
+
+ function handleRefreshServer() {
+ serverStore.fetchServerProps();
+ }
+
+
+
+
+
+
+
+
+ Server `/props` endpoint not available - using cached data
+
+
+
+
+
+
diff --git a/tools/server/webui/src/lib/components/app/index.ts b/tools/server/webui/src/lib/components/app/index.ts
index 2f559bd623a51..7c1af27ecd3fd 100644
--- a/tools/server/webui/src/lib/components/app/index.ts
+++ b/tools/server/webui/src/lib/components/app/index.ts
@@ -19,6 +19,7 @@ export { default as MessageBranchingControls } from './chat/ChatMessages/ChatMes
export { default as ChatProcessingInfo } from './chat/ChatProcessingInfo.svelte';
export { default as ChatScreenHeader } from './chat/ChatScreen/ChatScreenHeader.svelte';
+export { default as ChatScreenWarning } from './chat/ChatScreen/ChatScreenWarning.svelte';
export { default as ChatScreen } from './chat/ChatScreen/ChatScreen.svelte';
export { default as ChatSettingsDialog } from './chat/ChatSettings/ChatSettingsDialog.svelte';
diff --git a/tools/server/webui/src/lib/constants/localstorage-keys.ts b/tools/server/webui/src/lib/constants/localstorage-keys.ts
new file mode 100644
index 0000000000000..9fcc7bab93d1d
--- /dev/null
+++ b/tools/server/webui/src/lib/constants/localstorage-keys.ts
@@ -0,0 +1 @@
+export const SERVER_PROPS_LOCALSTORAGE_KEY = 'LlamaCppWebui.serverProps';
diff --git a/tools/server/webui/src/lib/stores/server.svelte.ts b/tools/server/webui/src/lib/stores/server.svelte.ts
index 7abaa2bdf512b..8007102222cb9 100644
--- a/tools/server/webui/src/lib/stores/server.svelte.ts
+++ b/tools/server/webui/src/lib/stores/server.svelte.ts
@@ -1,3 +1,5 @@
+import { browser } from '$app/environment';
+import { SERVER_PROPS_LOCALSTORAGE_KEY } from '$lib/constants/localstorage-keys';
import { ChatService } from '$lib/services/chat';
import { config } from '$lib/stores/settings.svelte';
@@ -34,12 +36,51 @@ import { config } from '$lib/stores/settings.svelte';
* - Slots endpoint availability (for processing state monitoring)
* - Context window size and token limits
*/
+
class ServerStore {
+ constructor() {
+ if (!browser) return;
+
+ const cachedProps = this.readCachedServerProps();
+ if (cachedProps) {
+ this._serverProps = cachedProps;
+ }
+ }
+
private _serverProps = $state(null);
private _loading = $state(false);
private _error = $state(null);
+ private _serverWarning = $state(null);
private _slotsEndpointAvailable = $state(null);
+ private readCachedServerProps(): ApiLlamaCppServerProps | null {
+ if (!browser) return null;
+
+ try {
+ const raw = localStorage.getItem(SERVER_PROPS_LOCALSTORAGE_KEY);
+ if (!raw) return null;
+
+ return JSON.parse(raw) as ApiLlamaCppServerProps;
+ } catch (error) {
+ console.warn('Failed to read cached server props from localStorage:', error);
+ return null;
+ }
+ }
+
+ private persistServerProps(props: ApiLlamaCppServerProps | null): void {
+ if (!browser) return;
+
+ try {
+ if (props) {
+ localStorage.setItem(SERVER_PROPS_LOCALSTORAGE_KEY, JSON.stringify(props));
+ } else {
+ localStorage.removeItem(SERVER_PROPS_LOCALSTORAGE_KEY);
+ }
+ } catch (error) {
+ console.warn('Failed to persist server props to localStorage:', error);
+ }
+ }
+
get serverProps(): ApiLlamaCppServerProps | null {
return this._serverProps;
}
@@ -52,6 +93,10 @@ class ServerStore {
return this._error;
}
+ get serverWarning(): string | null {
+ return this._serverWarning;
+ }
+
get modelName(): string | null {
if (!this._serverProps?.model_path) return null;
return this._serverProps.model_path.split(/(\\|\/)/).pop() || null;
@@ -123,30 +168,43 @@ class ServerStore {
async fetchServerProps(): Promise {
this._loading = true;
this._error = null;
+ this._serverWarning = null;
try {
console.log('Fetching server properties...');
const props = await ChatService.getServerProps();
this._serverProps = props;
+ this.persistServerProps(props);
console.log('Server properties loaded:', props);
// Check slots endpoint availability after server props are loaded
await this.checkSlotsEndpointAvailability();
} catch (error) {
+ const hadCachedProps = this._serverProps !== null;
let errorMessage = 'Failed to connect to server';
+ let isOfflineLikeError = false;
+ let isServerSideError = false;
if (error instanceof Error) {
// Handle specific error types with user-friendly messages
if (error.name === 'TypeError' && error.message.includes('fetch')) {
errorMessage = 'Server is not running or unreachable';
+ isOfflineLikeError = true;
} else if (error.message.includes('ECONNREFUSED')) {
errorMessage = 'Connection refused - server may be offline';
+ isOfflineLikeError = true;
} else if (error.message.includes('ENOTFOUND')) {
errorMessage = 'Server not found - check server address';
+ isOfflineLikeError = true;
} else if (error.message.includes('ETIMEDOUT')) {
errorMessage = 'Connection timeout - server may be overloaded';
+ isOfflineLikeError = true;
+ } else if (error.message.includes('503')) {
+ errorMessage = 'Server temporarily unavailable - try again shortly';
+ isServerSideError = true;
} else if (error.message.includes('500')) {
errorMessage = 'Server error - check server logs';
+ isServerSideError = true;
} else if (error.message.includes('404')) {
errorMessage = 'Server endpoint not found';
} else if (error.message.includes('403') || error.message.includes('401')) {
@@ -154,7 +212,37 @@ class ServerStore {
}
}
- this._error = errorMessage;
+ let cachedProps: ApiLlamaCppServerProps | null = null;
+
+ if (!hadCachedProps) {
+ cachedProps = this.readCachedServerProps();
+ if (cachedProps) {
+ this._serverProps = cachedProps;
+ this._error = null;
+
+ if (isOfflineLikeError || isServerSideError) {
+ this._serverWarning = errorMessage;
+ }
+
+ console.warn(
+ 'Failed to refresh server properties, using cached values from localStorage:',
+ errorMessage
+ );
+ } else {
+ this._error = errorMessage;
+ }
+ } else {
+ this._error = null;
+
+ if (isOfflineLikeError || isServerSideError) {
+ this._serverWarning = errorMessage;
+ }
+
+ console.warn(
+ 'Failed to refresh server properties, continuing with cached values:',
+ errorMessage
+ );
+ }
console.error('Error fetching server properties:', error);
} finally {
this._loading = false;
@@ -167,8 +255,10 @@ class ServerStore {
clear(): void {
this._serverProps = null;
this._error = null;
+ this._serverWarning = null;
this._loading = false;
this._slotsEndpointAvailable = null;
+ this.persistServerProps(null);
}
}
@@ -177,6 +267,7 @@ export const serverStore = new ServerStore();
export const serverProps = () => serverStore.serverProps;
export const serverLoading = () => serverStore.loading;
export const serverError = () => serverStore.error;
+export const serverWarning = () => serverStore.serverWarning;
export const modelName = () => serverStore.modelName;
export const supportedModalities = () => serverStore.supportedModalities;
export const supportsVision = () => serverStore.supportsVision;
diff --git a/tools/server/webui/src/lib/utils/api-key-validation.ts b/tools/server/webui/src/lib/utils/api-key-validation.ts
index d7bbf9c2ebdc9..b8be21568f786 100644
--- a/tools/server/webui/src/lib/utils/api-key-validation.ts
+++ b/tools/server/webui/src/lib/utils/api-key-validation.ts
@@ -27,11 +27,10 @@ export async function validateApiKey(fetch: typeof globalThis.fetch): Promise= 500) {
- throw error(response.status, 'Server error - check if llama.cpp server is running');
- } else {
- throw error(response.status, `Server responded with status ${response.status}`);
}
+
+ console.warn(`Server responded with status ${response.status} during API key validation`);
+ return;
}
} catch (err) {
// If it's already a SvelteKit error, re-throw it
@@ -40,6 +39,6 @@ export async function validateApiKey(fetch: typeof globalThis.fetch): Promise