28 KiB
28 KiB
diff --git a/.gitignore b/.gitignore
index cff1b08..7217f01 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,8 +53,7 @@ test_*.js
*_debug.js
test-*.js
-# HTML généré (logs viewer)
-logs-viewer.html
+# HTML généré était ici mais maintenant on le garde dans tools/
# Unit test reports
TEST*.xml
diff --git a/code.js b/code.js
index a1dcd72..a2bdb22 100644
--- a/code.js
+++ b/code.js
@@ -1,6 +1,6 @@
/*
code.js — bundle concaténé
- Généré: 2025-09-03T04:21:57.159Z
+ Généré: 2025-09-04T01:10:08.540Z
Source: lib
Fichiers: 16
Ordre: topo
@@ -57,12 +57,13 @@ const fileDest = pino.destination({
});
tee.pipe(fileDest);
-// Custom levels for Pino to include TRACE and PROMPT
+// Custom levels for Pino to include TRACE, PROMPT, and LLM
const customLevels = {
trace: 5, // Below debug (10)
debug: 10,
info: 20,
prompt: 25, // New level for prompts (between info and warn)
+ llm: 26, // New level for LLM interactions (between prompt and warn)
warn: 30,
error: 40,
fatal: 50
@@ -178,6 +179,9 @@ async function logSh(message, level = 'INFO') {
case 'prompt':
logger.prompt(traceData, message);
break;
+ case 'llm':
+ logger.llm(traceData, message);
+ break;
default:
logger.info(traceData, message);
}
@@ -1282,7 +1286,10 @@ async function callLLM(llmProvider, prompt, options = {}, personality = null) {
// 📢 AFFICHAGE PROMPT COMPLET POUR DEBUG AVEC INFO IA
logSh(`\n🔍 ===== PROMPT ENVOYÉ À ${llmProvider.toUpperCase()} (${config.model}) | PERSONNALITÉ: ${personality?.nom || 'AUCUNE'} =====`, 'PROMPT');
logSh(prompt, 'PROMPT');
- logSh(`===== FIN PROMPT ${llmProvider.toUpperCase()} (${personality?.nom || 'AUCUNE'}) =====\n`, 'PROMPT');
+
+ // 📤 LOG LLM REQUEST COMPLET
+ logSh(`📤 LLM REQUEST [${llmProvider.toUpperCase()}] (${config.model}) | Personnalité: ${personality?.nom || 'AUCUNE'}`, 'LLM');
+ logSh(prompt, 'LLM');
// Préparer la requête selon le provider
const requestData = buildRequestData(llmProvider, prompt, options, personality);
@@ -1293,8 +1300,12 @@ async function callLLM(llmProvider, prompt, options = {}, personality = null) {
// Parser la réponse selon le format du provider
const content = parseResponse(llmProvider, response);
+ // 📥 LOG LLM RESPONSE COMPLET
+ logSh(`📥 LLM RESPONSE [${llmProvider.toUpperCase()}] (${config.model}) | Durée: ${Date.now() - startTime}ms`, 'LLM');
+ logSh(content, 'LLM');
+
const duration = Date.now() - startTime;
- logSh(`✅ ${llmProvider.toUpperCase()} (${personality?.nom || 'sans personnalité'}) réponse en ${duration}ms: "${content.substring(0, 150)}${content.length > 150 ? '...' : ''}"`, 'INFO');
+ logSh(`✅ ${llmProvider.toUpperCase()} (${personality?.nom || 'sans personnalité'}) réponse en ${duration}ms`, 'INFO');
// Enregistrer les stats d'usage
await recordUsageStats(llmProvider, prompt.length, content.length, duration);
@@ -1727,6 +1738,8 @@ module.exports = {
LLM_CONFIG
};
+
+
/*
┌────────────────────────────────────────────────────────────────────┐
│ File: lib/ElementExtraction.js │
@@ -2309,38 +2322,21 @@ CONTEXTE:
`;
missingElements.forEach((missing, index) => {
- prompt += `${index + 1}. [${missing.name}] `;
-
- // INSTRUCTIONS SPÉCIFIQUES PAR TYPE
- if (missing.type.includes('titre_h1')) {
- prompt += `→ Titre H1 principal (8-10 mots) pour ${contextAnalysis.mainKeyword}\n`;
- } else if (missing.type.includes('titre_h2')) {
- prompt += `→ Titre H2 section (6-8 mots) lié à ${contextAnalysis.mainKeyword}\n`;
- } else if (missing.type.includes('titre_h3')) {
- prompt += `→ Sous-titre H3 (4-6 mots) spécialisé ${contextAnalysis.mainKeyword}\n`;
- } else if (missing.type.includes('texte') || missing.type.includes('txt')) {
- prompt += `→ Thème/sujet pour paragraphe 150 mots sur ${contextAnalysis.mainKeyword}\n`;
- } else if (missing.type.includes('faq_question')) {
- prompt += `→ Question client directe sur ${contextAnalysis.mainKeyword} (8-12 mots)\n`;
- } else if (missing.type.includes('faq_reponse')) {
- prompt += `→ Thème réponse experte ${contextAnalysis.mainKeyword} (2-4 mots)\n`;
- } else {
- prompt += `→ Expression/mot-clé pertinent ${contextAnalysis.mainKeyword}\n`;
- }
+ prompt += `${index + 1}. [${missing.name}] → Mot-clé SEO\n`;
});
prompt += `\nCONSIGNES:
-- Reste dans le thème ${contextAnalysis.mainKeyword}
-- Varie les angles et expressions
-- Évite répétitions avec mots-clés existants
-- Précis et pertinents
+- Thème: ${contextAnalysis.mainKeyword}
+- Mots-clés SEO naturels
+- Varie les termes
+- Évite répétitions
FORMAT:
[${missingElements[0].name}]
-Expression/mot-clé généré 1
+mot-clé
[${missingElements[1] ? missingElements[1].name : 'exemple'}]
-Expression/mot-clé généré 2
+mot-clé
etc...`;
@@ -2596,6 +2592,11 @@ const { logSh } = require('./ErrorReporting');
const { tracer } = require('./trace.js');
const { selectMultiplePersonalitiesWithAI, getPersonalities } = require('./BrainConfig');
+// Utilitaire pour les délais
+function sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
/**
* NOUVELLE APPROCHE - Multi-Personnalités Batch Enhancement
* 4 personnalités différentes utilisées dans le pipeline pour maximum d'anti-détection
@@ -2804,8 +2805,8 @@ async function generateAllContentBase(hierarchy, csvData, aiProvider) {
}
/**
- * ÉTAPE 2 - Enhancement technique BATCH OPTIMISÉ avec IA configurable
- * OPTIMISATION : 1 appel extraction + 1 appel enhancement au lieu de 20+
+ * ÉTAPE 2 - Enhancement technique ÉLÉMENT PAR ÉLÉMENT avec IA configurable
+ * NOUVEAU : Traitement individuel pour fiabilité maximale et debug précis
*/
async function enhanceAllTechnicalTerms(baseContents, csvData, aiProvider) {
logSh('🔧 === DÉBUT ENHANCEMENT TECHNIQUE ===', 'INFO');
@@ -2872,96 +2873,96 @@ async function enhanceAllTechnicalTerms(baseContents, csvData, aiProvider) {
}
/**
- * NOUVELLE FONCTION : Extraction batch TOUS les termes techniques
+ * Analyser un seul élément pour détecter les termes techniques
*/
-async function extractAllTechnicalTermsBatch(baseContents, csvData, aiProvider) {
- const contentEntries = Object.keys(baseContents);
-
- const batchAnalysisPrompt = `MISSION: Analyser ces ${contentEntries.length} contenus et identifier leurs termes techniques.
+async function analyzeSingleElementTechnicalTerms(tag, content, csvData, aiProvider) {
+ const prompt = `MISSION: Analyser ce contenu et déterminer s'il contient des termes techniques.
CONTEXTE: ${csvData.mc0} - Secteur: signalétique/impression
-CONTENUS À ANALYSER:
-
-${contentEntries.map((tag, i) => `[${i + 1}] TAG: ${tag}
-CONTENU: "${baseContents[tag]}"`).join('\n\n')}
+CONTENU À ANALYSER:
+TAG: ${tag}
+CONTENU: "${content}"
CONSIGNES:
-- Identifie UNIQUEMENT les vrais termes techniques métier/industrie
+- Cherche UNIQUEMENT des vrais termes techniques métier/industrie
- Évite mots génériques (qualité, service, pratique, personnalisé, etc.)
-- Focus: matériaux, procédés, normes, dimensions, technologies
-- Si aucun terme technique → "AUCUN"
+- Focus: matériaux, procédés, normes, dimensions, technologies spécifiques
-EXEMPLES VALIDES: dibond, impression UV, fraisage CNC, épaisseur 3mm, aluminium brossé
-EXEMPLES INVALIDES: durable, pratique, personnalisé, moderne, esthétique
+EXEMPLES VALIDES: dibond, impression UV, fraisage CNC, épaisseur 3mm, aluminium brossé, anodisation
+EXEMPLES INVALIDES: durable, pratique, personnalisé, moderne, esthétique, haute performance
-FORMAT RÉPONSE EXACT:
-[1] dibond, impression UV, 3mm OU AUCUN
-[2] aluminium, fraisage CNC OU AUCUN
-[3] AUCUN
-etc... (${contentEntries.length} lignes total)`;
+RÉPONSE REQUISE:
+- Si termes techniques trouvés: "OUI - termes: [liste des termes séparés par virgules]"
+- Si aucun terme technique: "NON"
+
+EXEMPLE:
+OUI - termes: aluminium composite, impression numérique, gravure laser`;
try {
- const analysisResponse = await callLLM(aiProvider, batchAnalysisPrompt, {
- temperature: 0.3,
- maxTokens: 2000
- }, csvData.personality);
-
- return parseAllTechnicalTermsResponse(analysisResponse, baseContents, contentEntries);
-
+ const response = await callLLM(aiProvider, prompt, { temperature: 0.3 });
+
+ if (response.toUpperCase().startsWith('OUI')) {
+ // Extraire les termes de la réponse
+ const termsMatch = response.match(/termes:\s*(.+)/i);
+ const terms = termsMatch ? termsMatch[1].trim() : '';
+ logSh(`✅ [${tag}] Termes techniques détectés: ${terms}`, 'DEBUG');
+ return true;
+ } else {
+ logSh(`⏭️ [${tag}] Pas de termes techniques`, 'DEBUG');
+ return false;
+ }
} catch (error) {
- logSh(`❌ FATAL: Extraction termes techniques batch échouée: ${error.message}`, 'ERROR');
- throw new Error(`FATAL: Analyse termes techniques impossible - arrêt du workflow: ${error.message}`);
+ logSh(`❌ ERREUR analyse ${tag}: ${error.message}`, 'ERROR');
+ return false; // En cas d'erreur, on skip l'enhancement
}
}
/**
- * NOUVELLE FONCTION : Enhancement batch TOUS les éléments
+ * Enhancer un seul élément techniquement
*/
-async function enhanceAllElementsTechnicalBatch(elementsNeedingEnhancement, csvData, aiProvider) {
- if (elementsNeedingEnhancement.length === 0) return {};
-
- const batchEnhancementPrompt = `MISSION: Améliore UNIQUEMENT la précision technique de ces ${elementsNeedingEnhancement.length} contenus.
+async function enhanceSingleElementTechnical(tag, content, csvData, aiProvider) {
+ const prompt = `MISSION: Améliore ce contenu en intégrant des termes techniques précis.
-PERSONNALITÉ: ${csvData.personality?.nom} (${csvData.personality?.style})
-CONTEXTE: ${csvData.mc0} - Secteur: Signalétique/impression
-VOCABULAIRE PRÉFÉRÉ: ${csvData.personality?.vocabulairePref}
-
-CONTENUS + TERMES À AMÉLIORER:
-
-${elementsNeedingEnhancement.map((item, i) => `[${i + 1}] TAG: ${item.tag}
-CONTENU ACTUEL: "${item.content}"
-TERMES TECHNIQUES À INTÉGRER: ${item.technicalTerms.join(', ')}`).join('\n\n')}
+CONTEXTE: ${csvData.mc0} - Secteur: signalétique/impression
-CONSIGNES STRICTES:
-- Améliore UNIQUEMENT la précision technique, garde le style ${csvData.personality?.nom}
-- GARDE la même longueur, structure et ton
-- Intègre naturellement les termes techniques listés
-- NE CHANGE PAS le fond du message ni le style personnel
-- Utilise un vocabulaire expert mais accessible
-- ÉVITE les répétitions excessives
-- RESPECTE le niveau technique: ${csvData.personality?.niveauTechnique}
-- Termes techniques secteur: dibond, aluminium, impression UV, fraisage, épaisseur, PMMA
+CONTENU À AMÉLIORER:
+TAG: ${tag}
+CONTENU: "${content}"
-FORMAT RÉPONSE:
-[1] Contenu avec amélioration technique selon ${csvData.personality?.nom}
-[2] Contenu avec amélioration technique selon ${csvData.personality?.nom}
-etc... (${elementsNeedingEnhancement.length} éléments total)`;
+OBJECTIFS:
+- Remplace les termes génériques par des termes techniques précis
+- Ajoute des spécifications techniques réalistes
+- Maintient le même style et longueur
+- Intègre naturellement: matériaux (dibond, aluminium composite), procédés (impression UV, gravure laser), dimensions, normes
+
+EXEMPLE DE TRANSFORMATION:
+"matériaux haute performance" → "dibond 3mm ou aluminium composite"
+"impression moderne" → "impression UV haute définition"
+"fixation solide" → "fixation par chevilles inox Ø6mm"
+
+CONTRAINTES:
+- GARDE la même structure
+- MÊME longueur approximative
+- Style cohérent avec l'original
+- RÉPONDS DIRECTEMENT par le contenu amélioré, sans préfixe`;
try {
- const enhanced = await callLLM(aiProvider, batchEnhancementPrompt, {
- temperature: 0.4,
- maxTokens: 5000 // Plus large pour batch total
- }, csvData.personality);
-
- return parseTechnicalEnhancementBatchResponse(enhanced, elementsNeedingEnhancement);
-
+ const enhancedContent = await callLLM(aiProvider, prompt, { temperature: 0.7 });
+ return enhancedContent.trim();
} catch (error) {
- logSh(`❌ FATAL: Enhancement technique batch échoué: ${error.message}`, 'ERROR');
- throw new Error(`FATAL: Enhancement technique batch impossible - arrêt du workflow: ${error.message}`);
+ logSh(`❌ ERREUR enhancement ${tag}: ${error.message}`, 'ERROR');
+ return content; // En cas d'erreur, on retourne le contenu original
}
}
+// ANCIENNES FONCTIONS BATCH SUPPRIMÉES - REMPLACÉES PAR TRAITEMENT INDIVIDUEL
+
+/**
+ * NOUVELLE FONCTION : Enhancement batch TOUS les éléments
+ */
+// FONCTION SUPPRIMÉE : enhanceAllElementsTechnicalBatch() - Remplacée par traitement individuel
+
/**
* ÉTAPE 3 - Enhancement transitions BATCH avec IA configurable
*/
@@ -3015,7 +3016,8 @@ async function enhanceAllTransitions(baseContents, csvData, aiProvider) {
const batchTransitionsPrompt = `MISSION: Améliore UNIQUEMENT les transitions et fluidité de ces contenus.
-PERSONNALITÉ: ${csvData.personality?.nom} (${csvData.personality?.style})
+CONTEXTE: Article SEO professionnel pour site web commercial
+PERSONNALITÉ: ${csvData.personality?.nom} (${csvData.personality?.style} adapté web)
CONNECTEURS PRÉFÉRÉS: ${csvData.personality?.connecteursPref}
CONTENUS:
@@ -3093,9 +3095,10 @@ async function enhanceAllPersonalityStyle(baseContents, csvData, aiProvider) {
const batchStylePrompt = `MISSION: Adapte UNIQUEMENT le style de ces contenus selon ${personality.nom}.
+CONTEXTE: Finalisation article SEO pour site e-commerce professionnel
PERSONNALITÉ: ${personality.nom}
DESCRIPTION: ${personality.description}
-STYLE CIBLE: ${personality.style}
+STYLE CIBLE: ${personality.style} adapté au web professionnel
VOCABULAIRE: ${personality.vocabulairePref}
CONNECTEURS: ${personality.connecteursPref}
NIVEAU TECHNIQUE: ${personality.niveauTechnique}
@@ -3149,9 +3152,8 @@ etc...`;
/**
* Sleep function replacement for Utilities.sleep
*/
-function sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
-}
+
+// FONCTION SUPPRIMÉE : sleep() dupliquée - déjà définie ligne 12
/**
* RESTAURÉ DEPUIS .GS : Génération des paires FAQ cohérentes
@@ -3187,32 +3189,33 @@ async function generateFAQPairsRestored(faqPairs, csvData, aiProvider) {
function createBatchFAQPairsPrompt(faqPairs, csvData) {
const personality = csvData.personality;
- let prompt = `PERSONNALITÉ: ${personality.nom} | ${personality.description}
-STYLE: ${personality.style}
-VOCABULAIRE: ${personality.vocabulairePref}
-CONNECTEURS: ${personality.connecteursPref}
-NIVEAU TECHNIQUE: ${personality.niveauTechnique}
+ let prompt = `=== 1. CONTEXTE ===
+Entreprise: Autocollant.fr - signalétique personnalisée
+Sujet: ${csvData.mc0}
+Section: FAQ pour article SEO commercial
-GÉNÈRE ${faqPairs.length} PAIRES FAQ COHÉRENTES pour ${csvData.mc0}:
+=== 2. PERSONNALITÉ ===
+Rédacteur: ${personality.nom}
+Style: ${personality.style}
+Ton: ${personality.description || 'professionnel'}
-RÈGLES STRICTES:
-- QUESTIONS: Neutres, directes, langage client naturel (8-15 mots)
-- RÉPONSES: Style ${personality.style}, vocabulaire ${personality.vocabulairePref} (50-80 mots)
-- Sujets à couvrir: prix, livraison, personnalisation, installation, durabilité
-- ÉVITE répétitions excessives et expressions trop familières
-- Style ${personality.nom} reconnaissable mais PROFESSIONNEL
-- PAS de messages d'excuse ("je n'ai pas l'information")
-- RÉPONDS DIRECTEMENT par questions et réponses, sans préfixe
+=== 3. RÈGLES GÉNÉRALES ===
+- Questions naturelles de clients
+- Réponses expertes et rassurantes
+- Langage professionnel mais accessible
+- Textes rédigés humainement et de façon authentique
+- Couvrir: prix, livraison, personnalisation, installation, durabilité
+- IMPÉRATIF: Respecter strictement les contraintes XML
+
+=== 4. PAIRES FAQ À GÉNÉRER ===
-PAIRES À GÉNÉRER:
`;
faqPairs.forEach((pair, index) => {
const questionTag = pair.question.tag.replace(/\|/g, '').replace(/[{}]/g, '').replace(/<\/?strong>/g, '');
const answerTag = pair.answer.tag.replace(/\|/g, '').replace(/[{}]/g, '').replace(/<\/?strong>/g, '');
- prompt += `${index + 1}. [${questionTag}] + [${answerTag}]
- Question client sur ${csvData.mc0} → Réponse ${personality.style}
+ prompt += `${index + 1}. [${questionTag}] + [${answerTag}] - Paire FAQ naturelle
`;
});
@@ -3533,10 +3536,25 @@ function findAssociatedTitle(textElement, existingResults) {
function createBatchBasePrompt(elements, type, csvData, existingResults = {}) {
const personality = csvData.personality;
- let prompt = `RÉDACTEUR: ${personality.nom} | Style: ${personality.style}
-SUJET: ${csvData.mc0}
-
-${type === 'titre' ? 'GÉNÈRE DES TITRES COURTS ET IMPACTANTS' : `GÉNÈRE ${elements.length} ${type.toUpperCase()}S PROFESSIONNELS`}:
+ let prompt = `=== 1. CONTEXTE ===
+Entreprise: Autocollant.fr - signalétique personnalisée
+Sujet: ${csvData.mc0}
+Type d'article: SEO professionnel pour site commercial
+
+=== 2. PERSONNALITÉ ===
+Rédacteur: ${personality.nom}
+Style: ${personality.style}
+Ton: ${personality.description || 'professionnel'}
+
+=== 3. RÈGLES GÉNÉRALES ===
+- Contenu SEO optimisé
+- Langage naturel et fluide
+- Éviter répétitions
+- Pas de références techniques dans le contenu
+- Textes rédigés humainement et de façon authentique
+- IMPÉRATIF: Respecter strictement les contraintes XML (nombre de mots, etc.)
+
+=== 4. ÉLÉMENTS À GÉNÉRER ===
`;
// AJOUTER CONTEXTE DES TITRES POUR LES TEXTES
@@ -3548,7 +3566,7 @@ ${type === 'titre' ? 'GÉNÈRE DES TITRES COURTS ET IMPACTANTS' : `GÉNÈRE ${el
if (generatedTitles.length > 0) {
prompt += `
-CONTEXTE - TITRES GÉNÉRÉS:
+Titres existants pour contexte:
${generatedTitles.join('\n')}
`;
@@ -3560,34 +3578,33 @@ ${generatedTitles.join('\n')}
prompt += `${index + 1}. [${cleanTag}] `;
- // INSTRUCTIONS SPÉCIFIQUES ET COURTES PAR TYPE
+ // INSTRUCTIONS PROPRES PAR ÉLÉMENT
if (type === 'titre') {
if (elementInfo.element.type === 'titre_h1') {
- prompt += `CRÉER UN TITRE H1 PRINCIPAL (8-12 mots) sur "${csvData.t0}" - NE PAS écrire "Titre_H1_1"\n`;
+ prompt += `Titre principal accrocheur\n`;
} else if (elementInfo.element.type === 'titre_h2') {
- prompt += `CRÉER UN TITRE H2 SECTION (6-10 mots) sur "${csvData.mc0}" - NE PAS écrire "Titre_H2_X"\n`;
+ prompt += `Titre de section engageant\n`;
} else if (elementInfo.element.type === 'titre_h3') {
- prompt += `CRÉER UN TITRE H3 SOUS-SECTION (4-8 mots) - NE PAS écrire "Titre_H3_X"\n`;
+ prompt += `Sous-titre spécialisé\n`;
} else {
- prompt += `CRÉER UN TITRE ACCROCHEUR (4-10 mots) sur "${csvData.mc0}" - NE PAS écrire "Titre_"\n`;
+ prompt += `Titre pertinent\n`;
}
} else if (type === 'texte') {
- const wordCount = elementInfo.element.name && elementInfo.element.name.includes('H2') ? '150' : '100';
- prompt += `Paragraphe ${wordCount} mots, style ${personality.style}\n`;
+ prompt += `Paragraphe informatif\n`;
// ASSOCIER LE TITRE CORRESPONDANT AUTOMATIQUEMENT
const associatedTitle = findAssociatedTitle(elementInfo, existingResults);
if (associatedTitle) {
- prompt += ` Développe le titre: "${associatedTitle}"\n`;
+ prompt += ` Contexte: "${associatedTitle}"\n`;
}
if (elementInfo.element.resolvedContent) {
- prompt += ` Thème: "${elementInfo.element.resolvedContent}"\n`;
+ prompt += ` Angle: "${elementInfo.element.resolvedContent}"\n`;
}
} else if (type === 'intro') {
- prompt += `Introduction 80-100 mots, ton accueillant\n`;
+ prompt += `Introduction engageante\n`;
} else {
- prompt += `Contenu pertinent pour ${csvData.mc0}\n`;
+ prompt += `Contenu pertinent\n`;
}
});
@@ -3597,15 +3614,16 @@ ${generatedTitles.join('\n')}
- Phrases: ${personality.longueurPhrases}
- Niveau technique: ${personality.niveauTechnique}
-CONSIGNES STRICTES:
-- RESPECTE le style ${personality.style} de ${personality.nom} mais RESTE PROFESSIONNEL
-- INTERDICTION ABSOLUE: "du coup", "bon", "alors", "franchement", "nickel", "tip-top", "costaud" en excès
-- VARIE les connecteurs: ${personality.connecteursPref}
-- POUR LES TITRES: SEULEMENT le titre réel, JAMAIS de référence "Titre_H1_1" ou "Titre_H2_7"
-- EXEMPLE TITRE: "Plaques personnalisées résistantes aux intempéries" PAS "Titre_H2_1"
-- RÉPONDS DIRECTEMENT par le contenu demandé, SANS introduction ni nom de tag
-- PAS de message d'excuse du type "je n'ai pas l'information"
-- CONTENU cohérent et professionnel, évite la sur-familiarité
+CONSIGNES STRICTES POUR ARTICLE SEO:
+- CONTEXTE: Article professionnel pour site e-commerce, destiné aux clients potentiels
+- STYLE: ${personality.style} de ${personality.nom} mais ADAPTÉ au web professionnel
+- INTERDICTION ABSOLUE: expressions trop familières répétées ("du coup", "bon", "franchement", "nickel", "tip-top")
+- VOCABULAIRE: Mélange expertise technique + accessibilité client
+- SEO: Utilise naturellement "${csvData.mc0}" et termes associés
+- POUR LES TITRES: Titre SEO attractif UNIQUEMENT, JAMAIS "Titre_H1_1" ou "Titre_H2_7"
+- EXEMPLE TITRE: "Plaques personnalisées résistantes : guide complet 2024"
+- CONTENU: Informatif, rassurant, incite à l'achat SANS être trop commercial
+- RÉPONDS DIRECTEMENT par le contenu web demandé, SANS préfixe
FORMAT DE RÉPONSE ${type === 'titre' ? '(TITRES UNIQUEMENT)' : ''}:
[${elements[0].tag.replace(/\|/g, '').replace(/[{}]/g, '').replace(/<\/?strong>/g, '')}]
@@ -3696,107 +3714,11 @@ function cleanXMLTagsFromContent(content) {
// ============= PARSING FUNCTIONS =============
-/**
- * Parser réponse extraction termes
- */
-function parseAllTechnicalTermsResponse(response, baseContents, contentEntries) {
- const results = [];
- const regex = /\[(\d+)\]\s*([^[]*?)(?=\[\d+\]|$)/gs;
- let match;
- const parsedItems = {};
-
- // Parser la réponse
- while ((match = regex.exec(response)) !== null) {
- const index = parseInt(match[1]) - 1; // Convertir en 0-indexé
- const termsText = match[2].trim();
- parsedItems[index] = termsText;
- }
-
- // Mapper aux éléments
- contentEntries.forEach((tag, index) => {
- const termsText = parsedItems[index] || 'AUCUN';
- const hasTerms = !termsText.toUpperCase().includes('AUCUN');
-
- const technicalTerms = hasTerms ?
- termsText.split(',').map(t => t.trim()).filter(t => t.length > 0) :
- [];
-
- results.push({
- tag: tag,
- content: baseContents[tag],
- technicalTerms: technicalTerms,
- needsEnhancement: hasTerms && technicalTerms.length > 0
- });
-
- logSh(`🔍 [${tag}]: ${hasTerms ? technicalTerms.join(', ') : 'pas de termes techniques'}`, 'DEBUG');
- });
-
- const enhancementCount = results.filter(r => r.needsEnhancement).length;
- logSh(`📊 Analyse terminée: ${enhancementCount}/${contentEntries.length} éléments ont besoin d'enhancement`, 'INFO');
-
- return results;
index cff1b08..7217f01 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,8 +53,7 @@ test_*.js
*_debug.js
test-*.js
-# HTML généré (logs viewer)
-logs-viewer.html
+# HTML généré était ici mais maintenant on le garde dans tools/
# Unit test reports
TEST*.xml
diff --git a/code.js b/code.js
index a1dcd72..a2bdb22 100644
--- a/code.js
+++ b/code.js
@@ -1,6 +1,6 @@
/*
code.js — bundle concaténé
- Généré: 2025-09-03T04:21:57.159Z
+ Généré: 2025-09-04T01:10:08.540Z
Source: lib
Fichiers: 16
Ordre: topo
@@ -57,12 +57,13 @@ const fileDest = pino.destination({
});
tee.pipe(fileDest);
-// Custom levels for Pino to include TRACE and PROMPT
+// Custom levels for Pino to include TRACE, PROMPT, and LLM
const customLevels = {
trace: 5, // Below debug (10)
debug: 10,
info: 20,
prompt: 25, // New level for prompts (between info and warn)
+ llm: 26, // New level for LLM interactions (between prompt and warn)
warn: 30,
error: 40,
fatal: 50
@@ -178,6 +179,9 @@ async function logSh(message, level = 'INFO') {
case 'prompt':
logger.prompt(traceData, message);
break;
+ case 'llm':
+ logger.llm(traceData, message);
+ break;
default:
logger.info(traceData, message);
}
@@ -1282,7 +1286,10 @@ async function callLLM(llmProvider, prompt, options = {}, personality = null) {
// 📢 AFFICHAGE PROMPT COMPLET POUR DEBUG AVEC INFO IA
logSh(`\n🔍 ===== PROMPT ENVOYÉ À ${llmProvider.toUpperCase()} (${config.model}) | PERSONNALITÉ: ${personality?.nom || 'AUCUNE'} =====`, 'PROMPT');
logSh(prompt, 'PROMPT');
- logSh(`===== FIN PROMPT ${llmProvider.toUpperCase()} (${personality?.nom || 'AUCUNE'}) =====\n`, 'PROMPT');
+
+ // 📤 LOG LLM REQUEST COMPLET
+ logSh(`📤 LLM REQUEST [${llmProvider.toUpperCase()}] (${config.model}) | Personnalité: ${personality?.nom || 'AUCUNE'}`, 'LLM');
+ logSh(prompt, 'LLM');
// Préparer la requête selon le provider
const requestData = buildRequestData(llmProvider, prompt, options, personality);
@@ -1293,8 +1300,12 @@ async function callLLM(llmProvider, prompt, options = {}, personality = null) {
// Parser la réponse selon le format du provider
const content = parseResponse(llmProvider, response);
+ // 📥 LOG LLM RESPONSE COMPLET
+ logSh(`📥 LLM RESPONSE [${llmProvider.toUpperCase()}] (${config.model}) | Durée: ${Date.now() - startTime}ms`, 'LLM');
+ logSh(content, 'LLM');
+
const duration = Date.now() - startTime;
- logSh(`✅ ${llmProvider.toUpperCase()} (${personality?.nom || 'sans personnalité'}) réponse en ${duration}ms: "${content.substring(0, 150)}${content.length > 150 ? '...' : ''}"`, 'INFO');
+ logSh(`✅ ${llmProvider.toUpperCase()} (${personality?.nom || 'sans personnalité'}) réponse en ${duration}ms`, 'INFO');
// Enregistrer les stats d'usage
await recordUsageStats(llmProvider, prompt.length, content.length, duration);
@@ -1727,6 +1738,8 @@ module.exports = {
LLM_CONFIG
};
+
+
/*
┌────────────────────────────────────────────────────────────────────┐
│ File: lib/ElementExtraction.js │
@@ -2309,38 +2322,21 @@ CONTEXTE:
`;
missingElements.forEach((missing, index) => {
- prompt += `${index + 1}. [${missing.name}] `;
-
- // INSTRUCTIONS SPÉCIFIQUES PAR TYPE
- if (missing.type.includes('titre_h1')) {
- prompt += `→ Titre H1 principal (8-10 mots) pour ${contextAnalysis.mainKeyword}\n`;
- } else if (missing.type.includes('titre_h2')) {
- prompt += `→ Titre H2 section (6-8 mots) lié à ${contextAnalysis.mainKeyword}\n`;
- } else if (missing.type.includes('titre_h3')) {
- prompt += `→ Sous-titre H3 (4-6 mots) spécialisé ${contextAnalysis.mainKeyword}\n`;
- } else if (missing.type.includes('texte') || missing.type.includes('txt')) {
- prompt += `→ Thème/sujet pour paragraphe 150 mots sur ${contextAnalysis.mainKeyword}\n`;
- } else if (missing.type.includes('faq_question')) {
- prompt += `→ Question client directe sur ${contextAnalysis.mainKeyword} (8-12 mots)\n`;
- } else if (missing.type.includes('faq_reponse')) {
- prompt += `→ Thème réponse experte ${contextAnalysis.mainKeyword} (2-4 mots)\n`;
- } else {
- prompt += `→ Expression/mot-clé pertinent ${contextAnalysis.mainKeyword}\n`;
- }
+ prompt += `${index + 1}. [${missing.name}] → Mot-clé SEO\n`;
});
prompt += `\nCONSIGNES:
-- Reste dans le thème ${contextAnalysis.mainKeyword}
-- Varie les angles et expressions
-- Évite répétitions avec mots-clés existants
-- Précis et pertinents
+- Thème: ${contextAnalysis.mainKeyword}
+- Mots-clés SEO naturels
+- Varie les termes
+- Évite répétitions
FORMAT:
[${missingElements[0].name}]
-Expression/mot-clé généré 1
+mot-clé
[${missingElements[1] ? missingElements[1].name : 'exemple'}]
-Expression/mot-clé généré 2
+mot-clé
etc...`;
@@ -2596,6 +2592,11 @@ const { logSh } = require('./ErrorReporting');
const { tracer } = require('./trace.js');
const { selectMultiplePersonalitiesWithAI, getPersonalities } = require('./BrainConfig');
+// Utilitaire pour les délais
+function sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
/**
* NOUVELLE APPROCHE - Multi-Personnalités Batch Enhancement
* 4 personnalités différentes utilisées dans le pipeline pour maximum d'anti-détection
@@ -2804,8 +2805,8 @@ async function generateAllContentBase(hierarchy, csvData, aiProvider) {
}
/**
- * ÉTAPE 2 - Enhancement technique BATCH OPTIMISÉ avec IA configurable
- * OPTIMISATION : 1 appel extraction + 1 appel enhancement au lieu de 20+
+ * ÉTAPE 2 - Enhancement technique ÉLÉMENT PAR ÉLÉMENT avec IA configurable
+ * NOUVEAU : Traitement individuel pour fiabilité maximale et debug précis
*/
async function enhanceAllTechnicalTerms(baseContents, csvData, aiProvider) {
logSh('🔧 === DÉBUT ENHANCEMENT TECHNIQUE ===', 'INFO');
@@ -2872,96 +2873,96 @@ async function enhanceAllTechnicalTerms(baseContents, csvData, aiProvider) {
}
/**
- * NOUVELLE FONCTION : Extraction batch TOUS les termes techniques
+ * Analyser un seul élément pour détecter les termes techniques
*/
-async function extractAllTechnicalTermsBatch(baseContents, csvData, aiProvider) {
- const contentEntries = Object.keys(baseContents);
-
- const batchAnalysisPrompt = `MISSION: Analyser ces ${contentEntries.length} contenus et identifier leurs termes techniques.
+async function analyzeSingleElementTechnicalTerms(tag, content, csvData, aiProvider) {
+ const prompt = `MISSION: Analyser ce contenu et déterminer s'il contient des termes techniques.
CONTEXTE: ${csvData.mc0} - Secteur: signalétique/impression
-CONTENUS À ANALYSER:
-
-${contentEntries.map((tag, i) => `[${i + 1}] TAG: ${tag}
-CONTENU: "${baseContents[tag]}"`).join('\n\n')}
+CONTENU À ANALYSER:
+TAG: ${tag}
+CONTENU: "${content}"
CONSIGNES:
-- Identifie UNIQUEMENT les vrais termes techniques métier/industrie
+- Cherche UNIQUEMENT des vrais termes techniques métier/industrie
- Évite mots génériques (qualité, service, pratique, personnalisé, etc.)
-- Focus: matériaux, procédés, normes, dimensions, technologies
-- Si aucun terme technique → "AUCUN"
+- Focus: matériaux, procédés, normes, dimensions, technologies spécifiques
-EXEMPLES VALIDES: dibond, impression UV, fraisage CNC, épaisseur 3mm, aluminium brossé
-EXEMPLES INVALIDES: durable, pratique, personnalisé, moderne, esthétique
+EXEMPLES VALIDES: dibond, impression UV, fraisage CNC, épaisseur 3mm, aluminium brossé, anodisation
+EXEMPLES INVALIDES: durable, pratique, personnalisé, moderne, esthétique, haute performance
-FORMAT RÉPONSE EXACT:
-[1] dibond, impression UV, 3mm OU AUCUN
-[2] aluminium, fraisage CNC OU AUCUN
-[3] AUCUN
-etc... (${contentEntries.length} lignes total)`;
+RÉPONSE REQUISE:
+- Si termes techniques trouvés: "OUI - termes: [liste des termes séparés par virgules]"
+- Si aucun terme technique: "NON"
+
+EXEMPLE:
+OUI - termes: aluminium composite, impression numérique, gravure laser`;
try {
- const analysisResponse = await callLLM(aiProvider, batchAnalysisPrompt, {
- temperature: 0.3,
- maxTokens: 2000
- }, csvData.personality);
-
- return parseAllTechnicalTermsResponse(analysisResponse, baseContents, contentEntries);
-
+ const response = await callLLM(aiProvider, prompt, { temperature: 0.3 });
+
+ if (response.toUpperCase().startsWith('OUI')) {
+ // Extraire les termes de la réponse
+ const termsMatch = response.match(/termes:\s*(.+)/i);
+ const terms = termsMatch ? termsMatch[1].trim() : '';
+ logSh(`✅ [${tag}] Termes techniques détectés: ${terms}`, 'DEBUG');
+ return true;
+ } else {
+ logSh(`⏭️ [${tag}] Pas de termes techniques`, 'DEBUG');
+ return false;
+ }
} catch (error) {
- logSh(`❌ FATAL: Extraction termes techniques batch échouée: ${error.message}`, 'ERROR');
- throw new Error(`FATAL: Analyse termes techniques impossible - arrêt du workflow: ${error.message}`);
+ logSh(`❌ ERREUR analyse ${tag}: ${error.message}`, 'ERROR');
+ return false; // En cas d'erreur, on skip l'enhancement
}
}
/**
- * NOUVELLE FONCTION : Enhancement batch TOUS les éléments
+ * Enhancer un seul élément techniquement
*/
-async function enhanceAllElementsTechnicalBatch(elementsNeedingEnhancement, csvData, aiProvider) {
- if (elementsNeedingEnhancement.length === 0) return {};
-
- const batchEnhancementPrompt = `MISSION: Améliore UNIQUEMENT la précision technique de ces ${elementsNeedingEnhancement.length} contenus.
+async function enhanceSingleElementTechnical(tag, content, csvData, aiProvider) {
+ const prompt = `MISSION: Améliore ce contenu en intégrant des termes techniques précis.
-PERSONNALITÉ: ${csvData.personality?.nom} (${csvData.personality?.style})
-CONTEXTE: ${csvData.mc0} - Secteur: Signalétique/impression
-VOCABULAIRE PRÉFÉRÉ: ${csvData.personality?.vocabulairePref}
-
-CONTENUS + TERMES À AMÉLIORER:
-
-${elementsNeedingEnhancement.map((item, i) => `[${i + 1}] TAG: ${item.tag}
-CONTENU ACTUEL: "${item.content}"
-TERMES TECHNIQUES À INTÉGRER: ${item.technicalTerms.join(', ')}`).join('\n\n')}
+CONTEXTE: ${csvData.mc0} - Secteur: signalétique/impression
-CONSIGNES STRICTES:
-- Améliore UNIQUEMENT la précision technique, garde le style ${csvData.personality?.nom}
-- GARDE la même longueur, structure et ton
-- Intègre naturellement les termes techniques listés
-- NE CHANGE PAS le fond du message ni le style personnel
-- Utilise un vocabulaire expert mais accessible
-- ÉVITE les répétitions excessives
-- RESPECTE le niveau technique: ${csvData.personality?.niveauTechnique}
-- Termes techniques secteur: dibond, aluminium, impression UV, fraisage, épaisseur, PMMA
+CONTENU À AMÉLIORER:
+TAG: ${tag}
+CONTENU: "${content}"
-FORMAT RÉPONSE:
-[1] Contenu avec amélioration technique selon ${csvData.personality?.nom}
-[2] Contenu avec amélioration technique selon ${csvData.personality?.nom}
-etc... (${elementsNeedingEnhancement.length} éléments total)`;
+OBJECTIFS:
+- Remplace les termes génériques par des termes techniques précis
+- Ajoute des spécifications techniques réalistes
+- Maintient le même style et longueur
+- Intègre naturellement: matériaux (dibond, aluminium composite), procédés (impression UV, gravure laser), dimensions, normes
+
+EXEMPLE DE TRANSFORMATION:
+"matériaux haute performance" → "dibond 3mm ou aluminium composite"
+"impression moderne" → "impression UV haute définition"
+"fixation solide" → "fixation par chevilles inox Ø6mm"
+
+CONTRAINTES:
+- GARDE la même structure
+- MÊME longueur approximative
+- Style cohérent avec l'original
+- RÉPONDS DIRECTEMENT par le contenu amélioré, sans préfixe`;
try {
- const enhanced = await callLLM(aiProvider, batchEnhancementPrompt, {
- temperature: 0.4,
- maxTokens: 5000 // Plus large pour batch total
- }, csvData.personality);
-
- return parseTechnicalEnhancementBatchResponse(enhanced, elementsNeedingEnhancement);
-
+ const enhancedContent = await callLLM(aiProvider, prompt, { temperature: 0.7 });
+ return enhancedContent.trim();
} catch (error) {
- logSh(`❌ FATAL: Enhancement technique batch échoué: ${error.message}`, 'ERROR');
- throw new Error(`FATAL: Enhancement technique batch impossible - arrêt du workflow: ${error.message}`);
+ logSh(`❌ ERREUR enhancement ${tag}: ${error.message}`, 'ERROR');
+ return content; // En cas d'erreur, on retourne le contenu original
}
}
+// ANCIENNES FONCTIONS BATCH SUPPRIMÉES - REMPLACÉES PAR TRAITEMENT INDIVIDUEL
+
+/**
+ * NOUVELLE FONCTION : Enhancement batch TOUS les éléments
+ */
+// FONCTION SUPPRIMÉE : enhanceAllElementsTechnicalBatch() - Remplacée par traitement individuel
+
/**
* ÉTAPE 3 - Enhancement transitions BATCH avec IA configurable
*/
@@ -3015,7 +3016,8 @@ async function enhanceAllTransitions(baseContents, csvData, aiProvider) {
const batchTransitionsPrompt = `MISSION: Améliore UNIQUEMENT les transitions et fluidité de ces contenus.
-PERSONNALITÉ: ${csvData.personality?.nom} (${csvData.personality?.style})
+CONTEXTE: Article SEO professionnel pour site web commercial
+PERSONNALITÉ: ${csvData.personality?.nom} (${csvData.personality?.style} adapté web)
CONNECTEURS PRÉFÉRÉS: ${csvData.personality?.connecteursPref}
CONTENUS:
@@ -3093,9 +3095,10 @@ async function enhanceAllPersonalityStyle(baseContents, csvData, aiProvider) {
const batchStylePrompt = `MISSION: Adapte UNIQUEMENT le style de ces contenus selon ${personality.nom}.
+CONTEXTE: Finalisation article SEO pour site e-commerce professionnel
PERSONNALITÉ: ${personality.nom}
DESCRIPTION: ${personality.description}
-STYLE CIBLE: ${personality.style}
+STYLE CIBLE: ${personality.style} adapté au web professionnel
VOCABULAIRE: ${personality.vocabulairePref}
CONNECTEURS: ${personality.connecteursPref}
NIVEAU TECHNIQUE: ${personality.niveauTechnique}
@@ -3149,9 +3152,8 @@ etc...`;
/**
* Sleep function replacement for Utilities.sleep
*/
-function sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
-}
+
+// FONCTION SUPPRIMÉE : sleep() dupliquée - déjà définie ligne 12
/**
* RESTAURÉ DEPUIS .GS : Génération des paires FAQ cohérentes
@@ -3187,32 +3189,33 @@ async function generateFAQPairsRestored(faqPairs, csvData, aiProvider) {
function createBatchFAQPairsPrompt(faqPairs, csvData) {
const personality = csvData.personality;
- let prompt = `PERSONNALITÉ: ${personality.nom} | ${personality.description}
-STYLE: ${personality.style}
-VOCABULAIRE: ${personality.vocabulairePref}
-CONNECTEURS: ${personality.connecteursPref}
-NIVEAU TECHNIQUE: ${personality.niveauTechnique}
+ let prompt = `=== 1. CONTEXTE ===
+Entreprise: Autocollant.fr - signalétique personnalisée
+Sujet: ${csvData.mc0}
+Section: FAQ pour article SEO commercial
-GÉNÈRE ${faqPairs.length} PAIRES FAQ COHÉRENTES pour ${csvData.mc0}:
+=== 2. PERSONNALITÉ ===
+Rédacteur: ${personality.nom}
+Style: ${personality.style}
+Ton: ${personality.description || 'professionnel'}
-RÈGLES STRICTES:
-- QUESTIONS: Neutres, directes, langage client naturel (8-15 mots)
-- RÉPONSES: Style ${personality.style}, vocabulaire ${personality.vocabulairePref} (50-80 mots)
-- Sujets à couvrir: prix, livraison, personnalisation, installation, durabilité
-- ÉVITE répétitions excessives et expressions trop familières
-- Style ${personality.nom} reconnaissable mais PROFESSIONNEL
-- PAS de messages d'excuse ("je n'ai pas l'information")
-- RÉPONDS DIRECTEMENT par questions et réponses, sans préfixe
+=== 3. RÈGLES GÉNÉRALES ===
+- Questions naturelles de clients
+- Réponses expertes et rassurantes
+- Langage professionnel mais accessible
+- Textes rédigés humainement et de façon authentique
+- Couvrir: prix, livraison, personnalisation, installation, durabilité
+- IMPÉRATIF: Respecter strictement les contraintes XML
+
+=== 4. PAIRES FAQ À GÉNÉRER ===
-PAIRES À GÉNÉRER:
`;
faqPairs.forEach((pair, index) => {
const questionTag = pair.question.tag.replace(/\|/g, '').replace(/[{}]/g, '').replace(/<\/?strong>/g, '');
const answerTag = pair.answer.tag.replace(/\|/g, '').replace(/[{}]/g, '').replace(/<\/?strong>/g, '');
- prompt += `${index + 1}. [${questionTag}] + [${answerTag}]
- Question client sur ${csvData.mc0} → Réponse ${personality.style}
+ prompt += `${index + 1}. [${questionTag}] + [${answerTag}] - Paire FAQ naturelle
`;
});
@@ -3533,10 +3536,25 @@ function findAssociatedTitle(textElement, existingResults) {
function createBatchBasePrompt(elements, type, csvData, existingResults = {}) {
const personality = csvData.personality;
- let prompt = `RÉDACTEUR: ${personality.nom} | Style: ${personality.style}
-SUJET: ${csvData.mc0}
-
-${type === 'titre' ? 'GÉNÈRE DES TITRES COURTS ET IMPACTANTS' : `GÉNÈRE ${elements.length} ${type.toUpperCase()}S PROFESSIONNELS`}:
+ let prompt = `=== 1. CONTEXTE ===
+Entreprise: Autocollant.fr - signalétique personnalisée
+Sujet: ${csvData.mc0}
+Type d'article: SEO professionnel pour site commercial
+
+=== 2. PERSONNALITÉ ===
+Rédacteur: ${personality.nom}
+Style: ${personality.style}
+Ton: ${personality.description || 'professionnel'}
+
+=== 3. RÈGLES GÉNÉRALES ===
+- Contenu SEO optimisé
+- Langage naturel et fluide
+- Éviter répétitions
+- Pas de références techniques dans le contenu
+- Textes rédigés humainement et de façon authentique
+- IMPÉRATIF: Respecter strictement les contraintes XML (nombre de mots, etc.)
+
+=== 4. ÉLÉMENTS À GÉNÉRER ===
`;
// AJOUTER CONTEXTE DES TITRES POUR LES TEXTES
@@ -3548,7 +3566,7 @@ ${type === 'titre' ? 'GÉNÈRE DES TITRES COURTS ET IMPACTANTS' : `GÉNÈRE ${el
if (generatedTitles.length > 0) {
prompt += `
-CONTEXTE - TITRES GÉNÉRÉS:
+Titres existants pour contexte:
${generatedTitles.join('\n')}
`;
@@ -3560,34 +3578,33 @@ ${generatedTitles.join('\n')}
prompt += `${index + 1}. [${cleanTag}] `;
- // INSTRUCTIONS SPÉCIFIQUES ET COURTES PAR TYPE
+ // INSTRUCTIONS PROPRES PAR ÉLÉMENT
if (type === 'titre') {
if (elementInfo.element.type === 'titre_h1') {
- prompt += `CRÉER UN TITRE H1 PRINCIPAL (8-12 mots) sur "${csvData.t0}" - NE PAS écrire "Titre_H1_1"\n`;
+ prompt += `Titre principal accrocheur\n`;
} else if (elementInfo.element.type === 'titre_h2') {
- prompt += `CRÉER UN TITRE H2 SECTION (6-10 mots) sur "${csvData.mc0}" - NE PAS écrire "Titre_H2_X"\n`;
+ prompt += `Titre de section engageant\n`;
} else if (elementInfo.element.type === 'titre_h3') {
- prompt += `CRÉER UN TITRE H3 SOUS-SECTION (4-8 mots) - NE PAS écrire "Titre_H3_X"\n`;
+ prompt += `Sous-titre spécialisé\n`;
} else {
- prompt += `CRÉER UN TITRE ACCROCHEUR (4-10 mots) sur "${csvData.mc0}" - NE PAS écrire "Titre_"\n`;
+ prompt += `Titre pertinent\n`;
}
} else if (type === 'texte') {
- const wordCount = elementInfo.element.name && elementInfo.element.name.includes('H2') ? '150' : '100';
- prompt += `Paragraphe ${wordCount} mots, style ${personality.style}\n`;
+ prompt += `Paragraphe informatif\n`;
// ASSOCIER LE TITRE CORRESPONDANT AUTOMATIQUEMENT
const associatedTitle = findAssociatedTitle(elementInfo, existingResults);
if (associatedTitle) {
- prompt += ` Développe le titre: "${associatedTitle}"\n`;
+ prompt += ` Contexte: "${associatedTitle}"\n`;
}
if (elementInfo.element.resolvedContent) {
- prompt += ` Thème: "${elementInfo.element.resolvedContent}"\n`;
+ prompt += ` Angle: "${elementInfo.element.resolvedContent}"\n`;
}
} else if (type === 'intro') {
- prompt += `Introduction 80-100 mots, ton accueillant\n`;
+ prompt += `Introduction engageante\n`;
} else {
- prompt += `Contenu pertinent pour ${csvData.mc0}\n`;
+ prompt += `Contenu pertinent\n`;
}
});
@@ -3597,15 +3614,16 @@ ${generatedTitles.join('\n')}
- Phrases: ${personality.longueurPhrases}
- Niveau technique: ${personality.niveauTechnique}
-CONSIGNES STRICTES:
-- RESPECTE le style ${personality.style} de ${personality.nom} mais RESTE PROFESSIONNEL
-- INTERDICTION ABSOLUE: "du coup", "bon", "alors", "franchement", "nickel", "tip-top", "costaud" en excès
-- VARIE les connecteurs: ${personality.connecteursPref}
-- POUR LES TITRES: SEULEMENT le titre réel, JAMAIS de référence "Titre_H1_1" ou "Titre_H2_7"
-- EXEMPLE TITRE: "Plaques personnalisées résistantes aux intempéries" PAS "Titre_H2_1"
-- RÉPONDS DIRECTEMENT par le contenu demandé, SANS introduction ni nom de tag
-- PAS de message d'excuse du type "je n'ai pas l'information"
-- CONTENU cohérent et professionnel, évite la sur-familiarité
+CONSIGNES STRICTES POUR ARTICLE SEO:
+- CONTEXTE: Article professionnel pour site e-commerce, destiné aux clients potentiels
+- STYLE: ${personality.style} de ${personality.nom} mais ADAPTÉ au web professionnel
+- INTERDICTION ABSOLUE: expressions trop familières répétées ("du coup", "bon", "franchement", "nickel", "tip-top")
+- VOCABULAIRE: Mélange expertise technique + accessibilité client
+- SEO: Utilise naturellement "${csvData.mc0}" et termes associés
+- POUR LES TITRES: Titre SEO attractif UNIQUEMENT, JAMAIS "Titre_H1_1" ou "Titre_H2_7"
+- EXEMPLE TITRE: "Plaques personnalisées résistantes : guide complet 2024"
+- CONTENU: Informatif, rassurant, incite à l'achat SANS être trop commercial
+- RÉPONDS DIRECTEMENT par le contenu web demandé, SANS préfixe
FORMAT DE RÉPONSE ${type === 'titre' ? '(TITRES UNIQUEMENT)' : ''}:
[${elements[0].tag.replace(/\|/g, '').replace(/[{}]/g, '').replace(/<\/?strong>/g, '')}]
@@ -3696,107 +3714,11 @@ function cleanXMLTagsFromContent(content) {
// ============= PARSING FUNCTIONS =============
-/**
- * Parser réponse extraction termes
- */
-function parseAllTechnicalTermsResponse(response, baseContents, contentEntries) {
- const results = [];
- const regex = /\[(\d+)\]\s*([^[]*?)(?=\[\d+\]|$)/gs;
- let match;
- const parsedItems = {};
-
- // Parser la réponse
- while ((match = regex.exec(response)) !== null) {
- const index = parseInt(match[1]) - 1; // Convertir en 0-indexé
- const termsText = match[2].trim();
- parsedItems[index] = termsText;
- }
-
- // Mapper aux éléments
- contentEntries.forEach((tag, index) => {
- const termsText = parsedItems[index] || 'AUCUN';
- const hasTerms = !termsText.toUpperCase().includes('AUCUN');
-
- const technicalTerms = hasTerms ?
- termsText.split(',').map(t => t.trim()).filter(t => t.length > 0) :
- [];
-
- results.push({
- tag: tag,
- content: baseContents[tag],
- technicalTerms: technicalTerms,
- needsEnhancement: hasTerms && technicalTerms.length > 0
- });
-
- logSh(`🔍 [${tag}]: ${hasTerms ? technicalTerms.join(', ') : 'pas de termes techniques'}`, 'DEBUG');
- });
-
- const enhancementCount = results.filter(r => r.needsEnhancement).length;
- logSh(`📊 Analyse terminée: ${enhancementCount}/${contentEntries.length} éléments ont besoin d'enhancement`, 'INFO');
-
- return results;