- Add comprehensive documentation (IMPLEMENTATION_COMPLETE, ProductionReady, QUICK_START, STARTUP_ANALYSIS) - Add startup scripts (start-server.sh, start-server.bat, check-setup.sh) - Add configs directory structure with README - Add ValidationGuards and Main.js backup - Add LLM monitoring HTML interface - Add cache templates and XML files - Add technical report (rapport_technique.md) - Add bundled code.js 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1622 lines
47 KiB
Markdown
1622 lines
47 KiB
Markdown
# 🚀 Production Ready - Interface Configuration & Runner
|
|
|
|
**Date**: 2025-10-08
|
|
**Version**: 1.0.0
|
|
**Objectif**: Créer une interface complète pour configurer, sauvegarder et exécuter des workflows modulaires de production
|
|
|
|
---
|
|
|
|
## 📋 Table des Matières
|
|
|
|
1. [Vue d'Ensemble](#vue-densemble)
|
|
2. [Architecture Proposée](#architecture-proposée)
|
|
3. [Architecture Modulaire Actuelle](#architecture-modulaire-actuelle)
|
|
4. [Composants à Créer](#composants-à-créer)
|
|
5. [Backend - Nouveaux Endpoints](#backend---nouveaux-endpoints)
|
|
6. [Schéma des Données](#schéma-des-données)
|
|
7. [Design System](#design-system)
|
|
8. [JavaScript - Fonctions Clés](#javascript---fonctions-clés)
|
|
9. [Plan d'Exécution Séquentiel](#plan-dexécution-séquentiel)
|
|
10. [Tests à Effectuer](#tests-à-effectuer)
|
|
11. [Notes Importantes](#notes-importantes)
|
|
|
|
---
|
|
|
|
## 🎯 Vue d'Ensemble
|
|
|
|
### Problématique
|
|
|
|
Actuellement, l'interface `test-modulaire.html` permet de tester des configurations modulaires, mais :
|
|
- ❌ Pas de sauvegarde des configurations testées
|
|
- ❌ Pas de réutilisation facile des configs
|
|
- ❌ Pas d'interface dédiée pour la production (Google Sheets)
|
|
|
|
### Solution Proposée
|
|
|
|
Créer **3 pages web** interconnectées :
|
|
|
|
1. **`index.html`** - Page d'accueil avec navigation
|
|
2. **`config-editor.html`** - Éditeur de configuration avec Save/Load/Delete
|
|
3. **`production-runner.html`** - Runner de production avec logs temps réel
|
|
|
|
---
|
|
|
|
## 🏗️ Architecture Proposée
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ INDEX.HTML │
|
|
│ (Page d'accueil) │
|
|
│ │
|
|
│ ┌──────────────────────┐ ┌────────────────────────┐ │
|
|
│ │ 🔧 Configuration │ │ 🚀 Production Run │ │
|
|
│ │ Editor │ │ Runner │ │
|
|
│ └──────────────────────┘ └────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
│ │
|
|
▼ ▼
|
|
┌────────────────────┐ ┌──────────────────────┐
|
|
│ config-editor.html │ │ production-runner.html│
|
|
│ │ │ │
|
|
│ - Éditer config │ │ - Load config │
|
|
│ - Save/Load │ │ - Select GSheet row │
|
|
│ - Test live │ │ - Run production │
|
|
│ - Delete │ │ - View results │
|
|
└────────────────────┘ └──────────────────────┘
|
|
```
|
|
|
|
### Flux de Travail
|
|
|
|
1. **Création Config** → `config-editor.html`
|
|
- Éditer les 4 couches modulaires
|
|
- Tester en direct
|
|
- Sauvegarder avec nom
|
|
|
|
2. **Exécution Production** → `production-runner.html`
|
|
- Charger config sauvegardée
|
|
- Sélectionner ligne Google Sheets
|
|
- Lancer workflow complet
|
|
- Voir résultats + lien GSheets
|
|
|
|
---
|
|
|
|
## 📐 Architecture Modulaire Actuelle
|
|
|
|
### 4 Couches Modulaires
|
|
|
|
| Couche | Stacks Disponibles | Total |
|
|
|--------|-------------------|-------|
|
|
| **Selective Enhancement** | `lightEnhancement`, `standardEnhancement`, `fullEnhancement`, `personalityFocus`, `fluidityFocus`, `adaptive` | **6** |
|
|
| **Adversarial Generation** | `none`, `light`, `standard`, `heavy`, `adaptive` | **5** |
|
|
| **Human Simulation** | `none`, `lightSimulation`, `standardSimulation`, `heavySimulation`, `adaptiveSimulation`, `personalityFocus`, `temporalFocus` | **7** |
|
|
| **Pattern Breaking** | `none`, `lightPatternBreaking`, `standardPatternBreaking`, `heavyPatternBreaking`, `adaptivePatternBreaking`, `syntaxFocus`, `connectorsFocus` | **7** |
|
|
|
|
### Configuration Standard Actuelle
|
|
|
|
```javascript
|
|
{
|
|
rowNumber: 2,
|
|
selectiveStack: 'standardEnhancement',
|
|
adversarialMode: 'light',
|
|
humanSimulationMode: 'none',
|
|
patternBreakingMode: 'none',
|
|
saveIntermediateSteps: true,
|
|
source: 'web_interface'
|
|
}
|
|
```
|
|
|
|
### Fichiers Modulaires Existants
|
|
|
|
- `lib/selective-enhancement/SelectiveLayers.js` - 6 stacks prédéfinis
|
|
- `lib/adversarial-generation/AdversarialLayers.js` - 5 modes défense
|
|
- `lib/human-simulation/HumanSimulationLayers.js` - 6 modes simulation
|
|
- `lib/pattern-breaking/PatternBreakingLayers.js` - 7 modes cassage patterns
|
|
|
|
---
|
|
|
|
## 🔧 Composants à Créer
|
|
|
|
### 1. Page d'Accueil : `public/index.html`
|
|
|
|
**Fonctionnalités :**
|
|
- Design simple et clean avec 2 grandes cards cliquables
|
|
- Navigation vers config-editor.html ou production-runner.html
|
|
- Affichage du status serveur (API `/api/status`)
|
|
- Statistiques rapides (nombre de configs sauvegardées, dernière exécution)
|
|
|
|
**Structure HTML :**
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>SEO Generator - Accueil</title>
|
|
<style>
|
|
/* Design system (voir section Design System) */
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>🎯 SEO Generator - Dashboard</h1>
|
|
<div class="server-status"></div>
|
|
</header>
|
|
|
|
<main>
|
|
<div class="card-container">
|
|
<!-- Card 1: Configuration Editor -->
|
|
<div class="card" onclick="navigateTo('config-editor.html')">
|
|
<div class="card-icon">🔧</div>
|
|
<h2>Éditeur de Configuration</h2>
|
|
<p>Créer, éditer et sauvegarder des configurations modulaires</p>
|
|
<ul>
|
|
<li>4 couches configurables</li>
|
|
<li>Save/Load configs</li>
|
|
<li>Test en direct</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Card 2: Production Runner -->
|
|
<div class="card" onclick="navigateTo('production-runner.html')">
|
|
<div class="card-icon">🚀</div>
|
|
<h2>Runner de Production</h2>
|
|
<p>Exécuter un workflow complet sur Google Sheets</p>
|
|
<ul>
|
|
<li>Load config sauvegardée</li>
|
|
<li>Sélection ligne GSheet</li>
|
|
<li>Logs temps réel</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-panel">
|
|
<h3>📊 Statistiques</h3>
|
|
<div class="stats-grid">
|
|
<div class="stat-item">
|
|
<span class="stat-value" id="configCount">-</span>
|
|
<span class="stat-label">Configs sauvegardées</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-value" id="lastRun">-</span>
|
|
<span class="stat-label">Dernière exécution</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
function navigateTo(page) {
|
|
window.location.href = page;
|
|
}
|
|
|
|
async function loadStats() {
|
|
try {
|
|
const statusResponse = await fetch('/api/status');
|
|
const statusData = await statusResponse.json();
|
|
|
|
const configResponse = await fetch('/api/config/list');
|
|
const configData = await configResponse.json();
|
|
|
|
document.getElementById('configCount').textContent = configData.count || 0;
|
|
|
|
const uptime = Math.floor(statusData.uptime / 1000);
|
|
document.getElementById('lastRun').textContent = `${uptime}s ago`;
|
|
} catch (error) {
|
|
console.error('Erreur chargement stats:', error);
|
|
}
|
|
}
|
|
|
|
window.onload = loadStats;
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
**API Calls :**
|
|
- `GET /api/status` : Status serveur + uptime
|
|
- `GET /api/config/list` : Nombre de configs sauvegardées
|
|
|
|
---
|
|
|
|
### 2. Éditeur de Config : `public/config-editor.html`
|
|
|
|
**Fonctionnalités :**
|
|
- **Formulaire complet** : 4 couches modulaires (selects inspirés de test-modulaire.html)
|
|
- **Ligne GSheet** : Input pour rowNumber
|
|
- **Actions :**
|
|
- 💾 **Save Config** : Sauvegarder avec nom personnalisé
|
|
- 📂 **Load Config** : Charger depuis liste déroulante
|
|
- 🗑️ **Delete Config** : Supprimer une config existante
|
|
- 🚀 **Test Live** : Tester la config immédiatement (comme test-modulaire.html)
|
|
- **Logs Temps Réel** : Container avec WebSocket (comme test-modulaire.html)
|
|
- **Preview Config** : Afficher JSON de la config actuelle
|
|
|
|
**Structure HTML Clé :**
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Éditeur de Configuration</title>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>🔧 Éditeur de Configuration Modulaire</h1>
|
|
<a href="index.html" class="btn-back">← Retour Accueil</a>
|
|
</header>
|
|
|
|
<div class="editor-container">
|
|
<!-- Section Configuration -->
|
|
<div class="config-section">
|
|
<h2>⚙️ Configuration Modulaire</h2>
|
|
|
|
<!-- Row Number -->
|
|
<div class="form-group">
|
|
<label>Ligne Google Sheet</label>
|
|
<input type="number" id="rowNumber" value="2" min="2">
|
|
</div>
|
|
|
|
<!-- Selective Enhancement -->
|
|
<div class="module-group">
|
|
<h3>🔧 Selective Enhancement</h3>
|
|
<select id="selectiveStack">
|
|
<option value="lightEnhancement">Light Enhancement</option>
|
|
<option value="standardEnhancement" selected>Standard Enhancement</option>
|
|
<option value="fullEnhancement">Full Enhancement</option>
|
|
<option value="personalityFocus">Personality Focus</option>
|
|
<option value="fluidityFocus">Fluidity Focus</option>
|
|
<option value="adaptive">Adaptive</option>
|
|
</select>
|
|
<div class="option-description">Base d'amélioration du contenu généré</div>
|
|
</div>
|
|
|
|
<!-- Adversarial Mode -->
|
|
<div class="module-group">
|
|
<h3>🎯 Adversarial Generation</h3>
|
|
<select id="adversarialMode">
|
|
<option value="none">None</option>
|
|
<option value="light" selected>Light</option>
|
|
<option value="standard">Standard</option>
|
|
<option value="heavy">Heavy</option>
|
|
<option value="adaptive">Adaptive</option>
|
|
</select>
|
|
<div class="option-description">Techniques adversariales anti-détection</div>
|
|
</div>
|
|
|
|
<!-- Human Simulation -->
|
|
<div class="module-group">
|
|
<h3>🧠 Human Simulation</h3>
|
|
<select id="humanSimulationMode">
|
|
<option value="none" selected>None</option>
|
|
<option value="lightSimulation">Light Simulation</option>
|
|
<option value="standardSimulation">Standard Simulation</option>
|
|
<option value="heavySimulation">Heavy Simulation</option>
|
|
<option value="adaptiveSimulation">Adaptive Simulation</option>
|
|
<option value="personalityFocus">Personality Focus</option>
|
|
<option value="temporalFocus">Temporal Focus</option>
|
|
</select>
|
|
<div class="option-description">Simulation comportement humain (fatigue, erreurs)</div>
|
|
</div>
|
|
|
|
<!-- Pattern Breaking -->
|
|
<div class="module-group">
|
|
<h3>🔧 Pattern Breaking</h3>
|
|
<select id="patternBreakingMode">
|
|
<option value="none" selected>None</option>
|
|
<option value="lightPatternBreaking">Light Pattern Breaking</option>
|
|
<option value="standardPatternBreaking">Standard Pattern Breaking</option>
|
|
<option value="heavyPatternBreaking">Heavy Pattern Breaking</option>
|
|
<option value="adaptivePatternBreaking">Adaptive Pattern Breaking</option>
|
|
<option value="syntaxFocus">Syntax Focus</option>
|
|
<option value="connectorsFocus">Connectors Focus</option>
|
|
</select>
|
|
<div class="option-description">Cassage patterns syntaxiques LLM</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section Save/Load -->
|
|
<div class="actions-section">
|
|
<h2>💾 Gestion Configurations</h2>
|
|
|
|
<!-- Save -->
|
|
<div class="save-group">
|
|
<input type="text" id="configName" placeholder="Nom configuration...">
|
|
<button onclick="saveConfig()" class="btn-primary">💾 Save Config</button>
|
|
</div>
|
|
|
|
<!-- Load -->
|
|
<div class="load-group">
|
|
<select id="savedConfigs">
|
|
<option value="">-- Charger une config --</option>
|
|
</select>
|
|
<button onclick="loadConfig()" class="btn-secondary">📂 Load</button>
|
|
<button onclick="deleteConfig()" class="btn-danger">🗑️ Delete</button>
|
|
</div>
|
|
|
|
<!-- Test Live -->
|
|
<div class="test-group">
|
|
<button onclick="testLive()" class="btn-primary">🚀 Test Live</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Preview JSON -->
|
|
<div class="preview-section">
|
|
<h3>📄 Preview Configuration</h3>
|
|
<pre id="configPreview"></pre>
|
|
</div>
|
|
|
|
<!-- Logs Temps Réel -->
|
|
<div class="logs-section">
|
|
<h3>🔍 Logs Temps Réel</h3>
|
|
<button onclick="clearLogs()" class="btn-clear">Clear</button>
|
|
<div class="logs-container" id="logsContainer"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="config-editor.js"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
**API Calls :**
|
|
- `POST /api/config/save` : Sauvegarder config avec nom
|
|
- `GET /api/config/list` : Lister toutes les configs
|
|
- `GET /api/config/:name` : Charger une config par nom
|
|
- `DELETE /api/config/:name` : Supprimer une config
|
|
- `POST /api/test-modulaire` : Tester la config (existant)
|
|
|
|
---
|
|
|
|
### 3. Runner de Production : `public/production-runner.html`
|
|
|
|
**Fonctionnalités :**
|
|
- **Load Config** : Dropdown pour sélectionner une config sauvegardée
|
|
- **Row Selection** : Input pour choisir la ligne Google Sheets
|
|
- **Run Button** : Lancer le workflow complet avec sauvegarde versionnée
|
|
- **Progress Tracking** : Barre de progression + étapes
|
|
- **Results Display** :
|
|
- Statistiques finales (word count, LLM utilisés, coût, durée)
|
|
- Lien vers Google Sheets
|
|
- Logs détaillés
|
|
- **WebSocket Logs** : Logs temps réel pendant l'exécution
|
|
|
|
**Structure HTML Clé :**
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Production Runner</title>
|
|
</head>
|
|
<body>
|
|
<div class="runner-container">
|
|
<!-- Header -->
|
|
<header>
|
|
<h1>🚀 Production Runner</h1>
|
|
<a href="index.html" class="btn-back">← Retour Accueil</a>
|
|
</header>
|
|
|
|
<!-- Configuration Selection -->
|
|
<div class="config-selection">
|
|
<h2>⚙️ Sélection Configuration</h2>
|
|
|
|
<div class="select-group">
|
|
<label>Configuration :</label>
|
|
<select id="configSelect" onchange="loadSelectedConfig()">
|
|
<option value="">-- Sélectionner une config --</option>
|
|
</select>
|
|
<button onclick="refreshConfigs()">🔄 Refresh</button>
|
|
</div>
|
|
|
|
<div class="config-display" id="configDisplay">
|
|
<!-- Affichage config sélectionnée -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Row Selection -->
|
|
<div class="row-selection">
|
|
<h2>📊 Google Sheets</h2>
|
|
<div class="form-group">
|
|
<label>Ligne à traiter :</label>
|
|
<input type="number" id="rowNumber" value="2" min="2" max="1000">
|
|
<span class="help-text">Ligne du Google Sheet "Instructions"</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Run Controls -->
|
|
<div class="run-controls">
|
|
<button onclick="runProduction()" class="btn-run" id="btnRun">
|
|
🚀 Lancer Production
|
|
</button>
|
|
<button onclick="cancelRun()" class="btn-cancel" id="btnCancel" disabled>
|
|
🛑 Annuler
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Progress Tracking -->
|
|
<div class="progress-section" id="progressSection" style="display: none;">
|
|
<h2>⏳ Progression</h2>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" id="progressFill"></div>
|
|
</div>
|
|
<div class="progress-steps" id="progressSteps">
|
|
<!-- Étapes dynamiques -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Results -->
|
|
<div class="results-section" id="resultsSection" style="display: none;">
|
|
<h2>📊 Résultats</h2>
|
|
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<span class="stat-label">Mots Générés</span>
|
|
<span class="stat-value" id="wordCount">-</span>
|
|
</div>
|
|
<div class="stat-card">
|
|
<span class="stat-label">Durée Totale</span>
|
|
<span class="stat-value" id="duration">-</span>
|
|
</div>
|
|
<div class="stat-card">
|
|
<span class="stat-label">LLM Utilisés</span>
|
|
<span class="stat-value" id="llmUsed">-</span>
|
|
</div>
|
|
<div class="stat-card">
|
|
<span class="stat-label">Coût Estimé</span>
|
|
<span class="stat-value" id="cost">-</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="links-section">
|
|
<a href="#" id="gsheetsLink" target="_blank" class="btn-link">
|
|
📊 Voir dans Google Sheets
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Logs Temps Réel -->
|
|
<div class="logs-section">
|
|
<h3>🔍 Logs Temps Réel</h3>
|
|
<button onclick="clearLogs()" class="btn-clear">Clear</button>
|
|
<div class="logs-container" id="logsContainer"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="production-runner.js"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
**API Calls :**
|
|
- `GET /api/config/list` : Lister configs disponibles
|
|
- `GET /api/config/:name` : Charger config sélectionnée
|
|
- `POST /api/production-run` : Lancer workflow complet (nouveau endpoint)
|
|
|
|
---
|
|
|
|
## 🔧 Backend - Nouveaux Endpoints
|
|
|
|
### 1. Système de Stockage des Configurations
|
|
|
|
**Fichier : `lib/ConfigManager.js`**
|
|
|
|
```javascript
|
|
// ========================================
|
|
// FICHIER: ConfigManager.js
|
|
// RESPONSABILITÉ: Gestion CRUD des configurations modulaires
|
|
// STOCKAGE: Fichiers JSON dans configs/
|
|
// ========================================
|
|
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
const { logSh } = require('./ErrorReporting');
|
|
|
|
class ConfigManager {
|
|
constructor() {
|
|
this.configDir = path.join(__dirname, '../configs');
|
|
this.ensureConfigDir();
|
|
}
|
|
|
|
async ensureConfigDir() {
|
|
try {
|
|
await fs.mkdir(this.configDir, { recursive: true });
|
|
} catch (error) {
|
|
logSh(`⚠️ Erreur création dossier configs: ${error.message}`, 'WARNING');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sauvegarder une configuration
|
|
*/
|
|
async saveConfig(name, config) {
|
|
const sanitizedName = name.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
const filePath = path.join(this.configDir, `${sanitizedName}.json`);
|
|
|
|
const configData = {
|
|
name: sanitizedName,
|
|
displayName: name,
|
|
config,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
};
|
|
|
|
await fs.writeFile(filePath, JSON.stringify(configData, null, 2), 'utf-8');
|
|
logSh(`💾 Config sauvegardée: ${name}`, 'INFO');
|
|
|
|
return { success: true, name: sanitizedName };
|
|
}
|
|
|
|
/**
|
|
* Charger une configuration
|
|
*/
|
|
async loadConfig(name) {
|
|
const sanitizedName = name.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
const filePath = path.join(this.configDir, `${sanitizedName}.json`);
|
|
|
|
try {
|
|
const data = await fs.readFile(filePath, 'utf-8');
|
|
const configData = JSON.parse(data);
|
|
logSh(`📂 Config chargée: ${name}`, 'DEBUG');
|
|
return configData;
|
|
} catch (error) {
|
|
logSh(`❌ Config non trouvée: ${name}`, 'ERROR');
|
|
throw new Error(`Configuration "${name}" non trouvée`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Lister toutes les configurations
|
|
*/
|
|
async listConfigs() {
|
|
try {
|
|
const files = await fs.readdir(this.configDir);
|
|
const jsonFiles = files.filter(f => f.endsWith('.json'));
|
|
|
|
const configs = await Promise.all(
|
|
jsonFiles.map(async (file) => {
|
|
const filePath = path.join(this.configDir, file);
|
|
const data = await fs.readFile(filePath, 'utf-8');
|
|
const configData = JSON.parse(data);
|
|
|
|
return {
|
|
name: configData.name,
|
|
displayName: configData.displayName || configData.name,
|
|
createdAt: configData.createdAt,
|
|
updatedAt: configData.updatedAt
|
|
};
|
|
})
|
|
);
|
|
|
|
return configs.sort((a, b) =>
|
|
new Date(b.updatedAt) - new Date(a.updatedAt)
|
|
);
|
|
|
|
} catch (error) {
|
|
logSh(`⚠️ Erreur listing configs: ${error.message}`, 'WARNING');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Supprimer une configuration
|
|
*/
|
|
async deleteConfig(name) {
|
|
const sanitizedName = name.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
const filePath = path.join(this.configDir, `${sanitizedName}.json`);
|
|
|
|
await fs.unlink(filePath);
|
|
logSh(`🗑️ Config supprimée: ${name}`, 'INFO');
|
|
|
|
return { success: true };
|
|
}
|
|
}
|
|
|
|
module.exports = { ConfigManager };
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Nouveaux Endpoints dans `ManualServer.js`
|
|
|
|
**Ajouter dans `setupAPIRoutes()` :**
|
|
|
|
```javascript
|
|
// ========================================
|
|
// ENDPOINTS GESTION CONFIGURATIONS
|
|
// ========================================
|
|
|
|
// Sauvegarder une configuration
|
|
this.app.post('/api/config/save', async (req, res) => {
|
|
try {
|
|
const { name, config } = req.body;
|
|
|
|
if (!name || !config) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Nom et configuration requis'
|
|
});
|
|
}
|
|
|
|
const { ConfigManager } = require('../ConfigManager');
|
|
const configManager = new ConfigManager();
|
|
|
|
const result = await configManager.saveConfig(name, config);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `Configuration "${name}" sauvegardée`,
|
|
savedName: result.name
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur save config: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// Lister les configurations
|
|
this.app.get('/api/config/list', async (req, res) => {
|
|
try {
|
|
const { ConfigManager } = require('../ConfigManager');
|
|
const configManager = new ConfigManager();
|
|
|
|
const configs = await configManager.listConfigs();
|
|
|
|
res.json({
|
|
success: true,
|
|
configs,
|
|
count: configs.length
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur list configs: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// Charger une configuration
|
|
this.app.get('/api/config/:name', async (req, res) => {
|
|
try {
|
|
const { name } = req.params;
|
|
|
|
const { ConfigManager } = require('../ConfigManager');
|
|
const configManager = new ConfigManager();
|
|
|
|
const configData = await configManager.loadConfig(name);
|
|
|
|
res.json({
|
|
success: true,
|
|
config: configData
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur load config: ${error.message}`, 'ERROR');
|
|
res.status(404).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// Supprimer une configuration
|
|
this.app.delete('/api/config/:name', async (req, res) => {
|
|
try {
|
|
const { name } = req.params;
|
|
|
|
const { ConfigManager } = require('../ConfigManager');
|
|
const configManager = new ConfigManager();
|
|
|
|
await configManager.deleteConfig(name);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `Configuration "${name}" supprimée`
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur delete config: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// ========================================
|
|
// ENDPOINT PRODUCTION RUN
|
|
// ========================================
|
|
|
|
this.app.post('/api/production-run', async (req, res) => {
|
|
try {
|
|
const {
|
|
rowNumber,
|
|
selectiveStack,
|
|
adversarialMode,
|
|
humanSimulationMode,
|
|
patternBreakingMode,
|
|
saveIntermediateSteps = true
|
|
} = req.body;
|
|
|
|
if (!rowNumber) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'rowNumber requis'
|
|
});
|
|
}
|
|
|
|
logSh(`🚀 PRODUCTION RUN: Row ${rowNumber}`, 'INFO');
|
|
|
|
// Appel handleFullWorkflow depuis Main.js
|
|
const { handleFullWorkflow } = require('../Main');
|
|
|
|
const result = await handleFullWorkflow({
|
|
rowNumber,
|
|
selectiveStack: selectiveStack || 'standardEnhancement',
|
|
adversarialMode: adversarialMode || 'light',
|
|
humanSimulationMode: humanSimulationMode || 'none',
|
|
patternBreakingMode: patternBreakingMode || 'none',
|
|
saveIntermediateSteps,
|
|
source: 'production_web'
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
result: {
|
|
wordCount: result.compiledWordCount,
|
|
duration: result.totalDuration,
|
|
llmUsed: result.llmUsed,
|
|
cost: result.estimatedCost,
|
|
slug: result.slug,
|
|
gsheetsLink: `https://docs.google.com/spreadsheets/d/${process.env.GOOGLE_SHEETS_ID}`
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
logSh(`❌ Erreur production run: ${error.message}`, 'ERROR');
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 Schéma des Données
|
|
|
|
### Format Config Sauvegardée
|
|
|
|
**Fichier : `configs/nom-config.json`**
|
|
|
|
```json
|
|
{
|
|
"name": "config_standard_heavy",
|
|
"displayName": "Config Standard Heavy",
|
|
"config": {
|
|
"rowNumber": 2,
|
|
"selectiveStack": "standardEnhancement",
|
|
"adversarialMode": "heavy",
|
|
"humanSimulationMode": "standardSimulation",
|
|
"patternBreakingMode": "standardPatternBreaking",
|
|
"saveIntermediateSteps": true,
|
|
"source": "web_interface"
|
|
},
|
|
"createdAt": "2025-10-08T14:30:00.000Z",
|
|
"updatedAt": "2025-10-08T14:30:00.000Z"
|
|
}
|
|
```
|
|
|
|
### API Responses
|
|
|
|
#### `GET /api/config/list`
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"configs": [
|
|
{
|
|
"name": "config_standard_heavy",
|
|
"displayName": "Config Standard Heavy",
|
|
"createdAt": "2025-10-08T14:30:00.000Z",
|
|
"updatedAt": "2025-10-08T14:30:00.000Z"
|
|
}
|
|
],
|
|
"count": 1
|
|
}
|
|
```
|
|
|
|
#### `POST /api/production-run`
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"result": {
|
|
"wordCount": 2151,
|
|
"duration": 45320,
|
|
"llmUsed": "claude,openai,mistral",
|
|
"cost": 0.15,
|
|
"slug": "plaque-personnalisee",
|
|
"gsheetsLink": "https://docs.google.com/spreadsheets/d/xxx"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🎨 Design System
|
|
|
|
### Palette de Couleurs
|
|
|
|
```css
|
|
:root {
|
|
/* Primary Colors */
|
|
--primary: #667eea;
|
|
--secondary: #764ba2;
|
|
|
|
/* Semantic Colors */
|
|
--success: #48bb78;
|
|
--warning: #ed8936;
|
|
--error: #f56565;
|
|
--info: #4299e1;
|
|
|
|
/* Backgrounds */
|
|
--bg-light: #f7fafc;
|
|
--bg-dark: #1a202c;
|
|
--bg-card: #ffffff;
|
|
|
|
/* Text */
|
|
--text-dark: #2d3748;
|
|
--text-light: #a0aec0;
|
|
--text-muted: #718096;
|
|
|
|
/* Borders */
|
|
--border-light: #e2e8f0;
|
|
--border-dark: #2d3748;
|
|
}
|
|
```
|
|
|
|
### Typography
|
|
|
|
```css
|
|
body {
|
|
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
font-size: 14px;
|
|
line-height: 1.6;
|
|
color: var(--text-dark);
|
|
}
|
|
|
|
h1 {
|
|
font-size: 1.8em;
|
|
font-weight: 700;
|
|
margin-bottom: 25px;
|
|
}
|
|
|
|
h2 {
|
|
font-size: 1.4em;
|
|
font-weight: 600;
|
|
margin: 20px 0 15px 0;
|
|
}
|
|
|
|
h3 {
|
|
font-size: 1.2em;
|
|
font-weight: 600;
|
|
margin: 15px 0 10px 0;
|
|
}
|
|
```
|
|
|
|
### Components
|
|
|
|
```css
|
|
/* Buttons */
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
|
color: white;
|
|
padding: 12px 24px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
}
|
|
|
|
/* Cards */
|
|
.card {
|
|
background: var(--bg-card);
|
|
border-radius: 15px;
|
|
padding: 25px;
|
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 15px 40px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
/* Form Groups */
|
|
.form-group {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
font-weight: 600;
|
|
margin-bottom: 8px;
|
|
color: var(--text-dark);
|
|
}
|
|
|
|
.form-group input,
|
|
.form-group select {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: 2px solid var(--border-light);
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
transition: border-color 0.2s;
|
|
}
|
|
|
|
.form-group input:focus,
|
|
.form-group select:focus {
|
|
outline: none;
|
|
border-color: var(--primary);
|
|
}
|
|
|
|
/* Logs Container */
|
|
.logs-container {
|
|
background: var(--bg-dark);
|
|
color: #e2e8f0;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
font-family: 'Fira Code', monospace;
|
|
font-size: 12px;
|
|
height: 300px;
|
|
overflow-y: auto;
|
|
border: 1px solid var(--border-dark);
|
|
}
|
|
|
|
.log-entry {
|
|
margin-bottom: 4px;
|
|
padding: 2px 0;
|
|
}
|
|
|
|
.log-debug { color: #63b3ed; }
|
|
.log-info { color: #68d391; }
|
|
.log-warn { color: #f6e05e; }
|
|
.log-error { color: #fc8181; }
|
|
.log-trace { color: #a0aec0; }
|
|
```
|
|
|
|
---
|
|
|
|
## 💻 JavaScript - Fonctions Clés
|
|
|
|
### config-editor.js
|
|
|
|
```javascript
|
|
// ========================================
|
|
// GESTION CONFIGURATION EDITOR
|
|
// ========================================
|
|
|
|
let ws = null;
|
|
|
|
// WebSocket connection
|
|
function connectWebSocket() {
|
|
ws = new WebSocket('ws://localhost:8081');
|
|
|
|
ws.onopen = () => console.log('WebSocket connected');
|
|
|
|
ws.onmessage = (event) => {
|
|
try {
|
|
const logData = JSON.parse(event.data);
|
|
displayLog(logData);
|
|
} catch (e) {
|
|
displayLog({ level: 'info', msg: event.data });
|
|
}
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
console.log('WebSocket disconnected');
|
|
setTimeout(connectWebSocket, 3000);
|
|
};
|
|
}
|
|
|
|
// Display log entry
|
|
function displayLog(logData) {
|
|
const logsDiv = document.getElementById('logsContainer');
|
|
const logEntry = document.createElement('div');
|
|
logEntry.className = 'log-entry';
|
|
|
|
let levelClass = 'log-info';
|
|
if (logData.level <= 10) levelClass = 'log-trace';
|
|
else if (logData.level === 20) levelClass = 'log-debug';
|
|
else if (logData.level === 30) levelClass = 'log-info';
|
|
else if (logData.level === 40) levelClass = 'log-warn';
|
|
else if (logData.level >= 50) levelClass = 'log-error';
|
|
|
|
logEntry.innerHTML = `<span class="${levelClass}">${logData.msg}</span>`;
|
|
logsDiv.appendChild(logEntry);
|
|
logsDiv.scrollTop = logsDiv.scrollHeight;
|
|
}
|
|
|
|
// Sauvegarder configuration
|
|
async function saveConfig() {
|
|
const configName = document.getElementById('configName').value;
|
|
|
|
if (!configName) {
|
|
showError('Veuillez entrer un nom de configuration');
|
|
return;
|
|
}
|
|
|
|
const config = {
|
|
rowNumber: parseInt(document.getElementById('rowNumber').value),
|
|
selectiveStack: document.getElementById('selectiveStack').value,
|
|
adversarialMode: document.getElementById('adversarialMode').value,
|
|
humanSimulationMode: document.getElementById('humanSimulationMode').value,
|
|
patternBreakingMode: document.getElementById('patternBreakingMode').value,
|
|
saveIntermediateSteps: true,
|
|
source: 'web_interface'
|
|
};
|
|
|
|
try {
|
|
const response = await fetch('/api/config/save', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name: configName, config })
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showSuccess(`Configuration "${configName}" sauvegardée !`);
|
|
await refreshConfigsList();
|
|
document.getElementById('configName').value = '';
|
|
} else {
|
|
showError(result.error);
|
|
}
|
|
|
|
} catch (error) {
|
|
showError(`Erreur sauvegarde: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Charger configuration
|
|
async function loadConfig() {
|
|
const configName = document.getElementById('savedConfigs').value;
|
|
|
|
if (!configName) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/config/${configName}`);
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
const config = result.config.config;
|
|
|
|
// Remplir les champs
|
|
document.getElementById('rowNumber').value = config.rowNumber;
|
|
document.getElementById('selectiveStack').value = config.selectiveStack;
|
|
document.getElementById('adversarialMode').value = config.adversarialMode;
|
|
document.getElementById('humanSimulationMode').value = config.humanSimulationMode;
|
|
document.getElementById('patternBreakingMode').value = config.patternBreakingMode;
|
|
|
|
showSuccess(`Configuration "${configName}" chargée !`);
|
|
updateConfigPreview();
|
|
} else {
|
|
showError(result.error);
|
|
}
|
|
|
|
} catch (error) {
|
|
showError(`Erreur chargement: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Supprimer configuration
|
|
async function deleteConfig() {
|
|
const configName = document.getElementById('savedConfigs').value;
|
|
|
|
if (!configName) return;
|
|
|
|
if (!confirm(`Supprimer la configuration "${configName}" ?`)) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/config/${configName}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showSuccess(`Configuration "${configName}" supprimée !`);
|
|
await refreshConfigsList();
|
|
} else {
|
|
showError(result.error);
|
|
}
|
|
|
|
} catch (error) {
|
|
showError(`Erreur suppression: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Refresh configs list
|
|
async function refreshConfigsList() {
|
|
try {
|
|
const response = await fetch('/api/config/list');
|
|
const result = await response.json();
|
|
|
|
const select = document.getElementById('savedConfigs');
|
|
select.innerHTML = '<option value="">-- Charger une config --</option>';
|
|
|
|
result.configs.forEach(config => {
|
|
const option = document.createElement('option');
|
|
option.value = config.name;
|
|
option.textContent = config.displayName;
|
|
select.appendChild(option);
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Erreur refresh configs:', error);
|
|
}
|
|
}
|
|
|
|
// Update config preview
|
|
function updateConfigPreview() {
|
|
const config = {
|
|
rowNumber: parseInt(document.getElementById('rowNumber').value),
|
|
selectiveStack: document.getElementById('selectiveStack').value,
|
|
adversarialMode: document.getElementById('adversarialMode').value,
|
|
humanSimulationMode: document.getElementById('humanSimulationMode').value,
|
|
patternBreakingMode: document.getElementById('patternBreakingMode').value
|
|
};
|
|
|
|
document.getElementById('configPreview').textContent = JSON.stringify(config, null, 2);
|
|
}
|
|
|
|
// Test live
|
|
async function testLive() {
|
|
const config = {
|
|
rowNumber: parseInt(document.getElementById('rowNumber').value),
|
|
selectiveStack: document.getElementById('selectiveStack').value,
|
|
adversarialMode: document.getElementById('adversarialMode').value,
|
|
humanSimulationMode: document.getElementById('humanSimulationMode').value,
|
|
patternBreakingMode: document.getElementById('patternBreakingMode').value,
|
|
source: 'web_interface'
|
|
};
|
|
|
|
showStatus('🚀 Test en cours...', 'loading');
|
|
clearLogs();
|
|
|
|
try {
|
|
const response = await fetch('/api/test-modulaire', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(config)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showStatus('✅ Test terminé avec succès!', 'success');
|
|
} else {
|
|
showStatus('❌ Erreur: ' + (result.error || 'Erreur inconnue'), 'error');
|
|
}
|
|
|
|
} catch (error) {
|
|
showStatus('❌ Erreur: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
// Clear logs
|
|
function clearLogs() {
|
|
document.getElementById('logsContainer').innerHTML = '';
|
|
}
|
|
|
|
// Show status
|
|
function showStatus(message, type) {
|
|
// Implementation
|
|
}
|
|
|
|
// Show success
|
|
function showSuccess(message) {
|
|
showStatus(message, 'success');
|
|
}
|
|
|
|
// Show error
|
|
function showError(message) {
|
|
showStatus(message, 'error');
|
|
}
|
|
|
|
// Initialize
|
|
window.onload = async () => {
|
|
connectWebSocket();
|
|
await refreshConfigsList();
|
|
updateConfigPreview();
|
|
|
|
// Auto-update preview on change
|
|
['rowNumber', 'selectiveStack', 'adversarialMode', 'humanSimulationMode', 'patternBreakingMode'].forEach(id => {
|
|
document.getElementById(id).addEventListener('change', updateConfigPreview);
|
|
});
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
### production-runner.js
|
|
|
|
```javascript
|
|
// ========================================
|
|
// GESTION PRODUCTION RUNNER
|
|
// ========================================
|
|
|
|
let ws = null;
|
|
let currentConfig = null;
|
|
|
|
// WebSocket connection (same as config-editor.js)
|
|
function connectWebSocket() {
|
|
// Same implementation
|
|
}
|
|
|
|
// Load selected config
|
|
async function loadSelectedConfig() {
|
|
const configName = document.getElementById('configSelect').value;
|
|
|
|
if (!configName) {
|
|
document.getElementById('configDisplay').innerHTML = '';
|
|
currentConfig = null;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/config/${configName}`);
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
currentConfig = result.config.config;
|
|
displayConfigDetails(currentConfig);
|
|
}
|
|
|
|
} catch (error) {
|
|
showError(`Erreur chargement config: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Display config details
|
|
function displayConfigDetails(config) {
|
|
const html = `
|
|
<div class="config-details">
|
|
<h3>Configuration Chargée</h3>
|
|
<ul>
|
|
<li><strong>Selective:</strong> ${config.selectiveStack}</li>
|
|
<li><strong>Adversarial:</strong> ${config.adversarialMode}</li>
|
|
<li><strong>Human Simulation:</strong> ${config.humanSimulationMode}</li>
|
|
<li><strong>Pattern Breaking:</strong> ${config.patternBreakingMode}</li>
|
|
</ul>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('configDisplay').innerHTML = html;
|
|
}
|
|
|
|
// Refresh configs dropdown
|
|
async function refreshConfigs() {
|
|
try {
|
|
const response = await fetch('/api/config/list');
|
|
const result = await response.json();
|
|
|
|
const select = document.getElementById('configSelect');
|
|
select.innerHTML = '<option value="">-- Sélectionner une config --</option>';
|
|
|
|
result.configs.forEach(config => {
|
|
const option = document.createElement('option');
|
|
option.value = config.name;
|
|
option.textContent = config.displayName;
|
|
select.appendChild(option);
|
|
});
|
|
|
|
showSuccess('Configs rafraîchies');
|
|
|
|
} catch (error) {
|
|
showError(`Erreur refresh: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Run production workflow
|
|
async function runProduction() {
|
|
const configName = document.getElementById('configSelect').value;
|
|
|
|
if (!configName) {
|
|
showError('Veuillez sélectionner une configuration');
|
|
return;
|
|
}
|
|
|
|
if (!currentConfig) {
|
|
showError('Configuration non chargée');
|
|
return;
|
|
}
|
|
|
|
// Override rowNumber from input
|
|
const config = {
|
|
...currentConfig,
|
|
rowNumber: parseInt(document.getElementById('rowNumber').value)
|
|
};
|
|
|
|
// UI Updates
|
|
document.getElementById('btnRun').disabled = true;
|
|
document.getElementById('btnCancel').disabled = false;
|
|
showProgressSection();
|
|
clearLogs();
|
|
|
|
try {
|
|
const response = await fetch('/api/production-run', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(config)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
hideProgressSection();
|
|
displayResults(result.result);
|
|
showSuccess('✅ Workflow terminé avec succès!');
|
|
} else {
|
|
hideProgressSection();
|
|
showError(result.error);
|
|
}
|
|
|
|
} catch (error) {
|
|
hideProgressSection();
|
|
showError(`Erreur production: ${error.message}`);
|
|
} finally {
|
|
document.getElementById('btnRun').disabled = false;
|
|
document.getElementById('btnCancel').disabled = true;
|
|
}
|
|
}
|
|
|
|
// Display results
|
|
function displayResults(result) {
|
|
document.getElementById('resultsSection').style.display = 'block';
|
|
|
|
document.getElementById('wordCount').textContent = result.wordCount || '-';
|
|
document.getElementById('duration').textContent = `${(result.duration / 1000).toFixed(1)}s`;
|
|
document.getElementById('llmUsed').textContent = result.llmUsed || '-';
|
|
document.getElementById('cost').textContent = `$${result.cost?.toFixed(4) || '0.00'}`;
|
|
|
|
document.getElementById('gsheetsLink').href = result.gsheetsLink;
|
|
}
|
|
|
|
// Show/hide progress
|
|
function showProgressSection() {
|
|
document.getElementById('progressSection').style.display = 'block';
|
|
updateProgress(10);
|
|
}
|
|
|
|
function hideProgressSection() {
|
|
document.getElementById('progressSection').style.display = 'none';
|
|
}
|
|
|
|
function updateProgress(percentage) {
|
|
document.getElementById('progressFill').style.width = percentage + '%';
|
|
}
|
|
|
|
// Cancel run
|
|
function cancelRun() {
|
|
// TODO: Implement cancel logic
|
|
showError('Annulation non implémentée');
|
|
}
|
|
|
|
// Clear logs
|
|
function clearLogs() {
|
|
document.getElementById('logsContainer').innerHTML = '';
|
|
}
|
|
|
|
// Initialize
|
|
window.onload = async () => {
|
|
connectWebSocket();
|
|
await refreshConfigs();
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 📅 Plan d'Exécution Séquentiel
|
|
|
|
| Étape | Tâche | Fichiers | Durée Estimée |
|
|
|-------|-------|----------|---------------|
|
|
| **1** | Créer `ConfigManager.js` | `lib/ConfigManager.js` | 30 min |
|
|
| **2** | Ajouter endpoints backend | `lib/modes/ManualServer.js` | 45 min |
|
|
| **3** | Créer page d'accueil | `public/index.html` + CSS | 1h |
|
|
| **4** | Créer éditeur de config | `public/config-editor.html` + `config-editor.js` | 2h |
|
|
| **5** | Créer runner de production | `public/production-runner.html` + `production-runner.js` | 2h |
|
|
| **6** | Tests end-to-end | Tous les fichiers | 1h |
|
|
| **TOTAL** | | | **~7h** |
|
|
|
|
### Ordre d'Implémentation Recommandé
|
|
|
|
1. **Backend First** (Étapes 1-2) :
|
|
- Créer ConfigManager.js
|
|
- Ajouter endpoints dans ManualServer.js
|
|
- Tester avec curl/Postman
|
|
|
|
2. **Frontend Simple → Complexe** (Étapes 3-5) :
|
|
- Page d'accueil (simple navigation)
|
|
- Éditeur de config (formulaires + CRUD)
|
|
- Runner de production (intégration complète)
|
|
|
|
3. **Tests & Polish** (Étape 6) :
|
|
- Tests fonctionnels
|
|
- Corrections bugs
|
|
- Amélioration UX
|
|
|
|
---
|
|
|
|
## 🧪 Tests à Effectuer
|
|
|
|
### 1. Tests Backend (ConfigManager)
|
|
|
|
- ✅ **Save Config** : Sauvegarder avec nom normal
|
|
- ✅ **Save Config** : Nom avec espaces/accents → sanitization
|
|
- ✅ **Load Config** : Charger config existante
|
|
- ✅ **Load Config** : Config inexistante → erreur 404
|
|
- ✅ **List Configs** : Retourne tableau vide si aucune config
|
|
- ✅ **List Configs** : Retourne configs triées par date
|
|
- ✅ **Delete Config** : Suppression réussie
|
|
- ✅ **Delete Config** : Config inexistante → erreur
|
|
|
|
### 2. Tests Frontend (Config Editor)
|
|
|
|
- ✅ **Save** : Sauvegarde + apparaît dans dropdown
|
|
- ✅ **Load** : Charge tous les champs correctement
|
|
- ✅ **Delete** : Supprime + met à jour dropdown
|
|
- ✅ **Test Live** : Exécute test modulaire
|
|
- ✅ **Preview** : JSON mis à jour en temps réel
|
|
- ✅ **WebSocket** : Logs temps réel reçus
|
|
|
|
### 3. Tests Frontend (Production Runner)
|
|
|
|
- ✅ **Load Config** : Affiche détails config
|
|
- ✅ **Run Production** : Lance workflow complet
|
|
- ✅ **Run Production** : Override rowNumber correct
|
|
- ✅ **Results** : Statistiques affichées correctement
|
|
- ✅ **Results** : Lien Google Sheets fonctionnel
|
|
- ✅ **WebSocket** : Logs temps réel pendant run
|
|
- ✅ **Progress** : Barre de progression visible
|
|
|
|
### 4. Tests End-to-End
|
|
|
|
- ✅ **Flow Complet** :
|
|
1. Accueil → Config Editor
|
|
2. Créer config + sauvegarder
|
|
3. Retour Accueil → Production Runner
|
|
4. Charger config + run production
|
|
5. Vérifier résultats Google Sheets
|
|
|
|
---
|
|
|
|
## 📝 Notes Importantes
|
|
|
|
### Compatibilité Existante
|
|
|
|
- ✅ **Ne modifie PAS** `test-modulaire.html` (reste intact)
|
|
- ✅ **Réutilise** les endpoints existants (`/api/test-modulaire`)
|
|
- ✅ **Compatible** avec architecture MANUAL/AUTO modes
|
|
- ✅ **Utilise** le WebSocket existant (port 8081)
|
|
- ✅ **Respecte** l'architecture modulaire (4 couches)
|
|
|
|
### Structure Fichiers Finale
|
|
|
|
```
|
|
seo-generator-server/
|
|
├── configs/ # 🆕 Nouveau dossier
|
|
│ ├── config_standard.json
|
|
│ ├── config_heavy.json
|
|
│ └── config_light.json
|
|
│
|
|
├── lib/
|
|
│ ├── ConfigManager.js # 🆕 Nouveau fichier
|
|
│ └── modes/
|
|
│ └── ManualServer.js # ✏️ Modifié (nouveaux endpoints)
|
|
│
|
|
├── public/
|
|
│ ├── index.html # 🆕 Nouveau
|
|
│ ├── config-editor.html # 🆕 Nouveau
|
|
│ ├── config-editor.js # 🆕 Nouveau
|
|
│ ├── production-runner.html # 🆕 Nouveau
|
|
│ ├── production-runner.js # 🆕 Nouveau
|
|
│ └── test-modulaire.html # ✅ Existant (non modifié)
|
|
│
|
|
└── ProductionReady.md # 📋 Ce fichier
|
|
```
|
|
|
|
### Endpoints Backend Créés
|
|
|
|
| Méthode | Endpoint | Description |
|
|
|---------|----------|-------------|
|
|
| `POST` | `/api/config/save` | Sauvegarder configuration |
|
|
| `GET` | `/api/config/list` | Lister configurations |
|
|
| `GET` | `/api/config/:name` | Charger configuration |
|
|
| `DELETE` | `/api/config/:name` | Supprimer configuration |
|
|
| `POST` | `/api/production-run` | Lancer workflow production |
|
|
|
|
### Endpoints Existants Réutilisés
|
|
|
|
| Méthode | Endpoint | Description |
|
|
|---------|----------|-------------|
|
|
| `GET` | `/api/status` | Status serveur |
|
|
| `POST` | `/api/test-modulaire` | Test modulaire (existant) |
|
|
|
|
### Dépendances
|
|
|
|
Aucune nouvelle dépendance npm requise. Utilise uniquement :
|
|
- Express (existant)
|
|
- WebSocket (existant)
|
|
- fs/promises (Node.js built-in)
|
|
- path (Node.js built-in)
|
|
|
|
---
|
|
|
|
## 🚀 Prêt à Implémenter
|
|
|
|
### Checklist Avant Démarrage
|
|
|
|
- [ ] Backup du code actuel
|
|
- [ ] Créer branche Git `feature/config-interface`
|
|
- [ ] Vérifier que `test-modulaire.html` fonctionne
|
|
- [ ] Vérifier que le serveur démarre en mode MANUAL
|
|
|
|
### Commandes de Démarrage
|
|
|
|
```bash
|
|
# 1. Créer branche Git
|
|
git checkout -b feature/config-interface
|
|
|
|
# 2. Créer dossier configs
|
|
mkdir -p configs
|
|
|
|
# 3. Démarrer serveur en mode MANUAL
|
|
npm start
|
|
|
|
# 4. Vérifier que le serveur tourne
|
|
curl http://localhost:3000/api/status
|
|
```
|
|
|
|
### Validation Finale
|
|
|
|
Après implémentation, vérifier :
|
|
- ✅ Les 3 pages sont accessibles et navigables
|
|
- ✅ Save/Load/Delete configs fonctionne
|
|
- ✅ Test Live fonctionne (depuis config-editor)
|
|
- ✅ Production Run fonctionne avec sauvegarde GSheets
|
|
- ✅ WebSocket logs s'affichent en temps réel
|
|
- ✅ `test-modulaire.html` fonctionne toujours (non cassé)
|
|
|
|
---
|
|
|
|
## 📞 Support
|
|
|
|
Pour toute question ou problème durant l'implémentation, se référer à :
|
|
- **CLAUDE.md** : Documentation complète du projet
|
|
- **lib/modes/ManualServer.js** : Implémentation serveur MANUAL
|
|
- **public/test-modulaire.html** : Exemple d'interface modulaire fonctionnelle
|
|
|
|
---
|
|
|
|
**Dernière mise à jour** : 2025-10-08
|
|
**Auteur** : Claude Code
|
|
**Status** : 📋 Prêt pour implémentation
|