feat: chat unifié, GestureCatalog, Copilot, Léa UI, extraction données, vérification replay
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>
This commit is contained in:
@@ -617,11 +617,8 @@
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="mode-toggle">
|
||||
<button class="mode-btn active" onclick="setMode('workflow')" id="modeWorkflow">
|
||||
📋 Workflows
|
||||
</button>
|
||||
<button class="mode-btn" onclick="setMode('agent')" id="modeAgent">
|
||||
🚀 Agent Libre
|
||||
<button class="mode-btn active" id="modeWorkflow">
|
||||
💬 Assistant
|
||||
</button>
|
||||
</div>
|
||||
<div class="status-pill" id="statusPill">
|
||||
@@ -715,6 +712,23 @@
|
||||
updateAgentProgress(data);
|
||||
});
|
||||
|
||||
// Copilot events
|
||||
socket.on('copilot_step', (data) => {
|
||||
showCopilotStep(data);
|
||||
});
|
||||
|
||||
socket.on('copilot_step_result', (data) => {
|
||||
updateCopilotStepResult(data);
|
||||
});
|
||||
|
||||
socket.on('copilot_complete', (data) => {
|
||||
completeCopilot(data);
|
||||
});
|
||||
|
||||
socket.on('copilot_error', (data) => {
|
||||
addMessage(`Copilot: ${data.message}`);
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// UI Functions
|
||||
// =====================================================
|
||||
@@ -853,40 +867,6 @@
|
||||
return card;
|
||||
}
|
||||
|
||||
function createAgentPlanCard(plan) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'action-card';
|
||||
|
||||
const stepsHtml = plan.steps.map((step, i) => `
|
||||
<div class="progress-step pending" id="step-${i}">
|
||||
<div class="progress-step-icon">${i + 1}</div>
|
||||
<span>${step.description}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="action-card-header">
|
||||
<div class="action-card-title">
|
||||
🚀 Plan d'exécution
|
||||
<span class="confidence-badge">${plan.steps.length} étapes</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-steps" style="margin-bottom: 12px;">
|
||||
${stepsHtml}
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-primary" onclick="executeAgentPlan()">
|
||||
<i class="bi bi-play-fill"></i> Exécuter
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="cancelAction()">
|
||||
<i class="bi bi-x"></i> Annuler
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
function createExecutionProgress() {
|
||||
const progress = document.createElement('div');
|
||||
progress.className = 'execution-progress';
|
||||
@@ -1033,11 +1013,7 @@
|
||||
addTypingIndicator();
|
||||
|
||||
try {
|
||||
if (currentMode === 'agent') {
|
||||
await sendAgentRequest(message);
|
||||
} else {
|
||||
await sendChatRequest(message);
|
||||
}
|
||||
await sendChatRequest(message);
|
||||
} catch (error) {
|
||||
removeTypingIndicator();
|
||||
addMessage(`❌ Erreur: ${error.message}`);
|
||||
@@ -1073,9 +1049,35 @@
|
||||
data.intent?.confidence || 0.9
|
||||
);
|
||||
addMessage(data.response.message, 'bot', card);
|
||||
} else if (data.result?.gesture) {
|
||||
// Geste primitif exécuté
|
||||
addMessage(data.response.message);
|
||||
} else if (data.result?.mode === 'copilot') {
|
||||
// Mode copilot — les étapes arrivent via WebSocket
|
||||
addMessage(data.response.message);
|
||||
} else if (data.result?.success) {
|
||||
const progress = createExecutionProgress();
|
||||
addMessage(data.response.message, 'bot', progress);
|
||||
} else if (data.result?.teach_me) {
|
||||
// Workflow non trouvé — proposer l'apprentissage
|
||||
const teachCard = document.createElement('div');
|
||||
teachCard.className = 'action-card';
|
||||
teachCard.innerHTML = `
|
||||
<div class="action-card-header">
|
||||
<div class="action-card-title">
|
||||
Apprentissage disponible
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin: 8px 0; opacity: 0.8; font-size: 0.9em;">
|
||||
Lancez l'enregistrement sur votre PC et montrez-moi comment faire.
|
||||
</p>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-primary" onclick="window.open('/api/help', '_blank')">
|
||||
<i class="bi bi-mortarboard"></i> Comment m'apprendre ?
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
addMessage(data.response.message, 'bot', teachCard);
|
||||
} else if (data.result?.workflows) {
|
||||
let msg = data.response.message + '\n\n';
|
||||
data.result.workflows.slice(0, 5).forEach(w => {
|
||||
@@ -1087,30 +1089,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function sendAgentRequest(message) {
|
||||
const response = await fetch('/api/agent/plan', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ request: message })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
removeTypingIndicator();
|
||||
|
||||
if (data.error) {
|
||||
addMessage(`❌ ${data.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.plan) {
|
||||
pendingConfirmation = data.plan;
|
||||
const card = createAgentPlanCard(data.plan);
|
||||
addMessage(`J'ai préparé un plan pour "${message}":`, 'bot', card);
|
||||
} else {
|
||||
addMessage(data.message || "Je n'ai pas pu créer de plan pour cette demande.");
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmAction() {
|
||||
if (!pendingConfirmation) return;
|
||||
|
||||
@@ -1127,40 +1105,11 @@
|
||||
|
||||
// Show execution progress
|
||||
const progress = createExecutionProgress();
|
||||
addMessage("⏳ Exécution en cours...", 'bot', progress);
|
||||
addMessage("Execution en cours...", 'bot', progress);
|
||||
|
||||
pendingConfirmation = null;
|
||||
}
|
||||
|
||||
async function executeAgentPlan() {
|
||||
if (!pendingConfirmation) return;
|
||||
|
||||
isProcessing = true;
|
||||
updateInputState();
|
||||
|
||||
addMessage("⏳ Exécution du plan en cours...", 'bot');
|
||||
|
||||
const response = await fetch('/api/agent/execute', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ plan: pendingConfirmation })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const results = data.results || [];
|
||||
const successCount = results.filter(r => r.success).length;
|
||||
addMessage(`✅ Plan exécuté: ${successCount}/${results.length} étapes réussies`);
|
||||
} else {
|
||||
addMessage(`❌ Erreur: ${data.error}`);
|
||||
}
|
||||
|
||||
pendingConfirmation = null;
|
||||
isProcessing = false;
|
||||
updateInputState();
|
||||
}
|
||||
|
||||
function modifyAction() {
|
||||
if (!pendingConfirmation) return;
|
||||
addMessage("✏️ Modification non implémentée. Décrivez les changements souhaités.");
|
||||
@@ -1173,7 +1122,79 @@
|
||||
|
||||
function cancelExecution() {
|
||||
socket.emit('cancel_execution');
|
||||
addMessage("⏹️ Demande d'annulation envoyée...");
|
||||
addMessage("Demande d'annulation envoyée...");
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Copilot Mode
|
||||
// =====================================================
|
||||
|
||||
function showCopilotStep(data) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'action-card';
|
||||
card.id = `copilot-step-${data.step_index}`;
|
||||
card.innerHTML = `
|
||||
<div class="action-card-header">
|
||||
<div class="action-card-title">
|
||||
Copilot - Étape ${data.step_index + 1}/${data.total}
|
||||
</div>
|
||||
<span style="font-size: 0.8em; opacity: 0.6;">${data.workflow}</span>
|
||||
</div>
|
||||
<p style="margin: 8px 0; font-size: 0.95em;">
|
||||
<strong>${data.action.type}</strong>: ${data.action.description}
|
||||
</p>
|
||||
<div class="action-buttons" id="copilot-btns-${data.step_index}">
|
||||
<button class="btn btn-primary" onclick="copilotApprove(${data.step_index})">
|
||||
<i class="bi bi-check-lg"></i> Exécuter
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="copilotSkip(${data.step_index})">
|
||||
<i class="bi bi-skip-forward"></i> Passer
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="copilotAbort()">
|
||||
<i class="bi bi-x-circle"></i> Annuler tout
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
addMessage(`Copilot étape ${data.step_index + 1}/${data.total}`, 'bot', card);
|
||||
}
|
||||
|
||||
function copilotApprove(stepIndex) {
|
||||
socket.emit('copilot_approve');
|
||||
const btns = document.getElementById(`copilot-btns-${stepIndex}`);
|
||||
if (btns) btns.innerHTML = '<span style="color: var(--success);">Approuvé - en cours...</span>';
|
||||
}
|
||||
|
||||
function copilotSkip(stepIndex) {
|
||||
socket.emit('copilot_skip');
|
||||
const btns = document.getElementById(`copilot-btns-${stepIndex}`);
|
||||
if (btns) btns.innerHTML = '<span style="color: var(--warning);">Passé</span>';
|
||||
}
|
||||
|
||||
function copilotAbort() {
|
||||
socket.emit('copilot_abort');
|
||||
}
|
||||
|
||||
function updateCopilotStepResult(data) {
|
||||
const card = document.getElementById(`copilot-step-${data.step_index}`);
|
||||
if (!card) return;
|
||||
|
||||
const btns = card.querySelector('.action-buttons') ||
|
||||
document.getElementById(`copilot-btns-${data.step_index}`);
|
||||
if (!btns) return;
|
||||
|
||||
if (data.status === 'completed') {
|
||||
btns.innerHTML = '<span style="color: var(--success);">Réussi</span>';
|
||||
} else if (data.status === 'failed') {
|
||||
btns.innerHTML = `<span style="color: var(--error);">Échoué: ${data.message}</span>`;
|
||||
} else if (data.status === 'skipped') {
|
||||
btns.innerHTML = '<span style="color: var(--warning);">Passé</span>';
|
||||
}
|
||||
}
|
||||
|
||||
function completeCopilot(data) {
|
||||
const statusColor = data.status === 'completed' ? 'var(--success)' :
|
||||
data.status === 'aborted' ? 'var(--error)' : 'var(--warning)';
|
||||
addMessage(`<span style="color: ${statusColor};">Copilot terminé: ${data.message}</span>`);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
|
||||
Reference in New Issue
Block a user