// ======================================== // INITIAL GENERATION LAYER - GÉNÉRATION INITIALE MODULAIRE // Responsabilité: Génération de contenu initial réutilisable // LLM: Claude Sonnet-4 (précision et créativité équilibrée) // ======================================== const { callLLM } = require('../LLMManager'); const { logSh } = require('../ErrorReporting'); const { tracer } = require('../trace'); const { chunkArray, sleep } = require('../selective-enhancement/SelectiveUtils'); /** * COUCHE GÉNÉRATION INITIALE MODULAIRE */ class InitialGenerationLayer { constructor() { this.name = 'InitialGeneration'; this.defaultLLM = 'claude'; this.priority = 0; // Priorité maximale - appliqué en premier } /** * MAIN METHOD - Générer contenu initial */ async apply(contentStructure, config = {}) { return await tracer.run('InitialGenerationLayer.apply()', async () => { const { llmProvider = this.defaultLLM, temperature = 0.7, csvData = null, context = {} } = config; await tracer.annotate({ initialGeneration: true, llmProvider, temperature, elementsCount: Object.keys(contentStructure).length, mc0: csvData?.mc0 }); const startTime = Date.now(); logSh(`🎯 INITIAL GENERATION: Génération contenu initial (${llmProvider})`, 'INFO'); logSh(` 📊 ${Object.keys(contentStructure).length} éléments à générer`, 'INFO'); try { // Créer les éléments à générer à partir de la structure const elementsToGenerate = this.prepareElementsForGeneration(contentStructure, csvData); // Générer en chunks pour gérer les gros contenus const results = {}; const chunks = chunkArray(Object.entries(elementsToGenerate), 4); // Chunks de 4 pour Claude for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) { const chunk = chunks[chunkIndex]; try { logSh(` 📦 Chunk génération ${chunkIndex + 1}/${chunks.length}: ${chunk.length} éléments`, 'DEBUG'); const generationPrompt = this.createInitialGenerationPrompt(chunk, csvData, config); const response = await callLLM(llmProvider, generationPrompt, { temperature, maxTokens: 4000 }, csvData?.personality); const chunkResults = this.parseInitialGenerationResponse(response, chunk); Object.assign(results, chunkResults); logSh(` ✅ Chunk génération ${chunkIndex + 1}: ${Object.keys(chunkResults).length} générés`, 'DEBUG'); // Délai entre chunks if (chunkIndex < chunks.length - 1) { await sleep(2000); } } catch (error) { logSh(` ❌ Chunk génération ${chunkIndex + 1} échoué: ${error.message}`, 'ERROR'); // Fallback: contenu basique chunk.forEach(([tag, instruction]) => { results[tag] = this.createFallbackContent(tag, csvData); }); } } const duration = Date.now() - startTime; const stats = { generated: Object.keys(results).length, total: Object.keys(contentStructure).length, generationRate: (Object.keys(results).length / Math.max(Object.keys(contentStructure).length, 1)) * 100, duration, llmProvider, temperature }; logSh(`✅ INITIAL GENERATION TERMINÉE: ${stats.generated}/${stats.total} générés (${duration}ms)`, 'INFO'); await tracer.event('Initial generation appliquée', stats); return { content: results, stats }; } catch (error) { const duration = Date.now() - startTime; logSh(`❌ INITIAL GENERATION ÉCHOUÉE après ${duration}ms: ${error.message}`, 'ERROR'); throw error; } }, { contentStructure: Object.keys(contentStructure), config }); } /** * PRÉPARER ÉLÉMENTS POUR GÉNÉRATION */ prepareElementsForGeneration(contentStructure, csvData) { const elements = {}; // Convertir la structure en instructions de génération Object.entries(contentStructure).forEach(([tag, placeholder]) => { elements[tag] = { type: this.detectElementType(tag), instruction: this.createInstructionFromPlaceholder(placeholder, csvData), context: csvData?.mc0 || 'contenu personnalisé' }; }); return elements; } /** * DÉTECTER TYPE D'ÉLÉMENT */ detectElementType(tag) { const tagLower = tag.toLowerCase(); if (tagLower.includes('titre') || tagLower.includes('h1') || tagLower.includes('h2')) { return 'titre'; } else if (tagLower.includes('intro') || tagLower.includes('introduction')) { return 'introduction'; } else if (tagLower.includes('conclusion')) { return 'conclusion'; } else if (tagLower.includes('faq') || tagLower.includes('question')) { return 'faq'; } else { return 'contenu'; } } /** * CRÉER INSTRUCTION À PARTIR DU PLACEHOLDER */ createInstructionFromPlaceholder(placeholder, csvData) { // Si c'est déjà une vraie instruction, la garder if (typeof placeholder === 'string' && placeholder.length > 30) { return placeholder; } // Sinon, créer une instruction basique const mc0 = csvData?.mc0 || 'produit'; return `Rédige un contenu professionnel et engageant sur ${mc0}`; } /** * CRÉER PROMPT GÉNÉRATION INITIALE */ createInitialGenerationPrompt(chunk, csvData, config) { const personality = csvData?.personality; const mc0 = csvData?.mc0 || 'contenu personnalisé'; let prompt = `MISSION: Génère du contenu SEO initial de haute qualité. CONTEXTE: ${mc0} - Article optimisé SEO ${personality ? `PERSONNALITÉ: ${personality.nom} (${personality.style})` : ''} TEMPÉRATURE: ${config.temperature || 0.7} (créativité équilibrée) ÉLÉMENTS À GÉNÉRER: ${chunk.map(([tag, data], i) => `[${i + 1}] TAG: ${tag} TYPE: ${data.type} INSTRUCTION: ${data.instruction} CONTEXTE: ${data.context}`).join('\n\n')} CONSIGNES GÉNÉRATION: - CRÉE du contenu original et engageant${personality ? ` avec le style ${personality.style}` : ''} - INTÈGRE naturellement le mot-clé "${mc0}" - RESPECTE les bonnes pratiques SEO (mots-clés, structure) - ADAPTE longueur selon type d'élément: * Titres: 8-15 mots * Introduction: 2-3 phrases (40-80 mots) * Contenu: 3-6 phrases (80-200 mots) * Conclusion: 2-3 phrases (40-80 mots) - ÉVITE contenu générique, sois spécifique et informatif - UTILISE un ton professionnel mais accessible VOCABULAIRE RECOMMANDÉ SELON CONTEXTE: - Si signalétique: matériaux (dibond, aluminium), procédés (gravure, impression) - Adapte selon le domaine du mot-clé principal FORMAT RÉPONSE: [1] Contenu généré pour premier élément [2] Contenu généré pour deuxième élément etc... IMPORTANT: Réponse DIRECTE par les contenus générés, pas d'explication.`; return prompt; } /** * PARSER RÉPONSE GÉNÉRATION INITIALE */ parseInitialGenerationResponse(response, chunk) { const results = {}; const regex = /\[(\d+)\]\s*([^[]*?)(?=\n\[\d+\]|$)/gs; let match; let index = 0; while ((match = regex.exec(response)) && index < chunk.length) { let generatedContent = match[2].trim(); const [tag] = chunk[index]; // Nettoyer contenu généré generatedContent = this.cleanGeneratedContent(generatedContent); if (generatedContent && generatedContent.length > 10) { results[tag] = generatedContent; logSh(`✅ Généré [${tag}]: "${generatedContent.substring(0, 60)}..."`, 'DEBUG'); } else { results[tag] = this.createFallbackContent(tag, chunk[index][1]); logSh(`⚠️ Fallback génération [${tag}]: contenu invalide`, 'WARNING'); } index++; } // Compléter les manquants while (index < chunk.length) { const [tag, data] = chunk[index]; results[tag] = this.createFallbackContent(tag, data); index++; } return results; } /** * NETTOYER CONTENU GÉNÉRÉ */ cleanGeneratedContent(content) { if (!content) return content; // Supprimer préfixes indésirables content = content.replace(/^(voici\s+)?le\s+contenu\s+(généré|pour)\s*[:.]?\s*/gi, ''); content = content.replace(/^(contenu|élément)\s+(généré|pour)\s*[:.]?\s*/gi, ''); content = content.replace(/^(bon,?\s*)?(alors,?\s*)?/gi, ''); // Nettoyer formatage content = content.replace(/\*\*[^*]+\*\*/g, ''); // Gras markdown content = content.replace(/\s{2,}/g, ' '); // Espaces multiples content = content.trim(); return content; } /** * CRÉER CONTENU FALLBACK */ createFallbackContent(tag, data) { const mc0 = data?.context || 'produit'; const type = data?.type || 'contenu'; switch (type) { case 'titre': return `${mc0.charAt(0).toUpperCase()}${mc0.slice(1)} de qualité professionnelle`; case 'introduction': return `Découvrez notre gamme complète de ${mc0}. Qualité premium et service personnalisé.`; case 'conclusion': return `Faites confiance à notre expertise pour votre ${mc0}. Contactez-nous pour plus d'informations.`; default: return `Notre ${mc0} répond à vos besoins avec des solutions adaptées et un service de qualité.`; } } } module.exports = { InitialGenerationLayer };