Ryker_Feng 6ca12c6c8f
feat(models): add user model favorites (#5441)
* feat(models): add user model favorites

* fix(models): use anchored favorites picker

* fix(models): keep model picker compact

* fix(models): remove obsolete favorites search path

* fix(models): address picker review feedback
2026-09-16 18:42:13 +08:00

86 lines
2.0 KiB
TypeScript

import { type Model } from "./types";
const FAVORITES_KEY_PREFIX = "deerflow.model-favorites.v1:";
interface FavoriteNamesPayload {
version: 1;
names: readonly string[];
}
export interface ModelChoiceProjection {
favorites: readonly Model[];
others: readonly Model[];
}
export function favoritesKey(userId: string): string {
return `${FAVORITES_KEY_PREFIX}${encodeURIComponent(userId)}`;
}
export function parseFavoriteNames(value: string | null): readonly string[] {
if (value === null) {
return [];
}
let payload: unknown;
try {
payload = JSON.parse(value);
} catch {
return [];
}
if (
typeof payload !== "object" ||
payload === null ||
!("version" in payload) ||
payload.version !== 1 ||
!("names" in payload) ||
!Array.isArray(payload.names)
) {
return [];
}
const names: string[] = [];
const seen = new Set<string>();
for (const name of payload.names) {
if (typeof name !== "string" || name.trim() === "" || seen.has(name)) {
continue;
}
seen.add(name);
names.push(name);
}
return names;
}
export function serializeFavoriteNames(names: readonly string[]): string {
const payload: FavoriteNamesPayload = { version: 1, names };
return JSON.stringify(payload);
}
export function updateFavoriteNames(
names: readonly string[],
name: string,
favorite: boolean,
): readonly string[] {
if (name.trim() === "") {
return names;
}
const includesName = names.includes(name);
if (favorite) {
return includesName ? names : [...names, name];
}
return includesName ? names.filter((candidate) => candidate !== name) : names;
}
export function projectModelChoices(
models: readonly Model[],
favoriteNames: readonly string[],
): ModelChoiceProjection {
const favoriteNameSet = new Set(favoriteNames);
return {
favorites: models.filter((model) => favoriteNameSet.has(model.name)),
others: models.filter((model) => !favoriteNameSet.has(model.name)),
};
}