mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-17 20:31:47 +02:00
ui : persist the active backend and guard server props fetches
Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
@@ -16,6 +16,9 @@ export const DB_APP_NAME_DEPRECATED = 'LlamacppWebui';
|
||||
|
||||
export const ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.alwaysAllowedTools`;
|
||||
|
||||
/** Id of the backend the selector and new requests target, restored on page load. */
|
||||
export const ACTIVE_BACKEND_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.activeBackend`;
|
||||
|
||||
/** Paused model download ids (`<repo>:<tag>`), restored on the next page load. */
|
||||
export const PAUSED_MODEL_DOWNLOADS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.pausedModelDownloads`;
|
||||
export const CONFIG_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.config`;
|
||||
|
||||
@@ -8,14 +8,34 @@
|
||||
*/
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
import { LOCAL_BACKEND_ID, SETTINGS_KEYS } from '$lib/constants';
|
||||
import { ACTIVE_BACKEND_LOCALSTORAGE_KEY, LOCAL_BACKEND_ID, SETTINGS_KEYS } from '$lib/constants';
|
||||
import { settingsStore } from '$lib/stores/settings/index.svelte';
|
||||
import type { Backend } from '$lib/types';
|
||||
import { setBackendsResolver } from '$lib/utils/api-base';
|
||||
import { createLocalBackend, parseBackendsSettings } from '$lib/utils/backend';
|
||||
|
||||
function loadActiveBackendId(): string {
|
||||
if (!browser) return LOCAL_BACKEND_ID;
|
||||
|
||||
try {
|
||||
return localStorage.getItem(ACTIVE_BACKEND_LOCALSTORAGE_KEY) ?? LOCAL_BACKEND_ID;
|
||||
} catch {
|
||||
return LOCAL_BACKEND_ID;
|
||||
}
|
||||
}
|
||||
|
||||
function persistActiveBackendId(backendId: string): void {
|
||||
if (!browser) return;
|
||||
|
||||
try {
|
||||
localStorage.setItem(ACTIVE_BACKEND_LOCALSTORAGE_KEY, backendId);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
class BackendsStore {
|
||||
activeId = $state<string>(LOCAL_BACKEND_ID);
|
||||
activeId = $state<string>(loadActiveBackendId());
|
||||
|
||||
get active(): Backend {
|
||||
const active = this.enabled.find((backend) => backend.id === this.activeId);
|
||||
@@ -56,14 +76,15 @@ class BackendsStore {
|
||||
this.saveExternal(this.external.filter((backend) => backend.id !== backendId));
|
||||
|
||||
if (this.activeId === backendId) {
|
||||
this.activeId = LOCAL_BACKEND_ID;
|
||||
this.setActive(LOCAL_BACKEND_ID);
|
||||
}
|
||||
}
|
||||
|
||||
setActive(backendId: string): void {
|
||||
this.activeId = this.list.some((backend) => backend.id === backendId)
|
||||
? backendId
|
||||
: LOCAL_BACKEND_ID;
|
||||
const id = this.list.some((backend) => backend.id === backendId) ? backendId : LOCAL_BACKEND_ID;
|
||||
|
||||
this.activeId = id;
|
||||
persistActiveBackendId(id);
|
||||
}
|
||||
|
||||
setLocalEnabled(enabled: boolean): void {
|
||||
|
||||
@@ -22,6 +22,7 @@ class ServerStore {
|
||||
props = $state<ApiLlamaCppServerProps | null>(null);
|
||||
role = $state<ServerRole | null>(null);
|
||||
status = $state<number | null>(null);
|
||||
private fetchBackendId: string | undefined;
|
||||
private fetchPromise: Promise<void> | null = null;
|
||||
private retryTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
@@ -62,6 +63,7 @@ class ServerStore {
|
||||
this.loading = false;
|
||||
this.role = null;
|
||||
this.fetchPromise = null;
|
||||
this.fetchBackendId = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +72,12 @@ class ServerStore {
|
||||
* splash and the chat screen every retry tick.
|
||||
*/
|
||||
async fetch({ background = false }: { background?: boolean } = {}): Promise<void> {
|
||||
if (this.fetchPromise) return this.fetchPromise;
|
||||
// props and role describe one server. a fetch started for another backend
|
||||
// must not be reused, and its response must not commit once the active
|
||||
// backend has changed while it was in flight
|
||||
const backendId = getBackend()?.id;
|
||||
|
||||
if (this.fetchPromise && this.fetchBackendId === backendId) return this.fetchPromise;
|
||||
|
||||
this.clearRetryTimer();
|
||||
|
||||
@@ -93,15 +100,20 @@ class ServerStore {
|
||||
this.error = null;
|
||||
}
|
||||
|
||||
const fetchPromise = (async () => {
|
||||
const promise = (async () => {
|
||||
try {
|
||||
const props = await PropsService.fetch();
|
||||
|
||||
// the active backend changed while this request was in flight
|
||||
if (getBackend()?.id !== backendId) return;
|
||||
|
||||
this.props = props;
|
||||
this.error = null;
|
||||
this.status = null;
|
||||
this.detectRole(props);
|
||||
} catch (error: unknown) {
|
||||
if (getBackend()?.id !== backendId) return;
|
||||
|
||||
this.error = error instanceof Error ? error.message : String(error);
|
||||
this.status = error instanceof ApiError ? error.status : null;
|
||||
console.error('Error fetching server properties:', error);
|
||||
@@ -113,13 +125,24 @@ class ServerStore {
|
||||
if (!background) {
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
this.fetchPromise = null;
|
||||
}
|
||||
})();
|
||||
|
||||
this.fetchPromise = fetchPromise;
|
||||
await fetchPromise;
|
||||
this.fetchPromise = promise;
|
||||
this.fetchBackendId = backendId;
|
||||
|
||||
// a backend switch clears the in-flight handle; only the fetch that is
|
||||
// still the current one may release it
|
||||
void promise
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
if (this.fetchPromise === promise) {
|
||||
this.fetchPromise = null;
|
||||
this.fetchBackendId = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
await promise;
|
||||
}
|
||||
|
||||
private clearRetryTimer(): void {
|
||||
|
||||
Reference in New Issue
Block a user