feat: Léa personnalité humaine + fichiers + fix doublon menu

- Small talk : café, merci, ça va, qui es-tu → réponses chaleureuses
- Bouton 📎 dans le chat pour envoyer des fichiers
- Polices 13-15pt, fenêtre 600x800
- Fix doublon "Discuter avec Léa" dans le systray
- IntentType.SMALL_TALK avec 7 catégories

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-03-18 00:01:04 +01:00
parent 32c6808afb
commit 8d6b49277f
2 changed files with 140 additions and 4 deletions

View File

@@ -37,6 +37,7 @@ class IntentType(Enum):
DENY = "deny" # Refuser une action
CLARIFY = "clarify" # Demander une clarification
DATA_IMPORT = "data_import" # Importer des données (Excel, CSV)
SMALL_TALK = "small_talk" # Conversation informelle (merci, café, ça va...)
UNKNOWN = "unknown" # Intention non reconnue
@@ -129,6 +130,24 @@ class IntentParser:
r"(?:que\s+sai[st]-(?:tu|vous)\s+faire)",
r"mes\s+tâches?",
],
# SMALL_TALK doit être AVANT QUERY pour que "qui es-tu" ne soit pas
# capturé par le pattern générique "qui + ..." de QUERY
IntentType.SMALL_TALK: [
# Remerciements
r"^(?:merci|thanks?|thx|super|génial|parfait|cool|nickel|impec|impeccable|excellent|formidable)(?:\s.*)?$",
# Adieux
r"^(?:au revoir|à plus|bye|bonne nuit|à bientôt|à demain|ciao|tchao|tchuss|adieu)(?:\s.*)?$",
# Compliments
r"^(?:bien joué|bravo|top|chapeau|impressionnant|pas mal|bien fait|beau travail|good job|nice|trop bien|magnifique)(?:\s.*)?$",
# Mécontentement
r"^(?:c'est nul|nul|pas bien|pas top|pas ouf|bof|mauvais|moche|horrible|catastrophe|c'est pas bon|ça craint|erreur|bug|naze|pourri)(?:\s.*)?$",
# Humour / café
r"(?:un café|café|coffee|fais-moi rire|blague|raconte.+blague|drôle|rigol[eo]|mdr|lol|haha|ptdr|xd|😂|🤣)",
# Identité — qui es-tu ?
r"(?:qui es[- ]tu|t'es qui|comment tu t'appelles|c'est quoi ton (?:nom|prénom)|t'es quoi|vous êtes qui|tu es quoi|tu t'appelles comment)",
# Sentiments — ça va ?
r"(?:ça va|comment (?:ça |tu |vous )?va[st]?|comment allez[- ]vous|tu vas bien|la forme|en forme)",
],
IntentType.QUERY: [
# Questions directes avec mots interrogatifs
r"(?:comment|pourquoi|quand|où|qui)\s+(.+)\??",
@@ -446,8 +465,8 @@ class IntentParser:
# Convertir en minuscules
normalized = query.lower()
# Supprimer la ponctuation excessive
normalized = re.sub(r'[!.]+$', '', normalized)
# Supprimer la ponctuation finale
normalized = re.sub(r'[!.?]+$', '', normalized)
# Normaliser les espaces
normalized = re.sub(r'\s+', ' ', normalized).strip()
@@ -598,6 +617,7 @@ INTENTIONS POSSIBLES:
- greeting: l'utilisateur dit bonjour/salut/hello
- confirm: l'utilisateur confirme (oui, ok, go)
- deny: l'utilisateur refuse (non, annule)
- small_talk: conversation informelle (merci, café, ça va, qui es-tu, bravo, c'est nul)
- unknown: impossible à déterminer
Réponds UNIQUEMENT en JSON valide (pas de texte avant/après):
@@ -733,6 +753,15 @@ if __name__ == "__main__":
"oui",
"annule",
"statut",
# SMALL_TALK — conversation informelle
"merci",
"un café",
"ça va ?",
"qui es-tu ?",
"c'est nul",
"bravo",
"au revoir",
"t'es qui",
# Fallback — ne doit PAS être EXECUTE
"blah blah test",
]

View File

@@ -14,6 +14,7 @@ Auteur: Dom - Janvier 2026
import logging
import random
import re
from dataclasses import dataclass
from enum import Enum
from typing import Dict, Any, List, Optional
@@ -220,11 +221,48 @@ class ResponseGenerator:
"Fichier **{filename}** reçu ! Je l'analyse...",
],
},
IntentType.SMALL_TALK: {
"thanks": [
"Avec plaisir ! N'hésitez pas si vous avez besoin d'autre chose 😊",
"De rien ! Je suis là pour ça 👍",
"Merci à vous ! Toujours prête à aider.",
],
"farewell": [
"À bientôt ! Je reste dans la barre des tâches si vous avez besoin 😊",
"Bonne continuation ! N'hésitez pas à revenir.",
"À plus tard ! Je ne bouge pas 👋",
],
"compliment": [
"Merci, c'est gentil ! J'apprends un peu plus chaque jour grâce à vous 😊",
"Oh merci ! Ça me fait plaisir 😄",
"C'est vous qui êtes formidable ! Merci pour votre confiance.",
],
"complaint": [
"Je suis désolée... Dites-moi ce qui ne va pas, je vais essayer de m'améliorer.",
"Oups... N'hésitez pas à me dire ce qui n'a pas marché, je ferai mieux la prochaine fois.",
"Pardon pour le désagrément. Comment puis-je corriger ça ?",
],
"humor": [
"Pas encore de machine à café intégrée... mais j'y travaille ! 😄 En attendant, je peux vous aider avec vos tâches ?",
"Ha ha ! Si seulement je pouvais... 😄 En attendant, dites-moi comment je peux vous aider !",
"L'humour c'est important au travail ! 😄 Bon, on s'y met ?",
],
"identity": [
"Je suis Léa, votre assistante ! Je peux apprendre vos tâches répétitives et les refaire à votre place 😊",
"Moi c'est Léa ! Je suis là pour automatiser tout ce qui vous ennuie au quotidien.",
"Je m'appelle Léa. Mon job : observer, apprendre, et vous faire gagner du temps 👍",
],
"feelings": [
"Très bien, merci de demander ! Et vous ? Prête à travailler si vous avez besoin 😊",
"En pleine forme ! Et vous, comment ça va ? Dites-moi si je peux aider.",
"Ça va super bien ! Toujours motivée pour vous donner un coup de main 💪",
],
},
IntentType.UNKNOWN: {
"default": [
"Je n'ai pas bien compris. Pouvez-vous reformuler ?",
"Je n'ai pas bien compris. Vous pouvez me demander de l'aide avec le bouton ❓",
"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 ?"
"Hmm, je n'ai pas saisi votre demande. Essayez de reformuler ou tapez « aide »."
]
}
}
@@ -773,6 +811,75 @@ class ResponseGenerator:
metadata=result,
)
def _handle_small_talk(
self,
intent: ParsedIntent,
context: Dict[str, Any],
result: Dict[str, Any]
) -> GeneratedResponse:
"""Handler pour la conversation informelle (merci, café, ça va, etc.)."""
templates = self.RESPONSE_TEMPLATES[IntentType.SMALL_TALK]
query = intent.raw_query.lower().strip()
# Déterminer la sous-catégorie de small talk
category = self._classify_small_talk(query)
category_templates = templates.get(category, templates["humor"])
message = random.choice(category_templates)
return GeneratedResponse(
message=message,
suggestions=[],
action_required=False,
)
@staticmethod
def _classify_small_talk(query: str) -> str:
"""Classifier le type de small talk à partir de la requête brute."""
# Remerciements
if re.search(
r"\b(?:merci|thanks?|thx|super|génial|parfait|cool|nickel|impec|impeccable|excellent|formidable)\b",
query
):
return "thanks"
# Adieux
if re.search(
r"\b(?:au revoir|à plus|bye|bonne nuit|à bientôt|à demain|ciao|tchao|tchuss|adieu)\b",
query
):
return "farewell"
# Identité
if re.search(
r"(?:qui es[- ]tu|t'es qui|comment tu t'appelles|c'est quoi ton (?:nom|prénom)|t'es quoi|vous êtes qui|tu t'appelles comment)",
query
):
return "identity"
# Sentiments
if re.search(
r"(?:ça va|comment (?:ça |tu |vous )?va[st]?|comment allez[- ]vous|tu vas bien|la forme|en forme)",
query
):
return "feelings"
# Mécontentement
if re.search(
r"\b(?:nul|pas bien|pas top|pas ouf|bof|mauvais|moche|horrible|catastrophe|ça craint|erreur|bug|naze|pourri)\b",
query
):
return "complaint"
# Compliments
if re.search(
r"\b(?:bien joué|bravo|top|chapeau|impressionnant|pas mal|bien fait|beau travail|good job|nice|trop bien|magnifique)\b",
query
):
return "compliment"
# Humour / café (fallback small_talk)
return "humor"
def _handle_unknown(
self,
intent: ParsedIntent,