malek-messaoudii commited on
Commit
1d46e48
·
1 Parent(s): c45f0d6

Add chat part

Browse files
Files changed (3) hide show
  1. main.py +10 -0
  2. routes/voice_chat_routes.py +145 -0
  3. services/chat_service.py +102 -0
main.py CHANGED
@@ -165,6 +165,16 @@ except ImportError as e:
165
  except Exception as e:
166
  logger.warning(f"⚠ Failed loading main API routes: {e}")
167
 
 
 
 
 
 
 
 
 
 
 
168
  # --- Basic routes ---
169
  @app.get("/health", tags=["Health"])
170
  async def health():
 
165
  except Exception as e:
166
  logger.warning(f"⚠ Failed loading main API routes: {e}")
167
 
168
+ # Dans main.py, après les autres routes
169
+ try:
170
+ from routes.voice_chat_routes import router as voice_chat_router
171
+ app.include_router(voice_chat_router, tags=["Voice Chat"])
172
+ logger.info("✓ Voice Chat route loaded")
173
+ except ImportError as e:
174
+ logger.warning(f"⚠ Voice Chat route not found: {e}")
175
+ except Exception as e:
176
+ logger.warning(f"⚠ Failed loading Voice Chat route: {e}")
177
+
178
  # --- Basic routes ---
179
  @app.get("/health", tags=["Health"])
180
  async def health():
routes/voice_chat_routes.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, UploadFile, File, HTTPException
2
+ from fastapi.responses import StreamingResponse
3
+ from pydantic import BaseModel
4
+ from typing import Optional
5
+ import tempfile
6
+ import os
7
+ from pathlib import Path
8
+ import io
9
+
10
+ from services.stt_service import speech_to_text
11
+ from services.tts_service import text_to_speech
12
+ from services.chat_service import generate_chat_response # Votre service de chatbot existant
13
+
14
+ router = APIRouter(prefix="/voice-chat", tags=["Voice Chat"])
15
+
16
+ class TextChatRequest(BaseModel):
17
+ text: str
18
+ conversation_id: Optional[str] = None
19
+
20
+ class VoiceChatResponse(BaseModel):
21
+ text_response: str
22
+ audio_url: Optional[str] = None
23
+ conversation_id: str
24
+
25
+ @router.post("/voice", response_model=VoiceChatResponse)
26
+ async def voice_chat_endpoint(
27
+ file: UploadFile = File(...),
28
+ conversation_id: Optional[str] = None
29
+ ):
30
+ """
31
+ Point d'entrée unique pour le chat vocal:
32
+ 1. STT: Audio → Texte
33
+ 2. Chatbot: Texte → Réponse
34
+ 3. TTS: Réponse → Audio
35
+ """
36
+ # 1. Vérifier le fichier audio
37
+ if not file.content_type.startswith('audio/'):
38
+ raise HTTPException(400, "Le fichier doit être un fichier audio")
39
+
40
+ # Créer un ID de conversation si non fourni
41
+ if not conversation_id:
42
+ import uuid
43
+ conversation_id = str(uuid.uuid4())
44
+
45
+ # 2. Sauvegarder temporairement l'audio
46
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_file:
47
+ temp_path = temp_file.name
48
+ content = await file.read()
49
+ temp_file.write(content)
50
+
51
+ try:
52
+ # 3. STT: Audio → Texte
53
+ user_text = speech_to_text(temp_path)
54
+
55
+ if not user_text or user_text.strip() == "":
56
+ raise HTTPException(400, "Aucune parole détectée dans l'audio")
57
+
58
+ # 4. Générer la réponse du chatbot (utilise votre logique existante)
59
+ chatbot_response = generate_chat_response(
60
+ user_input=user_text,
61
+ conversation_id=conversation_id
62
+ )
63
+
64
+ # 5. TTS: Réponse texte → Audio
65
+ audio_path = text_to_speech(
66
+ text=chatbot_response,
67
+ voice="Aaliyah-PlayAI", # ou une voix configurable
68
+ fmt="wav"
69
+ )
70
+
71
+ # 6. Lire le fichier audio
72
+ with open(audio_path, "rb") as audio_file:
73
+ audio_data = audio_file.read()
74
+
75
+ # Nettoyer les fichiers temporaires
76
+ os.unlink(temp_path)
77
+ if Path(audio_path).exists():
78
+ os.unlink(audio_path)
79
+
80
+ # 7. Retourner réponse
81
+ return VoiceChatResponse(
82
+ text_response=chatbot_response,
83
+ audio_url=f"/voice-chat/audio/{conversation_id}", # ou stream direct
84
+ conversation_id=conversation_id
85
+ )
86
+
87
+ except Exception as e:
88
+ # Nettoyer en cas d'erreur
89
+ if os.path.exists(temp_path):
90
+ os.unlink(temp_path)
91
+ raise HTTPException(500, f"Erreur lors du traitement vocal: {str(e)}")
92
+
93
+ @router.post("/text", response_model=VoiceChatResponse)
94
+ async def text_chat_endpoint(request: TextChatRequest):
95
+ """
96
+ Alternative: Chat texte avec réponse audio
97
+ Pour les utilisateurs qui préfèrent taper mais écouter la réponse
98
+ """
99
+ # Créer un ID de conversation si non fourni
100
+ if not request.conversation_id:
101
+ import uuid
102
+ conversation_id = str(uuid.uuid4())
103
+ else:
104
+ conversation_id = request.conversation_id
105
+
106
+ try:
107
+ # 1. Générer la réponse du chatbot
108
+ chatbot_response = generate_chat_response(
109
+ user_input=request.text,
110
+ conversation_id=conversation_id
111
+ )
112
+
113
+ # 2. TTS: Réponse texte → Audio
114
+ audio_path = text_to_speech(
115
+ text=chatbot_response,
116
+ voice="Aaliyah-PlayAI",
117
+ fmt="wav"
118
+ )
119
+
120
+ return VoiceChatResponse(
121
+ text_response=chatbot_response,
122
+ audio_url=f"/voice-chat/audio/{conversation_id}",
123
+ conversation_id=conversation_id
124
+ )
125
+
126
+ except Exception as e:
127
+ raise HTTPException(500, f"Erreur lors du chat: {str(e)}")
128
+
129
+ @router.get("/audio/{conversation_id}")
130
+ async def get_audio_stream(conversation_id: str):
131
+ """
132
+ Stream l'audio de la dernière réponse
133
+ (Cache ou base de données dans une version réelle)
134
+ """
135
+ # Dans une vraie implémentation, vous stockeriez l'audio
136
+ # temporairement ou en cache
137
+ return {"message": "Endpoint pour récupérer l'audio"}
138
+
139
+ @router.get("/conversation/{conversation_id}")
140
+ async def get_conversation_history(conversation_id: str):
141
+ """
142
+ Récupérer l'historique d'une conversation
143
+ """
144
+ # Implémentez selon votre système de stockage
145
+ return {"conversation_id": conversation_id, "history": []}
services/chat_service.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ from typing import Dict, List, Optional
3
+ from datetime import datetime
4
+ import json
5
+
6
+ # Pour Groq Chat API (si vous utilisez Groq)
7
+ import requests
8
+ from config import GROQ_API_KEY, GROQ_CHAT_MODEL
9
+
10
+ # Stockage en mémoire (remplacez par une base de données en production)
11
+ conversation_store: Dict[str, List[Dict]] = {}
12
+
13
+ def generate_chat_response(
14
+ user_input: str,
15
+ conversation_id: Optional[str] = None,
16
+ system_prompt: Optional[str] = None
17
+ ) -> str:
18
+ """
19
+ Génère une réponse de chatbot pour une entrée utilisateur
20
+ """
21
+ # 1. Gérer la conversation
22
+ if not conversation_id:
23
+ conversation_id = str(uuid.uuid4())
24
+ conversation_store[conversation_id] = []
25
+
26
+ # 2. Ajouter le message utilisateur à l'historique
27
+ conversation_store[conversation_id].append({
28
+ "role": "user",
29
+ "content": user_input,
30
+ "timestamp": datetime.now().isoformat()
31
+ })
32
+
33
+ # 3. Préparer le prompt système
34
+ if not system_prompt:
35
+ system_prompt = """Tu es un assistant vocal amical et utile.
36
+ Tes réponses doivent être naturelles à l'oral, concises
37
+ (max 2-3 phrases) et adaptées à une synthèse vocale."""
38
+
39
+ # 4. Préparer les messages pour l'API Groq
40
+ messages = [{"role": "system", "content": system_prompt}]
41
+
42
+ # Ajouter l'historique (limité aux derniers messages pour le contexte)
43
+ history = conversation_store[conversation_id][-5:] # Derniers 5 échanges
44
+ for msg in history:
45
+ messages.append({"role": msg["role"], "content": msg["content"]})
46
+
47
+ # 5. Appeler l'API Groq Chat
48
+ try:
49
+ response_text = call_groq_chat_api(messages)
50
+ except Exception as e:
51
+ # Fallback simple
52
+ response_text = f"Désolé, je ne peux pas répondre pour le moment. Erreur: {str(e)}"
53
+
54
+ # 6. Ajouter la réponse à l'historique
55
+ conversation_store[conversation_id].append({
56
+ "role": "assistant",
57
+ "content": response_text,
58
+ "timestamp": datetime.now().isoformat()
59
+ })
60
+
61
+ return response_text
62
+
63
+ def call_groq_chat_api(messages: List[Dict]) -> str:
64
+ """
65
+ Appelle l'API Groq Chat
66
+ """
67
+ if not GROQ_API_KEY:
68
+ raise RuntimeError("GROQ_API_KEY non configurée")
69
+
70
+ url = "https://api.groq.com/openai/v1/chat/completions"
71
+
72
+ headers = {
73
+ "Authorization": f"Bearer {GROQ_API_KEY}",
74
+ "Content-Type": "application/json"
75
+ }
76
+
77
+ payload = {
78
+ "model": GROQ_CHAT_MODEL,
79
+ "messages": messages,
80
+ "temperature": 0.7,
81
+ "max_tokens": 150, # Limité pour les réponses vocales
82
+ "top_p": 0.9,
83
+ }
84
+
85
+ response = requests.post(url, headers=headers, json=payload, timeout=30)
86
+ response.raise_for_status()
87
+
88
+ result = response.json()
89
+ return result["choices"][0]["message"]["content"]
90
+
91
+ def get_conversation_history(conversation_id: str) -> List[Dict]:
92
+ """
93
+ Récupère l'historique d'une conversation
94
+ """
95
+ return conversation_store.get(conversation_id, [])
96
+
97
+ def clear_conversation(conversation_id: str):
98
+ """
99
+ Efface une conversation
100
+ """
101
+ if conversation_id in conversation_store:
102
+ del conversation_store[conversation_id]