// ======================================== // FICHIER: NaturalConnectors.js // RESPONSABILITÉ: Humanisation des connecteurs et transitions // Remplacement connecteurs formels par versions naturelles // ======================================== const { logSh } = require('../ErrorReporting'); /** * CONNECTEURS FORMELS LLM À HUMANISER */ const FORMAL_CONNECTORS = { // Connecteurs trop formels/académiques formal: [ { connector: 'par ailleurs', alternatives: ['aussi', 'également', 'de plus', 'en plus'], suspicion: 0.75 }, { connector: 'en outre', alternatives: ['de plus', 'également', 'aussi', 'en plus'], suspicion: 0.80 }, { connector: 'de surcroît', alternatives: ['de plus', 'aussi', 'en plus'], suspicion: 0.85 }, { connector: 'qui plus est', alternatives: ['en plus', 'et puis', 'aussi'], suspicion: 0.80 }, { connector: 'par conséquent', alternatives: ['donc', 'alors', 'du coup', 'résultat'], suspicion: 0.70 }, { connector: 'en conséquence', alternatives: ['donc', 'alors', 'du coup'], suspicion: 0.75 }, { connector: 'néanmoins', alternatives: ['mais', 'pourtant', 'cependant', 'malgré ça'], suspicion: 0.65 }, { connector: 'toutefois', alternatives: ['mais', 'pourtant', 'cependant'], suspicion: 0.70 } ], // Débuts de phrases formels formalStarts: [ { phrase: 'il convient de noter que', alternatives: ['notons que', 'remarquons que', 'précisons que'], suspicion: 0.90 }, { phrase: 'il est important de souligner que', alternatives: ['soulignons que', 'notons que', 'précisons que'], suspicion: 0.85 }, { phrase: 'il est à noter que', alternatives: ['notons que', 'signalons que', 'précisons que'], suspicion: 0.80 }, { phrase: 'il convient de préciser que', alternatives: ['précisons que', 'ajoutons que', 'notons que'], suspicion: 0.75 }, { phrase: 'dans ce contexte', alternatives: ['ici', 'dans ce cas', 'alors'], suspicion: 0.70 } ], // Transitions artificielles artificialTransitions: [ { phrase: 'abordons maintenant', alternatives: ['passons à', 'voyons', 'parlons de'], suspicion: 0.75 }, { phrase: 'examinons à présent', alternatives: ['voyons', 'regardons', 'passons à'], suspicion: 0.80 }, { phrase: 'intéressons-nous désormais à', alternatives: ['voyons', 'parlons de', 'passons à'], suspicion: 0.85 }, { phrase: 'penchons-nous sur', alternatives: ['voyons', 'regardons', 'parlons de'], suspicion: 0.70 } ] }; /** * CONNECTEURS NATURELS PAR CONTEXTE */ const NATURAL_CONNECTORS_BY_CONTEXT = { // Selon le ton/registre souhaité casual: ['du coup', 'alors', 'et puis', 'aussi', 'en fait'], conversational: ['bon', 'eh bien', 'donc', 'alors', 'et puis'], technical: ['donc', 'ainsi', 'alors', 'par là', 'de cette façon'], commercial: ['donc', 'alors', 'ainsi', 'de plus', 'aussi'] }; /** * HUMANISATION CONNECTEURS ET TRANSITIONS - FONCTION PRINCIPALE * @param {string} text - Texte à humaniser * @param {object} options - Options { intensity, preserveMeaning, maxReplacements } * @returns {object} - { content, replacements, details } */ function humanizeTransitions(text, options = {}) { if (!text || text.trim().length === 0) { return { content: text, replacements: 0 }; } const config = { intensity: 0.6, preserveMeaning: true, maxReplacements: 4, tone: 'casual', // casual, conversational, technical, commercial ...options }; logSh(`🔗 Humanisation connecteurs: intensité ${config.intensity}, ton ${config.tone}`, 'DEBUG'); let modifiedText = text; let totalReplacements = 0; const replacementDetails = []; try { // 1. Remplacer connecteurs formels const connectorsResult = replaceFormalConnectors(modifiedText, config); modifiedText = connectorsResult.content; totalReplacements += connectorsResult.replacements; replacementDetails.push(...connectorsResult.details); // 2. Humaniser débuts de phrases if (totalReplacements < config.maxReplacements) { const startsResult = humanizeFormalStarts(modifiedText, config); modifiedText = startsResult.content; totalReplacements += startsResult.replacements; replacementDetails.push(...startsResult.details); } // 3. Remplacer transitions artificielles if (totalReplacements < config.maxReplacements) { const transitionsResult = replaceArtificialTransitions(modifiedText, config); modifiedText = transitionsResult.content; totalReplacements += transitionsResult.replacements; replacementDetails.push(...transitionsResult.details); } // 4. Ajouter variabilité contextuelle if (totalReplacements < config.maxReplacements) { const contextResult = addContextualVariability(modifiedText, config); modifiedText = contextResult.content; totalReplacements += contextResult.replacements; replacementDetails.push(...contextResult.details); } logSh(`🔗 Connecteurs humanisés: ${totalReplacements} remplacements effectués`, 'DEBUG'); } catch (error) { logSh(`❌ Erreur humanisation connecteurs: ${error.message}`, 'WARNING'); return { content: text, replacements: 0, details: [] }; } return { content: modifiedText, replacements: totalReplacements, details: replacementDetails }; } /** * REMPLACEMENT CONNECTEURS FORMELS */ function replaceFormalConnectors(text, config) { let modified = text; let replacements = 0; const details = []; FORMAL_CONNECTORS.formal.forEach(connector => { if (replacements >= Math.floor(config.maxReplacements / 2)) return; const regex = new RegExp(`\\b${connector.connector}\\b`, 'gi'); const matches = modified.match(regex); if (matches && Math.random() < (config.intensity * connector.suspicion)) { // Choisir alternative selon contexte/ton const availableAlts = connector.alternatives; const contextualAlts = NATURAL_CONNECTORS_BY_CONTEXT[config.tone] || []; // Préférer alternatives contextuelles si disponibles const preferredAlts = availableAlts.filter(alt => contextualAlts.includes(alt)); const finalAlts = preferredAlts.length > 0 ? preferredAlts : availableAlts; const chosen = finalAlts[Math.floor(Math.random() * finalAlts.length)]; const beforeText = modified; modified = modified.replace(regex, chosen); if (modified !== beforeText) { replacements++; details.push({ original: connector.connector, replacement: chosen, type: 'formal_connector', suspicion: connector.suspicion }); logSh(` 🔄 Connecteur formalisé: "${connector.connector}" → "${chosen}"`, 'DEBUG'); } } }); return { content: modified, replacements, details }; } /** * HUMANISATION DÉBUTS DE PHRASES FORMELS */ function humanizeFormalStarts(text, config) { let modified = text; let replacements = 0; const details = []; FORMAL_CONNECTORS.formalStarts.forEach(start => { if (replacements >= Math.floor(config.maxReplacements / 3)) return; const regex = new RegExp(start.phrase, 'gi'); if (modified.match(regex) && Math.random() < (config.intensity * start.suspicion)) { const alternative = start.alternatives[Math.floor(Math.random() * start.alternatives.length)]; const beforeText = modified; modified = modified.replace(regex, alternative); if (modified !== beforeText) { replacements++; details.push({ original: start.phrase, replacement: alternative, type: 'formal_start', suspicion: start.suspicion }); logSh(` 🚀 Début formalisé: "${start.phrase}" → "${alternative}"`, 'DEBUG'); } } }); return { content: modified, replacements, details }; } /** * REMPLACEMENT TRANSITIONS ARTIFICIELLES */ function replaceArtificialTransitions(text, config) { let modified = text; let replacements = 0; const details = []; FORMAL_CONNECTORS.artificialTransitions.forEach(transition => { if (replacements >= Math.floor(config.maxReplacements / 4)) return; const regex = new RegExp(transition.phrase, 'gi'); if (modified.match(regex) && Math.random() < (config.intensity * transition.suspicion * 0.8)) { const alternative = transition.alternatives[Math.floor(Math.random() * transition.alternatives.length)]; const beforeText = modified; modified = modified.replace(regex, alternative); if (modified !== beforeText) { replacements++; details.push({ original: transition.phrase, replacement: alternative, type: 'artificial_transition', suspicion: transition.suspicion }); logSh(` 🌉 Transition artificialisée: "${transition.phrase}" → "${alternative}"`, 'DEBUG'); } } }); return { content: modified, replacements, details }; } /** * AJOUT VARIABILITÉ CONTEXTUELLE */ function addContextualVariability(text, config) { let modified = text; let replacements = 0; const details = []; // Connecteurs génériques à contextualiser selon le ton const genericPatterns = [ { from: /\bet puis\b/g, contextual: true }, { from: /\bdone\b/g, contextual: true }, { from: /\bainsi\b/g, contextual: true } ]; const contextualReplacements = NATURAL_CONNECTORS_BY_CONTEXT[config.tone] || NATURAL_CONNECTORS_BY_CONTEXT.casual; genericPatterns.forEach(pattern => { if (replacements >= 2) return; if (pattern.contextual && Math.random() < (config.intensity * 0.4)) { const matches = modified.match(pattern.from); if (matches && contextualReplacements.length > 0) { const replacement = contextualReplacements[Math.floor(Math.random() * contextualReplacements.length)]; // Éviter remplacements identiques if (replacement !== matches[0]) { const beforeText = modified; modified = modified.replace(pattern.from, replacement); if (modified !== beforeText) { replacements++; details.push({ original: matches[0], replacement, type: 'contextual_variation', suspicion: 0.4 }); logSh(` 🎯 Variation contextuelle: "${matches[0]}" → "${replacement}"`, 'DEBUG'); } } } } }); return { content: modified, replacements, details }; } /** * DÉTECTION CONNECTEURS FORMELS DANS TEXTE */ function detectFormalConnectors(text) { if (!text || text.trim().length === 0) { return { count: 0, connectors: [], suspicionScore: 0 }; } const detectedConnectors = []; let totalSuspicion = 0; // Vérifier tous les types de connecteurs formels Object.values(FORMAL_CONNECTORS).flat().forEach(item => { const searchTerm = item.connector || item.phrase; const regex = new RegExp(`\\b${searchTerm}\\b`, 'gi'); const matches = text.match(regex); if (matches) { detectedConnectors.push({ connector: searchTerm, count: matches.length, suspicion: item.suspicion, alternatives: item.alternatives }); totalSuspicion += item.suspicion * matches.length; } }); const wordCount = text.split(/\s+/).length; const suspicionScore = wordCount > 0 ? totalSuspicion / wordCount : 0; logSh(`🔍 Connecteurs formels détectés: ${detectedConnectors.length} (score: ${suspicionScore.toFixed(3)})`, 'DEBUG'); return { count: detectedConnectors.length, connectors: detectedConnectors.map(c => c.connector), detailedConnectors: detectedConnectors, suspicionScore, recommendation: suspicionScore > 0.03 ? 'humanize' : 'minimal_changes' }; } /** * ANALYSE DENSITÉ CONNECTEURS FORMELS */ function analyzeConnectorFormality(text) { const detection = detectFormalConnectors(text); const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0); const density = detection.count / sentences.length; const formalityLevel = density > 0.4 ? 'high' : density > 0.2 ? 'medium' : 'low'; return { connectorsCount: detection.count, sentenceCount: sentences.length, density, formalityLevel, suspicionScore: detection.suspicionScore, recommendation: formalityLevel === 'high' ? 'extensive_humanization' : formalityLevel === 'medium' ? 'selective_humanization' : 'minimal_humanization' }; } // ============= EXPORTS ============= module.exports = { humanizeTransitions, replaceFormalConnectors, humanizeFormalStarts, replaceArtificialTransitions, addContextualVariability, detectFormalConnectors, analyzeConnectorFormality, FORMAL_CONNECTORS, NATURAL_CONNECTORS_BY_CONTEXT };