Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, Form, Response, BackgroundTasks | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| import requests | |
| import datetime | |
| import hashlib | |
| import os | |
| app = FastAPI(title="NEXUS COMMAND CENTER") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID", "AC3f5f0eb3da2c3f839fb021ac203ad781") | |
| TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN", "88e8a43f9abb88f9abdbf7867c80ab30") | |
| TWILIO_WHATSAPP_NUMBER = "whatsapp:+14155238886" | |
| tickets_db = {} | |
| compteur_ticket = 1 | |
| class DispatchOrdre(BaseModel): | |
| service: str | |
| class DemandeWeb(BaseModel): | |
| description: str | |
| def analyser_et_repondre_ia(texte_message: str, numero_expediteur: str, ticket_id: int): | |
| """Fonction exécutée en arrière-plan : interroge Ollama et met à jour le Dashboard""" | |
| try: | |
| url_ollama = "http://127.0.0.1:11434/api/generate" | |
| payload = { | |
| "model": "gemma4:e4b", | |
| "prompt": f"Tu es le dispatcher d'urgence NEXUS. Analyse ce message et réponds en incluant le service d'urgence le plus approprié (POLICE, POMPIERS, ou SAMU) : {texte_message}", | |
| "stream": False | |
| } | |
| reponse_ollama = requests.post(url_ollama, json=payload, timeout=180) | |
| analyse_ia = reponse_ollama.json().get("response", "Erreur IA") | |
| domaine_ia = "INCONNU" | |
| texte_maj = analyse_ia.upper() | |
| if "POLICE" in texte_maj: domaine_ia = "POLICE" | |
| elif "POMPIERS" in texte_maj: domaine_ia = "POMPIERS" | |
| elif "SAMU" in texte_maj: domaine_ia = "SAMU" | |
| tickets_db[ticket_id]["domaine_ia"] = domaine_ia | |
| tickets_db[ticket_id]["score"] = 8 if domaine_ia != "INCONNU" else 5 | |
| tickets_db[ticket_id]["status"] = "EN ATTENTE DISPATCH" | |
| except Exception as e: | |
| analyse_ia = f"⚠️ Échec du traitement local : {str(e)}" | |
| tickets_db[ticket_id]["domaine_ia"] = "ERREUR IA" | |
| url_twilio = f"https://api.twilio.com/2010-04-01/Accounts/{TWILIO_ACCOUNT_SID}/Messages.json" | |
| donnees_whatsapp = { | |
| "From": TWILIO_WHATSAPP_NUMBER, | |
| "To": numero_expediteur, | |
| "Body": f"🧠 [NEXUS PRIME - ANALYSE IA]\n\n{analyse_ia}" | |
| } | |
| requests.post(url_twilio, data=donnees_whatsapp, auth=(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)) | |
| async def twilio_webhook(background_tasks: BackgroundTasks, Body: str = Form(...), From: str = Form(...)): | |
| """Route Webhook : Répond immédiatement, sauvegarde le ticket et lance l'IA""" | |
| global compteur_ticket | |
| msg_hash = hashlib.md5(f"{From}:{Body}".encode()).hexdigest() | |
| if any(t.get("hash") == msg_hash for t in tickets_db.values()): | |
| return Response(content="<Response/>", media_type="application/xml") | |
| ticket_id = compteur_ticket | |
| compteur_ticket += 1 | |
| tickets_db[ticket_id] = { | |
| "id": ticket_id, | |
| "heure": datetime.datetime.now().strftime("%H:%M:%S"), | |
| "telephone": From, | |
| "message": Body, | |
| "domaine_ia": "Analyse en cours... ⏳", | |
| "score": 0, | |
| "status": "NOUVEAU", | |
| "hash": msg_hash | |
| } | |
| background_tasks.add_task(analyser_et_repondre_ia, Body, From, ticket_id) | |
| reponse_twiml = f"""<?xml version="1.0" encoding="UTF-8"?> | |
| <Response> | |
| <Message>NEXUS a bien reçu votre signalement (Dossier #{ticket_id}). Analyse IA en cours... ⏳</Message> | |
| </Response> | |
| """ | |
| return Response(content=reponse_twiml, media_type="application/xml") | |
| def get_tickets(): | |
| """Route pour que le Dashboard HTML récupère la liste des urgences""" | |
| return sorted(list(tickets_db.values()), key=lambda x: x["id"], reverse=True) | |
| def dispatch_ticket(ticket_id: int, ordre: DispatchOrdre): | |
| """Route appelée par le bouton du Dashboard pour envoyer les secours""" | |
| if ticket_id not in tickets_db: | |
| return {"error": "Ticket introuvable"} | |
| ticket = tickets_db[ticket_id] | |
| service_cible = ordre.service | |
| ticket["status"] = f"TRANSFÉRÉ -> {service_cible}" | |
| message_citoyen = f"✅ NEXUS INFO : Votre urgence a été transmise aux {service_cible}. Les secours sont en route." | |
| url_twilio = f"https://api.twilio.com/2010-04-01/Accounts/{TWILIO_ACCOUNT_SID}/Messages.json" | |
| donnees = {"From": TWILIO_WHATSAPP_NUMBER, "To": ticket["telephone"], "Body": message_citoyen} | |
| requests.post(url_twilio, data=donnees, auth=(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)) | |
| return {"status": "success", "ticket": ticket} | |
| def evaluer_ticket_web(demande: DemandeWeb): | |
| """Route réelle : interroge l'IA locale (Ollama) en direct pour le site web""" | |
| try: | |
| url_ollama = "http://127.0.0.1:11434/api/generate" | |
| payload = { | |
| "model": "gemma4:e4b", | |
| "prompt": f"Tu es le dispatcher d'urgence NEXUS. Analyse ce message et réponds en incluant le service d'urgence le plus approprié (POLICE, POMPIERS, ou SAMU) : {demande.description}", | |
| "stream": False | |
| } | |
| reponse_ollama = requests.post(url_ollama, json=payload, timeout=180) | |
| analyse_ia = reponse_ollama.json().get("response", "INCONNU").upper() | |
| domaine = "INCONNU" | |
| score = 5 | |
| friction = "COMPLET" | |
| question = "" | |
| if "POLICE" in analyse_ia: | |
| domaine = "POLICE" | |
| score = 8 | |
| elif "POMPIERS" in analyse_ia: | |
| domaine = "POMPIERS" | |
| score = 9 | |
| elif "SAMU" in analyse_ia: | |
| domaine = "SAMU" | |
| score = 10 | |
| else: | |
| friction = "INCOMPLET" | |
| score = 3 | |
| question = "L'analyse IA nécessite plus de contexte. Pouvez-vous préciser la nature exacte de l'urgence ?" | |
| return { | |
| "domaine": domaine, | |
| "score": score, | |
| "friction": friction, | |
| "question_ia": question | |
| } | |
| except Exception as e: | |
| return { | |
| "domaine": "ERREUR", | |
| "score": 0, | |
| "friction": "ERREUR SERVEUR", | |
| "question_ia": f"Impossible de joindre le modèle IA local : {str(e)}" | |
| } |