- Add complete modular demo interface (public/modular-pipeline-demo.html) - Add standalone demo server (simple-server.js) on port 3333 - Integrate real SelectiveCore, AdversarialCore, HumanSimulation, PatternBreaking modules - Add configurable pipeline with step-by-step content transformation display - Add new trend management and workflow configuration modules - Add comprehensive test suite for full pipeline validation - Update core modules for better modular integration and demo compatibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
779 lines
29 KiB
HTML
779 lines
29 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Démo Pipeline Modulaire SEO</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
min-height: 100vh;
|
||
padding: 10px; /* Réduire le padding pour gagner de l'espace vertical */
|
||
}
|
||
|
||
.container {
|
||
max-width: 1800px; /* Plus large pour PC */
|
||
margin: 0 auto;
|
||
width: 95%; /* Utiliser plus d'espace disponible */
|
||
background: white;
|
||
border-radius: 15px;
|
||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.header {
|
||
background: linear-gradient(45deg, #2c3e50, #34495e);
|
||
color: white;
|
||
padding: 30px;
|
||
text-align: center;
|
||
}
|
||
|
||
.header h1 {
|
||
font-size: 2.5em;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.header p {
|
||
opacity: 0.9;
|
||
font-size: 1.2em;
|
||
}
|
||
|
||
.main-content {
|
||
display: flex;
|
||
height: calc(100vh - 100px); /* Maximiser la hauteur - padding body (10px*2) + header (80px) */
|
||
}
|
||
|
||
.config-panel {
|
||
width: 350px; /* Plus étroit pour PC */
|
||
background: #f8f9fa;
|
||
border-right: 1px solid #dee2e6;
|
||
padding: 25px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.results-panel {
|
||
flex: 1; /* Prend tout l'espace restant */
|
||
padding: 25px;
|
||
overflow-y: auto;
|
||
background: #fafbfc; /* Arrière-plan légèrement différent */
|
||
}
|
||
|
||
.section {
|
||
margin-bottom: 30px;
|
||
background: white;
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||
}
|
||
|
||
.section h3 {
|
||
color: #2c3e50;
|
||
margin-bottom: 15px;
|
||
font-size: 1.3em;
|
||
border-bottom: 2px solid #3498db;
|
||
padding-bottom: 8px;
|
||
}
|
||
|
||
.module-item {
|
||
background: #ffffff;
|
||
border: 2px solid #e9ecef;
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
margin-bottom: 10px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
}
|
||
|
||
.module-item:hover {
|
||
border-color: #3498db;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 15px rgba(52, 152, 219, 0.2);
|
||
}
|
||
|
||
.module-item.selected {
|
||
border-color: #27ae60;
|
||
background: #d5f4e6;
|
||
}
|
||
|
||
.module-item h4 {
|
||
color: #2c3e50;
|
||
margin-bottom: 8px;
|
||
font-size: 1.1em;
|
||
}
|
||
|
||
.module-item p {
|
||
color: #6c757d;
|
||
font-size: 0.9em;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.pipeline-builder {
|
||
min-height: 200px;
|
||
border: 2px dashed #dee2e6;
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
background: #fafbfc;
|
||
}
|
||
|
||
.pipeline-step {
|
||
background: linear-gradient(45deg, #3498db, #2980b9);
|
||
color: white;
|
||
padding: 12px 20px;
|
||
margin: 8px 0;
|
||
border-radius: 25px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
box-shadow: 0 3px 10px rgba(52, 152, 219, 0.3);
|
||
}
|
||
|
||
.pipeline-step .remove-btn {
|
||
background: rgba(255,255,255,0.2);
|
||
border: none;
|
||
color: white;
|
||
width: 25px;
|
||
height: 25px;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.controls {
|
||
text-align: center;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.btn {
|
||
background: linear-gradient(45deg, #27ae60, #2ecc71);
|
||
color: white;
|
||
border: none;
|
||
padding: 15px 30px;
|
||
border-radius: 25px;
|
||
font-size: 1.1em;
|
||
cursor: pointer;
|
||
margin: 0 10px;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 5px 15px rgba(39, 174, 96, 0.3);
|
||
}
|
||
|
||
.btn:hover {
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 8px 25px rgba(39, 174, 96, 0.4);
|
||
}
|
||
|
||
.btn:disabled {
|
||
background: #95a5a6;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.btn-clear {
|
||
background: linear-gradient(45deg, #e74c3c, #c0392b);
|
||
box-shadow: 0 5px 15px rgba(231, 76, 60, 0.3);
|
||
}
|
||
|
||
.results-area {
|
||
background: #f8f9fa;
|
||
border-radius: 10px;
|
||
padding: 12px; /* Réduit de moitié : 25px → 12px */
|
||
min-height: 400px;
|
||
/* max-height supprimé pour que le conteneur puisse grandir avec le contenu */
|
||
border: 1px solid #dee2e6;
|
||
/* overflow-y: auto supprimé aussi - le scroll sera géré par le parent si besoin */
|
||
}
|
||
|
||
.step-result {
|
||
background: white;
|
||
border-left: 4px solid #3498db;
|
||
margin-bottom: 10px; /* Réduit de moitié : 20px → 10px */
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
/* min-height supprimé - s'adapte au contenu */
|
||
}
|
||
|
||
.step-header {
|
||
background: #3498db;
|
||
color: white;
|
||
padding: 8px 10px; /* Réduit de moitié : 15px 20px → 8px 10px */
|
||
font-weight: bold;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.copy-btn {
|
||
background: rgba(255,255,255,0.2);
|
||
border: 1px solid rgba(255,255,255,0.3);
|
||
color: white;
|
||
padding: 5px 12px;
|
||
border-radius: 15px;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.copy-btn:hover {
|
||
background: rgba(255,255,255,0.3);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.copy-btn:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.step-content {
|
||
padding: 12px; /* Réduit de moitié : 25px → 12px */
|
||
line-height: 1.7;
|
||
color: #2c3e50;
|
||
font-size: 15px; /* Légèrement plus gros pour PC */
|
||
/* Pas de max-height - prend toute la place disponible */
|
||
}
|
||
|
||
.step-content pre {
|
||
background: #f1f2f6;
|
||
padding: 15px;
|
||
border-radius: 5px;
|
||
overflow-x: auto;
|
||
margin: 10px 0;
|
||
border-left: 3px solid #3498db;
|
||
}
|
||
|
||
.loading {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #6c757d;
|
||
}
|
||
|
||
/* Optimisations pour grands écrans PC */
|
||
@media (min-width: 1600px) {
|
||
.container {
|
||
max-width: 2000px; /* Encore plus large sur très grands écrans */
|
||
}
|
||
|
||
.config-panel {
|
||
width: 400px; /* Un peu plus large sur grands écrans */
|
||
}
|
||
|
||
.step-content {
|
||
font-size: 16px; /* Texte plus gros sur grands écrans */
|
||
/* Pas de limitation de hauteur - utilise tout l'espace */
|
||
}
|
||
|
||
/* .step-result n'a plus de min-height - s'adapte au contenu */
|
||
}
|
||
|
||
.spinner {
|
||
border: 4px solid #f3f3f3;
|
||
border-top: 4px solid #3498db;
|
||
border-radius: 50%;
|
||
width: 40px;
|
||
height: 40px;
|
||
animation: spin 1s linear infinite;
|
||
margin: 0 auto 20px;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.config-option {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.config-option label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
font-weight: 600;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.config-option select, .config-option input {
|
||
width: 100%;
|
||
padding: 8px 12px;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 5px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.status-bar {
|
||
background: #2c3e50;
|
||
color: white;
|
||
padding: 10px 20px;
|
||
font-size: 14px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.step-count {
|
||
background: #3498db;
|
||
padding: 5px 12px;
|
||
border-radius: 15px;
|
||
font-size: 12px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>🚀 Pipeline Modulaire SEO</h1>
|
||
<p>Configuration libre et exécution étape par étape</p>
|
||
</div>
|
||
|
||
<div class="main-content">
|
||
<div class="config-panel">
|
||
<div class="section">
|
||
<h3>📝 Données d'Entrée</h3>
|
||
<div class="config-option">
|
||
<label>Mot-clé principal:</label>
|
||
<input type="text" id="keyword" value="plaque personnalisée" placeholder="Ex: plaque personnalisée">
|
||
</div>
|
||
<div class="config-option">
|
||
<label>Titre principal:</label>
|
||
<input type="text" id="title" value="Créer une plaque personnalisée unique" placeholder="Ex: Guide complet...">
|
||
</div>
|
||
<div class="config-option">
|
||
<label>Personnalité:</label>
|
||
<select id="personality">
|
||
<option value="Marc">Marc (technique)</option>
|
||
<option value="Sophie">Sophie (déco)</option>
|
||
<option value="Laurent">Laurent (commercial)</option>
|
||
<option value="Julie">Julie (architecture)</option>
|
||
<option value="Kévin">Kévin (terrain)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<h3>🔧 Modules Disponibles</h3>
|
||
|
||
<div class="module-item" data-module="selective-light">
|
||
<h4>🎯 Selective Light</h4>
|
||
<p>Enhancement technique léger avec OpenAI</p>
|
||
</div>
|
||
|
||
<div class="module-item" data-module="selective-standard">
|
||
<h4>⚡ Selective Standard</h4>
|
||
<p>Enhancement technique + transitions (OpenAI + Gemini)</p>
|
||
</div>
|
||
|
||
<div class="module-item" data-module="selective-full">
|
||
<h4>🔥 Selective Full</h4>
|
||
<p>Enhancement complet 3 couches (multi-LLM)</p>
|
||
</div>
|
||
|
||
<div class="module-item" data-module="adversarial-general">
|
||
<h4>🛡️ Adversarial General</h4>
|
||
<p>Anti-détection standard avec régénération</p>
|
||
</div>
|
||
|
||
<div class="module-item" data-module="adversarial-gptZero">
|
||
<h4>🎭 Adversarial GPTZero</h4>
|
||
<p>Anti-GPTZero spécialisé</p>
|
||
</div>
|
||
|
||
<div class="module-item" data-module="human-light">
|
||
<h4>👤 Human Light</h4>
|
||
<p>Simulation erreurs humaines légères</p>
|
||
</div>
|
||
|
||
<div class="module-item" data-module="human-personality">
|
||
<h4>🎨 Human Personality</h4>
|
||
<p>Erreurs spécifiques à la personnalité</p>
|
||
</div>
|
||
|
||
<div class="module-item" data-module="pattern-syntax">
|
||
<h4>🔀 Pattern Syntax</h4>
|
||
<p>Cassage patterns avec variations syntaxiques</p>
|
||
</div>
|
||
|
||
<div class="module-item" data-module="pattern-connectors">
|
||
<h4>🔗 Pattern Connectors</h4>
|
||
<p>Connecteurs naturels anti-LLM</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<h3>🏗️ Pipeline Configuré</h3>
|
||
<div style="background: #e8f4fd; border: 2px solid #3498db; border-radius: 8px; padding: 15px; margin-bottom: 15px;">
|
||
<p style="margin: 0; color: #2c3e50; font-weight: 600;">
|
||
⚡ <strong>Étape 0 (Automatique):</strong> Génération Normale avec Claude
|
||
</p>
|
||
<p style="margin: 5px 0 0 0; color: #6c757d; font-size: 0.9em;">
|
||
Cette étape est toujours exécutée en premier, avant vos modules personnalisés
|
||
</p>
|
||
</div>
|
||
<div class="pipeline-builder" id="pipeline-builder">
|
||
<p style="color: #6c757d; text-align: center; margin-top: 80px;">
|
||
Cliquez sur les modules ci-dessus pour construire votre pipeline
|
||
</p>
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<button class="btn" id="execute-btn" disabled>▶️ Exécuter Pipeline</button>
|
||
<button class="btn btn-clear" id="clear-btn">🗑️ Effacer</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="results-panel">
|
||
<div class="section">
|
||
<h3>📊 Résultats d'Exécution</h3>
|
||
<div class="results-area" id="results-area">
|
||
<div style="text-align: center; color: #6c757d; margin-top: 150px;">
|
||
<h4>🎯 Prêt à démarrer</h4>
|
||
<p>Configurez votre pipeline et lancez l'exécution pour voir les résultats étape par étape</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="status-bar">
|
||
<span>Status: <span id="status">Prêt</span></span>
|
||
<span class="step-count">Étapes: <span id="step-count">0</span></span>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let pipeline = [];
|
||
let currentStep = 0;
|
||
let executing = false;
|
||
|
||
const modules = {
|
||
'selective-light': {
|
||
name: 'Selective Light Enhancement',
|
||
description: 'Enhancement technique léger avec OpenAI',
|
||
action: 'Enhancement technique avec focus sur la précision et la clarté'
|
||
},
|
||
'selective-standard': {
|
||
name: 'Selective Standard Enhancement',
|
||
description: 'Enhancement technique + transitions (OpenAI + Gemini)',
|
||
action: 'Enhancement technique approfondi avec amélioration des transitions'
|
||
},
|
||
'selective-full': {
|
||
name: 'Selective Full Enhancement',
|
||
description: 'Enhancement complet 3 couches (multi-LLM)',
|
||
action: 'Enhancement complet avec couches techniques, transitions et style'
|
||
},
|
||
'adversarial-general': {
|
||
name: 'Adversarial General Defense',
|
||
description: 'Anti-détection standard avec régénération',
|
||
action: 'Application de défenses anti-détection générales'
|
||
},
|
||
'adversarial-gptZero': {
|
||
name: 'Adversarial GPTZero Defense',
|
||
description: 'Anti-GPTZero spécialisé',
|
||
action: 'Défense spécialisée contre la détection GPTZero'
|
||
},
|
||
'human-light': {
|
||
name: 'Human Light Simulation',
|
||
description: 'Simulation erreurs humaines légères',
|
||
action: 'Injection d\'erreurs humaines subtiles et naturelles'
|
||
},
|
||
'human-personality': {
|
||
name: 'Human Personality Simulation',
|
||
description: 'Erreurs spécifiques à la personnalité',
|
||
action: 'Simulation d\'erreurs typiques de la personnalité sélectionnée'
|
||
},
|
||
'pattern-syntax': {
|
||
name: 'Pattern Syntax Breaking',
|
||
description: 'Cassage patterns avec variations syntaxiques',
|
||
action: 'Cassage des patterns LLM via variations syntaxiques'
|
||
},
|
||
'pattern-connectors': {
|
||
name: 'Pattern Connectors Breaking',
|
||
description: 'Connecteurs naturels anti-LLM',
|
||
action: 'Remplacement des connecteurs par des alternatives naturelles'
|
||
}
|
||
};
|
||
|
||
// Event listeners
|
||
document.querySelectorAll('.module-item').forEach(item => {
|
||
item.addEventListener('click', () => {
|
||
const moduleId = item.dataset.module;
|
||
addToPipeline(moduleId);
|
||
updatePipelineDisplay();
|
||
});
|
||
});
|
||
|
||
document.getElementById('execute-btn').addEventListener('click', executePipeline);
|
||
document.getElementById('clear-btn').addEventListener('click', clearPipeline);
|
||
|
||
function addToPipeline(moduleId) {
|
||
pipeline.push(moduleId);
|
||
updateStepCount();
|
||
updateExecuteButton();
|
||
}
|
||
|
||
function updatePipelineDisplay() {
|
||
const builder = document.getElementById('pipeline-builder');
|
||
if (pipeline.length === 0) {
|
||
builder.innerHTML = '<p style="color: #6c757d; text-align: center; margin-top: 80px;">Cliquez sur les modules ci-dessus pour construire votre pipeline</p>';
|
||
return;
|
||
}
|
||
|
||
builder.innerHTML = pipeline.map((moduleId, index) => `
|
||
<div class="pipeline-step">
|
||
<span>${index + 1}. ${modules[moduleId].name}</span>
|
||
<button class="remove-btn" onclick="removeFromPipeline(${index})">×</button>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function removeFromPipeline(index) {
|
||
pipeline.splice(index, 1);
|
||
updatePipelineDisplay();
|
||
updateStepCount();
|
||
updateExecuteButton();
|
||
}
|
||
|
||
function clearPipeline() {
|
||
pipeline = [];
|
||
updatePipelineDisplay();
|
||
updateStepCount();
|
||
updateExecuteButton();
|
||
clearResults();
|
||
}
|
||
|
||
function updateStepCount() {
|
||
document.getElementById('step-count').textContent = pipeline.length;
|
||
}
|
||
|
||
function updateExecuteButton() {
|
||
// Le bouton est toujours actif car la génération normale peut être exécutée seule
|
||
document.getElementById('execute-btn').disabled = executing;
|
||
}
|
||
|
||
function updateStatus(status) {
|
||
document.getElementById('status').textContent = status;
|
||
}
|
||
|
||
function clearResults() {
|
||
document.getElementById('results-area').innerHTML = `
|
||
<div style="text-align: center; color: #6c757d; margin-top: 150px;">
|
||
<h4>🎯 Prêt à démarrer</h4>
|
||
<p>Configurez votre pipeline et lancez l'exécution pour voir les résultats étape par étape</p>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
async function executePipeline() {
|
||
// La génération normale peut être exécutée seule (pipeline vide) ou avec des modules
|
||
|
||
executing = true;
|
||
updateExecuteButton();
|
||
updateStatus('Exécution en cours...');
|
||
|
||
const resultsArea = document.getElementById('results-area');
|
||
resultsArea.innerHTML = '';
|
||
|
||
// Données d'entrée
|
||
const keyword = document.getElementById('keyword').value || 'plaque personnalisée';
|
||
const title = document.getElementById('title').value || 'Guide complet';
|
||
const personality = document.getElementById('personality').value || 'Marc';
|
||
|
||
updateStatus('Étape 0: Génération Normale Obligatoire');
|
||
|
||
// ÉTAPE OBLIGATOIRE: Génération normale avec Claude
|
||
let currentText;
|
||
try {
|
||
const normalResponse = await fetch('/api/generate-normal', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ keyword, title, personality })
|
||
});
|
||
|
||
const normalResult = await normalResponse.json();
|
||
|
||
if (normalResult.success) {
|
||
currentText = normalResult.content;
|
||
// Sauvegarder le contenu structuré pour les modules
|
||
window.currentStructuredContent = normalResult.structuredContent;
|
||
} else {
|
||
throw new Error(normalResult.error);
|
||
}
|
||
} catch (error) {
|
||
console.error('Erreur génération normale:', error);
|
||
// Fallback sur un texte simple
|
||
currentText = `# ${title}
|
||
|
||
## Introduction
|
||
|
||
${keyword} représente un marché en pleine expansion avec de nombreuses possibilités créatives. Dans ce guide complet, nous explorons les différentes options disponibles pour créer une ${keyword} qui répond parfaitement à vos besoins.
|
||
|
||
## Les avantages d'une ${keyword}
|
||
|
||
Une ${keyword} de qualité offre plusieurs avantages significatifs pour votre projet. Elle permet de personnaliser votre espace tout en conservant un aspect professionnel et esthétique.
|
||
|
||
## Conclusion
|
||
|
||
En conclusion, choisir la bonne ${keyword} nécessite une réflexion approfondie sur vos besoins spécifiques et votre budget disponible.`;
|
||
}
|
||
|
||
// Afficher le texte de génération normale (OBLIGATOIRE)
|
||
addStepResult('🌱 Génération Normale (OBLIGATOIRE)', 'Création du contenu de base avec Claude - Étape requise avant tout module', currentText, 0);
|
||
|
||
// Exécuter chaque étape du pipeline
|
||
for (let i = 0; i < pipeline.length; i++) {
|
||
const moduleId = pipeline[i];
|
||
const module = modules[moduleId];
|
||
|
||
updateStatus(`Étape ${i + 1}/${pipeline.length}: ${module.name}`);
|
||
|
||
try {
|
||
// Appel API du vrai module
|
||
// Utiliser le contenu structuré si disponible, sinon le texte courant
|
||
const contentToSend = window.currentStructuredContent ?
|
||
{ structuredContent: window.currentStructuredContent } :
|
||
currentText;
|
||
|
||
const moduleResponse = await fetch('/api/apply-module', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
moduleId,
|
||
content: contentToSend,
|
||
config: { keyword, personality }
|
||
})
|
||
});
|
||
|
||
const moduleResult = await moduleResponse.json();
|
||
|
||
if (moduleResult.success) {
|
||
// Le module retourne un objet avec statistiques, extraire le contenu
|
||
const responseContent = moduleResult.content;
|
||
|
||
if (typeof responseContent === 'string') {
|
||
currentText = responseContent;
|
||
// Plus de contenu structuré disponible
|
||
window.currentStructuredContent = null;
|
||
} else if (responseContent && typeof responseContent === 'object') {
|
||
// Si c'est un objet structuré, le garder pour les prochains modules
|
||
window.currentStructuredContent = responseContent;
|
||
|
||
// Assembler pour l'affichage
|
||
currentText = [
|
||
responseContent.Titre_H1 && `# ${responseContent.Titre_H1}`,
|
||
responseContent.Introduction && `## Introduction\n\n${responseContent.Introduction}`,
|
||
responseContent.Contenu_Principal && `## Contenu Principal\n\n${responseContent.Contenu_Principal}`,
|
||
responseContent.Conclusion && `## Conclusion\n\n${responseContent.Conclusion}`
|
||
].filter(Boolean).join('\n\n') || JSON.stringify(responseContent, null, 2);
|
||
} else {
|
||
currentText = JSON.stringify(responseContent, null, 2);
|
||
window.currentStructuredContent = null;
|
||
}
|
||
|
||
console.log('Module response:', moduleResult);
|
||
} else {
|
||
throw new Error(moduleResult.error);
|
||
}
|
||
|
||
addStepResult(
|
||
`${i + 1}. ${module.name}`,
|
||
module.action,
|
||
currentText,
|
||
i + 1
|
||
);
|
||
|
||
} catch (error) {
|
||
console.error(`Erreur module ${moduleId}:`, error);
|
||
addStepResult(
|
||
`${i + 1}. ${module.name} (ERREUR)`,
|
||
`Erreur: ${error.message}`,
|
||
currentText,
|
||
i + 1
|
||
);
|
||
}
|
||
}
|
||
|
||
executing = false;
|
||
updateExecuteButton();
|
||
updateStatus('Terminé');
|
||
}
|
||
|
||
// Plus besoin de simulation - on utilise les vrais modules !
|
||
|
||
function addStepResult(title, description, content, stepNumber = 0) {
|
||
const resultsArea = document.getElementById('results-area');
|
||
|
||
const stepDiv = document.createElement('div');
|
||
stepDiv.className = 'step-result';
|
||
|
||
const contentId = `content-${Date.now()}-${stepNumber}`;
|
||
|
||
stepDiv.innerHTML = `
|
||
<div class="step-header">
|
||
<span>${title}</span>
|
||
<div>
|
||
<button class="copy-btn" onclick="copyContent('${contentId}', event)">📋 Copier</button>
|
||
<span style="margin-left: 15px;">${new Date().toLocaleTimeString()}</span>
|
||
</div>
|
||
</div>
|
||
<div class="step-content">
|
||
<p><strong>Action:</strong> ${description}</p>
|
||
<details ${stepNumber === 0 ? 'open' : ''}>
|
||
<summary style="cursor: pointer; margin: 15px 0 10px 0; padding: 10px; background: #f8f9fa; border-radius: 5px;">
|
||
📄 Contenu généré (${content ? content.length : 0} caractères)
|
||
</summary>
|
||
<pre id="${contentId}">${content || 'Aucun contenu disponible'}</pre>
|
||
</details>
|
||
</div>
|
||
`;
|
||
|
||
resultsArea.appendChild(stepDiv);
|
||
|
||
// Auto-scroll vers le dernier résultat
|
||
stepDiv.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
||
}
|
||
|
||
// Fonction pour copier le contenu
|
||
window.copyContent = function(contentId, event) {
|
||
const contentElement = document.getElementById(contentId);
|
||
if (!contentElement) {
|
||
console.error('Element not found:', contentId);
|
||
alert('Élément non trouvé');
|
||
return;
|
||
}
|
||
|
||
const textToCopy = contentElement.textContent || contentElement.innerText;
|
||
|
||
if (!textToCopy || textToCopy.trim() === '') {
|
||
alert('Aucun contenu à copier');
|
||
return;
|
||
}
|
||
|
||
navigator.clipboard.writeText(textToCopy).then(() => {
|
||
// Feedback visuel
|
||
const btn = event.target;
|
||
const originalText = btn.textContent;
|
||
btn.textContent = '✅ Copié !';
|
||
btn.style.background = 'rgba(39, 174, 96, 0.3)';
|
||
|
||
setTimeout(() => {
|
||
btn.textContent = originalText;
|
||
btn.style.background = 'rgba(255,255,255,0.2)';
|
||
}, 2000);
|
||
}).catch(err => {
|
||
console.error('Erreur copie:', err);
|
||
alert('Erreur lors de la copie: ' + err.message);
|
||
});
|
||
}
|
||
|
||
// Initialisation
|
||
updateStepCount();
|
||
updateExecuteButton();
|
||
</script>
|
||
</body>
|
||
</html> |