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, Ban, CheckCircle2, Terminal, Wrench } 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); const [llmTest, setLlmTest] = useState(null); const [testingLlm, setTestingLlm] = useState(false); const [llmToolsTest, setLlmToolsTest] = useState(null); const [testingLlmTools, setTestingLlmTools] = useState(false); const [userActionError, setUserActionError] = useState(""); 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); } }; // Build LLM overrides from current form values (excluding masked api_key). // Used by both testLlm and testLlmTools so the operator can tweak base_url // / model / api_key in the form and test before saving. const buildLlmOverrides = (): Record => { const overrides: Record = {}; for (const k of ["llm.base_url", "llm.api_key", "llm.model", "llm.request_timeout"]) { const v = values[k]; if (v !== undefined && v !== null && !(typeof v === "string" && v.includes("***"))) { overrides[k] = v; } } return overrides; }; const testLlm = async () => { setError(""); setTestingLlm(true); setLlmTest(null); try { const r = await adminApi.testLlm(buildLlmOverrides()); if (r.ok) { setLlmTest({ ok: true, msg: t("admin.llm_test_ok", { latency: r.latency_ms ?? 0, preview: (r.response_preview || "").slice(0, 80), }), }); } else { setLlmTest({ ok: false, msg: t("admin.llm_test_fail", { error: r.error || `(${r.error_type || "unknown"})` }), }); } } catch (err: any) { setLlmTest({ ok: false, msg: t("admin.llm_test_fail", { error: err.response?.data?.detail || err.message }), }); } finally { setTestingLlm(false); } }; const testLlmTools = async () => { setError(""); setTestingLlmTools(true); setLlmToolsTest(null); try { const r = await adminApi.testLlmTools(buildLlmOverrides()); if (r.ok && r.tool_calls_returned) { // Model returned a proper tool_call — function-calling works. setLlmToolsTest({ ok: true, msg: t("admin.llm_test_tools_ok_with_call", { name: r.tool_call_name || "?", args: JSON.stringify(r.tool_call_args || {}), latency: r.latency_ms ?? 0, }), }); } else if (r.ok && !r.tool_calls_returned) { // Model responded but did NOT use the tool — function-calling is NOT // supported. The fallback JSON parser will still work, but tool-based // flows (orchestrator, step-writer) will be unreliable. setLlmToolsTest({ ok: false, msg: t("admin.llm_test_tools_ok_no_call", { text: (r.text || "").slice(0, 120), }), }); } else { setLlmToolsTest({ ok: false, msg: t("admin.llm_test_fail", { error: r.error || `(${r.error_type || "unknown"})` }), }); } } catch (err: any) { setLlmToolsTest({ ok: false, msg: t("admin.llm_test_fail", { error: err.response?.data?.detail || err.message }), }); } finally { setTestingLlmTools(false); } }; const toggleUserActive = async (userId: string, currentActive: boolean) => { setUserActionError(""); try { const updated = await adminApi.setUserActive(userId, !currentActive); setUsers((prev) => prev.map((u) => (u.id === userId ? { ...u, is_active: updated.is_active } : u))); } catch (err: any) { setUserActionError(err.response?.data?.detail || t("errors.unknown")); } }; 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 })} />
{/* LLM connectivity tests — runs against current form values (so the operator can tweak base_url / model / api_key and test BEFORE saving). */}
{llmTest && ( {llmTest.msg} )} {llmToolsTest && ( {llmToolsTest.msg} )}
{/* === 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 })} />

{t("admin.triggers_enabled_desc")}

{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" && ( {userActionError && (

{userActionError}

)}
{users.map((u) => ( ))}
Email Username Role Active Created {t("admin.users_actions")}
{u.email} {u.username} {u.is_admin ? ( admin ) : ( user )} {u.is_active ? ( ) : ( )} {new Date(u.created_at).toLocaleDateString()} {u.is_admin ? ( ) : u.is_active ? ( ) : ( )}
)}
); } 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); }} /> ); }