seo-generator-server/lib/generation/InitialGeneration.js

284 lines
9.5 KiB
JavaScript

// ========================================
// 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 };