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

47 KiB

🚀 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
  2. Architecture Proposée
  3. Architecture Modulaire Actuelle
  4. Composants à Créer
  5. Backend - Nouveaux Endpoints
  6. Schéma des Données
  7. Design System
  8. JavaScript - Fonctions Clés
  9. Plan d'Exécution Séquentiel
  10. Tests à Effectuer
  11. 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 Configconfig-editor.html

    • Éditer les 4 couches modulaires
    • Tester en direct
    • Sauvegarder avec nom
  2. Exécution Productionproduction-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

{
  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 :

<!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é :

<!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é :

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

// ========================================
// 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() :

// ========================================
// 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

{
  "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

{
  "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

{
  "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

: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

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

/* 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

// ========================================
// 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

// ========================================
// 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

# 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