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

+62
View File
@@ -161,3 +161,65 @@ export async function testConnection({ apiKey, model }) {
.trim() || '(réponse vide)';
return { reply, durationMs: Date.now() - started, usage: message.usage };
}
/**
* lookupInstitution() — recherche web automatique (outil natif Anthropic
* "web_search", exécuté côté serveur Anthropic) pour pré-remplir une fiche
* institution depuis l'admin (icône "baguette magique", InstitutionModal —
* demande Olivier 27/09/26) : au minimum une description succincte et le
* pays de domiciliation (code ISO 3166-1 alpha-2, cf. CountrySelect.jsx
* côté frontend).
*
* Contrairement à testConnection() (ping minimal, sans outil), cet appel
* n'est pas en streaming : pour un outil serveur, Anthropic exécute la/les
* recherche(s) et le tour de lecture des résultats en interne, la réponse
* finale (texte, éventuellement précédée de blocs server_tool_use /
* web_search_tool_result) arrivant en un seul message — donc pas de boucle
* côté client comme dans streamChat(), mais un appel qui peut prendre
* plusieurs secondes.
*
* @param {object} opts
* @param {string} opts.apiKey
* @param {string} opts.model
* @param {string} opts.nom Nom de l'institution à rechercher
* @param {string} [opts.url] URL du site officiel, si déjà connue (aide la recherche)
* @returns {Promise<{description:string, domiciliation:string|null, durationMs:number, sourcesCount:number}>}
*/
export async function lookupInstitution({ apiKey, model, nom, url }) {
const anthropic = new Anthropic({ apiKey });
const started = Date.now();
const prompt = [
`Recherche sur le web des informations fiables et à jour sur l'institution financière suivante : "${nom}"${url ? ` (site officiel présumé : ${url})` : ''}.`,
`C'est potentiellement une banque, néobanque, courtier, assureur, société de gestion ou établissement de paiement.`,
`Réponds UNIQUEMENT avec un objet JSON valide, sans aucun texte autour, au format exact :`,
`{"description": "<description succincte en français, 1 à 3 phrases : activité, statut/régulateur si pertinent>", "domiciliation": "<code pays ISO 3166-1 alpha-2 du siège social, ex. FR, LU, DE — null si inconnu>"}`,
].join('\n');
const message = await anthropic.messages.create({
model,
max_tokens: 1024,
tools: [{ type: 'web_search_20260318', name: 'web_search', max_uses: 4 }],
messages: [{ role: 'user', content: prompt }],
});
const textBlocks = (message.content || []).filter((b) => b.type === 'text').map((b) => b.text);
const fullText = textBlocks.join('\n').trim();
const sourcesCount = (message.content || []).filter((b) => b.type === 'web_search_tool_result').length;
let parsed;
try {
const jsonMatch = fullText.match(/\{[\s\S]*\}/);
parsed = JSON.parse(jsonMatch ? jsonMatch[0] : fullText);
} catch (e) {
throw new Error(`Réponse du modèle non exploitable (JSON attendu) : ${fullText.slice(0, 200) || '(vide)'}`);
}
const description = typeof parsed.description === 'string' ? parsed.description.trim() : '';
let domiciliation = typeof parsed.domiciliation === 'string' ? parsed.domiciliation.trim().toUpperCase() : null;
if (domiciliation && !/^[A-Z]{2}$/.test(domiciliation)) domiciliation = null;
if (!description) throw new Error("Le modèle n'a renvoyé aucune description exploitable.");
return { description, domiciliation, durationMs: Date.now() - started, sourcesCount };
}
@@ -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;
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with Inkscape (http://www.inkscape.org/) by Marsupilami -->
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg789"
version="1.1"
width="1024"
height="404"
viewBox="-3.2371623 -3.2371623 289.9450246 114.3797346">
<defs
id="defs786" />
<path
id="path668"
d="m 275.83984,0 c -4.79,0 -7.62891,3.35 -7.62891,7 0,3.65 2.57891,7 7.62891,7 5.05,0 7.63086,-3.32 7.63086,-7 0,-3.63 -2.84086,-7 -7.63086,-7 z M 24.4707,1 0,59.7793 H 13.85156 L 18.64062,48.34961 H 40.36133 L 45.15039,59.7793 H 58.99023 L 34.4707,1 Z m 225.09961,0 v 15.61914 a 25.56,25.56 0 0 0 -6.5,-0.85938 c -8.43,0 -13,4.00016 -15,5.91016 a 22.84,22.84 0 0 0 -6.36914,15.89062 c -0.07,14.13 7.65023,19.73024 11.49023,21.49024 a 17.61,17.61 0 0 0 8.26953,1.63867 15.33,15.33 0 0 0 8.75,-3.50976 l 0.39063,2.60937 h 12 V 1 Z m -47.23438,14.7539 c -2.15572,-0.005 -4.10437,0.32063 -5.48437,0.79688 a 16.93,16.93 0 0 0 -6.38086,3.75 l -0.5293,-3.52149 h -12 V 59.7793 h 13.13868 V 30.11914 a 20.65,20.65 0 0 1 3.56054,-2.50977 8,8 0 0 1 7.43946,-0.17969 c 2.3,1.29 3.36132,4.32 3.36132,8 v 24.39063 h 13.13868 V 34.52929 c 0.01,-9.26 -3.39969,-13.38015 -5.42969,-15.16015 -3.05625,-2.68125 -7.22158,-3.60684 -10.81446,-3.61524 z m -92.52539,0.006 a 27.77,27.77 0 0 0 -14.18945,4.45117 c -2.91,-3 -10.04992,-5.74164 -15.91992,-3.68164 a 17,17 0 0 0 -6.38086,3.75 L 72.86132,16.76967 H 60.79101 v 43 H 73.93164 V 30.0001 a 14.17,14.17 0 0 1 3.47851,-2.41016 c 1.47,-0.71 5.10047,-1.99906 7.73047,0.21094 2,1.71 2.11914,5.7389 2.11914,6.8789 v 25.08009 h 13 V 34.0001 a 29.55,29.55 0 0 0 -0.42968,-4.66993 24.52,24.52 0 0 1 3.86132,-2 c 3,-1.33 6.39953,-0.67062 7.51953,0.10938 2.41,1.68 2.65039,5.4907 2.65039,7.2207 v 25.08008 h 13 V 34.19932 c 0,-4.7 -0.66085,-10.61946 -4.88085,-14.43946 -4.22,-3.82 -10.79993,-4 -12.16993,-4 z m 21.91993,1.00977 v 25.42969 c 0,4.77 0.85085,10.00062 4.13085,13.64062 3.28001,3.64 8.55993,5.10953 13.41993,4.76954 a 16.84,16.84 0 0 0 6.43945,-1.78907 14.55,14.55 0 0 0 3.86914,-2.7207 l 0.56055,3.68945 h 12 V 16.78916 l 0.041,-0.0195 h -13.14062 v 29.79101 a 22.79,22.79 0 0 1 -3.42969,2.13868 10.18,10.18 0 0 1 -7.34961,0.43164 c -3,-1.14 -3.41015,-6.00078 -3.41016,-8.05078 V 16.76966 Z m 137.58007,0 V 59.7794 h 13.05078 V 16.76963 Z m -239.83984,2.09957 7,18.33008 h -14 z M 244.00586,26.629 a 9.39,9.39 0 0 1 0.61523,0.002 15.47,15.47 0 0 1 5,0.88867 l -0.0508,19.25977 a 7.4,7.4 0 0 1 -3.19922,2.25 c -4.54,1.86 -10.34055,-0.23 -11.56055,-8 a 15.26,15.26 0 0 1 0.63086,-8.14844 9.39,9.39 0 0 1 8.56446,-6.25195 z"
style="fill:#0c1c49" />
<rect
style="fill:#0c1c49"
id="rect682"
height="11.07"
width="281.85999"
y="70.849991"
x="0.51071012"
class="cls-1" />
<path
id="path686"
d="m 81.51562,92.6875 a 7.22,7.22 0 0 0 -1.43554,0.14258 c -1.94,0.29 -3.81969,1.44976 -4.17969,3.50976 a 3.94,3.94 0 0 0 1.2207,3.75 11.13,11.13 0 0 0 3.85938,1.73047 7.75,7.75 0 0 1 1.7207,0.70899 1.18,1.18 0 0 1 0.48047,1.55078 c -0.29,0.53 -1.09063,0.66015 -1.64063,0.66015 a 3.76,3.76 0 0 1 -2,-0.4707 4.41,4.41 0 0 1 -1.26953,-1.04687 l -2.61133,2.01757 a 6.27,6.27 0 0 0 2.30078,1.83985 c 3.27,1.62 9.23969,0.70039 9.17969,-3.84961 a 4.09,4.09 0 0 0 -1.7207,-3.41016 15.09,15.09 0 0 0 -4,-1.59961 3.5,3.5 0 0 1 -1.61914,-0.88086 1,1 0 0 1 0.34961,-1.45898 2.91,2.91 0 0 1 1.91015,-0.18164 3.37,3.37 0 0 1 1.75,1 4.59,4.59 0 0 1 0.43946,0.54101 l 2.61132,-2 A 5.56,5.56 0 0 0 86.08008,94.32031 7.22,7.22 0 0 0 81.51562,92.6875 Z m -3.24414,10.53516 0.0684,-0.0527 -0.0996,0.0195 a 4.41,4.41 0 0 0 0.0312,0.0332 z M 94.23437,92.6875 a 7.22,7.22 0 0 0 -1.43359,0.14258 c -1.94,0.29 -3.81969,1.44976 -4.17969,3.50976 a 3.94,3.94 0 0 0 1.21875,3.75 11.13,11.13 0 0 0 3.86133,1.73047 7.75,7.75 0 0 1 1.71875,0.70899 1.18,1.18 0 0 1 0.48047,1.55078 c -0.29,0.54 -1.09063,0.66015 -1.64063,0.66015 a 3.75,3.75 0 0 1 -2,-0.4707 4.4,4.4 0 0 1 -1.27344,-1.05273 l -2.60546,2.01367 a 6.27,6.27 0 0 0 2.30078,1.83984 c 3.27,1.62 9.23969,0.70039 9.17968,-3.84961 a 4.09,4.09 0 0 0 -1.7207,-3.41015 15.06,15.06 0 0 0 -4,-1.59961 3.49,3.49 0 0 1 -1.61914,-0.88086 1,1 0 0 1 0.34961,-1.46094 2.9,2.9 0 0 1 1.91016,-0.17969 3.36,3.36 0 0 1 1.75,1 4.51,4.51 0 0 1 0.43945,0.54102 l 2.60938,-2 a 5.55,5.55 0 0 0 -0.7793,-0.91016 7.22,7.22 0 0 0 -4.56641,-1.63281 z m -3.24805,10.5293 0.0742,-0.0566 -0.0996,0.0293 a 4.4,4.4 0 0 0 0.0254,0.0274 z M 202.25976,92.74609 c -0.26877,0.01 -0.53633,0.032 -0.79883,0.0645 a 6.15,6.15 0 0 0 -4.83984,3.3789 9.58,9.58 0 0 0 -0.87109,4.78125 8,8 0 0 0 1.45117,4.42969 c 1.55,2.04 3.83039,2.64047 6.40039,2.48047 A 16.05,16.05 0 0 0 208.30078,107 v -7.41992 h -5.7207 v 2.88086 l 0.0215,0.0586 h 2.06836 v 2.12109 a 5.73,5.73 0 0 1 -2.71875,-0.10156 c -0.61,-0.25 -2.43141,-1.14937 -2.19141,-4.85937 a 4.41,4.41 0 0 1 1.15039,-3 2.4,2.4 0 0 1 3.08985,-0.21875 3.82,3.82 0 0 1 0.93164,1.28906 l 3.25,-1.66016 a 6.52,6.52 0 0 0 -0.85156,-1.39062 c -1.21626,-1.505 -3.18893,-2.02149 -5.07032,-1.95313 z M 65.21093,93 l -5.64062,14.51953 3.96094,0.0312 1,-2.5 h 4.8789 l 0.94141,2.5 h 4.17969 L 68.85156,93 Z m 36.98047,0 v 14.51953 h 10.79883 v -3.34961 h -6.90039 v -2.36914 h 5.25 v -3.07031 h -5.25 v -2.40039 h 6.45117 V 93 Z m 11.93946,0 v 3.33008 h 4.07031 v 11.18945 h 3.92969 V 96.33008 h 4 V 93 Z m 18.33984,0 v 14.51953 l 3.74023,0.01 v -7.13868 l 3,7.22852 h 1.58985 l 2.90039,-7.18945 v 7.09961 h 3.76953 V 93 h -4 l -3.38086,8.65039 L 136.4707,93 Z m 22.4707,0 -5.64062,14.51953 3.9707,0.0312 1,-2.5 h 4.86914 l 0.93946,2.5 h 4.17968 L 158.58008,93 Z m 11.10938,0 v 14.51953 l 3.81054,0.01 V 99.32054 L 175,107.52953 h 3.55078 V 93 h -3.83008 v 7.68945 L 169.99023,93 Z m 19.92969,0 -5.65039,14.51953 3.9707,0.0312 1,-2.5 h 4.88086 l 0.93945,2.5 h 4.17969 L 189.62109,93 Z m 25.13085,0 v 14.51953 h 10.8086 v -3.34961 h -6.89844 v -2.36914 h 5.25 v -3.07031 h -5.25 v -2.40039 h 6.44922 V 93 Z m 13.24024,0 v 14.51953 l 3.81836,0.01 v -7.13868 l 3,7.22852 h 1.55078 l 2.90039,-7.18945 v 7.09961 h 3.76953 V 93 h -4 L 231.9707,101.65039 228.35156,93 Z m 18.30859,0 v 14.51953 h 10.81055 v -3.34961 h -6.91016 v -2.36914 h 5.25 v -3.07031 h -5.25 v -2.40039 h 6.44922 V 93 Z m 13.25,0 v 14.51953 l 3.81055,0.01 v -8.20899 l 5.14062,8.20899 h 3.54883 V 93 h -3.83007 v 7.68945 L 259.85156,93 Z m 14.44141,0 v 3.33008 h 4.06836 v 11.18945 h 3.93164 V 96.33008 h 4 V 93 Z m -203.40039,4.63086 1.57031,4.41992 h -3.14062 z m 89.73047,0 1.56836,4.41992 h -3.13868 z m 31.03906,0 1.57031,4.41992 h -3.14062 z"
style="fill:#009fe3" />
</svg>
<!-- version: 20171223, original size: 283.4707 107.90541, border: 3% -->

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

+90
View File
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 1000 269.23077"
version="1.1"
id="svg331"
sodipodi:docname="A.svg"
width="1000"
height="269.23077"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata337">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs335" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#111111"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1017"
id="namedview333"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="0.15913462"
inkscape:cx="1067.5699"
inkscape:cy="-1171.2951"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg331" />
<path
d="m 290,123.84615 c 0,7.69231 0.76923,15.38462 2.30769,21.53847 1.53846,6.92307 3.84616,12.30769 7.69231,17.6923 3.84615,4.61539 7.69231,9.23077 13.84615,11.53846 6.15385,2.3077 12.3077,4.61539 20,4.61539 7.69231,0 13.84616,-1.53846 20.76923,-3.84615 6.15385,-2.3077 10.76924,-5.38462 14.61539,-7.69231 V 71.53846 C 365.38462,70 361.53846,70 356.92308,69.23077 c -4.61539,-0.76923 -10.76923,-0.76923 -18.46154,-0.76923 -14.61539,0 -26.15385,4.61538 -35.38462,14.61538 C 294.61538,92.30769 290,106.15385 290,123.84615 Z M 266.92308,90 c 3.84615,-10 8.46154,-18.46154 15.38461,-25.38461 6.92308,-6.92308 14.61539,-12.3077 23.84616,-16.15385 9.23077,-2.30769 20,-4.61539 31.53846,-4.61539 12.30769,0 23.84615,0.76924 33.84615,3.07693 10,2.30769 17.69231,3.84615 23.84616,5.38461 v 212.30769 l -26.92308,4.61539 v -76.92308 c -3.07692,2.30769 -8.46154,4.61539 -15.38462,6.92308 -6.92307,2.30769 -14.61538,3.84615 -23.07692,3.84615 -10.76923,0 -20.76923,-2.30769 -29.23077,-6.15384 -8.46154,-3.84616 -15.38461,-9.23077 -21.53846,-16.15385 -6.15385,-6.92308 -10,-15.38461 -13.07692,-25.38461 -3.07693,-10 -4.61539,-20 -4.61539,-31.53847 0,-12.30769 1.53846,-23.84615 5.38462,-33.84615 z m 300.76923,33.07692 c 0,12.3077 -1.53846,23.07693 -5.38462,33.07693 -3.84615,10 -8.46154,18.46153 -14.61538,25.38461 -6.15385,6.92308 -13.84616,12.30769 -22.30769,16.15385 -8.46154,3.84615 -18.46154,5.38461 -28.46154,5.38461 -10,0 -20,-1.53846 -28.46154,-5.38461 C 460,193.84615 452.30769,188.46154 446.15385,181.53846 440,174.61538 434.61538,166.15385 431.53846,156.15385 c -3.84615,-10 -5.38461,-20.76923 -5.38461,-33.07693 0,-11.53846 1.53846,-23.07692 5.38461,-32.30769 3.84616,-10 8.46154,-18.46154 14.61539,-25.38461 6.15384,-6.92308 13.84615,-12.3077 22.30769,-16.15385 8.46154,-3.84615 18.46154,-5.38462 28.46154,-5.38462 10,0 20,1.53847 28.46154,5.38462 8.46153,3.84615 16.15384,9.23077 22.30769,16.15385 6.15384,6.92307 11.53846,15.38461 14.61538,25.38461 3.84616,10 5.38462,20.76923 5.38462,32.30769 z m -28.46154,0 c 0,-16.92307 -3.84615,-30.76923 -11.53846,-40.76923 -7.69231,-10 -18.46154,-15.38461 -31.53846,-15.38461 -13.07693,0 -23.84616,5.38461 -31.53847,15.38461 -7.6923,10 -10.76923,23.84616 -10.76923,40.76923 0,16.92308 3.84616,30.76923 11.53847,40.76923 7.6923,10 18.46153,15.38462 31.53846,15.38462 13.07692,0 23.84615,-5.38462 31.53846,-15.38462 6.92308,-10 10.76923,-23.07692 10.76923,-40.76923 z m 58.46154,-71.53846 c 6.15384,-1.53846 14.61538,-3.07692 24.61538,-4.61538 10,-1.53846 22.30769,-2.30769 35.38462,-2.30769 12.30769,0 22.30769,1.53846 30,5.38461 7.69231,3.07692 14.61538,8.46154 19.23077,14.61539 5.38461,4.61538 8.46154,12.30769 10.76923,20.76923 2.30769,8.46153 3.07692,17.6923 3.07692,28.46153 v 85.38462 H 693.84615 V 120 c 0,-9.23077 -0.76923,-17.69231 -1.53846,-23.84615 C 691.53846,90 689.23077,83.84615 686.15385,80 683.07692,76.15385 679.23077,73.07692 674.61538,70.76923 670,68.46154 663.84615,67.69231 656.15385,67.69231 h -9.23077 c 0,0 -6.92308,0.76923 -10,1.53846 C 633.84615,70 631.53846,70 629.23077,70 c -2.30769,0.76923 -3.84615,0.76923 -4.61539,0.76923 V 199.23077 H 597.69231 Z M 782.30769,46.92308 H 840 V 70 h -57.69231 v 70 c 0,7.69231 0.76923,13.84615 1.53846,19.23077 1.53847,4.61538 3.07693,9.23077 5.38462,11.53846 2.30769,3.07692 5.38461,4.61539 8.46154,6.15385 3.84615,1.53846 7.69231,1.53846 12.30769,1.53846 8.46154,0 14.61538,-0.76923 20,-3.07692 4.61538,-1.53847 8.46154,-3.07693 10,-3.84616 l 5.38462,22.30769 c -3.07693,1.53847 -7.69231,3.07693 -14.61539,5.38462 -6.92308,2.30769 -14.61538,3.07692 -23.07692,3.07692 -10,0 -19.23077,-1.53846 -25.38462,-3.84615 -6.92307,-2.30769 -12.30769,-6.92308 -16.15384,-11.53846 -3.84616,-5.38462 -6.92308,-11.53846 -8.46154,-19.23077 C 756.15385,160 755.38462,150.76923 755.38462,140.76923 V 4.61539 L 782.30769,0 Z M 1000,123.07692 c 0,12.3077 -1.53846,23.07693 -5.38462,33.07693 -3.84615,10 -8.46153,18.46153 -14.61538,25.38461 -6.15385,6.92308 -13.84615,12.30769 -22.30769,16.15385 -8.46154,3.84615 -18.46154,5.38461 -28.46154,5.38461 -10,0 -20,-1.53846 -28.46154,-5.38461 -8.46154,-3.84616 -16.15385,-9.23077 -22.30769,-16.15385 -6.15385,-6.92308 -11.53846,-15.38461 -14.61539,-25.38461 -3.84615,-10 -5.38461,-20.76923 -5.38461,-33.07693 0,-11.53846 1.53846,-23.07692 5.38461,-32.30769 3.84616,-10 8.46154,-18.46154 14.61539,-25.38461 6.15384,-6.92308 13.84615,-12.3077 22.30769,-16.15385 8.46154,-3.84615 18.46154,-5.38462 28.46154,-5.38462 10,0 20,1.53847 28.46154,5.38462 8.46154,3.84615 16.15384,9.23077 22.30769,16.15385 6.15385,6.92307 11.53846,15.38461 14.61538,25.38461 3.84616,10 5.38462,20.76923 5.38462,32.30769 z m -28.46154,0 c 0,-16.92307 -3.84615,-30.76923 -11.53846,-40.76923 -7.69231,-10 -18.46154,-15.38461 -31.53846,-15.38461 -13.07692,0 -23.84616,5.38461 -31.53846,15.38461 -7.69231,10 -11.53846,23.84616 -11.53846,40.76923 0,16.92308 3.84615,30.76923 11.53846,40.76923 7.6923,10 18.46154,15.38462 31.53846,15.38462 13.07692,0 23.84615,-5.38462 31.53846,-15.38462 7.69231,-Line truncated
class="fill-logo-1"
id="path319"
inkscape:connector-curvature="0"
style="stroke-width:7.69230747;fill:#262a3e;fill-opacity:1" />
<g
id="g329"
transform="scale(7.6923077)"
style="fill:#6b5aed;fill-opacity:1">
<path
d="m 3.5,11.8 c 2.1,2.1 4.9,3.3 7.8,3.5 C 11.1,12.4 9.9,9.6 7.8,7.5 5.7,5.4 3,4.2 0,4 0.2,6.9 1.4,9.7 3.5,11.8 Z"
class="fill-logo-2"
id="path321"
inkscape:connector-curvature="0"
style="fill:#6b5aed;fill-opacity:1" />
<path
d="m 20.5,11.8 c -2.1,2.1 -4.9,3.3 -7.8,3.5 0.2,-2.9 1.4,-5.7 3.5,-7.8 C 18.2,5.4 21,4.2 24,4 23.8,6.9 22.6,9.7 20.5,11.8 Z"
class="fill-logo-2"
id="path323"
inkscape:connector-curvature="0"
style="fill:#6b5aed;fill-opacity:1" />
<path
d="m 20.5,20.1 c -2.1,-2.1 -4.9,-3.3 -7.8,-3.5 0.2,2.9 1.4,5.7 3.5,7.8 2.1,2.1 4.9,3.3 7.8,3.5 -0.2,-3 -1.4,-5.8 -3.5,-7.8 z"
class="fill-logo-2"
id="path325"
inkscape:connector-curvature="0"
style="fill:#6b5aed;fill-opacity:1" />
<path
d="M 3.5,20.1 C 5.6,18 8.4,16.8 11.4,16.6 11.2,19.5 10,22.3 7.9,24.4 5.7,26.5 3,27.7 0,27.9 c 0.2,-3 1.4,-5.8 3.5,-7.8 z"
class="fill-logo-2"
id="path327"
inkscape:connector-curvature="0"
style="fill:#6b5aed;fill-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+54
View File
@@ -46,6 +46,10 @@ function IconChevronLeft() {
return <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"/></svg>;
}
function IconWand() {
return <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72Z"/><path d="m14 7 3 3"/><path d="M5 6v4"/><path d="M19 14v4"/><path d="M10 2v2"/><path d="M7 8H3"/><path d="M21 16h-4"/><path d="M11 3H9"/></svg>;
}
/* ── Modale de création/édition d'une institution ────────────────
Formulaire simple à un seul écran (contrairement au RefModal des plateformes, à 5 étapes) :
une institution financière n'a qu'une poignée de champs, un wizard serait disproportionné. */
@@ -54,6 +58,9 @@ function InstitutionModal({ row, onClose, onSaved }) {
const [categories, setCategories] = useState([]);
const [err, setErr] = useState(null);
const [saving, setSaving] = useState(false);
const [aiAvailable, setAiAvailable] = useState(false);
const [aiLoading, setAiLoading] = useState(false);
const [aiMsg, setAiMsg] = useState(null); // { ok:boolean, text:string }
const [form, setForm] = useState({
nom: row?.nom || '',
@@ -67,8 +74,40 @@ function InstitutionModal({ row, onClose, onSaved }) {
api.get('/ref-categories-institutions').then(setCategories).catch(() => {});
}, []);
// Icône « baguette magique » (demande Olivier 27/09/26) : visible seulement si le
// connecteur IA est activé (Admin > Intelligence artificielle) — la disponibilité d'un
// fournisseur compatible (Anthropic natif) est vérifiée côté serveur lors de l'appel.
useEffect(() => {
api.get('/admin/ai/config').then(cfg => setAiAvailable(!!cfg?.actif)).catch(() => setAiAvailable(false));
}, []);
const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
const handleAiLookup = async () => {
if (!form.nom.trim()) { setAiMsg({ ok: false, text: 'Renseignez le nom avant de lancer la recherche.' }); return; }
setAiLoading(true); setAiMsg(null);
try {
const r = await api.post('/institutions-referentiel/ai-lookup', {
nom: form.nom.trim(),
url: form.url.trim() || undefined,
});
if (r.ok) {
setForm(f => ({
...f,
description: r.description || f.description,
domiciliation: r.domiciliation || f.domiciliation,
}));
setAiMsg({ ok: true, text: "Description et domiciliation renseignées automatiquement — vérifiez avant d'enregistrer." });
} else {
setAiMsg({ ok: false, text: r.error || 'Recherche IA indisponible.' });
}
} catch (ex) {
setAiMsg({ ok: false, text: ex.message });
} finally {
setAiLoading(false);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!form.nom.trim()) { setErr('Le nom est requis.'); return; }
@@ -113,6 +152,21 @@ function InstitutionModal({ row, onClose, onSaved }) {
<label>URL</label>
<input value={form.url} onChange={e => set('url', e.target.value)} placeholder="https://…" />
</div>
{aiAvailable && (
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<button type="button" onClick={handleAiLookup} disabled={aiLoading || !form.nom.trim()}
style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, padding: '5px 10px',
border: '1px solid var(--border)', borderRadius: 6,
cursor: (aiLoading || !form.nom.trim()) ? 'not-allowed' : 'pointer',
background: 'var(--surface-2)', color: 'var(--text)', opacity: (aiLoading || !form.nom.trim()) ? 0.6 : 1 }}
title="Rechercher automatiquement sur le web (description, domiciliation) via l'Assistant IA">
<IconWand /> {aiLoading ? 'Recherche en cours…' : "Compléter avec l'IA"}
</button>
{aiMsg && (
<span style={{ fontSize: 12, color: aiMsg.ok ? '#16a34a' : '#dc2626' }}>{aiMsg.text}</span>
)}
</div>
)}
<div>
<label>Domiciliation</label>
<CountrySelect value={form.domiciliation} onChange={code => set('domiciliation', code)} showCode />