From 32c6808afb8aa2705083090b76bb7ac42bed0140 Mon Sep 17 00:00:00 2001 From: Dom Date: Tue, 17 Mar 2026 23:36:28 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20L=C3=A9a=20humanis=C3=A9e=20=E2=80=94?= =?UTF-8?q?=20plus=20de=20jargon=20technique,=20ton=20chaleureux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "Workflow" → "tâche" partout - Vouvoiement, ton de collègue bienveillante - Noms de tâches lisibles (Bloc-notes — Écriture et sauvegarde) - Notifications féminisées (Connectée, prête) - Boutons : Apprenez-moi, Lancer, Données, Arrêter, Aide - Intent parser enrichi (langage naturel humain) Co-Authored-By: Claude Opus 4.6 (1M context) --- agent_chat/intent_parser.py | 44 ++++++---- agent_chat/response_generator.py | 146 +++++++++++++++---------------- 2 files changed, 99 insertions(+), 91 deletions(-) diff --git a/agent_chat/intent_parser.py b/agent_chat/intent_parser.py index 85ac85bba..2b98358ea 100644 --- a/agent_chat/intent_parser.py +++ b/agent_chat/intent_parser.py @@ -92,10 +92,12 @@ class IntentParser: ], IntentType.EXECUTE: [ # 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"(?:lance[rz]?|exécute[rz]?|démarre[rz]?|fai[st]|run|start|execute)\s+(.+)", + r"(?:je veux|je voudrais|peux-tu|pouvez-vous)\s+(.+)", r"(?:facturer?|créer?|générer?|exporter?)\s+(.+)", r"^(.+)\s+(?:maintenant|tout de suite|svp|stp)$", + # Langage humain — demande de replay + r"(?:refai[st](?:es)?|refaire|recommence[rz]?|rejoue[rz]?)\s+(?:la\s+)?(?:tâche\s+)?(.+)", # 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+(.+)", @@ -113,13 +115,19 @@ class IntentParser: r"(?:déconnecte[rz]?|logout|log\s*out|sign\s*out)\s*(.+)?", # Raccourcis clavier r"(?:ctrl|alt|shift|maj)\s*\+\s*\w+", + # Langage humain — demande d'apprentissage (déclenche l'enregistrement) + r"(?:apprends|apprenez)[- ]moi\s+(.+)", ], IntentType.LIST: [ - 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"(?:workflows?|processus)\s+disponibles?", - r"(?:voir|afficher)\s+(?:les\s+|tous\s+les\s+)?workflows?", + r"(?:liste|montre|affiche|quels?\s+sont)\s+(?:les\s+|des\s+)?(?:workflows?|tâches?|processus|automatisations?)", + r"(?:quels?|quelles?)\s+(?:workflows?|tâches?|processus|automatisations?)", + r"liste\s+des\s+(?:workflows?|tâches?)", + r"(?:workflows?|tâches?|processus)\s+disponibles?", + r"(?:voir|afficher)\s+(?:les\s+|tous\s+les\s+|mes\s+)?(?:workflows?|tâches?)", + # Langage humain — demande de liste + r"(?:qu'est-ce que\s+(?:tu|vous)\s+sai[st]\s+faire)", + r"(?:que\s+sai[st]-(?:tu|vous)\s+faire)", + r"mes\s+tâches?", ], IntentType.QUERY: [ # Questions directes avec mots interrogatifs @@ -141,9 +149,9 @@ class IntentParser: 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"(?:qu'est-ce que|que)\s+(?:je peux|tu peux|vous pouvez)\s+faire", r"^quoi\s+faire\s*\??$", - r"(?:que\s+)?(?:puis-je|peux-tu|peut-on)\s+faire\s*\??", + r"(?:que\s+)?(?:puis-je|peux-tu|pouvez-vous|peut-on)\s+faire\s*\??", r"(?:besoin\s+d'aide|j'ai\s+besoin\s+d'aide)", ], IntentType.GREETING: [ @@ -156,8 +164,10 @@ class IntentParser: r"(?:terminé|fini|done)\s*\?", ], IntentType.CANCEL: [ - r"(?:annule|stop|arrête|cancel|abort)", - r"(?:laisse tomber|oublie)", + r"(?:annule[rz]?|stop|arrête[rz]?|cancel|abort)", + r"(?:laisse[rz]?\s+tomber|oublie[rz]?)", + # Langage humain — stop courant + r"^(?:arrêtez|stoppe[rz]?)$", ], IntentType.HISTORY: [ r"(?:historique|history|dernières?\s+commandes?)", @@ -550,9 +560,9 @@ class IntentParser: """Vérifier si une clarification est nécessaire.""" if intent_type == IntentType.EXECUTE: - # Si pas de hint de workflow, demander clarification + # Si pas de hint de tâche, demander clarification if not workflow_hint: - return True, "Quel workflow souhaitez-vous exécuter ?" + return True, "Quelle tâche souhaitez-vous lancer ?" # Si le hint est trop vague if len(workflow_hint.split()) <= 1: @@ -571,18 +581,18 @@ class IntentParser: workflow_names = [w.get("name", "") for w in self._workflows_cache[:15]] workflows_context = f"\nWorkflows disponibles: {', '.join(workflow_names)}" - prompt = f"""Tu es un assistant RPA. Analyse cette requête utilisateur. + prompt = f"""Tu es Léa, une assistante chaleureuse. Analyse cette requête utilisateur. REQUÊTE: "{query}" {workflows_context} {f"Contexte conversation: {json.dumps(context, ensure_ascii=False)}" if context else ""} INTENTIONS POSSIBLES: -- 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) +- execute: l'utilisateur veut lancer/refaire une tâche ou une action UI (geste). Inclut "apprends-moi", "refais la tâche", "lance" +- list: l'utilisateur veut voir les tâches disponibles (mots-clés: liste, quels, tâches, qu'est-ce que tu sais faire, mes tâches) - 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 +- cancel: l'utilisateur veut arrêter/annuler (arrête, stop, annule) - history: l'utilisateur veut voir l'historique - help: l'utilisateur demande de l'aide ou ce qu'il peut faire - greeting: l'utilisateur dit bonjour/salut/hello diff --git a/agent_chat/response_generator.py b/agent_chat/response_generator.py index 9cbb7b522..ed2a6b0b2 100644 --- a/agent_chat/response_generator.py +++ b/agent_chat/response_generator.py @@ -60,39 +60,40 @@ class ResponseGenerator: """ # Templates de réponses par type d'intention + # Ton : collègue chaleureuse et professionnelle, vouvoiement RESPONSE_TEMPLATES = { IntentType.EXECUTE: { "success": [ - "J'ai lancé le workflow '{workflow}'. {details}", - "Le workflow '{workflow}' est en cours d'exécution. {details}", - "C'est parti pour '{workflow}' ! {details}" + "C'est parti, je lance '{workflow}'. {details}", + "Je m'occupe de '{workflow}'. {details}", + "'{workflow}' est en cours ! {details}" ], "error": [ - "Impossible d'exécuter '{workflow}': {error}", - "Erreur lors du lancement de '{workflow}': {error}", - "Le workflow '{workflow}' a échoué: {error}" + "Hmm, je n'ai pas réussi à faire '{workflow}' : {error}", + "Désolée, '{workflow}' a rencontré un souci : {error}", + "Oups, '{workflow}' n'a pas fonctionné : {error}" ], "not_found": [ - "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 !" + "Je ne connais pas encore '{query}'. Montrez-moi comment faire et je l'apprendrai !", + "'{query}' m'est inconnu pour l'instant. Vous pouvez me montrer en cliquant sur « Apprenez-moi ».", + "Je ne sais pas encore faire '{query}'. Montrez-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.", + "Mode pas-à-pas activé pour '{workflow}'. Je vous demande de valider chaque étape.", ] }, IntentType.LIST: { "success": [ - "Voici les workflows disponibles :\n{list}", - "J'ai trouvé {count} workflows :\n{list}", + "Voici les tâches que je sais faire :\n{list}", + "J'ai {count} tâches en mémoire :\n{list}", ], "empty": [ - "Aucun workflow n'est configuré pour le moment.", - "La liste des workflows est vide." + "Je n'ai encore appris aucune tâche. Montrez-moi quelque chose !", + "Ma liste est vide pour le moment. Apprenez-moi une première tâche !" ] }, IntentType.QUERY: { @@ -101,77 +102,78 @@ class ResponseGenerator: "À propos de '{topic}' :\n{answer}" ], "not_found": [ - "Je n'ai pas d'information sur '{topic}'.", - "Je ne peux pas répondre à cette question sur '{topic}'." + "Je n'ai pas d'information sur '{topic}'. Pouvez-vous préciser ?", + "Désolée, je ne peux pas vous répondre sur '{topic}'." ] }, IntentType.HELP: { "general": [ - "Je suis votre assistant RPA. Voici ce que je peux faire :\n\n" - "• Exécuter des workflows : \"lance facturation client Acme\"\n" - "• Lister les workflows : \"quels workflows sont disponibles ?\"\n" - "• Voir le statut : \"où en est l'exécution ?\"\n" - "• Annuler : \"annule\"\n\n" - "Tapez votre commande en langage naturel !", + "Je suis Léa, votre assistante. Voici ce que je peux faire :\n\n" + "• Apprendre une tâche : cliquez sur « Apprenez-moi »\n" + "• Refaire une tâche : \"lance facturation\" ou cliquez sur « Lancer »\n" + "• Voir mes tâches : \"qu'est-ce que tu sais faire ?\"\n" + "• Importer des données : \"importe le fichier Excel\"\n" + "• Arrêter : \"arrête\"\n\n" + "Parlez-moi naturellement, je fais de mon mieux pour comprendre !", ] }, 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.", + "Bonjour ! Je suis Léa. Que puis-je faire pour vous ?", + "Bonjour ! Comment puis-je vous aider aujourd'hui ?", + "Bonjour ! Dites-moi ce dont vous avez besoin, ou tapez « aide ».", ] }, IntentType.STATUS: { "running": [ - "Exécution en cours : '{workflow}'\nProgression : {progress}%\n{message}", - "Le workflow '{workflow}' s'exécute ({progress}%): {message}" + "Je suis en train de faire '{workflow}' — progression : {progress}%\n{message}", + "'{workflow}' est en cours ({progress}%) : {message}" ], "idle": [ - "Aucune exécution en cours. Système prêt.", - "Tout est calme. Que puis-je faire pour vous ?" + "Tout est calme, je suis disponible. Que puis-je faire pour vous ?", + "Rien en cours. Je suis prête !" ], "completed": [ - "Dernière exécution : '{workflow}' - {status}", + "La dernière tâche '{workflow}' est terminée : {status}", "'{workflow}' est terminé : {status}" ] }, IntentType.CANCEL: { "success": [ - "Exécution annulée.", - "J'ai arrêté le workflow en cours.", - "Annulation effectuée." + "C'est arrêté.", + "J'ai tout arrêté.", + "Annulation faite." ], "nothing": [ - "Rien à annuler, aucune exécution en cours.", - "Il n'y a pas d'exécution active." + "Il n'y a rien en cours à arrêter.", + "Rien à annuler, je suis disponible." ] }, IntentType.HISTORY: { "success": [ - "Voici vos dernières commandes :\n{history}", + "Voici vos dernières actions :\n{history}", "Historique récent :\n{history}" ], "empty": [ "Pas encore d'historique.", - "Vous n'avez pas encore exécuté de commandes." + "Vous n'avez encore rien fait avec moi." ] }, IntentType.CONFIRM: { "accepted": [ - "Très bien, j'exécute '{workflow}'.", + "Très bien, je m'en occupe : '{workflow}'.", "C'est parti pour '{workflow}' !", - "Confirmé. Lancement de '{workflow}'." + "Entendu. Je lance '{workflow}'." ], "no_pending": [ - "Il n'y a rien à confirmer.", - "Aucune action en attente de confirmation." + "Il n'y a rien à confirmer pour le moment.", + "Aucune action en attente." ] }, IntentType.DENY: { "cancelled": [ - "Action annulée.", - "D'accord, j'annule.", + "D'accord, c'est annulé.", + "Entendu, j'annule.", "Compris, on oublie." ] }, @@ -186,15 +188,15 @@ class ResponseGenerator: "Fichier **{filename}** prêt : {total_rows} lignes avec les colonnes {columns}. On crée la table '{table_name}' ?", ], "imported": [ - "Table **'{table_name}'** créée avec {row_count} lignes et {col_count} colonnes ({columns}). Vous pouvez maintenant utiliser 'Pour chaque ligne' dans un workflow !", + "Table **'{table_name}'** créée avec {row_count} lignes et {col_count} colonnes ({columns}). Vous pouvez maintenant l'utiliser dans une tâche !", "Import réussi ! Table **'{table_name}'** : {row_count} lignes, {col_count} colonnes ({columns}).", ], "list_tables": [ - "Voici les tables disponibles :\n{tables_list}", - "Tables dans la base :\n{tables_list}", + "Voici vos tables de données :\n{tables_list}", + "Tables disponibles :\n{tables_list}", ], "no_tables": [ - "Aucune table n'a été importée pour l'instant. Envoyez-moi un fichier Excel pour commencer !", + "Vous n'avez pas encore de données importées. Envoyez-moi un fichier Excel pour commencer !", "La base est vide. Importez un fichier Excel pour créer votre première table.", ], "table_info": [ @@ -204,15 +206,15 @@ class ResponseGenerator: "J'ai trouvé {count} fichiers Excel dans le dossier :\n{files_list}\n\nDites-moi lequel importer !", ], "folder_empty": [ - "Aucun fichier Excel trouvé dans le dossier '{folder}'. Vérifiez le chemin.", + "Je n'ai trouvé aucun fichier Excel dans '{folder}'. Vérifiez le chemin.", ], "file_not_found": [ - "Je n'ai pas trouvé le fichier '{file_path}'. Vérifiez le chemin ou envoyez-le via le bouton 📎.", + "Je n'ai pas trouvé le fichier '{file_path}'. Vérifiez le chemin ou envoyez-le directement.", "Fichier introuvable : '{file_path}'. Vous pouvez aussi glisser un fichier dans le chat.", ], "error": [ - "Erreur lors de l'import : {error}", - "L'import a échoué : {error}", + "Désolée, l'import a échoué : {error}", + "Oups, un souci lors de l'import : {error}", ], "uploaded": [ "Fichier **{filename}** reçu ! Je l'analyse...", @@ -220,9 +222,9 @@ class ResponseGenerator: }, IntentType.UNKNOWN: { "default": [ - "Je n'ai pas compris. Pouvez-vous reformuler ?", - "Désolé, je ne comprends pas '{query}'. Tapez 'aide' pour voir les commandes.", - "'{query}' ? Je ne suis pas sûr de comprendre." + "Je n'ai pas bien compris. Pouvez-vous reformuler ?", + "Désolée, je ne comprends pas. Tapez « aide » pour voir ce que je sais faire.", + "Hmm, je n'ai pas saisi votre demande. Pouvez-vous préciser ?" ] } } @@ -231,21 +233,21 @@ class ResponseGenerator: CONTEXTUAL_SUGGESTIONS = { "after_execute": [ "voir le statut", - "annuler", - "liste des workflows" + "arrêter", + "mes tâches" ], "after_error": [ "aide", - "liste des workflows", + "mes tâches", "réessayer" ], "after_list": [ - "exécuter un workflow", + "lancer une tâche", "aide" ], "idle": [ - "facturer client X", - "liste des workflows", + "qu'est-ce que tu sais faire ?", + "apprenez-moi", "aide" ], "after_import": [ @@ -334,7 +336,7 @@ class ResponseGenerator: Générer un message de progression. Args: - workflow_name: Nom du workflow + workflow_name: Nom de la tâche progress: Pourcentage de progression step: Étape actuelle current: Numéro de l'étape @@ -348,11 +350,11 @@ class ResponseGenerator: filled = int(bar_length * progress / 100) bar = "█" * filled + "░" * (bar_length - filled) - message = f"**{workflow_name}** [{bar}] {progress}%\n\nÉtape {current}/{total}: {step}" + message = f"**{workflow_name}** [{bar}] {progress}%\n\nÉtape {current}/{total} : {step}" return GeneratedResponse( message=message, - suggestions=["annuler"] if progress < 100 else [], + suggestions=["arrêter"] if progress < 100 else [], action_required=False, metadata={ "workflow": workflow_name, @@ -372,7 +374,7 @@ class ResponseGenerator: Générer un message de résultat d'exécution. Args: - workflow_name: Nom du workflow + workflow_name: Nom de la tâche success: Succès ou échec message: Message détaillé duration: Durée d'exécution en secondes @@ -381,18 +383,14 @@ class ResponseGenerator: GeneratedResponse avec le résultat """ if success: - emoji = "✅" - status = "terminé avec succès" + response_message = f"C'est fait ! **{workflow_name}** s'est bien passé.\n\n{message}" suggestions = self.CONTEXTUAL_SUGGESTIONS["idle"] else: - emoji = "❌" - status = "échoué" + response_message = f"Hmm, **{workflow_name}** n'a pas fonctionné.\n\n{message}" suggestions = self.CONTEXTUAL_SUGGESTIONS["after_error"] - response_message = f"{emoji} **{workflow_name}** {status}\n\n{message}" - if duration: - response_message += f"\n\nDurée: {duration:.1f}s" + response_message += f"\n\nDurée : {duration:.1f}s" return GeneratedResponse( message=response_message, @@ -446,7 +444,7 @@ class ResponseGenerator: template = random.choice(templates["not_found"]) query = result.get("query", intent.raw_query) message = template.format(query=query) - suggestions = ["lister les workflows", "aide", "enregistrer un workflow"] + suggestions = ["mes tâches", "aide", "apprenez-moi"] else: template = random.choice(templates["error"]) @@ -764,7 +762,7 @@ class ResponseGenerator: suggestions = self.CONTEXTUAL_SUGGESTIONS["after_error"] else: - message = "Je n'ai pas compris la demande d'import. Précisez le fichier ou dites 'montre les tables'." + message = "Je n'ai pas compris votre demande. Précisez le fichier ou dites « montre les tables »." suggestions = ["montre les tables", "aide"] return GeneratedResponse( @@ -788,7 +786,7 @@ class ResponseGenerator: return GeneratedResponse( message=message, - suggestions=["aide", "liste des workflows"], + suggestions=["aide", "mes tâches"], action_required=False )