malek-messaoudii commited on
Commit
a71355d
·
1 Parent(s): c059296

Update chat voice part

Browse files
models/stt.py CHANGED
@@ -6,6 +6,6 @@ class STTResponse(BaseModel):
6
  class Config:
7
  json_schema_extra = {
8
  "example": {
9
- "text": "Bonjour, comment allez-vous aujourd'hui ?"
10
  }
11
  }
 
6
  class Config:
7
  json_schema_extra = {
8
  "example": {
9
+ "text": "Hello, how are you today?"
10
  }
11
  }
models/tts.py CHANGED
@@ -1,5 +1,4 @@
1
  from pydantic import BaseModel, Field
2
- from typing import Optional
3
 
4
  class TTSRequest(BaseModel):
5
  text: str = Field(..., min_length=1, max_length=5000)
@@ -9,7 +8,7 @@ class TTSRequest(BaseModel):
9
  class Config:
10
  json_schema_extra = {
11
  "example": {
12
- "text": "Bonjour, ceci est un test de synthèse vocale.",
13
  "voice": "Aaliyah-PlayAI",
14
  "format": "wav"
15
  }
 
1
  from pydantic import BaseModel, Field
 
2
 
3
  class TTSRequest(BaseModel):
4
  text: str = Field(..., min_length=1, max_length=5000)
 
8
  class Config:
9
  json_schema_extra = {
10
  "example": {
11
+ "text": "Hello, this is a test of text-to-speech.",
12
  "voice": "Aaliyah-PlayAI",
13
  "format": "wav"
14
  }
models/voice_chat.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import Optional
3
+
4
+ class TextChatRequest(BaseModel):
5
+ text: str
6
+ conversation_id: Optional[str] = None
7
+
8
+ class VoiceChatResponse(BaseModel):
9
+ text_response: str
10
+ audio_url: Optional[str] = None
11
+ conversation_id: str
12
+
13
+ class Config:
14
+ json_schema_extra = {
15
+ "example": {
16
+ "text_response": "Hello! How can I help you today?",
17
+ "audio_url": "/voice-chat/audio/123e4567",
18
+ "conversation_id": "123e4567"
19
+ }
20
+ }
routes/stt_routes.py CHANGED
@@ -1,42 +1,42 @@
1
  from fastapi import APIRouter, UploadFile, File, HTTPException
2
- from fastapi.responses import JSONResponse
3
  from services.stt_service import speech_to_text
4
  from models.stt import STTResponse
5
- import os
6
- import uuid
7
  import tempfile
8
- from pathlib import Path
9
 
10
  router = APIRouter(prefix="/stt", tags=["Speech To Text"])
11
 
12
  @router.post("/", response_model=STTResponse)
13
  async def convert_speech_to_text(file: UploadFile = File(...)):
14
  """
15
- Convert uploaded audio file to text using Groq's Whisper API
16
  """
17
- # Vérifier le type de fichier
18
  if not file.content_type or not file.content_type.startswith('audio/'):
19
  raise HTTPException(status_code=400, detail="File must be an audio file")
20
 
21
- # Créer un fichier temporaire unique
22
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_file:
23
  temp_path = temp_file.name
24
-
25
- # Écrire le contenu téléchargé
26
  content = await file.read()
 
 
 
 
 
27
  temp_file.write(content)
28
 
29
  try:
30
- # Convertir audio en texte
31
  text = speech_to_text(temp_path)
32
 
33
- # Nettoyer le fichier temporaire
34
  os.unlink(temp_path)
35
 
36
  return STTResponse(text=text)
37
 
38
  except Exception as e:
39
- # Nettoyer en cas d'erreur
40
  if os.path.exists(temp_path):
41
  os.unlink(temp_path)
42
  raise HTTPException(status_code=500, detail=str(e))
 
1
  from fastapi import APIRouter, UploadFile, File, HTTPException
 
2
  from services.stt_service import speech_to_text
3
  from models.stt import STTResponse
 
 
4
  import tempfile
5
+ import os
6
 
7
  router = APIRouter(prefix="/stt", tags=["Speech To Text"])
8
 
9
  @router.post("/", response_model=STTResponse)
10
  async def convert_speech_to_text(file: UploadFile = File(...)):
11
  """
12
+ Convert uploaded audio file to text (English only)
13
  """
14
+ # Check file type
15
  if not file.content_type or not file.content_type.startswith('audio/'):
16
  raise HTTPException(status_code=400, detail="File must be an audio file")
17
 
18
+ # Create temporary file
19
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_file:
20
  temp_path = temp_file.name
 
 
21
  content = await file.read()
22
+
23
+ if len(content) == 0:
24
+ os.unlink(temp_path)
25
+ raise HTTPException(status_code=400, detail="Audio file is empty")
26
+
27
  temp_file.write(content)
28
 
29
  try:
30
+ # Convert audio to text
31
  text = speech_to_text(temp_path)
32
 
33
+ # Clean up
34
  os.unlink(temp_path)
35
 
36
  return STTResponse(text=text)
37
 
38
  except Exception as e:
39
+ # Clean up on error
40
  if os.path.exists(temp_path):
41
  os.unlink(temp_path)
42
  raise HTTPException(status_code=500, detail=str(e))
routes/tts_routes.py CHANGED
@@ -2,32 +2,31 @@ from fastapi import APIRouter, HTTPException
2
  from fastapi.responses import FileResponse
3
  from models.tts import TTSRequest
4
  from services.tts_service import text_to_speech
5
- import os
6
  from pathlib import Path
7
 
8
  router = APIRouter(prefix="/tts", tags=["Text To Speech"])
9
 
10
  @router.post("/")
11
- async def generate_speech(request: TTSRequest):
12
  """
13
- Convert text to speech and return audio file
14
  """
15
  try:
16
- # Générer l'audio
17
  audio_path = text_to_speech(
18
  text=request.text,
19
  voice=request.voice,
20
  fmt=request.format
21
  )
22
 
23
- # Vérifier que le fichier existe
24
  if not Path(audio_path).exists():
25
  raise HTTPException(status_code=500, detail="Audio file generation failed")
26
 
27
- # Déterminer le type MIME
28
  media_type = "audio/wav" if request.format == "wav" else "audio/mpeg"
29
 
30
- # Retourner le fichier audio
31
  return FileResponse(
32
  path=audio_path,
33
  filename=f"speech.{request.format}",
 
2
  from fastapi.responses import FileResponse
3
  from models.tts import TTSRequest
4
  from services.tts_service import text_to_speech
 
5
  from pathlib import Path
6
 
7
  router = APIRouter(prefix="/tts", tags=["Text To Speech"])
8
 
9
  @router.post("/")
10
+ async def generate_tts(request: TTSRequest):
11
  """
12
+ Convert text to speech (English only)
13
  """
14
  try:
15
+ # Generate audio
16
  audio_path = text_to_speech(
17
  text=request.text,
18
  voice=request.voice,
19
  fmt=request.format
20
  )
21
 
22
+ # Verify file exists
23
  if not Path(audio_path).exists():
24
  raise HTTPException(status_code=500, detail="Audio file generation failed")
25
 
26
+ # Determine MIME type
27
  media_type = "audio/wav" if request.format == "wav" else "audio/mpeg"
28
 
29
+ # Return audio file
30
  return FileResponse(
31
  path=audio_path,
32
  filename=f"speech.{request.format}",
routes/voice_chat_routes.py CHANGED
@@ -1,5 +1,5 @@
1
  from fastapi import APIRouter, UploadFile, File, HTTPException, Query
2
- from fastapi.responses import FileResponse, StreamingResponse
3
  from pydantic import BaseModel
4
  from typing import Optional
5
  import tempfile
@@ -10,21 +10,12 @@ import io
10
 
11
  from services.stt_service import speech_to_text
12
  from services.tts_service import text_to_speech
13
- # Assurez-vous que ce service existe et fonctionne correctement
14
  from services.chat_service import generate_chat_response
 
15
 
16
  router = APIRouter(prefix="/voice-chat", tags=["Voice Chat"])
17
 
18
- class TextChatRequest(BaseModel):
19
- text: str
20
- conversation_id: Optional[str] = None
21
-
22
- class VoiceChatResponse(BaseModel):
23
- text_response: str
24
- audio_url: Optional[str] = None
25
- conversation_id: str
26
-
27
- # Stockage temporaire pour l'audio généré
28
  audio_cache = {}
29
 
30
  @router.post("/voice", response_model=VoiceChatResponse)
@@ -33,71 +24,75 @@ async def voice_chat_endpoint(
33
  conversation_id: Optional[str] = Query(None)
34
  ):
35
  """
36
- Point d'entrée unique pour le chat vocal:
37
- 1. STT: Audio → Texte
38
- 2. Chatbot: TexteRéponse
39
- 3. TTS: Réponse → Audio
40
  """
41
- # 1. Vérifier le fichier audio
42
  if not file.content_type or not file.content_type.startswith('audio/'):
43
  raise HTTPException(
44
  status_code=400,
45
- detail=f"Le fichier doit être un fichier audio. Type reçu: {file.content_type}"
46
  )
47
 
48
- # 2. Créer un ID de conversation si non fourni
49
  if not conversation_id:
50
  conversation_id = str(uuid.uuid4())
51
 
52
- # 3. Sauvegarder temporairement l'audio
53
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_file:
54
  temp_path = temp_file.name
55
  content = await file.read()
56
 
57
- # Vérifier que le fichier n'est pas vide
58
  if len(content) == 0:
59
  os.unlink(temp_path)
60
- raise HTTPException(status_code=400, detail="Le fichier audio est vide")
61
 
62
  temp_file.write(content)
63
 
64
  try:
65
- # 4. STT: Audio → Texte
66
  user_text = speech_to_text(temp_path)
67
 
68
  if not user_text or user_text.strip() == "":
69
  raise HTTPException(
70
  status_code=400,
71
- detail="Aucune parole détectée dans l'audio. Essayez de parler plus clairement."
72
  )
73
 
74
- # 5. Générer la réponse du chatbot
75
- # Assurez-vous que generate_chat_response fonctionne correctement
 
76
  chatbot_response = generate_chat_response(
77
  user_input=user_text,
78
  conversation_id=conversation_id
79
  )
80
 
81
- # 6. TTS: Réponse texte → Audio
 
 
82
  audio_path = text_to_speech(
83
  text=chatbot_response,
84
- voice="Aaliyah-PlayAI",
85
  fmt="wav"
86
  )
87
 
88
- # 7. Lire le fichier audio
89
  with open(audio_path, "rb") as audio_file:
90
  audio_data = audio_file.read()
91
 
92
- # Stocker l'audio dans le cache
93
- audio_cache[conversation_id] = audio_data
 
 
94
 
95
- # Nettoyer les fichiers temporaires
96
  os.unlink(temp_path)
97
  if Path(audio_path).exists():
98
  os.unlink(audio_path)
99
 
100
- # 8. Retourner réponse avec URL pour récupérer l'audio
101
  return VoiceChatResponse(
102
  text_response=chatbot_response,
103
  audio_url=f"/voice-chat/audio/{conversation_id}",
@@ -105,64 +100,69 @@ async def voice_chat_endpoint(
105
  )
106
 
107
  except HTTPException:
108
- # Relancer les HTTPException
109
  raise
110
  except Exception as e:
111
- # Nettoyer en cas d'erreur
112
  if os.path.exists(temp_path):
113
  os.unlink(temp_path)
114
 
115
- # Log l'erreur complète pour le débogage
116
  import traceback
117
  error_details = traceback.format_exc()
118
- print(f"Erreur dans voice_chat_endpoint: {error_details}")
119
 
120
  raise HTTPException(
121
  status_code=500,
122
- detail=f"Erreur lors du traitement vocal: {str(e)}"
123
  )
124
 
125
  @router.post("/text", response_model=VoiceChatResponse)
126
  async def text_chat_endpoint(request: TextChatRequest):
127
  """
128
- Alternative: Chat texte avec réponse audio
129
- Pour les utilisateurs qui préfèrent taper mais écouter la réponse
130
  """
131
  try:
132
- # 1. Créer un ID de conversation si non fourni
133
  if not request.conversation_id:
134
  conversation_id = str(uuid.uuid4())
135
  else:
136
  conversation_id = request.conversation_id
137
 
138
- # 2. Vérifier que le texte n'est pas vide
139
  if not request.text or request.text.strip() == "":
140
- raise HTTPException(status_code=400, detail="Le texte ne peut pas être vide")
 
 
141
 
142
- # 3. Générer la réponse du chatbot
143
  chatbot_response = generate_chat_response(
144
  user_input=request.text,
145
  conversation_id=conversation_id
146
  )
147
 
148
- # 4. TTS: Réponse texte → Audio
 
 
149
  audio_path = text_to_speech(
150
  text=chatbot_response,
151
  voice="Aaliyah-PlayAI",
152
  fmt="wav"
153
  )
154
 
155
- # 5. Lire et stocker l'audio
156
  with open(audio_path, "rb") as audio_file:
157
  audio_data = audio_file.read()
158
 
159
- audio_cache[conversation_id] = audio_data
 
 
 
160
 
161
- # 6. Nettoyer le fichier temporaire
162
  if Path(audio_path).exists():
163
  os.unlink(audio_path)
164
 
165
- # 7. Retourner réponse
166
  return VoiceChatResponse(
167
  text_response=chatbot_response,
168
  audio_url=f"/voice-chat/audio/{conversation_id}",
@@ -174,66 +174,47 @@ async def text_chat_endpoint(request: TextChatRequest):
174
  except Exception as e:
175
  import traceback
176
  error_details = traceback.format_exc()
177
- print(f"Erreur dans text_chat_endpoint: {error_details}")
178
 
179
  raise HTTPException(
180
  status_code=500,
181
- detail=f"Erreur lors du chat: {str(e)}"
182
  )
183
 
184
  @router.get("/audio/{conversation_id}")
185
  async def get_audio_stream(conversation_id: str):
186
  """
187
- Stream l'audio de la dernière réponse
188
  """
189
  if conversation_id not in audio_cache:
190
  raise HTTPException(
191
  status_code=404,
192
- detail=f"Aucun audio trouvé pour la conversation {conversation_id}"
193
  )
194
 
195
- audio_data = audio_cache[conversation_id]
196
 
197
- # Retourner l'audio en streaming
198
  return StreamingResponse(
199
  io.BytesIO(audio_data),
200
  media_type="audio/wav",
201
  headers={
202
- "Content-Disposition": f"attachment; filename=response_{conversation_id}.wav"
203
  }
204
  )
205
 
206
- @router.get("/conversation/{conversation_id}")
207
- async def get_conversation_history(conversation_id: str):
208
  """
209
- Récupérer l'historique d'une conversation
210
  """
211
- try:
212
- from services.chat_service import get_conversation_history
213
- history = get_conversation_history(conversation_id)
214
-
215
- return {
216
- "conversation_id": conversation_id,
217
- "history": history,
218
- "message_count": len(history)
 
219
  }
220
- except Exception as e:
221
- raise HTTPException(
222
- status_code=500,
223
- detail=f"Erreur lors de la récupération de l'historique: {str(e)}"
224
- )
225
-
226
- # Endpoint pour nettoyer le cache audio (optionnel)
227
- @router.delete("/audio/{conversation_id}")
228
- async def clear_audio_cache(conversation_id: str):
229
- """
230
- Supprimer l'audio d'une conversation du cache
231
- """
232
- if conversation_id in audio_cache:
233
- del audio_cache[conversation_id]
234
- return {"message": f"Audio de la conversation {conversation_id} supprimé"}
235
- else:
236
- raise HTTPException(
237
- status_code=404,
238
- detail=f"Aucun audio trouvé pour la conversation {conversation_id}"
239
- )
 
1
  from fastapi import APIRouter, UploadFile, File, HTTPException, Query
2
+ from fastapi.responses import StreamingResponse
3
  from pydantic import BaseModel
4
  from typing import Optional
5
  import tempfile
 
10
 
11
  from services.stt_service import speech_to_text
12
  from services.tts_service import text_to_speech
 
13
  from services.chat_service import generate_chat_response
14
+ from models.voice_chat import TextChatRequest, VoiceChatResponse
15
 
16
  router = APIRouter(prefix="/voice-chat", tags=["Voice Chat"])
17
 
18
+ # Temporary audio cache
 
 
 
 
 
 
 
 
 
19
  audio_cache = {}
20
 
21
  @router.post("/voice", response_model=VoiceChatResponse)
 
24
  conversation_id: Optional[str] = Query(None)
25
  ):
26
  """
27
+ Complete voice chat endpoint (English only):
28
+ 1. STT: Audio → Text
29
+ 2. Chatbot: TextResponse
30
+ 3. TTS: Response → Audio
31
  """
32
+ # 1. Check audio file
33
  if not file.content_type or not file.content_type.startswith('audio/'):
34
  raise HTTPException(
35
  status_code=400,
36
+ detail=f"File must be an audio file. Received: {file.content_type}"
37
  )
38
 
39
+ # 2. Create conversation ID if not provided
40
  if not conversation_id:
41
  conversation_id = str(uuid.uuid4())
42
 
43
+ # 3. Save audio temporarily
44
  with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_file:
45
  temp_path = temp_file.name
46
  content = await file.read()
47
 
 
48
  if len(content) == 0:
49
  os.unlink(temp_path)
50
+ raise HTTPException(status_code=400, detail="Audio file is empty")
51
 
52
  temp_file.write(content)
53
 
54
  try:
55
+ # 4. STT: Audio → Text (English)
56
  user_text = speech_to_text(temp_path)
57
 
58
  if not user_text or user_text.strip() == "":
59
  raise HTTPException(
60
  status_code=400,
61
+ detail="No speech detected in audio."
62
  )
63
 
64
+ print(f"🎤 STT Result: {user_text}")
65
+
66
+ # 5. Generate chatbot response (English)
67
  chatbot_response = generate_chat_response(
68
  user_input=user_text,
69
  conversation_id=conversation_id
70
  )
71
 
72
+ print(f"🤖 Chatbot Response: {chatbot_response}")
73
+
74
+ # 6. TTS: Response text → Audio (English voice)
75
  audio_path = text_to_speech(
76
  text=chatbot_response,
77
+ voice="Aaliyah-PlayAI", # English voice
78
  fmt="wav"
79
  )
80
 
81
+ # 7. Read and store audio
82
  with open(audio_path, "rb") as audio_file:
83
  audio_data = audio_file.read()
84
 
85
+ audio_cache[conversation_id] = {
86
+ "audio": audio_data,
87
+ "text": chatbot_response
88
+ }
89
 
90
+ # 8. Clean up temporary files
91
  os.unlink(temp_path)
92
  if Path(audio_path).exists():
93
  os.unlink(audio_path)
94
 
95
+ # 9. Return response
96
  return VoiceChatResponse(
97
  text_response=chatbot_response,
98
  audio_url=f"/voice-chat/audio/{conversation_id}",
 
100
  )
101
 
102
  except HTTPException:
 
103
  raise
104
  except Exception as e:
105
+ # Clean up on error
106
  if os.path.exists(temp_path):
107
  os.unlink(temp_path)
108
 
 
109
  import traceback
110
  error_details = traceback.format_exc()
111
+ print(f" Error in voice_chat_endpoint: {error_details}")
112
 
113
  raise HTTPException(
114
  status_code=500,
115
+ detail=f"Error during voice processing: {str(e)}"
116
  )
117
 
118
  @router.post("/text", response_model=VoiceChatResponse)
119
  async def text_chat_endpoint(request: TextChatRequest):
120
  """
121
+ Text chat with audio response (English only)
122
+ For users who prefer to type but hear the response
123
  """
124
  try:
125
+ # 1. Create conversation ID if not provided
126
  if not request.conversation_id:
127
  conversation_id = str(uuid.uuid4())
128
  else:
129
  conversation_id = request.conversation_id
130
 
131
+ # 2. Validate text
132
  if not request.text or request.text.strip() == "":
133
+ raise HTTPException(status_code=400, detail="Text cannot be empty")
134
+
135
+ print(f"📝 Text received: {request.text}")
136
 
137
+ # 3. Generate chatbot response
138
  chatbot_response = generate_chat_response(
139
  user_input=request.text,
140
  conversation_id=conversation_id
141
  )
142
 
143
+ print(f"🤖 Chatbot Response: {chatbot_response}")
144
+
145
+ # 4. TTS with English voice
146
  audio_path = text_to_speech(
147
  text=chatbot_response,
148
  voice="Aaliyah-PlayAI",
149
  fmt="wav"
150
  )
151
 
152
+ # 5. Read and store audio
153
  with open(audio_path, "rb") as audio_file:
154
  audio_data = audio_file.read()
155
 
156
+ audio_cache[conversation_id] = {
157
+ "audio": audio_data,
158
+ "text": chatbot_response
159
+ }
160
 
161
+ # 6. Clean up
162
  if Path(audio_path).exists():
163
  os.unlink(audio_path)
164
 
165
+ # 7. Return response
166
  return VoiceChatResponse(
167
  text_response=chatbot_response,
168
  audio_url=f"/voice-chat/audio/{conversation_id}",
 
174
  except Exception as e:
175
  import traceback
176
  error_details = traceback.format_exc()
177
+ print(f" Error in text_chat_endpoint: {error_details}")
178
 
179
  raise HTTPException(
180
  status_code=500,
181
+ detail=f"Error during chat: {str(e)}"
182
  )
183
 
184
  @router.get("/audio/{conversation_id}")
185
  async def get_audio_stream(conversation_id: str):
186
  """
187
+ Stream audio of the last response
188
  """
189
  if conversation_id not in audio_cache:
190
  raise HTTPException(
191
  status_code=404,
192
+ detail=f"No audio found for conversation {conversation_id}"
193
  )
194
 
195
+ audio_data = audio_cache[conversation_id]["audio"]
196
 
 
197
  return StreamingResponse(
198
  io.BytesIO(audio_data),
199
  media_type="audio/wav",
200
  headers={
201
+ "Content-Disposition": f"attachment; filename=response_{conversation_id[:8]}.wav"
202
  }
203
  )
204
 
205
+ @router.get("/test")
206
+ async def test_endpoint():
207
  """
208
+ Test endpoint to verify API is working
209
  """
210
+ return {
211
+ "status": "ok",
212
+ "message": "Voice Chat API is working (English only)",
213
+ "endpoints": {
214
+ "POST /voice-chat/voice": "Voice input → Voice response",
215
+ "POST /voice-chat/text": "Text input → Voice response",
216
+ "GET /voice-chat/audio/{id}": "Get audio response",
217
+ "POST /stt/": "Speech to text",
218
+ "POST /tts/": "Text to speech"
219
  }
220
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/chat_service.py CHANGED
@@ -6,7 +6,7 @@ import json
6
 
7
  from config import GROQ_API_KEY, GROQ_CHAT_MODEL
8
 
9
- # Stockage en mémoire des conversations
10
  conversation_store: Dict[str, List[Dict]] = {}
11
 
12
  def generate_chat_response(
@@ -15,82 +15,80 @@ def generate_chat_response(
15
  system_prompt: Optional[str] = None
16
  ) -> str:
17
  """
18
- Génère une réponse de chatbot pour une entrée utilisateur
19
  """
20
  try:
21
- # 1. Vérifier que l'input n'est pas vide
22
  if not user_input or not isinstance(user_input, str):
23
- raise ValueError("L'entrée utilisateur doit être une chaîne de caractères non vide")
24
 
25
  user_input = user_input.strip()
26
  if len(user_input) == 0:
27
- return "Je n'ai pas entendu ce que vous avez dit. Pouvez-vous répéter ?"
28
 
29
- # 2. Gérer la conversation
30
  if not conversation_id:
31
  conversation_id = str(uuid.uuid4())
32
 
33
- # Initialiser la conversation si elle n'existe pas
34
  if conversation_id not in conversation_store:
35
  conversation_store[conversation_id] = []
36
 
37
- # 3. Ajouter le message utilisateur à l'historique
38
  conversation_store[conversation_id].append({
39
  "role": "user",
40
  "content": user_input,
41
  "timestamp": datetime.now().isoformat()
42
  })
43
 
44
- # 4. Préparer le prompt système
45
  if not system_prompt:
46
- system_prompt = """Tu es un assistant vocal amical et utile.
47
- Tes réponses doivent être naturelles à l'oral, concises
48
- (max 2-3 phrases) et adaptées à une synthèse vocale.
49
- Sois courtois et serviable."""
50
 
51
- # 5. Préparer les messages pour l'API Groq
52
  messages = [{"role": "system", "content": system_prompt}]
53
 
54
- # Ajouter l'historique (limité aux derniers messages)
55
- history = conversation_store[conversation_id][-6:] # Derniers 6 messages
56
  for msg in history:
57
  messages.append({"role": msg["role"], "content": msg["content"]})
58
 
59
- # 6. Appeler l'API Groq Chat
60
  if not GROQ_API_KEY:
61
- # Fallback si pas de clé API
62
- response_text = f"Bonjour ! Vous avez dit : '{user_input}'. Je suis configuré pour répondre, mais l'API Groq n'est pas configurée."
63
  else:
64
  try:
65
  response_text = call_groq_chat_api(messages)
66
  except Exception as api_error:
67
- # Fallback en cas d'erreur API
68
- print(f"Erreur API Groq: {api_error}")
69
- response_text = f"D'accord, j'ai compris : {user_input}. Je suis un chatbot et je vous réponds."
70
 
71
- # 7. Ajouter la réponse à l'historique
72
  conversation_store[conversation_id].append({
73
  "role": "assistant",
74
  "content": response_text,
75
  "timestamp": datetime.now().isoformat()
76
  })
77
 
78
- # Limiter la taille de l'historique
79
  if len(conversation_store[conversation_id]) > 20:
80
  conversation_store[conversation_id] = conversation_store[conversation_id][-10:]
81
 
82
  return response_text
83
 
84
  except Exception as e:
85
- print(f"Erreur dans generate_chat_response: {e}")
86
- return f"Désolé, une erreur est survenue : {str(e)}"
87
 
88
  def call_groq_chat_api(messages: List[Dict]) -> str:
89
  """
90
- Appelle l'API Groq Chat
91
  """
92
  if not GROQ_API_KEY:
93
- raise RuntimeError("GROQ_API_KEY non configurée")
94
 
95
  url = "https://api.groq.com/openai/v1/chat/completions"
96
 
@@ -103,7 +101,7 @@ def call_groq_chat_api(messages: List[Dict]) -> str:
103
  "model": GROQ_CHAT_MODEL,
104
  "messages": messages,
105
  "temperature": 0.7,
106
- "max_tokens": 300, # Augmenté pour des réponses plus complètes
107
  "top_p": 0.9,
108
  "stream": False
109
  }
@@ -115,24 +113,24 @@ def call_groq_chat_api(messages: List[Dict]) -> str:
115
  result = response.json()
116
 
117
  if "choices" not in result or len(result["choices"]) == 0:
118
- raise ValueError("Réponse invalide de l'API Groq")
119
 
120
  return result["choices"][0]["message"]["content"]
121
 
122
  except requests.exceptions.RequestException as e:
123
- raise Exception(f"Erreur de connexion à l'API Groq: {str(e)}")
124
  except KeyError as e:
125
- raise Exception(f"Format de réponse invalide: {str(e)}")
126
 
127
  def get_conversation_history(conversation_id: str) -> List[Dict]:
128
  """
129
- Récupère l'historique d'une conversation
130
  """
131
  return conversation_store.get(conversation_id, [])
132
 
133
- def clear_conversation(conversation_id: str):
134
  """
135
- Efface une conversation
136
  """
137
  if conversation_id in conversation_store:
138
  del conversation_store[conversation_id]
 
6
 
7
  from config import GROQ_API_KEY, GROQ_CHAT_MODEL
8
 
9
+ # In-memory conversation storage
10
  conversation_store: Dict[str, List[Dict]] = {}
11
 
12
  def generate_chat_response(
 
15
  system_prompt: Optional[str] = None
16
  ) -> str:
17
  """
18
+ Generate chatbot response for user input (English only)
19
  """
20
  try:
21
+ # 1. Validate input
22
  if not user_input or not isinstance(user_input, str):
23
+ raise ValueError("User input must be a non-empty string")
24
 
25
  user_input = user_input.strip()
26
  if len(user_input) == 0:
27
+ return "I didn't hear what you said. Can you repeat?"
28
 
29
+ # 2. Handle conversation
30
  if not conversation_id:
31
  conversation_id = str(uuid.uuid4())
32
 
33
+ # Initialize conversation if it doesn't exist
34
  if conversation_id not in conversation_store:
35
  conversation_store[conversation_id] = []
36
 
37
+ # 3. Add user message to history
38
  conversation_store[conversation_id].append({
39
  "role": "user",
40
  "content": user_input,
41
  "timestamp": datetime.now().isoformat()
42
  })
43
 
44
+ # 4. Prepare system prompt (English only)
45
  if not system_prompt:
46
+ system_prompt = """You are a friendly and helpful English voice assistant.
47
+ Respond in English only. Keep responses concise (2-3 sentences max),
48
+ natural for speech, and helpful. Be polite and engaging."""
 
49
 
50
+ # 5. Prepare messages for Groq API
51
  messages = [{"role": "system", "content": system_prompt}]
52
 
53
+ # Add conversation history (last 6 messages)
54
+ history = conversation_store[conversation_id][-6:]
55
  for msg in history:
56
  messages.append({"role": msg["role"], "content": msg["content"]})
57
 
58
+ # 6. Call Groq Chat API
59
  if not GROQ_API_KEY:
60
+ # Fallback if no API key
61
+ response_text = f"Hello! You said: '{user_input}'. I'm a voice assistant configured to respond in English."
62
  else:
63
  try:
64
  response_text = call_groq_chat_api(messages)
65
  except Exception as api_error:
66
+ print(f"Groq API error: {api_error}")
67
+ response_text = f"I understand you said: {user_input}. How can I help you today?"
 
68
 
69
+ # 7. Add response to history
70
  conversation_store[conversation_id].append({
71
  "role": "assistant",
72
  "content": response_text,
73
  "timestamp": datetime.now().isoformat()
74
  })
75
 
76
+ # Limit history size
77
  if len(conversation_store[conversation_id]) > 20:
78
  conversation_store[conversation_id] = conversation_store[conversation_id][-10:]
79
 
80
  return response_text
81
 
82
  except Exception as e:
83
+ print(f"Error in generate_chat_response: {e}")
84
+ return "Sorry, an error occurred. Can you please repeat?"
85
 
86
  def call_groq_chat_api(messages: List[Dict]) -> str:
87
  """
88
+ Call Groq Chat API
89
  """
90
  if not GROQ_API_KEY:
91
+ raise RuntimeError("GROQ_API_KEY is not configured")
92
 
93
  url = "https://api.groq.com/openai/v1/chat/completions"
94
 
 
101
  "model": GROQ_CHAT_MODEL,
102
  "messages": messages,
103
  "temperature": 0.7,
104
+ "max_tokens": 300,
105
  "top_p": 0.9,
106
  "stream": False
107
  }
 
113
  result = response.json()
114
 
115
  if "choices" not in result or len(result["choices"]) == 0:
116
+ raise ValueError("Invalid response from Groq API")
117
 
118
  return result["choices"][0]["message"]["content"]
119
 
120
  except requests.exceptions.RequestException as e:
121
+ raise Exception(f"Groq API connection error: {str(e)}")
122
  except KeyError as e:
123
+ raise Exception(f"Invalid response format: {str(e)}")
124
 
125
  def get_conversation_history(conversation_id: str) -> List[Dict]:
126
  """
127
+ Get conversation history
128
  """
129
  return conversation_store.get(conversation_id, [])
130
 
131
+ def clear_conversation(conversation_id: str) -> bool:
132
  """
133
+ Clear a conversation
134
  """
135
  if conversation_id in conversation_store:
136
  del conversation_store[conversation_id]
services/stt_service.py CHANGED
@@ -1,11 +1,9 @@
1
  import requests
2
  from config import GROQ_API_KEY, GROQ_STT_MODEL
3
- import tempfile
4
- import os
5
 
6
  def speech_to_text(audio_file: str) -> str:
7
  """
8
- Convert audio file to text using Groq's Whisper API
9
  """
10
  if not GROQ_API_KEY:
11
  raise RuntimeError("GROQ_API_KEY is not set in config")
@@ -16,19 +14,19 @@ def speech_to_text(audio_file: str) -> str:
16
  "Authorization": f"Bearer {GROQ_API_KEY}"
17
  }
18
 
19
- # Lire le fichier audio
20
  with open(audio_file, "rb") as audio_data:
21
  files = {
22
- "file": (os.path.basename(audio_file), audio_data, "audio/wav")
23
  }
24
  data = {
25
  "model": GROQ_STT_MODEL,
 
26
  "temperature": 0,
27
  "response_format": "json"
28
  }
29
 
30
  try:
31
- response = requests.post(url, headers=headers, files=files, data=data)
32
  response.raise_for_status()
33
 
34
  result = response.json()
 
1
  import requests
2
  from config import GROQ_API_KEY, GROQ_STT_MODEL
 
 
3
 
4
  def speech_to_text(audio_file: str) -> str:
5
  """
6
+ Convert audio file to text using Groq's Whisper API (English only)
7
  """
8
  if not GROQ_API_KEY:
9
  raise RuntimeError("GROQ_API_KEY is not set in config")
 
14
  "Authorization": f"Bearer {GROQ_API_KEY}"
15
  }
16
 
 
17
  with open(audio_file, "rb") as audio_data:
18
  files = {
19
+ "file": (audio_file, audio_data, "audio/wav")
20
  }
21
  data = {
22
  "model": GROQ_STT_MODEL,
23
+ "language": "en", # Force English
24
  "temperature": 0,
25
  "response_format": "json"
26
  }
27
 
28
  try:
29
+ response = requests.post(url, headers=headers, files=files, data=data, timeout=30)
30
  response.raise_for_status()
31
 
32
  result = response.json()
services/tts_service.py CHANGED
@@ -4,10 +4,13 @@ import os
4
  from pathlib import Path
5
  from config import GROQ_API_KEY, GROQ_TTS_MODEL
6
 
7
- def text_to_speech(text: str, voice: str = "Aaliyah-PlayAI", fmt: str = "wav") -> str:
 
 
 
 
8
  """
9
- Convert text to speech using Groq's TTS API
10
- Returns the path to the generated audio file
11
  """
12
  if not GROQ_API_KEY:
13
  raise RuntimeError("GROQ_API_KEY is not set in config")
@@ -30,19 +33,19 @@ def text_to_speech(text: str, voice: str = "Aaliyah-PlayAI", fmt: str = "wav") -
30
  }
31
 
32
  try:
33
- # Créer un répertoire temporaire pour les fichiers audio
34
  temp_dir = Path("temp_audio")
35
  temp_dir.mkdir(exist_ok=True)
36
 
37
- # Nom de fichier unique
38
  output_filename = f"tts_{uuid.uuid4().hex[:8]}.{fmt}"
39
  output_path = temp_dir / output_filename
40
 
41
- # Appel API Groq
42
  response = requests.post(url, headers=headers, json=payload, timeout=30)
43
  response.raise_for_status()
44
 
45
- # Sauvegarder le fichier audio
46
  with open(output_path, "wb") as f:
47
  f.write(response.content)
48
 
 
4
  from pathlib import Path
5
  from config import GROQ_API_KEY, GROQ_TTS_MODEL
6
 
7
+ def text_to_speech(
8
+ text: str,
9
+ voice: str = "Aaliyah-PlayAI",
10
+ fmt: str = "wav"
11
+ ) -> str:
12
  """
13
+ Convert text to speech using Groq's TTS API (English only)
 
14
  """
15
  if not GROQ_API_KEY:
16
  raise RuntimeError("GROQ_API_KEY is not set in config")
 
33
  }
34
 
35
  try:
36
+ # Create temp directory for audio files
37
  temp_dir = Path("temp_audio")
38
  temp_dir.mkdir(exist_ok=True)
39
 
40
+ # Unique filename
41
  output_filename = f"tts_{uuid.uuid4().hex[:8]}.{fmt}"
42
  output_path = temp_dir / output_filename
43
 
44
+ # Call Groq API
45
  response = requests.post(url, headers=headers, json=payload, timeout=30)
46
  response.raise_for_status()
47
 
48
+ # Save audio file
49
  with open(output_path, "wb") as f:
50
  f.write(response.content)
51