diff --git a/tools/server/public/index.html.gz b/tools/server/public/index.html.gz index 0144abda45..e2ef75aebe 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/models/ModelsSelector.svelte b/tools/server/webui/src/lib/components/app/models/ModelsSelector.svelte index bf489443fa..0ed6e5c1dd 100644 --- a/tools/server/webui/src/lib/components/app/models/ModelsSelector.svelte +++ b/tools/server/webui/src/lib/components/app/models/ModelsSelector.svelte @@ -77,7 +77,7 @@ let filteredOptions = $derived(filterModelOptions(options, searchTerm)); let groupedFilteredOptions = $derived( - groupModelOptions(filteredOptions, modelsStore.favouriteModelIds, (m) => + groupModelOptions(filteredOptions, modelsStore.favoriteModelIds, (m) => modelsStore.isModelLoaded(m) ) ); @@ -353,7 +353,7 @@ {@const { option, flatIndex } = item} {@const isSelected = currentModel === option.model || activeId === option.id} {@const isHighlighted = flatIndex === highlightedIndex} - {@const isFav = modelsStore.favouriteModelIds.has(option.model)} + {@const isFav = modelsStore.favoriteModelIds.has(option.model)} 0} -

Favourite models

- {#each groups.favourites as item (`fav-${item.option.id}`)} +{#if groups.favorites.length > 0} +

Favorite models

+ {#each groups.favorites as item (`fav-${item.option.id}`)} {@render render(item, true)} {/each} {/if} diff --git a/tools/server/webui/src/lib/components/app/models/ModelsSelectorOption.svelte b/tools/server/webui/src/lib/components/app/models/ModelsSelectorOption.svelte index 8f44bb8de1..3236130a9d 100644 --- a/tools/server/webui/src/lib/components/app/models/ModelsSelectorOption.svelte +++ b/tools/server/webui/src/lib/components/app/models/ModelsSelectorOption.svelte @@ -46,7 +46,10 @@ }); let isOperationInProgress = $derived(modelsStore.isModelOperationInProgress(option.model)); let isFailed = $derived(serverStatus === ServerModelStatus.FAILED); - let isLoaded = $derived(serverStatus === ServerModelStatus.LOADED && !isOperationInProgress); + let isSleeping = $derived(serverStatus === ServerModelStatus.SLEEPING); + let isLoaded = $derived( + (serverStatus === ServerModelStatus.LOADED || isSleeping) && !isOperationInProgress + ); let isLoading = $derived(serverStatus === ServerModelStatus.LOADING || isOperationInProgress); @@ -85,17 +88,17 @@ modelsStore.toggleFavourite(option.model)} + onclick={() => modelsStore.toggleFavorite(option.model)} /> {:else} modelsStore.toggleFavourite(option.model)} + onclick={() => modelsStore.toggleFavorite(option.model)} /> {/if} @@ -129,6 +132,23 @@ /> + {:else if isSleeping} +
+ + + +
{:else if isLoaded}
diff --git a/tools/server/webui/src/lib/components/app/models/ModelsSelectorSheet.svelte b/tools/server/webui/src/lib/components/app/models/ModelsSelectorSheet.svelte index 26f2b72d2b..fe88c979f9 100644 --- a/tools/server/webui/src/lib/components/app/models/ModelsSelectorSheet.svelte +++ b/tools/server/webui/src/lib/components/app/models/ModelsSelectorSheet.svelte @@ -76,7 +76,7 @@ let filteredOptions = $derived(filterModelOptions(options, searchTerm)); let groupedFilteredOptions = $derived( - groupModelOptions(filteredOptions, modelsStore.favouriteModelIds, (m) => + groupModelOptions(filteredOptions, modelsStore.favoriteModelIds, (m) => modelsStore.isModelLoaded(m) ) ); diff --git a/tools/server/webui/src/lib/components/app/models/index.ts b/tools/server/webui/src/lib/components/app/models/index.ts index 6a87345053..b4bcdf4308 100644 --- a/tools/server/webui/src/lib/components/app/models/index.ts +++ b/tools/server/webui/src/lib/components/app/models/index.ts @@ -47,7 +47,7 @@ export { default as ModelsSelector } from './ModelsSelector.svelte'; /** * **ModelsSelectorList** - Grouped model options list * - * Renders grouped model options (loaded, favourites, available) with section + * Renders grouped model options (loaded, favorites, available) with section * headers and org subgroups. Shared between ModelsSelector and ModelsSelectorSheet * to avoid template duplication. * @@ -59,7 +59,7 @@ export { default as ModelsSelectorList } from './ModelsSelectorList.svelte'; /** * **ModelsSelectorOption** - Single model option row * - * Renders a single model option with selection state, favourite toggle, + * Renders a single model option with selection state, favorite toggle, * load/unload actions, status indicators, and an info button. * Used inside ModelsSelectorList or directly in custom render snippets. */ diff --git a/tools/server/webui/src/lib/components/app/models/utils.ts b/tools/server/webui/src/lib/components/app/models/utils.ts index b3616ede8e..ae1f511e9f 100644 --- a/tools/server/webui/src/lib/components/app/models/utils.ts +++ b/tools/server/webui/src/lib/components/app/models/utils.ts @@ -13,7 +13,7 @@ export interface OrgGroup { export interface GroupedModelOptions { loaded: ModelItem[]; - favourites: ModelItem[]; + favorites: ModelItem[]; available: OrgGroup[]; } @@ -32,7 +32,7 @@ export function filterModelOptions(options: ModelOption[], searchTerm: string): export function groupModelOptions( filteredOptions: ModelOption[], - favouriteIds: Set, + favoriteIds: Set, isModelLoaded: (model: string) => boolean ): GroupedModelOptions { // Loaded models @@ -43,24 +43,24 @@ export function groupModelOptions( } } - // Favourites (excluding loaded) + // Favorites (excluding loaded) const loadedModelIds = new Set(loaded.map((item) => item.option.model)); - const favourites: ModelItem[] = []; + const favorites: ModelItem[] = []; for (let i = 0; i < filteredOptions.length; i++) { if ( - favouriteIds.has(filteredOptions[i].model) && + favoriteIds.has(filteredOptions[i].model) && !loadedModelIds.has(filteredOptions[i].model) ) { - favourites.push({ option: filteredOptions[i], flatIndex: i }); + favorites.push({ option: filteredOptions[i], flatIndex: i }); } } - // Available models grouped by org (excluding loaded and favourites) + // Available models grouped by org (excluding loaded and favorites) const available: OrgGroup[] = []; const orgGroups = new SvelteMap(); for (let i = 0; i < filteredOptions.length; i++) { const option = filteredOptions[i]; - if (loadedModelIds.has(option.model) || favouriteIds.has(option.model)) continue; + if (loadedModelIds.has(option.model) || favoriteIds.has(option.model)) continue; const key = option.parsedId?.orgName ?? ''; if (!orgGroups.has(key)) orgGroups.set(key, []); @@ -71,5 +71,5 @@ export function groupModelOptions( available.push({ orgName: orgName || null, items }); } - return { loaded, favourites, available }; + return { loaded, favorites, available }; } diff --git a/tools/server/webui/src/lib/constants/localstorage-keys.ts b/tools/server/webui/src/lib/constants/localstorage-keys.ts index dc4d69b4ec..2a2b629fd6 100644 --- a/tools/server/webui/src/lib/constants/localstorage-keys.ts +++ b/tools/server/webui/src/lib/constants/localstorage-keys.ts @@ -1,4 +1,4 @@ export const CONFIG_LOCALSTORAGE_KEY = 'LlamaCppWebui.config'; export const USER_OVERRIDES_LOCALSTORAGE_KEY = 'LlamaCppWebui.userOverrides'; -export const FAVOURITE_MODELS_LOCALSTORAGE_KEY = 'LlamaCppWebui.favouriteModels'; +export const FAVORITE_MODELS_LOCALSTORAGE_KEY = 'LlamaCppWebui.favoriteModels'; export const MCP_DEFAULT_ENABLED_LOCALSTORAGE_KEY = 'LlamaCppWebui.mcpDefaultEnabled'; diff --git a/tools/server/webui/src/lib/enums/server.ts b/tools/server/webui/src/lib/enums/server.ts index 7f30eab2cf..c9d599c52b 100644 --- a/tools/server/webui/src/lib/enums/server.ts +++ b/tools/server/webui/src/lib/enums/server.ts @@ -16,5 +16,6 @@ export enum ServerModelStatus { UNLOADED = 'unloaded', LOADING = 'loading', LOADED = 'loaded', + SLEEPING = 'sleeping', FAILED = 'failed' } diff --git a/tools/server/webui/src/lib/stores/models.svelte.ts b/tools/server/webui/src/lib/stores/models.svelte.ts index 50c32034a6..d7c885844f 100644 --- a/tools/server/webui/src/lib/stores/models.svelte.ts +++ b/tools/server/webui/src/lib/stores/models.svelte.ts @@ -7,7 +7,7 @@ import { TTLCache } from '$lib/utils'; import { MODEL_PROPS_CACHE_TTL_MS, MODEL_PROPS_CACHE_MAX_ENTRIES, - FAVOURITE_MODELS_LOCALSTORAGE_KEY + FAVORITE_MODELS_LOCALSTORAGE_KEY } from '$lib/constants'; /** @@ -57,7 +57,7 @@ class ModelsStore { private modelUsage = $state>>(new Map()); private modelLoadingStates = new SvelteMap(); - favouriteModelIds = $state>(this.loadFavouritesFromStorage()); + favoriteModelIds = $state>(this.loadFavoritesFromStorage()); /** * Model-specific props cache with TTL @@ -90,7 +90,11 @@ class ModelsStore { get loadedModelIds(): string[] { return this.routerModels - .filter((m) => m.status.value === ServerModelStatus.LOADED) + .filter( + (m) => + m.status.value === ServerModelStatus.LOADED || + m.status.value === ServerModelStatus.SLEEPING + ) .map((m) => m.id); } @@ -215,7 +219,11 @@ class ModelsStore { isModelLoaded(modelId: string): boolean { const model = this.routerModels.find((m) => m.id === modelId); - return model?.status.value === ServerModelStatus.LOADED || false; + return ( + model?.status.value === ServerModelStatus.LOADED || + model?.status.value === ServerModelStatus.SLEEPING || + false + ); } isModelOperationInProgress(modelId: string): boolean { @@ -621,17 +629,17 @@ class ModelsStore { /** * * - * Favourites + * Favorites * * */ - isFavourite(modelId: string): boolean { - return this.favouriteModelIds.has(modelId); + isFavorite(modelId: string): boolean { + return this.favoriteModelIds.has(modelId); } - toggleFavourite(modelId: string): void { - const next = new SvelteSet(this.favouriteModelIds); + toggleFavorite(modelId: string): void { + const next = new SvelteSet(this.favoriteModelIds); if (next.has(modelId)) { next.delete(modelId); @@ -639,22 +647,22 @@ class ModelsStore { next.add(modelId); } - this.favouriteModelIds = next; + this.favoriteModelIds = next; try { - localStorage.setItem(FAVOURITE_MODELS_LOCALSTORAGE_KEY, JSON.stringify([...next])); + localStorage.setItem(FAVORITE_MODELS_LOCALSTORAGE_KEY, JSON.stringify([...next])); } catch { - toast.error('Failed to save favourite models to local storage'); + toast.error('Failed to save favorite models to local storage'); } } - private loadFavouritesFromStorage(): Set { + private loadFavoritesFromStorage(): Set { try { - const raw = localStorage.getItem(FAVOURITE_MODELS_LOCALSTORAGE_KEY); + const raw = localStorage.getItem(FAVORITE_MODELS_LOCALSTORAGE_KEY); return raw ? new Set(JSON.parse(raw) as string[]) : new Set(); } catch { - toast.error('Failed to load favourite models from local storage'); + toast.error('Failed to load favorite models from local storage'); return new Set(); } @@ -713,4 +721,4 @@ export const loadingModelIds = () => modelsStore.loadingModelIds; export const propsCacheVersion = () => modelsStore.propsCacheVersion; export const singleModelName = () => modelsStore.singleModelName; export const selectedModelContextSize = () => modelsStore.selectedModelContextSize; -export const favouriteModelIds = () => modelsStore.favouriteModelIds; +export const favoriteModelIds = () => modelsStore.favoriteModelIds; diff --git a/tools/server/webui/src/lib/types/api.d.ts b/tools/server/webui/src/lib/types/api.d.ts index 7cbd6db97b..f7f876c875 100644 --- a/tools/server/webui/src/lib/types/api.d.ts +++ b/tools/server/webui/src/lib/types/api.d.ts @@ -54,7 +54,7 @@ export interface ApiChatMessageData { * Model status object from /models endpoint */ export interface ApiModelStatus { - /** Status value: loaded, unloaded, loading, failed */ + /** Status value: loaded, unloaded, loading, sleeping, failed */ value: ServerModelStatus; /** Command line arguments used when loading (only for loaded models) */ args?: string[];