seo-generator-server/lib/StepByStepSessionManager.js

364 lines
9.1 KiB
JavaScript

// ========================================
// FICHIER: StepByStepSessionManager.js
// RESPONSABILITÉ: Gestion des sessions step-by-step
// ========================================
// Pas besoin d'uuid externe, on utilise notre générateur simple
const { logSh } = require('./ErrorReporting');
/**
* GESTIONNAIRE DE SESSIONS STEP-BY-STEP
* Gère les sessions de test modulaire pas-à-pas avec TTL
*/
class StepByStepSessionManager {
constructor() {
this.sessions = new Map();
this.TTL = 30 * 60 * 1000; // 30 minutes
// Nettoyage automatique toutes les 5 minutes
setInterval(() => this.cleanupExpiredSessions(), 5 * 60 * 1000);
logSh('🎯 SessionManager initialisé', 'DEBUG');
}
// ========================================
// GESTION DES SESSIONS
// ========================================
/**
* Crée une nouvelle session
*/
createSession(inputData) {
const sessionId = this.generateUUID();
const session = {
id: sessionId,
createdAt: Date.now(),
lastAccessedAt: Date.now(),
inputData: this.validateInputData(inputData),
currentStep: 0,
completedSteps: [],
results: [],
globalStats: {
totalDuration: 0,
totalTokens: 0,
totalCost: 0,
llmCalls: [],
startTime: Date.now(),
endTime: null
},
steps: this.generateStepsList(),
status: 'initialized'
};
this.sessions.set(sessionId, session);
logSh(`✅ Session créée: ${sessionId}`, 'INFO');
return session;
}
/**
* Récupère une session
*/
getSession(sessionId) {
const session = this.sessions.get(sessionId);
if (!session) {
throw new Error(`Session introuvable: ${sessionId}`);
}
if (this.isSessionExpired(session)) {
this.deleteSession(sessionId);
throw new Error(`Session expirée: ${sessionId}`);
}
session.lastAccessedAt = Date.now();
return session;
}
/**
* Met à jour une session
*/
updateSession(sessionId, updates) {
const session = this.getSession(sessionId);
Object.assign(session, updates);
session.lastAccessedAt = Date.now();
logSh(`📝 Session mise à jour: ${sessionId}`, 'DEBUG');
return session;
}
/**
* Supprime une session
*/
deleteSession(sessionId) {
const deleted = this.sessions.delete(sessionId);
if (deleted) {
logSh(`🗑️ Session supprimée: ${sessionId}`, 'INFO');
}
return deleted;
}
/**
* Liste toutes les sessions actives
*/
listSessions() {
const sessions = [];
for (const [id, session] of this.sessions) {
if (!this.isSessionExpired(session)) {
sessions.push({
id: session.id,
createdAt: session.createdAt,
status: session.status,
currentStep: session.currentStep,
totalSteps: session.steps.length,
inputData: {
mc0: session.inputData.mc0,
personality: session.inputData.personality
}
});
}
}
return sessions;
}
// ========================================
// GESTION DES ÉTAPES
// ========================================
/**
* Ajoute le résultat d'une étape
*/
addStepResult(sessionId, stepId, result) {
const session = this.getSession(sessionId);
// Marquer l'étape comme complétée
if (!session.completedSteps.includes(stepId)) {
session.completedSteps.push(stepId);
}
// Ajouter le résultat
const stepResult = {
stepId: stepId,
system: result.system,
timestamp: Date.now(),
success: result.success,
result: result.result || null,
error: result.error || null,
stats: result.stats || {},
formatted: result.formatted || null
};
session.results.push(stepResult);
// Mettre à jour les stats globales
this.updateGlobalStats(session, result.stats || {});
// Mettre à jour le statut de l'étape
const step = session.steps.find(s => s.id === stepId);
if (step) {
step.status = result.success ? 'completed' : 'error';
step.duration = (result.stats && result.stats.duration) || 0;
step.error = result.error || null;
}
// Mettre à jour currentStep si nécessaire
if (stepId > session.currentStep) {
session.currentStep = stepId;
}
logSh(`📊 Résultat étape ${stepId} ajouté à session ${sessionId}`, 'DEBUG');
return session;
}
/**
* Obtient le résultat d'une étape
*/
getStepResult(sessionId, stepId) {
const session = this.getSession(sessionId);
return session.results.find(r => r.stepId === stepId) || null;
}
/**
* Reset une session
*/
resetSession(sessionId) {
const session = this.getSession(sessionId);
session.currentStep = 0;
session.completedSteps = [];
session.results = [];
session.globalStats = {
totalDuration: 0,
totalTokens: 0,
totalCost: 0,
llmCalls: [],
startTime: Date.now(),
endTime: null
};
session.steps = this.generateStepsList();
session.status = 'initialized';
logSh(`🔄 Session reset: ${sessionId}`, 'INFO');
return session;
}
// ========================================
// HELPERS PRIVÉS
// ========================================
/**
* Génère un UUID simple
*/
generateUUID() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
/**
* Valide les données d'entrée
*/
validateInputData(inputData) {
const validated = {
mc0: inputData.mc0 || 'mot-clé principal',
t0: inputData.t0 || 'titre principal',
mcPlus1: inputData.mcPlus1 || '',
tPlus1: inputData.tPlus1 || '',
personality: inputData.personality || 'random',
tMinus1: inputData.tMinus1 || '',
xmlTemplate: inputData.xmlTemplate || null
};
return validated;
}
/**
* Génère la liste des étapes
*/
generateStepsList() {
return [
{
id: 1,
system: 'initial-generation',
name: 'Initial Generation',
description: 'Génération de contenu initial avec Claude',
status: 'pending',
duration: 0,
error: null
},
{
id: 2,
system: 'selective',
name: 'Selective Enhancement',
description: 'Amélioration sélective (Technique → Transitions → Style)',
status: 'pending',
duration: 0,
error: null
},
{
id: 3,
system: 'adversarial',
name: 'Adversarial Generation',
description: 'Génération adversariale anti-détection',
status: 'pending',
duration: 0,
error: null
},
{
id: 4,
system: 'human-simulation',
name: 'Human Simulation',
description: 'Simulation comportements humains',
status: 'pending',
duration: 0,
error: null
},
{
id: 5,
system: 'pattern-breaking',
name: 'Pattern Breaking',
description: 'Cassage de patterns IA',
status: 'pending',
duration: 0,
error: null
}
];
}
/**
* Met à jour les statistiques globales
*/
updateGlobalStats(session, stepStats) {
const global = session.globalStats;
global.totalDuration += stepStats.duration || 0;
global.totalTokens += stepStats.tokensUsed || 0;
global.totalCost += stepStats.cost || 0;
if (stepStats.llmCalls && Array.isArray(stepStats.llmCalls)) {
global.llmCalls.push(...stepStats.llmCalls);
}
// Marquer la fin si toutes les étapes sont complétées
if (session.completedSteps.length === session.steps.length) {
global.endTime = Date.now();
session.status = 'completed';
}
}
/**
* Vérifie si une session est expirée
*/
isSessionExpired(session) {
return (Date.now() - session.lastAccessedAt) > this.TTL;
}
/**
* Nettoie les sessions expirées
*/
cleanupExpiredSessions() {
let cleaned = 0;
for (const [id, session] of this.sessions) {
if (this.isSessionExpired(session)) {
this.sessions.delete(id);
cleaned++;
}
}
if (cleaned > 0) {
logSh(`🧹 ${cleaned} sessions expirées nettoyées`, 'DEBUG');
}
}
// ========================================
// EXPORT/IMPORT
// ========================================
/**
* Exporte une session au format JSON
*/
exportSession(sessionId) {
const session = this.getSession(sessionId);
return {
session: {
id: session.id,
createdAt: new Date(session.createdAt).toISOString(),
inputData: session.inputData,
results: session.results,
globalStats: session.globalStats,
steps: session.steps.map(step => ({
...step,
duration: step.duration ? `${step.duration}ms` : '0ms'
}))
},
exportedAt: new Date().toISOString(),
version: '1.0.0'
};
}
}
// Instance singleton
const sessionManager = new StepByStepSessionManager();
module.exports = {
StepByStepSessionManager,
sessionManager
};