Add admin AI models panel (list, test, select Mistral models)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
billisdeadandClaude Sonnet 5.5 committed 2026-10-12 09:55:37 +02:00
1 parent defc07648c
commit 13677b3ce3
4 files changed
+162 -5

No files matched your search

+78 -1
View File
@@ -670,13 +670,78 @@ function BlacklistPanel() {
);
}
function AiModelsPanel() {
const { toast } = useToast();
const [data, setData] = useState<{ filter: string; synthesis: string; available: string[]; error: string | null } | null>(null);
const [filter, setFilter] = useState("");
const [synthesis, setSynthesis] = useState("");
const [busy, setBusy] = useState(false);
const load = useCallback(async () => {
const res = await adminFetch("/api/admin/ai/models");
if (!res.ok) return;
const d = await res.json();
setData(d); setFilter(d.filter); setSynthesis(d.synthesis);
}, []);
useEffect(() => { load(); }, [load]);
const test = async (model: string) => {
setBusy(true);
const r = await adminFetch("/api/admin/ai/test", { method: "POST", body: JSON.stringify({ model }) });
const d = await r.json().catch(() => ({}));
setBusy(false);
toast(d.ok
? { title: "Modèle OK", description: model }
: { title: "Échec", description: d.error ?? `Erreur ${r.status}`, variant: "destructive" });
return !!d.ok;
};
const save = async () => {
setBusy(true);
const r = await adminFetch("/api/admin/ai/models", { method: "POST", body: JSON.stringify({ filter, synthesis }) });
setBusy(false);
toast(r.ok ? { title: "Modèles enregistrés" } : { title: "Erreur", variant: "destructive" });
if (r.ok) load();
};
const opts = data?.available ?? [];
const field = (label: string, value: string, set: (v: string) => void) => (
<div className="space-y-1">
<label className="text-xs text-muted-foreground">{label}</label>
<div className="flex gap-2">
<Input list="ai-models" value={value} onChange={e => set(e.target.value)} className="font-mono text-xs" />
<Button size="sm" variant="outline" disabled={busy || !value} onClick={() => test(value)}>Tester</Button>
</div>
</div>
);
return (
<div className="space-y-4">
<h2 className="font-serif font-semibold text-lg text-primary">Modèles IA</h2>
{data?.error && (
<div className="text-xs border border-destructive/40 rounded-md p-3 bg-destructive/5 text-destructive">
Clé API inutilisable ou fournisseur injoignable : <span className="font-mono">{data.error}</span>
<p className="mt-1 text-muted-foreground">La clé se change dans <code>MISTRAL_API_KEY</code> (.env) puis redémarrage de l'API.</p>
</div>
)}
<datalist id="ai-models">{opts.map(m => <option key={m} value={m} />)}</datalist>
{field("Modèle de filtrage (rapide)", filter, setFilter)}
{field("Modèle de synthèse (qualité)", synthesis, setSynthesis)}
<div className="flex items-center gap-3">
<Button size="sm" onClick={save} disabled={busy}>Enregistrer</Button>
<span className="text-xs text-muted-foreground">{opts.length} modèle(s) accessible(s) avec la clé actuelle</span>
</div>
</div>
);
}
// ─── Composant principal Admin ────────────────────────────────────────────────
export default function Admin() {
const { authed, login, logout } = useAdminAuth();
const { toast } = useToast();
const [activePanel, setActivePanel] = useState<"contributions" | "consultations" | "blacklist">("contributions");
const [activePanel, setActivePanel] = useState<"contributions" | "consultations" | "blacklist" | "ia">("contributions");
const [stats, setStats] = useState<Stats | null>(null);
const [list, setList] = useState<IdeaList | null>(null);
const [status, setStatus] = useState("all");
@@ -875,6 +940,16 @@ export default function Admin() {
>
<ShieldBan className="h-3 w-3" /> Blacklist
</button>
<button
onClick={() => setActivePanel("ia")}
className={`px-3 py-1 rounded-md text-xs font-medium transition-colors ${
activePanel === "ia"
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground"
}`}
>
IA
</button>
</div>
<div className="ml-auto flex items-center gap-2">
@@ -904,6 +979,8 @@ export default function Admin() {
<ConsultationsPanel />
)}
{activePanel === "ia" && <AiModelsPanel />}
{/* ─── Panel Blacklist IP ─── */}
{activePanel === "blacklist" && (
<BlacklistPanel />