notes memory
This commit is contained in:
265
app.py
265
app.py
@@ -1,6 +1,7 @@
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import smtplib
|
||||
import tempfile
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
@@ -10,6 +11,15 @@ from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
from flask import Flask, jsonify, request, send_from_directory
|
||||
from groq import Groq
|
||||
from memory import (
|
||||
add_memory,
|
||||
add_repository,
|
||||
add_task,
|
||||
init_db,
|
||||
search_memory,
|
||||
upsert_client,
|
||||
upsert_project,
|
||||
)
|
||||
|
||||
load_dotenv()
|
||||
|
||||
@@ -29,6 +39,7 @@ TTS_PROVIDER = os.getenv("TTS_PROVIDER", "browser").lower()
|
||||
|
||||
app = Flask(__name__, static_folder=str(STATIC_DIR), static_url_path="")
|
||||
app.secret_key = os.getenv("APP_SECRET", "dev-secret-change-me")
|
||||
init_db()
|
||||
|
||||
client = Groq(api_key=GROQ_API_KEY)
|
||||
histories = {}
|
||||
@@ -46,6 +57,21 @@ Quand l'utilisateur veut ajouter, memoriser ou modifier un contact email, collec
|
||||
Quand tu as les deux informations, reponds uniquement avec ce JSON :
|
||||
{"action": "ajouter_contact", "nom": "...", "email": "..."}
|
||||
|
||||
Tu disposes aussi d'une memoire professionnelle persistante pour les clients, projets, taches, notes et depots Git.
|
||||
Quand l'utilisateur te demande de noter, memoriser, ajouter ou associer une information professionnelle, reponds uniquement avec un JSON.
|
||||
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": "ajouter_repo", "nom": "...", "projet": "...", "chemin": "...", "url": "...", "branche": "..."}
|
||||
{"action": "ajouter_note", "contenu": "...", "sujet": "...", "client": "...", "projet": "...", "type": "note"}
|
||||
{"action": "chercher_memoire", "requete": "..."}
|
||||
|
||||
Si une phrase contient plusieurs choses a enregistrer, utilise :
|
||||
{"actions": [{...}, {...}]}
|
||||
|
||||
Quand l'utilisateur demande ce que tu sais d'un client, d'un projet, d'une tache ou d'un depot, utilise chercher_memoire.
|
||||
|
||||
Pour tout le reste, reponds en francais naturel, avec des phrases courtes.
|
||||
N'utilise pas d'anglais sauf si l'utilisateur le demande explicitement.
|
||||
En mode voiture, sois bref : une ou deux phrases maximum."""
|
||||
@@ -87,11 +113,23 @@ def extraire_action(reponse):
|
||||
data, _ = decodeur.raw_decode(reponse[index:])
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
if isinstance(data, dict) and data.get("action") in {"envoyer_email", "ajouter_contact"}:
|
||||
if isinstance(data, dict) and (data.get("action") in ACTIONS or isinstance(data.get("actions"), list)):
|
||||
return data
|
||||
return None
|
||||
|
||||
|
||||
ACTIONS = {
|
||||
"envoyer_email",
|
||||
"ajouter_contact",
|
||||
"ajouter_client",
|
||||
"ajouter_projet",
|
||||
"ajouter_tache",
|
||||
"ajouter_repo",
|
||||
"ajouter_note",
|
||||
"chercher_memoire",
|
||||
}
|
||||
|
||||
|
||||
def ajouter_contact(nom, email):
|
||||
contacts = charger_contacts()
|
||||
nom_normalise = " ".join(nom.strip().lower().split())
|
||||
@@ -137,6 +175,78 @@ def demander_au_llm(session_id, texte):
|
||||
return contenu
|
||||
|
||||
|
||||
def detecter_action_directe(texte):
|
||||
texte_nettoye = texte.strip()
|
||||
texte_min = texte_nettoye.lower()
|
||||
|
||||
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,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
if recherche_match:
|
||||
return {
|
||||
"action": "chercher_memoire",
|
||||
"requete": recherche_match.group(1).strip(" ?.,;:"),
|
||||
}
|
||||
|
||||
if re.search(r"\b(liste|affiche|montre)\b.*\bclients?\b", texte_min):
|
||||
return {"action": "chercher_memoire", "requete": ""}
|
||||
|
||||
if re.search(r"\b(liste|affiche|montre)\b.*\b(projets?|taches?|repos?|depots?)\b", texte_min):
|
||||
return {"action": "chercher_memoire", "requete": ""}
|
||||
|
||||
repo_match = re.search(
|
||||
r"(?:associe|ajoute|note)\s+(?:le\s+)?repo\s+(\S+)(?:\s+au\s+projet\s+(.+))?",
|
||||
texte_nettoye,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
if repo_match:
|
||||
chemin_ou_url = repo_match.group(1).strip(" .,;:")
|
||||
projet = (repo_match.group(2) or "").strip(" .,;:")
|
||||
return {
|
||||
"action": "ajouter_repo",
|
||||
"chemin": chemin_ou_url if not chemin_ou_url.startswith(("http://", "https://", "git@")) else "",
|
||||
"url": chemin_ou_url if chemin_ou_url.startswith(("http://", "https://", "git@")) else "",
|
||||
"projet": projet,
|
||||
}
|
||||
|
||||
client_match = re.search(
|
||||
r"\b([A-Z0-9][A-Z0-9&.\- ]{2,})\s+est\s+(?:un|une)\s+nouveau\s+client\b",
|
||||
texte_nettoye,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
if not client_match:
|
||||
return None
|
||||
|
||||
nom_client = client_match.group(1).strip(" .,;:")
|
||||
actions = [
|
||||
{
|
||||
"action": "ajouter_client",
|
||||
"nom": nom_client,
|
||||
"notes": texte_nettoye,
|
||||
"statut": "actif",
|
||||
}
|
||||
]
|
||||
|
||||
rappel_match = re.search(
|
||||
r"(?:je\s+dois\s+|il\s+faut\s+|note\s+(?:quelque\s+part\s+)?que\s+je\s+dois\s+)(rappeler\s+.+)",
|
||||
texte_nettoye,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
if rappel_match:
|
||||
actions.append(
|
||||
{
|
||||
"action": "ajouter_tache",
|
||||
"titre": rappel_match.group(1).strip(" .,;:"),
|
||||
"client": nom_client,
|
||||
"details": texte_nettoye,
|
||||
}
|
||||
)
|
||||
|
||||
return {"actions": actions}
|
||||
|
||||
|
||||
def transcrire_audio(fichier):
|
||||
suffix = Path(fichier.filename or "voice.webm").suffix or ".webm"
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
|
||||
@@ -186,20 +296,145 @@ def generer_audio(texte):
|
||||
pass
|
||||
|
||||
|
||||
def traiter_message(session_id, texte, avec_audio=False):
|
||||
reponse = demander_au_llm(session_id, texte)
|
||||
action = extraire_action(reponse)
|
||||
email_sent = False
|
||||
contact_saved = False
|
||||
def executer_action(action):
|
||||
nom_action = action.get("action")
|
||||
|
||||
if action and action.get("action") == "envoyer_email":
|
||||
if nom_action == "envoyer_email":
|
||||
destinataire = envoyer_email(action["destinataire"], action["sujet"], action["corps"])
|
||||
reponse = f"Email envoye a {destinataire}."
|
||||
email_sent = True
|
||||
elif action and action.get("action") == "ajouter_contact":
|
||||
return f"Email envoye a {destinataire}.", {"emailSent": True}
|
||||
|
||||
if nom_action == "ajouter_contact":
|
||||
nom, email = ajouter_contact(action["nom"], action["email"])
|
||||
reponse = f"Contact ajoute : {nom.title()}."
|
||||
contact_saved = True
|
||||
return f"Contact ajoute : {nom.title()}.", {"contactSaved": True}
|
||||
|
||||
if nom_action == "ajouter_client":
|
||||
client_mem = upsert_client(
|
||||
action.get("nom", ""),
|
||||
notes=action.get("notes", ""),
|
||||
status=action.get("statut", "actif"),
|
||||
)
|
||||
return f"C'est note : {client_mem['name']} est dans tes clients.", {"memorySaved": True}
|
||||
|
||||
if nom_action == "ajouter_projet":
|
||||
projet = upsert_project(
|
||||
action.get("nom", ""),
|
||||
client_name=action.get("client", ""),
|
||||
status=action.get("statut", "actif"),
|
||||
summary=action.get("resume", ""),
|
||||
next_action=action.get("prochaine_action", ""),
|
||||
)
|
||||
return f"C'est note pour le projet {projet['name']}.", {"memorySaved": True}
|
||||
|
||||
if nom_action == "ajouter_tache":
|
||||
tache = add_task(
|
||||
action.get("titre", ""),
|
||||
client_name=action.get("client", ""),
|
||||
project_name=action.get("projet", ""),
|
||||
details=action.get("details", ""),
|
||||
due_at=action.get("echeance", ""),
|
||||
)
|
||||
return f"Tache ajoutee : {tache['title']}.", {"memorySaved": True}
|
||||
|
||||
if nom_action == "ajouter_repo":
|
||||
repo = add_repository(
|
||||
action.get("nom", ""),
|
||||
project_name=action.get("projet", ""),
|
||||
local_path=action.get("chemin", ""),
|
||||
remote_url=action.get("url", ""),
|
||||
main_branch=action.get("branche", ""),
|
||||
)
|
||||
return f"Depot associe : {repo['name']}.", {"memorySaved": True}
|
||||
|
||||
if nom_action == "ajouter_note":
|
||||
note = add_memory(
|
||||
action.get("contenu", ""),
|
||||
kind=action.get("type", "note"),
|
||||
subject=action.get("sujet", ""),
|
||||
client_name=action.get("client", ""),
|
||||
project_name=action.get("projet", ""),
|
||||
)
|
||||
sujet = note["subject"] or "memoire professionnelle"
|
||||
return f"C'est note dans {sujet}.", {"memorySaved": True}
|
||||
|
||||
if nom_action == "chercher_memoire":
|
||||
resultat = search_memory(action.get("requete", ""))
|
||||
return formater_memoire(resultat), {"memoryRead": True}
|
||||
|
||||
return "", {}
|
||||
|
||||
|
||||
def executer_actions(action_data):
|
||||
actions = action_data.get("actions")
|
||||
if not isinstance(actions, list):
|
||||
actions = [action_data]
|
||||
|
||||
reponses = []
|
||||
flags = {
|
||||
"emailSent": False,
|
||||
"contactSaved": False,
|
||||
"memorySaved": False,
|
||||
"memoryRead": False,
|
||||
}
|
||||
for action in actions:
|
||||
if not isinstance(action, dict):
|
||||
continue
|
||||
reponse, action_flags = executer_action(action)
|
||||
if reponse:
|
||||
reponses.append(reponse)
|
||||
for cle in flags:
|
||||
flags[cle] = flags[cle] or bool(action_flags.get(cle))
|
||||
|
||||
return " ".join(reponses), flags
|
||||
|
||||
|
||||
def formater_memoire(resultat):
|
||||
lignes = []
|
||||
for client_mem in resultat["clients"][:5]:
|
||||
detail = f"Client : {client_mem['name']}"
|
||||
if client_mem.get("notes"):
|
||||
detail += f" - {client_mem['notes']}"
|
||||
lignes.append(detail)
|
||||
|
||||
for projet in resultat["projects"][:5]:
|
||||
detail = f"Projet : {projet['name']}"
|
||||
if projet.get("client_name"):
|
||||
detail += f" ({projet['client_name']})"
|
||||
if projet.get("status"):
|
||||
detail += f" - statut : {projet['status']}"
|
||||
if projet.get("next_action"):
|
||||
detail += f" - prochaine action : {projet['next_action']}"
|
||||
lignes.append(detail)
|
||||
|
||||
for tache in resultat["tasks"][:5]:
|
||||
contexte = tache.get("client_name") or tache.get("project_name") or "general"
|
||||
lignes.append(f"Tache : {tache['title']} ({contexte})")
|
||||
|
||||
for repo in resultat["repositories"][:5]:
|
||||
chemin = repo.get("local_path") or repo.get("remote_url") or "chemin non renseigne"
|
||||
lignes.append(f"Depot : {repo['name']} - {chemin}")
|
||||
|
||||
for note in resultat["memories"][:5]:
|
||||
sujet = note.get("subject") or note.get("client_name") or note.get("project_name") or "note"
|
||||
lignes.append(f"Note : {sujet} - {note['content']}")
|
||||
|
||||
if not lignes:
|
||||
return "Je n'ai rien trouve dans la memoire professionnelle."
|
||||
return "Voici ce que j'ai trouve :\n" + "\n".join(f"- {ligne}" for ligne in lignes[:10])
|
||||
|
||||
|
||||
def traiter_message(session_id, texte, avec_audio=False):
|
||||
action_directe = detecter_action_directe(texte)
|
||||
reponse = json.dumps(action_directe, ensure_ascii=False) if action_directe else demander_au_llm(session_id, texte)
|
||||
action = extraire_action(reponse)
|
||||
flags = {
|
||||
"emailSent": False,
|
||||
"contactSaved": False,
|
||||
"memorySaved": False,
|
||||
"memoryRead": False,
|
||||
}
|
||||
|
||||
if action:
|
||||
reponse, flags = executer_actions(action)
|
||||
|
||||
audio_url = None
|
||||
if avec_audio and TTS_PROVIDER == "groq":
|
||||
@@ -208,8 +443,10 @@ def traiter_message(session_id, texte, avec_audio=False):
|
||||
return {
|
||||
"reply": reponse,
|
||||
"audioUrl": audio_url,
|
||||
"emailSent": email_sent,
|
||||
"contactSaved": contact_saved,
|
||||
"emailSent": flags["emailSent"],
|
||||
"contactSaved": flags["contactSaved"],
|
||||
"memorySaved": flags["memorySaved"],
|
||||
"memoryRead": flags["memoryRead"],
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user