"use client"; import { AnimatePresence, motion } from "framer-motion"; import { useEffect, useState } from "react"; import { LOCALES, useI18n } from "@/lib/i18n"; import { cn } from "@/lib/utils"; import { openInstallSheet, useInstallAvailable } from "@/lib/install"; import { clearApiKey, saveSettings } from "@/lib/settings"; import { useHealth } from "@/lib/use-health"; import { AlertTriangleIcon, CheckCircleIcon, ChevronRightIcon, DownloadIcon, 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 installAvailable = useInstallAvailable(); const { locale, setLocale, t } = useI18n(); 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" >

{t("settings.title")}

{t("settings.subtitle.before")}{" "} .env.local{" "} {t("settings.subtitle.after")}

{/* --------------------------------------------- language */}

{t("language.label")}

{LOCALES.map((entry) => ( ))}

{t("language.note")}

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

{t("settings.apiKey")}

{apiKey ? ( {t("settings.savedInBrowser")} ) : needsKeyMode ? ( {t("settings.required")} ) : ( {t("settings.notNeeded")} )}
setKeyDraft(event.target.value)} onKeyDown={(event) => { event.stopPropagation(); if (event.key === "Enter") save(); }} placeholder={t("settings.pasteKey")} 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" />

{t("settings.keyNote")} OLLAMA_AUTH=local {t("settings.keyNoteAfter")}{" "} .env.local {t("settings.keyNoteEnd")} ollama signin {t("settings.keyNoteEnd2")}

{apiKey ? ( ) : null} {saved ? ( {t("settings.savedTesting")} ) : null}
{/* ------------------------------------------------ install */} {installAvailable ? (
) : null} {loading && !health ? (
{t("settings.checking")}
) : null} {error ? (
{error}
) : null} {health ? : null} ) : null} ); } function HealthPanel({ health }: { health: ReturnType["health"] }) { const { t } = useI18n(); 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 ? t("health.ok") : health.needsKey ? t("health.needsKey") : health.keyAccepted === false ? t("health.rejected") : health.reachable ? t("health.modelMissing") : t("health.unreachable")} {health.error ?

{health.error}

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

{t("health.createKey.before")}{" "} ollama.com/settings/keys {" "} {t("health.createKey.after")} ollama signin {t("health.createKey.end")}{" "} 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 { t } = useI18n(); const badge = model.available === true ? { text: t("health.available"), className: "bg-emerald-100 text-emerald-900 dark:bg-emerald-400/20 dark:text-emerald-200" } : model.available === false ? { text: t("health.notFound"), className: "bg-rose-50 text-rose-800 dark:bg-rose-500/15 dark:text-rose-200" } : { text: t("health.unknown"), className: "bg-[var(--surface-sunken)] text-frost-600 dark:text-frost-300" }; return (

{title}

{badge.text}

{model.resolved}

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

{t("health.configuredAs", { model: model.configured })}

) : null} {model.note ?

{model.note}

: null}
); }