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:
Dom
2026-03-15 10:02:09 +01:00
parent 74a1cb4e03
commit cf495dd82f
93 changed files with 12463 additions and 1080 deletions

View File

@@ -73,9 +73,16 @@ class ResponseGenerator:
"Le workflow '{workflow}' a échoué: {error}"
],
"not_found": [
"Je n'ai pas trouvé de workflow correspondant à '{query}'.",
"Aucun workflow ne correspond à '{query}'. Voulez-vous voir la liste ?",
"'{query}' ne correspond à aucun workflow connu."
"Je ne sais pas encore faire '{query}'. Montre-moi comment faire et je l'apprendrai !",
"'{query}' m'est inconnu pour l'instant. Tu peux me montrer en enregistrant un workflow.",
"Je ne connais pas '{query}'. Montre-moi et je m'en souviendrai !"
],
"gesture": [
"{gesture_name} ({gesture_keys}) envoyé !",
"Raccourci {gesture_name} ({gesture_keys}) exécuté.",
],
"copilot": [
"Mode pas-à-pas activé pour '{workflow}'. Validez chaque étape.",
]
},
IntentType.LIST: {
@@ -108,6 +115,13 @@ class ResponseGenerator:
"Tapez votre commande en langage naturel !",
]
},
IntentType.GREETING: {
"default": [
"Bonjour ! Je suis votre assistant RPA. Comment puis-je vous aider ?",
"Salut ! Que puis-je faire pour vous ?",
"Bonjour ! Tapez une commande ou 'aide' pour voir ce que je peux faire.",
]
},
IntentType.STATUS: {
"running": [
"Exécution en cours : '{workflow}'\nProgression : {progress}%\n{message}",
@@ -355,7 +369,21 @@ class ResponseGenerator:
"""Handler pour les intentions d'exécution."""
templates = self.RESPONSE_TEMPLATES[IntentType.EXECUTE]
if result.get("success"):
if result.get("gesture"):
# Geste primitif (raccourci clavier)
template = random.choice(templates["gesture"])
message = template.format(
gesture_name=result.get("gesture_name", "?"),
gesture_keys=result.get("gesture_keys", "?"),
)
suggestions = self.CONTEXTUAL_SUGGESTIONS["after_execute"]
elif result.get("mode") == "copilot":
template = random.choice(templates["copilot"])
message = template.format(workflow=result.get("workflow", "?"))
suggestions = ["approuver", "passer", "annuler"]
elif result.get("success"):
template = random.choice(templates["success"])
workflow = result.get("workflow", intent.workflow_hint or "inconnu")
details = ""
@@ -369,8 +397,9 @@ class ResponseGenerator:
elif result.get("not_found"):
template = random.choice(templates["not_found"])
message = template.format(query=intent.raw_query)
suggestions = self.CONTEXTUAL_SUGGESTIONS["after_error"]
query = result.get("query", intent.raw_query)
message = template.format(query=query)
suggestions = ["lister les workflows", "aide", "enregistrer un workflow"]
else:
template = random.choice(templates["error"])
@@ -426,6 +455,22 @@ class ResponseGenerator:
action_required=False
)
def _handle_greeting(
self,
intent: ParsedIntent,
context: Dict[str, Any],
result: Dict[str, Any]
) -> GeneratedResponse:
"""Handler pour les salutations."""
templates = self.RESPONSE_TEMPLATES[IntentType.GREETING]
message = random.choice(templates["default"])
return GeneratedResponse(
message=message,
suggestions=self.CONTEXTUAL_SUGGESTIONS["idle"],
action_required=False
)
def _handle_status(
self,
intent: ParsedIntent,