seogeneratorserver/lib/pattern-breaking/NaturalConnectors.js
StillHammer dbf1a3de8c Add technical plan for multi-format export system
Added plan.md with complete architecture for format-agnostic content generation:
- Support for Markdown, HTML, Plain Text, JSON formats
- New FormatExporter module with neutral data structure
- Integration strategy with existing ContentAssembly and ArticleStorage
- Bonus features: SEO metadata generation, readability scoring, WordPress Gutenberg format
- Implementation roadmap with 4 phases (6h total estimated)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 16:14:29 +08:00

366 lines
13 KiB
JavaScript

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