- 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>
246 lines
7.5 KiB
JavaScript
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);
|
|
}
|
|
}
|