seo-generator-server/lib/modes/ModeManager.js
StillHammer 64fb319e65 refactor: Synchronisation complète du codebase - Application de tous les patches
Application systématique et méthodique de tous les patches historiques.

##  FICHIERS SYNCHRONISÉS (19 fichiers)

### Core & Infrastructure:
- server.js (14 patches) - Lazy loading ModeManager, SIGINT hard kill, timing logs
- ModeManager.js (4 patches) - Instrumentation complète avec timing détaillé

### Pipeline System:
- PipelineDefinition.js (6 patches) - Source unique getLLMProvidersList()
- pipeline-builder.js (8 patches) - Standardisation LLM providers
- pipeline-runner.js (6 patches) - Affichage résultats structurés + debug console
- pipeline-builder.html (2 patches) - Fallback providers synchronisés
- pipeline-runner.html (3 patches) - UI améliorée résultats

### Enhancement Layers:
- TechnicalLayer.js (1 patch) - defaultLLM: 'gpt-4o-mini'
- StyleLayer.js (1 patch) - Type safety vocabulairePref
- PatternBreakingCore.js (1 patch) - Mapping modifications
- PatternBreakingLayers.js (1 patch) - LLM standardisé

### Validators & Tests:
- QualityMetrics.js (1 patch) - callLLM('gpt-4o-mini')
- PersonalityValidator.js (1 patch) - Provider gpt-4o-mini
- AntiDetectionValidator.js - Synchronisé

### Documentation:
- TODO.md (1 patch) - Section LiteLLM pour tracking coûts
- CLAUDE.md - Documentation à jour

### Tools:
- tools/analyze-skipped-exports.js (nouveau)
- tools/apply-claude-exports.js (nouveau)
- tools/apply-claude-exports-fuzzy.js (nouveau)

## 🎯 Changements principaux:
-  Standardisation LLM providers (openai → gpt-4o-mini, claude → claude-sonnet-4-5)
-  Lazy loading optimisé (ModeManager chargé à la demande)
-  SIGINT immediate exit (pas de graceful shutdown)
-  Type safety renforcé (conversions string explicites)
-  Instrumentation timing complète
-  Debug logging amélioré (console.log résultats pipeline)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 20:36:17 +08:00

504 lines
14 KiB
JavaScript

// ========================================
// FICHIER: ModeManager.js
// RESPONSABILITÉ: Gestionnaire modes exclusifs serveur
// MODES: MANUAL (interface client) | AUTO (traitement batch GSheets)
// ========================================
const { logSh } = require('../ErrorReporting');
const fs = require('fs');
const path = require('path');
/**
* GESTIONNAIRE MODES EXCLUSIFS
* Gère le basculement entre mode MANUAL et AUTO de façon exclusive
*/
class ModeManager {
// ========================================
// CONSTANTES ET ÉTAT
// ========================================
static MODES = {
MANUAL: 'manual', // Interface client + API + WebSocket
AUTO: 'auto' // Traitement batch Google Sheets
};
static currentMode = null;
static isLocked = false;
static lockReason = null;
static modeStartTime = null;
static activeServices = {
manualServer: null,
autoProcessor: null,
websocketServer: null
};
// Stats par mode
static stats = {
manual: { sessions: 0, requests: 0, lastActivity: null },
auto: { processed: 0, errors: 0, lastProcessing: null }
};
// ========================================
// INITIALISATION ET DÉTECTION MODE
// ========================================
/**
* Initialise le gestionnaire de modes
* @param {string} initialMode - Mode initial (manual|auto|detect)
*/
static async initialize(initialMode = 'detect') {
const startTime = Date.now();
logSh('🎛️ Initialisation ModeManager...', 'INFO');
try {
// Détecter mode selon arguments ou config
logSh('⏱️ Détection mode...', 'INFO');
const t1 = Date.now();
const detectedMode = this.detectIntendedMode(initialMode);
logSh(`✓ Mode détecté: ${detectedMode.toUpperCase()} en ${Date.now() - t1}ms`, 'INFO');
// Nettoyer état précédent si nécessaire
logSh('⏱️ Nettoyage état précédent...', 'INFO');
const t2 = Date.now();
await this.cleanupPreviousState();
logSh(`✓ Nettoyage terminé en ${Date.now() - t2}ms`, 'INFO');
// Basculer vers le mode détecté
logSh(`⏱️ Basculement vers mode ${detectedMode.toUpperCase()}...`, 'INFO');
const t3 = Date.now();
await this.switchToMode(detectedMode);
logSh(`✓ Basculement terminé en ${Date.now() - t3}ms`, 'INFO');
// Sauvegarder état
logSh('⏱️ Sauvegarde état...', 'INFO');
const t4 = Date.now();
this.saveModeState();
logSh(`✓ État sauvegardé en ${Date.now() - t4}ms`, 'INFO');
logSh(`✅ ModeManager initialisé en mode ${this.currentMode.toUpperCase()} (total: ${Date.now() - startTime}ms)`, 'INFO');
return this.currentMode;
} catch (error) {
logSh(`❌ Erreur initialisation ModeManager: ${error.message}`, 'ERROR');
throw new Error(`Échec initialisation ModeManager: ${error.message}`);
}
}
/**
* Détecte le mode souhaité selon arguments CLI et env
*/
static detectIntendedMode(initialMode) {
// 1. Argument explicite
if (initialMode === this.MODES.MANUAL || initialMode === this.MODES.AUTO) {
return initialMode;
}
// 2. Arguments de ligne de commande
const args = process.argv.slice(2);
const modeArg = args.find(arg => arg.startsWith('--mode='));
if (modeArg) {
const mode = modeArg.split('=')[1];
if (Object.values(this.MODES).includes(mode)) {
return mode;
}
}
// 3. Variable d'environnement
const envMode = process.env.SERVER_MODE?.toLowerCase();
if (Object.values(this.MODES).includes(envMode)) {
return envMode;
}
// 4. Script npm spécifique
const npmScript = process.env.npm_lifecycle_event;
if (npmScript === 'auto') return this.MODES.AUTO;
// 5. Défaut = MANUAL
return this.MODES.MANUAL;
}
// ========================================
// CHANGEMENT DE MODES
// ========================================
/**
* Bascule vers un mode spécifique
*/
static async switchToMode(targetMode, force = false) {
if (!Object.values(this.MODES).includes(targetMode)) {
throw new Error(`Mode invalide: ${targetMode}`);
}
if (this.currentMode === targetMode) {
logSh(`Mode ${targetMode} déjà actif`, 'DEBUG');
return true;
}
// Vérifier si changement possible
if (!force && !await this.canSwitchToMode(targetMode)) {
throw new Error(`Impossible de basculer vers ${targetMode}: ${this.lockReason}`);
}
logSh(`🔄 Basculement ${this.currentMode || 'NONE'}${targetMode}...`, 'INFO');
try {
// Arrêter mode actuel
await this.stopCurrentMode();
// Démarrer nouveau mode
await this.startMode(targetMode);
// Mettre à jour état
this.currentMode = targetMode;
this.modeStartTime = Date.now();
this.lockReason = null;
logSh(`✅ Basculement terminé: Mode ${targetMode.toUpperCase()} actif`, 'INFO');
return true;
} catch (error) {
logSh(`❌ Échec basculement vers ${targetMode}: ${error.message}`, 'ERROR');
// Tentative de récupération
try {
await this.emergencyRecovery();
} catch (recoveryError) {
logSh(`❌ Échec récupération d'urgence: ${recoveryError.message}`, 'ERROR');
}
throw error;
}
}
/**
* Vérifie si le basculement est possible
*/
static async canSwitchToMode(targetMode) {
// Mode verrouillé
if (this.isLocked) {
this.lockReason = 'Mode verrouillé pour opération critique';
return false;
}
// Vérifications spécifiques par mode
switch (targetMode) {
case this.MODES.MANUAL:
return await this.canSwitchToManual();
case this.MODES.AUTO:
return await this.canSwitchToAuto();
default:
return false;
}
}
/**
* Peut-on basculer vers MANUAL ?
*/
static async canSwitchToManual() {
// Si mode AUTO actif, vérifier processus
if (this.currentMode === this.MODES.AUTO) {
const autoProcessor = this.activeServices.autoProcessor;
if (autoProcessor && autoProcessor.isProcessing()) {
this.lockReason = 'Traitement automatique en cours, arrêt requis';
return false;
}
}
return true;
}
/**
* Peut-on basculer vers AUTO ?
*/
static async canSwitchToAuto() {
// Si mode MANUAL actif, vérifier clients
if (this.currentMode === this.MODES.MANUAL) {
const manualServer = this.activeServices.manualServer;
if (manualServer && manualServer.hasActiveClients()) {
this.lockReason = 'Clients actifs en mode MANUAL, déconnexion requise';
return false;
}
}
return true;
}
// ========================================
// DÉMARRAGE ET ARRÊT SERVICES
// ========================================
/**
* Démarre un mode spécifique
*/
static async startMode(mode) {
logSh(`🚀 Démarrage mode ${mode.toUpperCase()}...`, 'DEBUG');
switch (mode) {
case this.MODES.MANUAL:
await this.startManualMode();
break;
case this.MODES.AUTO:
await this.startAutoMode();
break;
default:
throw new Error(`Mode de démarrage inconnu: ${mode}`);
}
}
/**
* Démarre le mode MANUAL
*/
static async startManualMode() {
const t1 = Date.now();
logSh('⏱️ Chargement module ManualServer...', 'INFO');
const { ManualServer } = require('./ManualServer');
logSh(`✓ ManualServer chargé en ${Date.now() - t1}ms`, 'INFO');
const t2 = Date.now();
logSh('⏱️ Instanciation ManualServer...', 'INFO');
this.activeServices.manualServer = new ManualServer();
logSh(`✓ ManualServer instancié en ${Date.now() - t2}ms`, 'INFO');
const t3 = Date.now();
logSh('⏱️ Démarrage ManualServer.start()...', 'INFO');
await this.activeServices.manualServer.start();
logSh(`✓ ManualServer.start() terminé en ${Date.now() - t3}ms`, 'INFO');
logSh('✅ Mode MANUAL démarré', 'INFO');
}
/**
* Démarre le mode AUTO
*/
static async startAutoMode() {
const { AutoProcessor } = require('./AutoProcessor');
logSh('🤖 Démarrage AutoProcessor...', 'DEBUG');
this.activeServices.autoProcessor = new AutoProcessor();
await this.activeServices.autoProcessor.start();
logSh('✅ Mode AUTO démarré', 'DEBUG');
}
/**
* Arrête le mode actuel
*/
static async stopCurrentMode() {
if (!this.currentMode) return;
logSh(`🛑 Arrêt mode ${this.currentMode.toUpperCase()}...`, 'DEBUG');
try {
switch (this.currentMode) {
case this.MODES.MANUAL:
await this.stopManualMode();
break;
case this.MODES.AUTO:
await this.stopAutoMode();
break;
}
logSh(`✅ Mode ${this.currentMode.toUpperCase()} arrêté`, 'DEBUG');
} catch (error) {
logSh(`⚠️ Erreur arrêt mode ${this.currentMode}: ${error.message}`, 'WARNING');
// Continue malgré l'erreur pour permettre le changement
}
}
/**
* Arrête le mode MANUAL
*/
static async stopManualMode() {
if (this.activeServices.manualServer) {
await this.activeServices.manualServer.stop();
this.activeServices.manualServer = null;
}
}
/**
* Arrête le mode AUTO
*/
static async stopAutoMode() {
if (this.activeServices.autoProcessor) {
await this.activeServices.autoProcessor.stop();
this.activeServices.autoProcessor = null;
}
}
// ========================================
// ÉTAT ET MONITORING
// ========================================
/**
* État actuel du gestionnaire
*/
static getStatus() {
return {
currentMode: this.currentMode,
isLocked: this.isLocked,
lockReason: this.lockReason,
modeStartTime: this.modeStartTime,
uptime: this.modeStartTime ? Date.now() - this.modeStartTime : 0,
stats: { ...this.stats },
activeServices: {
manualServer: !!this.activeServices.manualServer,
autoProcessor: !!this.activeServices.autoProcessor
}
};
}
/**
* Vérifie si mode MANUAL actif
*/
static isManualMode() {
return this.currentMode === this.MODES.MANUAL;
}
/**
* Vérifie si mode AUTO actif
*/
static isAutoMode() {
return this.currentMode === this.MODES.AUTO;
}
/**
* Verrouille le mode actuel
*/
static lockMode(reason = 'Opération critique') {
this.isLocked = true;
this.lockReason = reason;
logSh(`🔒 Mode ${this.currentMode} verrouillé: ${reason}`, 'INFO');
}
/**
* Déverrouille le mode
*/
static unlockMode() {
this.isLocked = false;
this.lockReason = null;
logSh(`🔓 Mode ${this.currentMode} déverrouillé`, 'INFO');
}
// ========================================
// GESTION ERREURS ET RÉCUPÉRATION
// ========================================
/**
* Nettoyage état précédent
*/
static async cleanupPreviousState() {
logSh('🧹 Nettoyage état précédent...', 'DEBUG');
// Arrêter tous les services actifs
await this.stopCurrentMode();
// Reset état
this.isLocked = false;
this.lockReason = null;
logSh('✅ Nettoyage terminé', 'DEBUG');
}
/**
* Récupération d'urgence
*/
static async emergencyRecovery() {
logSh('🚨 Récupération d\'urgence...', 'WARNING');
try {
// Forcer arrêt de tous les services
await this.forceStopAllServices();
// Reset état complet
this.currentMode = null;
this.isLocked = false;
this.lockReason = null;
this.modeStartTime = null;
logSh('✅ Récupération d\'urgence terminée', 'INFO');
} catch (error) {
logSh(`❌ Échec récupération d'urgence: ${error.message}`, 'ERROR');
throw error;
}
}
/**
* Arrêt forcé de tous les services
*/
static async forceStopAllServices() {
const services = Object.keys(this.activeServices);
for (const serviceKey of services) {
const service = this.activeServices[serviceKey];
if (service) {
try {
if (typeof service.stop === 'function') {
await service.stop();
}
} catch (error) {
logSh(`⚠️ Erreur arrêt forcé ${serviceKey}: ${error.message}`, 'WARNING');
}
this.activeServices[serviceKey] = null;
}
}
}
// ========================================
// PERSISTANCE ET CONFIGURATION
// ========================================
/**
* Sauvegarde l'état du mode
*/
static saveModeState() {
try {
const stateFile = path.join(__dirname, '../..', 'mode-state.json');
const state = {
currentMode: this.currentMode,
modeStartTime: this.modeStartTime,
stats: this.stats,
timestamp: new Date().toISOString()
};
fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
} catch (error) {
logSh(`⚠️ Erreur sauvegarde état mode: ${error.message}`, 'WARNING');
}
}
/**
* Restaure l'état du mode
*/
static loadModeState() {
try {
const stateFile = path.join(__dirname, '../..', 'mode-state.json');
if (fs.existsSync(stateFile)) {
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
this.stats = state.stats || this.stats;
return state;
}
} catch (error) {
logSh(`⚠️ Erreur chargement état mode: ${error.message}`, 'WARNING');
}
return null;
}
}
// ============= EXPORTS =============
module.exports = { ModeManager };