Amélioration

This commit is contained in:
ocroguennec committed 2026-09-27 11:38:45 +02:00
1 parent add66a9310
commit e0145c8727
46 files changed
+305

No files matched your search

@@ -7,6 +7,10 @@ import { fileURLToPath } from 'url';
import sharp from 'sharp';
import db from '../db/index.js';
import { HttpError } from '../middleware/errorHandler.js';
import { getDefaultActiveProvider } from '../ai/providerConfig.js';
import { decryptSecret } from '../ai/crypto.js';
import { lookupInstitution } from '../ai/providers/anthropic.js';
import { audit } from '../utils/audit.js';
/**
* routes/institutionsReferentiel.js — Référentiel admin des institutions financières
@@ -281,4 +285,68 @@ router.delete('/:id/icone', (req, res, next) => {
} catch (e) { next(e); }
});
// ── POST /api/institutions-referentiel/ai-lookup ────────────────────────────
// Icône "baguette magique" d'InstitutionModal (demande Olivier 27/09/26) : recherche
// web automatique via le connecteur IA (Admin > Intelligence artificielle), pour
// pré-remplir au minimum description + domiciliation depuis le nom (et l'URL si
// déjà saisie) tapés dans le formulaire — fonctionne aussi bien en création (avant
// tout enregistrement) qu'en édition. Réponse toujours 200 : { ok:false, error }
// n'est pas une erreur HTTP mais un échec applicatif (IA désactivée, aucun
// fournisseur actif, type de fournisseur non pris en charge...) — même précédent
// que POST /providers/:id/test dans adminAi.js.
const AiLookupSchema = z.object({
nom: z.string().min(1).max(200),
url: z.string().url().optional().or(z.literal('')).nullable(),
});
router.post('/ai-lookup', async (req, res, next) => {
try {
const { nom, url } = AiLookupSchema.parse(req.body);
const config = db.prepare('SELECT actif FROM ai_config WHERE id = 1').get();
if (!config?.actif) {
return res.json({ ok: false, error: "L'Assistant IA n'est pas activé (Admin > Intelligence artificielle)." });
}
const provider = getDefaultActiveProvider();
if (!provider) {
return res.json({ ok: false, error: "Aucun fournisseur IA actif n'est configuré." });
}
// Seul le fournisseur "anthropic" (natif) expose l'outil de recherche web
// serveur utilisé par lookupInstitution() — un fournisseur openai_compatible
// n'a aucune garantie d'accès web réel selon le modèle/service derrière
// base_url, on préfère donc refuser explicitement plutôt que de renvoyer une
// réponse non vérifiée présentée comme une recherche internet.
if (provider.type !== 'anthropic') {
return res.json({
ok: false,
error: `La recherche web automatique n'est prise en charge que pour un fournisseur Anthropic (natif) — le fournisseur actif ("${provider.nom}") est de type "${provider.type}".`,
});
}
let apiKey;
try {
apiKey = decryptSecret(provider.api_key_chiffree);
} catch (e) {
throw new HttpError(500, `Impossible de déchiffrer la clé API : ${e.message}`);
}
try {
const result = await lookupInstitution({ apiKey, model: provider.modele, nom, url: url || undefined });
audit(req, {
action: 'institution_ai_lookup', category: 'ai', actorId: req.user.id,
details: { nom, ok: true, dureeMs: result.durationMs, sourcesCount: result.sourcesCount },
});
res.json({ ok: true, description: result.description, domiciliation: result.domiciliation, dureeMs: result.durationMs });
} catch (e) {
audit(req, {
action: 'institution_ai_lookup', category: 'ai', actorId: req.user.id,
details: { nom, ok: false, error: e.message },
});
res.json({ ok: false, error: e.message });
}
} catch (e) { next(e); }
});
export default router;