From 0244521f5c806fa36da8e5fba243fa60a3025c2a Mon Sep 17 00:00:00 2001 From: StillHammer Date: Mon, 13 Oct 2025 15:01:02 +0800 Subject: [PATCH] feat(selective-smart-touch): Add intelligent analysis-driven enhancement system + validation spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## SelectiveSmartTouch (NEW) - Architecture révolutionnaire: Analyse intelligente → Améliorations ciblées précises - 5 modules: SmartAnalysisLayer, SmartTechnicalLayer, SmartStyleLayer, SmartReadabilityLayer, SmartTouchCore - Système 10% segments: amélioration uniquement des segments les plus faibles (intensity-based) - Détection contexte globale pour prompts adaptatifs multi-secteurs - Intégration complète dans PipelineExecutor et PipelineDefinition ## Pipeline Validator Spec (NEW) - Spécification complète système validation qualité par LLM - 5 critères universels: Qualité, Verbosité, SEO, Répétitions, Naturalité - Échantillonnage intelligent par filtrage balises (pas XML) - Évaluation multi-versions avec justifications détaillées - Coût estimé: ~$1/validation (260 appels LLM) ## Optimizations - Réduction intensités fullEnhancement (technical 1.0→0.7, style 0.8→0.5) - Ajout gardes-fous anti-familiarité excessive dans StyleLayer - Sauvegarde étapes intermédiaires activée par défaut (pipeline-runner) ## Fixes - Fix typo critique SmartTouchCore.js:110 (determineLayers ToApply → determineLayersToApply) - Prompts généralisés multi-secteurs (e-commerce, SaaS, services, informatif) 🚀 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude --- PIPELINE_VALIDATOR_SPEC.md | 877 ++++++++++++++++++ lib/Main.js | 9 +- lib/human-simulation/HumanSimulationCore.js | 2 +- lib/modes/ManualServer.js | 7 +- lib/pipeline/PipelineDefinition.js | 19 + lib/pipeline/PipelineExecutor.js | 46 + lib/selective-enhancement/SelectiveLayers.js | 6 +- lib/selective-enhancement/StyleLayer.js | 30 +- lib/selective-enhancement/TechnicalLayer.js | 53 +- lib/selective-enhancement/TransitionLayer.js | 24 +- lib/selective-smart-touch/README.md | 350 +++++++ .../SmartAnalysisLayer.js | 479 ++++++++++ .../SmartReadabilityLayer.js | 210 +++++ lib/selective-smart-touch/SmartStyleLayer.js | 215 +++++ .../SmartTechnicalLayer.js | 278 ++++++ lib/selective-smart-touch/SmartTouchCore.js | 379 ++++++++ public/pipeline-builder.js | 2 +- public/pipeline-runner.html | 10 + public/pipeline-runner.js | 6 +- 19 files changed, 2955 insertions(+), 47 deletions(-) create mode 100644 PIPELINE_VALIDATOR_SPEC.md create mode 100644 lib/selective-smart-touch/README.md create mode 100644 lib/selective-smart-touch/SmartAnalysisLayer.js create mode 100644 lib/selective-smart-touch/SmartReadabilityLayer.js create mode 100644 lib/selective-smart-touch/SmartStyleLayer.js create mode 100644 lib/selective-smart-touch/SmartTechnicalLayer.js create mode 100644 lib/selective-smart-touch/SmartTouchCore.js diff --git a/PIPELINE_VALIDATOR_SPEC.md b/PIPELINE_VALIDATOR_SPEC.md new file mode 100644 index 0000000..8d380a4 --- /dev/null +++ b/PIPELINE_VALIDATOR_SPEC.md @@ -0,0 +1,877 @@ +# 📋 PIPELINE VALIDATOR - Spécification Technique + +## 🎯 OBJECTIF GÉNÉRAL + +Créer une interface dédiée de validation qualitative permettant d'évaluer l'évolution du contenu à travers les différentes étapes d'un pipeline, en utilisant des critères LLM objectifs avec notation et justification détaillée. + +--- + +## 🧠 COMPRÉHENSION DU BESOIN + +### Workflow utilisateur +1. **Chargement** : Sélectionner une config de pipeline + une personnalité Google Sheets +2. **Exécution** : Run le pipeline complet (identique à pipeline-runner) +3. **Échantillonnage intelligent** : Extraire automatiquement 3 types d'éléments du contenu généré +4. **Évaluation multi-critères** : Chaque critère = 1 appel LLM → note/10 + justification +5. **Visualisation comparative** : Voir l'évolution d'un échantillon à travers toutes les étapes + +### Valeur ajoutée +- **Traçabilité** : Comprendre l'impact réel de chaque layer du pipeline +- **Objectivité** : Évaluation quantitative via LLM (pas de subjectivité humaine) +- **Debugging** : Identifier quelle étape dégrade/améliore la qualité +- **Optimisation** : Comparer différentes configs de pipeline scientifiquement + +--- + +## 🏗️ ARCHITECTURE SYSTÈME + +### 1. FICHIERS À CRÉER + +#### Frontend +- **`public/pipeline-validator.html`** - Interface principale + - Section chargement (config + personnalité) + - Zone de contrôle (run, status, progress) + - Panneau d'échantillonnage (liste des 3 types) + - Tableau de scores (critères × étapes) + - Vue détaillée comparative (sélection d'échantillon) + +- **`public/pipeline-validator.js`** - Logique frontend + - Gestion UI (sélections, boutons, modals) + - Communication WebSocket pour progression temps réel + - Rendering des scores et graphiques + - Navigation entre échantillons + +- **`public/css/pipeline-validator.css`** - Styles dédiés + - Layout responsive (sidebar + main content) + - Cartes de scores avec couleurs (vert>7, orange 5-7, rouge<5) + - Vue comparative side-by-side ou verticale + +#### Backend +- **`lib/validation/ValidatorCore.js`** - Orchestrateur principal + - Exécution pipeline avec sauvegarde toutes versions + - Échantillonnage intelligent post-génération + - Orchestration évaluations LLM par critère + - Agrégation résultats + génération rapport + +- **`lib/validation/SamplingEngine.js`** - Moteur d'échantillonnage + - Extraction titres (H1, H2, H3, title tags) + - Sélection paragraphes (longueur moyenne, représentatifs) + - Extraction FAQ (paires question/réponse) + - Parsing XML/HTML intelligent + +- **`lib/validation/CriteriaEvaluator.js`** - Évaluateur multi-critères + - Définition des critères d'évaluation (voir section dédiée) + - Appels LLM avec prompts structurés + - Parsing réponses LLM (score + justification) + - Retry logic + gestion erreurs + +- **`lib/validation/ValidationAPI.js`** - Endpoints API + - POST `/api/validation/run` - Lancer validation complète + - GET `/api/validation/status/:id` - Status temps réel + - GET `/api/validation/results/:id` - Récupérer résultats + - GET `/api/validation/sample/:id/:type/:index` - Détail échantillon + +#### Modifications à faire +- **`lib/APIController.js`** - Ajouter routes validation +- **`lib/pipeline/PipelineExecutor.js`** - Ajouter flag `saveAllVersions: true` pour forcer sauvegarde toutes étapes intermédiaires +- **`server.js`** - Exposer endpoints validation + +--- + +## 📊 ÉCHANTILLONNAGE INTELLIGENT + +### Source des données : Objet `content` (pas de XML) + +Après chaque étape du pipeline, on a un simple objet JavaScript : +```javascript +content = { + "|MC0|": "Texte du mot-clé principal...", + "|T0|": "Titre principal", + "|T-1|": "Sous-titre 1", + "|L-1|": "Texte liste 1...", + "|FAQ_Q1|": "Question FAQ ?", + "|FAQ_A1|": "Réponse FAQ...", + ... +} +``` + +**Versions sauvegardées** : Chaque step sauvegarde son objet `content` en JSON +- `v1.0.json` (génération initiale) +- `v1.1.json` (après step 1) +- `v1.2.json` (après step 2) +- `v2.0.json` (final) + +### Catégories d'échantillons + +#### 1. **Titres** (TOUS) +**Sélection** : Toutes les balises contenant `T` dans leur nom +- `|T0|`, `|T-1|`, `|T+1|`, etc. + +**Pourquoi tous** : Les titres sont critiques pour SEO et structure, peu nombreux + +#### 2. **Contenus principaux** (4 échantillons) +**Sélection** : Balises de contenu long (`MC*`, `L*`) +- Prendre les 4 premières : `|MC0|`, `|MC+1|`, `|L-1|`, `|L+1|` + +**Objectif** : Évaluer la qualité rédactionnelle sur échantillon représentatif + +#### 3. **FAQ** (4 balises = 2 paires) +**Sélection** : Balises contenant `FAQ` +- 2 paires Q/A = 4 balises : `|FAQ_Q1|`, `|FAQ_A1|`, `|FAQ_Q2|`, `|FAQ_A2|` +- Si <2 paires disponibles, prendre ce qui existe + +**Objectif** : Évaluer naturalité question/réponse + +### Algorithme d'échantillonnage + +```javascript +// Charger version finale +const finalContent = JSON.parse(fs.readFileSync('v2.0.json')); +const allTags = Object.keys(finalContent); + +// Catégoriser automatiquement +const samples = { + titles: allTags.filter(tag => tag.includes('T')), // Tous + content: allTags.filter(tag => tag.includes('MC') || tag.includes('L')).slice(0, 4), + faqs: allTags.filter(tag => tag.includes('FAQ')).slice(0, 4) +}; + +// Pour chaque échantillon, extraire versions à travers toutes étapes +for (const tag of [...samples.titles, ...samples.content, ...samples.faqs]) { + const versions = {}; + + for (const versionPath of ['v1.0.json', 'v1.1.json', 'v1.2.json', 'v2.0.json']) { + const versionContent = JSON.parse(fs.readFileSync(versionPath)); + versions[versionPath] = versionContent[tag] || "[Non disponible à cette étape]"; + } + + samplesData[tag] = { + tag, + type: tag.includes('T') ? 'title' : tag.includes('FAQ') ? 'faq' : 'content', + versions + }; +} +``` + +--- + +## 🎯 CRITÈRES D'ÉVALUATION LLM + +### Principe général +- **1 critère = 1 appel LLM** (parallélisation possible) +- **5 critères universels** applicables à tous types de contenu (titres, paragraphes, FAQ) +- **Prompt structuré** : contexte + critère + échelle notation + demande justification +- **Output attendu** : JSON `{ score: 8, reasoning: "Justification détaillée..." }` +- **LLM utilisé** : Claude Sonnet (objectivité optimale) +- **Temperature** : 0.3 (cohérence entre évaluations) + +--- + +### 1. **Qualité globale** (0-10) + +**Évalue** : +- Grammaire, orthographe, syntaxe impeccables +- Cohérence et fluidité du texte +- Pertinence par rapport au contexte (MC0, personnalité) + +**Prompt template** : +``` +Tu es un évaluateur objectif de contenu SEO. + +CONTEXTE: +- Mot-clé principal: {MC0} +- Thématique: {T0} +- Personnalité: {personality.nom} +- Type de contenu: {type} (titre/contenu/faq) + +ÉLÉMENT À ÉVALUER: +"{text}" + +CRITÈRE: Qualité globale +Évalue la qualité rédactionnelle globale : +- Grammaire et syntaxe impeccables ? +- Texte fluide et cohérent ? +- Pertinent par rapport au mot-clé "{MC0}" ? + +ÉCHELLE: +10 = Qualité exceptionnelle, aucune faute +7-9 = Bonne qualité, légères imperfections +4-6 = Qualité moyenne, plusieurs problèmes +1-3 = Faible qualité, nombreuses erreurs +0 = Inutilisable + +Réponds en JSON strict: +{ + "score": 7.5, + "reasoning": "Justification en 2-3 phrases concrètes..." +} +``` + +--- + +### 2. **Verbosité / Concision** (0-10) + +**Évalue** : +- Densité informationnelle (info utile vs fluff) +- Longueur appropriée au type de contenu +- Évite délayage et remplissage inutile + +**Prompt template** : +``` +CRITÈRE: Verbosité et concision +Évalue la concision du texte : +- Densité informationnelle élevée (info utile / longueur totale) ? +- Longueur appropriée pour un {type} (ni trop court, ni verbeux) ? +- Absence de fluff et remplissage inutile ? + +ÉCHELLE: +10 = Parfaitement concis, chaque mot compte +7-9 = Plutôt concis, peu de superflu +4-6 = Moyennement verbeux, du remplissage +1-3 = Très verbeux, beaucoup de fluff +0 = Délayage excessif + +Réponds en JSON strict: +{ + "score": 8.0, + "reasoning": "..." +} +``` + +--- + +### 3. **SEO et mots-clés** (0-10) + +**Évalue** : +- Intégration naturelle des mots-clés pertinents +- Structure optimisée (densité, placement) +- Évite sur-optimisation (keyword stuffing) + +**Prompt template** : +``` +CRITÈRE: SEO et mots-clés +Évalue l'optimisation SEO : +- Mots-clés (notamment "{MC0}") intégrés naturellement ? +- Densité appropriée (ni trop faible, ni keyword stuffing) ? +- Structure SEO-friendly ? + +ÉCHELLE: +10 = SEO optimal et naturel +7-9 = Bon SEO, quelques améliorations possibles +4-6 = SEO moyen, manque d'optimisation ou sur-optimisé +1-3 = SEO faible ou contre-productif +0 = Aucune considération SEO + +Réponds en JSON strict: +{ + "score": 7.0, + "reasoning": "..." +} +``` + +--- + +### 4. **Répétitions et variations** (0-10) + +**Évalue** : +- Évite répétitions lexicales excessives +- Variété du vocabulaire et des formulations +- Usage de synonymes et paraphrases + +**Prompt template** : +``` +CRITÈRE: Répétitions et variations lexicales +Évalue la variété lexicale : +- Répétitions de mots/expressions évitées ? +- Vocabulaire varié et riche ? +- Paraphrases et synonymes utilisés intelligemment ? + +ÉCHELLE: +10 = Très varié, aucune répétition notable +7-9 = Plutôt varié, quelques répétitions mineures +4-6 = Variété moyenne, répétitions visibles +1-3 = Très répétitif, vocabulaire pauvre +0 = Répétitions excessives + +Réponds en JSON strict: +{ + "score": 8.5, + "reasoning": "..." +} +``` + +--- + +### 5. **Naturalité humaine** (0-10) + +**Évalue** : +- Le texte semble-t-il écrit par un humain (vs IA détectable) +- Variations de style, imperfections réalistes +- Évite patterns LLM typiques + +**Prompt template** : +``` +CRITÈRE: Naturalité humaine +Évalue si le texte semble écrit par un humain : +- Semble-t-il rédigé par un humain authentique ? +- Présence de variations naturelles et imperfections réalistes ? +- Absence de patterns IA typiques (phrases trop parfaites, formules creuses, superlatifs excessifs) ? + +ÉCHELLE: +10 = 100% indétectable, parfaitement humain +7-9 = Très naturel, légères traces IA +4-6 = Moyennement naturel, patterns IA visibles +1-3 = Clairement IA, très artificiel +0 = Robotique et détectable immédiatement + +Réponds en JSON strict: +{ + "score": 6.5, + "reasoning": "..." +} +``` + +--- + +### Résumé des 5 critères + +| Critère | Objectif | Score 10 = | Score 0 = | +|---------|----------|------------|-----------| +| **Qualité globale** | Vérifier la qualité rédactionnelle | Impeccable | Inutilisable | +| **Verbosité** | Évaluer la concision | Très concis | Délayage excessif | +| **SEO** | Mesurer l'optimisation | Optimal naturel | Aucun SEO | +| **Répétitions** | Vérifier la variété lexicale | Très varié | Très répétitif | +| **Naturalité** | Détecter l'origine humaine vs IA | 100% humain | Robotique | + +--- + +## 🔄 WORKFLOW D'EXÉCUTION + +### Phase 1 : INITIALISATION (Frontend) +1. Utilisateur charge config de pipeline (dropdown ou upload JSON) +2. Utilisateur sélectionne personnalité (dropdown depuis Google Sheets) +3. Affichage preview : pipeline steps + personnalité info +4. Bouton "Lancer Validation" → POST `/api/validation/run` + +### Phase 2 : EXÉCUTION PIPELINE (Backend - ValidatorCore) +1. Créer UUID unique pour cette validation +2. Appeler PipelineExecutor avec `saveAllVersions: true` + - Force sauvegarde objet `content` après chaque step + - Génère versions JSON : v1.0.json (init) → v1.1.json (step1) → v1.2.json (step2) → v2.0.json (final) +3. Stocker toutes versions dans structure : + ``` + validations/ + {uuid}/ + config.json # Config pipeline utilisée + personality.json # Personnalité sélectionnée + versions/ + v1.0.json # { "|MC0|": "...", "|T0|": "...", ... } + v1.1.json + v1.2.json + v2.0.json + samples/ + all-samples.json # (à créer après échantillonnage) + results/ + evaluations.json # (à créer après évaluations) + ``` +4. WebSocket broadcast progression : "Génération étape 1/4...", "Génération étape 2/4..." + +### Phase 3 : ÉCHANTILLONNAGE (Backend - SamplingEngine) +1. Charger `v2.0.json` (version finale) +2. Extraire échantillons par filtrage automatique des balises : + - Tous titres (balises contenant `T`) → liste `samples.titles` + - 4 contenus (balises `MC*`, `L*`) → liste `samples.content` + - 4 FAQ (balises `FAQ*`) → liste `samples.faqs` +3. Pour chaque échantillon, extraire versions à travers toutes étapes : + ```javascript + // Exemple pour "|MC0|" + for (const versionFile of ['v1.0.json', 'v1.1.json', 'v1.2.json', 'v2.0.json']) { + const versionContent = JSON.parse(fs.readFileSync(versionFile)); + samplesData["|MC0|"].versions[versionFile] = versionContent["|MC0|"]; + } + + // Résultat + { + "tag": "|MC0|", + "type": "content", + "versions": { + "v1.0.json": "Texte initial...", + "v1.1.json": "Texte après step1...", + "v1.2.json": "Texte après step2...", + "v2.0.json": "Texte final..." + } + } + ``` +4. Sauvegarder `samples/all-samples.json` +5. WebSocket broadcast : "Échantillonnage terminé : X titres, Y contenus, Z FAQ" + +### Phase 4 : ÉVALUATION LLM (Backend - CriteriaEvaluator) +1. Pour chaque ÉCHANTILLON (tous types confondus : titres + contenus + FAQ) +2. Pour chaque CRITÈRE (5 critères universels) +3. Pour chaque VERSION (v1.0.json, v1.1.json, v1.2.json, v2.0.json) +4. Faire appel LLM : + ``` + Prompt structure: + --- + Tu es un évaluateur objectif de contenu SEO. + + CONTEXTE: + - Mot-clé principal: {MC0} + - Thématique: {T0} + - Personnalité: {personality.nom} + + ÉLÉMENT À ÉVALUER: + Type: {type} (titre/paragraphe/FAQ) + Contenu: "{content}" + + CRITÈRE: {criteriaName} + Description: {criteriaDescription} + + TÂCHE: + Évalue cet élément selon le critère ci-dessus. + Donne une note de 0 à 10 (précision: 0.5). + Justifie ta notation en 2-3 phrases concrètes. + + RÉPONSE ATTENDUE (JSON strict): + { + "score": 7.5, + "reasoning": "Justification détaillée..." + } + --- + ``` +5. Parser réponse LLM, valider JSON, stocker dans : + ``` + results/ + {sampleId}/ + {criteriaId}/ + v1.0.json + v1.1.json + ... + ``` +6. WebSocket broadcast progression : "Évaluation P1 - Critère 1/5 - Étape 2/4" + +**Optimisation** : Paralléliser appels LLM (max 5 simultanés pour éviter rate limits) + +### Phase 5 : AGRÉGATION (Backend - ValidatorCore) +1. Calculer scores moyens par : + - Échantillon × Critère × Version → score moyen + justifications combinées + - Critère × Version → score moyen global + - Version globale → score moyen tous critères +2. Générer rapport JSON : + ```json + { + "uuid": "...", + "timestamp": "...", + "config": {...}, + "personality": {...}, + "samples": { + "titles": [...], + "paragraphs": [...], + "faqs": [...] + }, + "evaluations": { + "P1": { + "qualite_redactionnelle": { + "v1.0": { "score": 6.5, "reasoning": "..." }, + "v1.1": { "score": 7.0, "reasoning": "..." } + } + } + }, + "aggregated": { + "byVersion": { + "v1.0": { "avgScore": 6.2, "breakdown": {...} }, + "v1.1": { "avgScore": 6.8, "breakdown": {...} } + }, + "byCriteria": {...}, + "overall": { "avgScore": 7.3 } + } + } + ``` +3. Sauvegarder : `validations/{uuid}/report.json` +4. WebSocket broadcast : "✅ Validation terminée ! Score global : 7.3/10" + +--- + +## 🎨 INTERFACE UTILISATEUR + +### Layout général +``` ++---------------------------------------------------------------+ +| PIPELINE VALIDATOR [Config]| ++---------------------------------------------------------------+ +| [Config: default.json ▼] [Personnalité: Sophie ▼] [🚀 RUN] | ++---------------------------------------------------------------+ +| Progress: ████████████████░░░░ 80% - Évaluation P2... | ++---------------------------------------------------------------+ +| ÉCHANTILLONS | VUE DÉTAILLÉE | +| | | +| 📋 Titres (5) | Échantillon sélectionné: P2 | +| ☑ T1: Comment... | ----------------------------------- | +| ☐ T2: Les avantages | 📊 SCORES PAR ÉTAPE | +| ☐ T3: Guide... | v1.0 → v1.1 → v1.2 → v2.0 | +| | 6.5 7.2 7.8 8.1 (Qualité)| +| 📝 Paragraphes (4) | 7.0 7.3 6.8 7.5 (Natural)| +| ☐ P1: Introduction | ... | +| ☑ P2: Description | ----------------------------------- | +| ☐ P3: Technique | 📝 CONTENU PAR ÉTAPE | +| ☐ P4: Conclusion | [v1.0] [v1.1] [v1.2] [v2.0] | +| | (onglets ou slider) | +| ❓ FAQ (2) | | +| ☐ F1: Comment...? | Contenu de P2 à l'étape v1.1: | +| ☐ F2: Pourquoi...? | "Lorem ipsum dolor sit amet..." | +| | | +| | ----------------------------------- | +| | 💬 JUSTIFICATIONS CRITÈRE | +| | Qualité rédactionnelle (v1.1): 7.2 | +| | "La syntaxe est fluide mais..." | ++---------------------------------------------------------------+ +``` + +### Interactions clés + +1. **Sélection échantillon** (sidebar gauche) + - Click sur échantillon → charge vue détaillée + - Highlight actif (bordure bleue) + - Badge avec score moyen global (couleur selon score) + +2. **Vue détaillée - Onglet Scores** + - Graphique ligne : évolution score par critère à travers versions + - Tableau scores : critères (lignes) × versions (colonnes) + - Code couleur : 🟢 >7, 🟠 5-7, 🔴 <5 + - Hover sur score → tooltip avec justification courte + +3. **Vue détaillée - Onglet Comparaison** + - Slider ou onglets pour naviguer entre versions + - Diff highlighting : vert (améliorations), rouge (dégradations) si possible + - Métadonnées : durée step, module appliqué, LLM utilisé + +4. **Vue détaillée - Onglet Justifications** + - Accordéon : 1 section par critère + - Pour chaque critère : timeline versions avec score + reasoning + +5. **Export rapport** + - Bouton "📥 Exporter rapport" → télécharge JSON complet + - Bouton "📊 Exporter CSV scores" → tableau scores pour analyse externe + +--- + +## 🔧 MODIFICATIONS NÉCESSAIRES + +### Fichiers existants à modifier + +#### 1. `lib/pipeline/PipelineExecutor.js` +**Modifications** : +- Ajouter paramètre config : `saveAllVersions: boolean` +- Si `true` : après chaque step, sauvegarder version intermédiaire + - Nommer versions : v1.0, v1.1, v1.2, etc. + - Stocker dans dossier spécifié : `outputDir` +- Retourner array de paths : `versionPaths: ['v1.0.xml', 'v1.1.xml', ...]` + +**Pseudo-code** : +```javascript +SI saveAllVersions === true : + versionPaths = [] + version = 1.0 + + sauvegarder(content, `${outputDir}/v${version}.xml`) + versionPaths.push(...) + + POUR CHAQUE step du pipeline : + appliquer step + version += 0.1 + sauvegarder(content, `${outputDir}/v${version}.xml`) + versionPaths.push(...) + + version = 2.0 + sauvegarder(finalContent, `${outputDir}/v${version}.xml`) + + retourner { content, versionPaths, stats } +``` + +#### 2. `lib/APIController.js` +**Modifications** : +- Importer ValidatorCore, ValidationAPI +- Ajouter routes : + ``` + POST /api/validation/run + GET /api/validation/status/:id + GET /api/validation/results/:id + GET /api/validation/samples/:id + DELETE /api/validation/:id + ``` +- Déléguer logique à ValidationAPI + +#### 3. `server.js` +**Modifications** : +- Exposer routes validation via APIController +- Assurer WebSocket écoute events validation pour broadcast temps réel + +#### 4. `lib/Main.js` (optionnel) +**Modifications** : +- Si ValidatorCore appelle handleFullWorkflow, s'assurer compatibilité avec flag saveAllVersions +- OU créer méthode dédiée `handleValidationWorkflow()` + +--- + +## 📦 DÉPENDANCES + +### NPM packages (déjà présents) +- `express` - API REST +- `ws` - WebSocket temps réel +- Tous les LLM clients (Anthropic, OpenAI, etc.) +- `uuid` - Génération IDs validation +- `fs` - Lecture/écriture fichiers JSON + +### Packages potentiels à ajouter +- `diff` - Highlighting différences entre versions (optionnel, pour UI comparaison) +- Aucune autre dépendance nécessaire (sélection balises = filtrage JavaScript natif) + +--- + +## 🧪 TESTING STRATEGY + +### Tests unitaires +1. **SamplingEngine** : + - Test filtrage titres (object avec balises T*, sans balises T) + - Test sélection contenus (MC*, L*) avec slice + - Test extraction FAQ (balises FAQ*, cas <2 paires disponibles) + +2. **CriteriaEvaluator** : + - Mock appels LLM → test parsing réponses + - Test gestion erreurs (LLM timeout, JSON invalide) + - Test retry logic + +3. **ValidatorCore** : + - Test orchestration complète (mock PipelineExecutor + SamplingEngine + Evaluator) + - Test agrégation scores + +### Tests d'intégration +1. Validation end-to-end : + - Config pipeline simple (2 steps) + personnalité test + - Vérifier génération toutes versions + - Vérifier échantillonnage correct + - Vérifier appels LLM réels (1 critère seulement pour rapidité) + - Vérifier rapport final cohérent + +### Tests UI +1. Chargement configs et personnalités +2. Affichage progression temps réel +3. Navigation entre échantillons +4. Affichage scores et graphiques +5. Export rapport + +--- + +## 📈 MÉTRIQUES DE SUCCÈS + +### Fonctionnel +- ✅ Exécution pipeline complète avec sauvegarde toutes versions +- ✅ Échantillonnage intelligent (diversité respectée) +- ✅ Évaluation LLM tous critères (100% réussite parsing) +- ✅ Rapport JSON complet et cohérent +- ✅ UI responsive et intuitive + +### Performance +- ⏱️ Durée totale validation < 5min (pipeline 4 steps, 9 échantillons, 5 critères = ~180 appels LLM) +- ⏱️ Affichage progression temps réel < 500ms latence +- 💾 Stockage validation < 10MB (JSON + XMLs) + +### UX +- 👁️ Clarté visualisation scores (code couleur intuitif) +- 🔍 Facilité navigation entre échantillons +- 📊 Pertinence justifications LLM (human-readable) + +--- + +## 🚀 PHASES DE DÉVELOPPEMENT + +### Phase 1 : Backend Core (Priorité 1) +1. Créer `ValidatorCore.js` - orchestration basique +2. Modifier `PipelineExecutor.js` - flag saveAllVersions (sauvegarde JSON après chaque step) +3. Créer `SamplingEngine.js` - filtrage balises simple (T*, MC*, L*, FAQ*) +4. Tester workflow complet sans évaluation LLM + +### Phase 2 : Évaluation LLM (Priorité 1) +1. Créer `CriteriaEvaluator.js` - 2 critères seulement (qualité + naturalité) +2. Définir prompts LLM structurés +3. Implémenter parsing + retry logic +4. Tester sur 1 échantillon × 2 critères × 3 versions + +### Phase 3 : API & WebSocket (Priorité 2) +1. Créer `ValidationAPI.js` - endpoints CRUD +2. Intégrer dans `APIController.js` +3. WebSocket broadcast progression +4. Tester via Postman/curl + +### Phase 4 : Frontend Basique (Priorité 2) +1. Créer `pipeline-validator.html` - layout structure +2. Créer `pipeline-validator.js` - logique UI basique +3. Formulaire run + affichage progression +4. Affichage liste échantillons (sans vue détaillée) + +### Phase 5 : Frontend Avancé (Priorité 3) +1. Vue détaillée échantillon (onglets scores/comparaison/justifications) +2. Graphiques évolution scores (Chart.js ou similaire) +3. Diff highlighting versions +4. Export rapport (JSON/CSV) + +### Phase 6 : Critères Complets (Priorité 3) +1. Implémenter tous critères (5 par type) +2. Affiner prompts LLM selon résultats tests +3. Ajouter échantillonnage FAQ + +### Phase 7 : Polish & Optimisation (Priorité 4) +1. Parallélisation appels LLM (pool workers) +2. Cache résultats évaluations (si re-run même config) +3. UI animations et micro-interactions +4. Documentation utilisateur + +--- + +## ⚠️ POINTS D'ATTENTION + +### Challenges techniques + +1. **Volume appels LLM** : + - Exemple : 4 steps × 9 échantillons × 5 critères = 180 appels + - Solution : Parallélisation + gestion rate limits + - Alternative : Évaluer seulement versions clés (v1.0, milieu, v2.0) + +2. **Sélection balises robuste** : + - Structures de balises variées selon templates + - Solution : Filtrage simple par `.includes()` avec fallbacks, gestion balises manquantes + +3. **Cohérence évaluations LLM** : + - Variabilité réponses LLM même prompt + - Solution : Temperature basse (0.3), prompts très structurés, moyenne sur 2-3 runs ? + +4. **Storage validations** : + - Accumulation fichiers si beaucoup de validations + - Solution : Cleanup automatique après 7 jours, ou limit stockage 50 validations + +### UX considérations + +1. **Durée exécution longue** (3-5min) + - User peut quitter page → validation continue en background + - Notification email/webhook quand terminé ? + - OU : Polling API status si user revient + +2. **Complexité interface** : + - Beaucoup d'informations (scores, justifications, versions) + - Solution : Progressive disclosure (accordéons, onglets), filtres + +3. **Interprétation scores** : + - User peut ne pas comprendre critères + - Solution : Tooltips explicatifs, guide méthodologie + +--- + +## 🎓 ÉVOLUTIONS FUTURES + +### V2 Features +1. **Comparaison multi-validations** : + - Comparer 2 configs de pipeline côte à côte + - Graphique : évolution scores Config A vs Config B + +2. **Critères personnalisés** : + - User peut définir ses propres critères d'évaluation + - Upload JSON ou formulaire UI + +3. **Benchmarking automatique** : + - Base de données scores moyens par secteur/type contenu + - Afficher percentile (votre score vs moyenne) + +4. **Export rapport PDF** : + - Rapport visuel professionnel (graphiques, tableaux) + - Pour présentation clients/stakeholders + +5. **A/B Testing intégré** : + - Définir 2+ variants pipeline + - Run validation sur tous, afficher gagnant selon critères pondérés + +--- + +## ✅ CHECKLIST PRÉ-DÉVELOPPEMENT + +Avant de commencer à coder, valider : + +- [ ] Comprendre parfaitement workflow utilisateur (run test manuel similaire) +- [ ] Valider structure données échantillons (JSON schema) +- [ ] Tester 1 prompt LLM évaluation manuellement (Claude playground) +- [ ] Vérifier `PipelineExecutor` actuel peut sauvegarder versions intermédiaires +- [ ] Confirmer structure dossiers validations (`validations/{uuid}/...`) +- [ ] Designer mockup UI (Figma/papier) pour valider UX +- [ ] Estimer coût LLM par validation (180 appels × prix Claude) + +--- + +## 💰 ESTIMATION COÛTS LLM + +### Hypothèses +- Pipeline : 4 steps → 4 versions (v1.0, v1.1, v1.2, v2.0) +- Échantillons : ~5 titres + 4 contenus + 4 FAQ = **13 balises** +- Critères : **5 critères universels** (applicables à tous) +- Total appels : 13 échantillons × 5 critères × 4 versions = **260 appels** + +### Coût par appel (Claude Sonnet) +- Input : ~500 tokens (contexte + prompt + échantillon) +- Output : ~150 tokens (JSON score + reasoning) +- Prix Claude Sonnet 4.5 : $3/M input + $15/M output +- Coût par appel : (500 × $3/1M) + (150 × $15/1M) = $0.0015 + $0.00225 = **~$0.00375** + +### Coût total par validation +- 260 appels × $0.00375 = **~$0.98 par validation** (≈ $1) + +### Optimisations possibles +1. **Réduire versions évaluées** (seulement v1.0, v1.2, v2.0) → 3 versions au lieu de 4 = **-25% = $0.73** +2. **Critères prioritaires** (3 au lieu de 5 : Qualité, SEO, Naturalité) → **-40% = $0.59** +3. **Échantillonnage réduit** (3 titres, 2 contenus, 2 FAQ = 7 balises) → **-46% = $0.53** +4. **Combiner toutes optimisations** → **~$0.25-$0.35 par validation** + +### Recommandation +**Configuration standard** : 260 appels, $1/validation → Bon compromis exhaustivité/coût + +**Configuration économique** : 3 critères + 3 versions + 7 balises = 63 appels → **$0.24/validation** + +--- + +## 📝 RÉSUMÉ EXÉCUTIF + +### Ce qui sera créé +1. **Interface web complète** : `pipeline-validator.html/.js/.css` +2. **Backend validation** : 4 nouveaux modules (`ValidatorCore`, `SamplingEngine`, `CriteriaEvaluator`, `ValidationAPI`) +3. **Modifications légères** : `PipelineExecutor` (flag saveAllVersions), `APIController` (routes) +4. **Système d'évaluation** : **5 critères universels** LLM (Qualité, Verbosité, SEO, Répétitions, Naturalité), notation 0-10 + justifications +5. **Visualisation avancée** : Scores évolution, comparaison versions, justifications détaillées + +### Format de données +- **Entrée** : Objet JavaScript `content = { "|MC0|": "texte", "|T0|": "titre", ... }` +- **Versions sauvegardées** : JSON (`v1.0.json`, `v1.1.json`, `v1.2.json`, `v2.0.json`) +- **Échantillonnage** : Sélection automatique par filtrage de balises (pas de parsing XML) +- **Output** : Rapport JSON complet avec scores et justifications + +### Code réutilisé +- 70% logique `pipeline-runner.html/.js` (UI run, progression, WebSocket) +- 100% `PipelineExecutor.js` (juste ajout flag `saveAllVersions`) +- 100% `LLMManager.js` (appels LLM pour évaluations) +- Structure dossiers `configs/`, `personalities` (Google Sheets) + +### Complexité +- **Backend** : Moyenne (orchestration simple, sélection balises, appels LLM) +- **Frontend** : Moyenne-Haute (visualisation scores, comparaison versions) +- **Durée développement estimée** : 3-4 jours (phases 1-5) + +### Coût opérationnel +- **~$1 par validation** (260 appels LLM Claude Sonnet) +- **Mode économique** : $0.24/validation (63 appels) + +### Valeur business +- ✅ **Objectivité** : Validation qualité quantitative (vs jugement subjectif) +- ✅ **Traçabilité** : Impact mesurable de chaque layer pipeline +- ✅ **Optimisation** : Comparaison scientifique de configs (data-driven) +- ✅ **Reporting** : Scores quantitatifs présentables aux clients +- ✅ **Debugging** : Identification précise des étapes qui dégradent/améliorent + +--- + +**DOCUMENT PRÊT POUR VALIDATION AVANT DÉVELOPPEMENT** 🚀 diff --git a/lib/Main.js b/lib/Main.js index 7b7df7b..e773190 100644 --- a/lib/Main.js +++ b/lib/Main.js @@ -1006,12 +1006,15 @@ module.exports = { const executor = new PipelineExecutor(); + // ✅ Récupérer saveIntermediateSteps depuis data.options.saveIntermediateSteps OU data.saveIntermediateSteps + const saveIntermediateSteps = data.options?.saveIntermediateSteps || data.saveIntermediateSteps || false; + const result = await executor.execute( data.pipelineConfig, data.rowNumber || 2, { stopOnError: data.stopOnError, - saveIntermediateSteps: data.saveIntermediateSteps || false // ✅ Passer saveIntermediateSteps + saveIntermediateSteps // ✅ Passer saveIntermediateSteps } ); @@ -1020,13 +1023,13 @@ module.exports = { success: result.success, finalContent: result.finalContent, executionLog: result.executionLog, + versionHistory: result.versionHistory, // ✅ Inclure versionHistory stats: { totalDuration: result.metadata.totalDuration, personality: result.metadata.personality, pipelineName: result.metadata.pipelineName, totalSteps: result.metadata.totalSteps, - successfulSteps: result.metadata.successfulSteps, - versionHistory: result.versionHistory // ✅ Inclure versionHistory + successfulSteps: result.metadata.successfulSteps } }; } diff --git a/lib/human-simulation/HumanSimulationCore.js b/lib/human-simulation/HumanSimulationCore.js index b4d5be8..ed65973 100644 --- a/lib/human-simulation/HumanSimulationCore.js +++ b/lib/human-simulation/HumanSimulationCore.js @@ -92,7 +92,7 @@ async function applyHumanSimulationLayer(content, options = {}) { processedContent = fatigueResult.content; elementModifications += fatigueResult.modifications; simulationStats.fatigueModifications += fatigueResult.modifications; - + logSh(` 💤 Fatigue: ${fatigueResult.modifications} modifications (niveau: ${globalContext.fatigueLevel.toFixed(2)})`, 'DEBUG'); } diff --git a/lib/modes/ManualServer.js b/lib/modes/ManualServer.js index 75727d5..c762c8d 100644 --- a/lib/modes/ManualServer.js +++ b/lib/modes/ManualServer.js @@ -622,7 +622,7 @@ class ManualServer { // Exécuter un pipeline this.app.post('/api/pipeline/execute', async (req, res) => { try { - const { pipelineConfig, rowNumber } = req.body; + const { pipelineConfig, rowNumber, options = {} } = req.body; if (!pipelineConfig) { return res.status(400).json({ @@ -639,12 +639,16 @@ class ManualServer { } logSh(`🚀 Exécution pipeline: ${pipelineConfig.name} (row ${rowNumber})`, 'INFO'); + if (options.saveIntermediateSteps) { + logSh(` 💾 Sauvegarde étapes intermédiaires ACTIVÉE`, 'INFO'); + } const { handleFullWorkflow } = require('../Main'); const result = await handleFullWorkflow({ pipelineConfig, rowNumber, + options, // ✅ Transmettre les options au workflow source: 'pipeline_api' }); @@ -653,6 +657,7 @@ class ManualServer { result: { finalContent: result.finalContent, executionLog: result.executionLog, + versionHistory: result.versionHistory, // ✅ Inclure version history stats: result.stats } }); diff --git a/lib/pipeline/PipelineDefinition.js b/lib/pipeline/PipelineDefinition.js index 30901c9..1a8e86f 100644 --- a/lib/pipeline/PipelineDefinition.js +++ b/lib/pipeline/PipelineDefinition.js @@ -45,6 +45,24 @@ const AVAILABLE_MODULES = { llmProvider: { type: 'string', enum: AVAILABLE_LLM_PROVIDERS.map(p => p.id), default: 'gpt-4o-mini' } } }, + smarttouch: { + name: 'SmartTouch (Analyse→Ciblé)', + description: 'Analyse intelligente puis améliorations précises ciblées (nouvelle génération)', + modes: [ + 'full', // Analyse + Technical + Style + Readability + 'analysis_only', // Analyse uniquement sans amélioration + 'technical_only', // Améliorations techniques ciblées uniquement + 'style_only', // Améliorations style ciblées uniquement + 'readability_only' // Améliorations lisibilité ciblées uniquement + ], + defaultIntensity: 1.0, + defaultLLM: 'gpt-4o-mini', + parameters: { + llmProvider: { type: 'string', enum: AVAILABLE_LLM_PROVIDERS.map(p => p.id), default: 'gpt-4o-mini' }, + skipAnalysis: { type: 'boolean', default: false, description: 'Passer l\'analyse (mode legacy)' }, + layersOrder: { type: 'array', default: ['technical', 'style', 'readability'], description: 'Ordre d\'application des couches' } + } + }, adversarial: { name: 'Adversarial Generation', description: 'Techniques anti-détection', @@ -289,6 +307,7 @@ class PipelineDefinition { const DURATIONS = { generation: 15, selective: 20, + smarttouch: 25, // ✅ AJOUTÉ: smarttouch (analyse + améliorations ciblées) adversarial: 25, human: 15, pattern: 18 diff --git a/lib/pipeline/PipelineExecutor.js b/lib/pipeline/PipelineExecutor.js index 682d59f..ae97964 100644 --- a/lib/pipeline/PipelineExecutor.js +++ b/lib/pipeline/PipelineExecutor.js @@ -18,6 +18,7 @@ const { saveGeneratedArticleOrganic } = require('../ArticleStorage'); const { generateSimple } = require('../selective-enhancement/SelectiveUtils'); const { applySelectiveLayer } = require('../selective-enhancement/SelectiveCore'); const { applyPredefinedStack: applySelectiveStack } = require('../selective-enhancement/SelectiveLayers'); +const { SmartTouchCore } = require('../selective-smart-touch/SmartTouchCore'); // ✅ NOUVEAU: SelectiveSmartTouch const { applyAdversarialLayer } = require('../adversarial-generation/AdversarialCore'); const { applyPredefinedStack: applyAdversarialStack } = require('../adversarial-generation/AdversarialLayers'); const { applyHumanSimulationLayer } = require('../human-simulation/HumanSimulationCore'); @@ -192,6 +193,9 @@ class PipelineExecutor { case 'selective': return await this.runSelective(step, csvData); + case 'smarttouch': // ✅ NOUVEAU: SelectiveSmartTouch + return await this.runSmartTouch(step, csvData); + case 'adversarial': return await this.runAdversarial(step, csvData); @@ -295,6 +299,48 @@ class PipelineExecutor { }, { mode: step.mode, intensity: step.intensity }); } + /** + * ✅ NOUVEAU: Exécute SelectiveSmartTouch (Analyse→Améliorations ciblées) + */ + async runSmartTouch(step, csvData) { + return tracer.run('PipelineExecutor.runSmartTouch', async () => { + + if (!this.currentContent) { + throw new Error('Aucun contenu à améliorer. Génération requise avant SmartTouch'); + } + + // ✅ Extraire llmProvider depuis parameters (comme les autres modules) + const llmProvider = step.parameters?.llmProvider || 'gpt-4o-mini'; // Default gpt-4o-mini pour analyse objective + + logSh(`🧠 SMART TOUCH: Mode ${step.mode}, LLM: ${llmProvider}`, 'INFO'); + + // Instancier SmartTouchCore + const smartTouch = new SmartTouchCore(); + + // Configuration + const config = { + mode: step.mode || 'full', // full, analysis_only, technical_only, style_only, readability_only + intensity: step.intensity || 1.0, + csvData, + llmProvider: llmProvider, // ✅ Passer le LLM choisi dans pipeline + skipAnalysis: step.parameters?.skipAnalysis || false, + layersOrder: step.parameters?.layersOrder || ['technical', 'style', 'readability'] + }; + + // Exécuter SmartTouch + const result = await smartTouch.apply(this.currentContent, config); + + logSh(`✓ SmartTouch: ${result.modifications || 0} modifications appliquées avec ${llmProvider}`, 'DEBUG'); + + return { + content: result.content || result, + modifications: result.modifications || 0, + analysisResults: result.analysisResults // Inclure analyse pour debugging + }; + + }, { mode: step.mode, intensity: step.intensity }); + } + /** * Exécute l'adversarial generation */ diff --git a/lib/selective-enhancement/SelectiveLayers.js b/lib/selective-enhancement/SelectiveLayers.js index 0641960..89eeb20 100644 --- a/lib/selective-enhancement/SelectiveLayers.js +++ b/lib/selective-enhancement/SelectiveLayers.js @@ -36,10 +36,10 @@ const PREDEFINED_STACKS = { // Stack complet - Toutes couches séquentielles fullEnhancement: { name: 'fullEnhancement', - description: 'Enhancement complet multi-LLM (OpenAI + Mistral)', + description: 'Enhancement complet multi-LLM (OpenAI + Mistral) - modéré pour éviter sur-stylisation', layers: [ - { type: 'technical', llm: 'gpt-4o-mini', intensity: 1.0 }, - { type: 'style', llm: 'mistral-small', intensity: 0.8 } + { type: 'technical', llm: 'gpt-4o-mini', intensity: 0.7 }, // ✅ MODÉRÉ: Réduit de 1.0 à 0.7 + { type: 'style', llm: 'mistral-small', intensity: 0.5 } // ✅ MODÉRÉ: Réduit de 0.8 à 0.5 pour éviter familiarité excessive ], layersCount: 2 }, diff --git a/lib/selective-enhancement/StyleLayer.js b/lib/selective-enhancement/StyleLayer.js index 54055a5..e99da11 100644 --- a/lib/selective-enhancement/StyleLayer.js +++ b/lib/selective-enhancement/StyleLayer.js @@ -435,9 +435,8 @@ INTENSITÉ: ${config.intensity} (0.5=léger, 1.0=standard, 1.5=intensif) CONTENUS À STYLISER: -${chunk.map((item, i) => `[${i + 1}] TAG: ${item.tag} -PROBLÈMES: ${item.styleIssues.join(', ')} -CONTENU: "${item.content}"`).join('\n\n')} +${chunk.map((item, i) => `[${i + 1}] "${item.content}" +PROBLÈMES STYLE: ${item.styleIssues.join(', ')}`).join('\n\n')} PROFIL PERSONNALITÉ ${personality?.nom || 'Standard'}: ${personality ? `- Style: ${personality.style} @@ -459,20 +458,29 @@ CONSIGNES STRICTES: - Applique SEULEMENT style et personnalité sur la forme - RESPECTE impérativement le niveau ${personality?.niveauTechnique || 'standard'} - ÉVITE exagération qui rendrait artificiel +- JAMAIS de répétitions de paragraphes entiers +- ⚠️ MAINTIENS un TON PROFESSIONNEL même avec personnalité décontractée +- ⚠️ LIMITE l'usage des connecteurs familiers ("du coup", "voilà", "écoutez") à 1-2 MAX par texte TECHNIQUES STYLE: ${personality?.vocabulairePref ? `- Intégrer naturellement: ${personality.vocabulairePref}` : '- Vocabulaire professionnel équilibré'} - Adapter registre de langue selon ${personality?.style || 'professionnel'} - Expressions et tournures caractéristiques personnalité - Ton cohérent: ${this.getExpectedTone(personality)} mais naturel -- Connecteurs préférés: ${personality?.connecteursPref || 'variés et naturels'} +- Connecteurs préférés: ${personality?.connecteursPref || 'variés et naturels'} - MAIS AVEC PARCIMONIE (max 1-2 par texte) + +RÈGLES VOCABULAIRE: +- ✅ BON: "Pour entretenir votre plaque, nettoyez-la régulièrement" → pro et direct +- ❌ MAUVAIS: "Écoutez, pour entretenir votre plaque, donc du coup, voilà ce qu'il faut faire" → trop familier +- ✅ BON: 1-2 touches de personnalité par paragraphe maximum +- ❌ MAUVAIS: Bombarder chaque phrase de marqueurs oraux excessifs FORMAT RÉPONSE: [1] Contenu avec style personnalisé [2] Contenu avec style personnalisé etc... -IMPORTANT: Réponse DIRECTE par les contenus stylisés, pas d'explication.`; +IMPORTANT: Réponds UNIQUEMENT par les contenus stylisés, SANS balises TAG, SANS métadonnées, SANS explications.`; return prompt; } @@ -519,17 +527,23 @@ IMPORTANT: Réponse DIRECTE par les contenus stylisés, pas d'explication.`; */ cleanStyleContent(content) { if (!content) return content; - + + // ✅ Supprimer balises TAG résiduelles + content = content.replace(/^TAG:\s*[^\s]+\s+/gi, ''); + content = content.replace(/\bTAG:\s*[^\s]+\s+/gi, ''); + content = content.replace(/^CONTENU:\s*/gi, ''); + content = content.replace(/^PROBLÈMES STYLE:\s*/gi, ''); + // Supprimer préfixes indésirables content = content.replace(/^(voici\s+)?le\s+contenu\s+(stylisé|adapté|personnalisé)\s*[:.]?\s*/gi, ''); content = content.replace(/^(avec\s+)?style\s+[^:]*\s*[:.]?\s*/gi, ''); content = content.replace(/^(dans\s+le\s+style\s+de\s+)[^:]*[:.]?\s*/gi, ''); - + // Nettoyer formatage content = content.replace(/\*\*[^*]+\*\*/g, ''); // Gras markdown content = content.replace(/\s{2,}/g, ' '); // Espaces multiples content = content.trim(); - + return content; } } diff --git a/lib/selective-enhancement/TechnicalLayer.js b/lib/selective-enhancement/TechnicalLayer.js index e84aaf5..e00320c 100644 --- a/lib/selective-enhancement/TechnicalLayer.js +++ b/lib/selective-enhancement/TechnicalLayer.js @@ -330,7 +330,7 @@ class TechnicalLayer { */ createTechnicalEnhancementPrompt(chunk, csvData, config) { const personality = csvData?.personality; - + let prompt = `MISSION: Améliore UNIQUEMENT la précision technique de ces contenus. CONTEXTE: ${csvData?.mc0 || 'Signalétique personnalisée'} - Secteur: impression/signalétique @@ -339,31 +339,39 @@ INTENSITÉ: ${config.intensity} (0.5=léger, 1.0=standard, 1.5=intensif) ÉLÉMENTS À AMÉLIORER TECHNIQUEMENT: -${chunk.map((item, i) => `[${i + 1}] TAG: ${item.tag} -CONTENU: "${item.content}" -AMÉLIORATIONS: ${item.improvements.join(', ')} -${item.missingTerms.length > 0 ? `TERMES À INTÉGRER: ${item.missingTerms.join(', ')}` : ''}`).join('\n\n')} +${chunk.map((item, i) => `[${i + 1}] "${item.content}" +AMÉLIORATIONS SUGGÉRÉES: ${item.improvements.join(', ')} +${item.missingTerms.length > 0 ? `TERMES UTILES (à intégrer si pertinent): ${item.missingTerms.join(', ')}` : ''}`).join('\n\n')} CONSIGNES TECHNIQUES: - GARDE exactement le même message et ton${personality ? ` ${personality.style}` : ''} -- AJOUTE précision technique naturelle et vocabulaire spécialisé -- INTÈGRE termes métier : matériaux, procédés, normes, dimensions -- REMPLACE vocabulaire générique par termes techniques appropriés -- ÉVITE jargon incompréhensible, reste accessible +- AJOUTE précision technique NATURELLE sans sur-techniciser +- INTÈGRE termes métier SEULEMENT si utiles au lecteur: matériaux, dimensions, normes +- RESTE ACCESSIBLE au grand public - privilégie clarté sur technicité excessive +- ÉVITE absolument le jargon pompeux ("plaque numérologique domestique" → "numéro de maison") - PRESERVE longueur approximative (±15%) +- JAMAIS de répétitions de paragraphes entiers -VOCABULAIRE TECHNIQUE RECOMMANDÉ: -- Matériaux: dibond, aluminium anodisé, PMMA coulé, PVC expansé -- Procédés: impression UV, gravure laser, découpe numérique, fraisage CNC -- Finitions: brossé, poli, texturé, laqué -- Fixations: perçage, adhésif double face, vis inox, plots de fixation +RÈGLES VOCABULAIRE TECHNIQUE: +- ✅ BON: "plaque en aluminium 3mm" → clair et précis +- ❌ MAUVAIS: "support métallique en alliage d'aluminium anodisé de calibre 3mm" → pompeux +- ✅ BON: "impression UV haute qualité" → informatif +- ❌ MAUVAIS: "procédé d'impression par rayonnement ultraviolet avec encres polymériques" → trop technique +- ✅ BON: Mentionner 1-2 détails techniques utiles par paragraphe +- ❌ MAUVAIS: Bombarder chaque phrase de termes techniques + +VOCABULAIRE TECHNIQUE AUTORISÉ (utiliser avec modération): +- Matériaux basiques: dibond, aluminium, PVC, acrylique +- Procédés simples: impression UV, gravure laser, découpe numérique +- Dimensions standards: 3mm, 30x20cm, format A4 +- ÉVITER: PMMA coulé, fraisage CNC, anodisation, spécifications ISO (sauf si vraiment pertinent) FORMAT RÉPONSE: -[1] Contenu avec amélioration technique précise -[2] Contenu avec amélioration technique précise +[1] Contenu amélioré avec précision technique modérée +[2] Contenu amélioré avec précision technique modérée etc... -IMPORTANT: Réponse DIRECTE par les contenus améliorés, pas d'explication.`; +IMPORTANT: Réponds UNIQUEMENT par les contenus améliorés, SANS balises TAG, SANS métadonnées, SANS explications.`; return prompt; } @@ -410,17 +418,22 @@ IMPORTANT: Réponse DIRECTE par les contenus améliorés, pas d'explication.`; */ cleanTechnicalContent(content) { if (!content) return content; - + + // ✅ Supprimer balises TAG résiduelles + content = content.replace(/^TAG:\s*[^\s]+\s+/gi, ''); + content = content.replace(/\bTAG:\s*[^\s]+\s+/gi, ''); + content = content.replace(/^CONTENU:\s*/gi, ''); + // Supprimer préfixes indésirables content = content.replace(/^(voici\s+)?le\s+contenu\s+amélioré\s*[:.]?\s*/gi, ''); content = content.replace(/^(avec\s+)?amélioration\s+technique\s*[:.]?\s*/gi, ''); content = content.replace(/^(bon,?\s*)?(alors,?\s*)?pour\s+/gi, ''); - + // Nettoyer formatage content = content.replace(/\*\*[^*]+\*\*/g, ''); // Gras markdown content = content.replace(/\s{2,}/g, ' '); // Espaces multiples content = content.trim(); - + return content; } diff --git a/lib/selective-enhancement/TransitionLayer.js b/lib/selective-enhancement/TransitionLayer.js index f449ad3..dedfb19 100644 --- a/lib/selective-enhancement/TransitionLayer.js +++ b/lib/selective-enhancement/TransitionLayer.js @@ -390,7 +390,7 @@ class TransitionLayer { */ createTransitionEnhancementPrompt(chunk, csvData, config) { const personality = csvData?.personality; - + let prompt = `MISSION: Améliore UNIQUEMENT les transitions et fluidité de ces contenus. CONTEXTE: Article SEO ${csvData?.mc0 || 'signalétique personnalisée'} @@ -400,9 +400,8 @@ INTENSITÉ: ${config.intensity} (0.5=léger, 1.0=standard, 1.5=intensif) CONTENUS À FLUIDIFIER: -${chunk.map((item, i) => `[${i + 1}] TAG: ${item.tag} -PROBLÈMES: ${item.issues.join(', ')} -CONTENU: "${item.content}"`).join('\n\n')} +${chunk.map((item, i) => `[${i + 1}] "${item.content}" +PROBLÈMES DÉTECTÉS: ${item.issues.join(', ')}`).join('\n\n')} OBJECTIFS FLUIDITÉ: - Connecteurs plus naturels et variés${personality?.connecteursPref ? `: ${personality.connecteursPref}` : ''} @@ -417,10 +416,11 @@ CONSIGNES STRICTES: - Améliore SEULEMENT la fluidité et les enchaînements - RESPECTE le style ${personality?.nom || 'professionnel'}${personality?.style ? ` (${personality.style})` : ''} - ÉVITE sur-correction qui rendrait artificiel +- JAMAIS de répétitions de paragraphes entiers TECHNIQUES FLUIDITÉ: - Varier connecteurs logiques sans répétition -- Alterner phrases courtes (8-12 mots) et moyennes (15-20 mots) +- Alterner phrases courtes (8-12 mots) et moyennes (15-20 mots) - Utiliser pronoms et reprises pour cohésion - Ajouter transitions implicites par reformulation - Équilibrer registre soutenu/accessible @@ -430,7 +430,7 @@ FORMAT RÉPONSE: [2] Contenu avec transitions améliorées etc... -IMPORTANT: Réponse DIRECTE par les contenus fluidifiés, pas d'explication.`; +IMPORTANT: Réponds UNIQUEMENT par les contenus fluidifiés, SANS balises TAG, SANS métadonnées, SANS explications.`; return prompt; } @@ -477,17 +477,23 @@ IMPORTANT: Réponse DIRECTE par les contenus fluidifiés, pas d'explication.`; */ cleanTransitionContent(content) { if (!content) return content; - + + // ✅ Supprimer balises TAG résiduelles + content = content.replace(/^TAG:\s*[^\s]+\s+/gi, ''); + content = content.replace(/\bTAG:\s*[^\s]+\s+/gi, ''); + content = content.replace(/^CONTENU:\s*/gi, ''); + content = content.replace(/^PROBLÈMES DÉTECTÉS:\s*/gi, ''); + // Supprimer préfixes indésirables content = content.replace(/^(voici\s+)?le\s+contenu\s+(fluidifié|amélioré)\s*[:.]?\s*/gi, ''); content = content.replace(/^(avec\s+)?transitions\s+améliorées\s*[:.]?\s*/gi, ''); content = content.replace(/^(bon,?\s*)?(alors,?\s*)?/, ''); - + // Nettoyer formatage content = content.replace(/\*\*[^*]+\*\*/g, ''); // Gras markdown content = content.replace(/\s{2,}/g, ' '); // Espaces multiples content = content.trim(); - + return content; } } diff --git a/lib/selective-smart-touch/README.md b/lib/selective-smart-touch/README.md new file mode 100644 index 0000000..e14383b --- /dev/null +++ b/lib/selective-smart-touch/README.md @@ -0,0 +1,350 @@ +# SelectiveSmartTouch - Architecture Nouvelle Génération + +## 🎯 **Concept Révolutionnaire** + +SelectiveSmartTouch est une **refonte complète de l'approche Selective Enhancement** basée sur le principe **Analyse → Amélioration Ciblée**. + +### **Problème de l'ancienne approche** +``` +❌ Approche "Améliore tout" (Selective Enhancement legacy): +LLM: "Voici un texte, améliore-le techniquement" +→ LLM devine ce qui manque +→ Résultats aléatoires, parfois hors-sujet +→ Prompts spécifiques à la signalétique (pas généralisable) +→ Pas de contrôle précis sur les modifications +``` + +### **Solution SmartTouch** +``` +✅ Approche Analyse→Ciblée (SelectiveSmartTouch): +1. ANALYSE (LLM température 0.2): "Ce texte manque de : X, Y, Z" +2. AMÉLIORATION GUIDÉE: "Ajoute précisément X, Y, Z sans toucher au reste" +→ Contrôle total, résultats prévisibles +→ Prompts génériques (multi-secteurs) +→ Logs structurés JSON (debugging facile) +→ Coûts optimisés (analyse=petit modèle) +``` + +--- + +## 📂 **Architecture Modulaire** + +### **Phase 1: Analyse Intelligente** +**SmartAnalysisLayer.js** - Analyse objective du contenu + +```javascript +const analysis = await smartAnalysis.analyzeElement(content, { mc0, personality }); + +// Retourne JSON structuré: +{ + "technical": { + "needed": true, + "score": 0.4, + "missing": ["données chiffrées", "spécifications"], + "issues": ["vocabulaire trop générique"] + }, + "style": { + "needed": true, + "score": 0.5, + "genericPhrases": ["nos solutions", "notre expertise"] + }, + "readability": { + "needed": false, + "score": 0.8, + "complexSentences": [], + "repetitiveConnectors": [] + }, + "improvements": [ + "Ajouter données concrètes (chiffres, dimensions)", + "Remplacer expressions génériques: nos solutions, notre expertise" + ], + "overallScore": 0.57 +} +``` + +**Caractéristiques** : +- LLM: GPT-4o-mini (objectivité) +- Température: 0.2 (précision max) +- Fallback algorithmique si LLM échoue +- Analyse multi-dimensionnelle (technique, style, lisibilité, vocabulaire) + +### **Phase 2: Améliorations Ciblées** + +#### **SmartTechnicalLayer.js** +Applique **UNIQUEMENT** les améliorations techniques identifiées + +```javascript +const result = await smartTechnical.applyTargeted(content, analysis, { + mc0, personality, intensity: 1.0 +}); + +// Skip automatique si analysis.technical.needed === false +// Prompt ciblé: "Ajoute données chiffrées, remplace vocabulaire générique" +``` + +**Prompts génériques multi-secteurs** : +- E-commerce: "Dimensions: 30x20cm, épaisseur 3mm" +- SaaS: "Compatible avec 95% des systèmes" +- Services: "Délai: 3-5 jours ouvrés" + +#### **SmartStyleLayer.js** +Améliorations style **UNIQUEMENT** si nécessaire + +```javascript +const result = await smartStyle.applyTargeted(content, analysis, { + mc0, personality, intensity: 1.0 +}); + +// Prompts adaptatifs selon secteur +// Exemples mode, SaaS, services, contenu informatif +``` + +#### **SmartReadabilityLayer.js** +Lisibilité **UNIQUEMENT** si score < 0.6 + +```javascript +const result = await smartReadability.applyTargeted(content, analysis, { + intensity: 1.0 +}); + +// Simplifie phrases longues +// Varie connecteurs répétitifs +// Fluidifie structure +``` + +--- + +## 🚀 **Utilisation** + +### **1. Via SmartTouchCore (orchestrateur)** + +```javascript +const { SmartTouchCore } = require('./selective-smart-touch/SmartTouchCore'); + +const smartTouch = new SmartTouchCore(); + +const result = await smartTouch.apply(content, { + mode: 'full', // ou 'technical_only', 'style_only', etc. + intensity: 1.0, + csvData: { mc0, personality }, + layersOrder: ['technical', 'style', 'readability'] // Personnalisable +}); + +console.log(result.modifications); // Nombre de modifications +console.log(result.analysisResults); // Analyse JSON détaillée +``` + +### **2. Via Pipeline Builder (UI)** + +1. Glisser module **"SmartTouch (Analyse→Ciblé)"** depuis palette Enhancement +2. Choisir mode: + - **full**: Analyse + toutes améliorations (recommandé) + - **analysis_only**: Analyse seule pour debugging + - **technical_only**: Technique uniquement + - **style_only**: Style uniquement + - **readability_only**: Lisibilité uniquement + +3. Configurer intensité (0.1-2.0) +4. Sauvegarder et exécuter + +### **3. Via PipelineExecutor** + +```javascript +const pipeline = { + name: "SmartTouch Test", + pipeline: [ + { step: 1, module: 'generation', mode: 'simple', intensity: 1.0 }, + { step: 2, module: 'smarttouch', mode: 'full', intensity: 1.0 } + ] +}; + +const executor = new PipelineExecutor(); +const result = await executor.execute(pipeline, rowNumber); +``` + +--- + +## 📊 **Modes Disponibles** + +| Mode | Description | Couches appliquées | Cas d'usage | +|------|-------------|-------------------|-------------| +| **full** | Analyse + toutes améliorations | Technical + Style + Readability | Production (recommandé) | +| **analysis_only** | Analyse sans modification | Aucune | Debugging, audit qualité | +| **technical_only** | Améliorations techniques ciblées | Technical | Contenu trop générique | +| **style_only** | Améliorations style ciblées | Style | Adapter personnalité | +| **readability_only** | Améliorations lisibilité ciblées | Readability | Phrases complexes | + +--- + +## 🎨 **Exemples Génériques (Multi-Secteurs)** + +### **E-commerce Mode** +``` +❌ AVANT: "Produit de qualité aux dimensions optimales" +✅ APRÈS: "Dimensions: 30x20cm, épaisseur 3mm - qualité premium" +``` + +### **SaaS/Tech** +``` +❌ AVANT: "Notre plateforme innovante optimise vos processus" +✅ APRÈS: "Automatisez vos workflows en 3 clics - compatible 95% des systèmes" +``` + +### **Services Professionnels** +``` +❌ AVANT: "Nos solutions de qualité" +✅ APRÈS: "Expertise comptable garantissant votre conformité fiscale" +``` + +### **Contenu Informatif** +``` +❌ AVANT: "Le réchauffement climatique est un problème important" +✅ APRÈS: "Le réchauffement climatique atteint +1.2°C depuis 1850" +``` + +--- + +## 🔄 **Comparaison Selective vs SmartTouch** + +| Critère | Selective (ancien) | SmartTouch (nouveau) | +|---------|-------------------|----------------------| +| **Contrôle** | ❌ Faible | ✅ Fort (dictature exacte) | +| **Prévisibilité** | ❌ Aléatoire | ✅ Déterministe | +| **Généricité** | ❌ Spécifique signalétique | ✅ Multi-secteurs | +| **Debugging** | ❌ Boîte noire | ✅ Analyse JSON visible | +| **Coûts tokens** | ⚠️ Moyen | ✅ Optimisé | +| **Qualité** | ⚠️ Variable | ✅ Consistante | + +--- + +## 📈 **Statistiques & Logs** + +SmartTouch retourne des stats détaillées : + +```javascript +{ + "mode": "full", + "analysisResults": { /* JSON analyses par élément */ }, + "layersApplied": [ + { "name": "technical", "modifications": 5, "duration": 2300 }, + { "name": "style", "modifications": 3, "duration": 1800 }, + { "name": "readability", "modifications": 2, "duration": 1500 } + ], + "totalModifications": 10, + "elementsProcessed": 12, + "elementsImproved": 8, + "duration": 5600 +} +``` + +**Logs structurés** : +``` +🔍 SMART ANALYSIS BATCH: 12 éléments + ✅ [Titre_H1]: 2 améliorations + ✅ [Paragraphe_1]: 3 améliorations + 📊 Score moyen: 0.62 | Améliorations totales: 18 + +🔧 === PHASE 2: AMÉLIORATIONS CIBLÉES === + 🎯 Couche: technical + ✅ 5 modifications appliquées (2300ms) + 🎯 Couche: style + ⏭️ Skip (score: 0.85 - aucune amélioration nécessaire) +``` + +--- + +## 🧪 **Testing** + +### **Test unitaire** +```javascript +const { SmartAnalysisLayer } = require('./SmartAnalysisLayer'); + +const analyzer = new SmartAnalysisLayer(); +const analysis = await analyzer.analyzeElement("Contenu test...", {}); + +expect(analysis.overallScore).toBeGreaterThan(0); +expect(analysis.improvements).toBeInstanceOf(Array); +``` + +### **Test intégration** +```bash +# Via Pipeline Builder UI +npm start +# → http://localhost:3000/pipeline-builder.html +# → Glisser "SmartTouch" + configurer + tester +``` + +--- + +## 📝 **Migration depuis Selective** + +```javascript +// ANCIEN (Selective Enhancement) +const result = await applySelectiveStack(content, 'fullEnhancement', config); + +// NOUVEAU (SmartTouch) +const smartTouch = new SmartTouchCore(); +const result = await smartTouch.apply(content, { + mode: 'full', + intensity: config.intensity, + csvData: config.csvData +}); +``` + +**Backward compatible** : Selective Enhancement reste disponible, SmartTouch est un complément. + +--- + +## 🛠️ **Configuration Avancée** + +### **Ordre des couches personnalisé** +```javascript +const result = await smartTouch.apply(content, { + mode: 'full', + layersOrder: ['style', 'technical', 'readability'] // Style en premier +}); +``` + +### **Skip analyse (mode legacy)** +```javascript +const result = await smartTouch.apply(content, { + mode: 'technical_only', + skipAnalysis: true // Applique directement (moins précis) +}); +``` + +--- + +## 🚦 **Statut du Module** + +- ✅ **Core modules créés** (Analysis, Technical, Style, Readability, Core) +- ✅ **Intégration PipelineExecutor** (module `smarttouch` reconnu) +- ✅ **Intégration Pipeline Builder** (drag-and-drop UI) +- ✅ **Intégration API** (PipelineDefinition, modes, durées) +- ⏳ **Tests production** (en cours) +- ⏳ **Documentation utilisateur** (à compléter) + +--- + +## 🎓 **Philosophie de Design** + +1. **Analyse avant action** : Comprendre avant de modifier +2. **Ciblage précis** : Améliorer UNIQUEMENT ce qui est nécessaire +3. **Généricité maximale** : Fonctionne pour tout type de contenu +4. **Logs transparents** : JSON structuré pour debugging +5. **Optimisation coûts** : Analyse légère, améliorations ciblées + +--- + +## 🔗 **Voir aussi** + +- `lib/selective-enhancement/` - Architecture legacy (toujours disponible) +- `lib/pipeline/PipelineExecutor.js` - Orchestrateur principal +- `lib/pipeline/PipelineDefinition.js` - Définition modules +- `public/pipeline-builder.html` - Interface UI + +--- + +**Auteur**: Architecture SmartTouch - Nouvelle Génération SEO Generator +**Date**: 2025-01-13 +**Version**: 1.0.0 diff --git a/lib/selective-smart-touch/SmartAnalysisLayer.js b/lib/selective-smart-touch/SmartAnalysisLayer.js new file mode 100644 index 0000000..017d85b --- /dev/null +++ b/lib/selective-smart-touch/SmartAnalysisLayer.js @@ -0,0 +1,479 @@ +// ======================================== +// SMART ANALYSIS LAYER - Analyse intelligente avant amélioration +// Responsabilité: Analyser contenu et identifier améliorations précises nécessaires +// LLM: GPT-4o-mini (objectivité, température basse) +// Architecture: Phase 1 de SelectiveSmartTouch (Analyse → Amélioration ciblée) +// ======================================== + +const { callLLM } = require('../LLMManager'); +const { logSh } = require('../ErrorReporting'); +const { tracer } = require('../trace'); + +/** + * SMART ANALYSIS LAYER + * Analyse objective du contenu pour identifier améliorations précises + */ +class SmartAnalysisLayer { + constructor() { + this.name = 'SmartAnalysis'; + this.defaultLLM = 'gpt-4o-mini'; + } + + /** + * ANALYSE COMPLÈTE D'UN ÉLÉMENT + * @param {string} content - Contenu à analyser + * @param {object} context - Contexte (mc0, personality, llmProvider, etc.) + * @returns {object} - Analyse JSON structurée + */ + async analyzeElement(content, context = {}) { + return await tracer.run('SmartAnalysis.analyzeElement()', async () => { + const { mc0, personality, llmProvider } = context; + + // ✅ Utiliser LLM fourni dans context, sinon fallback sur defaultLLM + const llmToUse = llmProvider || this.defaultLLM; + + await tracer.annotate({ + smartAnalysis: true, + contentLength: content.length, + hasMc0: !!mc0, + hasPersonality: !!personality, + llmProvider: llmToUse + }); + + const startTime = Date.now(); + logSh(`🔍 SMART ANALYSIS: Analyse d'un élément (${content.length} chars) avec ${llmToUse}`, 'DEBUG'); + + try { + const prompt = this.createAnalysisPrompt(content, context); + + const response = await callLLM(llmToUse, prompt, { + temperature: 0.2, // Basse température = objectivité + maxTokens: 1500 + }); + + // Parser JSON de l'analyse + const analysis = this.parseAnalysisResponse(response); + + const duration = Date.now() - startTime; + logSh(`✅ Analyse terminée: ${analysis.improvements.length} améliorations identifiées (${duration}ms)`, 'DEBUG'); + + await tracer.event('Smart Analysis terminée', { + duration, + improvementsCount: analysis.improvements.length, + needsImprovement: analysis.overallScore < 0.7 + }); + + return analysis; + + } catch (error) { + const duration = Date.now() - startTime; + logSh(`❌ SMART ANALYSIS ÉCHOUÉE (${duration}ms): ${error.message}`, 'ERROR'); + + // Fallback: analyse basique algorithmique + return this.fallbackAnalysis(content, context); + } + }, { contentLength: content.length, context }); + } + + /** + * ANALYSE BATCH DE PLUSIEURS ÉLÉMENTS + */ + async analyzeBatch(contentMap, context = {}) { + return await tracer.run('SmartAnalysis.analyzeBatch()', async () => { + const startTime = Date.now(); + const results = {}; + + logSh(`🔍 SMART ANALYSIS BATCH: ${Object.keys(contentMap).length} éléments`, 'INFO'); + + for (const [tag, content] of Object.entries(contentMap)) { + try { + results[tag] = await this.analyzeElement(content, context); + logSh(` ✅ [${tag}]: ${results[tag].improvements.length} améliorations`, 'DEBUG'); + } catch (error) { + logSh(` ❌ [${tag}]: Analyse échouée - ${error.message}`, 'ERROR'); + results[tag] = this.fallbackAnalysis(content, context); + } + + // Petit délai pour éviter rate limiting + await new Promise(resolve => setTimeout(resolve, 500)); + } + + const duration = Date.now() - startTime; + logSh(`✅ SMART ANALYSIS BATCH terminé: ${Object.keys(results).length} éléments analysés (${duration}ms)`, 'INFO'); + + return results; + }, { elementsCount: Object.keys(contentMap).length }); + } + + /** + * DÉTECTION CONTEXTUELLE (B2C vs B2B, niveau technique) + * Permet d'adapter les améliorations au public cible + */ + detectContentContext(content, personality = null) { + logSh('🔍 Détection contexte contenu...', 'DEBUG'); + + const context = { + audience: 'unknown', // B2C, B2B, mixed + techLevel: 'medium', // low, medium, high, too_high + contentType: 'unknown', // ecommerce, service, informative, technical + sector: 'general' + }; + + // === DÉTECTION AUDIENCE === + const b2cSignals = [ + 'acheter', 'votre maison', 'chez vous', 'personnalisé', 'facile', + 'simple', 'idéal pour vous', 'particulier', 'famille', 'clients' + ]; + const b2bSignals = [ + 'entreprise', 'professionnel', 'solution industrielle', + 'cahier des charges', 'conformité', 'optimisation des processus' + ]; + + const b2cCount = b2cSignals.filter(s => content.toLowerCase().includes(s)).length; + const b2bCount = b2bSignals.filter(s => content.toLowerCase().includes(s)).length; + + if (b2cCount > b2bCount && b2cCount > 2) context.audience = 'B2C'; + else if (b2bCount > b2cCount && b2bCount > 2) context.audience = 'B2B'; + else if (b2cCount > 0 && b2bCount > 0) context.audience = 'mixed'; + + // === DÉTECTION NIVEAU TECHNIQUE === + const jargonWords = [ + 'norme', 'coefficient', 'résistance', 'ISO', 'ASTM', 'certifié', + 'EN ', 'DIN', 'conforme', 'spécification', 'standard', 'référence' + ]; + const technicalSpecs = (content.match(/\d+\s*(mm|cm|kg|°C|%|watt|lumen|J\/cm²|K⁻¹)/g) || []).length; + const jargonCount = jargonWords.filter(w => content.includes(w)).length; + + if (jargonCount > 5 || technicalSpecs > 8) { + context.techLevel = 'too_high'; + } else if (jargonCount > 2 || technicalSpecs > 4) { + context.techLevel = 'high'; + } else if (jargonCount === 0 && technicalSpecs < 2) { + context.techLevel = 'low'; + } + + // === DÉTECTION TYPE CONTENU === + const ecommerceKeywords = ['prix', 'acheter', 'commander', 'livraison', 'stock', 'produit']; + const serviceKeywords = ['prestation', 'accompagnement', 'conseil', 'expertise', 'service']; + + if (ecommerceKeywords.filter(k => content.toLowerCase().includes(k)).length > 2) { + context.contentType = 'ecommerce'; + } else if (serviceKeywords.filter(k => content.toLowerCase().includes(k)).length > 2) { + context.contentType = 'service'; + } else if (jargonCount > 3) { + context.contentType = 'technical'; + } else { + context.contentType = 'informative'; + } + + // === INTÉGRATION DONNÉES PERSONALITY === + if (personality) { + if (personality.targetAudience?.toLowerCase().includes('grand public')) { + context.audience = 'B2C'; + } + if (personality.secteur) { + context.sector = personality.secteur; + } + if (personality.tone?.includes('accessible') || personality.tone?.includes('simple')) { + context.preferSimple = true; + } + } + + logSh(`✅ Contexte détecté: audience=${context.audience}, techLevel=${context.techLevel}, type=${context.contentType}`, 'DEBUG'); + + return context; + } + + /** + * ANALYSE PAR SEGMENTS (découpe + score individuel) + * Permet de sélectionner seulement les segments les plus faibles + */ + analyzeBySegments(content, context = {}) { + logSh('🔍 Analyse par segments...', 'DEBUG'); + + // Découper en phrases (simpliste mais efficace) + const sentences = content.split(/(?<=[.!?])\s+/).filter(s => s.trim().length > 10); + + const segments = sentences.map((sentence, index) => { + // Score algorithmique rapide de chaque phrase + const wordCount = sentence.split(/\s+/).length; + const hasNumbers = /\d+/.test(sentence); + const genericWords = ['nos solutions', 'notre expertise', 'qualité', 'service'].filter(w => sentence.toLowerCase().includes(w)).length; + const jargonWords = ['norme', 'coefficient', 'ISO', 'certifié'].filter(w => sentence.includes(w)).length; + + let score = 0.5; // Score de base + + // Pénalités + if (wordCount > 30) score -= 0.2; // Trop longue + if (genericWords > 0) score -= 0.15 * genericWords; // Vocabulaire générique + if (jargonWords > 1) score -= 0.1 * jargonWords; // Trop de jargon + + // Bonus + if (hasNumbers && wordCount < 20) score += 0.1; // Concise avec données + + score = Math.max(0.0, Math.min(1.0, score)); // Clamp entre 0 et 1 + + return { + index, + content: sentence, + score, + wordCount, + issues: [ + wordCount > 30 ? 'trop_longue' : null, + genericWords > 0 ? 'vocabulaire_générique' : null, + jargonWords > 1 ? 'trop_technique' : null + ].filter(Boolean) + }; + }); + + logSh(`✅ ${segments.length} segments analysés`, 'DEBUG'); + + return segments; + } + + /** + * SÉLECTION DES X% SEGMENTS LES PLUS FAIBLES + * Retourne indices des segments à améliorer + */ + selectWeakestSegments(segments, percentage = 0.1) { + // Trier par score croissant (plus faibles d'abord) + const sortedSegments = [...segments].sort((a, b) => a.score - b.score); + + // Calculer combien de segments à prendre + const countToSelect = Math.max(1, Math.ceil(segments.length * percentage)); + + // Prendre les N segments les plus faibles + const selectedSegments = sortedSegments.slice(0, countToSelect); + + logSh(`📊 Sélection: ${selectedSegments.length}/${segments.length} segments les plus faibles (${(percentage * 100).toFixed(0)}%)`, 'INFO'); + + // Retourner dans l'ordre original (par index) + return selectedSegments.sort((a, b) => a.index - b.index); + } + + /** + * CRÉER PROMPT D'ANALYSE (générique, multi-secteur) + */ + createAnalysisPrompt(content, context) { + const { mc0, personality } = context; + + return `MISSION: Analyse OBJECTIVE de ce contenu et identifie les améliorations précises nécessaires. + +CONTENU À ANALYSER: +"${content}" + +${mc0 ? `CONTEXTE SUJET: ${mc0}` : 'CONTEXTE: Générique'} +${personality ? `PERSONNALITÉ CIBLE: ${personality.nom} (${personality.style})` : ''} + +ANALYSE CES DIMENSIONS: + +1. DIMENSION TECHNIQUE: + - Manque-t-il des informations factuelles concrètes ? + - Le contenu est-il trop générique ou vague ? + - Y a-t-il besoin de données chiffrées, dimensions, spécifications ? + +2. DIMENSION STYLE: + - Le ton est-il cohérent ? + ${personality ? `- Le style correspond-il à "${personality.style}" ?` : '- Le style est-il professionnel ?'} + - Y a-t-il des expressions trop génériques ("nos solutions", "notre expertise") ? + +3. DIMENSION LISIBILITÉ: + - Les phrases sont-elles trop longues ou complexes ? + - Les connecteurs sont-ils répétitifs ? + - La structure est-elle fluide ? + +4. DIMENSION VOCABULAIRE: + - Mots génériques à remplacer par termes spécifiques ? + - Vocabulaire adapté au sujet ? + +IMPORTANT: Sois OBJECTIF et SÉLECTIF. Ne liste QUE les améliorations réellement nécessaires. +Si le contenu est déjà bon sur une dimension, indique "needed: false" pour cette dimension. + +RETOURNE UN JSON (et UNIQUEMENT du JSON valide): +{ + "technical": { + "needed": true/false, + "score": 0.0-1.0, + "missing": ["élément précis manquant 1", "élément précis manquant 2"], + "issues": ["problème identifié"] + }, + "style": { + "needed": true/false, + "score": 0.0-1.0, + "toneIssues": ["problème de ton"], + "genericPhrases": ["expression générique à personnaliser"] + }, + "readability": { + "needed": true/false, + "score": 0.0-1.0, + "complexSentences": [numéro_ligne], + "repetitiveConnectors": ["connecteur répété"] + }, + "vocabulary": { + "needed": true/false, + "score": 0.0-1.0, + "genericWords": ["mot générique"], + "suggestions": ["terme spécifique suggéré"] + }, + "improvements": [ + "Amélioration précise 1", + "Amélioration précise 2" + ], + "overallScore": 0.0-1.0 +}`; + } + + /** + * PARSER RÉPONSE JSON DE L'ANALYSE + */ + parseAnalysisResponse(response) { + try { + // Nettoyer la réponse (supprimer markdown, etc.) + let cleanResponse = response.trim(); + + // Supprimer balises markdown JSON + cleanResponse = cleanResponse.replace(/```json\s*/gi, ''); + cleanResponse = cleanResponse.replace(/```\s*/g, ''); + + // Parser JSON + const analysis = JSON.parse(cleanResponse); + + // Valider structure + if (!analysis.technical || !analysis.style || !analysis.readability || !analysis.vocabulary) { + throw new Error('Structure JSON incomplète'); + } + + // Valider que improvements est un array + if (!Array.isArray(analysis.improvements)) { + analysis.improvements = []; + } + + return analysis; + + } catch (error) { + logSh(`⚠️ Parsing JSON échoué: ${error.message}, tentative de récupération...`, 'WARNING'); + + // Tentative d'extraction partielle + try { + const jsonMatch = response.match(/\{[\s\S]*\}/); + if (jsonMatch) { + return JSON.parse(jsonMatch[0]); + } + } catch (e) { + // Ignore + } + + throw new Error(`Impossible de parser l'analyse JSON: ${error.message}`); + } + } + + /** + * ANALYSE FALLBACK ALGORITHMIQUE (si LLM échoue) + */ + fallbackAnalysis(content, context) { + logSh(`🔄 Fallback: Analyse algorithmique de base`, 'DEBUG'); + + const wordCount = content.split(/\s+/).length; + const sentenceCount = content.split(/[.!?]+/).filter(s => s.trim().length > 5).length; + const avgSentenceLength = wordCount / Math.max(sentenceCount, 1); + + // Analyse basique + const genericWords = ['nos solutions', 'notre expertise', 'qualité', 'service', 'professionnel']; + const foundGeneric = genericWords.filter(word => content.toLowerCase().includes(word)); + + const improvements = []; + let technicalScore = 0.5; + let styleScore = 0.5; + let readabilityScore = 0.5; + + // Détection phrases trop longues + if (avgSentenceLength > 25) { + improvements.push('Raccourcir les phrases trop longues (> 25 mots)'); + readabilityScore = 0.4; + } + + // Détection vocabulaire générique + if (foundGeneric.length > 2) { + improvements.push(`Remplacer expressions génériques: ${foundGeneric.join(', ')}`); + styleScore = 0.4; + } + + // Détection manque de données concrètes + const hasNumbers = /\d+/.test(content); + if (!hasNumbers && wordCount > 50) { + improvements.push('Ajouter données concrètes (chiffres, dimensions, pourcentages)'); + technicalScore = 0.4; + } + + return { + technical: { + needed: technicalScore < 0.6, + score: technicalScore, + missing: hasNumbers ? [] : ['données chiffrées'], + issues: [] + }, + style: { + needed: styleScore < 0.6, + score: styleScore, + toneIssues: [], + genericPhrases: foundGeneric + }, + readability: { + needed: readabilityScore < 0.6, + score: readabilityScore, + complexSentences: avgSentenceLength > 25 ? [1] : [], + repetitiveConnectors: [] + }, + vocabulary: { + needed: foundGeneric.length > 0, + score: foundGeneric.length > 2 ? 0.3 : 0.6, + genericWords: foundGeneric, + suggestions: [] + }, + improvements, + overallScore: (technicalScore + styleScore + readabilityScore) / 3, + fallbackUsed: true + }; + } + + /** + * RÉSUMER ANALYSES BATCH + */ + summarizeBatchAnalysis(analysisResults) { + const summary = { + totalElements: Object.keys(analysisResults).length, + needsImprovement: 0, + averageScore: 0, + commonIssues: { + technical: 0, + style: 0, + readability: 0, + vocabulary: 0 + }, + totalImprovements: 0 + }; + + let totalScore = 0; + + Object.values(analysisResults).forEach(analysis => { + totalScore += analysis.overallScore; + + if (analysis.overallScore < 0.7) { + summary.needsImprovement++; + } + + if (analysis.technical.needed) summary.commonIssues.technical++; + if (analysis.style.needed) summary.commonIssues.style++; + if (analysis.readability.needed) summary.commonIssues.readability++; + if (analysis.vocabulary.needed) summary.commonIssues.vocabulary++; + + summary.totalImprovements += analysis.improvements.length; + }); + + summary.averageScore = totalScore / summary.totalElements; + + return summary; + } +} + +module.exports = { SmartAnalysisLayer }; diff --git a/lib/selective-smart-touch/SmartReadabilityLayer.js b/lib/selective-smart-touch/SmartReadabilityLayer.js new file mode 100644 index 0000000..0ecb0e1 --- /dev/null +++ b/lib/selective-smart-touch/SmartReadabilityLayer.js @@ -0,0 +1,210 @@ +// ======================================== +// SMART READABILITY LAYER - Améliorations lisibilité CIBLÉES +// Responsabilité: Appliquer UNIQUEMENT les améliorations lisibilité identifiées par analyse +// LLM: GPT-4o-mini (clarté et structure) +// Architecture: Phase 2 de SelectiveSmartTouch (post-analyse) +// ======================================== + +const { callLLM } = require('../LLMManager'); +const { logSh } = require('../ErrorReporting'); +const { tracer } = require('../trace'); + +/** + * SMART READABILITY LAYER + * Applique améliorations lisibilité précises identifiées par SmartAnalysisLayer + */ +class SmartReadabilityLayer { + constructor() { + this.name = 'SmartReadability'; + this.defaultLLM = 'gpt-4o-mini'; + } + + /** + * APPLIQUER AMÉLIORATIONS LISIBILITÉ CIBLÉES + */ + async applyTargeted(content, analysis, context = {}) { + return await tracer.run('SmartReadability.applyTargeted()', async () => { + const { mc0, personality, intensity = 1.0 } = context; + + // Si aucune amélioration lisibilité nécessaire, skip + if (!analysis.readability.needed) { + logSh(`⏭️ SMART READABILITY: Aucune amélioration nécessaire (score: ${analysis.readability.score.toFixed(2)})`, 'DEBUG'); + return { + content, + modifications: 0, + skipped: true, + reason: 'No readability improvements needed' + }; + } + + await tracer.annotate({ + smartReadability: true, + contentLength: content.length, + intensity + }); + + // ✅ Utiliser LLM fourni dans context, sinon fallback sur defaultLLM + const llmToUse = context.llmProvider || this.defaultLLM; + + const startTime = Date.now(); + logSh(`📖 SMART READABILITY: Application améliorations lisibilité ciblées avec ${llmToUse}`, 'DEBUG'); + + try { + const prompt = this.createTargetedPrompt(content, analysis, context); + + const response = await callLLM(llmToUse, prompt, { + temperature: 0.4, // Précision pour structure + maxTokens: 2500 + }, personality); + + const improvedContent = this.cleanResponse(response); + const modifications = this.countModifications(content, improvedContent); + + const duration = Date.now() - startTime; + logSh(`✅ SMART READABILITY terminé: ${modifications} modifications appliquées (${duration}ms)`, 'DEBUG'); + + await tracer.event('Smart Readability appliqué', { + duration, + modifications + }); + + return { + content: improvedContent, + modifications, + duration + }; + + } catch (error) { + const duration = Date.now() - startTime; + logSh(`❌ SMART READABILITY É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 } = context; + + // Extraire améliorations lisibilité + const readabilityImprovements = analysis.improvements.filter(imp => + imp.toLowerCase().includes('lisib') || + imp.toLowerCase().includes('phrase') || + imp.toLowerCase().includes('connecteur') || + imp.toLowerCase().includes('structure') || + imp.toLowerCase().includes('fluidité') + ); + + return `MISSION: Améliore UNIQUEMENT la lisibilité selon les points listés. + +CONTENU ORIGINAL: +"${content}" + +${mc0 ? `CONTEXTE: ${mc0}` : ''} +INTENSITÉ: ${intensity.toFixed(1)} + +AMÉLIORATIONS LISIBILITÉ À APPLIQUER: +${readabilityImprovements.map((imp, i) => `${i + 1}. ${imp}`).join('\n')} + +${analysis.readability.complexSentences && analysis.readability.complexSentences.length > 0 ? ` +PHRASES TROP COMPLEXES (à simplifier): +${analysis.readability.complexSentences.map(line => `- Ligne ${line}`).join('\n')} +` : ''} + +${analysis.readability.repetitiveConnectors && analysis.readability.repetitiveConnectors.length > 0 ? ` +CONNECTEURS RÉPÉTITIFS (à varier): +${analysis.readability.repetitiveConnectors.map(conn => `- "${conn}"`).join('\n')} +` : ''} + +CONSIGNES STRICTES: +- Applique UNIQUEMENT les améliorations lisibilité listées +- NE CHANGE PAS le sens, ton ou style général +- GARDE le même contenu informatif +- Phrases: 15-25 mots idéalement (simplifier si > 30 mots) +- Connecteurs: variés et naturels +- Structure: fluide et logique + +TECHNIQUES LISIBILITÉ: + +**Simplifier phrases longues:** +- ✅ AVANT: "Ce produit, qui a été conçu par nos experts après plusieurs années de recherche, permet d'obtenir des résultats exceptionnels." +- ✅ APRÈS: "Ce produit a été conçu par nos experts après plusieurs années de recherche. Il permet d'obtenir des résultats exceptionnels." + +**Varier connecteurs:** +- ✅ AVANT: "Par ailleurs... Par ailleurs... Par ailleurs..." +- ✅ APRÈS: "Par ailleurs... De plus... En outre..." + +**Fluidifier structure:** +- ✅ AVANT: "Produit X. Produit Y. Produit Z." (juxtaposition sèche) +- ✅ APRÈS: "Produit X offre... Quant au produit Y, il propose... Enfin, produit Z permet..." + +**Clarifier relations:** +- ✅ AVANT: "Ce service existe. Il est pratique." (lien flou) +- ✅ APRÈS: "Ce service existe. Grâce à lui, vous gagnez du temps." (lien clair) + +RÈGLES LISIBILITÉ: +- Privilégie clarté immédiate +- Évite subordonnées multiples imbriquées +- Structure logique: contexte → explication → bénéfice +- Connecteurs variés: évite répétitions sur 3 phrases consécutives + +FORMAT RÉPONSE: +Retourne UNIQUEMENT le contenu fluidifié, 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 FLUIDIFIÉ:\s*/gi, ''); + cleaned = cleaned.replace(/^(voici\s+)?le\s+contenu\s+(fluidifié|lisible)\s*[:.]?\s*/gi, ''); + cleaned = cleaned.replace(/^(avec\s+)?amélioration[s]?\s+lisibilité\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; + } +} + +module.exports = { SmartReadabilityLayer }; diff --git a/lib/selective-smart-touch/SmartStyleLayer.js b/lib/selective-smart-touch/SmartStyleLayer.js new file mode 100644 index 0000000..0d32f76 --- /dev/null +++ b/lib/selective-smart-touch/SmartStyleLayer.js @@ -0,0 +1,215 @@ +// ======================================== +// 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' + }; + } + + await tracer.annotate({ + smartStyle: true, + contentLength: content.length, + hasPersonality: !!personality, + intensity + }); + + // ✅ 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 } = 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') + ); + + 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)} + +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; + } +} + +module.exports = { SmartStyleLayer }; diff --git a/lib/selective-smart-touch/SmartTechnicalLayer.js b/lib/selective-smart-touch/SmartTechnicalLayer.js new file mode 100644 index 0000000..8db9409 --- /dev/null +++ b/lib/selective-smart-touch/SmartTechnicalLayer.js @@ -0,0 +1,278 @@ +// ======================================== +// SMART TECHNICAL LAYER - Améliorations techniques CIBLÉES +// Responsabilité: Appliquer UNIQUEMENT les améliorations techniques identifiées par analyse +// LLM: GPT-4o-mini (précision technique) +// Architecture: Phase 2 de SelectiveSmartTouch (post-analyse) +// ======================================== + +const { callLLM } = require('../LLMManager'); +const { logSh } = require('../ErrorReporting'); +const { tracer } = require('../trace'); + +/** + * SMART TECHNICAL LAYER + * Applique améliorations techniques précises identifiées par SmartAnalysisLayer + */ +class SmartTechnicalLayer { + constructor() { + this.name = 'SmartTechnical'; + this.defaultLLM = 'gpt-4o-mini'; + } + + /** + * APPLIQUER AMÉLIORATIONS TECHNIQUES CIBLÉES + * @param {string} content - Contenu original + * @param {object} analysis - Analyse de SmartAnalysisLayer + * @param {object} context - Contexte (mc0, personality, intensity) + * @returns {object} - { content, modifications } + */ + async applyTargeted(content, analysis, context = {}) { + return await tracer.run('SmartTechnical.applyTargeted()', async () => { + const { mc0, personality, intensity = 1.0, contentContext } = context; + + // Si aucune amélioration technique nécessaire, skip + if (!analysis.technical.needed || analysis.improvements.length === 0) { + logSh(`⏭️ SMART TECHNICAL: Aucune amélioration nécessaire (score: ${analysis.technical.score.toFixed(2)})`, 'DEBUG'); + return { + content, + modifications: 0, + skipped: true, + reason: 'No technical improvements needed' + }; + } + + // === GARDE-FOU 1: Détection contenu déjà trop technique === + if (contentContext?.techLevel === 'too_high') { + logSh(`🛡️ GARDE-FOU: Contenu déjà trop technique (level: ${contentContext.techLevel}), SKIP amélioration`, 'WARN'); + return { + content, + modifications: 0, + skipped: true, + reason: 'Content already too technical - avoided over-engineering' + }; + } + + // === GARDE-FOU 2: Comptage specs techniques existantes === + const existingSpecs = (content.match(/\d+\s*(mm|cm|kg|°C|%|watt|lumen|J\/cm²|K⁻¹)/g) || []).length; + const existingNorms = (content.match(/(ISO|ASTM|EN\s|DIN|norme)/gi) || []).length; + + if (existingSpecs > 6 || existingNorms > 3) { + logSh(`🛡️ GARDE-FOU: Trop de specs existantes (${existingSpecs} specs, ${existingNorms} normes), SKIP pour éviter surcharge`, 'WARN'); + return { + content, + modifications: 0, + skipped: true, + reason: `Specs overload (${existingSpecs} specs, ${existingNorms} norms) - avoided adding more` + }; + } + + // === GARDE-FOU 3: Si B2C + niveau high, limiter portée === + if (contentContext?.audience === 'B2C' && contentContext.techLevel === 'high') { + logSh(`🛡️ GARDE-FOU: B2C + niveau technique déjà high, limitation de la portée`, 'INFO'); + // Réduire nombre d'améliorations à appliquer + analysis.improvements = analysis.improvements.slice(0, 2); // Max 2 améliorations + } + + await tracer.annotate({ + smartTechnical: true, + contentLength: content.length, + improvementsCount: analysis.improvements.length, + intensity, + guardrailsApplied: true, + existingSpecs, + existingNorms + }); + + // ✅ Utiliser LLM fourni dans context, sinon fallback sur defaultLLM + const llmToUse = context.llmProvider || this.defaultLLM; + + const startTime = Date.now(); + logSh(`🔧 SMART TECHNICAL: Application de ${analysis.improvements.length} améliorations ciblées avec ${llmToUse}`, 'DEBUG'); + + try { + const prompt = this.createTargetedPrompt(content, analysis, context); + + const response = await callLLM(llmToUse, prompt, { + temperature: 0.4, // Précision technique + maxTokens: 2500 + }, personality); + + const improvedContent = this.cleanResponse(response); + + // Calculer nombre de modifications + const modifications = this.countModifications(content, improvedContent); + + const duration = Date.now() - startTime; + logSh(`✅ SMART TECHNICAL terminé: ${modifications} modifications appliquées (${duration}ms)`, 'DEBUG'); + + await tracer.event('Smart Technical appliqué', { + duration, + modifications, + improvementsRequested: analysis.improvements.length + }); + + return { + content: improvedContent, + modifications, + duration, + improvementsApplied: analysis.improvements + }; + + } catch (error) { + const duration = Date.now() - startTime; + logSh(`❌ SMART TECHNICAL ÉCHOUÉ (${duration}ms): ${error.message}`, 'ERROR'); + + return { + content, // Fallback: contenu original + modifications: 0, + error: error.message, + fallback: true + }; + } + }, { contentLength: content.length, analysis }); + } + + /** + * CRÉER PROMPT CIBLÉ (instructions précises, exemples génériques) + */ + createTargetedPrompt(content, analysis, context) { + const { mc0, personality, intensity = 1.0, contentContext } = context; + + // Extraire uniquement les améliorations techniques de la liste globale + const technicalImprovements = analysis.improvements.filter(imp => + imp.toLowerCase().includes('technique') || + imp.toLowerCase().includes('données') || + imp.toLowerCase().includes('chiffr') || + imp.toLowerCase().includes('précision') || + imp.toLowerCase().includes('spécif') || + analysis.technical.missing.some(missing => imp.includes(missing)) + ); + + // === ADAPTER PROMPT SELON CONTEXTE (B2C vs B2B) === + const isB2C = contentContext?.audience === 'B2C'; + const isTechnicalContent = contentContext?.contentType === 'technical'; + + let technicalGuidelines = ''; + if (isB2C && !isTechnicalContent) { + technicalGuidelines = ` +⚠️ CONTEXTE: Contenu B2C grand public - SIMPLICITÉ MAXIMALE +- Ajoute UNIQUEMENT 2-3 spécifications SIMPLES et UTILES pour un client +- ÉVITE absolument: normes ISO/ASTM/EN, coefficients techniques, jargon industriel +- PRIVILÉGIE: dimensions pratiques, matériaux compréhensibles, bénéfices concrets +- INTERDICTION: termes comme "coefficient", "résistance à la corrosion", "norme", "conformité" +- MAX 1-2 données chiffrées pertinentes (ex: taille, poids, durée)`; + } else if (isTechnicalContent) { + technicalGuidelines = ` +📋 CONTEXTE: Contenu technique - Précision acceptable +- Ajoute spécifications techniques précises si nécessaire +- Normes et standards acceptables si pertinents +- Garde équilibre entre précision et lisibilité`; + } else { + technicalGuidelines = ` +🎯 CONTEXTE: Contenu standard - Équilibre +- Ajoute 2-4 spécifications pertinentes +- Évite jargon technique excessif +- Privilégie clarté et accessibilité`; + } + + return `MISSION: Améliore UNIQUEMENT les aspects techniques PRÉCIS listés ci-dessous. + +CONTENU ORIGINAL: +"${content}" + +${mc0 ? `CONTEXTE SUJET: ${mc0}` : ''} +${personality ? `PERSONNALITÉ: ${personality.nom} (${personality.style})` : ''} +INTENSITÉ: ${intensity.toFixed(1)} (0.5=léger, 1.0=standard, 1.5=intensif) +${technicalGuidelines} + +AMÉLIORATIONS TECHNIQUES À APPLIQUER: +${technicalImprovements.map((imp, i) => `${i + 1}. ${imp}`).join('\n')} + +${analysis.technical.missing.length > 0 ? ` +ÉLÉMENTS MANQUANTS IDENTIFIÉS: +${analysis.technical.missing.map((item, i) => `- ${item}`).join('\n')} +` : ''} + +CONSIGNES STRICTES: +- Applique UNIQUEMENT les améliorations listées ci-dessus +- NE CHANGE PAS le ton, style ou structure générale +- NE TOUCHE PAS aux aspects non mentionnés +- Garde la même longueur approximative (±20%) +- Intègre les éléments manquants de façon NATURELLE +- ${isB2C ? 'PRIORITÉ ABSOLUE: Reste SIMPLE et ACCESSIBLE' : 'Reste ACCESSIBLE - pas de jargon excessif'} + +EXEMPLES D'AMÉLIORATION TECHNIQUE (génériques): +${isB2C ? ` +- ✅ BON (B2C): "Dimensions: 30x20cm, épaisseur 3mm" → clair et utile +- ❌ MAUVAIS (B2C): "Dimensions: 30x20cm, épaisseur 3mm, résistance 1,5 J/cm² (norme EN 12354-2)" → trop technique +- ✅ BON (B2C): "Délai de livraison: 3-5 jours" → simple +- ❌ MAUVAIS (B2C): "Conformité ISO 9001, délai d'expédition optimisé selon norme" → jargon inutile` : ` +- ✅ BON: "Dimensions: 30x20cm, épaisseur 3mm" → données concrètes +- ❌ MAUVAIS: "Produit de qualité aux dimensions optimales" → vague +- ✅ BON: "Délai de livraison: 3-5 jours ouvrés" → précis +- ❌ MAUVAIS: "Livraison rapide" → imprécis`} +- ✅ BON: "Compatible avec 95% des systèmes" → chiffre concret +- ❌ MAUVAIS: "Très compatible" → vague + +RÈGLES VOCABULAIRE TECHNIQUE: +- Privilégie clarté sur technicité excessive +- ${isB2C ? 'MAX 1-2 détails techniques SIMPLES' : '1-2 détails techniques pertinents par paragraphe MAX'} +- Évite le jargon pompeux inutile +- Vocabulaire accessible ${isB2C ? 'au GRAND PUBLIC' : 'à un public large'} + +FORMAT RÉPONSE: +Retourne UNIQUEMENT le contenu amélioré, SANS balises, SANS métadonnées, SANS explications.`; + } + + /** + * NETTOYER RÉPONSE LLM + */ + cleanResponse(response) { + if (!response) return response; + + let cleaned = response.trim(); + + // Supprimer balises et préfixes indésirables + 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 AMÉLIORÉ:\s*/gi, ''); + cleaned = cleaned.replace(/^(voici\s+)?le\s+contenu\s+amélioré\s*[:.]?\s*/gi, ''); + cleaned = cleaned.replace(/^(avec\s+)?amélioration[s]?\s+technique[s]?\s*[:.]?\s*/gi, ''); + + // Nettoyer formatage markdown + cleaned = cleaned.replace(/\*\*([^*]+)\*\*/g, '$1'); // **texte** → texte + cleaned = cleaned.replace(/\s{2,}/g, ' '); // Espaces multiples + cleaned = cleaned.trim(); + + return cleaned; + } + + /** + * COMPTER MODIFICATIONS (comparaison contenu original vs amélioré) + */ + countModifications(original, improved) { + if (original === improved) return 0; + + // Méthode simple: compter mots différents + const originalWords = original.toLowerCase().split(/\s+/); + const improvedWords = improved.toLowerCase().split(/\s+/); + + let differences = 0; + + // Compter ajouts/suppressions + differences += Math.abs(originalWords.length - improvedWords.length); + + // Compter modifications (mots communs) + const minLength = Math.min(originalWords.length, improvedWords.length); + for (let i = 0; i < minLength; i++) { + if (originalWords[i] !== improvedWords[i]) { + differences++; + } + } + + return differences; + } +} + +module.exports = { SmartTechnicalLayer }; diff --git a/lib/selective-smart-touch/SmartTouchCore.js b/lib/selective-smart-touch/SmartTouchCore.js new file mode 100644 index 0000000..93ec0c8 --- /dev/null +++ b/lib/selective-smart-touch/SmartTouchCore.js @@ -0,0 +1,379 @@ +// ======================================== +// SMART TOUCH CORE - Orchestrateur SelectiveSmartTouch +// Responsabilité: Orchestration complète Analyse → Améliorations ciblées +// Architecture: Analyse intelligente PUIS améliorations précises (contrôle total) +// ======================================== + +const { logSh } = require('../ErrorReporting'); +const { tracer } = require('../trace'); +const { SmartAnalysisLayer } = require('./SmartAnalysisLayer'); +const { SmartTechnicalLayer } = require('./SmartTechnicalLayer'); +const { SmartStyleLayer } = require('./SmartStyleLayer'); +const { SmartReadabilityLayer } = require('./SmartReadabilityLayer'); + +/** + * SMART TOUCH CORE + * Orchestrateur principal: Analyse → Technical → Style → Readability (ciblé) + */ +class SmartTouchCore { + constructor() { + this.name = 'SelectiveSmartTouch'; + + // Instancier les layers + this.analysisLayer = new SmartAnalysisLayer(); + this.technicalLayer = new SmartTechnicalLayer(); + this.styleLayer = new SmartStyleLayer(); + this.readabilityLayer = new SmartReadabilityLayer(); + } + + /** + * APPLIQUER SMART TOUCH COMPLET + * @param {object} content - Map {tag: texte} + * @param {object} config - Configuration + * @returns {object} - Résultat avec stats détaillées + */ + async apply(content, config = {}) { + return await tracer.run('SmartTouchCore.apply()', async () => { + const { + mode = 'full', // 'analysis_only', 'technical_only', 'style_only', 'readability_only', 'full' + intensity = 1.0, + csvData = null, + llmProvider = 'gpt-4o-mini', // ✅ LLM à utiliser (extrait du pipeline config) + skipAnalysis = false, // Si true, applique sans analyser (mode legacy) + layersOrder = ['technical', 'style', 'readability'] // Ordre d'application personnalisable + } = config; + + await tracer.annotate({ + selectiveSmartTouch: true, + mode, + intensity, + elementsCount: Object.keys(content).length, + personality: csvData?.personality?.nom + }); + + const startTime = Date.now(); + logSh(`🧠 SELECTIVE SMART TOUCH START: ${Object.keys(content).length} éléments | Mode: ${mode}`, 'INFO'); + + try { + let currentContent = { ...content }; + const stats = { + mode, + analysisResults: {}, + layersApplied: [], + totalModifications: 0, + elementsProcessed: Object.keys(content).length, + elementsImproved: 0, + duration: 0 + }; + + // ======================================== + // PHASE 1: ANALYSE INTELLIGENTE + // ======================================== + if (!skipAnalysis) { + logSh(`\n📊 === PHASE 1: ANALYSE INTELLIGENTE ===`, 'INFO'); + + const analysisResults = await this.analysisLayer.analyzeBatch(currentContent, { + mc0: csvData?.mc0, + personality: csvData?.personality, + llmProvider // ✅ Passer LLM à l'analyse batch + }); + + stats.analysisResults = analysisResults; + + // Résumer analyse + const summary = this.analysisLayer.summarizeBatchAnalysis(analysisResults); + logSh(` 📋 Résumé analyse: ${summary.needsImprovement}/${summary.totalElements} éléments nécessitent amélioration`, 'INFO'); + logSh(` 📊 Score moyen: ${summary.averageScore.toFixed(2)} | Améliorations totales: ${summary.totalImprovements}`, 'INFO'); + logSh(` 🎯 Besoins: Technical=${summary.commonIssues.technical} | Style=${summary.commonIssues.style} | Readability=${summary.commonIssues.readability}`, 'INFO'); + + // Si mode analysis_only, retourner ici + if (mode === 'analysis_only') { + const duration = Date.now() - startTime; + logSh(`✅ SELECTIVE SMART TOUCH (ANALYSIS ONLY) terminé: ${duration}ms`, 'INFO'); + + return { + content: currentContent, + stats: { + ...stats, + duration, + analysisOnly: true + }, + modifications: 0, + analysisResults + }; + } + + // ======================================== + // PHASE 2: AMÉLIORATIONS CIBLÉES + // ======================================== + logSh(`\n🔧 === PHASE 2: AMÉLIORATIONS CIBLÉES ===`, 'INFO'); + + // Déterminer quelles couches appliquer + const layersToApply = this.determineLayersToApply(mode, layersOrder); + + // === DÉTECTION CONTEXTE GLOBALE (1 seule fois) === + const contentContext = this.analysisLayer.detectContentContext( + Object.values(currentContent).join(' '), + csvData?.personality + ); + + for (const layerName of layersToApply) { + const layerStartTime = Date.now(); + logSh(`\n 🎯 Couche: ${layerName}`, 'INFO'); + + let layerModifications = 0; + const layerResults = {}; + + // Appliquer la couche sur chaque élément + for (const [tag, text] of Object.entries(currentContent)) { + const analysis = analysisResults[tag]; + if (!analysis) continue; + + try { + // === SYSTÈME 10% SEGMENTS === + // Calculer pourcentage de texte à améliorer selon intensity + // intensity 1.0 = 10%, 0.5 = 5%, 1.5 = 15% + const percentageToImprove = intensity * 0.1; + + // Analyser par segments pour identifier les plus faibles + const segments = this.analysisLayer.analyzeBySegments(text, { + mc0: csvData?.mc0, + personality: csvData?.personality + }); + + // Sélectionner les X% segments les plus faibles + const weakestSegments = this.analysisLayer.selectWeakestSegments( + segments, + percentageToImprove + ); + + logSh(` 📊 [${tag}] ${segments.length} segments, ${weakestSegments.length} sélectionnés (${(percentageToImprove * 100).toFixed(0)}%)`, 'DEBUG'); + + // Appliquer amélioration UNIQUEMENT sur segments sélectionnés + const result = await this.applyLayerToSegments( + layerName, + segments, + weakestSegments, + analysis, + { + mc0: csvData?.mc0, + personality: csvData?.personality, + intensity, + contentContext, // Passer contexte aux layers + llmProvider // ✅ Passer LLM choisi dans pipeline + } + ); + + if (!result.skipped && result.content !== text) { + currentContent[tag] = result.content; + layerModifications += result.modifications || 0; + stats.elementsImproved++; + } + + layerResults[tag] = result; + + } catch (error) { + logSh(` ❌ [${tag}] Échec ${layerName}: ${error.message}`, 'ERROR'); + } + } + + const layerDuration = Date.now() - layerStartTime; + + stats.layersApplied.push({ + name: layerName, + modifications: layerModifications, + duration: layerDuration + }); + + stats.totalModifications += layerModifications; + + logSh(` ✅ ${layerName} terminé: ${layerModifications} modifications (${layerDuration}ms)`, 'INFO'); + } + + } else { + // Mode skipAnalysis: appliquer sans analyse (legacy fallback) + logSh(`⚠️ Mode skipAnalysis activé: application directe sans analyse préalable`, 'WARNING'); + + // TODO: Implémenter mode legacy si nécessaire + logSh(`❌ Mode skipAnalysis non implémenté pour SmartTouch (requiert analyse)`, 'ERROR'); + } + + // ======================================== + // RÉSULTATS FINAUX + // ======================================== + const duration = Date.now() - startTime; + stats.duration = duration; + + logSh(`\n✅ === SELECTIVE SMART TOUCH TERMINÉ ===`, 'INFO'); + logSh(` 📊 ${stats.elementsImproved}/${stats.elementsProcessed} éléments améliorés`, 'INFO'); + logSh(` 🔄 ${stats.totalModifications} modifications totales`, 'INFO'); + logSh(` ⏱️ Durée: ${duration}ms`, 'INFO'); + logSh(` 🎯 Couches appliquées: ${stats.layersApplied.map(l => l.name).join(' → ')}`, 'INFO'); + + await tracer.event('SelectiveSmartTouch terminé', stats); + + return { + content: currentContent, + stats, + modifications: stats.totalModifications, + analysisResults: stats.analysisResults + }; + + } catch (error) { + const duration = Date.now() - startTime; + logSh(`❌ SELECTIVE SMART TOUCH ÉCHOUÉ après ${duration}ms: ${error.message}`, 'ERROR'); + + return { + content, // Fallback: contenu original + stats: { + error: error.message, + duration, + fallback: true + }, + modifications: 0, + fallback: true + }; + } + }, { content: Object.keys(content), config }); + } + + /** + * DÉTERMINER COUCHES À APPLIQUER + */ + determineLayersToApply(mode, layersOrder) { + switch (mode) { + case 'technical_only': + return ['technical']; + case 'style_only': + return ['style']; + case 'readability_only': + return ['readability']; + case 'full': + default: + return layersOrder; // Ordre personnalisable + } + } + + /** + * APPLIQUER UNE COUCHE SPÉCIFIQUE + */ + async applyLayer(layerName, content, analysis, context) { + switch (layerName) { + case 'technical': + return await this.technicalLayer.applyTargeted(content, analysis, context); + case 'style': + return await this.styleLayer.applyTargeted(content, analysis, context); + case 'readability': + return await this.readabilityLayer.applyTargeted(content, analysis, context); + default: + throw new Error(`Couche inconnue: ${layerName}`); + } + } + + /** + * APPLIQUER COUCHE SUR SEGMENTS SÉLECTIONNÉS UNIQUEMENT (10% système) + * @param {string} layerName - Nom de la couche + * @param {array} allSegments - Tous les segments du texte + * @param {array} weakestSegments - Segments sélectionnés à améliorer + * @param {object} analysis - Analyse globale + * @param {object} context - Contexte + * @returns {object} - { content: texte réassemblé, modifications, ... } + */ + async applyLayerToSegments(layerName, allSegments, weakestSegments, analysis, context) { + // Si aucun segment à améliorer, retourner texte original + if (weakestSegments.length === 0) { + const originalContent = allSegments.map(s => s.content).join(' '); + return { + content: originalContent, + modifications: 0, + skipped: true, + reason: 'No weak segments identified' + }; + } + + // Créer Map des indices des segments à améliorer pour lookup rapide + const weakIndices = new Set(weakestSegments.map(s => s.index)); + + // === AMÉLIORER UNIQUEMENT LES SEGMENTS FAIBLES === + const improvedSegments = []; + let totalModifications = 0; + + for (const segment of allSegments) { + if (weakIndices.has(segment.index)) { + // AMÉLIORER ce segment + try { + const result = await this.applyLayer(layerName, segment.content, analysis, context); + + improvedSegments.push({ + ...segment, + content: result.skipped ? segment.content : result.content, + improved: !result.skipped + }); + + totalModifications += result.modifications || 0; + + } catch (error) { + logSh(` ⚠️ Échec amélioration segment ${segment.index}: ${error.message}`, 'WARN'); + // Fallback: garder segment original + improvedSegments.push({ ...segment, improved: false }); + } + } else { + // GARDER segment intact + improvedSegments.push({ ...segment, improved: false }); + } + } + + // === RÉASSEMBLER TEXTE COMPLET === + const reassembledContent = improvedSegments.map(s => s.content).join(' '); + + // Nettoyer espaces multiples + const cleanedContent = reassembledContent.replace(/\s{2,}/g, ' ').trim(); + + const improvedCount = improvedSegments.filter(s => s.improved).length; + + logSh(` ✅ ${improvedCount}/${allSegments.length} segments améliorés (${totalModifications} modifs)`, 'DEBUG'); + + return { + content: cleanedContent, + modifications: totalModifications, + segmentsImproved: improvedCount, + segmentsTotal: allSegments.length, + skipped: false + }; + } + + /** + * MODES DISPONIBLES + */ + static getAvailableModes() { + return [ + { + name: 'analysis_only', + description: 'Analyse uniquement (sans amélioration)', + layers: [] + }, + { + name: 'technical_only', + description: 'Améliorations techniques ciblées uniquement', + layers: ['technical'] + }, + { + name: 'style_only', + description: 'Améliorations style ciblées uniquement', + layers: ['style'] + }, + { + name: 'readability_only', + description: 'Améliorations lisibilité ciblées uniquement', + layers: ['readability'] + }, + { + name: 'full', + description: 'Analyse + toutes améliorations ciblées (recommandé)', + layers: ['technical', 'style', 'readability'] + } + ]; + } +} + +module.exports = { SmartTouchCore }; diff --git a/public/pipeline-builder.js b/public/pipeline-builder.js index f2dbf9e..cf44ab7 100644 --- a/public/pipeline-builder.js +++ b/public/pipeline-builder.js @@ -96,7 +96,7 @@ function renderModulesPalette() { const categories = { core: ['generation'], - enhancement: ['selective'], + enhancement: ['selective', 'smarttouch'], // ✅ AJOUTÉ: smarttouch protection: ['adversarial', 'human', 'pattern'] }; diff --git a/public/pipeline-runner.html b/public/pipeline-runner.html index 4136034..a5c2c8d 100644 --- a/public/pipeline-runner.html +++ b/public/pipeline-runner.html @@ -310,6 +310,16 @@ +
+ +

+ Chaque étape du pipeline sera sauvegardée avec sa version (v1.1, v1.2, etc.) - ✅ Activé par défaut +

+
+ diff --git a/public/pipeline-runner.js b/public/pipeline-runner.js index d300680..daeeaf3 100644 --- a/public/pipeline-runner.js +++ b/public/pipeline-runner.js @@ -120,6 +120,7 @@ async function runPipeline() { } const rowNumber = parseInt(document.getElementById('rowNumber').value); + const saveIntermediateSteps = document.getElementById('saveIntermediateSteps').checked; if (!rowNumber || rowNumber < 2) { showStatus('Numéro de ligne invalide (minimum 2)', 'error'); @@ -144,7 +145,10 @@ async function runPipeline() { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pipelineConfig: state.selectedPipeline, - rowNumber: rowNumber + rowNumber: rowNumber, + options: { + saveIntermediateSteps: saveIntermediateSteps + } }) });