import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { adminApi } from "@/api"; import type { LlmLog, SettingsOut } from "@/types"; import { Button } from "@/components/ui/Button"; import { Input } from "@/components/ui/Input"; import { Card, CardBody, CardHeader } from "@/components/ui/Card"; import { Save, ArrowLeft, Activity, Users, Zap } from "lucide-react"; export function AdminPanelPage() { const { t } = useTranslation(); const navigate = useNavigate(); const [settings, setSettings] = useState(null); const [values, setValues] = useState>({}); const [logs, setLogs] = useState([]); const [users, setUsers] = useState([]); const [tab, setTab] = useState<"settings" | "logs" | "users">("settings"); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [saved, setSaved] = useState(false); const [error, setError] = useState(""); const [embeddingTest, setEmbeddingTest] = useState(null); const [testingEmbeddings, setTestingEmbeddings] = useState(false); useEffect(() => { (async () => { try { const s = await adminApi.getSettings(); setSettings(s); setValues(s.values); const l = await adminApi.listLlmLogs(50); setLogs(l); const u = await adminApi.listUsers(); setUsers(u); } catch (err: any) { setError(err.response?.data?.detail || t("errors.unknown")); } finally { setLoading(false); } })(); }, []); const save = async () => { setError(""); setSaving(true); setSaved(false); try { // Strip masked api_key unless user typed a new one const payload: Record = { ...values }; for (const k of ["llm.api_key", "embedding.api_key"]) { if (typeof payload[k] === "string" && payload[k].includes("***")) { delete payload[k]; } } const s = await adminApi.updateSettings(payload); setSettings(s); setValues(s.values); setSaved(true); setTimeout(() => setSaved(false), 2000); } catch (err: any) { setError(err.response?.data?.detail || t("errors.unknown")); } finally { setSaving(false); } }; const testEmbeddings = async () => { setError(""); setTestingEmbeddings(true); setEmbeddingTest(null); try { // Build overrides from current form values (excluding masked api_key) const overrides: Record = {}; for (const k of [ "embedding.provider", "embedding.base_url", "embedding.api_key", "embedding.model", "embedding.dim", "embedding.request_timeout", "llm.base_url", "llm.api_key", ]) { const v = values[k]; if (v !== undefined && v !== null && !(typeof v === "string" && v.includes("***"))) { overrides[k] = v; } } const r = await adminApi.testEmbeddings(overrides); if (r.ok) { setEmbeddingTest({ ok: true, msg: t("admin.embedding_test_ok", { provider: r.provider, dim: r.dim, norm: r.sample_norm, }), }); } else { setEmbeddingTest({ ok: false, msg: t("admin.embedding_test_fail", { error: r.error || "unknown" }) }); } } catch (err: any) { setEmbeddingTest({ ok: false, msg: t("admin.embedding_test_fail", { error: err.response?.data?.detail || err.message }), }); } finally { setTestingEmbeddings(false); } }; if (loading) { return
{t("common.loading")}
; } return (

{t("admin.title")}

{error &&

{error}

} {/* Tabs */}
setTab("settings")}> {t("admin.settings")} setTab("logs")}> {t("admin.llm_logs")} setTab("users")}> {t("admin.users")}
{tab === "settings" && settings && (
setValues({ ...values, "llm.base_url": e.target.value })} placeholder="http://localhost:1234/v1" /> setValues({ ...values, "llm.api_key": e.target.value })} placeholder="sk-..." /> setValues({ ...values, "llm.model": e.target.value })} placeholder="local-model" />
setValues({ ...values, "llm.temperature": v })} step={0.1} /> setValues({ ...values, "llm.step_temperature": v })} step={0.1} /> setValues({ ...values, "llm.summary_temperature": v })} step={0.1} /> setValues({ ...values, "llm.max_tokens": v })} /> setValues({ ...values, "llm.request_timeout": v })} />
{/* === Embeddings / RAG === */}
setValues({ ...values, "embedding.base_url": e.target.value })} placeholder="http://localhost:1234/v1" /> setValues({ ...values, "embedding.api_key": e.target.value })} placeholder="sk-... (пусто = как у LLM)" />
setValues({ ...values, "embedding.model": e.target.value })} placeholder="text-embedding-3-small" /> setValues({ ...values, "embedding.dim": v })} /> setValues({ ...values, "embedding.request_timeout": v })} />
{embeddingTest && ( {embeddingTest.msg} )}
setValues({ ...values, "context.recent_messages": v })} /> setValues({ ...values, "context.compress_threshold": v })} /> setValues({ ...values, "context.summary_messages": v })} /> setValues({ ...values, "context.max_tokens_total": v })} />
setValues({ ...values, "triggers.check_interval": v })} />
{saved && {t("admin.saved")}}
)} {tab === "logs" && (
{logs.map((l) => ( ))}
Time Purpose Model Tokens Latency Status
{new Date(l.created_at).toLocaleTimeString()} {l.purpose} {l.model} {l.total_tokens ?? "—"} {l.latency_ms ? `${l.latency_ms}ms` : "—"} {l.error ? ( err ) : ( ok )}
)} {tab === "users" && (
{users.map((u) => ( ))}
Email Username Role Active Created
{u.email} {u.username} {u.is_admin ? ( admin ) : ( user )} {u.is_active ? ( ) : ( )} {new Date(u.created_at).toLocaleDateString()}
)}
); } function TabButton({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) { return ( ); } function NumberInput({ label, value, onChange, step, }: { label: string; value: any; onChange: (v: number) => void; step?: number; }) { return ( { const v = parseFloat(e.target.value); onChange(isNaN(v) ? 0 : v); }} /> ); }