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>
547 lines
19 KiB
JavaScript
547 lines
19 KiB
JavaScript
// ========================================
|
|
// FICHIER: lib/element-extraction.js - CONVERTI POUR NODE.JS
|
|
// Description: Extraction et parsing des éléments XML
|
|
// ========================================
|
|
|
|
// 🔄 NODE.JS IMPORTS
|
|
const { logSh } = require('./ErrorReporting');
|
|
const { logElementsList } = require('./selective-enhancement/SelectiveUtils');
|
|
|
|
// ============= EXTRACTION PRINCIPALE =============
|
|
|
|
async function extractElements(xmlTemplate, csvData) {
|
|
try {
|
|
await logSh('Extraction éléments avec séparation tag/contenu...', 'DEBUG');
|
|
|
|
const regex = /\|([^|]+)\|/g;
|
|
const elements = [];
|
|
let match;
|
|
|
|
while ((match = regex.exec(xmlTemplate)) !== null) {
|
|
const originalMatch = match[1];
|
|
let fullMatch = match[1]; // Ex: "Titre_H1_1{{T0}}" ou "Titre_H3_3{{MC+1_3}}"
|
|
|
|
// RÉPARER les variables cassées par les balises HTML AVANT de les chercher
|
|
// Ex: <strong>{{</strong>MC+1_1}} → {{MC+1_1}}
|
|
fullMatch = fullMatch
|
|
.replace(/<strong>\{\{<\/strong>/g, '{{')
|
|
.replace(/<strong>\{<\/strong>/g, '{')
|
|
.replace(/<code><strong>\{\{<\/strong><\/code>/g, '{{')
|
|
.replace(/<strong><strong>\{\{<\/strong>/g, '{{')
|
|
.replace(/<\/strong>\}\}<\/strong>/g, '}}')
|
|
.replace(/<\/strong>\}<\/strong>/g, '}')
|
|
.replace(/<\/strong>/g, '') // Enlever </strong> orphelins
|
|
.replace(/<strong>/g, '') // Enlever <strong> orphelins
|
|
.replace(/<code>/g, '') // Enlever <code> orphelins
|
|
.replace(/<\/code>/g, ''); // Enlever </code> orphelins
|
|
|
|
// Log debug si changement
|
|
if (originalMatch !== fullMatch && originalMatch.includes('{{')) {
|
|
await logSh(` 🔧 Réparation HTML: "${originalMatch.substring(0, 80)}" → "${fullMatch.substring(0, 80)}"`, 'DEBUG');
|
|
}
|
|
|
|
// Séparer nom du tag et variables
|
|
const nameMatch = fullMatch.match(/^([^{]+)/);
|
|
let tagName = nameMatch ? nameMatch[1].trim() : fullMatch.split('{')[0];
|
|
tagName = tagName.replace(/<\/?strong>/g, ''); // Nettoyage
|
|
|
|
const variablesMatch = fullMatch.match(/\{\{([^}]+)\}\}/g);
|
|
|
|
// CAPTURER les instructions EN GARDANT les {{variables}} intactes
|
|
// Stratégie : d'abord enlever temporairement toutes les {{variables}},
|
|
// trouver la position de {instruction}, puis revenir au texte original
|
|
let instructionsMatch = null;
|
|
|
|
// Créer une version sans {{variables}} pour trouver où est {instruction}
|
|
const withoutVars = fullMatch.replace(/\{\{[^}]+\}\}/g, '');
|
|
const tempInstructionMatch = withoutVars.match(/\{([^}]+)\}/);
|
|
|
|
if (tempInstructionMatch) {
|
|
// On a trouvé une instruction dans la version sans variables
|
|
// Trouver le PREMIER { qui n'est PAS suivi de { (= début instruction)
|
|
let instructionStart = -1;
|
|
for (let idx = 0; idx < fullMatch.length - 1; idx++) {
|
|
if (fullMatch[idx] === '{' && fullMatch[idx + 1] !== '{') {
|
|
instructionStart = idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (instructionStart !== -1) {
|
|
// Capturer jusqu'à la } de fermeture (en ignorant les }} de variables)
|
|
let depth = 0;
|
|
let instructionEnd = -1;
|
|
let i = instructionStart;
|
|
|
|
while (i < fullMatch.length) {
|
|
if (fullMatch[i] === '{') {
|
|
if (fullMatch[i+1] === '{') {
|
|
// C'est une variable, skip les deux {
|
|
i += 2;
|
|
continue;
|
|
} else {
|
|
depth++;
|
|
}
|
|
} else if (fullMatch[i] === '}') {
|
|
if (fullMatch[i+1] === '}') {
|
|
// Fin de variable, skip les deux }
|
|
i += 2;
|
|
continue;
|
|
} else {
|
|
depth--;
|
|
if (depth === 0) {
|
|
instructionEnd = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (instructionEnd !== -1) {
|
|
const instructionContent = fullMatch.substring(instructionStart + 1, instructionEnd);
|
|
instructionsMatch = [fullMatch.substring(instructionStart, instructionEnd + 1), instructionContent];
|
|
|
|
// Log debug instruction capturée
|
|
await logSh(` 📜 Instruction capturée (${tagName}): ${instructionContent.substring(0, 80)}...`, 'DEBUG');
|
|
}
|
|
}
|
|
}
|
|
|
|
// TAG PUR (sans variables)
|
|
const pureTag = `|${tagName}|`;
|
|
|
|
// RÉSOUDRE le contenu des variables
|
|
const resolvedContent = resolveVariablesContent(variablesMatch, csvData);
|
|
|
|
// RÉSOUDRE aussi les variables DANS les instructions
|
|
let resolvedInstructions = instructionsMatch ? instructionsMatch[1] : null;
|
|
if (resolvedInstructions) {
|
|
const originalInstruction = resolvedInstructions;
|
|
// Remplacer chaque variable {{XX}} par sa valeur résolue
|
|
resolvedInstructions = resolvedInstructions.replace(/\{\{([^}]+)\}\}/g, (match, variable) => {
|
|
const singleVarMatch = [match];
|
|
return resolveVariablesContent(singleVarMatch, csvData);
|
|
});
|
|
// Log si changement
|
|
if (originalInstruction !== resolvedInstructions && originalInstruction.includes('{{')) {
|
|
await logSh(` ✨ Instructions résolues (${tagName}): ${originalInstruction.substring(0, 60)} → ${resolvedInstructions.substring(0, 60)}`, 'DEBUG');
|
|
}
|
|
}
|
|
|
|
elements.push({
|
|
originalTag: pureTag, // ← TAG PUR : |Titre_H3_3|
|
|
name: tagName, // ← Titre_H3_3
|
|
variables: variablesMatch || [], // ← [{{MC+1_3}}]
|
|
resolvedContent: resolvedContent, // ← "Plaque de rue en aluminium"
|
|
instructions: resolvedInstructions, // ← Instructions avec variables résolues
|
|
type: getElementType(tagName),
|
|
originalFullMatch: fullMatch // ← Backup si besoin
|
|
});
|
|
|
|
await logSh(`Tag séparé: ${pureTag} → "${resolvedContent}"`, 'DEBUG');
|
|
}
|
|
|
|
await logSh(`${elements.length} éléments extraits avec séparation`, 'INFO');
|
|
|
|
// 📊 DÉTAIL DES ÉLÉMENTS EXTRAITS
|
|
logElementsList(elements, 'ÉLÉMENTS EXTRAITS (depuis XML + Google Sheets)');
|
|
|
|
return elements;
|
|
|
|
} catch (error) {
|
|
await logSh(`Erreur extractElements: ${error}`, 'ERROR');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// ============= RÉSOLUTION VARIABLES - IDENTIQUE =============
|
|
|
|
function resolveVariablesContent(variablesMatch, csvData) {
|
|
if (!variablesMatch || variablesMatch.length === 0) {
|
|
return ""; // Pas de variables à résoudre
|
|
}
|
|
|
|
let resolvedContent = "";
|
|
|
|
variablesMatch.forEach(variable => {
|
|
const cleanVar = variable.replace(/[{}]/g, ''); // Enlever {{ }}
|
|
|
|
switch (cleanVar) {
|
|
case 'T0':
|
|
resolvedContent += csvData.t0;
|
|
break;
|
|
case 'MC0':
|
|
resolvedContent += csvData.mc0;
|
|
break;
|
|
case 'T-1':
|
|
resolvedContent += csvData.tMinus1;
|
|
break;
|
|
case 'L-1':
|
|
resolvedContent += csvData.lMinus1;
|
|
break;
|
|
default:
|
|
// Gérer MC+1_1, MC+1_2, etc.
|
|
if (cleanVar.startsWith('MC+1_')) {
|
|
const index = parseInt(cleanVar.split('_')[1]) - 1;
|
|
const mcPlus1 = csvData.mcPlus1.split(',').map(s => s.trim());
|
|
resolvedContent += mcPlus1[index] || `[${cleanVar} non défini]`;
|
|
}
|
|
else if (cleanVar.startsWith('T+1_')) {
|
|
const index = parseInt(cleanVar.split('_')[1]) - 1;
|
|
const tPlus1 = csvData.tPlus1.split(',').map(s => s.trim());
|
|
resolvedContent += tPlus1[index] || `[${cleanVar} non défini]`;
|
|
}
|
|
else if (cleanVar.startsWith('L+1_')) {
|
|
const index = parseInt(cleanVar.split('_')[1]) - 1;
|
|
const lPlus1 = csvData.lPlus1.split(',').map(s => s.trim());
|
|
resolvedContent += lPlus1[index] || `[${cleanVar} non défini]`;
|
|
}
|
|
else {
|
|
resolvedContent += `[${cleanVar} non résolu]`;
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
|
|
return resolvedContent;
|
|
}
|
|
|
|
// ============= CLASSIFICATION ÉLÉMENTS - IDENTIQUE =============
|
|
|
|
function getElementType(name) {
|
|
if (name.includes('Titre_H1')) return 'titre_h1';
|
|
if (name.includes('Titre_H2')) return 'titre_h2';
|
|
if (name.includes('Titre_H3')) return 'titre_h3';
|
|
if (name.includes('Intro_')) return 'intro';
|
|
if (name.includes('Txt_')) return 'texte';
|
|
if (name.includes('Faq_q')) return 'faq_question';
|
|
if (name.includes('Faq_a')) return 'faq_reponse';
|
|
if (name.includes('Faq_H3')) return 'faq_titre';
|
|
return 'autre';
|
|
}
|
|
|
|
// ============= GÉNÉRATION SÉQUENTIELLE - ADAPTÉE =============
|
|
|
|
async function generateAllContent(elements, csvData, xmlTemplate) {
|
|
await logSh(`Début génération pour ${elements.length} éléments`, 'INFO');
|
|
|
|
const generatedContent = {};
|
|
|
|
for (let index = 0; index < elements.length; index++) {
|
|
const element = elements[index];
|
|
|
|
try {
|
|
await logSh(`Élément ${index + 1}/${elements.length}: ${element.name}`, 'DEBUG');
|
|
|
|
const prompt = createPromptForElement(element, csvData);
|
|
|
|
// 🔄 NODE.JS : Import callOpenAI depuis LLM manager (le prompt/réponse seront loggés par LLMManager)
|
|
const { callLLM } = require('./LLMManager');
|
|
const content = await callLLM('gpt-4o-mini', prompt, {}, csvData.personality);
|
|
|
|
generatedContent[element.originalTag] = content;
|
|
|
|
// 🔄 NODE.JS : Pas de Utilities.sleep(), les appels API gèrent leur rate limiting
|
|
|
|
} catch (error) {
|
|
await logSh(`ERREUR élément ${element.name}: ${error.toString()}`, 'ERROR');
|
|
generatedContent[element.originalTag] = `[Erreur génération: ${element.name}]`;
|
|
}
|
|
}
|
|
|
|
await logSh(`Génération terminée. ${Object.keys(generatedContent).length} éléments`, 'INFO');
|
|
return generatedContent;
|
|
}
|
|
|
|
// ============= PARSING STRUCTURE - IDENTIQUE =============
|
|
|
|
function parseElementStructure(element) {
|
|
// NETTOYER le nom : enlever <strong>, </strong>, {{...}}, {...}
|
|
let cleanName = element.name
|
|
.replace(/<\/?strong>/g, '') // ← ENLEVER <strong>
|
|
.replace(/\{\{[^}]*\}\}/g, '') // Enlever {{MC0}}
|
|
.replace(/\{[^}]*\}/g, ''); // Enlever {instructions}
|
|
|
|
const parts = cleanName.split('_');
|
|
|
|
return {
|
|
type: parts[0],
|
|
level: parts[1],
|
|
indices: parts.slice(2).map(Number),
|
|
hierarchyPath: parts.slice(1).join('_'),
|
|
originalElement: element,
|
|
variables: element.variables || [],
|
|
instructions: element.instructions
|
|
};
|
|
}
|
|
|
|
// ============= HIÉRARCHIE INTELLIGENTE - ADAPTÉE =============
|
|
|
|
async function buildSmartHierarchy(elements) {
|
|
await logSh(`🏗️ CONSTRUCTION HIÉRARCHIE - Début avec ${elements.length} éléments`, 'INFO');
|
|
|
|
const hierarchy = {};
|
|
|
|
elements.forEach((element, index) => {
|
|
const structure = parseElementStructure(element);
|
|
const path = structure.hierarchyPath;
|
|
|
|
// 📊 LOG: Détailler chaque élément traité
|
|
logSh(` [${index + 1}/${elements.length}] ${element.name}`, 'DEBUG');
|
|
logSh(` 📍 Path: ${path}`, 'DEBUG');
|
|
logSh(` 📝 Type: ${structure.type}`, 'DEBUG');
|
|
logSh(` 📄 ResolvedContent: "${element.resolvedContent}"`, 'DEBUG');
|
|
logSh(` 📜 Instructions: "${element.instructions ? element.instructions.substring(0, 80) : 'AUCUNE'}"`, 'DEBUG');
|
|
|
|
if (!hierarchy[path]) {
|
|
hierarchy[path] = {
|
|
title: null,
|
|
text: null,
|
|
questions: [],
|
|
children: {}
|
|
};
|
|
}
|
|
|
|
// Associer intelligemment
|
|
if (structure.type === 'Titre') {
|
|
hierarchy[path].title = structure; // Tout l'objet avec variables + instructions
|
|
logSh(` ✅ Assigné comme TITRE dans hiérarchie[${path}].title`, 'DEBUG');
|
|
} else if (structure.type === 'Txt') {
|
|
hierarchy[path].text = structure;
|
|
logSh(` ✅ Assigné comme TEXTE dans hiérarchie[${path}].text`, 'DEBUG');
|
|
} else if (structure.type === 'Intro') {
|
|
hierarchy[path].text = structure;
|
|
logSh(` ✅ Assigné comme INTRO dans hiérarchie[${path}].text`, 'DEBUG');
|
|
} else if (structure.type === 'Faq') {
|
|
hierarchy[path].questions.push(structure);
|
|
logSh(` ✅ Ajouté comme FAQ dans hiérarchie[${path}].questions`, 'DEBUG');
|
|
}
|
|
});
|
|
|
|
// 📊 LOG: Résumé de la hiérarchie construite
|
|
const mappingSummary = Object.keys(hierarchy).map(path => {
|
|
const section = hierarchy[path];
|
|
return `${path}:[T:${section.title ? '✓' : '✗'} Txt:${section.text ? '✓' : '✗'} FAQ:${section.questions.length}]`;
|
|
}).join(' | ');
|
|
|
|
await logSh(`📊 HIÉRARCHIE CONSTRUITE: ${Object.keys(hierarchy).length} sections`, 'INFO');
|
|
await logSh(` ${mappingSummary}`, 'INFO');
|
|
|
|
// 📊 LOG: Détail complet d'une section exemple
|
|
const firstPath = Object.keys(hierarchy)[0];
|
|
if (firstPath) {
|
|
const firstSection = hierarchy[firstPath];
|
|
await logSh(`📋 EXEMPLE SECTION [${firstPath}]:`, 'DEBUG');
|
|
if (firstSection.title) {
|
|
await logSh(` 📌 Title.instructions: "${firstSection.title.instructions ? firstSection.title.instructions.substring(0, 100) : 'AUCUNE'}"`, 'DEBUG');
|
|
}
|
|
if (firstSection.text) {
|
|
await logSh(` 📌 Text.instructions: "${firstSection.text.instructions ? firstSection.text.instructions.substring(0, 100) : 'AUCUNE'}"`, 'DEBUG');
|
|
}
|
|
}
|
|
|
|
return hierarchy;
|
|
}
|
|
|
|
// ============= PARSERS RÉPONSES - ADAPTÉS =============
|
|
|
|
async function parseTitlesResponse(response, allTitles) {
|
|
const results = {};
|
|
|
|
// Utiliser regex pour extraire [TAG] contenu
|
|
const regex = /\[([^\]]+)\]\s*\n([^[]*?)(?=\n\[|$)/gs;
|
|
let match;
|
|
|
|
while ((match = regex.exec(response)) !== null) {
|
|
const tag = match[1].trim();
|
|
const content = match[2].trim();
|
|
|
|
// Nettoyer le contenu (enlever # et balises HTML si présentes)
|
|
const cleanContent = content
|
|
.replace(/^#+\s*/, '') // Enlever # du début
|
|
.replace(/<\/?[^>]+(>|$)/g, ""); // Enlever balises HTML
|
|
|
|
results[`|${tag}|`] = cleanContent;
|
|
|
|
await logSh(`✓ Titre parsé [${tag}]: "${cleanContent}"`, 'DEBUG');
|
|
}
|
|
|
|
// Fallback si parsing échoue
|
|
if (Object.keys(results).length === 0) {
|
|
await logSh('Parsing titres échoué, fallback ligne par ligne', 'WARNING');
|
|
const lines = response.split('\n').filter(line => line.trim());
|
|
|
|
allTitles.forEach((titleInfo, index) => {
|
|
if (lines[index]) {
|
|
results[titleInfo.tag] = lines[index].trim();
|
|
}
|
|
});
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
async function parseTextsResponse(response, allTexts) {
|
|
const results = {};
|
|
|
|
await logSh('Parsing réponse textes avec vrais tags...', 'DEBUG');
|
|
|
|
// Utiliser regex pour extraire [TAG] contenu avec les vrais noms
|
|
const regex = /\[([^\]]+)\]\s*\n([^[]*?)(?=\n\[|$)/gs;
|
|
let match;
|
|
|
|
while ((match = regex.exec(response)) !== null) {
|
|
const tag = match[1].trim();
|
|
const content = match[2].trim();
|
|
|
|
// Nettoyer le contenu
|
|
const cleanContent = content.replace(/^#+\s*/, '').replace(/<\/?[^>]+(>|$)/g, "");
|
|
|
|
results[`|${tag}|`] = cleanContent;
|
|
|
|
await logSh(`✓ Texte parsé [${tag}]: "${cleanContent}"`, 'DEBUG');
|
|
}
|
|
|
|
// Fallback si parsing échoue - mapper par position
|
|
if (Object.keys(results).length === 0) {
|
|
await logSh('Parsing textes échoué, fallback ligne par ligne', 'WARNING');
|
|
|
|
const lines = response.split('\n')
|
|
.map(line => line.trim())
|
|
.filter(line => line.length > 0 && !line.startsWith('['));
|
|
|
|
for (let index = 0; index < allTexts.length; index++) {
|
|
const textInfo = allTexts[index];
|
|
if (index < lines.length) {
|
|
let content = lines[index];
|
|
content = content.replace(/^\d+\.\s*/, ''); // Enlever "1. " si présent
|
|
results[textInfo.tag] = content;
|
|
|
|
await logSh(`✓ Texte fallback ${index + 1} → ${textInfo.tag}: "${content}"`, 'DEBUG');
|
|
} else {
|
|
await logSh(`✗ Pas assez de lignes pour ${textInfo.tag}`, 'WARNING');
|
|
results[textInfo.tag] = `[Texte manquant ${index + 1}]`;
|
|
}
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
// ============= PARSER FAQ SPÉCIALISÉ - ADAPTÉ =============
|
|
|
|
async function parseFAQPairsResponse(response, faqPairs) {
|
|
const results = {};
|
|
|
|
await logSh('Parsing réponse paires FAQ...', 'DEBUG');
|
|
|
|
// Parser avec regex pour capturer question + réponse
|
|
const regex = /\[([^\]]+)\]\s*\n([^[]*?)(?=\n\[|$)/gs;
|
|
let match;
|
|
|
|
const parsedItems = {};
|
|
|
|
while ((match = regex.exec(response)) !== null) {
|
|
const tag = match[1].trim();
|
|
const content = match[2].trim();
|
|
|
|
const cleanContent = content.replace(/^#+\s*/, '').replace(/<\/?[^>]+(>|$)/g, "");
|
|
|
|
parsedItems[tag] = cleanContent;
|
|
|
|
await logSh(`✓ Item FAQ parsé [${tag}]: "${cleanContent}"`, 'DEBUG');
|
|
}
|
|
|
|
// Mapper aux tags originaux avec |
|
|
Object.keys(parsedItems).forEach(cleanTag => {
|
|
const content = parsedItems[cleanTag];
|
|
results[`|${cleanTag}|`] = content;
|
|
});
|
|
|
|
// Vérification de cohérence paires
|
|
let pairsCompletes = 0;
|
|
for (const pair of faqPairs) {
|
|
const hasQuestion = results[pair.question.tag];
|
|
const hasAnswer = results[pair.answer.tag];
|
|
|
|
if (hasQuestion && hasAnswer) {
|
|
pairsCompletes++;
|
|
await logSh(`✓ Paire FAQ ${pair.number} complète: Q+R`, 'DEBUG');
|
|
} else {
|
|
await logSh(`⚠ Paire FAQ ${pair.number} incomplète: Q=${!!hasQuestion} R=${!!hasAnswer}`, 'WARNING');
|
|
}
|
|
}
|
|
|
|
await logSh(`${pairsCompletes}/${faqPairs.length} paires FAQ complètes`, 'INFO');
|
|
|
|
// FATAL si paires FAQ manquantes
|
|
if (pairsCompletes < faqPairs.length) {
|
|
const manquantes = faqPairs.length - pairsCompletes;
|
|
await logSh(`❌ FATAL: ${manquantes} paires FAQ manquantes sur ${faqPairs.length}`, 'ERROR');
|
|
throw new Error(`FATAL: Génération FAQ incomplète (${manquantes}/${faqPairs.length} manquantes) - arrêt du workflow`);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
async function parseOtherElementsResponse(response, allOtherElements) {
|
|
const results = {};
|
|
|
|
await logSh('Parsing réponse autres éléments...', 'DEBUG');
|
|
|
|
const regex = /\[([^\]]+)\]\s*\n([^[]*?)(?=\n\[|$)/gs;
|
|
let match;
|
|
|
|
while ((match = regex.exec(response)) !== null) {
|
|
const tag = match[1].trim();
|
|
const content = match[2].trim();
|
|
|
|
const cleanContent = content.replace(/^#+\s*/, '').replace(/<\/?[^>]+(>|$)/g, "");
|
|
|
|
results[`|${tag}|`] = cleanContent;
|
|
|
|
await logSh(`✓ Autre élément parsé [${tag}]: "${cleanContent}"`, 'DEBUG');
|
|
}
|
|
|
|
// Fallback si parsing partiel
|
|
if (Object.keys(results).length < allOtherElements.length) {
|
|
await logSh('Parsing autres éléments partiel, complétion fallback', 'WARNING');
|
|
|
|
const lines = response.split('\n')
|
|
.map(line => line.trim())
|
|
.filter(line => line.length > 0 && !line.startsWith('['));
|
|
|
|
allOtherElements.forEach((element, index) => {
|
|
if (!results[element.tag] && lines[index]) {
|
|
results[element.tag] = lines[index];
|
|
}
|
|
});
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
// ============= HELPER FUNCTIONS - ADAPTÉES =============
|
|
|
|
function createPromptForElement(element, csvData) {
|
|
// Cette fonction sera probablement définie dans content-generation.js
|
|
// Pour l'instant, retour basique
|
|
return `Génère du contenu pour ${element.type}: ${element.resolvedContent}`;
|
|
}
|
|
|
|
|
|
// 🔄 NODE.JS EXPORTS
|
|
module.exports = {
|
|
extractElements,
|
|
resolveVariablesContent,
|
|
getElementType,
|
|
generateAllContent,
|
|
parseElementStructure,
|
|
buildSmartHierarchy,
|
|
parseTitlesResponse,
|
|
parseTextsResponse,
|
|
parseFAQPairsResponse,
|
|
parseOtherElementsResponse,
|
|
createPromptForElement
|
|
}; |