Module 5 · Automatiser avec n8nEnviron 1 min
Le nœud Code : une ligne devient une requête
Entre la photo et l’appel, un nœud Code nommé « Compiler », en mode Run Once for Each Item, transforme chaque ligne en requête. Il réunit la charte, le gabarit et les trois fonctions des modules précédents. Le voici complet, prêt à coller :
javascript
// Nœud Code « Compiler », mode Run Once for Each Item
// Charte maison-zitoune 1.2, gabarit lifestyle 2.0 : recopiés depuis la bibliothèque
const MODELE = "gemini-nano-banana-2.1";
const MARQUE = {
marque: "Maison Zitoune",
version: "1.2",
genre: "photographie réaliste de terroir",
style: "authentique, généreux et sobre, couleurs naturelles, grain photographique léger",
palette: ["vert olive profond (#4B5320)", "or chaud (#C9A227)", "crème (#F3EBDD)", "brun du bois d'olivier"],
lumiere: "naturelle et douce, venant de la gauche, comme une fin d'après-midi d'automne",
composition: "sujet principal sur le tiers gauche, espace calme à droite pour le texte, peu d'objets",
matieres: ["bois d'olivier", "lin", "terre cuite", "verre"],
consignes: [
"Les couleurs dominantes restent celles de la palette.",
"Des mains de producteurs ou de cuisiniers au travail, jamais de visage en gros plan.",
],
};
const GABARITS = {
lifestyle: {
variables: ["produit", "photo_produit", "scene", "lieu", "moment"],
fiche: {
genre: "photographie de style de vie",
usage: "une publication Instagram et Facebook",
sujet: "{produit}, identique à l'image 1",
action: "{scene}",
lieu: "{lieu}, {moment}",
composition: "plan moyen, le produit net au premier plan sur le tiers gauche, les gestes au centre",
optique: "objectif 35 mm, légère profondeur de champ",
references: [{ fichier: "{photo_produit}", role: "le produit à reproduire exactement, étiquette comprise" }],
consignes: ["Aucun texte en dehors de l'étiquette."],
format: { ratio: "4:5", taille: "2K" },
},
},
};
const CHAMPS_MARQUE = ["style", "palette", "lumiere"];
const LIBELLES = {
genre: "Type d'image", usage: "Usage", sujet: "Sujet", action: "Action", lieu: "Lieu",
composition: "Composition", optique: "Optique", lumiere: "Lumière", style: "Style",
palette: "Palette", matieres: "Matières",
};
function remplir(gabarit, ligne) {
const manquantes = gabarit.variables.filter((v) => !ligne[v]);
if (manquantes.length > 0) throw new Error(`Ligne ${ligne.id} : il manque ${manquantes.join(", ")}`);
const texte = JSON.stringify(gabarit.fiche).replace(/\{(\w+)\}/g, (trou, nom) =>
nom in ligne ? JSON.stringify(String(ligne[nom])).slice(1, -1) : trou,
);
return JSON.parse(texte);
}
function fusionner(marque, fiche) {
const interdits = CHAMPS_MARQUE.filter((c) => c in fiche);
if (interdits.length > 0) throw new Error(`Le gabarit modifie la charte : ${interdits.join(", ")}`);
const resultat = JSON.parse(JSON.stringify(marque));
for (const [cle, valeur] of Object.entries(fiche)) {
const actuel = resultat[cle];
if (Array.isArray(actuel) && Array.isArray(valeur)) resultat[cle] = [...actuel, ...valeur];
else if (actuel && typeof actuel === "object" && typeof valeur === "object") resultat[cle] = { ...actuel, ...valeur };
else resultat[cle] = valeur;
}
return resultat;
}
function compiler(fiche) {
const lignes = (fiche.references ?? []).map((r, i) => `Image ${i + 1} : ${r.role}.`);
for (const [cle, libelle] of Object.entries(LIBELLES)) {
const valeur = fiche[cle];
if (!valeur || valeur.length === 0) continue;
lignes.push(`${libelle} : ${Array.isArray(valeur) ? valeur.join(", ") : valeur}.`);
}
if (fiche.texte?.length) {
const textes = fiche.texte.map((t) => `« ${t.contenu} », ${t.emplacement}, ${t.typo}`);
lignes.push(`Texte à écrire exactement : ${textes.join(" ; ")}. Aucun autre texte.`);
}
if (fiche.consignes?.length) lignes.push(fiche.consignes.join(" "));
if (fiche.format) lignes.push(`Format ${fiche.format.ratio}.`);
return lignes.join("\n");
}
const ligne = $("Lire la feuille").item.json;
const gabarit = GABARITS[ligne.gabarit];
if (!gabarit) throw new Error(`Ligne ${ligne.id} : gabarit inconnu « ${ligne.gabarit} »`);
const fiche = fusionner(MARQUE, remplir(gabarit, ligne));
return {
json: {
id: ligne.id,
body: {
model: MODELE,
store: false,
input: [
{ type: "text", text: compiler(fiche) },
{ type: "image", mime_type: "image/jpeg", data: $json.photo_b64 },
],
response_format: { type: "image", mime_type: "image/jpeg", aspect_ratio: fiche.format.ratio, image_size: fiche.format.taille },
},
},
};Les dernières instructions font le travail : retrouver la ligne lue par « Lire la feuille », choisir son gabarit, puis construire le corps de l’appel avec le texte compilé et la photo du produit en base64. Pour ajouter les gabarits packshot et story-titre, complétez l’objet GABARITS.
Une copie, une version notéeLa charte et le gabarit sont recopiés dans le nœud depuis la bibliothèque, qui reste la référence. Gardez leurs numéros de version en tête du code, et recopiez-les à chaque nouvelle version.