Application systématique et méthodique de tous les patches historiques. ## ✅ FICHIERS SYNCHRONISÉS (19 fichiers) ### Core & Infrastructure: - server.js (14 patches) - Lazy loading ModeManager, SIGINT hard kill, timing logs - ModeManager.js (4 patches) - Instrumentation complète avec timing détaillé ### Pipeline System: - PipelineDefinition.js (6 patches) - Source unique getLLMProvidersList() - pipeline-builder.js (8 patches) - Standardisation LLM providers - pipeline-runner.js (6 patches) - Affichage résultats structurés + debug console - pipeline-builder.html (2 patches) - Fallback providers synchronisés - pipeline-runner.html (3 patches) - UI améliorée résultats ### Enhancement Layers: - TechnicalLayer.js (1 patch) - defaultLLM: 'gpt-4o-mini' - StyleLayer.js (1 patch) - Type safety vocabulairePref - PatternBreakingCore.js (1 patch) - Mapping modifications - PatternBreakingLayers.js (1 patch) - LLM standardisé ### Validators & Tests: - QualityMetrics.js (1 patch) - callLLM('gpt-4o-mini') - PersonalityValidator.js (1 patch) - Provider gpt-4o-mini - AntiDetectionValidator.js - Synchronisé ### Documentation: - TODO.md (1 patch) - Section LiteLLM pour tracking coûts - CLAUDE.md - Documentation à jour ### Tools: - tools/analyze-skipped-exports.js (nouveau) - tools/apply-claude-exports.js (nouveau) - tools/apply-claude-exports-fuzzy.js (nouveau) ## 🎯 Changements principaux: - ✅ Standardisation LLM providers (openai → gpt-4o-mini, claude → claude-sonnet-4-5) - ✅ Lazy loading optimisé (ModeManager chargé à la demande) - ✅ SIGINT immediate exit (pas de graceful shutdown) - ✅ Type safety renforcé (conversions string explicites) - ✅ Instrumentation timing complète - ✅ Debug logging amélioré (console.log résultats pipeline) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
677 lines
25 KiB
JavaScript
677 lines
25 KiB
JavaScript
// ========================================
|
|
// FICHIER: StepExecutor.js
|
|
// RESPONSABILITÉ: Exécution des étapes modulaires
|
|
// ========================================
|
|
|
|
const { logSh } = require('./ErrorReporting');
|
|
const { validateElement, hasUnresolvedPlaceholders } = require('./ValidationGuards');
|
|
|
|
/**
|
|
* EXECUTEUR D'ÉTAPES MODULAIRES
|
|
* Execute les différents systèmes étape par étape avec stats détaillées
|
|
*/
|
|
class StepExecutor {
|
|
constructor() {
|
|
// Mapping des systèmes vers leurs exécuteurs
|
|
this.systems = {
|
|
'initial-generation': this.executeInitialGeneration.bind(this),
|
|
'selective': this.executeSelective.bind(this),
|
|
'adversarial': this.executeAdversarial.bind(this),
|
|
'human-simulation': this.executeHumanSimulation.bind(this),
|
|
'pattern-breaking': this.executePatternBreaking.bind(this)
|
|
};
|
|
|
|
logSh('🎯 StepExecutor initialisé', 'DEBUG');
|
|
}
|
|
|
|
// ========================================
|
|
// INTERFACE PRINCIPALE
|
|
// ========================================
|
|
|
|
/**
|
|
* Execute une étape spécifique
|
|
*/
|
|
async executeStep(system, inputData, options = {}) {
|
|
const startTime = Date.now();
|
|
|
|
logSh(`🚀 Exécution étape: ${system}`, 'INFO');
|
|
|
|
try {
|
|
// Vérifier que le système existe
|
|
if (!this.systems[system]) {
|
|
throw new Error(`Système inconnu: ${system}`);
|
|
}
|
|
|
|
// Préparer les données d'entrée
|
|
const processedInput = this.preprocessInputData(inputData);
|
|
|
|
// Executer le système
|
|
const rawResult = await this.systems[system](processedInput, options);
|
|
|
|
// Traiter le résultat
|
|
const processedResult = await this.postprocessResult(rawResult, system);
|
|
|
|
const duration = Date.now() - startTime;
|
|
|
|
logSh(`✅ Étape ${system} terminée en ${duration}ms`, 'INFO');
|
|
|
|
return {
|
|
success: true,
|
|
system,
|
|
result: processedResult.content,
|
|
formatted: this.formatOutput(processedResult.content, 'tag'),
|
|
xmlFormatted: this.formatOutput(processedResult.content, 'xml'),
|
|
stats: {
|
|
duration,
|
|
tokensUsed: processedResult.tokensUsed || 0,
|
|
cost: processedResult.cost || 0,
|
|
llmCalls: processedResult.llmCalls || [],
|
|
system: system,
|
|
timestamp: Date.now()
|
|
}
|
|
};
|
|
} catch (error) {
|
|
const duration = Date.now() - startTime;
|
|
|
|
logSh(`❌ Erreur étape ${system}: ${error.message}`, 'ERROR');
|
|
|
|
return {
|
|
success: false,
|
|
system,
|
|
error: error.message,
|
|
stats: {
|
|
duration,
|
|
system: system,
|
|
timestamp: Date.now(),
|
|
error: true
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// EXÉCUTEURS SPÉCIFIQUES
|
|
// ========================================
|
|
|
|
/**
|
|
* Construire la structure de contenu depuis la hiérarchie réelle
|
|
*/
|
|
buildContentStructureFromHierarchy(inputData, hierarchy) {
|
|
logSh(`🏗️ BUILD CONTENT STRUCTURE - Début`, 'INFO');
|
|
logSh(` 📊 Input: mc0="${inputData.mc0}"`, 'DEBUG');
|
|
logSh(` 📊 Hiérarchie: ${hierarchy ? Object.keys(hierarchy).length : 0} sections`, 'DEBUG');
|
|
|
|
const contentStructure = {};
|
|
|
|
// Si hiérarchie disponible, l'utiliser
|
|
if (hierarchy && Object.keys(hierarchy).length > 0) {
|
|
logSh(`🔍 Hiérarchie reçue: ${Object.keys(hierarchy).length} sections`, 'INFO');
|
|
logSh(`🔍 Première section sample: ${JSON.stringify(Object.values(hierarchy)[0]).substring(0, 200)}`, 'DEBUG');
|
|
|
|
let validationErrors = 0;
|
|
let elementCount = 0;
|
|
|
|
Object.entries(hierarchy).forEach(([path, section], sectionIndex) => {
|
|
logSh(`📂 SECTION [${sectionIndex + 1}/${Object.keys(hierarchy).length}] path="${path}"`, 'DEBUG');
|
|
|
|
// Générer pour le titre si présent
|
|
if (section.title && section.title.originalElement) {
|
|
elementCount++;
|
|
|
|
// ✅ SOLUTION D: Validation guard avant utilisation
|
|
try {
|
|
validateElement(section.title.originalElement, {
|
|
strict: true,
|
|
checkInstructions: true,
|
|
context: `StepExecutor buildContent - path: ${path} (title)`
|
|
});
|
|
} catch (validationError) {
|
|
validationErrors++;
|
|
logSh(`⚠️ Validation échouée pour titre [${section.title.originalElement.name}]: ${validationError.message}`, 'WARNING');
|
|
// Ne pas bloquer, utiliser fallback
|
|
}
|
|
|
|
const tag = section.title.originalElement.name;
|
|
const instruction = section.title.instructions || section.title.originalElement.instructions || `Rédige un titre pour ${inputData.mc0}`;
|
|
|
|
// 📊 LOG: Détailler l'instruction extraite
|
|
logSh(` 📌 TITRE [${elementCount}] tag="${tag}"`, 'DEBUG');
|
|
logSh(` 🔹 section.title.instructions: "${section.title.instructions ? section.title.instructions.substring(0, 80) : 'NULL'}"`, 'DEBUG');
|
|
logSh(` 🔹 section.title.originalElement.instructions: "${section.title.originalElement.instructions ? section.title.originalElement.instructions.substring(0, 80) : 'NULL'}"`, 'DEBUG');
|
|
logSh(` ➡️ INSTRUCTION FINALE: "${instruction.substring(0, 100)}"`, 'INFO');
|
|
|
|
// ✅ Double-vérification des instructions avant ajout
|
|
const instructionCheck = hasUnresolvedPlaceholders(instruction);
|
|
if (instructionCheck.hasIssues) {
|
|
logSh(`⚠️ Instruction pour [${tag}] contient des placeholders: ${instructionCheck.placeholders.join(', ')}`, 'WARNING');
|
|
}
|
|
|
|
contentStructure[tag] = instruction;
|
|
}
|
|
|
|
// Générer pour le texte si présent
|
|
if (section.text && section.text.originalElement) {
|
|
elementCount++;
|
|
|
|
// ✅ SOLUTION D: Validation guard
|
|
try {
|
|
validateElement(section.text.originalElement, {
|
|
strict: true,
|
|
checkInstructions: true,
|
|
context: `StepExecutor buildContent - path: ${path} (text)`
|
|
});
|
|
} catch (validationError) {
|
|
validationErrors++;
|
|
logSh(`⚠️ Validation échouée pour texte [${section.text.originalElement.name}]: ${validationError.message}`, 'WARNING');
|
|
}
|
|
|
|
const tag = section.text.originalElement.name;
|
|
const instruction = section.text.instructions || section.text.originalElement.instructions || `Rédige du contenu sur ${inputData.mc0}`;
|
|
|
|
// 📊 LOG: Détailler l'instruction extraite
|
|
logSh(` 📌 TEXTE [${elementCount}] tag="${tag}"`, 'DEBUG');
|
|
logSh(` 🔹 section.text.instructions: "${section.text.instructions ? section.text.instructions.substring(0, 80) : 'NULL'}"`, 'DEBUG');
|
|
logSh(` 🔹 section.text.originalElement.instructions: "${section.text.originalElement.instructions ? section.text.originalElement.instructions.substring(0, 80) : 'NULL'}"`, 'DEBUG');
|
|
logSh(` ➡️ INSTRUCTION FINALE: "${instruction.substring(0, 100)}"`, 'INFO');
|
|
|
|
const instructionCheck = hasUnresolvedPlaceholders(instruction);
|
|
if (instructionCheck.hasIssues) {
|
|
logSh(`⚠️ Instruction pour [${tag}] contient des placeholders: ${instructionCheck.placeholders.join(', ')}`, 'WARNING');
|
|
}
|
|
|
|
contentStructure[tag] = instruction;
|
|
}
|
|
|
|
// Générer pour les questions FAQ si présentes
|
|
if (section.questions && section.questions.length > 0) {
|
|
section.questions.forEach(q => {
|
|
if (q.originalElement) {
|
|
// ✅ SOLUTION D: Validation guard
|
|
try {
|
|
validateElement(q.originalElement, {
|
|
strict: true,
|
|
checkInstructions: true,
|
|
context: `StepExecutor buildContent - path: ${path} (question)`
|
|
});
|
|
} catch (validationError) {
|
|
validationErrors++;
|
|
logSh(`⚠️ Validation échouée pour question [${q.originalElement.name}]: ${validationError.message}`, 'WARNING');
|
|
}
|
|
|
|
const tag = q.originalElement.name;
|
|
const instruction = q.instructions || q.originalElement.instructions || `Rédige une question/réponse FAQ sur ${inputData.mc0}`;
|
|
|
|
const instructionCheck = hasUnresolvedPlaceholders(instruction);
|
|
if (instructionCheck.hasIssues) {
|
|
logSh(`⚠️ Instruction pour [${tag}] contient des placeholders: ${instructionCheck.placeholders.join(', ')}`, 'WARNING');
|
|
}
|
|
|
|
contentStructure[tag] = instruction;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
if (validationErrors > 0) {
|
|
logSh(`⚠️ ${validationErrors} erreurs de validation détectées lors de la construction de la structure`, 'WARNING');
|
|
logSh(`💡 Cela indique que MissingKeywords.js n'a pas correctement synchronisé resolvedContent et instructions`, 'WARNING');
|
|
}
|
|
|
|
logSh(`✅ STRUCTURE CONSTRUITE: ${Object.keys(contentStructure).length} éléments prêts pour génération`, 'INFO');
|
|
logSh(`📊 RÉSUMÉ INSTRUCTIONS:`, 'INFO');
|
|
|
|
// 📊 LOG: Afficher toutes les instructions finales
|
|
Object.entries(contentStructure).forEach(([tag, instruction], idx) => {
|
|
const shortInstr = instruction.length > 80 ? instruction.substring(0, 80) + '...' : instruction;
|
|
logSh(` [${idx + 1}] ${tag}: "${shortInstr}"`, 'INFO');
|
|
});
|
|
} else {
|
|
// Fallback: structure générique si pas de hiérarchie
|
|
logSh(`⚠️ Pas de hiérarchie, utilisation structure générique`, 'WARNING');
|
|
contentStructure['Titre_H1'] = `Rédige un titre H1 accrocheur et optimisé SEO sur ${inputData.mc0}`;
|
|
contentStructure['Introduction'] = `Rédige une introduction engageante qui présente ${inputData.mc0}`;
|
|
contentStructure['Contenu_Principal'] = `Développe le contenu principal détaillé sur ${inputData.mc0} avec des informations utiles et techniques`;
|
|
contentStructure['Conclusion'] = `Rédige une conclusion percutante qui encourage à l'action pour ${inputData.mc0}`;
|
|
}
|
|
|
|
return contentStructure;
|
|
}
|
|
|
|
/**
|
|
* Execute Initial Generation
|
|
*/
|
|
async executeInitialGeneration(inputData, options = {}) {
|
|
try {
|
|
const { InitialGenerationLayer } = require('./generation/InitialGeneration');
|
|
|
|
logSh('🎯 Démarrage Génération Initiale', 'DEBUG');
|
|
|
|
const config = {
|
|
temperature: options.temperature || 0.7,
|
|
maxTokens: options.maxTokens || 4000
|
|
};
|
|
|
|
// Créer la structure de contenu à générer depuis la hiérarchie réelle
|
|
// La hiérarchie peut être dans inputData.hierarchy OU options.hierarchy
|
|
const hierarchy = options.hierarchy || inputData.hierarchy;
|
|
const contentStructure = this.buildContentStructureFromHierarchy(inputData, hierarchy);
|
|
|
|
logSh(`📊 Structure construite: ${Object.keys(contentStructure).length} éléments depuis hiérarchie`, 'DEBUG');
|
|
|
|
const initialGenerator = new InitialGenerationLayer();
|
|
const result = await initialGenerator.apply(contentStructure, {
|
|
...config,
|
|
csvData: inputData,
|
|
llmProvider: 'claude'
|
|
});
|
|
|
|
return {
|
|
content: result.content || result,
|
|
tokensUsed: result.stats?.tokensUsed || 200,
|
|
cost: (result.stats?.tokensUsed || 200) * 0.00002,
|
|
llmCalls: [
|
|
{ provider: 'claude', tokens: result.stats?.tokensUsed || 200, cost: 0.004, phase: 'initial_generation' }
|
|
],
|
|
phases: {
|
|
initialGeneration: result.stats
|
|
},
|
|
beforeAfter: {
|
|
before: contentStructure,
|
|
after: result.content
|
|
}
|
|
};
|
|
} catch (error) {
|
|
logSh(`❌ Erreur Initial Generation: ${error.message}`, 'ERROR');
|
|
|
|
return this.createFallbackContent('initial-generation', inputData, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute Selective Enhancement
|
|
*/
|
|
async executeSelective(inputData, options = {}) {
|
|
try {
|
|
// Import dynamique pour éviter les dépendances circulaires
|
|
const { applyPredefinedStack } = require('./selective-enhancement/SelectiveLayers');
|
|
|
|
logSh('🎯 Démarrage Selective Enhancement seulement', 'DEBUG');
|
|
|
|
const config = {
|
|
selectiveStack: options.selectiveStack || 'standardEnhancement',
|
|
temperature: options.temperature || 0.7,
|
|
maxTokens: options.maxTokens || 3000
|
|
};
|
|
|
|
// Vérifier si on a du contenu à améliorer
|
|
let contentToEnhance = null;
|
|
|
|
if (options.inputContent && Object.keys(options.inputContent).length > 0) {
|
|
// Utiliser le contenu fourni
|
|
contentToEnhance = options.inputContent;
|
|
} else {
|
|
// Fallback: créer un contenu basique pour le test
|
|
logSh('⚠️ Pas de contenu d\'entrée, création d\'un contenu basique pour test', 'WARNING');
|
|
contentToEnhance = {
|
|
'Titre_H1': inputData.t0 || 'Titre principal',
|
|
'Introduction': `Contenu sur ${inputData.mc0}`,
|
|
'Contenu_Principal': `Développement du sujet ${inputData.mc0}`,
|
|
'Conclusion': `Conclusion sur ${inputData.mc0}`
|
|
};
|
|
}
|
|
|
|
const beforeContent = JSON.parse(JSON.stringify(contentToEnhance)); // Deep copy
|
|
|
|
// ÉTAPE ENHANCEMENT - Améliorer le contenu fourni avec la stack spécifiée
|
|
logSh(`🎯 Enhancement sélectif du contenu avec stack: ${config.selectiveStack}`, 'DEBUG');
|
|
const result = await applyPredefinedStack(contentToEnhance, config.selectiveStack, {
|
|
csvData: inputData,
|
|
analysisMode: false
|
|
});
|
|
|
|
return {
|
|
content: result.content || result,
|
|
tokensUsed: result.tokensUsed || 300,
|
|
cost: (result.tokensUsed || 300) * 0.00002,
|
|
llmCalls: result.llmCalls || [
|
|
{ provider: 'gpt4', tokens: 100, cost: 0.002, phase: 'technical_enhancement' },
|
|
{ provider: 'gemini', tokens: 100, cost: 0.001, phase: 'transition_enhancement' },
|
|
{ provider: 'mistral', tokens: 100, cost: 0.0005, phase: 'style_enhancement' }
|
|
],
|
|
phases: {
|
|
selectiveEnhancement: result.stats
|
|
},
|
|
beforeAfter: {
|
|
before: beforeContent,
|
|
after: result.content || result
|
|
}
|
|
};
|
|
} catch (error) {
|
|
logSh(`❌ Erreur Selective: ${error.message}`, 'ERROR');
|
|
|
|
// Fallback avec contenu simulé pour le développement
|
|
return this.createFallbackContent('selective', inputData, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute Adversarial Generation
|
|
*/
|
|
async executeAdversarial(inputData, options = {}) {
|
|
try {
|
|
const { applyPredefinedStack: applyAdversarialStack } = require('./adversarial-generation/AdversarialLayers');
|
|
|
|
logSh('🎯 Démarrage Adversarial Generation', 'DEBUG');
|
|
|
|
const config = {
|
|
adversarialMode: options.adversarialMode || 'standard',
|
|
temperature: options.temperature || 1.0,
|
|
antiDetectionLevel: options.antiDetectionLevel || 'medium'
|
|
};
|
|
|
|
// Vérifier si on a du contenu à transformer
|
|
let contentToTransform = null;
|
|
|
|
if (options.inputContent && Object.keys(options.inputContent).length > 0) {
|
|
contentToTransform = options.inputContent;
|
|
} else {
|
|
// Fallback: créer un contenu basique pour le test
|
|
logSh('⚠️ Pas de contenu d\'entrée, création d\'un contenu basique pour test', 'WARNING');
|
|
contentToTransform = {
|
|
'Titre_H1': inputData.t0 || 'Titre principal',
|
|
'Introduction': `Contenu sur ${inputData.mc0}`,
|
|
'Contenu_Principal': `Développement du sujet ${inputData.mc0}`,
|
|
'Conclusion': `Conclusion sur ${inputData.mc0}`
|
|
};
|
|
}
|
|
|
|
const beforeContent = JSON.parse(JSON.stringify(contentToTransform)); // Deep copy
|
|
|
|
// Mapping des modes vers les stacks prédéfinies
|
|
const modeToStack = {
|
|
'light': 'lightDefense',
|
|
'standard': 'standardDefense',
|
|
'heavy': 'heavyDefense',
|
|
'none': 'none',
|
|
'adaptive': 'adaptive'
|
|
};
|
|
|
|
const stackName = modeToStack[config.adversarialMode] || 'standardDefense';
|
|
logSh(`🎯 Adversarial avec stack: ${stackName} (mode: ${config.adversarialMode})`, 'DEBUG');
|
|
|
|
const result = await applyAdversarialStack(contentToTransform, stackName, {
|
|
csvData: inputData,
|
|
detectorTarget: config.detectorTarget || 'general',
|
|
intensity: config.intensity || 1.0
|
|
});
|
|
|
|
return {
|
|
content: result.content || result,
|
|
tokensUsed: result.tokensUsed || 200,
|
|
cost: (result.tokensUsed || 200) * 0.00002,
|
|
llmCalls: result.llmCalls || [
|
|
{ provider: 'claude', tokens: 100, cost: 0.002, phase: 'adversarial_generation' },
|
|
{ provider: 'mistral', tokens: 100, cost: 0.0005, phase: 'adversarial_enhancement' }
|
|
],
|
|
phases: {
|
|
adversarialGeneration: result.stats
|
|
},
|
|
beforeAfter: {
|
|
before: beforeContent,
|
|
after: result.content || result
|
|
}
|
|
};
|
|
} catch (error) {
|
|
logSh(`❌ Erreur Adversarial: ${error.message}`, 'ERROR');
|
|
|
|
return this.createFallbackContent('adversarial', inputData, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute Human Simulation
|
|
*/
|
|
async executeHumanSimulation(inputData, options = {}) {
|
|
try {
|
|
const { applyPredefinedSimulation } = require('./human-simulation/HumanSimulationLayers');
|
|
|
|
logSh('🎯 Démarrage Human Simulation', 'DEBUG');
|
|
|
|
const config = {
|
|
humanSimulationMode: options.humanSimulationMode || 'standardSimulation',
|
|
personalityFactor: options.personalityFactor || 0.7,
|
|
fatigueLevel: options.fatigueLevel || 'medium'
|
|
};
|
|
|
|
// Vérifier si on a du contenu à humaniser
|
|
let contentToHumanize = null;
|
|
|
|
if (options.inputContent && Object.keys(options.inputContent).length > 0) {
|
|
contentToHumanize = options.inputContent;
|
|
} else {
|
|
// Fallback: créer un contenu basique pour le test
|
|
logSh('⚠️ Pas de contenu d\'entrée, création d\'un contenu basique pour test', 'WARNING');
|
|
contentToHumanize = {
|
|
'Titre_H1': inputData.t0 || 'Titre principal',
|
|
'Introduction': `Contenu sur ${inputData.mc0}`,
|
|
'Contenu_Principal': `Développement du sujet ${inputData.mc0}`,
|
|
'Conclusion': `Conclusion sur ${inputData.mc0}`
|
|
};
|
|
}
|
|
|
|
const beforeContent = JSON.parse(JSON.stringify(contentToHumanize)); // Deep copy
|
|
|
|
const simulationMode = config.humanSimulationMode || 'standardSimulation';
|
|
logSh(`🎯 Human Simulation avec mode: ${simulationMode}`, 'DEBUG');
|
|
|
|
const result = await applyPredefinedSimulation(contentToHumanize, simulationMode, {
|
|
csvData: inputData,
|
|
...config
|
|
});
|
|
|
|
return {
|
|
content: result.content || result,
|
|
tokensUsed: result.tokensUsed || 180,
|
|
cost: (result.tokensUsed || 180) * 0.00002,
|
|
llmCalls: result.llmCalls || [
|
|
{ provider: 'gemini', tokens: 90, cost: 0.0009, phase: 'human_simulation' },
|
|
{ provider: 'claude', tokens: 90, cost: 0.0018, phase: 'personality_application' }
|
|
],
|
|
phases: {
|
|
humanSimulation: result.stats
|
|
},
|
|
beforeAfter: {
|
|
before: beforeContent,
|
|
after: result.content || result
|
|
}
|
|
};
|
|
} catch (error) {
|
|
logSh(`❌ Erreur Human Simulation: ${error.message}`, 'ERROR');
|
|
|
|
return this.createFallbackContent('human-simulation', inputData, error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute Pattern Breaking
|
|
*/
|
|
async executePatternBreaking(inputData, options = {}) {
|
|
try {
|
|
const { applyPatternBreakingStack } = require('./pattern-breaking/PatternBreakingLayers');
|
|
|
|
logSh('🎯 Démarrage Pattern Breaking', 'DEBUG');
|
|
|
|
const config = {
|
|
patternBreakingMode: options.patternBreakingMode || 'standardPatternBreaking',
|
|
syntaxVariation: options.syntaxVariation || 0.6,
|
|
connectorDiversity: options.connectorDiversity || 0.8
|
|
};
|
|
|
|
// Vérifier si on a du contenu à transformer
|
|
let contentToTransform = null;
|
|
|
|
if (options.inputContent && Object.keys(options.inputContent).length > 0) {
|
|
contentToTransform = options.inputContent;
|
|
} else {
|
|
// Fallback: créer un contenu basique pour le test
|
|
logSh('⚠️ Pas de contenu d\'entrée, création d\'un contenu basique pour test', 'WARNING');
|
|
contentToTransform = {
|
|
'Titre_H1': inputData.t0 || 'Titre principal',
|
|
'Introduction': `Contenu sur ${inputData.mc0}`,
|
|
'Contenu_Principal': `Développement du sujet ${inputData.mc0}`,
|
|
'Conclusion': `Conclusion sur ${inputData.mc0}`
|
|
};
|
|
}
|
|
|
|
const beforeContent = JSON.parse(JSON.stringify(contentToTransform)); // Deep copy
|
|
|
|
const patternMode = config.patternBreakingMode || 'standardPatternBreaking';
|
|
logSh(`🎯 Pattern Breaking avec mode: ${patternMode}`, 'DEBUG');
|
|
|
|
const result = await applyPatternBreakingStack(contentToTransform, patternMode, {
|
|
csvData: inputData,
|
|
...config
|
|
});
|
|
|
|
return {
|
|
content: result.content || result,
|
|
tokensUsed: result.tokensUsed || 120,
|
|
cost: (result.tokensUsed || 120) * 0.00002,
|
|
llmCalls: result.llmCalls || [
|
|
{ provider: 'gpt4', tokens: 60, cost: 0.0012, phase: 'pattern_analysis' },
|
|
{ provider: 'mistral', tokens: 60, cost: 0.0003, phase: 'pattern_breaking' }
|
|
],
|
|
phases: {
|
|
patternBreaking: result.stats
|
|
},
|
|
beforeAfter: {
|
|
before: beforeContent,
|
|
after: result.content || result
|
|
}
|
|
};
|
|
} catch (error) {
|
|
logSh(`❌ Erreur Pattern Breaking: ${error.message}`, 'ERROR');
|
|
|
|
return this.createFallbackContent('pattern-breaking', inputData, error);
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// HELPERS ET FORMATAGE
|
|
// ========================================
|
|
|
|
/**
|
|
* Préprocesse les données d'entrée
|
|
*/
|
|
preprocessInputData(inputData) {
|
|
return {
|
|
mc0: inputData.mc0 || 'mot-clé principal',
|
|
t0: inputData.t0 || 'titre principal',
|
|
mcPlus1: inputData.mcPlus1 || '',
|
|
tPlus1: inputData.tPlus1 || '',
|
|
personality: inputData.personality || { nom: 'Test', style: 'neutre' },
|
|
xmlTemplate: inputData.xmlTemplate || this.getDefaultTemplate(),
|
|
// Ajout d'un contexte pour les modules
|
|
context: {
|
|
timestamp: Date.now(),
|
|
source: 'step-by-step',
|
|
debug: true
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Post-traite le résultat
|
|
*/
|
|
async postprocessResult(rawResult, system) {
|
|
// Si le résultat est juste une chaîne, la transformer en objet
|
|
if (typeof rawResult === 'string') {
|
|
return {
|
|
content: { 'Contenu': rawResult },
|
|
tokensUsed: Math.floor(rawResult.length / 4), // Estimation
|
|
cost: 0.001,
|
|
llmCalls: [{ provider: 'unknown', tokens: 50, cost: 0.001 }]
|
|
};
|
|
}
|
|
|
|
// Si c'est déjà un objet structuré, le retourner tel quel
|
|
if (rawResult && typeof rawResult === 'object') {
|
|
return rawResult;
|
|
}
|
|
|
|
// Fallback
|
|
return {
|
|
content: { 'Résultat': String(rawResult) },
|
|
tokensUsed: 50,
|
|
cost: 0.001,
|
|
llmCalls: []
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Formate la sortie selon le format demandé
|
|
*/
|
|
formatOutput(content, format = 'tag') {
|
|
if (!content || typeof content !== 'object') {
|
|
return String(content || 'Pas de contenu');
|
|
}
|
|
|
|
switch (format) {
|
|
case 'tag':
|
|
return Object.entries(content)
|
|
.map(([tag, text]) => `[${tag}]\n${text}`)
|
|
.join('\n\n');
|
|
|
|
case 'xml':
|
|
return Object.entries(content)
|
|
.map(([tag, text]) => `<${tag.toLowerCase()}>${text}</${tag.toLowerCase()}>`)
|
|
.join('\n');
|
|
|
|
case 'json':
|
|
return JSON.stringify(content, null, 2);
|
|
|
|
default:
|
|
return this.formatOutput(content, 'tag');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Crée un contenu de fallback pour les erreurs
|
|
*/
|
|
createFallbackContent(system, inputData, error) {
|
|
const fallbackContent = {
|
|
'Titre_H1': `${inputData.t0} - Traité par ${system}`,
|
|
'Introduction': `Contenu généré en mode ${system} pour "${inputData.mc0}".`,
|
|
'Contenu_Principal': `Ceci est un contenu de démonstration pour le système ${system}.
|
|
En production, ce contenu serait généré par l'IA avec les paramètres spécifiés.`,
|
|
'Note_Technique': `⚠️ Mode fallback activé - Erreur: ${error.message}`
|
|
};
|
|
|
|
return {
|
|
content: fallbackContent,
|
|
tokensUsed: 100,
|
|
cost: 0.002,
|
|
llmCalls: [
|
|
{ provider: 'fallback', tokens: 100, cost: 0.002, error: error.message }
|
|
],
|
|
fallback: true
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Template XML par défaut
|
|
*/
|
|
getDefaultTemplate() {
|
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
<article>
|
|
<h1>|Titre_H1{{T0}}{Titre principal optimisé}|</h1>
|
|
<intro>|Introduction{{MC0}}{Introduction engageante}|</intro>
|
|
<content>|Contenu_Principal{{MC0,T0}}{Contenu principal détaillé}|</content>
|
|
<conclusion>|Conclusion{{T0}}{Conclusion percutante}|</conclusion>
|
|
</article>`;
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
StepExecutor
|
|
}; |