diff --git a/static/js/cookbook-deps-recipes.js b/static/js/cookbook-deps-recipes.js index 36ecf5bbd..afb1b7287 100644 --- a/static/js/cookbook-deps-recipes.js +++ b/static/js/cookbook-deps-recipes.js @@ -5,33 +5,37 @@ // Entries are matched first-hit; put the more specific patterns ABOVE the // generic fallback for that backend. -// Recipes hold ONLY the install command(s). The renderer prepends a -// `source /bin/activate` line for context using the active server's -// configured envPath; the Run button reuses env_prefix to activate that -// same venv before executing, so the install lands in your existing -// environment rather than a fresh `.venv` in some random CWD. +// Recipes carry two variants per entry: +// variants.pip → install into the configured venv via uv/pip +// variants.docker → pull the official container image +// +// The renderer prepends a `source /bin/activate` for the pip variant +// (env_prefix handles activation for Run). The docker variant skips the +// activate line — `docker pull` doesn't need a venv. + const _RECIPES = [ // ── vllm ────────────────────────────────────────────────────────────── - // MiniMax M2/M2.7 — same generic vllm install for now; kept as its own - // entry so future model-specific patches (FP8 quants, custom kernels) - // land in one obvious place without touching the catch-all. + // MiniMax M2/M2.7 — same as the generic vllm install/image for now; + // kept as its own entry so future model-specific patches land in one + // obvious place without touching the catch-all. { backend: 'vllm', label: 'MiniMax M2 / M2.7', match: (m) => /minimax[-_]?m\s?2(\.7)?/i.test(m || ''), - commands: [ - 'uv pip install -U vllm --torch-backend auto', - ], + variants: { + pip: { commands: ['uv pip install -U vllm --torch-backend auto'] }, + docker: { commands: ['docker pull vllm/vllm-openai:latest'] }, + }, }, - // Generic vllm fallback — auto-resolves the right torch backend (CUDA - // 12.x / 12.4 / ROCm) at install time so users don't have to know. + // Generic vllm fallback. { backend: 'vllm', label: 'Any vLLM model', match: () => true, - commands: [ - 'uv pip install -U vllm --torch-backend auto', - ], + variants: { + pip: { commands: ['uv pip install -U vllm --torch-backend auto'] }, + docker: { commands: ['docker pull vllm/vllm-openai:latest'] }, + }, }, // ── sglang ──────────────────────────────────────────────────────────── @@ -39,24 +43,35 @@ const _RECIPES = [ backend: 'sglang', label: 'Any SGLang model', match: () => true, - commands: [ - 'uv pip install -U "sglang[all]" --torch-backend auto', - ], + variants: { + pip: { commands: ['uv pip install -U "sglang[all]" --torch-backend auto'] }, + docker: { commands: ['docker pull lmsysorg/sglang:latest'] }, + }, }, // ── llama.cpp ───────────────────────────────────────────────────────── - // The cookbook-side rebuild path covers this for users who already have - // the engine compiled — but for a fresh box, surface a sane install. { backend: 'llama_cpp', label: 'Any GGUF model', match: () => true, - commands: [ - 'CMAKE_ARGS="-DGGML_CUDA=on" uv pip install -U "llama-cpp-python[server]"', - ], + variants: { + pip: { commands: ['CMAKE_ARGS="-DGGML_CUDA=on" uv pip install -U "llama-cpp-python[server]"'] }, + docker: { commands: ['docker pull ghcr.io/ggerganov/llama.cpp:server-cuda'] }, + }, }, ]; +export const RECIPE_VARIANTS = ['pip', 'docker']; +export const RECIPE_DEFAULT_VARIANT = 'pip'; + +// Get the commands array for a recipe + variant. Falls back to pip when +// the requested variant isn't defined for the recipe. +export function recipeCommands(recipe, variant) { + if (!recipe) return []; + const v = (recipe.variants || {})[variant] || (recipe.variants || {}).pip; + return (v && v.commands) || []; +} + // Backends we surface a recipe panel for. Other rows in the Dependencies // list keep the existing flat Install/Reinstall button without an expand // affordance. diff --git a/static/js/cookbook.js b/static/js/cookbook.js index 584ec2ff0..f3efc0079 100644 --- a/static/js/cookbook.js +++ b/static/js/cookbook.js @@ -8,7 +8,7 @@ import spinnerModule from './spinner.js'; import { providerLogo } from './providers.js'; import { makeWindowDraggable } from './windowDrag.js'; import { _diagnose, _showDiagnosis, _clearDiagnosis, _runQuickCmd, ERROR_PATTERNS } from './cookbook-diagnosis.js'; -import { RECIPE_BACKENDS, recipesForBackend, pickRecipe } from './cookbook-deps-recipes.js'; +import { RECIPE_BACKENDS, recipesForBackend, pickRecipe, recipeCommands, RECIPE_DEFAULT_VARIANT } from './cookbook-deps-recipes.js'; import { _hwfitCache, _hwfitDebounce, _hwfitFetch, _hwfitInit, _hwfitRenderList, _hwfitRenderHw, _renderGpuToggles, _expandModelRow, _fitColors, _hwfitColumns, _cachedModelIds, _gpuToggleTotal, _resetGpuToggleState } from './cookbook-hwfit.js'; // Sub-modules @@ -834,10 +834,12 @@ async function _fetchDependencies() { + recipePanel; }; - // Prepend the configured venv's activate line (when one is set) so the - // user sees the full sequence they'd need to paste, while Run keeps - // using env_prefix to activate the same venv before the pip command. - function _recipeDisplayText(commands) { + // Prepend the configured venv's activate line (pip variant only) so + // the user sees a paste-ready sequence; Run keeps using env_prefix to + // activate the same venv before the pip command. Docker variant skips + // the activate line — `docker pull` doesn't need a venv. + function _recipeDisplayText(commands, variant) { + if (variant === 'docker') return commands.join('\n'); const envPath = (_envState.envPath || '').replace(/\/+$/, ''); const activate = envPath ? `source ${envPath}${envPath.endsWith('/bin/activate') ? '' : '/bin/activate'}` @@ -864,12 +866,21 @@ async function _fetchDependencies() { const opts = modelOptions + otherOpt; // Initial recipe: the generic fallback (matches first time, no model id). const initial = pickRecipe(backend, '') || candidates[0]; - return `