Update services/streaming_voice_service.py
Browse files- services/streaming_voice_service.py +193 -250
services/streaming_voice_service.py
CHANGED
|
@@ -990,77 +990,106 @@ from core.silero_vad import SileroVAD
|
|
| 990 |
|
| 991 |
class VoskStreamingASR:
|
| 992 |
def __init__(self, model_path: str = None):
|
| 993 |
-
"""Khởi tạo VOSK ASR streaming"""
|
| 994 |
self.model = None
|
| 995 |
self.recognizer = None
|
| 996 |
self.sample_rate = 16000
|
| 997 |
self.is_streaming = False
|
| 998 |
-
self.partial_results = []
|
| 999 |
|
| 1000 |
# Tự động tải model nếu không có đường dẫn
|
| 1001 |
if model_path is None:
|
| 1002 |
model_path = self._download_vosk_model()
|
| 1003 |
|
| 1004 |
-
if os.path.exists(model_path):
|
| 1005 |
-
print("🔄 Đang tải VOSK model
|
| 1006 |
-
|
| 1007 |
-
|
| 1008 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1009 |
else:
|
| 1010 |
print(f"❌ Không tìm thấy VOSK model tại: {model_path}")
|
| 1011 |
|
| 1012 |
def _download_vosk_model(self):
|
| 1013 |
"""Tải VOSK model tiếng Việt tự động"""
|
| 1014 |
-
|
| 1015 |
-
|
| 1016 |
-
|
| 1017 |
-
|
| 1018 |
-
|
| 1019 |
-
|
| 1020 |
-
|
| 1021 |
-
|
| 1022 |
-
|
| 1023 |
-
|
| 1024 |
-
|
| 1025 |
-
|
| 1026 |
-
|
| 1027 |
urllib.request.urlretrieve(model_url, zip_path)
|
| 1028 |
|
| 1029 |
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
| 1030 |
zip_ref.extractall("models/")
|
| 1031 |
|
| 1032 |
-
|
| 1033 |
-
os.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1034 |
print("✅ Đã tải VOSK model thành công")
|
| 1035 |
-
|
| 1036 |
-
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
|
|
|
|
| 1040 |
|
| 1041 |
def start_stream(self):
|
| 1042 |
"""Bắt đầu stream mới"""
|
| 1043 |
if self.model is None:
|
|
|
|
| 1044 |
return False
|
| 1045 |
|
| 1046 |
-
|
| 1047 |
-
|
| 1048 |
-
|
| 1049 |
-
|
| 1050 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1051 |
|
| 1052 |
def process_audio_chunk(self, audio_chunk: np.ndarray, sample_rate: int = None) -> Dict[str, Any]:
|
| 1053 |
-
"""Xử lý audio chunk và trả về kết quả"""
|
| 1054 |
if self.recognizer is None or not self.is_streaming:
|
| 1055 |
return {"text": "", "partial": "", "is_final": False}
|
| 1056 |
|
| 1057 |
try:
|
| 1058 |
-
#
|
|
|
|
|
|
|
|
|
|
| 1059 |
if sample_rate and sample_rate != self.sample_rate:
|
| 1060 |
audio_chunk = self._resample_audio(audio_chunk, sample_rate, self.sample_rate)
|
| 1061 |
|
|
|
|
| 1062 |
if audio_chunk.dtype != np.int16:
|
| 1063 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1064 |
|
| 1065 |
# Chuyển đổi sang bytes
|
| 1066 |
audio_bytes = audio_chunk.tobytes()
|
|
@@ -1068,41 +1097,53 @@ class VoskStreamingASR:
|
|
| 1068 |
# Xử lý với VOSK
|
| 1069 |
if self.recognizer.AcceptWaveform(audio_bytes):
|
| 1070 |
# Kết quả cuối cùng
|
| 1071 |
-
|
|
|
|
| 1072 |
text = result.get('text', '').strip()
|
|
|
|
| 1073 |
if text:
|
| 1074 |
return {"text": text, "partial": "", "is_final": True}
|
| 1075 |
else:
|
| 1076 |
# Kết quả tạm thời
|
| 1077 |
-
|
|
|
|
| 1078 |
partial_text = partial_result.get('partial', '').strip()
|
| 1079 |
-
|
|
|
|
|
|
|
| 1080 |
|
| 1081 |
except Exception as e:
|
| 1082 |
print(f"❌ Lỗi VOSK processing: {e}")
|
|
|
|
| 1083 |
|
| 1084 |
return {"text": "", "partial": "", "is_final": False}
|
| 1085 |
|
| 1086 |
def stop_stream(self) -> str:
|
| 1087 |
"""Kết thúc stream và lấy kết quả cuối"""
|
| 1088 |
if self.recognizer:
|
| 1089 |
-
|
| 1090 |
-
|
| 1091 |
-
|
| 1092 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1093 |
return ""
|
| 1094 |
|
| 1095 |
def _resample_audio(self, audio: np.ndarray, orig_sr: int, target_sr: int) -> np.ndarray:
|
| 1096 |
-
"""Resample audio"""
|
| 1097 |
if orig_sr == target_sr:
|
| 1098 |
return audio
|
| 1099 |
try:
|
| 1100 |
from scipy import signal
|
| 1101 |
-
|
| 1102 |
-
|
| 1103 |
-
resampled_audio = signal.resample(audio,
|
| 1104 |
return resampled_audio.astype(np.int16)
|
| 1105 |
-
except Exception:
|
|
|
|
| 1106 |
return audio
|
| 1107 |
|
| 1108 |
class StreamingVoiceService:
|
|
@@ -1111,7 +1152,8 @@ class StreamingVoiceService:
|
|
| 1111 |
self.rag_system = rag_system
|
| 1112 |
self.tts_service = tts_service
|
| 1113 |
|
| 1114 |
-
# Khởi tạo VOSK ASR
|
|
|
|
| 1115 |
self.vosk_asr = VoskStreamingASR()
|
| 1116 |
|
| 1117 |
# Khởi tạo VAD
|
|
@@ -1119,7 +1161,6 @@ class StreamingVoiceService:
|
|
| 1119 |
self.is_listening = False
|
| 1120 |
self.speech_callback = None
|
| 1121 |
self.is_processing = False
|
| 1122 |
-
self.processing_lock = threading.Lock()
|
| 1123 |
|
| 1124 |
# Conversation context
|
| 1125 |
self.conversation_history = []
|
|
@@ -1131,37 +1172,50 @@ class StreamingVoiceService:
|
|
| 1131 |
self.processing_threads = []
|
| 1132 |
self.max_workers = 2
|
| 1133 |
|
| 1134 |
-
#
|
| 1135 |
self.vosk_stream_active = False
|
| 1136 |
self.last_voice_time = 0
|
| 1137 |
-
self.silence_timeout =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1138 |
|
| 1139 |
# Latency tracking
|
| 1140 |
self.latency_metrics = {
|
| 1141 |
-
'asr': [],
|
| 1142 |
-
'
|
| 1143 |
-
'llm': [],
|
| 1144 |
-
'tts': [],
|
| 1145 |
-
'total': [],
|
| 1146 |
-
'vad_detection': [],
|
| 1147 |
-
'queue_waiting': [],
|
| 1148 |
-
'vosk_processing': []
|
| 1149 |
}
|
| 1150 |
|
| 1151 |
self.current_callback = None
|
| 1152 |
|
| 1153 |
def start_listening(self, speech_callback: Callable) -> bool:
|
| 1154 |
-
"""Bắt đầu lắng nghe với VOSK streaming"""
|
| 1155 |
if self.is_listening:
|
|
|
|
| 1156 |
return False
|
| 1157 |
|
| 1158 |
self.current_callback = speech_callback
|
| 1159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1160 |
# Khởi động VOSK stream
|
| 1161 |
if not self.vosk_asr.start_stream():
|
| 1162 |
-
print("❌ Không thể khởi động VOSK
|
| 1163 |
return False
|
| 1164 |
|
|
|
|
| 1165 |
success = self.vad_processor.start_stream(self._on_speech_detected)
|
| 1166 |
|
| 1167 |
if success:
|
|
@@ -1169,6 +1223,7 @@ class StreamingVoiceService:
|
|
| 1169 |
self.is_processing = False
|
| 1170 |
self.vosk_stream_active = True
|
| 1171 |
self.last_voice_time = time.time()
|
|
|
|
| 1172 |
|
| 1173 |
# Khởi động worker threads
|
| 1174 |
if not self.processing_threads:
|
|
@@ -1185,74 +1240,76 @@ class StreamingVoiceService:
|
|
| 1185 |
threading.Thread(target=self._vosk_streaming_monitor, daemon=True).start()
|
| 1186 |
|
| 1187 |
print("🎙️ Đã bắt đầu lắng nghe với VOSK ASR streaming")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1188 |
return True
|
|
|
|
| 1189 |
return False
|
| 1190 |
|
| 1191 |
-
def stop_listening(self):
|
| 1192 |
-
"""Dừng lắng nghe"""
|
| 1193 |
-
self.vad_processor.stop_stream()
|
| 1194 |
-
self.is_listening = False
|
| 1195 |
-
self.is_processing = False
|
| 1196 |
-
self.vosk_stream_active = False
|
| 1197 |
-
self.current_callback = None
|
| 1198 |
-
|
| 1199 |
-
# Dừng VOSK stream
|
| 1200 |
-
final_text = self.vosk_asr.stop_stream()
|
| 1201 |
-
if final_text and len(final_text) > 1:
|
| 1202 |
-
self._process_final_transcription(final_text)
|
| 1203 |
-
|
| 1204 |
-
# Clear queue
|
| 1205 |
-
while not self.response_queue.empty():
|
| 1206 |
-
try:
|
| 1207 |
-
self.response_queue.get_nowait()
|
| 1208 |
-
self.response_queue.task_done()
|
| 1209 |
-
except queue.Empty:
|
| 1210 |
-
break
|
| 1211 |
-
|
| 1212 |
-
print("🛑 Đã dừng lắng nghe")
|
| 1213 |
-
|
| 1214 |
def _vosk_streaming_monitor(self):
|
| 1215 |
"""Theo dõi VOSK streaming và xử lý kết quả real-time"""
|
| 1216 |
while self.is_listening and self.vosk_stream_active:
|
| 1217 |
try:
|
| 1218 |
-
# Kiểm tra timeout im lặng
|
| 1219 |
current_time = time.time()
|
| 1220 |
silence_duration = current_time - self.last_voice_time
|
| 1221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1222 |
if silence_duration > self.silence_timeout and self.partial_transcription:
|
| 1223 |
-
|
| 1224 |
-
|
| 1225 |
-
|
| 1226 |
self.partial_transcription = ""
|
| 1227 |
-
self.vosk_asr.start_stream()
|
| 1228 |
|
| 1229 |
-
time.sleep(0.1)
|
| 1230 |
|
| 1231 |
except Exception as e:
|
| 1232 |
print(f"❌ Lỗi VOSK monitor: {e}")
|
| 1233 |
break
|
| 1234 |
|
| 1235 |
def _on_speech_detected(self, speech_audio: np.ndarray, sample_rate: int):
|
| 1236 |
-
"""Callback khi VAD phát hiện speech -
|
| 1237 |
-
|
| 1238 |
-
|
| 1239 |
-
if not self.vosk_stream_active:
|
| 1240 |
return
|
| 1241 |
|
| 1242 |
# Cập nhật thời gian có giọng nói
|
| 1243 |
self.last_voice_time = time.time()
|
| 1244 |
|
| 1245 |
-
#
|
| 1246 |
-
|
| 1247 |
-
result = self.vosk_asr.process_audio_chunk(speech_audio, sample_rate)
|
| 1248 |
-
vosk_latency = time.time() - vosk_start
|
| 1249 |
|
| 1250 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1251 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1252 |
# Xử lý kết quả partial
|
| 1253 |
if result['partial'] and len(result['partial']) > 1:
|
| 1254 |
self.partial_transcription = result['partial']
|
| 1255 |
-
print(f"🎯 VOSK Partial: {result['partial']}")
|
| 1256 |
|
| 1257 |
# Gửi partial result real-time
|
| 1258 |
if self.current_callback:
|
|
@@ -1265,184 +1322,61 @@ class StreamingVoiceService:
|
|
| 1265 |
|
| 1266 |
# Xử lý kết quả final
|
| 1267 |
if result['is_final'] and result['text'] and len(result['text']) > 1:
|
| 1268 |
-
print(f"✅ VOSK Final: {result['text']}")
|
| 1269 |
self._process_final_transcription(result['text'])
|
| 1270 |
self.partial_transcription = ""
|
|
|
|
| 1271 |
self.vosk_asr.start_stream() # Bắt đầu stream mới
|
| 1272 |
-
|
| 1273 |
-
vad_latency = time.time() - vad_start
|
| 1274 |
-
self._add_latency_sample('vad_detection', vad_latency)
|
| 1275 |
|
| 1276 |
def _process_final_transcription(self, transcription: str):
|
| 1277 |
"""Xử lý transcription cuối cùng"""
|
| 1278 |
if not transcription or len(transcription.strip()) < 2:
|
| 1279 |
return
|
| 1280 |
|
| 1281 |
-
print(f"📝 Final Transcription: {transcription}")
|
| 1282 |
self.current_transcription = transcription
|
| 1283 |
|
| 1284 |
# Đưa vào queue để xử lý
|
| 1285 |
try:
|
| 1286 |
self.response_queue.put(transcription, timeout=0.5)
|
|
|
|
| 1287 |
except queue.Full:
|
| 1288 |
print("⚠️ Queue đầy, bỏ qua transcription")
|
| 1289 |
|
| 1290 |
-
def _process_response_worker(self):
|
| 1291 |
-
"""Worker thread xử lý transcriptions"""
|
| 1292 |
-
while self.is_listening:
|
| 1293 |
-
try:
|
| 1294 |
-
queue_start = time.time()
|
| 1295 |
-
transcription = self.response_queue.get(timeout=1.0)
|
| 1296 |
-
queue_latency = time.time() - queue_start
|
| 1297 |
-
self._add_latency_sample('queue_waiting', queue_latency)
|
| 1298 |
-
|
| 1299 |
-
# Xử lý AI response
|
| 1300 |
-
self._process_ai_response(transcription)
|
| 1301 |
-
|
| 1302 |
-
self.response_queue.task_done()
|
| 1303 |
-
|
| 1304 |
-
except queue.Empty:
|
| 1305 |
-
continue
|
| 1306 |
-
except Exception as e:
|
| 1307 |
-
print(f"❌ Lỗi trong worker thread: {e}")
|
| 1308 |
-
continue
|
| 1309 |
-
|
| 1310 |
-
def _process_ai_response(self, transcription: str):
|
| 1311 |
-
"""Xử lý phản hồi AI cho transcription"""
|
| 1312 |
-
total_start_time = time.time()
|
| 1313 |
-
|
| 1314 |
-
try:
|
| 1315 |
-
# 1. AI Response Generation
|
| 1316 |
-
rag_start = time.time()
|
| 1317 |
-
response = self._generate_ai_response_optimized(transcription)
|
| 1318 |
-
rag_latency = time.time() - rag_start
|
| 1319 |
-
|
| 1320 |
-
# 2. TTS Conversion
|
| 1321 |
-
tts_start = time.time()
|
| 1322 |
-
tts_audio_path = self._text_to_speech_optimized(response)
|
| 1323 |
-
tts_latency = time.time() - tts_start
|
| 1324 |
-
|
| 1325 |
-
total_latency = time.time() - total_start_time
|
| 1326 |
-
|
| 1327 |
-
# Log latency metrics
|
| 1328 |
-
self._log_latency_metrics({
|
| 1329 |
-
'rag': rag_latency,
|
| 1330 |
-
'tts': tts_latency,
|
| 1331 |
-
'total': total_latency
|
| 1332 |
-
})
|
| 1333 |
-
|
| 1334 |
-
# Gọi callback
|
| 1335 |
-
if self.current_callback:
|
| 1336 |
-
result = {
|
| 1337 |
-
'transcription': transcription,
|
| 1338 |
-
'response': response,
|
| 1339 |
-
'tts_audio': tts_audio_path,
|
| 1340 |
-
'status': 'completed',
|
| 1341 |
-
'latency': total_latency
|
| 1342 |
-
}
|
| 1343 |
-
self.current_callback(result)
|
| 1344 |
-
|
| 1345 |
-
except Exception as e:
|
| 1346 |
-
print(f"❌ Lỗi xử lý AI response: {e}")
|
| 1347 |
-
traceback.print_exc()
|
| 1348 |
-
|
| 1349 |
-
if self.current_callback:
|
| 1350 |
-
self.current_callback({
|
| 1351 |
-
'transcription': transcription,
|
| 1352 |
-
'response': f"Xin lỗi, có lỗi xảy ra: {str(e)}",
|
| 1353 |
-
'tts_audio': None,
|
| 1354 |
-
'status': 'error'
|
| 1355 |
-
})
|
| 1356 |
-
|
| 1357 |
-
def _generate_ai_response_optimized(self, user_input: str) -> str:
|
| 1358 |
-
"""Sinh phản hồi AI với tối ưu hiệu suất"""
|
| 1359 |
-
llm_start = time.time()
|
| 1360 |
-
try:
|
| 1361 |
-
# Thêm vào lịch sử
|
| 1362 |
-
self.conversation_history.append({"role": "user", "content": user_input})
|
| 1363 |
-
|
| 1364 |
-
# Tìm kiếm RAG
|
| 1365 |
-
rag_start = time.time()
|
| 1366 |
-
rag_results = self.rag_system.semantic_search(user_input, top_k=2) if self.rag_system else []
|
| 1367 |
-
rag_latency = time.time() - rag_start
|
| 1368 |
-
|
| 1369 |
-
context_text = "\n".join([f"- {result.get('text', str(result))}" for result in rag_results]) if rag_results else ""
|
| 1370 |
-
|
| 1371 |
-
system_prompt = f"""Bạn là trợ lý AI thông minh chuyên về tiếng Việt.
|
| 1372 |
-
Hãy trả lời ngắn gọn, tự nhiên và hữu ích (dưới 100 từ).
|
| 1373 |
-
Thông tin tham khảo:
|
| 1374 |
-
{context_text}
|
| 1375 |
-
"""
|
| 1376 |
-
|
| 1377 |
-
messages = [{"role": "system", "content": system_prompt}]
|
| 1378 |
-
messages.extend(self.conversation_history[-6:])
|
| 1379 |
-
|
| 1380 |
-
llm_inference_start = time.time()
|
| 1381 |
-
completion = self.client.chat.completions.create(
|
| 1382 |
-
model=settings.MULTILINGUAL_LLM_MODEL,
|
| 1383 |
-
messages=messages,
|
| 1384 |
-
max_tokens=300,
|
| 1385 |
-
temperature=0.7
|
| 1386 |
-
)
|
| 1387 |
-
ttft = time.time() - llm_inference_start
|
| 1388 |
-
|
| 1389 |
-
response = completion.choices[0].message.content
|
| 1390 |
-
self.conversation_history.append({"role": "assistant", "content": response})
|
| 1391 |
-
|
| 1392 |
-
total_llm_latency = time.time() - llm_start
|
| 1393 |
-
|
| 1394 |
-
# Giới hạn lịch sử
|
| 1395 |
-
if len(self.conversation_history) > 12:
|
| 1396 |
-
self.conversation_history = self.conversation_history[-12:]
|
| 1397 |
-
|
| 1398 |
-
self._add_latency_sample('llm', total_llm_latency)
|
| 1399 |
-
self._add_latency_sample('rag', rag_latency)
|
| 1400 |
-
|
| 1401 |
-
print(f"✅ RAG Latency: {rag_latency:.2f}s")
|
| 1402 |
-
print(f"✅ LLM TTFT: {ttft:.2f}s")
|
| 1403 |
-
print(f"✅ Total LLM Latency: {total_llm_latency:.2f}s")
|
| 1404 |
-
|
| 1405 |
-
return response
|
| 1406 |
-
|
| 1407 |
-
except Exception as e:
|
| 1408 |
-
print(f"❌ Lỗi tạo AI response: {e}")
|
| 1409 |
-
return "Xin lỗi, tôi gặp lỗi khi tạo phản hồi. Vui lòng thử lại."
|
| 1410 |
-
|
| 1411 |
-
def _text_to_speech_optimized(self, text: str) -> Optional[str]:
|
| 1412 |
-
"""Chuyển văn bản thành giọng nói với tối ưu"""
|
| 1413 |
-
tts_start = time.time()
|
| 1414 |
-
try:
|
| 1415 |
-
if not text or text.startswith("❌") or text.startswith("Xin lỗi"):
|
| 1416 |
-
return None
|
| 1417 |
-
|
| 1418 |
-
tts_bytes = self.tts_service.text_to_speech(text, 'vi')
|
| 1419 |
-
tts_latency = time.time() - tts_start
|
| 1420 |
-
|
| 1421 |
-
if tts_bytes:
|
| 1422 |
-
audio_path = self.tts_service.save_audio_to_file(tts_bytes)
|
| 1423 |
-
self._add_latency_sample('tts', tts_latency)
|
| 1424 |
-
return audio_path
|
| 1425 |
-
except Exception as e:
|
| 1426 |
-
print(f"❌ Lỗi TTS: {e}")
|
| 1427 |
-
return None
|
| 1428 |
-
|
| 1429 |
def process_streaming_audio(self, audio_data: tuple) -> Dict[str, Any]:
|
| 1430 |
-
"""Xử lý audio streaming manual mode với VOSK"""
|
| 1431 |
if not audio_data:
|
| 1432 |
return self._create_error_response("❌ Không có dữ liệu âm thanh")
|
| 1433 |
|
| 1434 |
try:
|
| 1435 |
sample_rate, audio_array = audio_data
|
| 1436 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1437 |
# Khởi động VOSK stream tạm thời
|
| 1438 |
-
self.vosk_asr.start_stream()
|
|
|
|
| 1439 |
|
| 1440 |
# Xử lý audio với VOSK
|
| 1441 |
result = self.vosk_asr.process_audio_chunk(audio_array, sample_rate)
|
| 1442 |
|
| 1443 |
-
if result['is_final'] and result['text']:
|
| 1444 |
transcription = result['text']
|
| 1445 |
-
print(f"📝 Manual Transcription: {transcription}")
|
| 1446 |
|
| 1447 |
# Tạo phản hồi AI
|
| 1448 |
response = self._generate_ai_response_optimized(transcription)
|
|
@@ -1454,9 +1388,16 @@ Thông tin tham khảo:
|
|
| 1454 |
'tts_audio': tts_audio_path,
|
| 1455 |
'status': 'completed'
|
| 1456 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1457 |
else:
|
| 1458 |
return {
|
| 1459 |
-
'transcription':
|
| 1460 |
'response': "",
|
| 1461 |
'tts_audio': None,
|
| 1462 |
'status': 'listening'
|
|
@@ -1464,6 +1405,7 @@ Thông tin tham khảo:
|
|
| 1464 |
|
| 1465 |
except Exception as e:
|
| 1466 |
print(f"❌ Lỗi xử lý streaming audio: {e}")
|
|
|
|
| 1467 |
return self._create_error_response(f"❌ Lỗi: {str(e)}")
|
| 1468 |
|
| 1469 |
def _create_error_response(self, message: str) -> Dict[str, Any]:
|
|
@@ -1524,9 +1466,10 @@ Thông tin tham khảo:
|
|
| 1524 |
'queue_size': self.response_queue.qsize(),
|
| 1525 |
'worker_threads': len(self.processing_threads),
|
| 1526 |
'vosk_active': self.vosk_stream_active,
|
|
|
|
|
|
|
| 1527 |
'last_update': time.strftime("%H:%M:%S")
|
| 1528 |
}
|
| 1529 |
-
|
| 1530 |
def clear_conversation(self):
|
| 1531 |
"""Xóa lịch sử hội thoại"""
|
| 1532 |
self.conversation_history = []
|
|
|
|
| 990 |
|
| 991 |
class VoskStreamingASR:
|
| 992 |
def __init__(self, model_path: str = None):
|
| 993 |
+
"""Khởi tạo VOSK ASR streaming với debug"""
|
| 994 |
self.model = None
|
| 995 |
self.recognizer = None
|
| 996 |
self.sample_rate = 16000
|
| 997 |
self.is_streaming = False
|
|
|
|
| 998 |
|
| 999 |
# Tự động tải model nếu không có đường dẫn
|
| 1000 |
if model_path is None:
|
| 1001 |
model_path = self._download_vosk_model()
|
| 1002 |
|
| 1003 |
+
if model_path and os.path.exists(model_path):
|
| 1004 |
+
print(f"🔄 Đang tải VOSK model từ: {model_path}")
|
| 1005 |
+
try:
|
| 1006 |
+
self.model = Model(model_path)
|
| 1007 |
+
self.recognizer = KaldiRecognizer(self.model, self.sample_rate)
|
| 1008 |
+
self.recognizer.SetWords(True)
|
| 1009 |
+
print("✅ Đã tải VOSK model thành công")
|
| 1010 |
+
except Exception as e:
|
| 1011 |
+
print(f"❌ Lỗi khởi tạo VOSK model: {e}")
|
| 1012 |
else:
|
| 1013 |
print(f"❌ Không tìm thấy VOSK model tại: {model_path}")
|
| 1014 |
|
| 1015 |
def _download_vosk_model(self):
|
| 1016 |
"""Tải VOSK model tiếng Việt tự động"""
|
| 1017 |
+
try:
|
| 1018 |
+
import urllib.request
|
| 1019 |
+
import zipfile
|
| 1020 |
+
|
| 1021 |
+
model_url = "https://alphacephei.com/vosk/models/vosk-model-small-vn-0.4.zip"
|
| 1022 |
+
model_dir = "models/vosk-model-small-vn-0.4"
|
| 1023 |
+
zip_path = "models/vosk-model-small-vn-0.4.zip"
|
| 1024 |
+
|
| 1025 |
+
# Tạo thư mục nếu chưa có
|
| 1026 |
+
os.makedirs("models", exist_ok=True)
|
| 1027 |
+
|
| 1028 |
+
if not os.path.exists(model_dir):
|
| 1029 |
+
print("📥 Đang tải VOSK Vietnamese model...")
|
| 1030 |
urllib.request.urlretrieve(model_url, zip_path)
|
| 1031 |
|
| 1032 |
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
| 1033 |
zip_ref.extractall("models/")
|
| 1034 |
|
| 1035 |
+
# Đảm bảo thư mục tồn tại
|
| 1036 |
+
if os.path.exists("models/vosk-model-small-vn-0.4"):
|
| 1037 |
+
os.rename("models/vosk-model-small-vn-0.4", model_dir)
|
| 1038 |
+
|
| 1039 |
+
if os.path.exists(zip_path):
|
| 1040 |
+
os.remove(zip_path)
|
| 1041 |
print("✅ Đã tải VOSK model thành công")
|
| 1042 |
+
|
| 1043 |
+
return model_dir if os.path.exists(model_dir) else None
|
| 1044 |
+
|
| 1045 |
+
except Exception as e:
|
| 1046 |
+
print(f"❌ Lỗi tải VOSK model: {e}")
|
| 1047 |
+
return None
|
| 1048 |
|
| 1049 |
def start_stream(self):
|
| 1050 |
"""Bắt đầu stream mới"""
|
| 1051 |
if self.model is None:
|
| 1052 |
+
print("❌ VOSK model chưa được khởi tạo")
|
| 1053 |
return False
|
| 1054 |
|
| 1055 |
+
try:
|
| 1056 |
+
self.recognizer = KaldiRecognizer(self.model, self.sample_rate)
|
| 1057 |
+
self.recognizer.SetWords(True)
|
| 1058 |
+
self.is_streaming = True
|
| 1059 |
+
print("🎤 Đã khởi động VOSK stream")
|
| 1060 |
+
return True
|
| 1061 |
+
except Exception as e:
|
| 1062 |
+
print(f"❌ Lỗi khởi động VOSK stream: {e}")
|
| 1063 |
+
return False
|
| 1064 |
|
| 1065 |
def process_audio_chunk(self, audio_chunk: np.ndarray, sample_rate: int = None) -> Dict[str, Any]:
|
| 1066 |
+
"""Xử lý audio chunk và trả về kết quả - FIXED VERSION"""
|
| 1067 |
if self.recognizer is None or not self.is_streaming:
|
| 1068 |
return {"text": "", "partial": "", "is_final": False}
|
| 1069 |
|
| 1070 |
try:
|
| 1071 |
+
# DEBUG: Thông tin audio chunk
|
| 1072 |
+
print(f"🔊 Audio chunk: {len(audio_chunk)} samples, dtype: {audio_chunk.dtype}, max: {np.max(audio_chunk):.4f}")
|
| 1073 |
+
|
| 1074 |
+
# Chuẩn hóa audio - QUAN TRỌNG: VOSK cần audio ở dạng int16
|
| 1075 |
if sample_rate and sample_rate != self.sample_rate:
|
| 1076 |
audio_chunk = self._resample_audio(audio_chunk, sample_rate, self.sample_rate)
|
| 1077 |
|
| 1078 |
+
# Đảm bảo là int16 với giá trị phù hợp
|
| 1079 |
if audio_chunk.dtype != np.int16:
|
| 1080 |
+
if audio_chunk.dtype in [np.float32, np.float64]:
|
| 1081 |
+
# Audio float cần được scale về [-32768, 32767]
|
| 1082 |
+
audio_chunk = (audio_chunk * 32767).astype(np.int16)
|
| 1083 |
+
else:
|
| 1084 |
+
audio_chunk = audio_chunk.astype(np.int16)
|
| 1085 |
+
|
| 1086 |
+
# Kiểm tra âm lượng
|
| 1087 |
+
audio_rms = np.sqrt(np.mean(audio_chunk.astype(np.float32)**2)) / 32767.0
|
| 1088 |
+
print(f"📊 Audio RMS: {audio_rms:.4f}")
|
| 1089 |
+
|
| 1090 |
+
if audio_rms < 0.01: # Âm lượng quá thấp
|
| 1091 |
+
print("⚠️ Âm lượng quá thấp, bỏ qua")
|
| 1092 |
+
return {"text": "", "partial": "", "is_final": False}
|
| 1093 |
|
| 1094 |
# Chuyển đổi sang bytes
|
| 1095 |
audio_bytes = audio_chunk.tobytes()
|
|
|
|
| 1097 |
# Xử lý với VOSK
|
| 1098 |
if self.recognizer.AcceptWaveform(audio_bytes):
|
| 1099 |
# Kết quả cuối cùng
|
| 1100 |
+
result_json = self.recognizer.Result()
|
| 1101 |
+
result = json.loads(result_json)
|
| 1102 |
text = result.get('text', '').strip()
|
| 1103 |
+
print(f"✅ VOSK Final Result: '{text}'")
|
| 1104 |
if text:
|
| 1105 |
return {"text": text, "partial": "", "is_final": True}
|
| 1106 |
else:
|
| 1107 |
# Kết quả tạm thời
|
| 1108 |
+
partial_json = self.recognizer.PartialResult()
|
| 1109 |
+
partial_result = json.loads(partial_json)
|
| 1110 |
partial_text = partial_result.get('partial', '').strip()
|
| 1111 |
+
if partial_text:
|
| 1112 |
+
print(f"🎯 VOSK Partial: '{partial_text}'")
|
| 1113 |
+
return {"text": "", "partial": partial_text, "is_final": False}
|
| 1114 |
|
| 1115 |
except Exception as e:
|
| 1116 |
print(f"❌ Lỗi VOSK processing: {e}")
|
| 1117 |
+
traceback.print_exc()
|
| 1118 |
|
| 1119 |
return {"text": "", "partial": "", "is_final": False}
|
| 1120 |
|
| 1121 |
def stop_stream(self) -> str:
|
| 1122 |
"""Kết thúc stream và lấy kết quả cuối"""
|
| 1123 |
if self.recognizer:
|
| 1124 |
+
try:
|
| 1125 |
+
result_json = self.recognizer.FinalResult()
|
| 1126 |
+
result = json.loads(result_json)
|
| 1127 |
+
text = result.get('text', '').strip()
|
| 1128 |
+
self.is_streaming = False
|
| 1129 |
+
print(f"🛑 VOSK Final: '{text}'")
|
| 1130 |
+
return text
|
| 1131 |
+
except Exception as e:
|
| 1132 |
+
print(f"❌ Lỗi khi dừng VOSK stream: {e}")
|
| 1133 |
return ""
|
| 1134 |
|
| 1135 |
def _resample_audio(self, audio: np.ndarray, orig_sr: int, target_sr: int) -> np.ndarray:
|
| 1136 |
+
"""Resample audio với chất lượng tốt hơn"""
|
| 1137 |
if orig_sr == target_sr:
|
| 1138 |
return audio
|
| 1139 |
try:
|
| 1140 |
from scipy import signal
|
| 1141 |
+
# Tính số sample mới
|
| 1142 |
+
num_samples = int(len(audio) * target_sr / orig_sr)
|
| 1143 |
+
resampled_audio = signal.resample(audio, num_samples)
|
| 1144 |
return resampled_audio.astype(np.int16)
|
| 1145 |
+
except Exception as e:
|
| 1146 |
+
print(f"❌ Lỗi resample audio: {e}")
|
| 1147 |
return audio
|
| 1148 |
|
| 1149 |
class StreamingVoiceService:
|
|
|
|
| 1152 |
self.rag_system = rag_system
|
| 1153 |
self.tts_service = tts_service
|
| 1154 |
|
| 1155 |
+
# Khởi tạo VOSK ASR - FIXED: Thêm timeout và retry
|
| 1156 |
+
print("🔄 Đang khởi tạo VOSK ASR...")
|
| 1157 |
self.vosk_asr = VoskStreamingASR()
|
| 1158 |
|
| 1159 |
# Khởi tạo VAD
|
|
|
|
| 1161 |
self.is_listening = False
|
| 1162 |
self.speech_callback = None
|
| 1163 |
self.is_processing = False
|
|
|
|
| 1164 |
|
| 1165 |
# Conversation context
|
| 1166 |
self.conversation_history = []
|
|
|
|
| 1172 |
self.processing_threads = []
|
| 1173 |
self.max_workers = 2
|
| 1174 |
|
| 1175 |
+
# Streaming state
|
| 1176 |
self.vosk_stream_active = False
|
| 1177 |
self.last_voice_time = 0
|
| 1178 |
+
self.silence_timeout = 3.0 # Tăng timeout lên 3 giây
|
| 1179 |
+
|
| 1180 |
+
# Audio buffer để cải thiện nhận diện
|
| 1181 |
+
self.audio_buffer = []
|
| 1182 |
+
self.buffer_duration = 1.0 # Buffer 1 giây
|
| 1183 |
+
self.max_buffer_samples = 16000 # 1 giây ở 16kHz
|
| 1184 |
|
| 1185 |
# Latency tracking
|
| 1186 |
self.latency_metrics = {
|
| 1187 |
+
'asr': [], 'rag': [], 'llm': [], 'tts': [], 'total': [],
|
| 1188 |
+
'vad_detection': [], 'queue_waiting': [], 'vosk_processing': []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1189 |
}
|
| 1190 |
|
| 1191 |
self.current_callback = None
|
| 1192 |
|
| 1193 |
def start_listening(self, speech_callback: Callable) -> bool:
|
| 1194 |
+
"""Bắt đầu lắng nghe với VOSK streaming - FIXED VERSION"""
|
| 1195 |
if self.is_listening:
|
| 1196 |
+
print("⚠️ Đã đang lắng nghe")
|
| 1197 |
return False
|
| 1198 |
|
| 1199 |
self.current_callback = speech_callback
|
| 1200 |
|
| 1201 |
+
# Kiểm tra VOSK model
|
| 1202 |
+
if self.vosk_asr.model is None:
|
| 1203 |
+
print("❌ VOSK model không khả dụng")
|
| 1204 |
+
if self.current_callback:
|
| 1205 |
+
self.current_callback({
|
| 1206 |
+
'transcription': "Lỗi: VOSK model không khả dụng",
|
| 1207 |
+
'response': "Không thể khởi động nhận diện giọng nói",
|
| 1208 |
+
'tts_audio': None,
|
| 1209 |
+
'status': 'error'
|
| 1210 |
+
})
|
| 1211 |
+
return False
|
| 1212 |
+
|
| 1213 |
# Khởi động VOSK stream
|
| 1214 |
if not self.vosk_asr.start_stream():
|
| 1215 |
+
print("❌ Không thể khởi động VOSK stream")
|
| 1216 |
return False
|
| 1217 |
|
| 1218 |
+
# Khởi động VAD
|
| 1219 |
success = self.vad_processor.start_stream(self._on_speech_detected)
|
| 1220 |
|
| 1221 |
if success:
|
|
|
|
| 1223 |
self.is_processing = False
|
| 1224 |
self.vosk_stream_active = True
|
| 1225 |
self.last_voice_time = time.time()
|
| 1226 |
+
self.audio_buffer = []
|
| 1227 |
|
| 1228 |
# Khởi động worker threads
|
| 1229 |
if not self.processing_threads:
|
|
|
|
| 1240 |
threading.Thread(target=self._vosk_streaming_monitor, daemon=True).start()
|
| 1241 |
|
| 1242 |
print("🎙️ Đã bắt đầu lắng nghe với VOSK ASR streaming")
|
| 1243 |
+
|
| 1244 |
+
# Thông báo trạng thái
|
| 1245 |
+
if self.current_callback:
|
| 1246 |
+
self.current_callback({
|
| 1247 |
+
'transcription': "Đã bắt đầu lắng nghe... Hãy nói gì đó",
|
| 1248 |
+
'response': "",
|
| 1249 |
+
'tts_audio': None,
|
| 1250 |
+
'status': 'listening'
|
| 1251 |
+
})
|
| 1252 |
+
|
| 1253 |
return True
|
| 1254 |
+
|
| 1255 |
return False
|
| 1256 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1257 |
def _vosk_streaming_monitor(self):
|
| 1258 |
"""Theo dõi VOSK streaming và xử lý kết quả real-time"""
|
| 1259 |
while self.is_listening and self.vosk_stream_active:
|
| 1260 |
try:
|
|
|
|
| 1261 |
current_time = time.time()
|
| 1262 |
silence_duration = current_time - self.last_voice_time
|
| 1263 |
|
| 1264 |
+
# Xử lý audio buffer nếu có dữ liệu
|
| 1265 |
+
if self.audio_buffer and silence_duration > 0.5: # 0.5 giây im lặng
|
| 1266 |
+
combined_audio = np.concatenate(self.audio_buffer)
|
| 1267 |
+
if len(combined_audio) > 1600: # Ít nhất 0.1 giây audio
|
| 1268 |
+
result = self.vosk_asr.process_audio_chunk(combined_audio, 16000)
|
| 1269 |
+
self._handle_vosk_result(result)
|
| 1270 |
+
self.audio_buffer = []
|
| 1271 |
+
|
| 1272 |
+
# Timeout im lặng
|
| 1273 |
if silence_duration > self.silence_timeout and self.partial_transcription:
|
| 1274 |
+
print(f"⏰ Silence timeout, xử lý: '{self.partial_transcription}'")
|
| 1275 |
+
if len(self.partial_transcription) > 2: # Chỉ xử lý nếu có nội dung
|
| 1276 |
+
self._process_final_transcription(self.partial_transcription)
|
| 1277 |
self.partial_transcription = ""
|
| 1278 |
+
self.vosk_asr.start_stream()
|
| 1279 |
|
| 1280 |
+
time.sleep(0.1)
|
| 1281 |
|
| 1282 |
except Exception as e:
|
| 1283 |
print(f"❌ Lỗi VOSK monitor: {e}")
|
| 1284 |
break
|
| 1285 |
|
| 1286 |
def _on_speech_detected(self, speech_audio: np.ndarray, sample_rate: int):
|
| 1287 |
+
"""Callback khi VAD phát hiện speech - FIXED VERSION"""
|
| 1288 |
+
if not self.vosk_stream_active or not self.is_listening:
|
|
|
|
|
|
|
| 1289 |
return
|
| 1290 |
|
| 1291 |
# Cập nhật thời gian có giọng nói
|
| 1292 |
self.last_voice_time = time.time()
|
| 1293 |
|
| 1294 |
+
# Thêm vào audio buffer để cải thiện nhận diện
|
| 1295 |
+
self.audio_buffer.append(speech_audio)
|
|
|
|
|
|
|
| 1296 |
|
| 1297 |
+
# Giới hạn buffer size
|
| 1298 |
+
total_samples = sum(len(chunk) for chunk in self.audio_buffer)
|
| 1299 |
+
if total_samples > self.max_buffer_samples:
|
| 1300 |
+
# Giữ lại các chunk gần nhất
|
| 1301 |
+
while total_samples > self.max_buffer_samples and len(self.audio_buffer) > 1:
|
| 1302 |
+
removed = self.audio_buffer.pop(0)
|
| 1303 |
+
total_samples -= len(removed)
|
| 1304 |
|
| 1305 |
+
print(f"🎯 VAD detected: {len(speech_audio)} samples, Buffer: {len(self.audio_buffer)} chunks")
|
| 1306 |
+
|
| 1307 |
+
def _handle_vosk_result(self, result: Dict[str, Any]):
|
| 1308 |
+
"""Xử lý kết quả từ VOSK"""
|
| 1309 |
# Xử lý kết quả partial
|
| 1310 |
if result['partial'] and len(result['partial']) > 1:
|
| 1311 |
self.partial_transcription = result['partial']
|
| 1312 |
+
print(f"🎯 VOSK Partial: '{result['partial']}'")
|
| 1313 |
|
| 1314 |
# Gửi partial result real-time
|
| 1315 |
if self.current_callback:
|
|
|
|
| 1322 |
|
| 1323 |
# Xử lý kết quả final
|
| 1324 |
if result['is_final'] and result['text'] and len(result['text']) > 1:
|
| 1325 |
+
print(f"✅ VOSK Final: '{result['text']}'")
|
| 1326 |
self._process_final_transcription(result['text'])
|
| 1327 |
self.partial_transcription = ""
|
| 1328 |
+
self.audio_buffer = [] # Clear buffer
|
| 1329 |
self.vosk_asr.start_stream() # Bắt đầu stream mới
|
|
|
|
|
|
|
|
|
|
| 1330 |
|
| 1331 |
def _process_final_transcription(self, transcription: str):
|
| 1332 |
"""Xử lý transcription cuối cùng"""
|
| 1333 |
if not transcription or len(transcription.strip()) < 2:
|
| 1334 |
return
|
| 1335 |
|
| 1336 |
+
print(f"📝 Final Transcription: '{transcription}'")
|
| 1337 |
self.current_transcription = transcription
|
| 1338 |
|
| 1339 |
# Đưa vào queue để xử lý
|
| 1340 |
try:
|
| 1341 |
self.response_queue.put(transcription, timeout=0.5)
|
| 1342 |
+
print(f"📦 Đã đưa vào queue: '{transcription}'")
|
| 1343 |
except queue.Full:
|
| 1344 |
print("⚠️ Queue đầy, bỏ qua transcription")
|
| 1345 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1346 |
def process_streaming_audio(self, audio_data: tuple) -> Dict[str, Any]:
|
| 1347 |
+
"""Xử lý audio streaming manual mode với VOSK - FIXED VERSION"""
|
| 1348 |
if not audio_data:
|
| 1349 |
return self._create_error_response("❌ Không có dữ liệu âm thanh")
|
| 1350 |
|
| 1351 |
try:
|
| 1352 |
sample_rate, audio_array = audio_data
|
| 1353 |
|
| 1354 |
+
print(f"🎤 Manual audio: {len(audio_array)} samples, {sample_rate}Hz")
|
| 1355 |
+
|
| 1356 |
+
# Kiểm tra âm lượng
|
| 1357 |
+
if isinstance(audio_array, np.ndarray):
|
| 1358 |
+
if audio_array.dtype in [np.float32, np.float64]:
|
| 1359 |
+
audio_rms = np.sqrt(np.mean(audio_array**2))
|
| 1360 |
+
print(f"📊 Manual audio RMS: {audio_rms:.4f}")
|
| 1361 |
+
|
| 1362 |
+
if audio_rms < 0.01:
|
| 1363 |
+
return {
|
| 1364 |
+
'transcription': "Âm thanh quá nhỏ, hãy nói to hơn",
|
| 1365 |
+
'response': "",
|
| 1366 |
+
'tts_audio': None,
|
| 1367 |
+
'status': 'listening'
|
| 1368 |
+
}
|
| 1369 |
+
|
| 1370 |
# Khởi động VOSK stream tạm thời
|
| 1371 |
+
if not self.vosk_asr.start_stream():
|
| 1372 |
+
return self._create_error_response("❌ Không thể khởi động VOSK")
|
| 1373 |
|
| 1374 |
# Xử lý audio với VOSK
|
| 1375 |
result = self.vosk_asr.process_audio_chunk(audio_array, sample_rate)
|
| 1376 |
|
| 1377 |
+
if result['is_final'] and result['text'] and len(result['text']) > 1:
|
| 1378 |
transcription = result['text']
|
| 1379 |
+
print(f"📝 Manual Transcription: '{transcription}'")
|
| 1380 |
|
| 1381 |
# Tạo phản hồi AI
|
| 1382 |
response = self._generate_ai_response_optimized(transcription)
|
|
|
|
| 1388 |
'tts_audio': tts_audio_path,
|
| 1389 |
'status': 'completed'
|
| 1390 |
}
|
| 1391 |
+
elif result['partial']:
|
| 1392 |
+
return {
|
| 1393 |
+
'transcription': result['partial'],
|
| 1394 |
+
'response': "",
|
| 1395 |
+
'tts_audio': None,
|
| 1396 |
+
'status': 'listening'
|
| 1397 |
+
}
|
| 1398 |
else:
|
| 1399 |
return {
|
| 1400 |
+
'transcription': "Đang nghe... Hãy nói rõ hơn",
|
| 1401 |
'response': "",
|
| 1402 |
'tts_audio': None,
|
| 1403 |
'status': 'listening'
|
|
|
|
| 1405 |
|
| 1406 |
except Exception as e:
|
| 1407 |
print(f"❌ Lỗi xử lý streaming audio: {e}")
|
| 1408 |
+
traceback.print_exc()
|
| 1409 |
return self._create_error_response(f"❌ Lỗi: {str(e)}")
|
| 1410 |
|
| 1411 |
def _create_error_response(self, message: str) -> Dict[str, Any]:
|
|
|
|
| 1466 |
'queue_size': self.response_queue.qsize(),
|
| 1467 |
'worker_threads': len(self.processing_threads),
|
| 1468 |
'vosk_active': self.vosk_stream_active,
|
| 1469 |
+
'audio_buffer_chunks': len(self.audio_buffer),
|
| 1470 |
+
'last_voice_time': time.strftime("%H:%M:%S", time.localtime(self.last_voice_time)),
|
| 1471 |
'last_update': time.strftime("%H:%M:%S")
|
| 1472 |
}
|
|
|
|
| 1473 |
def clear_conversation(self):
|
| 1474 |
"""Xóa lịch sử hội thoại"""
|
| 1475 |
self.conversation_history = []
|