"use client"; import { useEffect, useRef, useState } from "react"; import { fetchEstimate, type ResourceEstimate } from "@/lib/modelfit"; import { VerdictPill } from "@/components/modelfit/VerdictPill"; const CTX_MIN = 2048; const CTX_MAX = 32768; const CTX_STEP = 2048; function fmtCtx(n: number): string { return n >= 1024 ? `${Math.round(n / 1024)}K` : `${n}`; } /** * Live memory planner (LM Studio's best interaction): drag the context length * and watch the estimated VRAM + will-it-run verdict update. Debounce-calls the * authoritative backend estimator (never a client-side guess) so the numbers * stay honest — no model is touched, this is pure estimation. */ export function MemoryPlanner({ modelId, paramsB, quantization, initial, }: { modelId: string; paramsB: number; quantization: string; initial: ResourceEstimate | null; }) { const [ctx, setCtx] = useState(initial?.context_tokens ?? 4096); const [est, setEst] = useState(initial); const [loading, setLoading] = useState(false); const timer = useRef | null>(null); useEffect(() => { setLoading(true); if (timer.current) clearTimeout(timer.current); timer.current = setTimeout(() => { fetchEstimate(modelId, paramsB, quantization, ctx) .then(setEst) .catch(() => {}) .finally(() => setLoading(false)); }, 250); return () => { if (timer.current) clearTimeout(timer.current); }; }, [ctx, modelId, paramsB, quantization]); const vram = est?.estimated_vram_gb ?? 0; const headroom = est?.headroom_gb; const avail = headroom != null ? vram + headroom : null; const pct = avail && avail > 0 ? Math.min(100, (vram / avail) * 100) : null; const barCol = pct == null ? "bg-sky-500" : pct > 90 ? "bg-red-500" : pct > 75 ? "bg-amber-500" : "bg-sky-500"; return (

Memory planner

{est?.verdict && }
Context length {fmtCtx(ctx)} tokens
setCtx(Number(e.target.value))} className="w-full accent-sky-500" aria-label="Context length" /> {pct != null && (
)}
~{vram.toFixed(1)} GB{avail != null ? ` / ${avail.toFixed(1)} GB` : ""} {loading ? "updating…" : "estimated"}
); }