# đ 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
SEO Generator - Accueil
đŻ SEO Generator - Dashboard
đ§
Ăditeur de Configuration
Créer, éditer et sauvegarder des configurations modulaires
- 4 couches configurables
- Save/Load configs
- Test en direct
đ
Runner de Production
Exécuter un workflow complet sur Google Sheets
- Load config sauvegardée
- Sélection ligne GSheet
- Logs temps réel
đ Statistiques
-
Configs sauvegardées
-
DerniÚre exécution
```
**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
Ăditeur de Configuration
âïž Configuration Modulaire
đ§ Selective Enhancement
Base d'amélioration du contenu généré
đŻ Adversarial Generation
Techniques adversariales anti-détection
đ§ Human Simulation
Simulation comportement humain (fatigue, erreurs)
đ§ Pattern Breaking
Cassage patterns syntaxiques LLM
đŸ Gestion Configurations
đ Preview Configuration
đ Logs Temps RĂ©el
```
**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
Production Runner
âïž SĂ©lection Configuration
đ RĂ©sultats
Mots Générés
-
Durée Totale
-
LLM Utilisés
-
Coût Estimé
-
đ Logs Temps RĂ©el
```
**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 = `${logData.msg}`;
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 = '';
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 = `
Configuration Chargée
- Selective: ${config.selectiveStack}
- Adversarial: ${config.adversarialMode}
- Human Simulation: ${config.humanSimulationMode}
- Pattern Breaking: ${config.patternBreakingMode}
`;
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 = '';
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