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 +
+