// ======================================== // 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'))); // Route spécifique pour l'interface step-by-step this.app.get('/step-by-step', (req, res) => { res.sendFile(path.join(__dirname, '../../public/step-by-step.html')); }); 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() } }); }); // Lancer le log viewer avec WebSocket this.app.post('/api/start-log-viewer', (req, res) => { this.handleStartLogViewer(req, res); }); // ======================================== // APIs STEP-BY-STEP // ======================================== // Initialiser une session step-by-step this.app.post('/api/step-by-step/init', async (req, res) => { await this.handleStepByStepInit(req, res); }); // Exécuter une étape this.app.post('/api/step-by-step/execute', async (req, res) => { await this.handleStepByStepExecute(req, res); }); // Status d'une session this.app.get('/api/step-by-step/status/:sessionId', (req, res) => { this.handleStepByStepStatus(req, res); }); // Reset une session this.app.post('/api/step-by-step/reset', (req, res) => { this.handleStepByStepReset(req, res); }); // Export résultats this.app.get('/api/step-by-step/export/:sessionId', (req, res) => { this.handleStepByStepExport(req, res); }); // Liste des sessions actives this.app.get('/api/step-by-step/sessions', (req, res) => { this.handleStepByStepSessions(req, res); }); // API pour récupérer les personnalités this.app.get('/api/personalities', async (req, res) => { await this.handleGetPersonalities(req, res); }); // 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 }); } } /** * Lance le log viewer avec WebSocket */ handleStartLogViewer(req, res) { try { const { spawn } = require('child_process'); const path = require('path'); const os = require('os'); // Démarrer le WebSocket pour logs process.env.ENABLE_LOG_WS = 'true'; const { initWebSocketServer } = require('../ErrorReporting'); initWebSocketServer(); // Servir le log viewer via une route HTTP au lieu d'un fichier local const logViewerUrl = `http://localhost:${this.config.port}/logs-viewer.html`; // Ouvrir dans le navigateur selon l'OS let command, args; switch (os.platform()) { case 'darwin': // macOS command = 'open'; args = [logViewerUrl]; break; case 'win32': // Windows command = 'cmd'; args = ['/c', 'start', logViewerUrl]; break; default: // Linux et WSL // Pour WSL, utiliser explorer.exe de Windows if (process.env.WSL_DISTRO_NAME) { command = '/mnt/c/Windows/System32/cmd.exe'; args = ['/c', 'start', logViewerUrl]; } else { command = 'xdg-open'; args = [logViewerUrl]; } break; } spawn(command, args, { detached: true, stdio: 'ignore' }); const logPort = process.env.LOG_WS_PORT || 8082; logSh(`🌐 Log viewer lancé avec WebSocket sur port ${logPort}`, 'INFO'); res.json({ success: true, message: 'Log viewer lancé', wsPort: logPort, viewerUrl: logViewerUrl, timestamp: new Date().toISOString() }); } catch (error) { logSh(`❌ Erreur lancement log viewer: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: 'Erreur lancement log viewer', message: error.message, timestamp: new Date().toISOString() }); } } // ======================================== // HANDLERS STEP-BY-STEP // ======================================== /** * Initialise une nouvelle session step-by-step */ async handleStepByStepInit(req, res) { try { const { sessionManager } = require('../StepByStepSessionManager'); const inputData = req.body; logSh(`🎯 Initialisation session step-by-step`, 'INFO'); logSh(` Input: ${JSON.stringify(inputData)}`, 'DEBUG'); const session = sessionManager.createSession(inputData); res.json({ success: true, sessionId: session.id, steps: session.steps.map(step => ({ id: step.id, system: step.system, name: step.name, description: step.description, status: step.status })), inputData: session.inputData, timestamp: new Date().toISOString() }); } catch (error) { logSh(`❌ Erreur init step-by-step: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: 'Erreur initialisation session', message: error.message, timestamp: new Date().toISOString() }); } } /** * Exécute une étape */ async handleStepByStepExecute(req, res) { try { const { sessionManager } = require('../StepByStepSessionManager'); const { StepExecutor } = require('../StepExecutor'); const { sessionId, stepId, options = {} } = req.body; if (!sessionId || !stepId) { return res.status(400).json({ success: false, error: 'sessionId et stepId requis', timestamp: new Date().toISOString() }); } logSh(`🚀 Exécution étape ${stepId} pour session ${sessionId}`, 'INFO'); // Récupérer la session const session = sessionManager.getSession(sessionId); // Trouver l'étape const step = session.steps.find(s => s.id === stepId); if (!step) { return res.status(400).json({ success: false, error: `Étape ${stepId} introuvable`, timestamp: new Date().toISOString() }); } // Marquer l'étape comme en cours step.status = 'executing'; // Créer l'exécuteur et lancer l'étape const executor = new StepExecutor(); const result = await executor.executeStep(step.system, session.inputData, options); // Ajouter le résultat à la session sessionManager.addStepResult(sessionId, stepId, result); // Déterminer la prochaine étape const nextStep = session.steps.find(s => s.id === stepId + 1); res.json({ success: true, stepId: stepId, system: step.system, name: step.name, result: { success: result.success, content: result.result, formatted: result.formatted, xmlFormatted: result.xmlFormatted, error: result.error }, stats: result.stats, nextStep: nextStep ? nextStep.id : null, sessionStatus: session.status, timestamp: new Date().toISOString() }); } catch (error) { logSh(`❌ Erreur exécution step-by-step: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: 'Erreur exécution étape', message: error.message, timestamp: new Date().toISOString() }); } } /** * Récupère le status d'une session */ handleStepByStepStatus(req, res) { try { const { sessionManager } = require('../StepByStepSessionManager'); const { sessionId } = req.params; const session = sessionManager.getSession(sessionId); res.json({ success: true, session: { id: session.id, status: session.status, createdAt: new Date(session.createdAt).toISOString(), currentStep: session.currentStep, completedSteps: session.completedSteps, totalSteps: session.steps.length, inputData: session.inputData, steps: session.steps, globalStats: session.globalStats, results: session.results.map(r => ({ stepId: r.stepId, system: r.system, success: r.success, timestamp: new Date(r.timestamp).toISOString(), stats: r.stats })) }, timestamp: new Date().toISOString() }); } catch (error) { logSh(`❌ Erreur status step-by-step: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: 'Erreur récupération status', message: error.message, timestamp: new Date().toISOString() }); } } /** * Reset une session */ handleStepByStepReset(req, res) { try { const { sessionManager } = require('../StepByStepSessionManager'); const { sessionId } = req.body; if (!sessionId) { return res.status(400).json({ success: false, error: 'sessionId requis', timestamp: new Date().toISOString() }); } const session = sessionManager.resetSession(sessionId); res.json({ success: true, sessionId: session.id, message: 'Session reset avec succès', steps: session.steps, timestamp: new Date().toISOString() }); } catch (error) { logSh(`❌ Erreur reset step-by-step: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: 'Erreur reset session', message: error.message, timestamp: new Date().toISOString() }); } } /** * Export les résultats d'une session */ handleStepByStepExport(req, res) { try { const { sessionManager } = require('../StepByStepSessionManager'); const { sessionId } = req.params; const exportData = sessionManager.exportSession(sessionId); res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Disposition', `attachment; filename="step-by-step-${sessionId}.json"`); res.json(exportData); } catch (error) { logSh(`❌ Erreur export step-by-step: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: 'Erreur export session', message: error.message, timestamp: new Date().toISOString() }); } } /** * Liste les sessions actives */ handleStepByStepSessions(req, res) { try { const { sessionManager } = require('../StepByStepSessionManager'); const sessions = sessionManager.listSessions(); res.json({ success: true, sessions: sessions, total: sessions.length, timestamp: new Date().toISOString() }); } catch (error) { logSh(`❌ Erreur list sessions step-by-step: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: 'Erreur récupération sessions', message: error.message, timestamp: new Date().toISOString() }); } } /** * Handler pour récupérer les personnalités disponibles */ async handleGetPersonalities(req, res) { try { const { getPersonalities } = require('../BrainConfig'); const personalities = await getPersonalities(); res.json({ success: true, personalities: personalities || [], total: (personalities || []).length, timestamp: new Date().toISOString() }); } catch (error) { logSh(`❌ Erreur récupération personnalités: ${error.message}`, 'ERROR'); res.status(500).json({ success: false, error: 'Erreur récupération personnalités', message: error.message, timestamp: new Date().toISOString() }); } } // ======================================== // INTERFACE WEB // ======================================== /** * Configure l'interface web */ setupWebInterface() { // Page d'accueil - Dashboard MANUAL this.app.get('/', (req, res) => { res.send(this.generateManualDashboard()); }); // Route pour le log viewer this.app.get('/logs-viewer.html', (req, res) => { const fs = require('fs'); const logViewerPath = path.join(__dirname, '../../tools/logs-viewer.html'); try { const content = fs.readFileSync(logViewerPath, 'utf-8'); res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.send(content); } catch (error) { logSh(`❌ Erreur lecture log viewer: ${error.message}`, 'ERROR'); res.status(500).send(`Erreur: ${error.message}`); } }); // 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 `
Interface Client + API + Tests Modulaires
Interface avancée pour tester toutes les combinaisons modulaires avec logs temps réel.
🚀 Ouvrir Interface Test ⚡ Interface Step-by-Step 📋 Configuration APILogs temps réel sur ws://localhost:${this.config.wsPort}