feat: import Excel via chat Léa, suppression nœuds VWB, fix temperature 0.1
- Chat Léa : "importe patients.xlsx" → preview → confirmation → table SQLite Bouton 📎 pour upload fichier, "montre les tables", "info table X" - VWB : suppression nœuds via touche Suppr/Backspace + bouton croix rouge - Fix : toutes les températures VLM à 0.1 (qwen3-vl bloque à 0.0) - Fix : capture VWB avec DISPLAY=:1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ class IntentType(Enum):
|
||||
CONFIRM = "confirm" # Confirmer une action
|
||||
DENY = "deny" # Refuser une action
|
||||
CLARIFY = "clarify" # Demander une clarification
|
||||
DATA_IMPORT = "data_import" # Importer des données (Excel, CSV)
|
||||
UNKNOWN = "unknown" # Intention non reconnue
|
||||
|
||||
|
||||
@@ -74,6 +75,21 @@ class IntentParser:
|
||||
|
||||
# Patterns pour la détection d'intentions par règles
|
||||
INTENT_PATTERNS = {
|
||||
IntentType.DATA_IMPORT: [
|
||||
# Import de fichiers Excel/CSV
|
||||
r"(?:importe|charge|lis|lire)\s+(?:le\s+fichier\s+|les\s+(?:données|feuilles)\s+(?:de|du|excel\s+du)\s+)?(.+\.xlsx?)\b",
|
||||
r"(?:importe|charge|lis|lire)\s+(?:le\s+fichier\s+|les\s+(?:données|feuilles)\s+(?:de|du|excel\s+du)\s+)?(.+\.csv)\b",
|
||||
r"(?:importe|charge|lis|lire)\s+(?:le\s+fichier\s+)?excel\s+(.+)",
|
||||
r"(?:importe|charge|lis|lire)\s+(?:les\s+)?(?:feuilles?\s+)?excel\s+(?:du\s+dossier\s+|de\s+)(.+)",
|
||||
r"(?:crée?|créer?)\s+une?\s+table\s+(?:à\s+partir\s+d[eu]'?\s*)(.+\.xlsx?)\b",
|
||||
# Lister les tables
|
||||
r"(?:montre|liste|affiche|voir)\s*(?:-moi\s+)?(?:les\s+)?tables?\b",
|
||||
r"(?:quelles?\s+)?tables?\s+(?:sont\s+)?(?:disponibles?|dans\s+la\s+base)",
|
||||
r"liste\s+(?:des?\s+)?tables?\s+(?:de\s+)?(?:la\s+)?(?:base)?",
|
||||
# Infos sur une table
|
||||
r"(?:combien\s+de\s+lignes?\s+(?:dans|pour)\s+(?:la\s+)?table\s+)(\w+)",
|
||||
r"(?:info|détails?|describe?|colonnes?|structure)\s+(?:de\s+)?(?:la\s+)?table\s+(\w+)",
|
||||
],
|
||||
IntentType.EXECUTE: [
|
||||
# Verbes d'action explicites
|
||||
r"(?:lance|exécute|démarre|fai[st]|run|start|execute)\s+(.+)",
|
||||
@@ -210,6 +226,25 @@ class IntentParser:
|
||||
# Expressions mathématiques : 5+2, 100*3, 12/4, 7-3, 2.5+3.1
|
||||
r"(\d+(?:[.,]\d+)?\s*[+\-*/x×÷]\s*\d+(?:[.,]\d+)?)",
|
||||
],
|
||||
"file_path": [
|
||||
# Chemins Windows : C:\data\fichier.xlsx
|
||||
r"([A-Za-z]:\\[^\s,]+\.(?:xlsx?|csv))",
|
||||
# Chemins Unix : /data/fichier.xlsx
|
||||
r"(/[^\s,]+\.(?:xlsx?|csv))",
|
||||
# Noms de fichier simples : patients.xlsx
|
||||
r"(?:^|\s)([\w\-\.]+\.(?:xlsx?|csv))(?:\s|$)",
|
||||
],
|
||||
"folder_path": [
|
||||
# Dossiers Windows : C:\data\imports
|
||||
r"(?:dossier|répertoire|dir|directory)\s+([A-Za-z]:\\[^\s,]+)",
|
||||
r"([A-Za-z]:\\[^\s,]+)(?:\s|$)",
|
||||
# Dossiers Unix : /data/imports
|
||||
r"(?:dossier|répertoire|dir|directory)\s+(/[^\s,]+)",
|
||||
],
|
||||
"table_name": [
|
||||
# Noms de table (exclure les mots courants comme "à", "de", "la")
|
||||
r"(?:table|la\s+table)\s+['\"]?(\w{2,})['\"]?",
|
||||
],
|
||||
}
|
||||
|
||||
def __init__(
|
||||
@@ -294,6 +329,10 @@ class IntentParser:
|
||||
# 4. Construire les paramètres depuis les entités
|
||||
parameters = self._entities_to_parameters(entities)
|
||||
|
||||
# 4b. Enrichir les paramètres DATA_IMPORT avec l'action et le chemin
|
||||
if intent_type == IntentType.DATA_IMPORT:
|
||||
parameters = self._enrich_data_import_params(normalized, query, parameters, entities)
|
||||
|
||||
# 5. Si le LLM est disponible et la confiance est basse, utiliser le LLM
|
||||
if self.use_llm and self.llm_available and rule_confidence < 0.7:
|
||||
llm_result = self._parse_with_llm(query, context)
|
||||
@@ -316,6 +355,82 @@ class IntentParser:
|
||||
clarification_question=clarification_question
|
||||
)
|
||||
|
||||
def _enrich_data_import_params(
|
||||
self,
|
||||
normalized: str,
|
||||
raw_query: str,
|
||||
parameters: Dict[str, Any],
|
||||
entities: List[Dict[str, Any]],
|
||||
) -> Dict[str, Any]:
|
||||
"""Enrichir les paramètres pour une intention DATA_IMPORT.
|
||||
|
||||
Détermine l'action (import_file, import_folder, list_tables, table_info)
|
||||
et extrait le chemin de fichier / nom de table.
|
||||
"""
|
||||
# Déterminer l'action
|
||||
list_patterns = [
|
||||
r"(?:montre|liste|affiche|voir)\s*(?:-moi\s+)?(?:les\s+)?tables?",
|
||||
r"(?:quelles?\s+)?tables?\s+(?:sont\s+)?(?:disponibles?|dans)",
|
||||
r"liste\s+(?:des?\s+)?tables?",
|
||||
]
|
||||
info_patterns = [
|
||||
r"combien\s+de\s+lignes?",
|
||||
r"(?:info|détails?|describe?|colonnes?|structure)\s+(?:de\s+)?(?:la\s+)?table",
|
||||
]
|
||||
folder_patterns = [
|
||||
r"(?:feuilles?\s+excel|fichiers?\s+excel)\s+(?:du|de)\s+(?:dossier|répertoire)",
|
||||
r"(?:importe|charge|lis)\s+(?:les\s+)?(?:feuilles?\s+)?excel\s+(?:du\s+dossier|de\s+)",
|
||||
]
|
||||
|
||||
action = "import_file" # Par défaut
|
||||
|
||||
for pat in list_patterns:
|
||||
if re.search(pat, normalized, re.IGNORECASE):
|
||||
action = "list_tables"
|
||||
break
|
||||
|
||||
if action == "import_file":
|
||||
for pat in info_patterns:
|
||||
if re.search(pat, normalized, re.IGNORECASE):
|
||||
action = "table_info"
|
||||
break
|
||||
|
||||
if action == "import_file":
|
||||
for pat in folder_patterns:
|
||||
if re.search(pat, normalized, re.IGNORECASE):
|
||||
action = "import_folder"
|
||||
break
|
||||
|
||||
parameters["action"] = action
|
||||
|
||||
# Extraire le chemin de fichier depuis les entités
|
||||
for entity in entities:
|
||||
if entity["type"] == "file_path" and "file_path" not in parameters:
|
||||
parameters["file_path"] = entity["value"]
|
||||
elif entity["type"] == "folder_path" and "folder_path" not in parameters:
|
||||
parameters["folder_path"] = entity["value"]
|
||||
elif entity["type"] == "table_name" and "table_name" not in parameters:
|
||||
parameters["table_name"] = entity["value"]
|
||||
|
||||
# Fallback : extraire un chemin de fichier depuis la requête brute
|
||||
if "file_path" not in parameters and action == "import_file":
|
||||
# Chercher un .xlsx/.xls/.csv dans la requête brute (supporte les chemins Windows)
|
||||
fp_match = re.search(
|
||||
r'([A-Za-z]:\\[^\s,]+\.(?:xlsx?|csv)|/[^\s,]+\.(?:xlsx?|csv)|[\w\-\.]+\.(?:xlsx?|csv))',
|
||||
raw_query,
|
||||
re.IGNORECASE,
|
||||
)
|
||||
if fp_match:
|
||||
parameters["file_path"] = fp_match.group(1)
|
||||
|
||||
# Extraire table_name pour table_info depuis la requête
|
||||
if action == "table_info" and "table_name" not in parameters:
|
||||
tm = re.search(r"table\s+['\"]?(\w+)['\"]?", normalized, re.IGNORECASE)
|
||||
if tm:
|
||||
parameters["table_name"] = tm.group(1)
|
||||
|
||||
return parameters
|
||||
|
||||
def _normalize_query(self, query: str) -> str:
|
||||
"""Normaliser une requête pour le matching."""
|
||||
# Convertir en minuscules
|
||||
|
||||
Reference in New Issue
Block a user