v1.0 - Version stable: multi-PC, détection UI-DETR-1, 3 modes exécution

- Frontend v4 accessible sur réseau local (192.168.1.40)
- Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard)
- Ollama GPU fonctionnel
- Self-healing interactif
- Dashboard confiance

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Dom
2026-01-29 11:23:51 +01:00
parent 21bfa3b337
commit a27b74cf22
1595 changed files with 412691 additions and 400 deletions

View File

@@ -64,6 +64,7 @@
<div class="tab" onclick="switchTab('system')">⚙️ Système</div>
<div class="tab" onclick="switchTab('corrections')">🔧 Corrections</div>
<div class="tab" onclick="switchTab('learning')">🧠 Apprentissage</div>
<div class="tab" onclick="switchTab('config')">🔧 Configuration</div>
</div>
<div class="container">
@@ -662,10 +663,259 @@
</div>
</div>
</div>
<!-- Tab: Configuration -->
<div id="tab-config" class="tab-content">
<div class="card" style="margin-bottom:20px;background:linear-gradient(135deg, #1e293b 0%, #0f172a 100%);">
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:15px;">
<div>
<h2 style="margin-bottom:5px;color:#e2e8f0;"><span class="icon">🔧</span> Configuration Systeme</h2>
<p style="color:#64748b;font-size:13px;">Configurez les services, modeles LLM/VLM, base de donnees et parametres de securite</p>
</div>
<div style="display:flex;gap:10px;">
<button class="btn btn-success" onclick="saveConfig()">💾 Sauvegarder</button>
<button class="btn btn-primary" onclick="refreshConfig()">🔄 Actualiser</button>
<button class="btn btn-secondary" onclick="exportConfig()">📥 Exporter</button>
<label class="btn btn-secondary" style="cursor:pointer;">
📤 Importer
<input type="file" id="importConfigFile" accept=".json" style="display:none;" onchange="importConfig(this)">
</label>
</div>
</div>
</div>
<div class="grid grid-2">
<!-- Section Services -->
<div class="card">
<h2><span class="icon">🌐</span> Services & Ports</h2>
<div id="configServices" class="config-section">
<div class="config-item">
<label>VWB Backend</label>
<div style="display:flex;gap:10px;">
<input type="text" id="cfg_vwb_backend_host" placeholder="localhost" class="config-input" style="flex:2;">
<input type="number" id="cfg_vwb_backend_port" placeholder="5000" class="config-input" style="flex:1;">
<button class="btn btn-small btn-primary" onclick="testConnection('service', 'vwb_backend')">Test</button>
</div>
</div>
<div class="config-item">
<label>VWB Frontend</label>
<div style="display:flex;gap:10px;">
<input type="text" id="cfg_vwb_frontend_host" placeholder="localhost" class="config-input" style="flex:2;">
<input type="number" id="cfg_vwb_frontend_port" placeholder="3000" class="config-input" style="flex:1;">
<button class="btn btn-small btn-primary" onclick="testConnection('service', 'vwb_frontend')">Test</button>
</div>
</div>
<div class="config-item">
<label>Agent Chat</label>
<div style="display:flex;gap:10px;">
<input type="text" id="cfg_agent_chat_host" placeholder="localhost" class="config-input" style="flex:2;">
<input type="number" id="cfg_agent_chat_port" placeholder="5002" class="config-input" style="flex:1;">
<button class="btn btn-small btn-primary" onclick="testConnection('service', 'agent_chat')">Test</button>
</div>
</div>
<div class="config-item">
<label>API Upload</label>
<div style="display:flex;gap:10px;">
<input type="text" id="cfg_upload_api_host" placeholder="localhost" class="config-input" style="flex:2;">
<input type="number" id="cfg_upload_api_port" placeholder="8000" class="config-input" style="flex:1;">
<button class="btn btn-small btn-primary" onclick="testConnection('service', 'upload_api')">Test</button>
</div>
</div>
</div>
</div>
<!-- Section LLM/VLM -->
<div class="card">
<h2><span class="icon">🤖</span> Modeles LLM & VLM</h2>
<div id="configLLM" class="config-section">
<div class="config-item">
<label>Provider</label>
<select id="cfg_llm_provider" class="config-input">
<option value="ollama">Ollama (local)</option>
<option value="openai">OpenAI</option>
<option value="anthropic">Anthropic</option>
</select>
</div>
<div class="config-item">
<label>URL Ollama</label>
<div style="display:flex;gap:10px;">
<input type="text" id="cfg_llm_base_url" placeholder="http://localhost:11434" class="config-input" style="flex:1;">
<button class="btn btn-small btn-primary" onclick="testOllamaConnection()">Test</button>
</div>
</div>
<div class="config-item">
<label>Modele LLM</label>
<div style="display:flex;gap:10px;">
<select id="cfg_llm_model" class="config-input" style="flex:1;">
<option value="">Chargement...</option>
</select>
<button class="btn btn-small btn-secondary" onclick="refreshOllamaModels()">🔄</button>
</div>
</div>
<div class="config-item">
<label>Modele VLM</label>
<select id="cfg_vlm_model" class="config-input">
<option value="">Chargement...</option>
</select>
</div>
<div class="config-item">
<label>Temperature</label>
<input type="range" id="cfg_llm_temperature" min="0" max="2" step="0.1" value="0.7" class="config-input" oninput="document.getElementById('tempValue').textContent = this.value">
<span id="tempValue" style="color:#3b82f6;margin-left:10px;">0.7</span>
</div>
<div class="config-item">
<label>Max Tokens</label>
<input type="number" id="cfg_llm_max_tokens" placeholder="2048" class="config-input" value="2048">
</div>
</div>
</div>
</div>
<div class="grid grid-2">
<!-- Section Detection -->
<div class="card">
<h2><span class="icon">👁️</span> Detection Visuelle</h2>
<div id="configDetection" class="config-section">
<div class="config-item">
<label>Modele OWL</label>
<select id="cfg_detection_owl_model" class="config-input">
<option value="google/owlv2-base-patch16-ensemble">OWLv2 Base Ensemble</option>
<option value="google/owlv2-large-patch14-ensemble">OWLv2 Large Ensemble</option>
<option value="google/owlvit-base-patch32">OWLViT Base</option>
</select>
</div>
<div class="config-item">
<label>Seuil de confiance</label>
<input type="range" id="cfg_detection_confidence" min="0.1" max="0.9" step="0.05" value="0.3" class="config-input" oninput="document.getElementById('confValue').textContent = this.value">
<span id="confValue" style="color:#3b82f6;margin-left:10px;">0.3</span>
</div>
<div class="config-item">
<label>Seuil NMS</label>
<input type="range" id="cfg_detection_nms" min="0.1" max="0.9" step="0.05" value="0.3" class="config-input" oninput="document.getElementById('nmsValue').textContent = this.value">
<span id="nmsValue" style="color:#3b82f6;margin-left:10px;">0.3</span>
</div>
<div class="config-item">
<label>
<input type="checkbox" id="cfg_detection_use_gpu" checked>
Utiliser GPU (CUDA)
</label>
</div>
</div>
</div>
<!-- Section Base de donnees -->
<div class="card">
<h2><span class="icon">💾</span> Base de Donnees</h2>
<div id="configDatabase" class="config-section">
<div class="config-item">
<label>Type</label>
<select id="cfg_database_type" class="config-input">
<option value="sqlite">SQLite (local)</option>
<option value="postgresql">PostgreSQL</option>
<option value="mysql">MySQL</option>
</select>
</div>
<div class="config-item">
<label>Chemin / URL</label>
<div style="display:flex;gap:10px;">
<input type="text" id="cfg_database_path" placeholder="data/training/workflows.db" class="config-input" style="flex:1;">
<button class="btn btn-small btn-primary" onclick="testDatabaseConnection()">Test</button>
</div>
</div>
<div class="config-item">
<label>
<input type="checkbox" id="cfg_database_backup_enabled" checked>
Backup automatique
</label>
</div>
<div class="config-item">
<label>Intervalle backup (heures)</label>
<input type="number" id="cfg_database_backup_interval" value="24" class="config-input">
</div>
</div>
</div>
</div>
<div class="grid grid-2">
<!-- Section Securite -->
<div class="card">
<h2><span class="icon">🔒</span> Securite</h2>
<div id="configSecurity" class="config-section">
<div class="config-item">
<label>
<input type="checkbox" id="cfg_security_encryption" checked>
Chiffrement des donnees
</label>
</div>
<div class="config-item">
<label>
<input type="checkbox" id="cfg_security_auth">
Authentification requise
</label>
</div>
<div class="config-item">
<label>Timeout session (minutes)</label>
<input type="number" id="cfg_security_timeout" value="60" class="config-input">
</div>
<div class="config-item">
<label>Origines autorisees (CORS)</label>
<input type="text" id="cfg_security_origins" placeholder="http://localhost:3000,http://localhost:5001" class="config-input">
</div>
</div>
</div>
<!-- Section Logs -->
<div class="card">
<h2><span class="icon">📝</span> Logs</h2>
<div id="configLogging" class="config-section">
<div class="config-item">
<label>Niveau de log</label>
<select id="cfg_logging_level" class="config-input">
<option value="DEBUG">DEBUG</option>
<option value="INFO" selected>INFO</option>
<option value="WARNING">WARNING</option>
<option value="ERROR">ERROR</option>
</select>
</div>
<div class="config-item">
<label>Fichier de log</label>
<input type="text" id="cfg_logging_file" placeholder="logs/rpa_vision.log" class="config-input">
</div>
<div class="config-item">
<label>Taille max (MB)</label>
<input type="number" id="cfg_logging_max_size" value="50" class="config-input">
</div>
<div class="config-item">
<label>Nombre de backups</label>
<input type="number" id="cfg_logging_backup_count" value="5" class="config-input">
</div>
</div>
</div>
</div>
<!-- Resultat des tests -->
<div class="card" style="margin-top:20px;" id="configTestResults" style="display:none;">
<h2><span class="icon"></span> Resultats des tests</h2>
<div id="testResultsContent" style="padding:15px;background:#0f172a;border-radius:8px;">
<p style="color:#64748b;text-align:center;">Cliquez sur "Test" pour verifier les connexions</p>
</div>
</div>
</div>
</div>
<style>
.execution-panel { padding: 15px; background: #0f172a; border-radius: 8px; }
/* Configuration styles */
.config-section { display: flex; flex-direction: column; gap: 15px; padding: 10px 0; }
.config-item { display: flex; flex-direction: column; gap: 5px; }
.config-item label { color: #94a3b8; font-size: 13px; font-weight: 500; display: flex; align-items: center; gap: 8px; }
.config-item label input[type="checkbox"] { width: 18px; height: 18px; accent-color: #3b82f6; }
.config-input { width: 100%; padding: 10px 12px; background: #0f172a; border: 1px solid #334155; border-radius: 8px; color: #e2e8f0; font-size: 14px; transition: border-color 0.2s; }
.config-input:focus { outline: none; border-color: #3b82f6; }
.config-input:hover { border-color: #475569; }
select.config-input { cursor: pointer; }
input[type="range"].config-input { padding: 0; height: 6px; border: none; cursor: pointer; }
.exec-idle { color: #64748b; text-align: center; padding: 30px; }
.exec-running { color: #22c55e; }
.exec-controls { display: flex; flex-direction: column; gap: 15px; }
@@ -887,6 +1137,7 @@
if (tabName === 'system') refreshSystemInfo();
if (tabName === 'corrections') refreshCorrectionPacks();
if (tabName === 'learning') refreshLearningStats();
if (tabName === 'config') refreshConfig();
}
// Update execution UI
@@ -2516,6 +2767,367 @@
}
}
// ============================================================
// SECTION: Configuration
// ============================================================
let currentConfig = {};
async function refreshConfig() {
try {
const data = await fetchJSON('/api/config');
if (data.success) {
currentConfig = data.config;
populateConfigForm(data.config);
showNotification('Configuration chargee', 'success');
}
} catch (e) {
console.error('Error loading config:', e);
showNotification('Erreur chargement config: ' + e.message, 'error');
}
}
function populateConfigForm(config) {
// Services
if (config.services) {
for (const [key, svc] of Object.entries(config.services)) {
const hostEl = document.getElementById(`cfg_${key}_host`);
const portEl = document.getElementById(`cfg_${key}_port`);
if (hostEl) hostEl.value = svc.host || 'localhost';
if (portEl) portEl.value = svc.port || '';
}
}
// LLM
if (config.llm) {
document.getElementById('cfg_llm_provider').value = config.llm.provider || 'ollama';
document.getElementById('cfg_llm_base_url').value = config.llm.base_url || 'http://localhost:11434';
document.getElementById('cfg_llm_temperature').value = config.llm.temperature || 0.7;
document.getElementById('tempValue').textContent = config.llm.temperature || 0.7;
document.getElementById('cfg_llm_max_tokens').value = config.llm.max_tokens || 2048;
// Charger les modeles Ollama
refreshOllamaModels();
}
// Detection
if (config.detection) {
document.getElementById('cfg_detection_owl_model').value = config.detection.owl_model || 'google/owlv2-base-patch16-ensemble';
document.getElementById('cfg_detection_confidence').value = config.detection.confidence_threshold || 0.3;
document.getElementById('confValue').textContent = config.detection.confidence_threshold || 0.3;
document.getElementById('cfg_detection_nms').value = config.detection.nms_threshold || 0.3;
document.getElementById('nmsValue').textContent = config.detection.nms_threshold || 0.3;
document.getElementById('cfg_detection_use_gpu').checked = config.detection.use_gpu !== false;
}
// Database
if (config.database) {
document.getElementById('cfg_database_type').value = config.database.type || 'sqlite';
document.getElementById('cfg_database_path').value = config.database.path || 'data/training/workflows.db';
document.getElementById('cfg_database_backup_enabled').checked = config.database.backup_enabled !== false;
document.getElementById('cfg_database_backup_interval').value = config.database.backup_interval_hours || 24;
}
// Security
if (config.security) {
document.getElementById('cfg_security_encryption').checked = config.security.enable_encryption !== false;
document.getElementById('cfg_security_auth').checked = config.security.require_authentication === true;
document.getElementById('cfg_security_timeout').value = config.security.session_timeout_minutes || 60;
document.getElementById('cfg_security_origins').value = (config.security.allowed_origins || []).join(',');
}
// Logging
if (config.logging) {
document.getElementById('cfg_logging_level').value = config.logging.level || 'INFO';
document.getElementById('cfg_logging_file').value = config.logging.file_path || 'logs/rpa_vision.log';
document.getElementById('cfg_logging_max_size').value = config.logging.max_size_mb || 50;
document.getElementById('cfg_logging_backup_count').value = config.logging.backup_count || 5;
}
}
function collectConfigFromForm() {
const config = {
version: currentConfig.version || '1.0.0',
services: {
vwb_backend: {
host: document.getElementById('cfg_vwb_backend_host').value || 'localhost',
port: parseInt(document.getElementById('cfg_vwb_backend_port').value) || 5000,
description: 'Visual Workflow Builder - Backend API'
},
vwb_frontend: {
host: document.getElementById('cfg_vwb_frontend_host').value || 'localhost',
port: parseInt(document.getElementById('cfg_vwb_frontend_port').value) || 3000,
description: 'Visual Workflow Builder - Interface React'
},
web_dashboard: {
host: 'localhost',
port: 5001,
description: 'Dashboard de monitoring RPA'
},
agent_chat: {
host: document.getElementById('cfg_agent_chat_host').value || 'localhost',
port: parseInt(document.getElementById('cfg_agent_chat_port').value) || 5002,
description: 'Agent conversationnel RPA'
},
upload_api: {
host: document.getElementById('cfg_upload_api_host').value || 'localhost',
port: parseInt(document.getElementById('cfg_upload_api_port').value) || 8000,
description: 'API d\'upload de sessions'
}
},
llm: {
provider: document.getElementById('cfg_llm_provider').value,
base_url: document.getElementById('cfg_llm_base_url').value,
model: document.getElementById('cfg_llm_model').value,
temperature: parseFloat(document.getElementById('cfg_llm_temperature').value),
max_tokens: parseInt(document.getElementById('cfg_llm_max_tokens').value),
description: 'Modele LLM pour le parsing de commandes'
},
vlm: {
provider: document.getElementById('cfg_llm_provider').value,
base_url: document.getElementById('cfg_llm_base_url').value,
model: document.getElementById('cfg_vlm_model').value,
description: 'Modele VLM pour l\'analyse visuelle'
},
detection: {
owl_model: document.getElementById('cfg_detection_owl_model').value,
confidence_threshold: parseFloat(document.getElementById('cfg_detection_confidence').value),
nms_threshold: parseFloat(document.getElementById('cfg_detection_nms').value),
use_gpu: document.getElementById('cfg_detection_use_gpu').checked,
description: 'Configuration du detecteur visuel OWL-v2'
},
embedding: currentConfig.embedding || {
model: 'clip',
dimension: 512,
use_gpu: true,
cache_size: 10000,
description: 'Configuration des embeddings visuels'
},
database: {
type: document.getElementById('cfg_database_type').value,
path: document.getElementById('cfg_database_path').value,
backup_enabled: document.getElementById('cfg_database_backup_enabled').checked,
backup_interval_hours: parseInt(document.getElementById('cfg_database_backup_interval').value),
description: 'Base de donnees des workflows'
},
faiss: currentConfig.faiss || {
index_type: 'Flat',
use_gpu: true,
nprobe: 10,
description: 'Configuration de l\'index FAISS'
},
security: {
enable_encryption: document.getElementById('cfg_security_encryption').checked,
session_timeout_minutes: parseInt(document.getElementById('cfg_security_timeout').value),
require_authentication: document.getElementById('cfg_security_auth').checked,
allowed_origins: document.getElementById('cfg_security_origins').value.split(',').map(s => s.trim()).filter(s => s),
description: 'Parametres de securite'
},
logging: {
level: document.getElementById('cfg_logging_level').value,
file_path: document.getElementById('cfg_logging_file').value,
max_size_mb: parseInt(document.getElementById('cfg_logging_max_size').value),
backup_count: parseInt(document.getElementById('cfg_logging_backup_count').value),
description: 'Configuration des logs'
}
};
return config;
}
async function saveConfig() {
try {
const config = collectConfigFromForm();
showNotification('Sauvegarde en cours...', 'info');
const response = await fetchJSON('/api/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config)
});
if (response.success) {
currentConfig = config;
showNotification('Configuration sauvegardee avec succes', 'success');
} else {
showNotification('Erreur: ' + (response.error || 'Inconnu'), 'error');
}
} catch (e) {
showNotification('Erreur: ' + e.message, 'error');
}
}
async function refreshOllamaModels() {
try {
const data = await fetchJSON('/api/config/ollama-models');
const llmSelect = document.getElementById('cfg_llm_model');
const vlmSelect = document.getElementById('cfg_vlm_model');
if (data.success && data.models) {
const llmOptions = data.models.map(m => `<option value="${m}" ${m === currentConfig.llm?.model ? 'selected' : ''}>${m}</option>`).join('');
const vlmOptions = data.models.map(m => `<option value="${m}" ${m === currentConfig.vlm?.model ? 'selected' : ''}>${m}</option>`).join('');
llmSelect.innerHTML = llmOptions || '<option value="">Aucun modele</option>';
vlmSelect.innerHTML = vlmOptions || '<option value="">Aucun modele</option>';
} else {
llmSelect.innerHTML = '<option value="">Ollama inaccessible</option>';
vlmSelect.innerHTML = '<option value="">Ollama inaccessible</option>';
}
} catch (e) {
console.error('Error loading Ollama models:', e);
document.getElementById('cfg_llm_model').innerHTML = '<option value="">Erreur chargement</option>';
document.getElementById('cfg_vlm_model').innerHTML = '<option value="">Erreur chargement</option>';
}
}
async function testOllamaConnection() {
const baseUrl = document.getElementById('cfg_llm_base_url').value;
showNotification('Test connexion Ollama...', 'info');
try {
const response = await fetchJSON('/api/config/test-connection', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'ollama', base_url: baseUrl })
});
updateTestResults('Ollama', response.success, response.message || response.error);
if (response.success) {
showNotification('Connexion Ollama OK - ' + (response.models?.length || 0) + ' modeles', 'success');
await refreshOllamaModels();
} else {
showNotification('Erreur Ollama: ' + response.error, 'error');
}
} catch (e) {
updateTestResults('Ollama', false, e.message);
showNotification('Erreur: ' + e.message, 'error');
}
}
async function testDatabaseConnection() {
const path = document.getElementById('cfg_database_path').value;
showNotification('Test connexion base de donnees...', 'info');
try {
const response = await fetchJSON('/api/config/test-connection', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'database', path: path })
});
updateTestResults('Database', response.success, response.message || response.error);
if (response.success) {
showNotification('Base de donnees accessible', 'success');
} else {
showNotification('Erreur DB: ' + response.error, 'error');
}
} catch (e) {
updateTestResults('Database', false, e.message);
showNotification('Erreur: ' + e.message, 'error');
}
}
async function testConnection(type, serviceKey) {
const hostEl = document.getElementById(`cfg_${serviceKey}_host`);
const portEl = document.getElementById(`cfg_${serviceKey}_port`);
if (!hostEl || !portEl) return;
showNotification(`Test connexion ${serviceKey}...`, 'info');
try {
const response = await fetchJSON('/api/config/test-connection', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'service',
host: hostEl.value,
port: portEl.value
})
});
updateTestResults(serviceKey, response.success, response.message || response.error);
if (response.success) {
showNotification(`${serviceKey} accessible`, 'success');
} else {
showNotification(`${serviceKey} inaccessible: ` + response.error, 'error');
}
} catch (e) {
updateTestResults(serviceKey, false, e.message);
showNotification('Erreur: ' + e.message, 'error');
}
}
function updateTestResults(name, success, message) {
const container = document.getElementById('testResultsContent');
const existing = container.querySelector(`[data-test="${name}"]`);
const html = `
<div data-test="${name}" style="display:flex;align-items:center;gap:10px;padding:8px;border-radius:6px;margin-bottom:5px;background:${success ? 'rgba(34,197,94,0.1)' : 'rgba(239,68,68,0.1)'};">
<span style="color:${success ? '#22c55e' : '#ef4444'};">${success ? '✅' : '❌'}</span>
<span style="color:#e2e8f0;font-weight:500;">${name}</span>
<span style="color:#94a3b8;font-size:12px;">${message}</span>
</div>
`;
if (existing) {
existing.outerHTML = html;
} else {
if (container.querySelector('.loading') || container.textContent.includes('Cliquez')) {
container.innerHTML = html;
} else {
container.innerHTML += html;
}
}
}
async function exportConfig() {
try {
const response = await fetch('/api/config/export');
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `rpa_config_${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
showNotification('Configuration exportee', 'success');
} catch (e) {
showNotification('Erreur export: ' + e.message, 'error');
}
}
async function importConfig(input) {
if (!input.files.length) return;
const file = input.files[0];
showNotification('Import en cours...', 'info');
try {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/config/import', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
showNotification('Configuration importee avec succes', 'success');
await refreshConfig();
} else {
showNotification('Erreur import: ' + data.error, 'error');
}
} catch (e) {
showNotification('Erreur import: ' + e.message, 'error');
}
input.value = ''; // Reset file input
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
initOverviewChart();