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:
parent
64fb319e65
commit
0244521f5c
877
PIPELINE_VALIDATOR_SPEC.md
Normal file
877
PIPELINE_VALIDATOR_SPEC.md
Normal 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** 🚀
|
||||
@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -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');
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
});
|
||||
|
||||
@ -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
|
||||
|
||||
@ -18,6 +18,7 @@ const { saveGeneratedArticleOrganic } = require('../ArticleStorage');
|
||||
const { generateSimple } = require('../selective-enhancement/SelectiveUtils');
|
||||
const { applySelectiveLayer } = require('../selective-enhancement/SelectiveCore');
|
||||
const { applyPredefinedStack: applySelectiveStack } = require('../selective-enhancement/SelectiveLayers');
|
||||
const { SmartTouchCore } = require('../selective-smart-touch/SmartTouchCore'); // ✅ NOUVEAU: SelectiveSmartTouch
|
||||
const { applyAdversarialLayer } = require('../adversarial-generation/AdversarialCore');
|
||||
const { applyPredefinedStack: applyAdversarialStack } = require('../adversarial-generation/AdversarialLayers');
|
||||
const { applyHumanSimulationLayer } = require('../human-simulation/HumanSimulationCore');
|
||||
@ -192,6 +193,9 @@ class PipelineExecutor {
|
||||
case 'selective':
|
||||
return await this.runSelective(step, csvData);
|
||||
|
||||
case 'smarttouch': // ✅ NOUVEAU: SelectiveSmartTouch
|
||||
return await this.runSmartTouch(step, csvData);
|
||||
|
||||
case 'adversarial':
|
||||
return await this.runAdversarial(step, csvData);
|
||||
|
||||
@ -295,6 +299,48 @@ class PipelineExecutor {
|
||||
}, { mode: step.mode, intensity: step.intensity });
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ NOUVEAU: Exécute SelectiveSmartTouch (Analyse→Améliorations ciblées)
|
||||
*/
|
||||
async runSmartTouch(step, csvData) {
|
||||
return tracer.run('PipelineExecutor.runSmartTouch', async () => {
|
||||
|
||||
if (!this.currentContent) {
|
||||
throw new Error('Aucun contenu à améliorer. Génération requise avant SmartTouch');
|
||||
}
|
||||
|
||||
// ✅ Extraire llmProvider depuis parameters (comme les autres modules)
|
||||
const llmProvider = step.parameters?.llmProvider || 'gpt-4o-mini'; // Default gpt-4o-mini pour analyse objective
|
||||
|
||||
logSh(`🧠 SMART TOUCH: Mode ${step.mode}, LLM: ${llmProvider}`, 'INFO');
|
||||
|
||||
// Instancier SmartTouchCore
|
||||
const smartTouch = new SmartTouchCore();
|
||||
|
||||
// Configuration
|
||||
const config = {
|
||||
mode: step.mode || 'full', // full, analysis_only, technical_only, style_only, readability_only
|
||||
intensity: step.intensity || 1.0,
|
||||
csvData,
|
||||
llmProvider: llmProvider, // ✅ Passer le LLM choisi dans pipeline
|
||||
skipAnalysis: step.parameters?.skipAnalysis || false,
|
||||
layersOrder: step.parameters?.layersOrder || ['technical', 'style', 'readability']
|
||||
};
|
||||
|
||||
// Exécuter SmartTouch
|
||||
const result = await smartTouch.apply(this.currentContent, config);
|
||||
|
||||
logSh(`✓ SmartTouch: ${result.modifications || 0} modifications appliquées avec ${llmProvider}`, 'DEBUG');
|
||||
|
||||
return {
|
||||
content: result.content || result,
|
||||
modifications: result.modifications || 0,
|
||||
analysisResults: result.analysisResults // Inclure analyse pour debugging
|
||||
};
|
||||
|
||||
}, { mode: step.mode, intensity: step.intensity });
|
||||
}
|
||||
|
||||
/**
|
||||
* Exécute l'adversarial generation
|
||||
*/
|
||||
|
||||
@ -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
|
||||
},
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
350
lib/selective-smart-touch/README.md
Normal file
350
lib/selective-smart-touch/README.md
Normal 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
|
||||
479
lib/selective-smart-touch/SmartAnalysisLayer.js
Normal file
479
lib/selective-smart-touch/SmartAnalysisLayer.js
Normal 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 };
|
||||
210
lib/selective-smart-touch/SmartReadabilityLayer.js
Normal file
210
lib/selective-smart-touch/SmartReadabilityLayer.js
Normal 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 };
|
||||
215
lib/selective-smart-touch/SmartStyleLayer.js
Normal file
215
lib/selective-smart-touch/SmartStyleLayer.js
Normal 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 };
|
||||
278
lib/selective-smart-touch/SmartTechnicalLayer.js
Normal file
278
lib/selective-smart-touch/SmartTechnicalLayer.js
Normal 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 };
|
||||
379
lib/selective-smart-touch/SmartTouchCore.js
Normal file
379
lib/selective-smart-touch/SmartTouchCore.js
Normal 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 };
|
||||
@ -96,7 +96,7 @@ function renderModulesPalette() {
|
||||
|
||||
const categories = {
|
||||
core: ['generation'],
|
||||
enhancement: ['selective'],
|
||||
enhancement: ['selective', 'smarttouch'], // ✅ AJOUTÉ: smarttouch
|
||||
protection: ['adversarial', 'human', 'pattern']
|
||||
};
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user