mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-25 07:27:30 -05:00
ui : sort provider models by recent use
Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
@@ -137,9 +137,18 @@ export function groupProviderOptions(
|
||||
loading: boolean;
|
||||
name: string;
|
||||
}[],
|
||||
limit = Infinity
|
||||
limit = Infinity,
|
||||
recentIds: readonly string[] = []
|
||||
): ProviderGroup[] {
|
||||
const byBackend = new SvelteMap<string, ModelItem[]>();
|
||||
const rank = new SvelteMap<string, number>();
|
||||
|
||||
recentIds.forEach((id, index) => rank.set(id, index));
|
||||
|
||||
const rankOf = (id: string) => rank.get(id) ?? Number.MAX_SAFE_INTEGER;
|
||||
// recently used models lead their section, the rest keep the backend's order
|
||||
const byRecency = (items: ModelItem[]) =>
|
||||
rank.size === 0 ? items : [...items].sort((a, b) => rankOf(a.option.id) - rankOf(b.option.id));
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const option = options[i];
|
||||
@@ -151,7 +160,7 @@ export function groupProviderOptions(
|
||||
}
|
||||
|
||||
return providers.map((provider) => {
|
||||
const items = byBackend.get(provider.backendId) ?? [];
|
||||
const items = byRecency(byBackend.get(provider.backendId) ?? []);
|
||||
|
||||
return { ...provider, items: items.slice(0, limit), matched: items.length };
|
||||
});
|
||||
|
||||
@@ -33,6 +33,9 @@ export const FAVORITE_MODELS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.favoriteMod
|
||||
|
||||
/** Model the user picked last, kept across reloads. Stores `{ id, model }`. */
|
||||
export const SELECTED_MODEL_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.selectedModel`;
|
||||
|
||||
/** Recently used model ids, most recent first, backend-qualified. */
|
||||
export const RECENT_MODELS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.recentModels`;
|
||||
export const REASONING_EFFORT_DEFAULT_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.reasoningEffortDefault`;
|
||||
export const CONVERSATION_TABS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.conversationTabs`;
|
||||
export const USER_OVERRIDES_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.userOverrides`;
|
||||
|
||||
@@ -53,6 +53,9 @@ export const MODEL_SELECTOR_ICON = Package;
|
||||
/** Models listed per remote provider before the "+ X more" line; search covers the rest. */
|
||||
export const REMOTE_PROVIDER_MODEL_LIMIT = 12;
|
||||
|
||||
/** Recently used models kept per browser, most recent first. */
|
||||
export const RECENT_MODEL_LIMIT = 20;
|
||||
|
||||
/** Model selector views: the favorites of every backend, the local server, the remote backends. */
|
||||
|
||||
export const ICON_STRIP_TRANSITION_DURATION = 150;
|
||||
|
||||
@@ -152,7 +152,8 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
|
||||
sectionOptions,
|
||||
remoteProviders,
|
||||
// a drill-in or a search reaches every model, the sections stay short
|
||||
providerViewId || searchTerm ? Infinity : REMOTE_PROVIDER_MODEL_LIMIT
|
||||
providerViewId || searchTerm ? Infinity : REMOTE_PROVIDER_MODEL_LIMIT,
|
||||
modelsStore.recentModelIds
|
||||
)
|
||||
);
|
||||
const groupedFilteredOptions = $derived.by(() => {
|
||||
|
||||
@@ -8,7 +8,12 @@
|
||||
*/
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
import { FAVORITE_MODELS_LOCALSTORAGE_KEY, SELECTED_MODEL_LOCALSTORAGE_KEY } from '$lib/constants';
|
||||
import {
|
||||
FAVORITE_MODELS_LOCALSTORAGE_KEY,
|
||||
RECENT_MODEL_LIMIT,
|
||||
RECENT_MODELS_LOCALSTORAGE_KEY,
|
||||
SELECTED_MODEL_LOCALSTORAGE_KEY
|
||||
} from '$lib/constants';
|
||||
import { ServerModelStatus } from '$lib/enums';
|
||||
import { ModelsService } from '$lib/services/models.service';
|
||||
// direct imports between stores, not via the barrel, to avoid circular deps
|
||||
@@ -45,11 +50,31 @@ function loadStoredSelection(): { id: string; model: string | null } | null {
|
||||
|
||||
const storedSelection = loadStoredSelection();
|
||||
|
||||
/** Recently used backend-qualified ids, most recent first. */
|
||||
function loadRecentModels(): string[] {
|
||||
if (!browser) return [];
|
||||
|
||||
try {
|
||||
const raw = localStorage.getItem(RECENT_MODELS_LOCALSTORAGE_KEY);
|
||||
|
||||
if (!raw) return [];
|
||||
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
|
||||
return Array.isArray(parsed)
|
||||
? parsed.filter((id): id is string => typeof id === 'string').slice(0, RECENT_MODEL_LIMIT)
|
||||
: [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class ModelsStore implements ModelPropsHost, ModelStatusHost {
|
||||
activeModels = $state<ModelOption[]>([]);
|
||||
error = $state<string | null>(null);
|
||||
favoriteModelIds = $state<Set<string>>(this.loadFavoritesFromStorage());
|
||||
loading = $state(false);
|
||||
recentModelIds = $state<string[]>(loadRecentModels());
|
||||
routerModels = $state<ApiModelDataEntry[]>([]);
|
||||
selectedModelId = $state<string | null>(storedSelection?.id ?? null);
|
||||
selectedModelName = $state<string | null>(storedSelection?.model ?? null);
|
||||
@@ -399,6 +424,7 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
|
||||
this.selectedModelName = option.model;
|
||||
this.selectionFromStorage = false;
|
||||
this.persistSelection();
|
||||
this.recordRecentModel(qualifiedId);
|
||||
} finally {
|
||||
this.updating = false;
|
||||
}
|
||||
@@ -606,6 +632,22 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost {
|
||||
}
|
||||
}
|
||||
|
||||
/** Move a model to the front of the recently used list. */
|
||||
private recordRecentModel(qualifiedId: string): void {
|
||||
this.recentModelIds = [
|
||||
qualifiedId,
|
||||
...this.recentModelIds.filter((id) => id !== qualifiedId)
|
||||
].slice(0, RECENT_MODEL_LIMIT);
|
||||
|
||||
if (!browser) return;
|
||||
|
||||
try {
|
||||
localStorage.setItem(RECENT_MODELS_LOCALSTORAGE_KEY, JSON.stringify(this.recentModelIds));
|
||||
} catch {
|
||||
console.warn('[ModelsStore] Failed to persist the recently used models');
|
||||
}
|
||||
}
|
||||
|
||||
private async runFetch(): Promise<void> {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
Reference in New Issue
Block a user