// ========================================
// 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');
const { APIController } = require('../APIController');
/**
* 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;
this.apiController = new APIController();
}
// ========================================
// 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);
});
// 🆕 API simple pour générer un article avec mot-clé
this.app.post('/api/generate-simple', async (req, res) => {
await this.handleGenerateSimple(req, res);
});
// ========================================
// 🚀 NOUVEAUX ENDPOINTS API RESTful
// ========================================
// === GESTION ARTICLES ===
this.app.get('/api/articles', async (req, res) => {
await this.apiController.getArticles(req, res);
});
this.app.get('/api/articles/:id', async (req, res) => {
await this.apiController.getArticle(req, res);
});
this.app.post('/api/articles', async (req, res) => {
await this.apiController.createArticle(req, res);
});
// === GESTION PROJETS ===
this.app.get('/api/projects', async (req, res) => {
await this.apiController.getProjects(req, res);
});
this.app.post('/api/projects', async (req, res) => {
await this.apiController.createProject(req, res);
});
// === GESTION TEMPLATES ===
this.app.get('/api/templates', async (req, res) => {
await this.apiController.getTemplates(req, res);
});
this.app.post('/api/templates', async (req, res) => {
await this.apiController.createTemplate(req, res);
});
// === CONFIGURATION ===
this.app.get('/api/config/personalities', async (req, res) => {
await this.apiController.getPersonalitiesConfig(req, res);
});
// === MONITORING ===
this.app.get('/api/health', async (req, res) => {
await this.apiController.getHealth(req, res);
});
this.app.get('/api/metrics', async (req, res) => {
await this.apiController.getMetrics(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();
logSh(`🚀 Execution step ${step.system} avec données: ${JSON.stringify(session.inputData)}`, 'DEBUG');
// Récupérer le contenu de l'étape précédente pour chaînage
let inputContent = null;
if (stepId > 1) {
const previousResult = session.results.find(r => r.stepId === stepId - 1);
logSh(`🔍 DEBUG Chaînage: previousResult=${!!previousResult}`, 'DEBUG');
if (previousResult) {
logSh(`🔍 DEBUG Chaînage: previousResult.result=${!!previousResult.result}`, 'DEBUG');
if (previousResult.result) {
// StepExecutor retourne un objet avec une propriété 'content'
if (previousResult.result.content) {
inputContent = previousResult.result.content;
logSh(`🔄 Chaînage: utilisation contenu.content étape ${stepId - 1}`, 'DEBUG');
} else {
// Fallback si c'est juste le contenu directement
inputContent = previousResult.result;
logSh(`🔄 Chaînage: utilisation contenu direct étape ${stepId - 1}`, 'DEBUG');
}
logSh(`🔍 DEBUG: inputContent type=${typeof inputContent}, keys=${Object.keys(inputContent || {})}`, 'DEBUG');
} else {
logSh(`🚨 DEBUG: previousResult.result est vide ou null !`, 'ERROR');
}
} else {
logSh(`🚨 DEBUG: Pas de previousResult trouvé pour stepId=${stepId - 1}`, 'ERROR');
}
}
// Ajouter le contenu d'entrée aux options si disponible
const executionOptions = {
...options,
inputContent: inputContent
};
const result = await executor.executeStep(step.system, session.inputData, executionOptions);
logSh(`📊 Résultat step ${step.system}: success=${result.success}, content=${Object.keys(result.content || {}).length} éléments, duration=${result.stats?.duration}ms`, 'INFO');
// Si pas d'erreur mais temps < 100ms, forcer une erreur pour debug
if (result.success && result.stats?.duration < 100) {
logSh(`⚠️ WARN: Step trop rapide (${result.stats?.duration}ms), probablement pas d'appel LLM réel`, 'WARN');
result.debugWarning = `⚠️ Exécution suspecte: ${result.stats?.duration}ms (probablement pas d'appel LLM)`;
}
// 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,
debugWarning: result.debugWarning
},
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()
});
}
}
/**
* 🆕 Handler pour génération simple d'article avec mot-clé
*/
async handleGenerateSimple(req, res) {
try {
const { keyword } = req.body;
// Validation basique
if (!keyword || typeof keyword !== 'string' || keyword.trim().length === 0) {
return res.status(400).json({
success: false,
error: 'Mot-clé requis',
message: 'Le paramètre "keyword" est obligatoire et doit être une chaîne non vide'
});
}
const cleanKeyword = keyword.trim();
logSh(`🎯 Génération simple pour mot-clé: "${cleanKeyword}"`, 'INFO');
// Créer un template XML simple basé sur le mot-clé
const simpleTemplate = `
|Titre_Principal{{${cleanKeyword}}}{Rédige un titre H1 accrocheur pour "${cleanKeyword}"}|
|Sous_Titre_1{{${cleanKeyword}}}{Rédige un sous-titre H2 pour "${cleanKeyword}"}|
|Sous_Titre_2{{${cleanKeyword}}}{Rédige un autre sous-titre H2 pour "${cleanKeyword}"}|
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}