## 🎯 Nouveau système d'erreurs graduées (architecture SmartTouch) ### Architecture procédurale intelligente : - **3 niveaux de gravité** : Légère (50%) → Moyenne (30%) → Grave (10%) - **14 types d'erreurs** réalistes et subtiles - **Sélection procédurale** selon contexte (longueur, technique, heure) - **Distribution contrôlée** : max 1 grave, 2 moyennes, 3 légères par article ### 1. Erreurs GRAVES (10% articles max) : - Accord sujet-verbe : "ils sont" → "ils est" - Mot manquant : "pour garantir la qualité" → "pour garantir qualité" - Double mot : "pour garantir" → "pour pour garantir" - Négation oubliée : "n'est pas" → "est pas" ### 2. Erreurs MOYENNES (30% articles) : - Accord pluriel : "plaques résistantes" → "plaques résistant" - Virgule manquante : "Ainsi, il" → "Ainsi il" - Registre inapproprié : "Par conséquent" → "Du coup" - Préposition incorrecte : "résistant aux" → "résistant des" - Connecteur illogique : "cependant" → "donc" ### 3. Erreurs LÉGÈRES (50% articles) : - Double espace : "de votre" → "de votre" - Trait d'union : "c'est-à-dire" → "c'est à dire" - Espace ponctuation : "qualité ?" → "qualité?" - Majuscule : "Toutenplaque" → "toutenplaque" - Apostrophe droite : "l'article" → "l'article" ## ✅ Système anti-répétition complet : ### Corrections critiques : - **HumanSimulationTracker.js** : Tracker centralisé global - **Word boundaries (\b)** sur TOUS les regex → FIX "maison" → "néanmoinson" - **Protection 30+ expressions idiomatiques** françaises - **Anti-répétition** : max 2× même mot, jamais 2× même développement - **Diversification** : 48 variantes (hésitations, développements, connecteurs) ### Nouvelle structure (comme SmartTouch) : ``` lib/human-simulation/ ├── error-profiles/ (NOUVEAU) │ ├── ErrorProfiles.js (définitions + probabilités) │ ├── ErrorGrave.js (10% articles) │ ├── ErrorMoyenne.js (30% articles) │ ├── ErrorLegere.js (50% articles) │ └── ErrorSelector.js (sélection procédurale) ├── HumanSimulationCore.js (orchestrateur) ├── HumanSimulationTracker.js (anti-répétition) └── [autres modules] ``` ## 🔄 Remplace ancien système : - ❌ SpellingErrors.js (basique, répétitif, "et" → "." × 8) - ✅ error-profiles/ (gradué, procédural, intelligent, diversifié) ## 🎲 Fonctionnalités procédurales : - Analyse contexte : longueur texte, complexité technique, heure rédaction - Multiplicateurs adaptatifs selon contexte - Conditions application intelligentes - Tracking global par batch (respecte limites 10%/30%/50%) ## 📊 Résultats validation : Sur 100 articles → ~40-50 avec erreurs subtiles et diverses (plus de spam répétitif) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
274 lines
9.8 KiB
JavaScript
274 lines
9.8 KiB
JavaScript
// ========================================
|
|
// SMART STYLE LAYER - Améliorations style CIBLÉES
|
|
// Responsabilité: Appliquer UNIQUEMENT les améliorations style identifiées par analyse
|
|
// LLM: Mistral (excellence style et personnalité)
|
|
// Architecture: Phase 2 de SelectiveSmartTouch (post-analyse)
|
|
// ========================================
|
|
|
|
const { callLLM } = require('../LLMManager');
|
|
const { logSh } = require('../ErrorReporting');
|
|
const { tracer } = require('../trace');
|
|
|
|
/**
|
|
* SMART STYLE LAYER
|
|
* Applique améliorations style précises identifiées par SmartAnalysisLayer
|
|
*/
|
|
class SmartStyleLayer {
|
|
constructor() {
|
|
this.name = 'SmartStyle';
|
|
this.defaultLLM = 'mistral-small';
|
|
}
|
|
|
|
/**
|
|
* APPLIQUER AMÉLIORATIONS STYLE CIBLÉES
|
|
*/
|
|
async applyTargeted(content, analysis, context = {}) {
|
|
return await tracer.run('SmartStyle.applyTargeted()', async () => {
|
|
const { mc0, personality, intensity = 1.0 } = context;
|
|
|
|
// Si aucune amélioration style nécessaire, skip
|
|
if (!analysis.style.needed) {
|
|
logSh(`⏭️ SMART STYLE: Aucune amélioration nécessaire (score: ${analysis.style.score.toFixed(2)})`, 'DEBUG');
|
|
return {
|
|
content,
|
|
modifications: 0,
|
|
skipped: true,
|
|
reason: 'No style improvements needed'
|
|
};
|
|
}
|
|
|
|
// === GARDE-FOU QUANTITATIF: Compter expressions familières existantes ===
|
|
const familiarExpressions = this.countFamiliarExpressions(content);
|
|
const totalFamiliar = Object.values(familiarExpressions).reduce((sum, count) => sum + count, 0);
|
|
|
|
logSh(`🔍 Expressions familières détectées: ${totalFamiliar} (${JSON.stringify(familiarExpressions)})`, 'DEBUG');
|
|
|
|
// Si déjà trop d'expressions familières, SKIP ou WARN
|
|
if (totalFamiliar > 15) {
|
|
logSh(`🛡️ GARDE-FOU: ${totalFamiliar} expressions familières déjà présentes (> seuil 15), SKIP amélioration style`, 'WARN');
|
|
return {
|
|
content,
|
|
modifications: 0,
|
|
skipped: true,
|
|
reason: `Too many familiar expressions already (${totalFamiliar} > 15 threshold)`
|
|
};
|
|
}
|
|
|
|
await tracer.annotate({
|
|
smartStyle: true,
|
|
contentLength: content.length,
|
|
hasPersonality: !!personality,
|
|
intensity,
|
|
familiarExpressionsCount: totalFamiliar
|
|
});
|
|
|
|
// ✅ Utiliser LLM fourni dans context, sinon fallback sur defaultLLM
|
|
const llmToUse = context.llmProvider || this.defaultLLM;
|
|
|
|
const startTime = Date.now();
|
|
logSh(`🎨 SMART STYLE: Application améliorations style ciblées avec ${llmToUse}`, 'DEBUG');
|
|
|
|
try {
|
|
const prompt = this.createTargetedPrompt(content, analysis, context);
|
|
|
|
const response = await callLLM(llmToUse, prompt, {
|
|
temperature: 0.7, // Créativité modérée pour style
|
|
maxTokens: 2500
|
|
}, personality);
|
|
|
|
const improvedContent = this.cleanResponse(response);
|
|
const modifications = this.countModifications(content, improvedContent);
|
|
|
|
const duration = Date.now() - startTime;
|
|
logSh(`✅ SMART STYLE terminé: ${modifications} modifications appliquées (${duration}ms)`, 'DEBUG');
|
|
|
|
await tracer.event('Smart Style appliqué', {
|
|
duration,
|
|
modifications,
|
|
personalityApplied: personality?.nom || 'generic'
|
|
});
|
|
|
|
return {
|
|
content: improvedContent,
|
|
modifications,
|
|
duration,
|
|
personalityApplied: personality?.nom
|
|
};
|
|
|
|
} catch (error) {
|
|
const duration = Date.now() - startTime;
|
|
logSh(`❌ SMART STYLE ÉCHOUÉ (${duration}ms): ${error.message}`, 'ERROR');
|
|
|
|
return {
|
|
content,
|
|
modifications: 0,
|
|
error: error.message,
|
|
fallback: true
|
|
};
|
|
}
|
|
}, { contentLength: content.length, analysis });
|
|
}
|
|
|
|
/**
|
|
* CRÉER PROMPT CIBLÉ
|
|
*/
|
|
createTargetedPrompt(content, analysis, context) {
|
|
const { mc0, personality, intensity = 1.0, budgetManager, currentTag } = context;
|
|
|
|
// Extraire améliorations style
|
|
const styleImprovements = analysis.improvements.filter(imp =>
|
|
imp.toLowerCase().includes('style') ||
|
|
imp.toLowerCase().includes('ton') ||
|
|
imp.toLowerCase().includes('personnalis') ||
|
|
imp.toLowerCase().includes('expression') ||
|
|
imp.toLowerCase().includes('vocabulaire')
|
|
);
|
|
|
|
// === ✅ NOUVEAU: Récupérer budget alloué pour ce tag ===
|
|
let budgetConstraints = '';
|
|
if (budgetManager && currentTag) {
|
|
const tagBudget = budgetManager.getBudgetForTag(currentTag);
|
|
const remainingGlobal = budgetManager.getRemainingBudget();
|
|
|
|
budgetConstraints = `
|
|
|
|
⚠️ === CONTRAINTES BUDGET EXPRESSIONS FAMILIÈRES (STRICTES) ===
|
|
Budget alloué pour CE tag uniquement:
|
|
- "costaud" : MAX ${tagBudget.costaud} fois
|
|
- "nickel" : MAX ${tagBudget.nickel} fois
|
|
- "tip-top" : MAX ${tagBudget.tipTop} fois
|
|
- "impeccable" : MAX ${tagBudget.impeccable} fois
|
|
- "solide" : MAX ${tagBudget.solide} fois
|
|
|
|
Budget global restant : ${remainingGlobal.remaining}/${remainingGlobal.total} expressions (${remainingGlobal.percentage}% consommé)
|
|
|
|
🚨 RÈGLE ABSOLUE: Ne dépasse JAMAIS ces limites. Si budget = 0, N'UTILISE PAS ce mot.
|
|
Si tu utilises un mot au-delà de son budget, le texte sera REJETÉ.`;
|
|
}
|
|
|
|
return `MISSION: Améliore UNIQUEMENT les aspects STYLE listés ci-dessous.
|
|
|
|
CONTENU ORIGINAL:
|
|
"${content}"
|
|
|
|
${mc0 ? `CONTEXTE SUJET: ${mc0}` : ''}
|
|
${personality ? `PERSONNALITÉ CIBLE: ${personality.nom} (${personality.style})
|
|
VOCABULAIRE PRÉFÉRÉ: ${personality.vocabulairePref || 'professionnel'}` : 'STYLE: Professionnel standard'}
|
|
INTENSITÉ: ${intensity.toFixed(1)}${budgetConstraints}
|
|
|
|
AMÉLIORATIONS STYLE À APPLIQUER:
|
|
${styleImprovements.map((imp, i) => `${i + 1}. ${imp}`).join('\n')}
|
|
|
|
${analysis.style.genericPhrases && analysis.style.genericPhrases.length > 0 ? `
|
|
EXPRESSIONS GÉNÉRIQUES À PERSONNALISER:
|
|
${analysis.style.genericPhrases.map(phrase => `- "${phrase}"`).join('\n')}
|
|
` : ''}
|
|
|
|
${analysis.style.toneIssues && analysis.style.toneIssues.length > 0 ? `
|
|
PROBLÈMES DE TON IDENTIFIÉS:
|
|
${analysis.style.toneIssues.map(issue => `- ${issue}`).join('\n')}
|
|
` : ''}
|
|
|
|
CONSIGNES STRICTES:
|
|
- Applique UNIQUEMENT les améliorations style listées ci-dessus
|
|
- NE CHANGE PAS le fond du message ni les informations factuelles
|
|
- GARDE la même structure et longueur (±15%)
|
|
${personality ? `- Applique style "${personality.style}" de façon MESURÉE (pas d'exagération)` : '- Style professionnel web standard'}
|
|
- ⚠️ MAINTIENS un TON PROFESSIONNEL (limite connecteurs oraux à 1-2 MAX)
|
|
- ⚠️ ÉVITE bombardement de marqueurs de personnalité
|
|
|
|
EXEMPLES AMÉLIORATION STYLE (génériques multi-secteurs):
|
|
|
|
**E-commerce mode:**
|
|
- ✅ BON: "Cette robe allie élégance et confort" → style commercial mesuré
|
|
- ❌ MAUVAIS: "Écoutez, du coup cette robe, voilà, elle est vraiment top" → trop oral
|
|
|
|
**Services professionnels:**
|
|
- ✅ BON: "Notre expertise comptable garantit votre conformité" → professionnel et spécifique
|
|
- ❌ MAUVAIS: "Nos solutions de qualité" → générique et vague
|
|
|
|
**SaaS/Tech:**
|
|
- ✅ BON: "Automatisez vos workflows en 3 clics" → action concrète
|
|
- ❌ MAUVAIS: "Notre plateforme innovante optimise vos processus" → buzzwords creux
|
|
|
|
**Contenu informatif:**
|
|
- ✅ BON: "Le réchauffement climatique atteint +1.2°C depuis 1850" → factuel et précis
|
|
- ❌ MAUVAIS: "Le réchauffement climatique est un problème important" → vague
|
|
|
|
RÈGLES VOCABULAIRE & TON:
|
|
- Remplace expressions génériques par spécificités
|
|
- 1-2 touches de personnalité par paragraphe MAXIMUM
|
|
- Pas de saturation de connecteurs familiers ("du coup", "voilà", "écoutez")
|
|
- Privilégie authenticité sur artifice
|
|
|
|
FORMAT RÉPONSE:
|
|
Retourne UNIQUEMENT le contenu stylisé, SANS balises, SANS métadonnées, SANS explications.`;
|
|
}
|
|
|
|
/**
|
|
* NETTOYER RÉPONSE
|
|
*/
|
|
cleanResponse(response) {
|
|
if (!response) return response;
|
|
|
|
let cleaned = response.trim();
|
|
|
|
// Supprimer balises
|
|
cleaned = cleaned.replace(/^TAG:\s*[^\s]+\s+/gi, '');
|
|
cleaned = cleaned.replace(/\bTAG:\s*[^\s]+\s+/gi, '');
|
|
cleaned = cleaned.replace(/^CONTENU:\s*/gi, '');
|
|
cleaned = cleaned.replace(/^CONTENU STYLISÉ:\s*/gi, '');
|
|
cleaned = cleaned.replace(/^(voici\s+)?le\s+contenu\s+(stylisé|avec\s+style)\s*[:.]?\s*/gi, '');
|
|
cleaned = cleaned.replace(/^(dans\s+le\s+style\s+de\s+)[^:]*[:.]?\s*/gi, '');
|
|
|
|
// Nettoyer formatage
|
|
cleaned = cleaned.replace(/\*\*([^*]+)\*\*/g, '$1');
|
|
cleaned = cleaned.replace(/\s{2,}/g, ' ');
|
|
cleaned = cleaned.trim();
|
|
|
|
return cleaned;
|
|
}
|
|
|
|
/**
|
|
* COMPTER MODIFICATIONS
|
|
*/
|
|
countModifications(original, improved) {
|
|
if (original === improved) return 0;
|
|
|
|
const originalWords = original.toLowerCase().split(/\s+/);
|
|
const improvedWords = improved.toLowerCase().split(/\s+/);
|
|
|
|
let differences = 0;
|
|
differences += Math.abs(originalWords.length - improvedWords.length);
|
|
|
|
const minLength = Math.min(originalWords.length, improvedWords.length);
|
|
for (let i = 0; i < minLength; i++) {
|
|
if (originalWords[i] !== improvedWords[i]) {
|
|
differences++;
|
|
}
|
|
}
|
|
|
|
return differences;
|
|
}
|
|
|
|
/**
|
|
* ✅ NOUVEAU: Compter expressions familières dans le contenu
|
|
*/
|
|
countFamiliarExpressions(content) {
|
|
const contentLower = content.toLowerCase();
|
|
|
|
return {
|
|
costaud: (contentLower.match(/costaud/g) || []).length,
|
|
nickel: (contentLower.match(/nickel/g) || []).length,
|
|
tipTop: (contentLower.match(/tip[\s-]?top/g) || []).length,
|
|
impeccable: (contentLower.match(/impeccable/g) || []).length,
|
|
solide: (contentLower.match(/solide/g) || []).length,
|
|
duCoup: (contentLower.match(/du coup/g) || []).length,
|
|
voila: (contentLower.match(/voilà/g) || []).length,
|
|
ecoutez: (contentLower.match(/écoutez/g) || []).length
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = { SmartStyleLayer };
|