import { useEffect, useState } from "react"; import { Bot, Undo2 } from "lucide-react"; import { api, type PolicyModel } from "../lib/api.ts"; import { OllamaModelsModal } from "./AdminPanel.tsx"; import { NodesSection } from "./OllamaNodes.tsx"; import { CatalogSection } from "./OllamaCatalog.tsx"; import { Button, Input, Spinner, Switch } from "./ui.tsx"; import { useT } from "../lib/i18n.ts"; import { cn } from "../lib/cn.ts"; /** Admin models section: node pool, per-model policy, Ollama library + HF catalog. */ export function ModelsPanel() { const [models, setModels] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [ollamaOpen, setOllamaOpen] = useState(false); const { t } = useT(); async function refresh(force = false) { setError(""); try { const res = await api.adminModelPolicy(force); setModels(res.models); } catch (err) { setError(err instanceof Error ? err.message : t("admin.loadFailed")); } finally { setLoading(false); } } useEffect(() => { refresh(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); function apply(updated: PolicyModel) { setModels((prev) => prev.map((m) => (m.id === updated.id ? updated : m))); } const groups = (() => { const byGroup = new Map(); for (const m of models) { const g = m.group ?? m.driver; const list = byGroup.get(g) ?? []; list.push(m); byGroup.set(g, list); } return [...byGroup.entries()]; })(); return (

{t("models.title")}

{t("models.desc")}

{error &&

{error}

} {loading &&

{t("common.loading")}

} {!loading && models.length === 0 && !error && (

{t("models.empty")}

)} {groups.map(([group, list]) => (

{group}

    {list.map((m) => ( ))}
))}

{t("models.ollamaTitle")}

{t("models.ollamaDesc")}

{ setOllamaOpen(false); refresh(); }} />
); } /* ---------- per-model policy rows ---------- */ function ModelRow({ model, onChanged, onError }: { model: PolicyModel; onChanged: (m: PolicyModel) => void; onError: (msg: string) => void; }) { const { t } = useT(); const [hourly, setHourly] = useState(model.hourly === 0 ? "" : String(model.hourly)); const [daily, setDaily] = useState(model.daily === 0 ? "" : String(model.daily)); const [busy, setBusy] = useState(false); useEffect(() => { setHourly(model.hourly === 0 ? "" : String(model.hourly)); setDaily(model.daily === 0 ? "" : String(model.daily)); }, [model.hourly, model.daily]); async function patch(p: { enabled?: boolean; hourly?: number; daily?: number }) { setBusy(true); try { const res = await api.adminModelPolicyPatch(model.id, p); onChanged(res.model); onError(""); } catch (err) { onError(err instanceof Error ? err.message : t("models.saveFailed")); } finally { setBusy(false); } } function commitNumber(kind: "hourly" | "daily", raw: string) { const v = raw.trim() === "" ? 0 : Number(raw); if (!Number.isInteger(v) || v < 0 || v > 1000000) { if (kind === "hourly") setHourly(model.hourly === 0 ? "" : String(model.hourly)); else setDaily(model.daily === 0 ? "" : String(model.daily)); return; } if (v === model[kind]) return; void patch({ [kind]: v }); } return (
  • {model.label || model.id} {model.id} void patch({ enabled: v })} />
  • ); }