// ======================================== // 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 };