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>
366 lines
13 KiB
JavaScript
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
|
|
}; |