import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { AdminApi } from "@/lib/api"; import { useToastStore } from "@/stores/toastStore"; import type { AdminSettingsResponse } from "@/types"; import { Button } from "@/components/ui/Button"; import { Card } from "@/components/ui/Card"; import { Input } from "@/components/ui/Input"; import { Spinner } from "@/components/ui/Spinner"; const GROUP_PREFIXES: Array<{ group: string; prefixes: string[]; labelKey: string }> = [ { group: "llm", prefixes: ["llm_", "llm."], labelKey: "admin.group_llm" }, { group: "embeddings", prefixes: ["embeddings_", "embeddings."], labelKey: "admin.group_embeddings" }, { group: "qdrant", prefixes: ["qdrant_", "qdrant."], labelKey: "admin.group_qdrant" }, { group: "ui", prefixes: ["ui_", "ui.", "site_", "site."], labelKey: "admin.group_ui" }, { group: "game", prefixes: ["game_", "game."], labelKey: "admin.group_game" }, ]; function groupFor(key: string): string { const lower = key.toLowerCase(); for (const g of GROUP_PREFIXES) { if (g.prefixes.some((p) => lower.startsWith(p))) return g.group; } return "other"; } export function SettingsPanel() { const { t } = useTranslation(); const pushToast = useToastStore((s) => s.push); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [draft, setDraft] = useState>({}); useEffect(() => { setLoading(true); AdminApi.settings() .then((res) => { setData(res); setDraft({ ...res.settings }); }) .catch(() => pushToast("error", t("admin.settings_load_failed"))) .finally(() => setLoading(false)); }, [pushToast, t]); const grouped = useMemo(() => { if (!data) return {} as Record>; const out: Record> = {}; for (const key of Object.keys(data.settings)) { const g = groupFor(key); (out[g] ||= []).push({ key, description: data.descriptions?.[key] }); } return out; }, [data]); const handleSave = async () => { if (!data) return; setSaving(true); try { // Save only changed keys const diff: Record = {}; for (const [k, v] of Object.entries(draft)) { if (data.settings[k] !== v) diff[k] = v; } if (Object.keys(diff).length === 0) { pushToast("info", "No changes to save."); return; } const res = await AdminApi.updateSettings(diff); setData(res); setDraft({ ...res.settings }); pushToast("success", t("admin.settings_saved")); } catch (err) { const msg = err instanceof Error ? err.message : "Failed"; pushToast("error", msg); } finally { setSaving(false); } }; if (loading) { return (
{t("common.loading")}
); } if (!data) { return

{t("common.no_data")}

; } const groupOrder = ["llm", "embeddings", "qdrant", "ui", "game", "other"]; const groupLabelKey: Record = { llm: "admin.group_llm", embeddings: "admin.group_embeddings", qdrant: "admin.group_qdrant", ui: "admin.group_ui", game: "admin.group_game", other: "common.details", }; return (

{t("admin.tab_settings")}

{groupOrder.map((g) => { const entries = grouped[g]; if (!entries || entries.length === 0) return null; return (
{entries.map(({ key, description }) => ( setDraft((d) => ({ ...d, [key]: e.target.value }))} /> ))}
); })}
); }