// ======================================== // FICHIER: ManualServer.js // RESPONSABILITÉ: Mode MANUAL - Interface Client + API + WebSocket // FONCTIONNALITÉS: Dashboard, tests modulaires, API complète // ======================================== const express = require('express'); const cors = require('cors'); const path = require('path'); const WebSocket = require('ws'); const { logSh } = require('../ErrorReporting'); const { handleModularWorkflow, benchmarkStacks } = require('../main_modulaire'); /** * SERVEUR MODE MANUAL * Interface client complète avec API, WebSocket et dashboard */ class ManualServer { constructor(options = {}) { this.config = { port: options.port || process.env.MANUAL_PORT || 3000, wsPort: options.wsPort || process.env.WS_PORT || 8081, host: options.host || '0.0.0.0', ...options }; this.app = null; this.server = null; this.wsServer = null; this.activeClients = new Set(); this.stats = { sessions: 0, requests: 0, testsExecuted: 0, startTime: Date.now(), lastActivity: null }; this.isRunning = false; } // ======================================== // DÉMARRAGE ET ARRÊT // ======================================== /** * Démarre le serveur MANUAL complet */ async start() { if (this.isRunning) { logSh('⚠️ ManualServer déjà en cours d\'exécution', 'WARNING'); return; } logSh('🎯 Démarrage ManualServer...', 'INFO'); try { // 1. Configuration Express await this.setupExpressApp(); // 2. Routes API this.setupAPIRoutes(); // 3. Interface Web this.setupWebInterface(); // 4. WebSocket pour logs temps réel await this.setupWebSocketServer(); // 5. Démarrage serveur HTTP await this.startHTTPServer(); // 6. Monitoring this.startMonitoring(); this.isRunning = true; this.stats.startTime = Date.now(); logSh(`✅ ManualServer démarré sur http://localhost:${this.config.port}`, 'INFO'); logSh(`📡 WebSocket logs sur ws://localhost:${this.config.wsPort}`, 'INFO'); } catch (error) { logSh(`❌ Erreur démarrage ManualServer: ${error.message}`, 'ERROR'); await this.stop(); throw error; } } /** * Arrête le serveur MANUAL */ async stop() { if (!this.isRunning) return; logSh('🛑 Arrêt ManualServer...', 'INFO'); try { // Déconnecter tous les clients WebSocket this.disconnectAllClients(); // Arrêter WebSocket server if (this.wsServer) { this.wsServer.close(); this.wsServer = null; } // Arrêter serveur HTTP if (this.server) { await new Promise((resolve) => { this.server.close(() => resolve()); }); this.server = null; } this.isRunning = false; logSh('✅ ManualServer arrêté', 'INFO'); } catch (error) { logSh(`⚠️ Erreur arrêt ManualServer: ${error.message}`, 'WARNING'); } } // ======================================== // CONFIGURATION EXPRESS // ======================================== /** * Configure l'application Express */ async setupExpressApp() { this.app = express(); // Middleware de base this.app.use(express.json({ limit: '10mb' })); this.app.use(express.urlencoded({ extended: true })); this.app.use(cors()); // Middleware de logs des requêtes this.app.use((req, res, next) => { this.stats.requests++; this.stats.lastActivity = Date.now(); logSh(`📥 ${req.method} ${req.path} - ${req.ip}`, 'TRACE'); next(); }); // Fichiers statiques this.app.use(express.static(path.join(__dirname, '../../public'))); logSh('⚙️ Express configuré', 'DEBUG'); } /** * Configure les routes API */ setupAPIRoutes() { // Route de status this.app.get('/api/status', (req, res) => { res.json({ success: true, mode: 'MANUAL', status: 'running', uptime: Date.now() - this.stats.startTime, stats: { ...this.stats }, clients: this.activeClients.size, timestamp: new Date().toISOString() }); }); // Test modulaire individuel this.app.post('/api/test-modulaire', async (req, res) => { await this.handleTestModulaire(req, res); }); // 🆕 Workflow modulaire avec sauvegarde par étapes this.app.post('/api/workflow-modulaire', async (req, res) => { await this.handleWorkflowModulaire(req, res); }); // Benchmark modulaire complet this.app.post('/api/benchmark-modulaire', async (req, res) => { await this.handleBenchmarkModulaire(req, res); }); // Configuration modulaire disponible this.app.get('/api/modulaire-config', (req, res) => { this.handleModulaireConfig(req, res); }); // Stats détaillées this.app.get('/api/stats', (req, res) => { res.json({ success: true, stats: { ...this.stats, uptime: Date.now() - this.stats.startTime, activeClients: this.activeClients.size, memory: process.memoryUsage(), timestamp: new Date().toISOString() } }); }); // Gestion d'erreurs API this.app.use('/api/*', (error, req, res, next) => { logSh(`❌ Erreur API ${req.path}: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: 'Erreur serveur interne', message: error.message, timestamp: new Date().toISOString() }); }); logSh('🛠️ Routes API configurées', 'DEBUG'); } // ======================================== // HANDLERS API // ======================================== /** * Gère les tests modulaires individuels */ async handleTestModulaire(req, res) { try { const config = req.body; this.stats.testsExecuted++; logSh(`🧪 Test modulaire: ${config.selectiveStack} + ${config.adversarialMode} + ${config.humanSimulationMode} + ${config.patternBreakingMode}`, 'INFO'); // Validation des paramètres if (!config.rowNumber || config.rowNumber < 2) { return res.status(400).json({ success: false, error: 'Numéro de ligne invalide (minimum 2)' }); } // Exécution du test const result = await handleModularWorkflow({ ...config, source: 'manual_server_api' }); logSh(`✅ Test modulaire terminé: ${result.stats.totalDuration}ms`, 'INFO'); res.json({ success: true, message: 'Test modulaire terminé avec succès', stats: result.stats, config: config, timestamp: new Date().toISOString() }); } catch (error) { logSh(`❌ Erreur test modulaire: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: error.message, config: req.body, timestamp: new Date().toISOString() }); } } /** * Gère les benchmarks modulaires */ async handleBenchmarkModulaire(req, res) { try { const { rowNumber = 2 } = req.body; logSh(`📊 Benchmark modulaire ligne ${rowNumber}...`, 'INFO'); if (rowNumber < 2) { return res.status(400).json({ success: false, error: 'Numéro de ligne invalide (minimum 2)' }); } const benchResults = await benchmarkStacks(rowNumber); const successfulTests = benchResults.filter(r => r.success); const avgDuration = successfulTests.length > 0 ? successfulTests.reduce((sum, r) => sum + r.duration, 0) / successfulTests.length : 0; this.stats.testsExecuted += benchResults.length; logSh(`📊 Benchmark terminé: ${successfulTests.length}/${benchResults.length} tests réussis`, 'INFO'); res.json({ success: true, message: `Benchmark terminé: ${successfulTests.length}/${benchResults.length} tests réussis`, summary: { totalTests: benchResults.length, successfulTests: successfulTests.length, failedTests: benchResults.length - successfulTests.length, averageDuration: Math.round(avgDuration), rowNumber: rowNumber }, results: benchResults, timestamp: new Date().toISOString() }); } catch (error) { logSh(`❌ Erreur benchmark modulaire: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: error.message, timestamp: new Date().toISOString() }); } } /** * 🆕 Gère les workflows modulaires avec sauvegarde par étapes */ async handleWorkflowModulaire(req, res) { try { const config = req.body; this.stats.testsExecuted++; // Configuration par défaut avec sauvegarde activée const workflowConfig = { rowNumber: config.rowNumber || 2, selectiveStack: config.selectiveStack || 'standardEnhancement', adversarialMode: config.adversarialMode || 'light', humanSimulationMode: config.humanSimulationMode || 'none', patternBreakingMode: config.patternBreakingMode || 'none', saveIntermediateSteps: config.saveIntermediateSteps !== false, // Par défaut true source: 'api_manual_server' }; logSh(`🔗 Workflow modulaire avec étapes: ligne ${workflowConfig.rowNumber}`, 'INFO'); logSh(` 📋 Config: ${workflowConfig.selectiveStack} + ${workflowConfig.adversarialMode} + ${workflowConfig.humanSimulationMode} + ${workflowConfig.patternBreakingMode}`, 'DEBUG'); logSh(` 💾 Sauvegarde étapes: ${workflowConfig.saveIntermediateSteps ? 'ACTIVÉE' : 'DÉSACTIVÉE'}`, 'INFO'); // Validation des paramètres if (workflowConfig.rowNumber < 2) { return res.status(400).json({ success: false, error: 'Numéro de ligne invalide (minimum 2)' }); } // Exécution du workflow complet const startTime = Date.now(); const result = await handleModularWorkflow(workflowConfig); const duration = Date.now() - startTime; // Statistiques finales const finalStats = { duration, success: result.success, versionsCreated: result.stats?.versionHistory?.length || 1, parentArticleId: result.stats?.parentArticleId, finalArticleId: result.storageResult?.articleId, totalModifications: { selective: result.stats?.selectiveEnhancements || 0, adversarial: result.stats?.adversarialModifications || 0, human: result.stats?.humanSimulationModifications || 0, pattern: result.stats?.patternBreakingModifications || 0 }, finalLength: result.stats?.finalLength || 0 }; logSh(`✅ Workflow modulaire terminé: ${finalStats.versionsCreated} versions créées en ${duration}ms`, 'INFO'); res.json({ success: true, message: `Workflow modulaire terminé avec succès (${finalStats.versionsCreated} versions sauvegardées)`, config: workflowConfig, stats: finalStats, versionHistory: result.stats?.versionHistory, result: { parentArticleId: finalStats.parentArticleId, finalArticleId: finalStats.finalArticleId, duration: finalStats.duration, modificationsCount: Object.values(finalStats.totalModifications).reduce((sum, val) => sum + val, 0), finalWordCount: result.storageResult?.wordCount, personality: result.stats?.personality }, timestamp: new Date().toISOString() }); } catch (error) { logSh(`❌ Erreur workflow modulaire: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: error.message, timestamp: new Date().toISOString() }); } } /** * Retourne la configuration modulaire */ handleModulaireConfig(req, res) { try { const config = { selectiveStacks: [ { value: 'lightEnhancement', name: 'Light Enhancement', description: 'Améliorations légères' }, { value: 'standardEnhancement', name: 'Standard Enhancement', description: 'Améliorations standard' }, { value: 'fullEnhancement', name: 'Full Enhancement', description: 'Améliorations complètes' }, { value: 'personalityFocus', name: 'Personality Focus', description: 'Focus personnalité' }, { value: 'fluidityFocus', name: 'Fluidity Focus', description: 'Focus fluidité' }, { value: 'adaptive', name: 'Adaptive', description: 'Adaptation automatique' } ], adversarialModes: [ { value: 'none', name: 'None', description: 'Aucune technique adversariale' }, { value: 'light', name: 'Light', description: 'Techniques adversariales légères' }, { value: 'standard', name: 'Standard', description: 'Techniques adversariales standard' }, { value: 'heavy', name: 'Heavy', description: 'Techniques adversariales intensives' }, { value: 'adaptive', name: 'Adaptive', description: 'Adaptation automatique' } ], humanSimulationModes: [ { value: 'none', name: 'None', description: 'Aucune simulation humaine' }, { value: 'lightSimulation', name: 'Light Simulation', description: 'Simulation légère' }, { value: 'standardSimulation', name: 'Standard Simulation', description: 'Simulation standard' }, { value: 'heavySimulation', name: 'Heavy Simulation', description: 'Simulation intensive' }, { value: 'adaptiveSimulation', name: 'Adaptive Simulation', description: 'Simulation adaptative' }, { value: 'personalityFocus', name: 'Personality Focus', description: 'Focus personnalité' }, { value: 'temporalFocus', name: 'Temporal Focus', description: 'Focus temporel' } ], patternBreakingModes: [ { value: 'none', name: 'None', description: 'Aucun pattern breaking' }, { value: 'lightPatternBreaking', name: 'Light Pattern Breaking', description: 'Pattern breaking léger' }, { value: 'standardPatternBreaking', name: 'Standard Pattern Breaking', description: 'Pattern breaking standard' }, { value: 'heavyPatternBreaking', name: 'Heavy Pattern Breaking', description: 'Pattern breaking intensif' }, { value: 'adaptivePatternBreaking', name: 'Adaptive Pattern Breaking', description: 'Pattern breaking adaptatif' }, { value: 'syntaxFocus', name: 'Syntax Focus', description: 'Focus syntaxe uniquement' }, { value: 'connectorsFocus', name: 'Connectors Focus', description: 'Focus connecteurs uniquement' } ] }; res.json({ success: true, config: config, timestamp: new Date().toISOString() }); } catch (error) { logSh(`❌ Erreur config modulaire: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: error.message }); } } // ======================================== // INTERFACE WEB // ======================================== /** * Configure l'interface web */ setupWebInterface() { // Page d'accueil - Dashboard MANUAL this.app.get('/', (req, res) => { res.send(this.generateManualDashboard()); }); // Route 404 this.app.use('*', (req, res) => { res.status(404).json({ success: false, error: 'Route non trouvée', path: req.originalUrl, mode: 'MANUAL', message: 'Cette route n\'existe pas en mode MANUAL' }); }); logSh('🌐 Interface web configurée', 'DEBUG'); } /** * Génère le dashboard HTML du mode MANUAL */ generateManualDashboard() { const uptime = Math.floor((Date.now() - this.stats.startTime) / 1000); return ` SEO Generator - Mode MANUAL

🎯 SEO Generator Server

MODE MANUAL

Interface Client + API + Tests Modulaires

✅ Mode MANUAL Actif
Interface complète disponible • WebSocket temps réel • API complète
${uptime}s
Uptime
${this.stats.requests}
Requêtes
${this.activeClients.size}
Clients WebSocket
${this.stats.testsExecuted}
Tests Exécutés

🧪 Interface Test Modulaire

Interface avancée pour tester toutes les combinaisons modulaires avec logs temps réel.

🚀 Ouvrir Interface Test 📋 Configuration API

📊 Monitoring & API

Endpoints disponibles en mode MANUAL.

📊 Status API 📈 Statistiques

🌐 WebSocket Logs

Logs temps réel sur ws://localhost:${this.config.wsPort}

Status: Déconnecté

💡 Informations Mode MANUAL

`; } // ======================================== // WEBSOCKET SERVER // ======================================== /** * Configure le serveur WebSocket pour logs temps réel */ async setupWebSocketServer() { try { this.wsServer = new WebSocket.Server({ port: this.config.wsPort, host: this.config.host }); this.wsServer.on('connection', (ws, req) => { this.handleWebSocketConnection(ws, req); }); this.wsServer.on('error', (error) => { logSh(`❌ Erreur WebSocket: ${error.message}`, 'ERROR'); }); logSh(`📡 WebSocket Server démarré sur ws://${this.config.host}:${this.config.wsPort}`, 'DEBUG'); } catch (error) { logSh(`⚠️ Impossible de démarrer WebSocket: ${error.message}`, 'WARNING'); // Continue sans WebSocket si erreur } } /** * Gère les nouvelles connexions WebSocket */ handleWebSocketConnection(ws, req) { const clientId = `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const clientIP = req.socket.remoteAddress; this.activeClients.add({ id: clientId, ws, ip: clientIP, connectedAt: Date.now() }); this.stats.sessions++; logSh(`📡 Nouveau client WebSocket: ${clientId} (${clientIP})`, 'DEBUG'); // Message de bienvenue ws.send(JSON.stringify({ type: 'welcome', message: 'Connecté aux logs temps réel SEO Generator (Mode MANUAL)', clientId: clientId, timestamp: new Date().toISOString() })); // Gestion fermeture ws.on('close', () => { this.activeClients.delete(clientId); logSh(`📡 Client WebSocket déconnecté: ${clientId}`, 'DEBUG'); }); // Gestion erreurs ws.on('error', (error) => { logSh(`⚠️ Erreur client WebSocket ${clientId}: ${error.message}`, 'WARNING'); }); } /** * Diffuse un message à tous les clients WebSocket */ broadcastToClients(logData) { if (this.activeClients.size === 0) return; const message = JSON.stringify({ type: 'log', ...logData, timestamp: new Date().toISOString() }); this.activeClients.forEach(client => { if (client.ws.readyState === WebSocket.OPEN) { try { client.ws.send(message); } catch (error) { // Client déconnecté, le supprimer this.activeClients.delete(client.id); } } }); } /** * Déconnecte tous les clients WebSocket */ disconnectAllClients() { this.activeClients.forEach(client => { try { client.ws.close(); } catch (error) { // Ignore les erreurs de fermeture } }); this.activeClients.clear(); logSh('📡 Tous les clients WebSocket déconnectés', 'DEBUG'); } // ======================================== // SERVEUR HTTP // ======================================== /** * Démarre le serveur HTTP */ async startHTTPServer() { return new Promise((resolve, reject) => { try { this.server = this.app.listen(this.config.port, this.config.host, () => { resolve(); }); this.server.on('error', (error) => { reject(error); }); } catch (error) { reject(error); } }); } // ======================================== // MONITORING // ======================================== /** * Démarre le monitoring du serveur */ startMonitoring() { const MONITOR_INTERVAL = 30000; // 30 secondes this.monitorInterval = setInterval(() => { this.performMonitoring(); }, MONITOR_INTERVAL); logSh('💓 Monitoring ManualServer démarré', 'DEBUG'); } /** * Effectue le monitoring périodique */ performMonitoring() { const memUsage = process.memoryUsage(); const uptime = Date.now() - this.stats.startTime; logSh(`💓 ManualServer Health - Clients: ${this.activeClients.size} | Requêtes: ${this.stats.requests} | RAM: ${Math.round(memUsage.rss / 1024 / 1024)}MB`, 'TRACE'); // Nettoyage clients WebSocket morts this.cleanupDeadClients(); } /** * Nettoie les clients WebSocket déconnectés */ cleanupDeadClients() { let cleaned = 0; this.activeClients.forEach(client => { if (client.ws.readyState !== WebSocket.OPEN) { this.activeClients.delete(client.id); cleaned++; } }); if (cleaned > 0) { logSh(`🧹 ${cleaned} clients WebSocket morts nettoyés`, 'DEBUG'); } } // ======================================== // ÉTAT ET CONTRÔLES // ======================================== /** * Vérifie s'il y a des clients actifs */ hasActiveClients() { return this.activeClients.size > 0; } /** * Retourne l'état du serveur MANUAL */ getStatus() { return { isRunning: this.isRunning, config: { ...this.config }, stats: { ...this.stats, uptime: Date.now() - this.stats.startTime }, activeClients: this.activeClients.size, urls: { dashboard: `http://localhost:${this.config.port}`, testInterface: `http://localhost:${this.config.port}/test-modulaire.html`, apiStatus: `http://localhost:${this.config.port}/api/status`, websocket: `ws://localhost:${this.config.wsPort}` } }; } } // ============= EXPORTS ============= module.exports = { ManualServer };