seo-generator-server/ProductionReady.md
StillHammer cd79ca9a4a chore: Add documentation, scripts and monitoring tools
- Add comprehensive documentation (IMPLEMENTATION_COMPLETE, ProductionReady, QUICK_START, STARTUP_ANALYSIS)
- Add startup scripts (start-server.sh, start-server.bat, check-setup.sh)
- Add configs directory structure with README
- Add ValidationGuards and Main.js backup
- Add LLM monitoring HTML interface
- Add cache templates and XML files
- Add technical report (rapport_technique.md)
- Add bundled code.js

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 16:10:56 +08:00

1622 lines
47 KiB
Markdown

# 🚀 Production Ready - Interface Configuration & Runner
**Date**: 2025-10-08
**Version**: 1.0.0
**Objectif**: Créer une interface complète pour configurer, sauvegarder et exécuter des workflows modulaires de production
---
## 📋 Table des Matières
1. [Vue d'Ensemble](#vue-densemble)
2. [Architecture Proposée](#architecture-proposée)
3. [Architecture Modulaire Actuelle](#architecture-modulaire-actuelle)
4. [Composants à Créer](#composants-à-créer)
5. [Backend - Nouveaux Endpoints](#backend---nouveaux-endpoints)
6. [Schéma des Données](#schéma-des-données)
7. [Design System](#design-system)
8. [JavaScript - Fonctions Clés](#javascript---fonctions-clés)
9. [Plan d'Exécution Séquentiel](#plan-dexécution-séquentiel)
10. [Tests à Effectuer](#tests-à-effectuer)
11. [Notes Importantes](#notes-importantes)
---
## 🎯 Vue d'Ensemble
### Problématique
Actuellement, l'interface `test-modulaire.html` permet de tester des configurations modulaires, mais :
- ❌ Pas de sauvegarde des configurations testées
- ❌ Pas de réutilisation facile des configs
- ❌ Pas d'interface dédiée pour la production (Google Sheets)
### Solution Proposée
Créer **3 pages web** interconnectées :
1. **`index.html`** - Page d'accueil avec navigation
2. **`config-editor.html`** - Éditeur de configuration avec Save/Load/Delete
3. **`production-runner.html`** - Runner de production avec logs temps réel
---
## 🏗️ Architecture Proposée
```
┌─────────────────────────────────────────────────────────┐
│ INDEX.HTML │
│ (Page d'accueil) │
│ │
│ ┌──────────────────────┐ ┌────────────────────────┐ │
│ │ 🔧 Configuration │ │ 🚀 Production Run │ │
│ │ Editor │ │ Runner │ │
│ └──────────────────────┘ └────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌────────────────────┐ ┌──────────────────────┐
│ config-editor.html │ │ production-runner.html│
│ │ │ │
│ - Éditer config │ │ - Load config │
│ - Save/Load │ │ - Select GSheet row │
│ - Test live │ │ - Run production │
│ - Delete │ │ - View results │
└────────────────────┘ └──────────────────────┘
```
### Flux de Travail
1. **Création Config**`config-editor.html`
- Éditer les 4 couches modulaires
- Tester en direct
- Sauvegarder avec nom
2. **Exécution Production**`production-runner.html`
- Charger config sauvegardée
- Sélectionner ligne Google Sheets
- Lancer workflow complet
- Voir résultats + lien GSheets
---
## 📐 Architecture Modulaire Actuelle
### 4 Couches Modulaires
| Couche | Stacks Disponibles | Total |
|--------|-------------------|-------|
| **Selective Enhancement** | `lightEnhancement`, `standardEnhancement`, `fullEnhancement`, `personalityFocus`, `fluidityFocus`, `adaptive` | **6** |
| **Adversarial Generation** | `none`, `light`, `standard`, `heavy`, `adaptive` | **5** |
| **Human Simulation** | `none`, `lightSimulation`, `standardSimulation`, `heavySimulation`, `adaptiveSimulation`, `personalityFocus`, `temporalFocus` | **7** |
| **Pattern Breaking** | `none`, `lightPatternBreaking`, `standardPatternBreaking`, `heavyPatternBreaking`, `adaptivePatternBreaking`, `syntaxFocus`, `connectorsFocus` | **7** |
### Configuration Standard Actuelle
```javascript
{
rowNumber: 2,
selectiveStack: 'standardEnhancement',
adversarialMode: 'light',
humanSimulationMode: 'none',
patternBreakingMode: 'none',
saveIntermediateSteps: true,
source: 'web_interface'
}
```
### Fichiers Modulaires Existants
- `lib/selective-enhancement/SelectiveLayers.js` - 6 stacks prédéfinis
- `lib/adversarial-generation/AdversarialLayers.js` - 5 modes défense
- `lib/human-simulation/HumanSimulationLayers.js` - 6 modes simulation
- `lib/pattern-breaking/PatternBreakingLayers.js` - 7 modes cassage patterns
---
## 🔧 Composants à Créer
### 1. Page d'Accueil : `public/index.html`
**Fonctionnalités :**
- Design simple et clean avec 2 grandes cards cliquables
- Navigation vers config-editor.html ou production-runner.html
- Affichage du status serveur (API `/api/status`)
- Statistiques rapides (nombre de configs sauvegardées, dernière exécution)
**Structure HTML :**
```html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SEO Generator - Accueil</title>
<style>
/* Design system (voir section Design System) */
</style>
</head>
<body>
<header>
<h1>🎯 SEO Generator - Dashboard</h1>
<div class="server-status"></div>
</header>
<main>
<div class="card-container">
<!-- Card 1: Configuration Editor -->
<div class="card" onclick="navigateTo('config-editor.html')">
<div class="card-icon">🔧</div>
<h2>Éditeur de Configuration</h2>
<p>Créer, éditer et sauvegarder des configurations modulaires</p>
<ul>
<li>4 couches configurables</li>
<li>Save/Load configs</li>
<li>Test en direct</li>
</ul>
</div>
<!-- Card 2: Production Runner -->
<div class="card" onclick="navigateTo('production-runner.html')">
<div class="card-icon">🚀</div>
<h2>Runner de Production</h2>
<p>Exécuter un workflow complet sur Google Sheets</p>
<ul>
<li>Load config sauvegardée</li>
<li>Sélection ligne GSheet</li>
<li>Logs temps réel</li>
</ul>
</div>
</div>
<div class="stats-panel">
<h3>📊 Statistiques</h3>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-value" id="configCount">-</span>
<span class="stat-label">Configs sauvegardées</span>
</div>
<div class="stat-item">
<span class="stat-value" id="lastRun">-</span>
<span class="stat-label">Dernière exécution</span>
</div>
</div>
</div>
</main>
<script>
function navigateTo(page) {
window.location.href = page;
}
async function loadStats() {
try {
const statusResponse = await fetch('/api/status');
const statusData = await statusResponse.json();
const configResponse = await fetch('/api/config/list');
const configData = await configResponse.json();
document.getElementById('configCount').textContent = configData.count || 0;
const uptime = Math.floor(statusData.uptime / 1000);
document.getElementById('lastRun').textContent = `${uptime}s ago`;
} catch (error) {
console.error('Erreur chargement stats:', error);
}
}
window.onload = loadStats;
</script>
</body>
</html>
```
**API Calls :**
- `GET /api/status` : Status serveur + uptime
- `GET /api/config/list` : Nombre de configs sauvegardées
---
### 2. Éditeur de Config : `public/config-editor.html`
**Fonctionnalités :**
- **Formulaire complet** : 4 couches modulaires (selects inspirés de test-modulaire.html)
- **Ligne GSheet** : Input pour rowNumber
- **Actions :**
- 💾 **Save Config** : Sauvegarder avec nom personnalisé
- 📂 **Load Config** : Charger depuis liste déroulante
- 🗑️ **Delete Config** : Supprimer une config existante
- 🚀 **Test Live** : Tester la config immédiatement (comme test-modulaire.html)
- **Logs Temps Réel** : Container avec WebSocket (comme test-modulaire.html)
- **Preview Config** : Afficher JSON de la config actuelle
**Structure HTML Clé :**
```html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Éditeur de Configuration</title>
</head>
<body>
<header>
<h1>🔧 Éditeur de Configuration Modulaire</h1>
<a href="index.html" class="btn-back">← Retour Accueil</a>
</header>
<div class="editor-container">
<!-- Section Configuration -->
<div class="config-section">
<h2>⚙️ Configuration Modulaire</h2>
<!-- Row Number -->
<div class="form-group">
<label>Ligne Google Sheet</label>
<input type="number" id="rowNumber" value="2" min="2">
</div>
<!-- Selective Enhancement -->
<div class="module-group">
<h3>🔧 Selective Enhancement</h3>
<select id="selectiveStack">
<option value="lightEnhancement">Light Enhancement</option>
<option value="standardEnhancement" selected>Standard Enhancement</option>
<option value="fullEnhancement">Full Enhancement</option>
<option value="personalityFocus">Personality Focus</option>
<option value="fluidityFocus">Fluidity Focus</option>
<option value="adaptive">Adaptive</option>
</select>
<div class="option-description">Base d'amélioration du contenu généré</div>
</div>
<!-- Adversarial Mode -->
<div class="module-group">
<h3>🎯 Adversarial Generation</h3>
<select id="adversarialMode">
<option value="none">None</option>
<option value="light" selected>Light</option>
<option value="standard">Standard</option>
<option value="heavy">Heavy</option>
<option value="adaptive">Adaptive</option>
</select>
<div class="option-description">Techniques adversariales anti-détection</div>
</div>
<!-- Human Simulation -->
<div class="module-group">
<h3>🧠 Human Simulation</h3>
<select id="humanSimulationMode">
<option value="none" selected>None</option>
<option value="lightSimulation">Light Simulation</option>
<option value="standardSimulation">Standard Simulation</option>
<option value="heavySimulation">Heavy Simulation</option>
<option value="adaptiveSimulation">Adaptive Simulation</option>
<option value="personalityFocus">Personality Focus</option>
<option value="temporalFocus">Temporal Focus</option>
</select>
<div class="option-description">Simulation comportement humain (fatigue, erreurs)</div>
</div>
<!-- Pattern Breaking -->
<div class="module-group">
<h3>🔧 Pattern Breaking</h3>
<select id="patternBreakingMode">
<option value="none" selected>None</option>
<option value="lightPatternBreaking">Light Pattern Breaking</option>
<option value="standardPatternBreaking">Standard Pattern Breaking</option>
<option value="heavyPatternBreaking">Heavy Pattern Breaking</option>
<option value="adaptivePatternBreaking">Adaptive Pattern Breaking</option>
<option value="syntaxFocus">Syntax Focus</option>
<option value="connectorsFocus">Connectors Focus</option>
</select>
<div class="option-description">Cassage patterns syntaxiques LLM</div>
</div>
</div>
<!-- Section Save/Load -->
<div class="actions-section">
<h2>💾 Gestion Configurations</h2>
<!-- Save -->
<div class="save-group">
<input type="text" id="configName" placeholder="Nom configuration...">
<button onclick="saveConfig()" class="btn-primary">💾 Save Config</button>
</div>
<!-- Load -->
<div class="load-group">
<select id="savedConfigs">
<option value="">-- Charger une config --</option>
</select>
<button onclick="loadConfig()" class="btn-secondary">📂 Load</button>
<button onclick="deleteConfig()" class="btn-danger">🗑️ Delete</button>
</div>
<!-- Test Live -->
<div class="test-group">
<button onclick="testLive()" class="btn-primary">🚀 Test Live</button>
</div>
</div>
<!-- Preview JSON -->
<div class="preview-section">
<h3>📄 Preview Configuration</h3>
<pre id="configPreview"></pre>
</div>
<!-- Logs Temps Réel -->
<div class="logs-section">
<h3>🔍 Logs Temps Réel</h3>
<button onclick="clearLogs()" class="btn-clear">Clear</button>
<div class="logs-container" id="logsContainer"></div>
</div>
</div>
<script src="config-editor.js"></script>
</body>
</html>
```
**API Calls :**
- `POST /api/config/save` : Sauvegarder config avec nom
- `GET /api/config/list` : Lister toutes les configs
- `GET /api/config/:name` : Charger une config par nom
- `DELETE /api/config/:name` : Supprimer une config
- `POST /api/test-modulaire` : Tester la config (existant)
---
### 3. Runner de Production : `public/production-runner.html`
**Fonctionnalités :**
- **Load Config** : Dropdown pour sélectionner une config sauvegardée
- **Row Selection** : Input pour choisir la ligne Google Sheets
- **Run Button** : Lancer le workflow complet avec sauvegarde versionnée
- **Progress Tracking** : Barre de progression + étapes
- **Results Display** :
- Statistiques finales (word count, LLM utilisés, coût, durée)
- Lien vers Google Sheets
- Logs détaillés
- **WebSocket Logs** : Logs temps réel pendant l'exécution
**Structure HTML Clé :**
```html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Production Runner</title>
</head>
<body>
<div class="runner-container">
<!-- Header -->
<header>
<h1>🚀 Production Runner</h1>
<a href="index.html" class="btn-back">← Retour Accueil</a>
</header>
<!-- Configuration Selection -->
<div class="config-selection">
<h2>⚙️ Sélection Configuration</h2>
<div class="select-group">
<label>Configuration :</label>
<select id="configSelect" onchange="loadSelectedConfig()">
<option value="">-- Sélectionner une config --</option>
</select>
<button onclick="refreshConfigs()">🔄 Refresh</button>
</div>
<div class="config-display" id="configDisplay">
<!-- Affichage config sélectionnée -->
</div>
</div>
<!-- Row Selection -->
<div class="row-selection">
<h2>📊 Google Sheets</h2>
<div class="form-group">
<label>Ligne à traiter :</label>
<input type="number" id="rowNumber" value="2" min="2" max="1000">
<span class="help-text">Ligne du Google Sheet "Instructions"</span>
</div>
</div>
<!-- Run Controls -->
<div class="run-controls">
<button onclick="runProduction()" class="btn-run" id="btnRun">
🚀 Lancer Production
</button>
<button onclick="cancelRun()" class="btn-cancel" id="btnCancel" disabled>
🛑 Annuler
</button>
</div>
<!-- Progress Tracking -->
<div class="progress-section" id="progressSection" style="display: none;">
<h2>⏳ Progression</h2>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="progress-steps" id="progressSteps">
<!-- Étapes dynamiques -->
</div>
</div>
<!-- Results -->
<div class="results-section" id="resultsSection" style="display: none;">
<h2>📊 Résultats</h2>
<div class="stats-grid">
<div class="stat-card">
<span class="stat-label">Mots Générés</span>
<span class="stat-value" id="wordCount">-</span>
</div>
<div class="stat-card">
<span class="stat-label">Durée Totale</span>
<span class="stat-value" id="duration">-</span>
</div>
<div class="stat-card">
<span class="stat-label">LLM Utilisés</span>
<span class="stat-value" id="llmUsed">-</span>
</div>
<div class="stat-card">
<span class="stat-label">Coût Estimé</span>
<span class="stat-value" id="cost">-</span>
</div>
</div>
<div class="links-section">
<a href="#" id="gsheetsLink" target="_blank" class="btn-link">
📊 Voir dans Google Sheets
</a>
</div>
</div>
<!-- Logs Temps Réel -->
<div class="logs-section">
<h3>🔍 Logs Temps Réel</h3>
<button onclick="clearLogs()" class="btn-clear">Clear</button>
<div class="logs-container" id="logsContainer"></div>
</div>
</div>
<script src="production-runner.js"></script>
</body>
</html>
```
**API Calls :**
- `GET /api/config/list` : Lister configs disponibles
- `GET /api/config/:name` : Charger config sélectionnée
- `POST /api/production-run` : Lancer workflow complet (nouveau endpoint)
---
## 🔧 Backend - Nouveaux Endpoints
### 1. Système de Stockage des Configurations
**Fichier : `lib/ConfigManager.js`**
```javascript
// ========================================
// FICHIER: ConfigManager.js
// RESPONSABILITÉ: Gestion CRUD des configurations modulaires
// STOCKAGE: Fichiers JSON dans configs/
// ========================================
const fs = require('fs').promises;
const path = require('path');
const { logSh } = require('./ErrorReporting');
class ConfigManager {
constructor() {
this.configDir = path.join(__dirname, '../configs');
this.ensureConfigDir();
}
async ensureConfigDir() {
try {
await fs.mkdir(this.configDir, { recursive: true });
} catch (error) {
logSh(`⚠️ Erreur création dossier configs: ${error.message}`, 'WARNING');
}
}
/**
* Sauvegarder une configuration
*/
async saveConfig(name, config) {
const sanitizedName = name.replace(/[^a-zA-Z0-9-_]/g, '_');
const filePath = path.join(this.configDir, `${sanitizedName}.json`);
const configData = {
name: sanitizedName,
displayName: name,
config,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
await fs.writeFile(filePath, JSON.stringify(configData, null, 2), 'utf-8');
logSh(`💾 Config sauvegardée: ${name}`, 'INFO');
return { success: true, name: sanitizedName };
}
/**
* Charger une configuration
*/
async loadConfig(name) {
const sanitizedName = name.replace(/[^a-zA-Z0-9-_]/g, '_');
const filePath = path.join(this.configDir, `${sanitizedName}.json`);
try {
const data = await fs.readFile(filePath, 'utf-8');
const configData = JSON.parse(data);
logSh(`📂 Config chargée: ${name}`, 'DEBUG');
return configData;
} catch (error) {
logSh(`❌ Config non trouvée: ${name}`, 'ERROR');
throw new Error(`Configuration "${name}" non trouvée`);
}
}
/**
* Lister toutes les configurations
*/
async listConfigs() {
try {
const files = await fs.readdir(this.configDir);
const jsonFiles = files.filter(f => f.endsWith('.json'));
const configs = await Promise.all(
jsonFiles.map(async (file) => {
const filePath = path.join(this.configDir, file);
const data = await fs.readFile(filePath, 'utf-8');
const configData = JSON.parse(data);
return {
name: configData.name,
displayName: configData.displayName || configData.name,
createdAt: configData.createdAt,
updatedAt: configData.updatedAt
};
})
);
return configs.sort((a, b) =>
new Date(b.updatedAt) - new Date(a.updatedAt)
);
} catch (error) {
logSh(`⚠️ Erreur listing configs: ${error.message}`, 'WARNING');
return [];
}
}
/**
* Supprimer une configuration
*/
async deleteConfig(name) {
const sanitizedName = name.replace(/[^a-zA-Z0-9-_]/g, '_');
const filePath = path.join(this.configDir, `${sanitizedName}.json`);
await fs.unlink(filePath);
logSh(`🗑️ Config supprimée: ${name}`, 'INFO');
return { success: true };
}
}
module.exports = { ConfigManager };
```
---
### 2. Nouveaux Endpoints dans `ManualServer.js`
**Ajouter dans `setupAPIRoutes()` :**
```javascript
// ========================================
// ENDPOINTS GESTION CONFIGURATIONS
// ========================================
// Sauvegarder une configuration
this.app.post('/api/config/save', async (req, res) => {
try {
const { name, config } = req.body;
if (!name || !config) {
return res.status(400).json({
success: false,
error: 'Nom et configuration requis'
});
}
const { ConfigManager } = require('../ConfigManager');
const configManager = new ConfigManager();
const result = await configManager.saveConfig(name, config);
res.json({
success: true,
message: `Configuration "${name}" sauvegardée`,
savedName: result.name
});
} catch (error) {
logSh(`❌ Erreur save config: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: error.message
});
}
});
// Lister les configurations
this.app.get('/api/config/list', async (req, res) => {
try {
const { ConfigManager } = require('../ConfigManager');
const configManager = new ConfigManager();
const configs = await configManager.listConfigs();
res.json({
success: true,
configs,
count: configs.length
});
} catch (error) {
logSh(`❌ Erreur list configs: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: error.message
});
}
});
// Charger une configuration
this.app.get('/api/config/:name', async (req, res) => {
try {
const { name } = req.params;
const { ConfigManager } = require('../ConfigManager');
const configManager = new ConfigManager();
const configData = await configManager.loadConfig(name);
res.json({
success: true,
config: configData
});
} catch (error) {
logSh(`❌ Erreur load config: ${error.message}`, 'ERROR');
res.status(404).json({
success: false,
error: error.message
});
}
});
// Supprimer une configuration
this.app.delete('/api/config/:name', async (req, res) => {
try {
const { name } = req.params;
const { ConfigManager } = require('../ConfigManager');
const configManager = new ConfigManager();
await configManager.deleteConfig(name);
res.json({
success: true,
message: `Configuration "${name}" supprimée`
});
} catch (error) {
logSh(`❌ Erreur delete config: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: error.message
});
}
});
// ========================================
// ENDPOINT PRODUCTION RUN
// ========================================
this.app.post('/api/production-run', async (req, res) => {
try {
const {
rowNumber,
selectiveStack,
adversarialMode,
humanSimulationMode,
patternBreakingMode,
saveIntermediateSteps = true
} = req.body;
if (!rowNumber) {
return res.status(400).json({
success: false,
error: 'rowNumber requis'
});
}
logSh(`🚀 PRODUCTION RUN: Row ${rowNumber}`, 'INFO');
// Appel handleFullWorkflow depuis Main.js
const { handleFullWorkflow } = require('../Main');
const result = await handleFullWorkflow({
rowNumber,
selectiveStack: selectiveStack || 'standardEnhancement',
adversarialMode: adversarialMode || 'light',
humanSimulationMode: humanSimulationMode || 'none',
patternBreakingMode: patternBreakingMode || 'none',
saveIntermediateSteps,
source: 'production_web'
});
res.json({
success: true,
result: {
wordCount: result.compiledWordCount,
duration: result.totalDuration,
llmUsed: result.llmUsed,
cost: result.estimatedCost,
slug: result.slug,
gsheetsLink: `https://docs.google.com/spreadsheets/d/${process.env.GOOGLE_SHEETS_ID}`
}
});
} catch (error) {
logSh(`❌ Erreur production run: ${error.message}`, 'ERROR');
res.status(500).json({
success: false,
error: error.message
});
}
});
```
---
## 📊 Schéma des Données
### Format Config Sauvegardée
**Fichier : `configs/nom-config.json`**
```json
{
"name": "config_standard_heavy",
"displayName": "Config Standard Heavy",
"config": {
"rowNumber": 2,
"selectiveStack": "standardEnhancement",
"adversarialMode": "heavy",
"humanSimulationMode": "standardSimulation",
"patternBreakingMode": "standardPatternBreaking",
"saveIntermediateSteps": true,
"source": "web_interface"
},
"createdAt": "2025-10-08T14:30:00.000Z",
"updatedAt": "2025-10-08T14:30:00.000Z"
}
```
### API Responses
#### `GET /api/config/list`
```json
{
"success": true,
"configs": [
{
"name": "config_standard_heavy",
"displayName": "Config Standard Heavy",
"createdAt": "2025-10-08T14:30:00.000Z",
"updatedAt": "2025-10-08T14:30:00.000Z"
}
],
"count": 1
}
```
#### `POST /api/production-run`
```json
{
"success": true,
"result": {
"wordCount": 2151,
"duration": 45320,
"llmUsed": "claude,openai,mistral",
"cost": 0.15,
"slug": "plaque-personnalisee",
"gsheetsLink": "https://docs.google.com/spreadsheets/d/xxx"
}
}
```
---
## 🎨 Design System
### Palette de Couleurs
```css
:root {
/* Primary Colors */
--primary: #667eea;
--secondary: #764ba2;
/* Semantic Colors */
--success: #48bb78;
--warning: #ed8936;
--error: #f56565;
--info: #4299e1;
/* Backgrounds */
--bg-light: #f7fafc;
--bg-dark: #1a202c;
--bg-card: #ffffff;
/* Text */
--text-dark: #2d3748;
--text-light: #a0aec0;
--text-muted: #718096;
/* Borders */
--border-light: #e2e8f0;
--border-dark: #2d3748;
}
```
### Typography
```css
body {
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 1.6;
color: var(--text-dark);
}
h1 {
font-size: 1.8em;
font-weight: 700;
margin-bottom: 25px;
}
h2 {
font-size: 1.4em;
font-weight: 600;
margin: 20px 0 15px 0;
}
h3 {
font-size: 1.2em;
font-weight: 600;
margin: 15px 0 10px 0;
}
```
### Components
```css
/* Buttons */
.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--secondary));
color: white;
padding: 12px 24px;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
/* Cards */
.card {
background: var(--bg-card);
border-radius: 15px;
padding: 25px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
cursor: pointer;
transition: all 0.2s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 40px rgba(0,0,0,0.15);
}
/* Form Groups */
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
font-weight: 600;
margin-bottom: 8px;
color: var(--text-dark);
}
.form-group input,
.form-group select {
width: 100%;
padding: 12px;
border: 2px solid var(--border-light);
border-radius: 8px;
font-size: 14px;
transition: border-color 0.2s;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: var(--primary);
}
/* Logs Container */
.logs-container {
background: var(--bg-dark);
color: #e2e8f0;
border-radius: 8px;
padding: 15px;
font-family: 'Fira Code', monospace;
font-size: 12px;
height: 300px;
overflow-y: auto;
border: 1px solid var(--border-dark);
}
.log-entry {
margin-bottom: 4px;
padding: 2px 0;
}
.log-debug { color: #63b3ed; }
.log-info { color: #68d391; }
.log-warn { color: #f6e05e; }
.log-error { color: #fc8181; }
.log-trace { color: #a0aec0; }
```
---
## 💻 JavaScript - Fonctions Clés
### config-editor.js
```javascript
// ========================================
// GESTION CONFIGURATION EDITOR
// ========================================
let ws = null;
// WebSocket connection
function connectWebSocket() {
ws = new WebSocket('ws://localhost:8081');
ws.onopen = () => console.log('WebSocket connected');
ws.onmessage = (event) => {
try {
const logData = JSON.parse(event.data);
displayLog(logData);
} catch (e) {
displayLog({ level: 'info', msg: event.data });
}
};
ws.onclose = () => {
console.log('WebSocket disconnected');
setTimeout(connectWebSocket, 3000);
};
}
// Display log entry
function displayLog(logData) {
const logsDiv = document.getElementById('logsContainer');
const logEntry = document.createElement('div');
logEntry.className = 'log-entry';
let levelClass = 'log-info';
if (logData.level <= 10) levelClass = 'log-trace';
else if (logData.level === 20) levelClass = 'log-debug';
else if (logData.level === 30) levelClass = 'log-info';
else if (logData.level === 40) levelClass = 'log-warn';
else if (logData.level >= 50) levelClass = 'log-error';
logEntry.innerHTML = `<span class="${levelClass}">${logData.msg}</span>`;
logsDiv.appendChild(logEntry);
logsDiv.scrollTop = logsDiv.scrollHeight;
}
// Sauvegarder configuration
async function saveConfig() {
const configName = document.getElementById('configName').value;
if (!configName) {
showError('Veuillez entrer un nom de configuration');
return;
}
const config = {
rowNumber: parseInt(document.getElementById('rowNumber').value),
selectiveStack: document.getElementById('selectiveStack').value,
adversarialMode: document.getElementById('adversarialMode').value,
humanSimulationMode: document.getElementById('humanSimulationMode').value,
patternBreakingMode: document.getElementById('patternBreakingMode').value,
saveIntermediateSteps: true,
source: 'web_interface'
};
try {
const response = await fetch('/api/config/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: configName, config })
});
const result = await response.json();
if (result.success) {
showSuccess(`Configuration "${configName}" sauvegardée !`);
await refreshConfigsList();
document.getElementById('configName').value = '';
} else {
showError(result.error);
}
} catch (error) {
showError(`Erreur sauvegarde: ${error.message}`);
}
}
// Charger configuration
async function loadConfig() {
const configName = document.getElementById('savedConfigs').value;
if (!configName) return;
try {
const response = await fetch(`/api/config/${configName}`);
const result = await response.json();
if (result.success) {
const config = result.config.config;
// Remplir les champs
document.getElementById('rowNumber').value = config.rowNumber;
document.getElementById('selectiveStack').value = config.selectiveStack;
document.getElementById('adversarialMode').value = config.adversarialMode;
document.getElementById('humanSimulationMode').value = config.humanSimulationMode;
document.getElementById('patternBreakingMode').value = config.patternBreakingMode;
showSuccess(`Configuration "${configName}" chargée !`);
updateConfigPreview();
} else {
showError(result.error);
}
} catch (error) {
showError(`Erreur chargement: ${error.message}`);
}
}
// Supprimer configuration
async function deleteConfig() {
const configName = document.getElementById('savedConfigs').value;
if (!configName) return;
if (!confirm(`Supprimer la configuration "${configName}" ?`)) return;
try {
const response = await fetch(`/api/config/${configName}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
showSuccess(`Configuration "${configName}" supprimée !`);
await refreshConfigsList();
} else {
showError(result.error);
}
} catch (error) {
showError(`Erreur suppression: ${error.message}`);
}
}
// Refresh configs list
async function refreshConfigsList() {
try {
const response = await fetch('/api/config/list');
const result = await response.json();
const select = document.getElementById('savedConfigs');
select.innerHTML = '<option value="">-- Charger une config --</option>';
result.configs.forEach(config => {
const option = document.createElement('option');
option.value = config.name;
option.textContent = config.displayName;
select.appendChild(option);
});
} catch (error) {
console.error('Erreur refresh configs:', error);
}
}
// Update config preview
function updateConfigPreview() {
const config = {
rowNumber: parseInt(document.getElementById('rowNumber').value),
selectiveStack: document.getElementById('selectiveStack').value,
adversarialMode: document.getElementById('adversarialMode').value,
humanSimulationMode: document.getElementById('humanSimulationMode').value,
patternBreakingMode: document.getElementById('patternBreakingMode').value
};
document.getElementById('configPreview').textContent = JSON.stringify(config, null, 2);
}
// Test live
async function testLive() {
const config = {
rowNumber: parseInt(document.getElementById('rowNumber').value),
selectiveStack: document.getElementById('selectiveStack').value,
adversarialMode: document.getElementById('adversarialMode').value,
humanSimulationMode: document.getElementById('humanSimulationMode').value,
patternBreakingMode: document.getElementById('patternBreakingMode').value,
source: 'web_interface'
};
showStatus('🚀 Test en cours...', 'loading');
clearLogs();
try {
const response = await fetch('/api/test-modulaire', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
const result = await response.json();
if (result.success) {
showStatus('✅ Test terminé avec succès!', 'success');
} else {
showStatus('❌ Erreur: ' + (result.error || 'Erreur inconnue'), 'error');
}
} catch (error) {
showStatus('❌ Erreur: ' + error.message, 'error');
}
}
// Clear logs
function clearLogs() {
document.getElementById('logsContainer').innerHTML = '';
}
// Show status
function showStatus(message, type) {
// Implementation
}
// Show success
function showSuccess(message) {
showStatus(message, 'success');
}
// Show error
function showError(message) {
showStatus(message, 'error');
}
// Initialize
window.onload = async () => {
connectWebSocket();
await refreshConfigsList();
updateConfigPreview();
// Auto-update preview on change
['rowNumber', 'selectiveStack', 'adversarialMode', 'humanSimulationMode', 'patternBreakingMode'].forEach(id => {
document.getElementById(id).addEventListener('change', updateConfigPreview);
});
};
```
---
### production-runner.js
```javascript
// ========================================
// GESTION PRODUCTION RUNNER
// ========================================
let ws = null;
let currentConfig = null;
// WebSocket connection (same as config-editor.js)
function connectWebSocket() {
// Same implementation
}
// Load selected config
async function loadSelectedConfig() {
const configName = document.getElementById('configSelect').value;
if (!configName) {
document.getElementById('configDisplay').innerHTML = '';
currentConfig = null;
return;
}
try {
const response = await fetch(`/api/config/${configName}`);
const result = await response.json();
if (result.success) {
currentConfig = result.config.config;
displayConfigDetails(currentConfig);
}
} catch (error) {
showError(`Erreur chargement config: ${error.message}`);
}
}
// Display config details
function displayConfigDetails(config) {
const html = `
<div class="config-details">
<h3>Configuration Chargée</h3>
<ul>
<li><strong>Selective:</strong> ${config.selectiveStack}</li>
<li><strong>Adversarial:</strong> ${config.adversarialMode}</li>
<li><strong>Human Simulation:</strong> ${config.humanSimulationMode}</li>
<li><strong>Pattern Breaking:</strong> ${config.patternBreakingMode}</li>
</ul>
</div>
`;
document.getElementById('configDisplay').innerHTML = html;
}
// Refresh configs dropdown
async function refreshConfigs() {
try {
const response = await fetch('/api/config/list');
const result = await response.json();
const select = document.getElementById('configSelect');
select.innerHTML = '<option value="">-- Sélectionner une config --</option>';
result.configs.forEach(config => {
const option = document.createElement('option');
option.value = config.name;
option.textContent = config.displayName;
select.appendChild(option);
});
showSuccess('Configs rafraîchies');
} catch (error) {
showError(`Erreur refresh: ${error.message}`);
}
}
// Run production workflow
async function runProduction() {
const configName = document.getElementById('configSelect').value;
if (!configName) {
showError('Veuillez sélectionner une configuration');
return;
}
if (!currentConfig) {
showError('Configuration non chargée');
return;
}
// Override rowNumber from input
const config = {
...currentConfig,
rowNumber: parseInt(document.getElementById('rowNumber').value)
};
// UI Updates
document.getElementById('btnRun').disabled = true;
document.getElementById('btnCancel').disabled = false;
showProgressSection();
clearLogs();
try {
const response = await fetch('/api/production-run', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
const result = await response.json();
if (result.success) {
hideProgressSection();
displayResults(result.result);
showSuccess('✅ Workflow terminé avec succès!');
} else {
hideProgressSection();
showError(result.error);
}
} catch (error) {
hideProgressSection();
showError(`Erreur production: ${error.message}`);
} finally {
document.getElementById('btnRun').disabled = false;
document.getElementById('btnCancel').disabled = true;
}
}
// Display results
function displayResults(result) {
document.getElementById('resultsSection').style.display = 'block';
document.getElementById('wordCount').textContent = result.wordCount || '-';
document.getElementById('duration').textContent = `${(result.duration / 1000).toFixed(1)}s`;
document.getElementById('llmUsed').textContent = result.llmUsed || '-';
document.getElementById('cost').textContent = `$${result.cost?.toFixed(4) || '0.00'}`;
document.getElementById('gsheetsLink').href = result.gsheetsLink;
}
// Show/hide progress
function showProgressSection() {
document.getElementById('progressSection').style.display = 'block';
updateProgress(10);
}
function hideProgressSection() {
document.getElementById('progressSection').style.display = 'none';
}
function updateProgress(percentage) {
document.getElementById('progressFill').style.width = percentage + '%';
}
// Cancel run
function cancelRun() {
// TODO: Implement cancel logic
showError('Annulation non implémentée');
}
// Clear logs
function clearLogs() {
document.getElementById('logsContainer').innerHTML = '';
}
// Initialize
window.onload = async () => {
connectWebSocket();
await refreshConfigs();
};
```
---
## 📅 Plan d'Exécution Séquentiel
| Étape | Tâche | Fichiers | Durée Estimée |
|-------|-------|----------|---------------|
| **1** | Créer `ConfigManager.js` | `lib/ConfigManager.js` | 30 min |
| **2** | Ajouter endpoints backend | `lib/modes/ManualServer.js` | 45 min |
| **3** | Créer page d'accueil | `public/index.html` + CSS | 1h |
| **4** | Créer éditeur de config | `public/config-editor.html` + `config-editor.js` | 2h |
| **5** | Créer runner de production | `public/production-runner.html` + `production-runner.js` | 2h |
| **6** | Tests end-to-end | Tous les fichiers | 1h |
| **TOTAL** | | | **~7h** |
### Ordre d'Implémentation Recommandé
1. **Backend First** (Étapes 1-2) :
- Créer ConfigManager.js
- Ajouter endpoints dans ManualServer.js
- Tester avec curl/Postman
2. **Frontend Simple → Complexe** (Étapes 3-5) :
- Page d'accueil (simple navigation)
- Éditeur de config (formulaires + CRUD)
- Runner de production (intégration complète)
3. **Tests & Polish** (Étape 6) :
- Tests fonctionnels
- Corrections bugs
- Amélioration UX
---
## 🧪 Tests à Effectuer
### 1. Tests Backend (ConfigManager)
-**Save Config** : Sauvegarder avec nom normal
-**Save Config** : Nom avec espaces/accents → sanitization
-**Load Config** : Charger config existante
-**Load Config** : Config inexistante → erreur 404
-**List Configs** : Retourne tableau vide si aucune config
-**List Configs** : Retourne configs triées par date
-**Delete Config** : Suppression réussie
-**Delete Config** : Config inexistante → erreur
### 2. Tests Frontend (Config Editor)
-**Save** : Sauvegarde + apparaît dans dropdown
-**Load** : Charge tous les champs correctement
-**Delete** : Supprime + met à jour dropdown
-**Test Live** : Exécute test modulaire
-**Preview** : JSON mis à jour en temps réel
-**WebSocket** : Logs temps réel reçus
### 3. Tests Frontend (Production Runner)
-**Load Config** : Affiche détails config
-**Run Production** : Lance workflow complet
-**Run Production** : Override rowNumber correct
-**Results** : Statistiques affichées correctement
-**Results** : Lien Google Sheets fonctionnel
-**WebSocket** : Logs temps réel pendant run
-**Progress** : Barre de progression visible
### 4. Tests End-to-End
-**Flow Complet** :
1. Accueil → Config Editor
2. Créer config + sauvegarder
3. Retour Accueil → Production Runner
4. Charger config + run production
5. Vérifier résultats Google Sheets
---
## 📝 Notes Importantes
### Compatibilité Existante
-**Ne modifie PAS** `test-modulaire.html` (reste intact)
-**Réutilise** les endpoints existants (`/api/test-modulaire`)
-**Compatible** avec architecture MANUAL/AUTO modes
-**Utilise** le WebSocket existant (port 8081)
-**Respecte** l'architecture modulaire (4 couches)
### Structure Fichiers Finale
```
seo-generator-server/
├── configs/ # 🆕 Nouveau dossier
│ ├── config_standard.json
│ ├── config_heavy.json
│ └── config_light.json
├── lib/
│ ├── ConfigManager.js # 🆕 Nouveau fichier
│ └── modes/
│ └── ManualServer.js # ✏️ Modifié (nouveaux endpoints)
├── public/
│ ├── index.html # 🆕 Nouveau
│ ├── config-editor.html # 🆕 Nouveau
│ ├── config-editor.js # 🆕 Nouveau
│ ├── production-runner.html # 🆕 Nouveau
│ ├── production-runner.js # 🆕 Nouveau
│ └── test-modulaire.html # ✅ Existant (non modifié)
└── ProductionReady.md # 📋 Ce fichier
```
### Endpoints Backend Créés
| Méthode | Endpoint | Description |
|---------|----------|-------------|
| `POST` | `/api/config/save` | Sauvegarder configuration |
| `GET` | `/api/config/list` | Lister configurations |
| `GET` | `/api/config/:name` | Charger configuration |
| `DELETE` | `/api/config/:name` | Supprimer configuration |
| `POST` | `/api/production-run` | Lancer workflow production |
### Endpoints Existants Réutilisés
| Méthode | Endpoint | Description |
|---------|----------|-------------|
| `GET` | `/api/status` | Status serveur |
| `POST` | `/api/test-modulaire` | Test modulaire (existant) |
### Dépendances
Aucune nouvelle dépendance npm requise. Utilise uniquement :
- Express (existant)
- WebSocket (existant)
- fs/promises (Node.js built-in)
- path (Node.js built-in)
---
## 🚀 Prêt à Implémenter
### Checklist Avant Démarrage
- [ ] Backup du code actuel
- [ ] Créer branche Git `feature/config-interface`
- [ ] Vérifier que `test-modulaire.html` fonctionne
- [ ] Vérifier que le serveur démarre en mode MANUAL
### Commandes de Démarrage
```bash
# 1. Créer branche Git
git checkout -b feature/config-interface
# 2. Créer dossier configs
mkdir -p configs
# 3. Démarrer serveur en mode MANUAL
npm start
# 4. Vérifier que le serveur tourne
curl http://localhost:3000/api/status
```
### Validation Finale
Après implémentation, vérifier :
- ✅ Les 3 pages sont accessibles et navigables
- ✅ Save/Load/Delete configs fonctionne
- ✅ Test Live fonctionne (depuis config-editor)
- ✅ Production Run fonctionne avec sauvegarde GSheets
- ✅ WebSocket logs s'affichent en temps réel
-`test-modulaire.html` fonctionne toujours (non cassé)
---
## 📞 Support
Pour toute question ou problème durant l'implémentation, se référer à :
- **CLAUDE.md** : Documentation complète du projet
- **lib/modes/ManualServer.js** : Implémentation serveur MANUAL
- **public/test-modulaire.html** : Exemple d'interface modulaire fonctionnelle
---
**Dernière mise à jour** : 2025-10-08
**Auteur** : Claude Code
**Status** : 📋 Prêt pour implémentation