"use client"; import { AnimatePresence, motion } from "framer-motion"; import { useEffect, useState } from "react"; import { cn } from "@/lib/utils"; import { clearApiKey, saveSettings } from "@/lib/settings"; import { useHealth } from "@/lib/use-health"; import { AlertTriangleIcon, CheckCircleIcon, XCircleIcon } from "@/components/icons"; import { Button, Spinner } from "@/components/ui"; export function SettingsDialog({ open, onClose }: { open: boolean; onClose: () => void }) { const { health, loading, error, refresh, apiKey } = useHealth(); const [keyDraft, setKeyDraft] = useState(apiKey); const [reveal, setReveal] = useState(false); const [saved, setSaved] = useState(false); useEffect(() => { if (!open) return; setKeyDraft(apiKey); setSaved(false); const onKey = (event: KeyboardEvent) => { if (event.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, apiKey, onClose]); const save = () => { saveSettings({ apiKey: keyDraft.trim() }); setSaved(true); window.setTimeout(() => setSaved(false), 2500); }; const forget = () => { clearApiKey(); setKeyDraft(""); }; const needsKeyMode = health?.authMode === "user-key"; return ( {open ? ( event.stopPropagation()} className="glass-panel thin-scroll max-h-[90dvh] w-full max-w-lg overflow-y-auto rounded-t-[1.6rem] p-5 pb-safe shadow-2xl sm:rounded-[1.35rem] sm:pb-5" >

Settings

Models come from .env.local on the server; your API key stays in this browser.

{/* ------------------------------------------------ API key */}

Ollama API key

{apiKey ? ( saved in this browser ) : needsKeyMode ? ( required ) : ( not needed )}
setKeyDraft(event.target.value)} onKeyDown={(event) => { event.stopPropagation(); if (event.key === "Enter") save(); }} placeholder="paste your key from ollama.com/settings/keys" autoComplete="off" spellCheck={false} className="min-w-40 flex-1 rounded-xl border border-[var(--hairline)] bg-[var(--surface-strong)] px-3 py-2 font-mono text-xs text-frost-950 outline-none transition placeholder:font-sans placeholder:text-frost-400 focus:border-accent-500 dark:text-frost-50" />

Stored only in this browser's local storage and sent with each request so the server can talk to Ollama for you. It is never written to the server, never logged and never put in a cookie. Prefer the local daemon? Set OLLAMA_AUTH=local in .env.local and use{" "} ollama signin instead.

{apiKey ? ( ) : null} {saved ? ( Saved — testing… ) : null}
{loading && !health ? (
Checking Ollama…
) : null} {error ? (
{error}
) : null} {health ? : null} ) : null} ); } function HealthPanel({ health }: { health: ReturnType["health"] }) { if (!health) return null; const tone = health.ok ? "emerald" : health.needsKey ? "amber" : health.reachable ? "amber" : "rose"; const toneClasses: Record = { emerald: "bg-emerald-50 text-emerald-800 dark:bg-emerald-500/15 dark:text-emerald-200", amber: "bg-amber-50 text-amber-800 dark:bg-amber-500/15 dark:text-amber-200", rose: "bg-rose-50 text-rose-800 dark:bg-rose-500/15 dark:text-rose-200", }; return (
{tone === "emerald" ? ( ) : tone === "amber" ? ( ) : ( )} {health.ok ? "Ollama is reachable and both models are available." : health.needsKey ? "Waiting for your API key." : health.keyAccepted === false ? "Ollama rejected your API key." : health.reachable ? "Ollama is reachable, but a configured model is missing." : "Ollama isn't reachable."} {health.error ?

{health.error}

: null}
{health.mode === "direct-cloud" && health.needsKey ? (

Create a key at{" "} ollama.com/settings/keys {" "} and paste it above. Tip: with the local daemon you can run ollama signin instead and set{" "} OLLAMA_AUTH=local.

) : null}
); } function Info({ label, value, mono }: { label: string; value: string; mono?: boolean }) { return (
{label}
{value}
); } function ModelRow({ title, model, }: { title: string; model: { configured: string; resolved: string; available: boolean | null; note?: string }; }) { const badge = model.available === true ? { text: "available", className: "bg-emerald-100 text-emerald-900 dark:bg-emerald-400/20 dark:text-emerald-200" } : model.available === false ? { text: "not found", className: "bg-rose-50 text-rose-800 dark:bg-rose-500/15 dark:text-rose-200" } : { text: "unknown", className: "bg-[var(--surface-sunken)] text-frost-600 dark:text-frost-300" }; return (

{title}

{badge.text}

{model.resolved}

{model.configured !== model.resolved ? (

configured as {model.configured}

) : null} {model.note ?

{model.note}

: null}
); }