fix(selective): Use specific keywords (resolvedContent) instead of general keyword
🐛 Problème identifié: - Le système utilisait toujours csvData.mc0 (mot-clé général) pour tous les éléments - Exemple: "plaque numero de maison" au lieu de "Plaque en PVC : économique et léger" ✅ Solution: - Ajout paramètre specificKeyword à createTypedPrompt() - Extraction de item.originalElement.resolvedContent avant génération - Pas de fallback sur csvData.mc0 (log d'erreur si manquant) - Détection des variables non résolues "[Mc+1_5 non résolu]" 📍 Fichier modifié: - lib/selective-enhancement/SelectiveUtils.js:603-615 (createTypedPrompt) - lib/selective-enhancement/SelectiveUtils.js:1083-1091 (extraction + appel) 🎯 Impact: - Chaque élément utilise maintenant son mot-clé spécifique - Prompts LLM plus précis et contextualisés - Meilleure qualité de contenu généré 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
db966a4ad6
commit
957df21e18
@ -6,6 +6,32 @@
|
|||||||
|
|
||||||
const { logSh } = require('../ErrorReporting');
|
const { logSh } = require('../ErrorReporting');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UTILITAIRE: Afficher liste complète des éléments (réutilisable)
|
||||||
|
*/
|
||||||
|
function logElementsList(elements, title = 'LISTE DES ÉLÉMENTS', generatedKeywords = null) {
|
||||||
|
logSh(`\n📋 === ${title} (${elements.length} éléments) ===`, 'INFO');
|
||||||
|
|
||||||
|
elements.forEach((el, idx) => {
|
||||||
|
// Déterminer la source si generatedKeywords fourni
|
||||||
|
let source = '📋 GSheet';
|
||||||
|
if (generatedKeywords) {
|
||||||
|
const isGenerated = generatedKeywords[el.name] ||
|
||||||
|
(generatedKeywords._subVariables && Object.keys(generatedKeywords._subVariables).some(k => k.startsWith(el.name + '_')));
|
||||||
|
source = isGenerated ? '🤖 IA' : '📋 GSheet';
|
||||||
|
}
|
||||||
|
|
||||||
|
logSh(` [${idx + 1}] ${source} ${el.name}`, 'INFO');
|
||||||
|
logSh(` 📄 resolvedContent: "${el.resolvedContent}"`, 'INFO');
|
||||||
|
if (el.instructions) {
|
||||||
|
const instPreview = el.instructions.length > 100 ? el.instructions.substring(0, 100) + '...' : el.instructions;
|
||||||
|
logSh(` 📜 instructions: "${instPreview}"`, 'INFO');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logSh(`=========================================\n`, 'INFO');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ANALYSEURS DE CONTENU SELECTIVE
|
* ANALYSEURS DE CONTENU SELECTIVE
|
||||||
*/
|
*/
|
||||||
@ -572,9 +598,20 @@ function detectLengthConstraintInInstruction(instruction) {
|
|||||||
/**
|
/**
|
||||||
* Créer un prompt adapté au type d'élément avec contraintes de longueur (legacy logic)
|
* Créer un prompt adapté au type d'élément avec contraintes de longueur (legacy logic)
|
||||||
* @param {string} associatedTitle - Titre généré précédemment pour les textes/intros (important pour cohérence)
|
* @param {string} associatedTitle - Titre généré précédemment pour les textes/intros (important pour cohérence)
|
||||||
|
* @param {string} specificKeyword - Mot-clé spécifique de l'élément (resolvedContent) au lieu du mot-clé général
|
||||||
*/
|
*/
|
||||||
function createTypedPrompt(tag, type, instruction, csvData, associatedTitle = null) {
|
function createTypedPrompt(tag, type, instruction, csvData, associatedTitle = null, specificKeyword = null) {
|
||||||
const keyword = csvData.mc0 || '';
|
// 🔥 FIX: Utiliser UNIQUEMENT le mot-clé spécifique de l'élément (resolvedContent)
|
||||||
|
// PAS de fallback sur csvData.mc0
|
||||||
|
let keyword;
|
||||||
|
if (specificKeyword) {
|
||||||
|
keyword = specificKeyword;
|
||||||
|
logSh(` 🎯 Mot-clé SPÉCIFIQUE utilisé: "${specificKeyword}"`, 'INFO');
|
||||||
|
} else {
|
||||||
|
logSh(` ❌ ERREUR: Aucun mot-clé spécifique (resolvedContent) fourni pour ${tag}`, 'ERROR');
|
||||||
|
keyword = ''; // Pas de fallback
|
||||||
|
}
|
||||||
|
|
||||||
const title = csvData.t0 || '';
|
const title = csvData.t0 || '';
|
||||||
const personality = csvData.personality;
|
const personality = csvData.personality;
|
||||||
|
|
||||||
@ -687,10 +724,26 @@ function createTypedPrompt(tag, type, instruction, csvData, associatedTitle = nu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 NOUVEAU : Injecter le titre associé pour textes/intros
|
// 🔥 LASER FOCUS sur le titre et extraction des mots-clés importants
|
||||||
let titleContext = '';
|
let titleContext = '';
|
||||||
if (associatedTitle && (type === 'intro' || type === 'paragraphe')) {
|
if (associatedTitle && (type === 'intro' || type === 'paragraphe')) {
|
||||||
titleContext = `\n🎯 TITRE ASSOCIÉ (IMPORTANT - utilise-le comme base): "${associatedTitle}"\n⚠️ CRUCIAL: Le contenu doit développer et être cohérent avec ce titre spécifique.\n`;
|
// Extraire les mots-clés importants (> 4 lettres, sauf mots vides courants)
|
||||||
|
const stopWords = ['dans', 'avec', 'pour', 'sans', 'sous', 'vers', 'chez', 'sur', 'par', 'tous', 'toutes', 'cette', 'votre', 'notre'];
|
||||||
|
const titleWords = associatedTitle
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[.,;:!?'"]/g, '')
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(word => word.length > 4 && !stopWords.includes(word));
|
||||||
|
|
||||||
|
const keywordsHighlight = titleWords.length > 0
|
||||||
|
? `Mots-clés à développer: ${titleWords.join(', ')}\n`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
titleContext = `
|
||||||
|
🎯 TITRE À DÉVELOPPER: "${associatedTitle}"
|
||||||
|
${keywordsHighlight}⚠️ IMPORTANT: Ton contenu doit développer SPÉCIFIQUEMENT ce titre et ses concepts clés.
|
||||||
|
Ne génère pas de contenu générique, concentre-toi sur les mots-clés identifiés.
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 Helper : Sélectionner aléatoirement max N éléments d'un array
|
// 🔥 Helper : Sélectionner aléatoirement max N éléments d'un array
|
||||||
@ -771,7 +824,7 @@ ${includePersonality ? `- ADOPTE le style et vocabulaire du profil personnalité
|
|||||||
- Niveau technique: ${personality?.niveauTechnique || 'moyen'}` : '- Formulation neutre et professionnelle (question FAQ)'}
|
- Niveau technique: ${personality?.niveauTechnique || 'moyen'}` : '- Formulation neutre et professionnelle (question FAQ)'}
|
||||||
- Ton naturel et humain, pas robotique
|
- Ton naturel et humain, pas robotique
|
||||||
- Intégration fluide du mot-clé "${keyword}"
|
- Intégration fluide du mot-clé "${keyword}"
|
||||||
${associatedTitle ? `- DÉVELOPPE spécifiquement le titre: "${associatedTitle}"` : ''}
|
${associatedTitle ? `- 🎯 FOCUS: Développe SPÉCIFIQUEMENT les concepts du titre "${associatedTitle}" (pas de contenu générique)` : ''}
|
||||||
- PAS de formatage markdown (ni **, ni ##, ni -)
|
- PAS de formatage markdown (ni **, ni ##, ni -)
|
||||||
- PAS de préambule ou conclusion ajoutée
|
- PAS de préambule ou conclusion ajoutée
|
||||||
- ⚠️ IMPÉRATIF: RESPECTE la contrainte de longueur indiquée ci-dessus
|
- ⚠️ IMPÉRATIF: RESPECTE la contrainte de longueur indiquée ci-dessus
|
||||||
@ -794,6 +847,26 @@ async function generateSimple(hierarchy, csvData, options = {}) {
|
|||||||
throw new Error('Hiérarchie vide ou invalide');
|
throw new Error('Hiérarchie vide ou invalide');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 📊 AFFICHER LA HIÉRARCHIE AVANT GÉNÉRATION
|
||||||
|
const allElementsFromHierarchy = [];
|
||||||
|
for (const [sectionKey, section] of Object.entries(hierarchy)) {
|
||||||
|
if (section.title && section.title.originalElement) {
|
||||||
|
allElementsFromHierarchy.push(section.title.originalElement);
|
||||||
|
}
|
||||||
|
if (section.text && section.text.originalElement) {
|
||||||
|
allElementsFromHierarchy.push(section.text.originalElement);
|
||||||
|
}
|
||||||
|
if (section.questions && section.questions.length > 0) {
|
||||||
|
section.questions.forEach(faq => {
|
||||||
|
if (faq.originalElement) {
|
||||||
|
allElementsFromHierarchy.push(faq.originalElement);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logElementsList(allElementsFromHierarchy, 'ÉLÉMENTS AVANT GÉNÉRATION (depuis hiérarchie)');
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
content: {},
|
content: {},
|
||||||
stats: {
|
stats: {
|
||||||
@ -840,20 +913,45 @@ async function generateSimple(hierarchy, csvData, options = {}) {
|
|||||||
|
|
||||||
// Fonction pour extraire l'instruction de l'élément
|
// Fonction pour extraire l'instruction de l'élément
|
||||||
const extractInstruction = (tag, item) => {
|
const extractInstruction = (tag, item) => {
|
||||||
if (typeof item === 'string') return item;
|
let extracted = null;
|
||||||
if (item.instructions) return item.instructions;
|
|
||||||
if (item.title && item.title.instructions) return item.title.instructions;
|
if (typeof item === 'string') {
|
||||||
if (item.text && item.text.instructions) return item.text.instructions;
|
extracted = item;
|
||||||
|
logSh(` 🔍 [${tag}] Instruction: "${extracted}"`, 'INFO');
|
||||||
|
return extracted;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.instructions) {
|
||||||
|
extracted = item.instructions;
|
||||||
|
logSh(` 🔍 [${tag}] Instruction (item.instructions): "${extracted}"`, 'INFO');
|
||||||
|
return extracted;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.title && item.title.instructions) {
|
||||||
|
extracted = item.title.instructions;
|
||||||
|
logSh(` 🔍 [${tag}] Instruction (title.instructions): "${extracted}"`, 'INFO');
|
||||||
|
return extracted;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.text && item.text.instructions) {
|
||||||
|
extracted = item.text.instructions;
|
||||||
|
logSh(` 🔍 [${tag}] Instruction (text.instructions): "${extracted}"`, 'INFO');
|
||||||
|
return extracted;
|
||||||
|
}
|
||||||
|
|
||||||
if (item.questions && Array.isArray(item.questions) && item.questions.length > 0) {
|
if (item.questions && Array.isArray(item.questions) && item.questions.length > 0) {
|
||||||
const faqItem = item.questions[0];
|
const faqItem = item.questions[0];
|
||||||
if (faqItem.originalElement && faqItem.originalElement.resolvedContent) {
|
if (faqItem.originalElement && faqItem.originalElement.resolvedContent) {
|
||||||
return faqItem.originalElement.resolvedContent;
|
extracted = faqItem.originalElement.resolvedContent;
|
||||||
|
logSh(` 🔍 [${tag}] Instruction (FAQ resolvedContent): "${extracted}"`, 'INFO');
|
||||||
|
return extracted;
|
||||||
}
|
}
|
||||||
return `Générer une ${tag.startsWith('q') ? 'question' : 'réponse'} FAQ pertinente sur ${csvData.mc0}`;
|
logSh(` ⚠️ [${tag}] Pas d'instruction FAQ - ignoré`, 'WARNING');
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return `Générer du contenu pertinent pour la section ${tag} sur "${csvData.mc0}"`;
|
logSh(` ⚠️ [${tag}] Pas d'instruction trouvée - ignoré`, 'WARNING');
|
||||||
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -918,6 +1016,10 @@ async function generateSimple(hierarchy, csvData, options = {}) {
|
|||||||
const { tag, item, isCouple } = batches[i];
|
const { tag, item, isCouple } = batches[i];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 📊 AFFICHER LA LISTE AVANT CHAQUE GÉNÉRATION
|
||||||
|
logSh(`\n🎯 === GÉNÉRATION DE: ${tag} (${i + 1}/${batches.length}) ===`, 'INFO');
|
||||||
|
logElementsList(allElementsFromHierarchy, `ÉTAT DES ÉLÉMENTS AVANT GÉNÉRATION DE ${tag}`);
|
||||||
|
|
||||||
logSh(`🎯 Génération: ${tag}${isCouple ? ` (couple: ${isCouple})` : ''}`, 'DEBUG');
|
logSh(`🎯 Génération: ${tag}${isCouple ? ` (couple: ${isCouple})` : ''}`, 'DEBUG');
|
||||||
|
|
||||||
// 🔥 NOUVEAU : Détecter si le prochain élément est un texte associé à un titre
|
// 🔥 NOUVEAU : Détecter si le prochain élément est un texte associé à un titre
|
||||||
@ -965,8 +1067,8 @@ async function generateSimple(hierarchy, csvData, options = {}) {
|
|||||||
instruction = instruction.replace(/\{[^}]*/g, '').replace(/[{}]/g, '').trim();
|
instruction = instruction.replace(/\{[^}]*/g, '').replace(/[{}]/g, '').trim();
|
||||||
|
|
||||||
if (!instruction || instruction.length < 10) {
|
if (!instruction || instruction.length < 10) {
|
||||||
logSh(` ⚠️ ${tag}: Instruction trop courte (${instruction?.length || 0} chars), utilisation fallback`, 'WARNING');
|
logSh(` ⚠️ ${tag}: Pas d'instruction spécifique - génération sans instruction`, 'WARNING');
|
||||||
instruction = `Générer du contenu pertinent pour ${tag} sur "${csvData.mc0}"`;
|
instruction = ""; // Générer quand même mais sans instruction spécifique
|
||||||
}
|
}
|
||||||
|
|
||||||
// Détecter le type d'élément
|
// Détecter le type d'élément
|
||||||
@ -979,8 +1081,15 @@ async function generateSimple(hierarchy, csvData, options = {}) {
|
|||||||
logSh(` 🎯 Utilisation du titre associé: "${lastGeneratedTitle}"`, 'INFO');
|
logSh(` 🎯 Utilisation du titre associé: "${lastGeneratedTitle}"`, 'INFO');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Créer le prompt avec contraintes de longueur + titre associé si disponible
|
// 🔥 FIX: Extraire le mot-clé spécifique (resolvedContent) de l'élément
|
||||||
const prompt = createTypedPrompt(tag, elementType, instruction, csvData, shouldUseTitle ? lastGeneratedTitle : null);
|
let specificKeyword = null;
|
||||||
|
if (item && item.originalElement && item.originalElement.resolvedContent) {
|
||||||
|
specificKeyword = item.originalElement.resolvedContent;
|
||||||
|
logSh(` 📝 Mot-clé spécifique extrait: "${specificKeyword}"`, 'DEBUG');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer le prompt avec contraintes de longueur + titre associé + mot-clé spécifique
|
||||||
|
const prompt = createTypedPrompt(tag, elementType, instruction, csvData, shouldUseTitle ? lastGeneratedTitle : null, specificKeyword);
|
||||||
|
|
||||||
// Appeler le LLM avec maxTokens augmenté
|
// Appeler le LLM avec maxTokens augmenté
|
||||||
let maxTokens = 1000; // Défaut augmenté
|
let maxTokens = 1000; // Défaut augmenté
|
||||||
@ -1183,27 +1292,30 @@ function generateImprovementReport(originalContent, enhancedContent, layerType =
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
// Utilitaires logging
|
||||||
|
logElementsList,
|
||||||
|
|
||||||
// Analyseurs
|
// Analyseurs
|
||||||
analyzeTechnicalQuality,
|
analyzeTechnicalQuality,
|
||||||
analyzeTransitionFluidity,
|
analyzeTransitionFluidity,
|
||||||
analyzeStyleConsistency,
|
analyzeStyleConsistency,
|
||||||
|
|
||||||
// Comparateurs
|
// Comparateurs
|
||||||
compareContentImprovement,
|
compareContentImprovement,
|
||||||
|
|
||||||
// Utilitaires contenu
|
// Utilitaires contenu
|
||||||
cleanGeneratedContent,
|
cleanGeneratedContent,
|
||||||
validateSelectiveContent,
|
validateSelectiveContent,
|
||||||
|
|
||||||
// Utilitaires techniques
|
// Utilitaires techniques
|
||||||
chunkArray,
|
chunkArray,
|
||||||
sleep,
|
sleep,
|
||||||
measurePerformance,
|
measurePerformance,
|
||||||
formatDuration,
|
formatDuration,
|
||||||
|
|
||||||
// Génération simple (remplace ContentGeneration.js)
|
// Génération simple (remplace ContentGeneration.js)
|
||||||
generateSimple,
|
generateSimple,
|
||||||
|
|
||||||
// Rapports
|
// Rapports
|
||||||
generateImprovementReport
|
generateImprovementReport
|
||||||
};
|
};
|
||||||
Loading…
Reference in New Issue
Block a user