Correctif Bug activation IA

This commit is contained in:
ocroguennec committed 2026-09-05 15:23:14 +02:00
1 parent b3f5f80d2d
commit 8d104f2058
3 files changed
+55 -5

No files matched your search

+11 -2
View File
@@ -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); }
});
+5 -2
View File
@@ -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); }
+39 -1
View File
@@ -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 }) {
)}
</div>
{aiConfig && !aiAvailable && (
<div style={{
display: 'flex', alignItems: 'center', gap: 8, margin: '0 20px 12px',
padding: '8px 14px', borderRadius: 8, fontSize: 12.5,
background: 'var(--surface-2)', color: 'var(--text-muted)', border: '1px solid var(--border)',
}}>
<IcoSparkle />
{aiUnavailableReason}
</div>
)}
{/* Tableau */}
{filtered.length === 0 ? (
<p style={{ color: 'var(--text-muted)', textAlign: 'center', padding: '40px 20px' }}>
@@ -692,7 +729,8 @@ export default function UsersSection({ currentUserId }) {
<AiToggle
checked={!!u.ai_enabled}
onClick={() => 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")}
/>
</td>
<td>