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

@@ -29,6 +29,7 @@ class IntentType(Enum):
LIST = "list" # Lister les workflows disponibles
CONFIGURE = "configure" # Configurer un paramètre
HELP = "help" # Demander de l'aide
GREETING = "greeting" # Salutation
STATUS = "status" # Vérifier le statut
CANCEL = "cancel" # Annuler l'exécution en cours
HISTORY = "history" # Voir l'historique
@@ -74,27 +75,64 @@ class IntentParser:
# Patterns pour la détection d'intentions par règles
INTENT_PATTERNS = {
IntentType.EXECUTE: [
r"(?:lance|exécute|démarre|fait|run|start|execute)\s+(.+)",
# Verbes d'action explicites
r"(?:lance|exécute|démarre|fai[st]|run|start|execute)\s+(.+)",
r"(?:je veux|je voudrais|peux-tu)\s+(.+)",
r"(?:facturer?|créer?|générer?|exporter?)\s+(.+)",
r"^(.+)\s+(?:maintenant|tout de suite|svp|stp)$",
# Gestes courants (UI actions) — doivent rester EXECUTE
r"(?:ferme[rz]?|ouvr[eir]+[sz]?|clique[rz]?|sélectionne[rz]?|coche[rz]?|décoche[rz]?)\s+(.+)",
r"(?:copie[rz]?|colle[rz]?|coupe[rz]?|supprime[rz]?|efface[rz]?)\s+(.+)",
r"(?:tape[rz]?|écri[rstv]+[sz]?|saisi[rstv]*[sz]?|rempli[rstv]*[sz]?|entre[rz]?)\s+(.+)",
r"(?:scroll(?:e[rz]?)?|défile[rz]?|fait(?:es)?\s+défiler)\s*(.+)?",
r"(?:glisse[rz]?|drag(?:ue)?[rz]?|déplace[rz]?|bouge[rz]?)\s+(.+)",
r"(?:double[- ]?clique[rz]?|clic\s+droit)\s+(.+)?",
r"(?:enregistre[rz]?|sauvegarde[rz]?|save)\s+(.+)?",
r"(?:imprime[rz]?|print)\s+(.+)?",
r"(?:envoie[rz]?|send|mail(?:e[rz]?)?|transmet[sz]?)\s+(.+)",
r"(?:télécharge[rz]?|download|upload)\s+(.+)?",
r"(?:actualise[rz]?|rafraîchi[rstv]*[sz]?|refresh|recharge[rz]?)\s*(.+)?",
r"(?:valide[rz]?|confirme[rz]?|soumets?|submit)\s+(.+)",
r"(?:connecte[rz]?|login|log\s*in|sign\s*in)\s*(.+)?",
r"(?:déconnecte[rz]?|logout|log\s*out|sign\s*out)\s*(.+)?",
# Raccourcis clavier
r"(?:ctrl|alt|shift|maj)\s*\+\s*\w+",
],
IntentType.LIST: [
r"(?:liste|montre|affiche|quels sont)\s+(?:les\s+|des\s+)?(?:workflows?|processus|automatisations?)",
r"(?:liste|montre|affiche|quels?\s+sont)\s+(?:les\s+|des\s+)?(?:workflows?|processus|automatisations?)",
r"(?:quels?|quelles?)\s+(?:workflows?|processus|automatisations?)",
r"liste\s+des\s+workflows?",
r"(?:qu'est-ce que|que)\s+(?:je peux|tu peux)\s+faire",
r"(?:workflows?|processus)\s+disponibles?",
r"(?:voir|afficher)\s+(?:les\s+|tous\s+les\s+)?workflows?",
],
IntentType.QUERY: [
r"(?:comment|pourquoi|quand|où|qui)\s+(.+)\?",
# Questions directes avec mots interrogatifs
r"(?:comment|pourquoi|quand|où|qui)\s+(.+)\??",
r"(?:explique|décris|détaille)\s+(.+)",
r"(?:qu'est-ce que|c'est quoi)\s+(.+)",
# Questions avec "quel/quelle/quels/quelles" (exclure workflows → LIST)
r"(?:quels?|quelles?)\s+(?!workflows?|processus|automatisations?)(.+)\??",
# "quoi" comme question (pas une commande, pas "quoi faire" = HELP)
r"^(?:c'est\s+)?quoi\s+(?!faire)(.+)\??$",
r"^quoi\s*\?+$",
# Questions indirectes
r"(?:dis[- ]moi|raconte|informe[- ]moi)\s+(.+)",
r"(?:je\s+(?:me\s+)?demande|je\s+(?:ne\s+)?comprends?\s+pas)\s+(.+)",
],
IntentType.HELP: [
r"(?:aide|help|assistance|sos)",
r"(?:comment ça marche|comment utiliser)",
r"^(?:aide|help|assistance|sos)$",
r"comment ça (?:marche|fonctionne)\s*\??",
r"comment (?:utiliser|ça s'utilise|on fait)\s*\??",
r"\?{2,}",
# "que peux-tu faire", "quoi faire" = demande d'aide
r"(?:qu'est-ce que|que)\s+(?:je peux|tu peux)\s+faire",
r"^quoi\s+faire\s*\??$",
r"(?:que\s+)?(?:puis-je|peux-tu|peut-on)\s+faire\s*\??",
r"(?:besoin\s+d'aide|j'ai\s+besoin\s+d'aide)",
],
IntentType.GREETING: [
r"^(?:bonjour|bonsoir|salut|hello|hi|hey|coucou|yo|wesh)(?:\s.*)?$",
r"^(?:bonne?\s+(?:journée|soirée|nuit|matinée))$",
],
IntentType.STATUS: [
r"(?:statut|status|état|où en est)",
@@ -119,6 +157,35 @@ class IntentParser:
],
}
# Verbes d'action reconnus pour le fallback EXECUTE
# Si aucun pattern ne matche, on vérifie la présence d'un de ces verbes
# avant de classifier en EXECUTE
ACTION_VERBS = {
# Actions de workflow/exécution
"lance", "lancer", "exécute", "exécuter", "démarre", "démarrer",
"fait", "fais", "run", "start", "execute",
# Actions métier
"facture", "facturer", "crée", "créer", "génère", "générer",
"exporte", "exporter", "importe", "importer",
# Actions UI / gestes
"ferme", "fermer", "ouvre", "ouvrir", "clique", "cliquer",
"sélectionne", "sélectionner", "coche", "cocher", "décoche", "décocher",
"copie", "copier", "colle", "coller", "coupe", "couper",
"supprime", "supprimer", "efface", "effacer",
"tape", "taper", "écris", "écrire", "saisis", "saisir",
"remplis", "remplir", "entre", "entrer",
"scroll", "scroller", "défile", "défiler",
"glisse", "glisser", "déplace", "déplacer", "drag",
"enregistre", "enregistrer", "sauvegarde", "sauvegarder", "save",
"imprime", "imprimer", "print",
"envoie", "envoyer", "send", "transmet", "transmettre",
"télécharge", "télécharger", "download", "upload",
"actualise", "actualiser", "rafraîchis", "rafraîchir", "refresh",
"valide", "valider", "confirme", "confirmer", "soumets", "soumettre",
"connecte", "connecter", "déconnecte", "déconnecter",
"login", "logout",
}
# Patterns pour l'extraction d'entités
ENTITY_PATTERNS = {
"client": [
@@ -280,11 +347,18 @@ class IntentParser:
best_confidence = confidence
best_intent = intent_type
# Si aucune intention trouvée mais la requête ressemble à une commande
# Fallback durci : ne classifier en EXECUTE que si un verbe d'action est présent
if best_intent == IntentType.UNKNOWN and len(query.split()) >= 2:
# Supposer que c'est une demande d'exécution
best_intent = IntentType.EXECUTE
best_confidence = 0.4
words = query.lower().split()
# Vérifier si au moins un mot est un verbe d'action connu
has_action_verb = any(word in self.ACTION_VERBS for word in words)
if has_action_verb:
best_intent = IntentType.EXECUTE
best_confidence = 0.40
else:
# Pas de verbe d'action reconnu → demander clarification
best_intent = IntentType.CLARIFY
best_confidence = 0.30
return best_intent, best_confidence
@@ -389,13 +463,14 @@ REQUÊTE: "{query}"
{f"Contexte conversation: {json.dumps(context, ensure_ascii=False)}" if context else ""}
INTENTIONS POSSIBLES:
- execute: l'utilisateur veut lancer/exécuter un workflow
- execute: l'utilisateur veut lancer/exécuter un workflow ou une action UI (geste)
- list: l'utilisateur veut voir les workflows disponibles (mots-clés: liste, quels, workflows, disponibles, montrer)
- query: l'utilisateur pose une question sur un workflow
- query: l'utilisateur pose une question (comment, pourquoi, c'est quoi, quel)
- status: l'utilisateur demande le statut d'exécution
- cancel: l'utilisateur veut annuler
- history: l'utilisateur veut voir l'historique
- help: l'utilisateur demande de l'aide
- help: l'utilisateur demande de l'aide ou ce qu'il peut faire
- greeting: l'utilisateur dit bonjour/salut/hello
- confirm: l'utilisateur confirme (oui, ok, go)
- deny: l'utilisateur refuse (non, annule)
- unknown: impossible à déterminer
@@ -504,16 +579,37 @@ if __name__ == "__main__":
parser = IntentParser(use_llm=False)
test_queries = [
# EXECUTE — actions explicites
"facturer le client Acme",
"lance le workflow de facturation",
"quels workflows sont disponibles ?",
"aide",
"oui",
"annule",
"statut",
"exporter le rapport en PDF pour Client ABC",
"créer une facture de 1500€ pour Société XYZ",
"facturer les clients de A à Z",
# EXECUTE — gestes UI
"ferme la fenêtre",
"ouvre un nouvel onglet",
"copier le texte",
"lance la facturation",
# LIST
"quels workflows sont disponibles ?",
"liste des workflows",
# QUERY — questions
"comment ça marche ?",
"c'est quoi ce workflow",
"pourquoi ce processus est lent ?",
# HELP
"aide",
"quoi faire ?",
"que peux-tu faire ?",
# GREETING
"bonjour",
"salut",
# Confirmations / annulations
"oui",
"annule",
"statut",
# Fallback — ne doit PAS être EXECUTE
"blah blah test",
]
print("=== Tests IntentParser ===\n")