# 🚀 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

🔧 Éditeur de Configuration Modulaire

← Retour Accueil

⚙ 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

🚀 Production Runner

← Retour Accueil

⚙ SĂ©lection Configuration

📊 Google Sheets

Ligne du Google Sheet "Instructions"

🔍 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

`; 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