From 8d104f205885781bb561e7203297519a17663f9b Mon Sep 17 00:00:00 2001 From: Olivier Date: Sat, 5 Sep 2026 15:23:14 +0200 Subject: [PATCH] Correctif Bug activation IA --- backend/src/routes/adminAi.js | 13 ++++++-- frontend/src/pages/AdminIA.jsx | 7 ++-- frontend/src/pages/admin/UsersSection.jsx | 40 ++++++++++++++++++++++- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/backend/src/routes/adminAi.js b/backend/src/routes/adminAi.js index 95cd651..88c8e85 100644 --- a/backend/src/routes/adminAi.js +++ b/backend/src/routes/adminAi.js @@ -260,8 +260,17 @@ router.patch('/config', (req, res, next) => { body.quotaQuotidien !== undefined ? body.quotaQuotidien : row.quota_quotidien, ); - audit(req, { action: 'ai_config_updated', category: 'ai', actorId: req.user.id, details: body }); - res.json({ ok: true }); + // Desactivation globale (transition actif -> inactif) : on retire aussi + // l'acces individuel de chaque utilisateur plutot que de le laisser + // "vrai" en base mais inoperant, ce qui afficherait un interrupteur + // actif mais sans effet dans le panneau Utilisateurs. + let usersReset = 0; + if (body.actif === false && row.actif) { + usersReset = db.prepare('UPDATE users SET ai_enabled = 0 WHERE ai_enabled = 1').run().changes; + } + + audit(req, { action: 'ai_config_updated', category: 'ai', actorId: req.user.id, details: { ...body, usersReset } }); + res.json({ ok: true, usersReset }); } catch (e) { next(e); } }); diff --git a/frontend/src/pages/AdminIA.jsx b/frontend/src/pages/AdminIA.jsx index 3aecac4..e6436cf 100644 --- a/frontend/src/pages/AdminIA.jsx +++ b/frontend/src/pages/AdminIA.jsx @@ -356,8 +356,11 @@ function ConfigSection() { const save = async (next) => { setSaving(true); try { - await api.patch('/admin/ai/config', next); - setBanner({ ok: true, msg: 'Configuration enregistrée.' }); + const result = await api.patch('/admin/ai/config', next); + const msg = result?.usersReset + ? `Configuration enregistrée. Accès IA retiré à ${result.usersReset} utilisateur${result.usersReset > 1 ? 's' : ''} (Assistant IA désactivé globalement).` + : 'Configuration enregistrée.'; + setBanner({ ok: true, msg }); await load(); } catch (e) { setBanner({ ok: false, msg: e.message }); } finally { setSaving(false); } diff --git a/frontend/src/pages/admin/UsersSection.jsx b/frontend/src/pages/admin/UsersSection.jsx index c326096..d94b46b 100644 --- a/frontend/src/pages/admin/UsersSection.jsx +++ b/frontend/src/pages/admin/UsersSection.jsx @@ -428,6 +428,11 @@ export default function UsersSection({ currentUserId }) { const [openMenu, setOpenMenu] = useState(null); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); + // null tant que non charge (voir l'effet ci-dessous) : l'interrupteur + // individuel de chaque utilisateur n'a d'effet que si l'IA est activee + // globalement ET qu'un fournisseur actif est configure (seule source de + // verite : aiAccess.js cote backend). + const [aiConfig, setAiConfig] = useState(null); const load = useCallback(async (initial = false) => { if (initial) setInitialLoading(true); @@ -437,6 +442,16 @@ export default function UsersSection({ currentUserId }) { }, []); useEffect(() => { load(true); }, [load]); + useEffect(() => { + let cancelled = false; + Promise.all([api.get('/admin/ai/config'), api.get('/admin/ai/providers')]) + .then(([cfg, provs]) => { + if (cancelled) return; + setAiConfig({ actif: !!cfg.actif, hasActiveProvider: provs.some(p => p.actif) }); + }) + .catch(() => { if (!cancelled) setAiConfig({ actif: false, hasActiveProvider: false }); }); + return () => { cancelled = true; }; + }, []); useEffect(() => { setPage(1); }, [search, filterStatus, filterRole]); useEffect(() => { if (!openMenu) return; @@ -458,6 +473,17 @@ export default function UsersSection({ currentUserId }) { const safePage = Math.min(page, totalPages); const paged = filtered.slice((safePage - 1) * pageSize, safePage * pageSize); + // true tant que aiConfig n'est pas encore charge : le vrai garde-fou reste + // cote serveur (getAiAccessStatus), ceci n'est qu'une aide visuelle. + const aiAvailable = !aiConfig || (aiConfig.actif && aiConfig.hasActiveProvider); + const aiUnavailableReason = !aiConfig + ? '' + : !aiConfig.actif + ? "L'Assistant IA est desactive globalement. Activez-le dans Assistant IA > Configuration avant d'accorder l'acces a un utilisateur." + : !aiConfig.hasActiveProvider + ? "Aucun fournisseur IA actif n'est configure. Configurez-en un dans Assistant IA > Fournisseurs avant d'accorder l'acces a un utilisateur." + : ''; + // ── Actions ─────────────────────────────────────────────────────────── const confirm = (title, message, onConfirm, confirmLabel) => setConfirmAction({ title, message, onConfirm, confirmLabel }); @@ -639,6 +665,17 @@ export default function UsersSection({ currentUserId }) { )} + {aiConfig && !aiAvailable && ( +
+ + {aiUnavailableReason} +
+ )} + {/* Tableau */} {filtered.length === 0 ? (

@@ -692,7 +729,8 @@ export default function UsersSection({ currentUserId }) { toggleAiAccess(u)} - title={u.ai_enabled ? "Désactiver l'accès à l'Assistant IA" : "Activer l'accès à l'Assistant IA"} + disabled={!aiAvailable} + title={!aiAvailable ? aiUnavailableReason : (u.ai_enabled ? "Désactiver l'accès à l'Assistant IA" : "Activer l'accès à l'Assistant IA")} />