import type { ModelDownload, Recipe, RecipeWithStatus, StarterPreset } from "@/lib/types"; import { defaultRuntimeForBackend } from "@/lib/serve-runtime"; const normalizeId = (value: string): string => value .toLowerCase() .replace(/[^a-z0-9-_]+/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, ""); function dedupeRecipeId(base: string, existingRecipes: Pick[]): string { const existingIds = new Set(existingRecipes.map((recipe) => recipe.id)); let recipeId = base || `model-${Date.now()}`; let suffix = 1; while (existingIds.has(recipeId)) { recipeId = `${base}-${suffix}`; suffix += 1; } return recipeId; } export function buildStarterRecipe( download: ModelDownload, existingRecipes: Pick[], preset?: StarterPreset | null, ): Recipe { const recipeBase = preset ? normalizeId(preset.id) : normalizeId(download.model_id.split("/").pop() ?? download.model_id); const recipeId = dedupeRecipeId(recipeBase, existingRecipes); const backend = preset?.backend ?? "vllm"; const modelPath = backend === "llamacpp" && preset?.gguf_file ? `${download.target_dir.replace(/\/+$/, "")}/${preset.gguf_file}` : download.target_dir; return { id: recipeId, name: preset?.name ?? download.model_id, model_path: modelPath, backend, runtime: defaultRuntimeForBackend(backend), served_model_name: download.model_id, trust_remote_code: true, dtype: "auto", max_model_len: 32768, gpu_memory_utilization: 0.9, tensor_parallel_size: 1, pipeline_parallel_size: 1, max_num_seqs: 256, kv_cache_dtype: "auto", extra_args: {}, ...(preset?.recipe_overrides ?? {}), }; }