Refonte majeure du système Agent Chat et ajout de nombreux modules : - Chat unifié : suppression du dual Workflows/Agent Libre, tout passe par /api/chat avec résolution en 3 niveaux (workflow → geste → "montre-moi") - GestureCatalog : 38 raccourcis clavier universels Windows avec matching sémantique, substitution automatique dans les replays, et endpoint /api/gestures - Mode Copilot : exécution pas-à-pas des workflows avec validation humaine via WebSocket (approve/skip/abort) avant chaque action - Léa UI (agent_v0/lea_ui/) : interface PyQt5 pour Windows avec overlay transparent pour feedback visuel pendant le replay - Data Extraction (core/extraction/) : moteur d'extraction visuelle de données (OCR + VLM → SQLite), avec schémas YAML et export CSV/Excel - ReplayVerifier (agent_v0/server_v1/) : vérification post-action par comparaison de screenshots, avec logique de retry (max 3) - IntentParser durci : meilleur fallback regex, type GREETING, patterns améliorés - Dashboard : nouvelles pages gestures, streaming, extractions - Tests : 63 tests GestureCatalog, 47 tests extraction, corrections tests existants - Dépréciation : /api/agent/plan et /api/agent/execute retournent HTTP 410, suppression du code hardcodé _plan_to_replay_actions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
215 lines
11 KiB
HTML
215 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>RPA Vision V3 - Extractions</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #0f172a; color: #e2e8f0; min-height: 100vh; }
|
|
.header { background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%); color: white; padding: 20px 30px; display: flex; justify-content: space-between; align-items: center; }
|
|
.header h1 { font-size: 24px; display: flex; align-items: center; gap: 10px; }
|
|
.header .nav-links { display: flex; gap: 15px; align-items: center; }
|
|
.header .nav-links a { color: rgba(255,255,255,0.8); text-decoration: none; font-size: 14px; padding: 8px 16px; border-radius: 8px; transition: all 0.2s; }
|
|
.header .nav-links a:hover { background: rgba(255,255,255,0.15); color: white; }
|
|
.header .nav-links a.active { background: rgba(255,255,255,0.2); color: white; font-weight: 600; }
|
|
|
|
.container { max-width: 1600px; margin: 0 auto; padding: 20px; }
|
|
|
|
.page-intro { background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); border-radius: 12px; padding: 25px; border: 1px solid #334155; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px; }
|
|
.page-intro div:first-child h2 { color: #e2e8f0; margin-bottom: 8px; font-size: 20px; }
|
|
.page-intro div:first-child p { color: #64748b; font-size: 14px; }
|
|
|
|
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 20px; }
|
|
.grid-2 { grid-template-columns: 1fr 1fr; }
|
|
.grid-4 { grid-template-columns: repeat(4, 1fr); }
|
|
|
|
.card { background: #1e293b; border-radius: 12px; padding: 20px; border: 1px solid #334155; }
|
|
.card h2 { font-size: 16px; margin-bottom: 15px; color: #94a3b8; display: flex; align-items: center; gap: 8px; }
|
|
.card h2 .icon { font-size: 20px; }
|
|
|
|
.stat-card { text-align: center; }
|
|
.stat-value { font-size: 36px; font-weight: bold; color: #3b82f6; }
|
|
.stat-label { font-size: 12px; color: #64748b; margin-top: 5px; text-transform: uppercase; }
|
|
|
|
.btn { padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s; }
|
|
.btn-primary { background: #3b82f6; color: white; }
|
|
.btn-primary:hover { background: #2563eb; }
|
|
.btn-success { background: #22c55e; color: white; }
|
|
.btn-success:hover { background: #16a34a; }
|
|
.btn-warning { background: #f59e0b; color: white; }
|
|
.btn-warning:hover { background: #d97706; }
|
|
.btn-small { padding: 8px 16px; font-size: 12px; }
|
|
|
|
.extraction-list { display: flex; flex-direction: column; gap: 12px; }
|
|
.extraction-item { display: flex; justify-content: space-between; align-items: center; padding: 20px; background: #0f172a; border-radius: 10px; border: 1px solid #334155; transition: all 0.2s; }
|
|
.extraction-item:hover { border-color: #3b82f6; }
|
|
.extraction-main { display: flex; align-items: center; gap: 15px; flex: 1; }
|
|
.extraction-icon { font-size: 32px; }
|
|
.extraction-details h4 { color: #e2e8f0; margin-bottom: 5px; }
|
|
.extraction-details p { color: #64748b; font-size: 12px; }
|
|
.extraction-stats { display: flex; gap: 20px; }
|
|
.extraction-stat { text-align: center; }
|
|
.extraction-stat .value { font-size: 18px; font-weight: bold; color: #3b82f6; }
|
|
.extraction-stat .label { font-size: 10px; color: #64748b; text-transform: uppercase; }
|
|
.extraction-actions { display: flex; gap: 8px; margin-left: 20px; }
|
|
|
|
.unavailable-msg { text-align: center; padding: 60px 20px; color: #64748b; }
|
|
.unavailable-msg .msg-icon { font-size: 48px; margin-bottom: 15px; }
|
|
.unavailable-msg h3 { color: #94a3b8; margin-bottom: 10px; }
|
|
.unavailable-msg p { font-size: 14px; max-width: 500px; margin: 0 auto; }
|
|
|
|
.empty-state { text-align: center; padding: 60px 20px; }
|
|
.empty-state .empty-icon { font-size: 64px; margin-bottom: 15px; opacity: 0.5; }
|
|
.empty-state h3 { color: #94a3b8; margin-bottom: 10px; font-size: 18px; }
|
|
.empty-state p { color: #64748b; font-size: 14px; max-width: 400px; margin: 0 auto; }
|
|
|
|
.status-badge { display: inline-block; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; }
|
|
.status-badge.completed { background: #052e16; color: #22c55e; }
|
|
.status-badge.running { background: #172554; color: #3b82f6; }
|
|
.status-badge.failed { background: #450a0a; color: #ef4444; }
|
|
|
|
@media (max-width: 768px) {
|
|
.grid-4 { grid-template-columns: 1fr 1fr; }
|
|
.grid-2 { grid-template-columns: 1fr; }
|
|
.header { flex-direction: column; gap: 15px; }
|
|
.extraction-item { flex-direction: column; gap: 15px; align-items: flex-start; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>RPA Vision V3 - Extractions</h1>
|
|
<div class="nav-links">
|
|
<a href="/">Dashboard</a>
|
|
<a href="/gestures">Gestes</a>
|
|
<a href="/streaming">Streaming</a>
|
|
<a href="/extractions" class="active">Extractions</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<div class="page-intro">
|
|
<div>
|
|
<h2>Extractions de donnees</h2>
|
|
<p>Visualisation des donnees extraites par le moteur RPA depuis les ecrans des applications (scraping visuel).</p>
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-primary" onclick="refreshExtractions()">Actualiser</button>
|
|
</div>
|
|
</div>
|
|
|
|
{% if not available %}
|
|
<div class="card">
|
|
<div class="unavailable-msg">
|
|
<div class="msg-icon">⚠️</div>
|
|
<h3>Module non disponible</h3>
|
|
<p>Le module <code>core.extraction</code> n'est pas encore installe. Cette fonctionnalite sera disponible dans une prochaine version.</p>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
|
|
<!-- Stats -->
|
|
<div class="grid grid-4">
|
|
<div class="card stat-card">
|
|
<div class="stat-value" id="statTotalExtractions">{{ stats.total }}</div>
|
|
<div class="stat-label">Extractions</div>
|
|
</div>
|
|
<div class="card stat-card">
|
|
<div class="stat-value" id="statTotalRecords">{{ stats.total_records }}</div>
|
|
<div class="stat-label">Enregistrements</div>
|
|
</div>
|
|
<div class="card stat-card">
|
|
<div class="stat-value" id="statSchemas">{{ stats.schemas }}</div>
|
|
<div class="stat-label">Schemas</div>
|
|
</div>
|
|
<div class="card stat-card">
|
|
<div class="stat-value" id="statLastExtraction">{{ stats.last_extraction or '-' }}</div>
|
|
<div class="stat-label">Derniere extraction</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Liste des extractions -->
|
|
<div class="card">
|
|
<h2><span class="icon">📋</span> Extractions passees</h2>
|
|
<div class="extraction-list" id="extractionList">
|
|
{% if extractions %}
|
|
{% for ext in extractions %}
|
|
<div class="extraction-item">
|
|
<div class="extraction-main">
|
|
<div class="extraction-icon">📄</div>
|
|
<div class="extraction-details">
|
|
<h4>{{ ext.schema_name or ext.name or 'Extraction' }}</h4>
|
|
<p>{{ ext.description or '' }} | {{ ext.date or '' }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="extraction-stats">
|
|
<div class="extraction-stat">
|
|
<div class="value">{{ ext.records_count or 0 }}</div>
|
|
<div class="label">Enregistrements</div>
|
|
</div>
|
|
<div class="extraction-stat">
|
|
<div class="value">{{ ext.fields_count or 0 }}</div>
|
|
<div class="label">Champs</div>
|
|
</div>
|
|
</div>
|
|
<div class="extraction-actions">
|
|
<span class="status-badge {{ ext.status or 'completed' }}">{{ ext.status or 'completed' }}</span>
|
|
{% if ext.id %}
|
|
<button class="btn btn-success btn-small" onclick="exportCSV('{{ ext.id }}')">Export CSV</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<div class="empty-icon">🔍</div>
|
|
<h3>Aucune extraction</h3>
|
|
<p>Les extractions apparaitront ici lorsque le moteur RPA aura extrait des donnees depuis les ecrans des applications.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
{% endif %}
|
|
</div>
|
|
|
|
<script>
|
|
async function fetchJSON(url) {
|
|
const response = await fetch(url);
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
return response.json();
|
|
}
|
|
|
|
async function refreshExtractions() {
|
|
try {
|
|
const data = await fetchJSON('/api/extractions');
|
|
// Recharger la page pour afficher les nouvelles donnees
|
|
// (plus simple que de mettre a jour le DOM Jinja)
|
|
window.location.reload();
|
|
} catch (e) {
|
|
console.error('Erreur refresh:', e);
|
|
}
|
|
}
|
|
|
|
async function exportCSV(extractionId) {
|
|
try {
|
|
const response = await fetch(`/api/extractions/${extractionId}/export?format=csv`);
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `extraction_${extractionId}.csv`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
window.URL.revokeObjectURL(url);
|
|
} catch (e) {
|
|
alert('Erreur export: ' + e.message);
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|