"use client"; import { useEffect, useState } from "react"; import { api, ApiError } from "@/lib/api"; import { useLive } from "@/lib/events"; import type { Model } from "@/lib/types"; import { PageHeader, Toggle, useToast } from "@/components/ui"; type S = Record; export default function SettingsPage() { const live = useLive(); const toast = useToast(); const [s, setS] = useState(null); const [defaults, setDefaults] = useState({}); const [paths, setPaths] = useState>({}); const [hfToken, setHfToken] = useState(false); const [models, setModels] = useState([]); const [aliases, setAliases] = useState>({}); const [alias, setAlias] = useState(""); const [aliasModel, setAliasModel] = useState(""); const [pw, setPw] = useState({ current: "", next: "", confirm: "" }); const [dirty, setDirty] = useState({}); const load = () => { api.get<{ settings: S; defaults: S; paths: Record; hf_token_set: boolean }>("/api/settings").then((r) => { setS(r.settings); setDefaults(r.defaults); setPaths(r.paths); setHfToken(r.hf_token_set); }); api.get<{ models: Model[]; aliases: Record }>("/api/models").then((r) => { setModels(r.models); setAliases(r.aliases); }); }; useEffect(() => { load(); }, [live.version]); const set = (k: string, v: string | number | boolean | null) => { setS((o) => ({ ...(o || {}), [k]: v })); setDirty((d) => ({ ...d, [k]: v })); }; const save = async () => { try { await api.patch("/api/settings", dirty); toast.push("Settings saved", "good"); setDirty({}); load(); } catch (e) { toast.push(e instanceof ApiError ? e.message : "Failed", "bad"); } }; const addAlias = async () => { try { await api.put("/api/aliases", { alias: alias.trim(), model_id: aliasModel }); setAlias(""); load(); } catch (e) { toast.push(e instanceof ApiError ? e.message : "Failed", "bad"); } }; const changePw = async (e: React.FormEvent) => { e.preventDefault(); if (pw.next !== pw.confirm) return toast.push("Passwords do not match", "bad"); try { await api.post("/api/auth/password", { current_password: pw.current, new_password: pw.next }); toast.push("Password changed", "good"); setPw({ current: "", next: "", confirm: "" }); } catch (err) { toast.push(err instanceof ApiError ? err.message : "Failed", "bad"); } }; if (!s) return
Loading…
; const textModels = models.filter((m) => m.installed && !m.embedding && !m.reranker); const Num = ({ k, label, hint, step = 1 }: { k: string; label: string; hint?: string; step?: number }) => ( ); const Sel = ({ k, label, hint }: { k: string; label: string; hint?: string }) => ( ); return (
{toast.view} Save changes} />
Memory policy
Models & runtimes
set("allow_mlx", v)} label="Allow MLX models" /> set("allow_gguf", v)} label="Allow GGUF (llama.cpp) models" /> set("allow_downloads", v)} label="Allow downloads from Hugging Face" /> set("log_prompts", v)} label="Log prompts and completions (privacy: off by default)" />
Hugging Face token: {hfToken ? configured (HF_TOKEN) : not set — gated repos will fail. Set HF_TOKEN in .env.}
Model aliases

Clients can request fast, coder, reasoning, vision, embedding, default… auto picks a model from the prompt (code, images, length) using these aliases.

{Object.entries(aliases).map(([a, mid]) => (
{a}→{mid}
))} {!Object.keys(aliases).length &&
No aliases yet.
}
setAlias(e.target.value)} list="alias-suggest" /> {["default", "fast", "coder", "reasoning", "vision", "embedding", "reranker"].map((a) =>
Account & paths
setPw({ ...pw, current: e.target.value })} required /> setPw({ ...pw, next: e.target.value })} required minLength={10} /> setPw({ ...pw, confirm: e.target.value })} required />
{Object.entries(paths).map(([k, v]) =>
{k}: {v}
)}
); }