// ======================================== // FICHIER: MissingKeywords.js - Version Node.js // Description: Génération automatique des mots-clés manquants // ======================================== const { logSh } = require('./ErrorReporting'); const { callLLM } = require('./LLMManager'); /** * Génère automatiquement les mots-clés manquants pour les éléments non définis * @param {Array} elements - Liste des éléments extraits * @param {Object} csvData - Données CSV avec personnalité * @returns {Object} Éléments mis à jour avec nouveaux mots-clés */ async function generateMissingKeywords(elements, csvData) { logSh('>>> GÉNÉRATION MOTS-CLÉS MANQUANTS <<<', 'INFO'); // 1. IDENTIFIER tous les éléments manquants const missingElements = []; elements.forEach(element => { if (element.resolvedContent.includes('non défini') || element.resolvedContent.includes('non résolu') || element.resolvedContent.trim() === '') { missingElements.push({ tag: element.originalTag, name: element.name, type: element.type, currentContent: element.resolvedContent, context: getElementContext(element, elements, csvData) }); } }); if (missingElements.length === 0) { logSh('Aucun mot-clé manquant détecté', 'INFO'); return {}; } logSh(`${missingElements.length} mots-clés manquants détectés`, 'INFO'); // 2. ANALYSER le contexte global disponible const contextAnalysis = analyzeAvailableContext(elements, csvData); // 3. GÉNÉRER tous les manquants en UN SEUL appel IA const generatedKeywords = await callOpenAIForMissingKeywords(missingElements, contextAnalysis, csvData); // 4. METTRE À JOUR les éléments avec les nouveaux mots-clés const updatedElements = updateElementsWithKeywords(elements, generatedKeywords); logSh(`Mots-clés manquants générés: ${Object.keys(generatedKeywords).length}`, 'INFO'); return updatedElements; } /** * Analyser le contexte disponible pour guider la génération * @param {Array} elements - Tous les éléments * @param {Object} csvData - Données CSV * @returns {Object} Analyse contextuelle */ function analyzeAvailableContext(elements, csvData) { const availableKeywords = []; const availableContent = []; // Récupérer tous les mots-clés/contenu déjà disponibles elements.forEach(element => { if (element.resolvedContent && !element.resolvedContent.includes('non défini') && !element.resolvedContent.includes('non résolu') && element.resolvedContent.trim() !== '') { if (element.type.includes('titre')) { availableKeywords.push(element.resolvedContent); } else { availableContent.push(element.resolvedContent.substring(0, 100)); } } }); return { mainKeyword: csvData.mc0, mainTitle: csvData.t0, availableKeywords: availableKeywords, availableContent: availableContent, theme: csvData.mc0, // Thème principal businessContext: "Autocollant.fr - signalétique personnalisée, plaques" }; } /** * Obtenir le contexte spécifique d'un élément * @param {Object} element - Élément à analyser * @param {Array} allElements - Tous les éléments * @param {Object} csvData - Données CSV * @returns {Object} Contexte de l'élément */ function getElementContext(element, allElements, csvData) { const context = { elementType: element.type, hierarchyLevel: element.name, nearbyElements: [] }; // Trouver les éléments proches dans la hiérarchie const elementParts = element.name.split('_'); if (elementParts.length >= 2) { const baseLevel = elementParts.slice(0, 2).join('_'); // Ex: "Titre_H3" allElements.forEach(otherElement => { if (otherElement.name.startsWith(baseLevel) && otherElement.resolvedContent && !otherElement.resolvedContent.includes('non défini')) { context.nearbyElements.push(otherElement.resolvedContent); } }); } return context; } /** * Appel IA pour générer tous les mots-clés manquants en un seul batch * @param {Array} missingElements - Éléments manquants * @param {Object} contextAnalysis - Analyse contextuelle * @param {Object} csvData - Données CSV avec personnalité * @returns {Object} Mots-clés générés */ async function callOpenAIForMissingKeywords(missingElements, contextAnalysis, csvData) { const personality = csvData.personality; let prompt = `Tu es ${personality.nom} (${personality.description}). Style: ${personality.style} MISSION: GÉNÈRE ${missingElements.length} MOTS-CLÉS/EXPRESSIONS MANQUANTS pour ${contextAnalysis.mainKeyword} CONTEXTE: - Sujet: ${contextAnalysis.mainKeyword} - Entreprise: Autocollant.fr (signalétique) - Mots-clés existants: ${contextAnalysis.availableKeywords.slice(0, 3).join(', ')} ÉLÉMENTS MANQUANTS: `; missingElements.forEach((missing, index) => { prompt += `${index + 1}. [${missing.name}] → Mot-clé SEO\n`; }); prompt += `\nCONSIGNES: - Thème: ${contextAnalysis.mainKeyword} - Mots-clés SEO naturels - Varie les termes - Évite répétitions FORMAT: [${missingElements[0].name}] mot-clé [${missingElements[1] ? missingElements[1].name : 'exemple'}] mot-clé etc...`; try { logSh('Génération mots-clés manquants...', 'DEBUG'); // Utilisation du LLM Manager avec fallback const response = await callLLM('openai', prompt, { temperature: 0.7, maxTokens: 2000 }, personality); // Parser la réponse const generatedKeywords = parseMissingKeywordsResponse(response, missingElements); return generatedKeywords; } catch (error) { logSh(`❌ FATAL: Génération mots-clés manquants échouée: ${error}`, 'ERROR'); throw new Error(`FATAL: Génération mots-clés LLM impossible - arrêt du workflow: ${error}`); } } /** * Parser la réponse IA pour extraire les mots-clés générés * @param {string} response - Réponse de l'IA * @param {Array} missingElements - Éléments manquants * @returns {Object} Mots-clés parsés */ function parseMissingKeywordsResponse(response, missingElements) { const results = {}; const regex = /\[([^\]]+)\]\s*\n([^[]*?)(?=\n\[|$)/gs; let match; while ((match = regex.exec(response)) !== null) { const elementName = match[1].trim(); const generatedKeyword = match[2].trim(); results[elementName] = generatedKeyword; logSh(`✓ Mot-clé généré [${elementName}]: "${generatedKeyword}"`, 'DEBUG'); } // FATAL si parsing partiel if (Object.keys(results).length < missingElements.length) { const manquants = missingElements.length - Object.keys(results).length; logSh(`❌ FATAL: Parsing mots-clés partiel - ${manquants}/${missingElements.length} manquants`, 'ERROR'); throw new Error(`FATAL: Parsing mots-clés incomplet (${manquants}/${missingElements.length} manquants) - arrêt du workflow`); } return results; } /** * Mettre à jour les éléments avec les nouveaux mots-clés générés * @param {Array} elements - Éléments originaux * @param {Object} generatedKeywords - Nouveaux mots-clés * @returns {Array} Éléments mis à jour */ function updateElementsWithKeywords(elements, generatedKeywords) { const updatedElements = elements.map(element => { const newKeyword = generatedKeywords[element.name]; if (newKeyword) { return { ...element, resolvedContent: newKeyword }; } return element; }); logSh('Éléments mis à jour avec nouveaux mots-clés', 'INFO'); return updatedElements; } // Exports CommonJS module.exports = { generateMissingKeywords };