feat(selective-smart-touch): Add intelligent analysis-driven enhancement system + validation spec

## SelectiveSmartTouch (NEW)
- Architecture révolutionnaire: Analyse intelligente → Améliorations ciblées précises
- 5 modules: SmartAnalysisLayer, SmartTechnicalLayer, SmartStyleLayer, SmartReadabilityLayer, SmartTouchCore
- Système 10% segments: amélioration uniquement des segments les plus faibles (intensity-based)
- Détection contexte globale pour prompts adaptatifs multi-secteurs
- Intégration complète dans PipelineExecutor et PipelineDefinition

## Pipeline Validator Spec (NEW)
- Spécification complète système validation qualité par LLM
- 5 critères universels: Qualité, Verbosité, SEO, Répétitions, Naturalité
- Échantillonnage intelligent par filtrage balises (pas XML)
- Évaluation multi-versions avec justifications détaillées
- Coût estimé: ~$1/validation (260 appels LLM)

## Optimizations
- Réduction intensités fullEnhancement (technical 1.0→0.7, style 0.8→0.5)
- Ajout gardes-fous anti-familiarité excessive dans StyleLayer
- Sauvegarde étapes intermédiaires activée par défaut (pipeline-runner)

## Fixes
- Fix typo critique SmartTouchCore.js:110 (determineLayers ToApply → determineLayersToApply)
- Prompts généralisés multi-secteurs (e-commerce, SaaS, services, informatif)

🚀 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
StillHammer 2025-10-13 15:01:02 +08:00
parent 64fb319e65
commit 0244521f5c
19 changed files with 2955 additions and 47 deletions

877
PIPELINE_VALIDATOR_SPEC.md Normal file
View File

@ -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** 🚀

View File

@ -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
}
};
}

View File

@ -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');
}

View File

@ -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
}
});

View File

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

View File

@ -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 (AnalyseAmé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
*/

View File

@ -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
},

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

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

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -96,7 +96,7 @@ function renderModulesPalette() {
const categories = {
core: ['generation'],
enhancement: ['selective'],
enhancement: ['selective', 'smarttouch'], // ✅ AJOUTÉ: smarttouch
protection: ['adversarial', 'human', 'pattern']
};

View File

@ -310,6 +310,16 @@
<input type="number" id="rowNumber" value="2" min="2" max="1000">
</div>
<div class="form-group">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="saveIntermediateSteps" checked style="width: auto; margin-right: 10px;">
<span>💾 Sauvegarder les étapes intermédiaires dans Google Sheets (Generated_Articles_Versioned)</span>
</label>
<p style="font-size: 12px; color: var(--text-light); margin-top: 5px; margin-left: 28px;">
Chaque étape du pipeline sera sauvegardée avec sa version (v1.1, v1.2, etc.) - ✅ Activé par défaut
</p>
</div>
<button class="btn-run" id="btnRun" onclick="runPipeline()" disabled>
🚀 Lancer l'Exécution
</button>

View File

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