diff --git a/app.py b/app.py index 30333e2..bba1749 100644 --- a/app.py +++ b/app.py @@ -15,6 +15,7 @@ from memory import ( add_memory, add_repository, add_task, + close_task, init_db, search_memory, upsert_client, @@ -43,6 +44,7 @@ init_db() client = Groq(api_key=GROQ_API_KEY) histories = {} +session_contexts = {} SYSTEM_PROMPT = """Tu es l'assistant personnel vocal de Laurent. Tu peux discuter normalement en francais et aider a envoyer des emails via Gmail. @@ -63,6 +65,7 @@ Actions disponibles : {"action": "ajouter_client", "nom": "...", "notes": "...", "statut": "actif"} {"action": "ajouter_projet", "nom": "...", "client": "...", "statut": "...", "resume": "...", "prochaine_action": "..."} {"action": "ajouter_tache", "titre": "...", "client": "...", "projet": "...", "details": "...", "echeance": "..."} +{"action": "terminer_tache", "id": 123} {"action": "ajouter_repo", "nom": "...", "projet": "...", "chemin": "...", "url": "...", "branche": "..."} {"action": "ajouter_note", "contenu": "...", "sujet": "...", "client": "...", "projet": "...", "type": "note"} {"action": "chercher_memoire", "requete": "..."} @@ -125,6 +128,7 @@ ACTIONS = { "ajouter_client", "ajouter_projet", "ajouter_tache", + "terminer_tache", "ajouter_repo", "ajouter_note", "chercher_memoire", @@ -176,7 +180,7 @@ def demander_au_llm(session_id, texte): return contenu -def detecter_action_directe(texte): +def detecter_action_directe(texte, session_id=None): texte_nettoye = texte.strip() texte_min = texte_nettoye.lower() @@ -202,6 +206,10 @@ def detecter_action_directe(texte): if recherche_taches: return recherche_taches + suppression_tache = detecter_suppression_tache(texte_nettoye, session_id) + if suppression_tache: + return suppression_tache + recherche_match = re.search( r"(?:qu(?:'| )?est[- ]?ce que tu sais sur|que sais[- ]?tu sur|cherche dans la memoire|memoire sur)\s+(.+)", texte_nettoye, @@ -309,6 +317,40 @@ def detecter_recherche_taches(texte): return {"action": "chercher_memoire", "requete": client_nom} +def detecter_suppression_tache(texte, session_id): + if not re.search(r"\b(enleve|enlève|supprime|retire|efface|termine|marque.*faite)\b", texte, flags=re.IGNORECASE): + return None + if not re.search(r"\btaches?\b", texte, flags=re.IGNORECASE): + return None + + contexte = session_contexts.get(session_id or "", {}) + taches = contexte.get("tasks") or [] + if not taches: + return None + + client_match = re.search( + r"\b(?:pour|sur)\s+(?:mon\s+client\s+|le\s+client\s+)?([A-Z0-9][A-Z0-9&.\- ]{1,40})", + texte, + flags=re.IGNORECASE, + ) + client_nom = nettoyer_nom_client(client_match.group(1)) if client_match else contexte.get("last_client", "") + candidates = [ + tache for tache in taches + if not client_nom or (tache.get("client_name") or "").lower() == client_nom.lower() + ] + if not candidates: + candidates = taches + + if re.search(r"\b(derniere|dernière|dernier|derniere affichee|dernière affichée)\b", texte, flags=re.IGNORECASE): + task_id = candidates[-1].get("id") + else: + task_id = candidates[0].get("id") if len(candidates) == 1 else None + + if not task_id: + return None + return {"action": "terminer_tache", "id": task_id} + + def extraire_liste_clients(texte): if not re.search(r"\bclients?\b", texte, flags=re.IGNORECASE): return [] @@ -459,6 +501,13 @@ def executer_action(action): ) return f"Tache ajoutee : {tache['title']}.", {"memorySaved": True} + if nom_action == "terminer_tache": + tache = close_task(action.get("id")) + if not tache: + return "Je n'ai pas retrouve cette tache ouverte.", {"memoryRead": True} + contexte = tache.get("client_name") or tache.get("project_name") or "memoire professionnelle" + return f"C'est fait : j'ai retire la tache {tache['title']} pour {contexte}.", {"memorySaved": True} + if nom_action == "ajouter_repo": repo = add_repository( action.get("nom", ""), @@ -482,7 +531,7 @@ def executer_action(action): if nom_action == "chercher_memoire": resultat = search_memory(action.get("requete", "")) - return formater_memoire(resultat), {"memoryRead": True} + return formater_memoire(resultat), {"memoryRead": True, "memoryResult": resultat, "memoryQuery": action.get("requete", "")} return "", {} @@ -498,6 +547,8 @@ def executer_actions(action_data): "contactSaved": False, "memorySaved": False, "memoryRead": False, + "memoryResult": None, + "memoryQuery": "", } actions_executees = [] for action in actions: @@ -507,8 +558,11 @@ def executer_actions(action_data): actions_executees.append(action) if reponse: reponses.append(reponse) - for cle in flags: + for cle in ("emailSent", "contactSaved", "memorySaved", "memoryRead"): flags[cle] = flags[cle] or bool(action_flags.get(cle)) + if action_flags.get("memoryResult"): + flags["memoryResult"] = action_flags["memoryResult"] + flags["memoryQuery"] = action_flags.get("memoryQuery", "") if actions_executees and all(action.get("action") == "ajouter_client" for action in actions_executees): noms = [action.get("nom", "").strip() for action in actions_executees if action.get("nom", "").strip()] @@ -518,6 +572,29 @@ def executer_actions(action_data): return " ".join(reponses), flags +def mettre_a_jour_contexte(session_id, texte, reponse, flags): + historique = histories.setdefault(session_id, []) + if not historique or historique[-1].get("content") != texte: + historique.append({"role": "user", "content": texte}) + historique.append({"role": "assistant", "content": reponse}) + del historique[:-20] + + resultat = flags.get("memoryResult") + if not resultat: + return + + contexte = session_contexts.setdefault(session_id, {}) + if flags.get("memoryQuery"): + contexte["last_query"] = flags["memoryQuery"] + if resultat.get("clients"): + contexte["last_client"] = resultat["clients"][0].get("name", "") + elif flags.get("memoryQuery"): + contexte["last_client"] = flags["memoryQuery"] + if resultat.get("projects"): + contexte["last_project"] = resultat["projects"][0].get("name", "") + contexte["tasks"] = resultat.get("tasks", []) + + def formater_memoire(resultat): lignes = [] for client_mem in resultat["clients"][:5]: @@ -554,7 +631,7 @@ def formater_memoire(resultat): def traiter_message(session_id, texte, avec_audio=False): - action_directe = detecter_action_directe(texte) + action_directe = detecter_action_directe(texte, session_id) reponse = json.dumps(action_directe, ensure_ascii=False) if action_directe else demander_au_llm(session_id, texte) action = extraire_action(reponse) flags = { @@ -562,10 +639,13 @@ def traiter_message(session_id, texte, avec_audio=False): "contactSaved": False, "memorySaved": False, "memoryRead": False, + "memoryResult": None, + "memoryQuery": "", } if action: reponse, flags = executer_actions(action) + mettre_a_jour_contexte(session_id, texte, reponse, flags) audio_url = None if avec_audio and TTS_PROVIDER == "groq": @@ -632,6 +712,7 @@ def reset(): data = request.get_json(silent=True) or {} session_id = data.get("sessionId") or "default" histories.pop(session_id, None) + session_contexts.pop(session_id, None) return jsonify({"ok": True}) diff --git a/memory.py b/memory.py index d33e998..b5618a1 100644 --- a/memory.py +++ b/memory.py @@ -195,6 +195,27 @@ def add_task(title, client_name="", project_name="", details="", due_at=""): return row_to_dict(db.execute("SELECT * FROM tasks WHERE id = ?", (cursor.lastrowid,)).fetchone()) +def close_task(task_id): + with connect() as db: + row = db.execute( + """ + SELECT t.*, c.name AS client_name, p.name AS project_name + FROM tasks t + LEFT JOIN clients c ON c.id = t.client_id + LEFT JOIN projects p ON p.id = t.project_id + WHERE t.id = ? AND t.done = 0 + """, + (task_id,), + ).fetchone() + if not row: + return None + db.execute( + "UPDATE tasks SET done = 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?", + (task_id,), + ) + return row_to_dict(row) + + def add_repository(name, project_name="", local_path="", remote_url="", main_branch=""): name = clean_text(name) or derive_repo_name(local_path, remote_url) if not name: