// ======================================== // ÉTAPE 3: ENHANCEMENT TRANSITIONS ADVERSARIAL // Responsabilité: Améliorer la fluidité avec Gemini + anti-détection // LLM: Gemini (température 0.6) + Prompts adversariaux // ======================================== const { callLLM } = require('../LLMManager'); const { logSh } = require('../ErrorReporting'); const { tracer } = require('../trace'); const { createAdversarialPrompt } = require('./AdversarialPromptEngine'); const { DetectorStrategyManager } = require('./DetectorStrategies'); /** * MAIN ENTRY POINT - ENHANCEMENT TRANSITIONS ADVERSARIAL * Input: { content: {}, csvData: {}, context: {}, adversarialConfig: {} } * Output: { content: {}, stats: {}, debug: {} } */ async function enhanceTransitionsAdversarial(input) { return await tracer.run('AdversarialTransitionEnhancement.enhanceTransitionsAdversarial()', async () => { const { content, csvData, context = {}, adversarialConfig = {} } = input; // Configuration adversariale par défaut const config = { detectorTarget: adversarialConfig.detectorTarget || 'general', intensity: adversarialConfig.intensity || 1.0, enableAdaptiveStrategy: adversarialConfig.enableAdaptiveStrategy || true, contextualMode: adversarialConfig.contextualMode !== false, ...adversarialConfig }; // Initialiser manager détecteur const detectorManager = new DetectorStrategyManager(config.detectorTarget); await tracer.annotate({ step: '3/4', llmProvider: 'gemini', elementsCount: Object.keys(content).length, mc0: csvData.mc0 }); const startTime = Date.now(); logSh(`🎯 ÉTAPE 3/4 ADVERSARIAL: Enhancement transitions (Gemini + ${config.detectorTarget})`, 'INFO'); logSh(` 📊 ${Object.keys(content).length} éléments à analyser`, 'INFO'); try { // 1. Analyser quels éléments ont besoin d'amélioration transitions const elementsNeedingTransitions = analyzeTransitionNeeds(content); logSh(` 📋 Analyse: ${elementsNeedingTransitions.length}/${Object.keys(content).length} éléments nécessitent fluidité`, 'INFO'); if (elementsNeedingTransitions.length === 0) { logSh(`✅ ÉTAPE 3/4: Transitions déjà optimales`, 'INFO'); return { content, stats: { processed: Object.keys(content).length, enhanced: 0, duration: Date.now() - startTime }, debug: { llmProvider: 'gemini', step: 3, enhancementsApplied: [] } }; } // 2. Améliorer en chunks avec prompts adversariaux pour Gemini const improvedResults = await improveTransitionsInChunksAdversarial(elementsNeedingTransitions, csvData, config, detectorManager); // 3. Merger avec contenu original const finalContent = { ...content }; let actuallyImproved = 0; Object.keys(improvedResults).forEach(tag => { if (improvedResults[tag] !== content[tag]) { finalContent[tag] = improvedResults[tag]; actuallyImproved++; } }); const duration = Date.now() - startTime; const stats = { processed: Object.keys(content).length, enhanced: actuallyImproved, candidate: elementsNeedingTransitions.length, duration }; logSh(`✅ ÉTAPE 3/4 TERMINÉE: ${stats.enhanced} éléments fluidifiés (${duration}ms)`, 'INFO'); await tracer.event(`Enhancement transitions terminé`, stats); return { content: finalContent, stats, debug: { llmProvider: 'gemini', step: 3, enhancementsApplied: Object.keys(improvedResults), transitionIssues: elementsNeedingTransitions.map(e => e.issues), adversarialConfig: config, detectorTarget: config.detectorTarget, intensity: config.intensity } }; } catch (error) { const duration = Date.now() - startTime; logSh(`❌ ÉTAPE 3/4 ÉCHOUÉE après ${duration}ms: ${error.message}`, 'ERROR'); // Fallback: retourner contenu original si Gemini indisponible logSh(`🔄 Fallback: contenu original conservé`, 'WARNING'); return { content, stats: { processed: Object.keys(content).length, enhanced: 0, duration }, debug: { llmProvider: 'gemini', step: 3, error: error.message, fallback: true } }; } }, input); } /** * Analyser besoin d'amélioration transitions */ function analyzeTransitionNeeds(content) { const elementsNeedingTransitions = []; Object.keys(content).forEach(tag => { const text = content[tag]; // Filtrer les éléments longs (>150 chars) qui peuvent bénéficier d'améliorations if (text.length > 150) { const needsTransitions = evaluateTransitionQuality(text); if (needsTransitions.needsImprovement) { elementsNeedingTransitions.push({ tag, content: text, issues: needsTransitions.issues, score: needsTransitions.score }); logSh(` 🔍 [${tag}]: Score=${needsTransitions.score.toFixed(2)}, Issues: ${needsTransitions.issues.join(', ')}`, 'DEBUG'); } } else { logSh(` ⏭️ [${tag}]: Trop court (${text.length}c), ignoré`, 'DEBUG'); } }); // Trier par score (plus problématique en premier) elementsNeedingTransitions.sort((a, b) => a.score - b.score); return elementsNeedingTransitions; } /** * Évaluer qualité transitions d'un texte */ function evaluateTransitionQuality(text) { const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 10); if (sentences.length < 2) { return { needsImprovement: false, score: 1.0, issues: [] }; } const issues = []; let score = 1.0; // Score parfait = 1.0, problématique = 0.0 // Analyse 1: Connecteurs répétitifs const repetitiveConnectors = analyzeRepetitiveConnectors(text); if (repetitiveConnectors > 0.3) { issues.push('connecteurs_répétitifs'); score -= 0.3; } // Analyse 2: Transitions abruptes const abruptTransitions = analyzeAbruptTransitions(sentences); if (abruptTransitions > 0.4) { issues.push('transitions_abruptes'); score -= 0.4; } // Analyse 3: Manque de variété dans longueurs const sentenceVariety = analyzeSentenceVariety(sentences); if (sentenceVariety < 0.3) { issues.push('phrases_uniformes'); score -= 0.2; } // Analyse 4: Trop formel ou trop familier const formalityIssues = analyzeFormalityBalance(text); if (formalityIssues > 0.5) { issues.push('formalité_déséquilibrée'); score -= 0.1; } return { needsImprovement: score < 0.6, score: Math.max(0, score), issues }; } /** * Améliorer transitions en chunks avec prompts adversariaux */ async function improveTransitionsInChunksAdversarial(elementsNeedingTransitions, csvData, adversarialConfig, detectorManager) { logSh(`🎯 Amélioration transitions adversarial: ${elementsNeedingTransitions.length} éléments`, 'DEBUG'); const results = {}; const chunks = chunkArray(elementsNeedingTransitions, 6); // Chunks plus petits pour Gemini for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) { const chunk = chunks[chunkIndex]; try { logSh(` 📦 Chunk ${chunkIndex + 1}/${chunks.length}: ${chunk.length} éléments`, 'DEBUG'); const basePrompt = createTransitionImprovementPrompt(chunk, csvData); // Générer prompt adversarial pour amélioration transitions const adversarialPrompt = createAdversarialPrompt(basePrompt, { detectorTarget: adversarialConfig.detectorTarget, intensity: adversarialConfig.intensity * 0.9, // Intensité légèrement réduite pour transitions elementType: 'transition_enhancement', personality: csvData.personality, contextualMode: adversarialConfig.contextualMode, csvData: csvData, debugMode: false }); const improvedResponse = await callLLM('gemini', adversarialPrompt, { temperature: 0.6, maxTokens: 2500 }, csvData.personality); const chunkResults = parseTransitionResponse(improvedResponse, chunk); Object.assign(results, chunkResults); logSh(` ✅ Chunk ${chunkIndex + 1}: ${Object.keys(chunkResults).length} améliorés`, 'DEBUG'); // Délai entre chunks if (chunkIndex < chunks.length - 1) { await sleep(1500); } } catch (error) { logSh(` ❌ Chunk ${chunkIndex + 1} échoué: ${error.message}`, 'ERROR'); // Fallback: garder contenu original pour ce chunk chunk.forEach(element => { results[element.tag] = element.content; }); } } return results; } /** * Créer prompt amélioration transitions */ function createTransitionImprovementPrompt(chunk, csvData) { const personality = csvData.personality; let prompt = `MISSION: Améliore UNIQUEMENT les transitions et fluidité de ces contenus. CONTEXTE: Article SEO ${csvData.mc0} PERSONNALITÉ: ${personality?.nom} (${personality?.style} web professionnel) CONNECTEURS PRÉFÉRÉS: ${personality?.connecteursPref} CONTENUS À FLUIDIFIER: ${chunk.map((item, i) => `[${i + 1}] TAG: ${item.tag} PROBLÈMES: ${item.issues.join(', ')} CONTENU: "${item.content}"`).join('\n\n')} OBJECTIFS: - Connecteurs plus naturels et variés: ${personality?.connecteursPref} - Transitions fluides entre idées - ÉVITE répétitions excessives ("du coup", "franchement", "par ailleurs") - Style ${personality?.style} mais professionnel web CONSIGNES STRICTES: - NE CHANGE PAS le fond du message - GARDE même structure et longueur - Améliore SEULEMENT la fluidité - RESPECTE le style ${personality?.nom} FORMAT RÉPONSE: [1] Contenu avec transitions améliorées [2] Contenu avec transitions améliorées etc...`; return prompt; } /** * Parser réponse amélioration transitions */ function parseTransitionResponse(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 improvedContent = match[2].trim(); const element = chunk[index]; // Nettoyer le contenu amélioré improvedContent = cleanImprovedContent(improvedContent); if (improvedContent && improvedContent.length > 10) { results[element.tag] = improvedContent; logSh(`✅ Improved [${element.tag}]: "${improvedContent.substring(0, 100)}..."`, 'DEBUG'); } else { results[element.tag] = element.content; logSh(`⚠️ Fallback [${element.tag}]: amélioration invalide`, 'WARNING'); } index++; } // Compléter les manquants while (index < chunk.length) { const element = chunk[index]; results[element.tag] = element.content; index++; } return results; } // ============= HELPER FUNCTIONS ============= function analyzeRepetitiveConnectors(content) { const connectors = ['par ailleurs', 'en effet', 'de plus', 'cependant', 'ainsi', 'donc']; let totalConnectors = 0; let repetitions = 0; connectors.forEach(connector => { const matches = (content.match(new RegExp(`\\b${connector}\\b`, 'gi')) || []); totalConnectors += matches.length; if (matches.length > 1) repetitions += matches.length - 1; }); return totalConnectors > 0 ? repetitions / totalConnectors : 0; } function analyzeAbruptTransitions(sentences) { if (sentences.length < 2) return 0; let abruptCount = 0; for (let i = 1; i < sentences.length; i++) { const current = sentences[i].trim(); const hasConnector = hasTransitionWord(current); if (!hasConnector && current.length > 30) { abruptCount++; } } return abruptCount / (sentences.length - 1); } function analyzeSentenceVariety(sentences) { if (sentences.length < 2) return 1; const lengths = sentences.map(s => s.trim().length); const avgLength = lengths.reduce((a, b) => a + b, 0) / lengths.length; const variance = lengths.reduce((acc, len) => acc + Math.pow(len - avgLength, 2), 0) / lengths.length; const stdDev = Math.sqrt(variance); return Math.min(1, stdDev / avgLength); } function analyzeFormalityBalance(content) { const formalIndicators = ['il convient de', 'par conséquent', 'néanmoins', 'toutefois']; const casualIndicators = ['du coup', 'bon', 'franchement', 'nickel']; let formalCount = 0; let casualCount = 0; formalIndicators.forEach(indicator => { if (content.toLowerCase().includes(indicator)) formalCount++; }); casualIndicators.forEach(indicator => { if (content.toLowerCase().includes(indicator)) casualCount++; }); const total = formalCount + casualCount; if (total === 0) return 0; // Déséquilibre si trop d'un côté const balance = Math.abs(formalCount - casualCount) / total; return balance; } function hasTransitionWord(sentence) { const connectors = ['par ailleurs', 'en effet', 'de plus', 'cependant', 'ainsi', 'donc', 'ensuite', 'puis', 'également', 'aussi']; return connectors.some(connector => sentence.toLowerCase().includes(connector)); } function cleanImprovedContent(content) { if (!content) return content; content = content.replace(/^(Bon,?\s*)?(alors,?\s*)?/, ''); content = content.replace(/\s{2,}/g, ' '); content = content.trim(); return content; } function chunkArray(array, size) { const chunks = []; for (let i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); } return chunks; } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } module.exports = { enhanceTransitionsAdversarial, // ← MAIN ENTRY POINT ADVERSARIAL analyzeTransitionNeeds, evaluateTransitionQuality, improveTransitionsInChunksAdversarial, createTransitionImprovementPrompt, parseTransitionResponse };