seo-generator-server/public/pipeline-runner.js
StillHammer 471058f731 Add flexible pipeline system with per-module LLM configuration
- New modular pipeline architecture allowing custom workflow combinations
- Per-step LLM provider configuration (Claude, OpenAI, Gemini, Deepseek, Moonshot, Mistral)
- Visual pipeline builder and runner interfaces with drag-and-drop
- 10 predefined pipeline templates (minimal-test to originality-bypass)
- Pipeline CRUD operations via ConfigManager and REST API
- Fix variable resolution in instructions (HTML tags were breaking {{variables}})
- Fix hardcoded LLM providers in AdversarialCore
- Add TESTS_LLM_PROVIDER.md documentation with validation results
- Update dashboard to disable legacy config editor

API Endpoints:
- POST /api/pipeline/save, execute, validate, estimate
- GET /api/pipeline/list, modules, templates

Backward compatible with legacy modular workflow system.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-09 14:01:52 +08:00

246 lines
7.5 KiB
JavaScript

/**
* Pipeline Runner - Client Side Logic
* Gestion de l'exécution des pipelines sauvegardés
*/
// État global
const state = {
pipelines: [],
selectedPipeline: null,
running: false
};
// ====================
// INITIALIZATION
// ====================
window.onload = async function() {
await loadPipelinesList();
};
// Charger la liste des pipelines
async function loadPipelinesList() {
try {
const response = await fetch('/api/pipeline/list');
const data = await response.json();
if (data.success) {
state.pipelines = data.pipelines;
renderPipelinesDropdown();
}
} catch (error) {
showStatus(`Erreur chargement pipelines: ${error.message}`, 'error');
}
}
// Rendre le dropdown de pipelines
function renderPipelinesDropdown() {
const select = document.getElementById('pipelineSelect');
select.innerHTML = '<option value="">-- Sélectionner un pipeline --</option>';
state.pipelines.forEach(pipeline => {
const option = document.createElement('option');
option.value = pipeline.name;
option.textContent = `${pipeline.name} (${pipeline.steps} étapes, ~${pipeline.estimatedDuration})`;
select.appendChild(option);
});
}
// ====================
// PIPELINE LOADING
// ====================
async function loadPipeline() {
const select = document.getElementById('pipelineSelect');
const pipelineName = select.value;
if (!pipelineName) {
document.getElementById('pipelinePreview').style.display = 'none';
document.getElementById('btnRun').disabled = true;
return;
}
try {
const response = await fetch(`/api/pipeline/${pipelineName}`);
const data = await response.json();
if (data.success) {
state.selectedPipeline = data.pipeline;
displayPipelinePreview(data.pipeline);
document.getElementById('btnRun').disabled = false;
}
} catch (error) {
showStatus(`Erreur chargement pipeline: ${error.message}`, 'error');
}
}
// Afficher la prévisualisation du pipeline
function displayPipelinePreview(pipeline) {
const preview = document.getElementById('pipelinePreview');
preview.style.display = 'block';
document.getElementById('pipelineName').textContent = pipeline.name;
document.getElementById('pipelineDesc').textContent = pipeline.description || 'Pas de description';
document.getElementById('summarySteps').textContent = pipeline.pipeline.length;
// Estimation durée
const estimatedSeconds = pipeline.pipeline.length * 20; // Rough estimate
const minutes = Math.floor(estimatedSeconds / 60);
const seconds = estimatedSeconds % 60;
document.getElementById('summaryDuration').textContent = minutes > 0
? `${minutes}m ${seconds}s`
: `${seconds}s`;
// Liste des étapes
const stepList = document.getElementById('stepList');
stepList.innerHTML = '';
pipeline.pipeline.forEach(step => {
const div = document.createElement('div');
div.className = 'step-item';
div.textContent = `${step.step}. ${step.module} (${step.mode}) - Intensité: ${step.intensity}`;
stepList.appendChild(div);
});
}
// ====================
// PIPELINE EXECUTION
// ====================
async function runPipeline() {
if (!state.selectedPipeline) {
showStatus('Aucun pipeline sélectionné', 'error');
return;
}
if (state.running) {
showStatus('Une exécution est déjà en cours', 'error');
return;
}
const rowNumber = parseInt(document.getElementById('rowNumber').value);
if (!rowNumber || rowNumber < 2) {
showStatus('Numéro de ligne invalide (minimum 2)', 'error');
return;
}
state.running = true;
document.getElementById('btnRun').disabled = true;
// Show progress section
document.getElementById('progressSection').style.display = 'block';
document.getElementById('progressBar').style.display = 'block';
document.getElementById('progressText').style.display = 'block';
document.getElementById('resultsSection').style.display = 'none';
showStatus('🚀 Exécution du pipeline en cours...', 'loading');
updateProgress(0, 'Initialisation...');
try {
const response = await fetch('/api/pipeline/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
pipelineConfig: state.selectedPipeline,
rowNumber: rowNumber
})
});
updateProgress(50, 'Traitement en cours...');
const data = await response.json();
updateProgress(100, 'Terminé!');
if (data.success) {
displayResults(data.result);
showStatus('✅ Pipeline exécuté avec succès!', 'success');
} else {
showStatus(`❌ Erreur: ${data.error}`, 'error');
}
} catch (error) {
showStatus(`❌ Erreur exécution: ${error.message}`, 'error');
console.error('Execution error:', error);
} finally {
state.running = false;
document.getElementById('btnRun').disabled = false;
setTimeout(() => {
document.getElementById('progressSection').style.display = 'none';
}, 2000);
}
}
// ====================
// RESULTS DISPLAY
// ====================
function displayResults(result) {
const resultsSection = document.getElementById('resultsSection');
resultsSection.style.display = 'block';
// Stats
document.getElementById('statDuration').textContent =
`${result.stats.totalDuration}ms`;
document.getElementById('statSuccessSteps').textContent =
`${result.stats.successfulSteps}/${result.stats.totalSteps}`;
document.getElementById('statPersonality').textContent =
result.stats.personality || 'N/A';
// Execution log
const logContainer = document.getElementById('executionLog');
logContainer.innerHTML = '';
if (result.executionLog && result.executionLog.length > 0) {
result.executionLog.forEach(logEntry => {
const div = document.createElement('div');
div.className = `log-entry ${logEntry.success ? 'log-success' : 'log-error'}`;
const status = logEntry.success ? '✓' : '✗';
const text = `${status} Étape ${logEntry.step}: ${logEntry.module} (${logEntry.mode}) ` +
`- ${logEntry.duration}ms`;
if (logEntry.modifications !== undefined) {
div.textContent = text + ` - ${logEntry.modifications} modifs`;
} else {
div.textContent = text;
}
if (!logEntry.success && logEntry.error) {
div.textContent += ` - Erreur: ${logEntry.error}`;
}
logContainer.appendChild(div);
});
} else {
logContainer.textContent = 'Aucun log d\'exécution disponible';
}
}
// ====================
// HELPERS
// ====================
function updateProgress(percentage, text) {
document.getElementById('progressFill').style.width = percentage + '%';
document.getElementById('progressText').textContent = text;
}
function showStatus(message, type) {
const status = document.getElementById('status');
status.textContent = message;
status.className = `status ${type}`;
status.style.display = 'block';
if (type !== 'loading') {
setTimeout(() => {
status.style.display = 'none';
}, 5000);
}
}