datbkpro commited on
Commit
dbf2148
·
1 Parent(s): cd6507a

voicebot offical

Browse files
app.py DELETED
@@ -1,1211 +0,0 @@
1
- import gradio as gr
2
- import groq
3
- import os
4
- import io
5
- import numpy as np
6
- import soundfile as sf
7
- from PIL import Image
8
- from dotenv import load_dotenv
9
- import pandas as pd
10
- import json
11
- from typing import List, Dict
12
- from sentence_transformers import SentenceTransformer
13
- import faiss
14
- import time
15
- import re
16
- from gtts import gTTS
17
- import edge_tts
18
- import asyncio
19
-
20
- # //load_dotenv()
21
- api_key = os.getenv("GROQ_API_KEY")
22
- # Get the GROQ_API_KEY from environment variables
23
- # api_key = os.environ.get("GROQ_API_KEY")
24
- if not api_key:
25
- raise ValueError("Please set the GROQ_API_KEY environment variable.")
26
-
27
- # Initialize the Groq client
28
- client = groq.Client(api_key=api_key)
29
-
30
- # Initialize Vietnamese embedding model
31
- print("🔄 Đang tải mô hình embedding tiếng Việt...")
32
- try:
33
- vietnamese_embedder = SentenceTransformer('keepitreal/vietnamese-sbert')
34
- print("✅ Đã tải mô hình embedding tiếng Việt")
35
- except Exception as e:
36
- print(f"❌ Lỗi tải mô hình embedding: {e}")
37
- vietnamese_embedder = None
38
-
39
- # Enhanced RAG system with Vietnamese embeddings
40
- class EnhancedRAGSystem:
41
- def __init__(self):
42
- self.documents = []
43
- self.metadatas = []
44
- self.embeddings = None
45
- self.index = None
46
- self.dimension = 384 # Dimension for Vietnamese SBERT
47
-
48
- print("✅ Đã khởi tạo Enhanced RAG system với embedding tiếng Việt")
49
-
50
- # Initialize sample nutrition data in Vietnamese
51
- self._initialize_sample_data()
52
-
53
- def _initialize_sample_data(self):
54
- """Khởi tạo dữ liệu dinh dưỡng mẫu bằng tiếng Việt"""
55
- nutrition_data = [
56
- "Chế độ ăn Địa Trung Hải giàu rau củ, trái cây, ngũ cốc nguyên hạt và dầu olive tốt cho tim mạch",
57
- "Protein từ thịt gà, cá hồi và đậu phụ giúp xây dựng cơ bắp và duy trì sức khỏe",
58
- "Trái cây họ cam quýt như cam, bưởi cung cấp vitamin C tăng cường hệ miễn dịch",
59
- "Rau xanh như cải bó xôi, bông cải xanh chứa nhiều chất xơ và vitamin K",
60
- "Cá hồi giàu omega-3 tốt cho não bộ và sức khỏe tim mạch",
61
- "Các loại hạt như hạnh nhân, óc chó cung cấp chất béo lành mạnh và protein",
62
- "Sữa chua Hy Lạp chứa probiotic tốt cho hệ tiêu hóa và giàu protein",
63
- "Gạo lứt và yến mạch là nguồn carbohydrate phức tạp cung cấp năng lượng lâu dài"
64
- ]
65
-
66
- self.add_documents(nutrition_data, [{"type": "nutrition", "source": "sample", "language": "vi"}] * len(nutrition_data))
67
-
68
- def add_documents(self, documents: List[str], metadatas: List[Dict] = None):
69
- """Thêm documents vào database với embedding"""
70
- if not documents:
71
- return
72
-
73
- # Generate embeddings for new documents
74
- if vietnamese_embedder is not None:
75
- try:
76
- new_embeddings = vietnamese_embedder.encode(documents)
77
-
78
- if self.embeddings is None:
79
- self.embeddings = new_embeddings
80
- else:
81
- self.embeddings = np.vstack([self.embeddings, new_embeddings])
82
-
83
- # Update FAISS index
84
- self._update_faiss_index()
85
-
86
- except Exception as e:
87
- print(f"❌ Lỗi tạo embedding: {e}")
88
-
89
- self.documents.extend(documents)
90
- self.metadatas.extend(metadatas or [{}] * len(documents))
91
- print(f"✅ Đã thêm {len(documents)} documents vào RAG database với embedding")
92
-
93
- def _update_faiss_index(self):
94
- """Cập nhật FAISS index với embeddings hiện tại"""
95
- if self.embeddings is None or len(self.embeddings) == 0:
96
- return
97
-
98
- try:
99
- dimension = self.embeddings.shape[1]
100
- self.index = faiss.IndexFlatIP(dimension) # Inner product for cosine similarity
101
- self.index.add(self.embeddings.astype(np.float32))
102
- except Exception as e:
103
- print(f"❌ Lỗi cập nhật FAISS index: {e}")
104
-
105
- def semantic_search(self, query: str, top_k: int = 3) -> List[Dict]:
106
- """Tìm kiếm ngữ nghĩa sử dụng embedding tiếng Việt"""
107
- if not self.documents or self.index is None:
108
- return self._fallback_keyword_search(query, top_k)
109
-
110
- try:
111
- # Encode query using Vietnamese embedder
112
- query_embedding = vietnamese_embedder.encode([query])
113
-
114
- # Search in FAISS index
115
- similarities, indices = self.index.search(query_embedding.astype(np.float32), min(top_k, len(self.documents)))
116
-
117
- results = []
118
- for i, (similarity, idx) in enumerate(zip(similarities[0], indices[0])):
119
- if idx < len(self.documents):
120
- results.append({
121
- 'id': str(idx),
122
- 'text': self.documents[idx],
123
- 'similarity': float(similarity),
124
- 'metadata': self.metadatas[idx] if idx < len(self.metadatas) else {}
125
- })
126
-
127
- return results
128
-
129
- except Exception as e:
130
- print(f"❌ Lỗi tìm kiếm ngữ nghĩa: {e}")
131
- return self._fallback_keyword_search(query, top_k)
132
-
133
- def _fallback_keyword_search(self, query: str, top_k: int = 3) -> List[Dict]:
134
- """Tìm kiếm dự phòng dựa trên từ khóa"""
135
- query_lower = query.lower()
136
- results = []
137
-
138
- for i, doc in enumerate(self.documents):
139
- score = 0
140
- for word in query_lower.split():
141
- if word in doc.lower():
142
- score += 1
143
-
144
- if score > 0:
145
- results.append({
146
- 'id': str(i),
147
- 'text': doc,
148
- 'similarity': min(score / 5, 1.0),
149
- 'metadata': self.metadatas[i] if i < len(self.metadatas) else {}
150
- })
151
-
152
- results.sort(key=lambda x: x['similarity'], reverse=True)
153
- return results[:top_k]
154
-
155
- def get_collection_stats(self) -> Dict:
156
- """Lấy thống kê collection"""
157
- return {
158
- 'count': len(self.documents),
159
- 'embedding_count': len(self.embeddings) if self.embeddings is not None else 0,
160
- 'name': 'enhanced_rag_vi',
161
- 'status': 'active',
162
- 'has_embeddings': self.embeddings is not None
163
- }
164
-
165
- class WikipediaProcessor:
166
- def __init__(self):
167
- self.supported_formats = ['.txt', '.csv', '.json']
168
-
169
- def process_uploaded_file(self, file_path: str) -> List[str]:
170
- """Xử lý file Wikipedia uploaded"""
171
- file_ext = os.path.splitext(file_path)[1].lower()
172
-
173
- try:
174
- if file_ext == '.txt':
175
- return self._process_txt_file(file_path)
176
- elif file_ext == '.csv':
177
- return self._process_csv_file(file_path)
178
- elif file_ext == '.json':
179
- return self._process_json_file(file_path)
180
- else:
181
- raise ValueError(f"Định dạng file không được hỗ trợ: {file_ext}")
182
- except Exception as e:
183
- raise Exception(f"Lỗi xử lý file: {str(e)}")
184
-
185
- def _process_txt_file(self, file_path: str) -> List[str]:
186
- """Xử lý file text"""
187
- with open(file_path, 'r', encoding='utf-8') as f:
188
- content = f.read()
189
-
190
- paragraphs = [p.strip() for p in content.split('\n\n') if p.strip() and len(p.strip()) > 20]
191
- return paragraphs
192
-
193
- def _process_csv_file(self, file_path: str) -> List[str]:
194
- """Xử lý file CSV"""
195
- try:
196
- df = pd.read_csv(file_path)
197
- documents = []
198
-
199
- for _, row in df.iterrows():
200
- doc_parts = []
201
- for col in df.columns:
202
- if pd.notna(row[col]) and str(row[col]).strip():
203
- doc_parts.append(f"{col}: {row[col]}")
204
- if doc_parts:
205
- documents.append(" | ".join(doc_parts))
206
-
207
- return documents
208
- except Exception as e:
209
- raise Exception(f"Lỗi đọc CSV: {str(e)}")
210
-
211
- def _process_json_file(self, file_path: str) -> List[str]:
212
- """Xử lý file JSON"""
213
- try:
214
- with open(file_path, 'r', encoding='utf-8') as f:
215
- data = json.load(f)
216
-
217
- documents = []
218
-
219
- def extract_text(obj, current_path=""):
220
- if isinstance(obj, dict):
221
- for key, value in obj.items():
222
- extract_text(value, f"{current_path}.{key}" if current_path else key)
223
- elif isinstance(obj, list):
224
- for item in obj:
225
- extract_text(item, current_path)
226
- elif isinstance(obj, str) and len(obj.strip()) > 10:
227
- documents.append(f"{current_path}: {obj.strip()}")
228
-
229
- extract_text(data)
230
- return documents
231
- except Exception as e:
232
- raise Exception(f"Lỗi đọc JSON: {str(e)}")
233
-
234
- # Enhanced TTS Service with multiple providers and chunking
235
- class EnhancedTTSService:
236
- def __init__(self):
237
- self.supported_languages = {
238
- 'vi': 'vi', # Vietnamese
239
- 'en': 'en', # English
240
- 'fr': 'fr', # French
241
- 'es': 'es', # Spanish
242
- 'de': 'de', # German
243
- 'ja': 'ja', # Japanese
244
- 'ko': 'ko', # Korean
245
- 'zh': 'zh' # Chinese
246
- }
247
- self.max_chunk_length = 200 # Maximum characters per TTS request
248
-
249
- def detect_language(self, text: str) -> str:
250
- """Đơn giản phát hiện ngôn ngữ dựa trên ký tự"""
251
- vietnamese_chars = set('àáâãèéêìíòóôõùúýăđĩũơưạảấầẩẫậắằẳẵặẹẻẽếềểễệỉịọỏốồổỗộớờởỡợụủứừửữựỳỵỷỹ')
252
- if any(char in vietnamese_chars for char in text.lower()):
253
- return 'vi'
254
- # Simple detection for other languages
255
- elif any(char in text for char in 'あいうえお'): # Japanese
256
- return 'ja'
257
- elif any(char in text for char in '你好'): # Chinese
258
- return 'zh'
259
- elif any(char in text for char in '안녕'): # Korean
260
- return 'ko'
261
- else:
262
- return 'en' # Default to English
263
-
264
- def split_text_into_chunks(self, text: str, max_length: int = None) -> List[str]:
265
- """Chia văn bản thành các đoạn nhỏ cho TTS"""
266
- if max_length is None:
267
- max_length = self.max_chunk_length
268
-
269
- # Split by sentences first
270
- sentences = re.split(r'[.!?]+', text)
271
- chunks = []
272
- current_chunk = ""
273
-
274
- for sentence in sentences:
275
- sentence = sentence.strip()
276
- if not sentence:
277
- continue
278
-
279
- # If sentence is too long, split by commas
280
- if len(sentence) > max_length:
281
- parts = re.split(r'[,;:]', sentence)
282
- for part in parts:
283
- part = part.strip()
284
- if not part:
285
- continue
286
- if len(current_chunk) + len(part) + 2 <= max_length:
287
- if current_chunk:
288
- current_chunk += ". " + part
289
- else:
290
- current_chunk = part
291
- else:
292
- if current_chunk:
293
- chunks.append(current_chunk)
294
- current_chunk = part
295
- else:
296
- if len(current_chunk) + len(sentence) + 2 <= max_length:
297
- if current_chunk:
298
- current_chunk += ". " + sentence
299
- else:
300
- current_chunk = sentence
301
- else:
302
- if current_chunk:
303
- chunks.append(current_chunk)
304
- current_chunk = sentence
305
-
306
- if current_chunk:
307
- chunks.append(current_chunk)
308
-
309
- return chunks
310
-
311
- def text_to_speech_gtts(self, text: str, language: str = 'vi') -> bytes:
312
- """Sử dụng gTTS (Google Text-to-Speech) library"""
313
- try:
314
- # Split long text into chunks
315
- chunks = self.split_text_into_chunks(text)
316
- audio_chunks = []
317
-
318
- for chunk in chunks:
319
- if not chunk.strip():
320
- continue
321
-
322
- tts = gTTS(text=chunk, lang=language, slow=False)
323
- audio_buffer = io.BytesIO()
324
- tts.write_to_fp(audio_buffer)
325
- audio_buffer.seek(0)
326
- audio_chunks.append(audio_buffer.read())
327
-
328
- # Small delay between requests
329
- time.sleep(0.1)
330
-
331
- # Combine all audio chunks
332
- if audio_chunks:
333
- return b''.join(audio_chunks)
334
- return None
335
-
336
- except Exception as e:
337
- print(f"❌ Lỗi gTTS: {e}")
338
- return None
339
-
340
- async def text_to_speech_edgetts(self, text: str, voice: str = 'vi-VN-NamMinhNeural') -> bytes:
341
- """Sử dụng Edge-TTS (Microsoft Edge) - async version"""
342
- try:
343
- communicate = edge_tts.Communicate(text, voice)
344
- audio_buffer = io.BytesIO()
345
-
346
- async for chunk in communicate.stream():
347
- if chunk["type"] == "audio":
348
- audio_buffer.write(chunk["data"])
349
-
350
- audio_buffer.seek(0)
351
- return audio_buffer.read()
352
-
353
- except Exception as e:
354
- print(f"❌ Lỗi Edge-TTS: {e}")
355
- return None
356
-
357
- def text_to_speech_edgetts_sync(self, text: str, voice: str = 'vi-VN-NamMinhNeural') -> bytes:
358
- """Sync wrapper for Edge-TTS"""
359
- try:
360
- return asyncio.run(self.text_to_speech_edgetts(text, voice))
361
- except Exception as e:
362
- print(f"❌ Lỗi Edge-TTS sync: {e}")
363
- return None
364
-
365
- def text_to_speech_fallback(self, text: str, language: str = 'vi') -> bytes:
366
- """Fallback TTS using simple method"""
367
- try:
368
- # Use gTTS as fallback
369
- return self.text_to_speech_gtts(text, language)
370
- except Exception as e:
371
- print(f"❌ Lỗi fallback TTS: {e}")
372
- return None
373
-
374
- def text_to_speech(self, text: str, language: str = None, provider: str = "auto") -> bytes:
375
- """Chuyển văn b���n thành giọng nói với nhiều nhà cung cấp"""
376
- if not text or len(text.strip()) == 0:
377
- return None
378
-
379
- if language is None:
380
- language = self.detect_language(text)
381
-
382
- # Clean and prepare text
383
- text = self.clean_text(text)
384
-
385
- try:
386
- if provider == "auto" or provider == "gtts":
387
- print(f"🔊 Đang sử dụng gTTS cho văn bản {len(text)} ký tự...")
388
- audio_bytes = self.text_to_speech_gtts(text, language)
389
- if audio_bytes:
390
- return audio_bytes
391
-
392
- if provider == "auto" or provider == "edgetts":
393
- print(f"🔊 Đang thử Edge-TTS cho văn bản {len(text)} ký tự...")
394
- voice_map = {
395
- 'vi': 'vi-VN-NamMinhNeural',
396
- 'en': 'en-US-AriaNeural',
397
- 'fr': 'fr-FR-DeniseNeural',
398
- 'es': 'es-ES-ElviraNeural',
399
- 'de': 'de-DE-KatjaNeural',
400
- 'ja': 'ja-JP-NanamiNeural',
401
- 'ko': 'ko-KR-SunHiNeural',
402
- 'zh': 'zh-CN-XiaoxiaoNeural'
403
- }
404
- voice = voice_map.get(language, 'vi-VN-NamMinhNeural')
405
- audio_bytes = self.text_to_speech_edgetts_sync(text, voice)
406
- if audio_bytes:
407
- return audio_bytes
408
-
409
- # Final fallback
410
- print(f"🔊 Đang sử dụng fallback TTS...")
411
- return self.text_to_speech_fallback(text, language)
412
-
413
- except Exception as e:
414
- print(f"❌ Lỗi TTS tổng hợp: {e}")
415
- return None
416
-
417
- def clean_text(self, text: str) -> str:
418
- """Làm sạch văn bản trước khi chuyển thành giọng nói"""
419
- # Remove URLs
420
- text = re.sub(r'http\S+', '', text)
421
- # Remove special characters but keep Vietnamese diacritics
422
- text = re.sub(r'[^\w\sàáâãèéêìíòóôõùúýăđĩũơưạảấầẩẫậắằẳẵặẹẻẽếềểễệỉịọỏốồổỗộớờởỡợụủứừửữựỳỵỷỹ.,!?;:()-]', '', text)
423
- # Replace multiple spaces with single space
424
- text = re.sub(r'\s+', ' ', text)
425
- # Remove extra whitespace
426
- text = text.strip()
427
- return text
428
-
429
- def save_audio_to_file(self, audio_bytes: bytes, filename: str = None) -> str:
430
- """Lưu audio bytes thành file tạm thời"""
431
- if audio_bytes is None:
432
- return None
433
-
434
- if filename is None:
435
- filename = f"tts_output_{int(time.time())}.mp3"
436
-
437
- temp_dir = "temp_audio"
438
- os.makedirs(temp_dir, exist_ok=True)
439
-
440
- filepath = os.path.join(temp_dir, filename)
441
- with open(filepath, 'wb') as f:
442
- f.write(audio_bytes)
443
-
444
- return filepath
445
-
446
- # Initialize systems
447
- rag_system = EnhancedRAGSystem()
448
- wikipedia_processor = WikipediaProcessor()
449
- tts_service = EnhancedTTSService()
450
-
451
- # Audio utility functions
452
- def numpy_to_mp3(audio_array: np.ndarray, sampling_rate: int = 24000) -> bytes:
453
- """Convert numpy array to MP3 bytes"""
454
- buffer = io.BytesIO()
455
- sf.write(buffer, audio_array, sampling_rate, format='mp3')
456
- buffer.seek(0)
457
- return buffer.read()
458
-
459
- def transcribe_audio(audio):
460
- """Chuyển đổi audio thành văn bản và tạo phản hồi với TTS"""
461
- if audio is None:
462
- return "No audio provided.", "", None
463
-
464
- sr, y = audio
465
-
466
- # Convert to mono if stereo
467
- if y.ndim > 1:
468
- y = y.mean(axis=1)
469
-
470
- # Normalize audio
471
- y = y.astype(np.float32)
472
- y /= np.max(np.abs(y))
473
-
474
- # Write audio to buffer
475
- buffer = io.BytesIO()
476
- sf.write(buffer, y, sr, format='wav')
477
- buffer.seek(0)
478
-
479
- try:
480
- # Use Whisper model for transcription
481
- completion = client.audio.transcriptions.create(
482
- model="whisper-large-v3-turbo",
483
- file=("audio.wav", buffer),
484
- response_format="text"
485
- )
486
- transcription = completion
487
- except Exception as e:
488
- transcription = f"Error in transcription: {str(e)}"
489
-
490
- response = generate_response_with_rag(transcription)
491
-
492
- # Generate TTS audio for response
493
- tts_audio = None
494
- if response and not response.startswith("Error"):
495
- tts_bytes = tts_service.text_to_speech(response, 'vi')
496
- if tts_bytes:
497
- tts_audio_path = tts_service.save_audio_to_file(tts_bytes)
498
- tts_audio = tts_audio_path
499
-
500
- return transcription, response, tts_audio
501
-
502
- def generate_response_with_rag(user_input):
503
- """Tạo phản hồi sử dụng RAG với embedding tiếng Việt"""
504
- if not user_input or user_input.startswith("Error"):
505
- return "No valid input available. Please try again."
506
-
507
- try:
508
- # Tìm kiếm thông tin liên quan từ RAG với embedding tiếng Việt
509
- rag_results = rag_system.semantic_search(user_input, top_k=3)
510
-
511
- # Tạo context từ RAG results
512
- context_text = ""
513
- if rag_results:
514
- context_text = "\n".join([f"- {doc['text']}" for doc in rag_results])
515
-
516
- # System prompt với RAG context
517
- system_prompt = """Bạn là trợ lý AI thông minh chuyên về tiếng Việt. Hãy sử dụng thông tin từ cơ sở kiến thức được cung cấp để trả lời câu hỏi một cách chính xác và hữu ích bằng tiếng Việt.
518
-
519
- Thông tin tham khảo từ cơ sở kiến thức:
520
- {context}
521
-
522
- Nếu thông tin từ cơ sở kiến thức không đủ để trả lời, hãy dựa vào kiến thức chung của bạn. Luôn trả lời bằng tiếng Việt tự nhiên và dễ hiểu."""
523
-
524
- messages = [
525
- {"role": "system", "content": system_prompt.format(context=context_text)},
526
- {"role": "user", "content": user_input}
527
- ]
528
-
529
- # Use Llama 3.3 70B model for text generation với RAG context
530
- completion = client.chat.completions.create(
531
- model="llama-3.3-70b-versatile",
532
- messages=messages,
533
- )
534
- return completion.choices[0].message.content
535
- except Exception as e:
536
- return f"Error in response generation: {str(e)}"
537
-
538
- def analyze_image_with_description(image, user_description):
539
- """Phân tích hình ảnh kết hợp với mô tả từ người dùng"""
540
- if image is None:
541
- return "No image uploaded."
542
-
543
- try:
544
- if user_description:
545
- prompt = f"""Người dùng tải lên một hình ảnh và mô tả: "{user_description}"
546
-
547
- Dựa trên mô tả này, hãy phân tích chi tiết bằng tiếng Việt:
548
- 1. Mô tả những gì có trong hình ảnh
549
- 2. Nếu là thức ăn: ước tính dinh dưỡng (calo, protein, carbs, chất béo, chất xơ)
550
- 3. Nếu là cảnh quan/con người: mô tả chi tiết và ý nghĩa
551
- 4. Đưa ra nhận xét hoặc lời khuyên liên quan"""
552
- else:
553
- prompt = """Hãy mô tả chi tiết bằng tiếng Việt những gì bạn nghĩ có thể có trong hình ảnh này. Nếu là thức ăn, hãy ước tính giá trị dinh dưỡng. Nếu là cảnh quan hoặc con người, hãy mô tả chi tiết."""
554
-
555
- chat_completion = client.chat.completions.create(
556
- messages=[
557
- {
558
- "role": "user",
559
- "content": prompt
560
- }
561
- ],
562
- model="llama-3.3-70b-versatile",
563
- )
564
- description = chat_completion.choices[0].message.content
565
- except Exception as e:
566
- description = f"Error in image analysis: {str(e)}"
567
-
568
- return description
569
-
570
- def respond(message, chat_history):
571
- """Xử lý chat với TTS output"""
572
- if chat_history is None:
573
- chat_history = []
574
-
575
- # Prepare the message history for the API
576
- messages = []
577
- for user_msg, assistant_msg in chat_history:
578
- messages.append({"role": "user", "content": user_msg})
579
- messages.append({"role": "assistant", "content": assistant_msg})
580
-
581
- messages.append({"role": "user", "content": message})
582
-
583
- try:
584
- # Sử dụng RAG để tìm kiếm thông tin liên quan
585
- rag_results = rag_system.semantic_search(message, top_k=2)
586
-
587
- # Thêm context từ RAG vào system prompt
588
- context_text = ""
589
- if rag_results:
590
- context_text = "\nThông tin tham khảo:\n" + "\n".join([f"- {doc['text']}" for doc in rag_results])
591
-
592
- system_message = {
593
- "role": "system",
594
- "content": f"Bạn là trợ lý AI hữu ích chuyên về tiếng Việt. Sử dụng thông tin từ cơ sở kiến thức khi có liên quan. Luôn trả lời bằng tiếng Việt tự nhiên.{context_text}"
595
- }
596
-
597
- # Chèn system message vào đầu
598
- messages_with_context = [system_message] + messages
599
-
600
- # Use Llama 3.3 70B model for generating assistant response
601
- completion = client.chat.completions.create(
602
- model="llama-3.3-70b-versatile",
603
- messages=messages_with_context,
604
- )
605
- assistant_message = completion.choices[0].message.content
606
- chat_history.append((message, assistant_message))
607
-
608
- # Generate TTS audio for the response
609
- tts_audio_path = None
610
- if assistant_message and not assistant_message.startswith("Error"):
611
- tts_bytes = tts_service.text_to_speech(assistant_message, 'vi')
612
- if tts_bytes:
613
- tts_audio_path = tts_service.save_audio_to_file(tts_bytes)
614
-
615
- except Exception as e:
616
- assistant_message = f"Error: {str(e)}"
617
- chat_history.append((message, assistant_message))
618
- tts_audio_path = None
619
-
620
- return "", chat_history, chat_history, tts_audio_path
621
-
622
- def upload_wikipedia_file(file):
623
- """Xử lý upload file Wikipedia"""
624
- if file is None:
625
- return "Vui lòng chọn file để upload"
626
-
627
- try:
628
- documents = wikipedia_processor.process_uploaded_file(file.name)
629
-
630
- if not documents:
631
- return "Không tìm thấy dữ liệu nào trong file."
632
-
633
- # Thêm metadata
634
- metadatas = [{"source": "wikipedia", "type": "knowledge", "file": os.path.basename(file.name), "language": "vi"} for _ in documents]
635
-
636
- rag_system.add_documents(documents, metadatas)
637
-
638
- stats = rag_system.get_collection_stats()
639
- return f"✅ Đã thêm {len(documents)} documents Wikipedia vào RAG database. Tổng số documents: {stats['count']}, Embeddings: {stats['embedding_count']}"
640
-
641
- except Exception as e:
642
- return f"❌ Lỗi xử lý file Wikipedia: {str(e)}"
643
-
644
- def get_rag_stats():
645
- """Lấy thống kê RAG database"""
646
- stats = rag_system.get_collection_stats()
647
- return f"📊 Thống kê RAG Database:\n- Tổng documents: {stats['count']}\n- Embeddings: {stats['embedding_count']}\n- Trạng thái: {stats['status']}\n- Hỗ trợ embedding: {stats['has_embeddings']}"
648
-
649
- def search_rag_database(query):
650
- """Tìm kiếm trong RAG database để debug"""
651
- if not query.strip():
652
- return []
653
-
654
- results = rag_system.semantic_search(query, top_k=5)
655
- return results
656
-
657
- def clear_chat_history(chat_history):
658
- """Xóa lịch sử chat"""
659
- return [], []
660
-
661
- # Enhanced Streaming Voice AI Functions with TTS
662
- def generate_streaming_response(audio_file):
663
- """Generate response for streaming voice AI với TTS"""
664
- if audio_file is None:
665
- return None, "No audio provided", None
666
-
667
- try:
668
- # Transcribe audio using Whisper
669
- with open(audio_file, "rb") as f:
670
- transcription = client.audio.transcriptions.create(
671
- model="whisper-large-v3-turbo",
672
- file=f,
673
- response_format="text"
674
- )
675
-
676
- # Generate response using RAG với embedding tiếng Việt
677
- rag_results = rag_system.semantic_search(transcription, top_k=2)
678
- context_text = ""
679
- if rag_results:
680
- context_text = "\nThông tin tham khảo:\n" + "\n".join([f"- {doc['text']}" for doc in rag_results])
681
-
682
- system_prompt = f"""Bạn là trợ lý AI thông minh và thân thiện chuyên về tiếng Việt. Hãy trả lời câu hỏi một cách tự nhiên và hữu ích bằng tiếng Việt. Sử dụng thông tin từ cơ sở kiến thức khi có liên quan.{context_text}"""
683
-
684
- messages = [
685
- {"role": "system", "content": system_prompt},
686
- {"role": "user", "content": transcription}
687
- ]
688
-
689
- completion = client.chat.completions.create(
690
- model="llama-3.3-70b-versatile",
691
- messages=messages,
692
- max_tokens=150
693
- )
694
-
695
- response = completion.choices[0].message.content
696
-
697
- # Generate TTS audio
698
- tts_audio_bytes = tts_service.text_to_speech(response, 'vi')
699
- if tts_audio_bytes:
700
- # Save to temporary file for audio output
701
- temp_audio_file = tts_service.save_audio_to_file(tts_audio_bytes)
702
- return response, response, temp_audio_file
703
-
704
- return response, response, None
705
-
706
- except Exception as e:
707
- error_msg = f"Error in streaming response: {str(e)}"
708
- return None, error_msg, None
709
-
710
- def read_streaming_response(answer):
711
- """Read response aloud using TTS"""
712
- if not answer:
713
- return answer, None
714
-
715
- try:
716
- tts_audio_bytes = tts_service.text_to_speech(answer, 'vi')
717
- if tts_audio_bytes:
718
- temp_audio_file = tts_service.save_audio_to_file(tts_audio_bytes)
719
- return answer, temp_audio_file
720
- except Exception as e:
721
- print(f"❌ Lỗi TTS: {e}")
722
-
723
- return answer, None
724
-
725
- # Enhanced Magic 8 Ball Functions with Vietnamese responses
726
- def generate_magic_8_ball_response(audio_file):
727
- """Generate Magic 8 Ball response for audio input với tiếng Việt"""
728
- if audio_file is None:
729
- return None, "No audio provided", None
730
-
731
- try:
732
- # Transcribe audio using Whisper
733
- with open(audio_file, "rb") as f:
734
- transcription = client.audio.transcriptions.create(
735
- model="whisper-large-v3-turbo",
736
- file=f,
737
- response_format="text"
738
- )
739
-
740
- # Magic 8 Ball system prompt in Vietnamese
741
- messages = [
742
- {
743
- "role": "system",
744
- "content": (
745
- )
746
- },
747
- {
748
- "role": "user",
749
- "content": f"Quả cầu pha lê xin hãy trả lời câu hỏi này - {transcription}"
750
- }
751
- ]
752
-
753
- completion = client.chat.completions.create(
754
- model="llama-3.3-70b-versatile",
755
- messages=messages,
756
- max_tokens=64,
757
- temperature=0.8
758
- )
759
-
760
- response = completion.choices[0].message.content
761
- # Clean up response
762
- response = response.replace("Magic 8 Ball", "").replace("Quả cầu pha lê", "").replace(":", "").strip()
763
-
764
- # Generate TTS audio
765
- tts_audio_bytes = tts_service.text_to_speech(response, 'vi')
766
- if tts_audio_bytes:
767
- temp_audio_file = tts_service.save_audio_to_file(tts_audio_bytes)
768
- return response, response, temp_audio_file
769
-
770
- return response, response, None
771
-
772
- except Exception as e:
773
- error_msg = f"Error in Magic 8 Ball response: {str(e)}"
774
- return None, error_msg, None
775
-
776
- def read_magic_8_ball_response(answer):
777
- """Read Magic 8 Ball response aloud using TTS"""
778
- if not answer:
779
- return answer, None
780
-
781
- try:
782
- tts_audio_bytes = tts_service.text_to_speech(answer, 'vi')
783
- if tts_audio_bytes:
784
- temp_audio_file = tts_service.save_audio_to_file(tts_audio_bytes)
785
- return answer, temp_audio_file
786
- except Exception as e:
787
- print(f"❌ Lỗi TTS: {e}")
788
-
789
- return answer, None
790
-
791
- # Text-to-Speech standalone function
792
- def text_to_speech_standalone(text, language, tts_provider):
793
- """Chức năng TTS độc lập"""
794
- if not text:
795
- return None
796
-
797
- try:
798
- tts_audio_bytes = tts_service.text_to_speech(text, language, tts_provider)
799
- if tts_audio_bytes:
800
- temp_audio_file = tts_service.save_audio_to_file(tts_audio_bytes)
801
- return temp_audio_file
802
- except Exception as e:
803
- print(f"❌ Lỗi TTS: {e}")
804
-
805
- return None
806
-
807
- # Custom CSS (giữ nguyên)
808
- custom_css = """
809
- .gradio-container {
810
- background-color: #1f1f1f;
811
- }
812
-
813
- .gr-markdown, .gr-markdown * {
814
- color: #ffffff !important;
815
- }
816
-
817
- .gr-textbox, .gr-textbox * {
818
- color: #ffffff !important;
819
- }
820
-
821
- .gr-label, .gr-label * {
822
- color: #ffffff !important;
823
- }
824
-
825
- .gr-chatbot, .gr-chatbot * {
826
- color: #ffffff !important;
827
- }
828
-
829
- .gr-json, .gr-json * {
830
- color: #ffffff !important;
831
- }
832
-
833
- .gr-box, .gr-block, .panel, .tab-item {
834
- background-color: #2d2d2d !important;
835
- border-color: #444444 !important;
836
- }
837
-
838
- input, textarea, select {
839
- color: #ffffff !important;
840
- background-color: #2d2d2d !important;
841
- border-color: #444444 !important;
842
- }
843
-
844
- ::placeholder {
845
- color: #aaaaaa !important;
846
- }
847
-
848
- .message {
849
- color: #ffffff !important;
850
- }
851
-
852
- .user-message, .bot-message {
853
- color: #ffffff !important;
854
- }
855
-
856
- h1, h2, h3, h4, h5, h6 {
857
- color: #ffffff !important;
858
- }
859
-
860
- .tab-nav {
861
- color: #ffffff !important;
862
- }
863
-
864
- .tab-item {
865
- color: #ffffff !important;
866
- }
867
-
868
- .gr-button {
869
- color: #ffffff !important;
870
- background-color: #f55036 !important;
871
- border-color: #f55036 !important;
872
- }
873
-
874
- .gr-button-secondary {
875
- color: #ffffff !important;
876
- background-color: #666666 !important;
877
- border-color: #666666 !important;
878
- }
879
-
880
- .gr-file, .gr-file * {
881
- color: #ffffff !important;
882
- }
883
-
884
- .gr-json {
885
- background-color: #2d2d2d !important;
886
- }
887
-
888
- .gr-audio, .gr-audio * {
889
- color: #ffffff !important;
890
- }
891
-
892
- .gr-image, .gr-image * {
893
- color: #ffffff !important;
894
- }
895
-
896
- .form, .form * {
897
- color: #ffffff !important;
898
- }
899
-
900
- .block, .block * {
901
- color: #ffffff !important;
902
- }
903
-
904
- div[data-testid="block"] {
905
- background-color: #2d2d2d !important;
906
- color: #ffffff !important;
907
- }
908
-
909
- .gr-component, .gr-component * {
910
- color: #ffffff !important;
911
- }
912
-
913
- #groq-badge {
914
- position: fixed;
915
- bottom: 20px;
916
- right: 20px;
917
- z-index: 1000;
918
- color: #ffffff !important;
919
- }
920
-
921
- .streaming-voice-container {
922
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
923
- padding: 20px;
924
- border-radius: 10px;
925
- margin: 10px 0;
926
- }
927
-
928
- .magic-8-ball-container {
929
- background: linear-gradient(135deg, #1a2a6c 0%, #b21f1f 50%, #fdbb2d 100%);
930
- padding: 20px;
931
- border-radius: 15px;
932
- margin: 10px 0;
933
- text-align: center;
934
- border: 3px solid #ffffff;
935
- }
936
-
937
- .magic-8-ball-title {
938
- font-size: 2.5em !important;
939
- font-weight: bold !important;
940
- text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
941
- margin-bottom: 10px !important;
942
- }
943
-
944
- .magic-8-ball-subtitle {
945
- font-size: 1.2em !important;
946
- opacity: 0.9;
947
- margin-bottom: 20px !important;
948
- }
949
-
950
- .tts-container {
951
- background: linear-gradient(135deg, #00b09b 0%, #96c93d 100%);
952
- padding: 20px;
953
- border-radius: 10px;
954
- margin: 10px 0;
955
- }
956
- """
957
-
958
- with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="orange", neutral_hue="slate")) as demo:
959
- gr.Markdown("# 🎙️ Groq x Gradio Multi-Modal với RAG Wikipedia & TTS Tiếng Việt")
960
- gr.Markdown("**Ứng dụng đa chức năng với Llama 3.3, Whisper, RAG embedding tiếng Việt**")
961
-
962
- with gr.Tab("🎙️ Audio"):
963
- gr.Markdown("## Nói chuyện với AI (có TTS)")
964
- with gr.Row():
965
- audio_input = gr.Audio(type="numpy", label="Nói hoặc tải lên file âm thanh")
966
- with gr.Row():
967
- transcription_output = gr.Textbox(
968
- label="Bản ghi âm",
969
- lines=5,
970
- interactive=True,
971
- placeholder="Bản ghi âm sẽ hiển thị ở đây..."
972
- )
973
- response_output = gr.Textbox(
974
- label="Phản hồi AI",
975
- lines=5,
976
- interactive=True,
977
- placeholder="Phản hồi của AI sẽ hiển thị ở đây..."
978
- )
979
- with gr.Row():
980
- tts_audio_output = gr.Audio(
981
- label="Phản hồi bằng giọng nói",
982
- interactive=False
983
- )
984
- process_button = gr.Button("Xử lý", variant="primary")
985
- process_button.click(
986
- transcribe_audio,
987
- inputs=audio_input,
988
- outputs=[transcription_output, response_output, tts_audio_output]
989
- )
990
-
991
- with gr.Tab("🔊 Streaming Voice"):
992
- gr.Markdown("## 🎤 Trò chuyện giọng nói thời gian thực với TTS")
993
- gr.Markdown("Nói chuyện tự nhiên với AI - Câu hỏi của bạn sẽ được chuyển thành văn bản và AI sẽ trả lời bằng giọng nói tiếng Việt")
994
-
995
- with gr.Group():
996
- with gr.Row():
997
- audio_out = gr.Audio(
998
- label="Câu trả lời bằng giọng nói",
999
- autoplay=True,
1000
- format="mp3"
1001
- )
1002
- answer_text = gr.Textbox(
1003
- label="Câu trả lời văn bản",
1004
- lines=5,
1005
- placeholder="Câu trả lời văn bản sẽ hiển thị ở đây..."
1006
- )
1007
- streaming_state = gr.State()
1008
-
1009
- with gr.Row():
1010
- audio_in = gr.Audio(
1011
- label="Nói câu hỏi của bạn",
1012
- sources="microphone",
1013
- type="filepath",
1014
- format="wav"
1015
- )
1016
-
1017
- audio_in.stop_recording(
1018
- generate_streaming_response,
1019
- inputs=[audio_in],
1020
- outputs=[streaming_state, answer_text, audio_out]
1021
- ).then(
1022
- fn=read_streaming_response,
1023
- inputs=[streaming_state],
1024
- outputs=[answer_text, audio_out]
1025
- )
1026
-
1027
- with gr.Tab("🎱 Magic 8 Ball"):
1028
- gr.HTML(
1029
- """
1030
- <div class="magic-8-ball-container">
1031
- <h1 class="magic-8-ball-title">Magic 8 Ball 🎱</h1>
1032
- <h3 class="magic-8-ball-subtitle">Hỏi một câu hỏi và nhận trí tuệ từ quả cầu thần kỳ bằng tiếng Việt</h3>
1033
- <p class="magic-8-ball-subtitle">Powered by Groq & Whisper & TTS</p>
1034
- </div>
1035
- """
1036
- )
1037
-
1038
- with gr.Group():
1039
- with gr.Row():
1040
- magic_audio_out = gr.Audio(
1041
- label="Câu trả lời bằng giọng nói",
1042
- autoplay=True,
1043
- format="mp3"
1044
- )
1045
- magic_answer = gr.Textbox(
1046
- label="Câu trả lời",
1047
- lines=3,
1048
- placeholder="Câu trả lời thần kỳ sẽ hiển thị ở đây..."
1049
- )
1050
- magic_state = gr.State()
1051
-
1052
- with gr.Row():
1053
- magic_audio_in = gr.Audio(
1054
- label="Nói câu hỏi của bạn",
1055
- sources="microphone",
1056
- type="filepath",
1057
- format="wav"
1058
- )
1059
-
1060
- magic_audio_in.stop_recording(
1061
- generate_magic_8_ball_response,
1062
- inputs=[magic_audio_in],
1063
- outputs=[magic_state, magic_answer, magic_audio_out]
1064
- ).then(
1065
- fn=read_magic_8_ball_response,
1066
- inputs=[magic_state],
1067
- outputs=[magic_answer, magic_audio_out]
1068
- )
1069
-
1070
- with gr.Tab("🔊 Text-to-Speech"):
1071
- gr.Markdown("## 🎵 Chuyển văn bản thành giọng nói nâng cao")
1072
- gr.Markdown("Nhập văn bản và chọn ngôn ngữ để chuyển thành giọng nói")
1073
-
1074
- with gr.Group():
1075
- with gr.Row():
1076
- tts_text_input = gr.Textbox(
1077
- label="Văn bản cần chuyển thành giọng nói",
1078
- lines=4,
1079
- placeholder="Nhập văn bản tại đây..."
1080
- )
1081
- with gr.Row():
1082
- tts_language = gr.Dropdown(
1083
- choices=["vi", "en", "fr", "es", "de", "ja", "ko", "zh"],
1084
- value="vi",
1085
- label="Ngôn ngữ"
1086
- )
1087
- tts_provider = gr.Dropdown(
1088
- choices=["auto", "gtts", "edgetts"],
1089
- value="auto",
1090
- label="Nhà cung cấp TTS"
1091
- )
1092
- with gr.Row():
1093
- tts_output_audio = gr.Audio(
1094
- label="Kết quả giọng nói",
1095
- interactive=False
1096
- )
1097
- tts_button = gr.Button("🔊 Chuyển thành giọng nói", variant="primary")
1098
-
1099
- tts_button.click(
1100
- text_to_speech_standalone,
1101
- inputs=[tts_text_input, tts_language, tts_provider],
1102
- outputs=[tts_output_audio]
1103
- )
1104
-
1105
- with gr.Tab("🖼️ Image"):
1106
- gr.Markdown("## Phân tích hình ảnh")
1107
- with gr.Row():
1108
- image_input = gr.Image(type="numpy", label="Tải lên hình ảnh")
1109
- with gr.Row():
1110
- image_description = gr.Textbox(
1111
- label="Mô tả hình ảnh của bạn (tùy chọn)",
1112
- placeholder="Mô tả ngắn về hình ảnh để AI phân tích chính xác hơn..."
1113
- )
1114
- with gr.Row():
1115
- image_output = gr.Textbox(label="Kết quả phân tích")
1116
- analyze_button = gr.Button("Phân tích hình ảnh", variant="primary")
1117
- analyze_button.click(
1118
- analyze_image_with_description,
1119
- inputs=[image_input, image_description],
1120
- outputs=[image_output]
1121
- )
1122
-
1123
- with gr.Tab("💬 Chat"):
1124
- gr.Markdown("## Trò chuyện với AI Assistant (có TTS)")
1125
- chatbot = gr.Chatbot()
1126
- state = gr.State([])
1127
- with gr.Row():
1128
- user_input = gr.Textbox(
1129
- show_label=False,
1130
- placeholder="Nhập tin nhắn của bạn ở đây...",
1131
- container=False,
1132
- scale=4
1133
- )
1134
- send_button = gr.Button("Gửi", variant="primary", scale=1)
1135
- clear_button = gr.Button("Xóa Chat", variant="secondary", scale=1)
1136
- with gr.Row():
1137
- chat_tts_output = gr.Audio(
1138
- label="Phản hồi bằng giọng nói",
1139
- interactive=False
1140
- )
1141
-
1142
- send_button.click(
1143
- respond,
1144
- inputs=[user_input, state],
1145
- outputs=[user_input, chatbot, state, chat_tts_output],
1146
- )
1147
- clear_button.click(
1148
- clear_chat_history,
1149
- inputs=[state],
1150
- outputs=[chatbot, state]
1151
- )
1152
-
1153
- with gr.Tab("📚 RAG Wikipedia"):
1154
- gr.Markdown("## Quản lý kiến thức với Wikipedia và Embedding Tiếng Việt")
1155
-
1156
- with gr.Row():
1157
- with gr.Column(scale=1):
1158
- gr.Markdown("### 📤 Upload dữ liệu Wikipedia")
1159
- file_upload = gr.File(
1160
- label="Tải lên file Wikipedia",
1161
- file_types=['.txt', '.csv', '.json'],
1162
- file_count="single"
1163
- )
1164
- upload_btn = gr.Button("📤 Upload Data", variant="primary")
1165
- upload_status = gr.Textbox(label="Trạng thái Upload", interactive=False)
1166
-
1167
- gr.Markdown("### 📊 Thống kê Database")
1168
- stats_btn = gr.Button("📊 Database Stats", variant="secondary")
1169
- stats_display = gr.Textbox(label="Thống kê", interactive=False)
1170
-
1171
- gr.Markdown("### 🔍 Tìm kiếm Database")
1172
- search_query = gr.Textbox(
1173
- label="Tìm kiếm trong database",
1174
- placeholder="Nhập từ khóa để tìm kiếm..."
1175
- )
1176
- search_btn = gr.Button("🔍 Tìm kiếm", variant="secondary")
1177
-
1178
- with gr.Column(scale=2):
1179
- gr.Markdown("### 📋 Kết quả tìm kiếm RAG")
1180
- rag_results = gr.JSON(
1181
- label="Tài liệu tham khảo tìm được"
1182
- )
1183
-
1184
- upload_btn.click(
1185
- upload_wikipedia_file,
1186
- inputs=[file_upload],
1187
- outputs=[upload_status]
1188
- )
1189
-
1190
- stats_btn.click(
1191
- get_rag_stats,
1192
- inputs=[],
1193
- outputs=[stats_display]
1194
- )
1195
-
1196
- search_btn.click(
1197
- search_rag_database,
1198
- inputs=[search_query],
1199
- outputs=[rag_results]
1200
- )
1201
-
1202
- gr.HTML("""
1203
- <div id="groq-badge">
1204
- <div style="color: #ffffff !important; font-weight: bold; background-color: #f55036; padding: 8px 12px; border-radius: 5px;">
1205
- POWERED BY DAT | VIETNAMESE EMBEDDING & ENHANCED TTS
1206
- </div>
1207
- </div>
1208
- """)
1209
-
1210
- if __name__ == "__main__":
1211
- demo.launch(share=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
config/__pycache__/settings.cpython-310.pyc ADDED
Binary file (1.33 kB). View file
 
config/settings.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ load_dotenv()
5
+
6
+ class Settings:
7
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
8
+
9
+ # Multilingual Model Settings
10
+ VIETNAMESE_EMBEDDING_MODEL = 'dangvantuan/vietnamese-embedding'
11
+ VIETNAMESE_LLM_MODEL = "Vietnamese_LLaMA2_13B_8K_SFT_General_Domain_Knowledge"
12
+
13
+ MULTILINGUAL_EMBEDDING_MODEL = 'Qwen/Qwen3-Embedding-4B'
14
+ MULTILINGUAL_LLM_MODEL = "meta-llama/Llama-3.1-8B-Instruct"
15
+
16
+ # Fallback models in case primary models fail
17
+ FALLBACK_MULTILINGUAL_EMBEDDING_MODEL = 'sentence-transformers/all-MiniLM-L6-v2'
18
+
19
+ # Default models (fallback)
20
+ DEFAULT_EMBEDDING_MODEL = 'dangvantuan/vietnamese-embedding'
21
+ DEFAULT_LLM_MODEL = "Vietnamese_LLaMA2_13B_8K_SFT_General_Domain_Knowledge"
22
+
23
+ WHISPER_MODEL = "whisper-large-v3-turbo"
24
+
25
+ # TTS Settings
26
+ MAX_CHUNK_LENGTH = 200
27
+ SUPPORTED_LANGUAGES = {
28
+ 'vi': 'vi', 'en': 'en', 'fr': 'fr', 'es': 'es',
29
+ 'de': 'de', 'ja': 'ja', 'ko': 'ko', 'zh': 'zh'
30
+ }
31
+
32
+ # RAG Settings
33
+ EMBEDDING_DIMENSION = 768 # For Vietnamese model
34
+ MULTILINGUAL_EMBEDDING_DIMENSION = 4096 # For Nemotron model
35
+
36
+ TOP_K_RESULTS = 3
37
+
38
+ # SpeechBrain VAD Settings
39
+ VAD_MODEL = "speechbrain/vad-crdnn-libriparty"
40
+ VAD_THRESHOLD = 0.5
41
+ VAD_MIN_SILENCE_DURATION = 0.5
42
+ VAD_SPEECH_PAD_DURATION = 0.1
43
+ SAMPLE_RATE = 16000
44
+
45
+ settings = Settings()
core/__pycache__/multilingual_manager.cpython-310.pyc ADDED
Binary file (5.64 kB). View file
 
core/__pycache__/rag_system.cpython-310.pyc ADDED
Binary file (7.83 kB). View file
 
core/__pycache__/speechbrain_vad.cpython-310.pyc ADDED
Binary file (4.99 kB). View file
 
core/__pycache__/tts_service.cpython-310.pyc ADDED
Binary file (6.24 kB). View file
 
core/__pycache__/wikipedia_processor.cpython-310.pyc ADDED
Binary file (3.14 kB). View file
 
core/multilingual_manager.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from typing import Dict, Tuple, Optional
3
+ from sentence_transformers import SentenceTransformer
4
+ from config.settings import settings
5
+
6
+ class MultilingualManager:
7
+ def __init__(self):
8
+ self.vietnamese_model = None
9
+ self.multilingual_model = None
10
+ self.current_language = 'vi'
11
+
12
+ # Phát hiện thuộc ngôn ngữ dựa trên các mẫu ký tự và từ phổ biến
13
+ self.language_patterns = {
14
+ 'vi': {
15
+ 'chars': set('àáâãèéêìíòóôõùúýăđĩũơưạảấầẩẫậắằẳẵặẹẻẽếềểễệỉịọỏốồổỗộớờởỡợụủứừửữựỳỵỷỹ'),
16
+ 'common_words': ['của', 'và', 'là', 'có', 'được', 'trong', 'cho', 'với', 'như', 'tôi']
17
+ },
18
+ 'en': {
19
+ 'chars': set('abcdefghijklmnopqrstuvwxyz'),
20
+ 'common_words': ['the', 'and', 'is', 'are', 'for', 'with', 'this', 'that', 'you', 'they']
21
+ },
22
+ 'fr': {
23
+ 'chars': set('àâæçèéêëîïôœùûüÿ'),
24
+ 'common_words': ['le', 'la', 'et', 'est', 'dans', 'pour', 'avec', 'vous', 'nous', 'ils']
25
+ },
26
+ 'es': {
27
+ 'chars': set('áéíóúñü'),
28
+ 'common_words': ['el', 'la', 'y', 'es', 'en', 'por', 'con', 'los', 'las', 'del']
29
+ },
30
+ 'de': {
31
+ 'chars': set('äöüß'),
32
+ 'common_words': ['der', 'die', 'das', 'und', 'ist', 'in', 'für', 'mit', 'sich', 'nicht']
33
+ },
34
+ 'ja': {
35
+ 'chars': set('ぁ-んァ-ン一-龯'),
36
+ 'common_words': ['の', 'に', 'は', 'を', 'た', 'で', 'し', 'が', 'ます', 'です']
37
+ },
38
+ 'ko': {
39
+ 'chars': set('가-힣'),
40
+ 'common_words': ['이', '그', '에', '를', '의', '에', '에서', '으로', '하다', '이다']
41
+ },
42
+ 'zh': {
43
+ 'chars': set('一-鿌'),
44
+ 'common_words': ['的', '是', '在', '有', '和', '了', '人', '我', '他', '这']
45
+ }
46
+ }
47
+ self._initialize_models()
48
+ def _initialize_models(self):
49
+ """Khởi tạo các mô hình đa ngôn ngữ"""
50
+ try:
51
+ print("🔄 Đang tải mô hình embedding tiếng Việt...")
52
+ self.vietnamese_model = SentenceTransformer(settings.VIETNAMESE_EMBEDDING_MODEL)
53
+ print("✅ Đã tải mô hình embedding tiếng Việt")
54
+ except Exception as e:
55
+ print(f"❌ Lỗi tải mô hình embedding tiếng Việt: {e}")
56
+ self.vietnamese_model = None
57
+
58
+ try:
59
+ print("🔄 Đang tải mô hình embedding đa ngôn ngữ...")
60
+ self.multilingual_model = SentenceTransformer(settings.MULTILINGUAL_EMBEDDING_MODEL,trust_remote_code=True )
61
+ print("✅ Đã tải mô hình embedding đa ngôn ngữ")
62
+ except Exception as e:
63
+ print(f"❌ Lỗi tải mô hình embedding đa ngôn ngữ: {e}")
64
+ self.multilingual_model = None
65
+
66
+ def detect_language(self, text: str) -> str:
67
+ """Phát hiện ngôn ngữ với độ chính xác cao"""
68
+ if not text or len(text.strip()) == 0:
69
+ return 'vi' # Default to Vietnamese
70
+
71
+ text_lower = text.lower()
72
+ scores = {}
73
+
74
+ for lang, patterns in self.language_patterns.items():
75
+ score = 0
76
+
77
+ # Score based on special characters
78
+ char_score = sum(1 for char in text if char in patterns['chars'])
79
+ score += char_score * 2
80
+
81
+ # Score based on common words
82
+ word_score = sum(1 for word in patterns['common_words'] if word in text_lower)
83
+ score += word_score
84
+
85
+ scores[lang] = score
86
+
87
+ # Return language with highest score
88
+ detected_lang = max(scores.items(), key=lambda x: x[1])[0]
89
+
90
+ # If no strong detection, use character-based fallback
91
+ if max(scores.values()) < 3:
92
+ vietnamese_chars = set('àáâãèéêìíòóôõùúýăđĩũơưạảấầẩẫậắằẳẵặẹẻẽếềểễệỉịọỏốồổỗộớờởỡợụủứừửữựỳỵỷỹ')
93
+ if any(char in vietnamese_chars for char in text):
94
+ return 'vi'
95
+ elif any(char in text for char in 'あいうえおぁ-んァ-ン'):
96
+ return 'ja'
97
+ elif any(char in text for char in '你好'):
98
+ return 'zh'
99
+ elif any(char in text for char in '안녕'):
100
+ return 'ko'
101
+ else:
102
+ return 'en' # Default to English for other cases
103
+
104
+ return detected_lang
105
+ def get_embedding_model(self, language: str = None) -> Optional[SentenceTransformer]:
106
+ """Lấy mô hình embedding dựa trên ngôn ngữ đã phát hiện"""
107
+ lang = language if language in settings.SUPPORTED_LANGUAGES else self.current_language
108
+
109
+ if lang == 'vi':
110
+ return self.vietnamese_model
111
+ else:
112
+ return self.multilingual_model
113
+
114
+ def get_llm_model_name(self, language: str = None) -> str:
115
+ """Lấy tên mô hình LLM dựa trên ngôn ngữ đã phát hiện"""
116
+ lang = language if language in settings.SUPPORTED_LANGUAGES else self.current_language
117
+
118
+ if lang == 'vi':
119
+ return settings.VIETNAMESE_LLM_MODEL
120
+ else:
121
+ return settings.MULTILINGUAL_LLM_MODEL
122
+
123
+ def get_language_info(self, language: str = None) -> Dict:
124
+ """Lấy thông tin ngôn ngữ bao gồm mã và tên đầy đủ"""
125
+ lang = language if language in settings.SUPPORTED_LANGUAGES else self.current_language
126
+
127
+ model_info = {
128
+ 'vi': {
129
+ 'name': 'Tiếng Việt',
130
+ 'embedding_model': settings.VIETNAMESE_EMBEDDING_MODEL,
131
+ 'llm_model': settings.VIETNAMESE_LLM_MODEL,
132
+ 'status': 'active' if self.vietnamese_model else 'inactive'
133
+ },
134
+ 'other': {
135
+ 'name': 'Multilingual',
136
+ 'embedding_model': settings.MULTILINGUAL_EMBEDDING_MODEL,
137
+ 'llm_model': settings.MULTILINGUAL_LLM_MODEL,
138
+ 'status': 'active' if self.multilingual_model else 'inactive'
139
+ }
140
+ }
141
+
142
+ if lang == 'vi':
143
+ return model_info['vi']
144
+ else:
145
+ return model_info['other']
146
+
core/rag_system.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import faiss
3
+ from typing import List, Dict, Optional
4
+ from sentence_transformers import SentenceTransformer
5
+ from models.schemas import RAGSearchResult
6
+ from config.settings import settings
7
+ from core.multilingual_manager import MultilingualManager
8
+
9
+ class EnhancedRAGSystem:
10
+ def __init__(self):
11
+ self.documents: List[str] = []
12
+ self.metadatas: List[Dict] = []
13
+ self.embeddings: Optional[np.ndarray] = None
14
+ self.index: Optional[faiss.Index] = None
15
+
16
+ # Multilingual support
17
+ self.multilingual_manager = MultilingualManager()
18
+ self.current_dimension = settings.EMBEDDING_DIMENSION
19
+
20
+ self._initialize_sample_data() # SỬA TÊN HÀM
21
+
22
+ def _initialize_sample_data(self): # SỬA TÊN HÀM
23
+ """Khởi tạo dữ liệu mẫu"""
24
+ # Vietnamese sample data
25
+ vietnamese_data = [
26
+ "Rau xanh cung cấp nhiều vitamin và chất xơ tốt cho sức khỏe",
27
+ "Trái cây tươi chứa nhiều vitamin C và chất chống oxy hóa",
28
+ "Cá hồi giàu omega-3 tốt cho tim mạch và trí não",
29
+ "Nước rất quan trọng cho cơ thể, nên uống ít nhất 2 lít mỗi ngày",
30
+ "Hà Nội là thủ đô của Việt Nam, nằm ở miền Bắc",
31
+ "Thành phố Hồ Chí Minh là thành phố lớn nhất Việt Nam",
32
+ "Việt Nam có khí hậu nhiệt đới gió mùa với 4 mùa rõ rệt"
33
+ ]
34
+
35
+ # English sample data
36
+ english_data = [
37
+ "Green vegetables provide many vitamins and fiber that are good for health",
38
+ "Fresh fruits contain lots of vitamin C and antioxidants",
39
+ "Salmon is rich in omega-3 which is good for heart and brain",
40
+ "Water is very important for the body, should drink at least 2 liters per day",
41
+ "London is the capital of England and the United Kingdom",
42
+ "New York City is the most populous city in the United States",
43
+ "The United States has diverse climate zones from tropical to arctic"
44
+ ]
45
+
46
+ # Vietnamese metadata - SỬA LỖI SYNTAX
47
+ vietnamese_metadatas = [
48
+ {"type": "nutrition", "source": "sample", "language": "vi"},
49
+ {"type": "nutrition", "source": "sample", "language": "vi"},
50
+ {"type": "nutrition", "source": "sample", "language": "vi"},
51
+ {"type": "health", "source": "sample", "language": "vi"},
52
+ {"type": "geography", "source": "sample", "language": "vi"},
53
+ {"type": "geography", "source": "sample", "language": "vi"},
54
+ {"type": "geography", "source": "sample", "language": "vi"}
55
+ ]
56
+
57
+ # English metadata - SỬA LỖI SYNTAX
58
+ english_metadatas = [
59
+ {"type": "nutrition", "source": "sample", "language": "en"},
60
+ {"type": "nutrition", "source": "sample", "language": "en"},
61
+ {"type": "nutrition", "source": "sample", "language": "en"},
62
+ {"type": "health", "source": "sample", "language": "en"},
63
+ {"type": "geography", "source": "sample", "language": "en"},
64
+ {"type": "geography", "source": "sample", "language": "en"},
65
+ {"type": "geography", "source": "sample", "language": "en"}
66
+ ]
67
+
68
+ # Add documents with language metadata
69
+ self.add_documents(vietnamese_data, vietnamese_metadatas)
70
+ self.add_documents(english_data, english_metadatas)
71
+
72
+ def add_documents(self, documents: List[str], metadatas: List[Dict] = None):
73
+ """Thêm documents vào database với embedding phù hợp"""
74
+ if not documents:
75
+ return
76
+
77
+ # Ensure metadatas has the same length as documents
78
+ if metadatas is None:
79
+ metadatas = [{} for _ in documents]
80
+ elif len(metadatas) != len(documents):
81
+ # Extend or truncate metadatas to match documents length
82
+ if len(metadatas) < len(documents):
83
+ metadatas = metadatas + [{} for _ in range(len(documents) - len(metadatas))]
84
+ else:
85
+ metadatas = metadatas[:len(documents)]
86
+
87
+ # Detect language for each document and create embeddings accordingly
88
+ new_embeddings_list = []
89
+ valid_documents = []
90
+ valid_metadatas = []
91
+
92
+ for i, doc in enumerate(documents):
93
+ if not doc or len(doc.strip()) == 0:
94
+ continue
95
+
96
+ language = metadatas[i].get('language', 'vi')
97
+ embedding_model = self.multilingual_manager.get_embedding_model(language)
98
+
99
+ if embedding_model is not None:
100
+ try:
101
+ # Create embedding for this document
102
+ doc_embedding = embedding_model.encode([doc])
103
+ new_embeddings_list.append(doc_embedding[0])
104
+ valid_documents.append(doc)
105
+ valid_metadatas.append(metadatas[i])
106
+
107
+ except Exception as e:
108
+ print(f"❌ Lỗi tạo embedding cho document {i}: {e}")
109
+
110
+ if not valid_documents:
111
+ return
112
+
113
+ # Convert list of embeddings to numpy array
114
+ new_embeddings = np.array(new_embeddings_list)
115
+
116
+ # Handle dimension mismatch
117
+ if self.embeddings is not None and self.embeddings.shape[1] != new_embeddings.shape[1]:
118
+ print(f"⚠️ Phát hiện dimension mismatch ({self.embeddings.shape[1]} vs {new_embeddings.shape[1]}), tạo index mới...")
119
+ self.embeddings = None
120
+ self.index = None
121
+
122
+ # Update embeddings
123
+ if self.embeddings is None:
124
+ self.embeddings = new_embeddings
125
+ self.current_dimension = new_embeddings.shape[1]
126
+ else:
127
+ self.embeddings = np.vstack([self.embeddings, new_embeddings])
128
+
129
+ # Update FAISS index
130
+ self._update_faiss_index()
131
+
132
+ self.documents.extend(valid_documents)
133
+ self.metadatas.extend(valid_metadatas)
134
+ print(f"✅ Đã thêm {len(valid_documents)} documents vào RAG database")
135
+
136
+ def _update_faiss_index(self):
137
+ """Cập nhật FAISS index với embeddings hiện tại"""
138
+ if self.embeddings is None or len(self.embeddings) == 0:
139
+ return
140
+
141
+ try:
142
+ dimension = self.embeddings.shape[1]
143
+ self.index = faiss.IndexFlatIP(dimension)
144
+
145
+ # Normalize embeddings for cosine similarity
146
+ faiss.normalize_L2(self.embeddings)
147
+ self.index.add(self.embeddings.astype(np.float32))
148
+
149
+ print(f"✅ Đã cập nhật FAISS index với dimension {dimension}")
150
+ except Exception as e:
151
+ print(f"❌ Lỗi cập nhật FAISS index: {e}")
152
+
153
+ def semantic_search(self, query: str, top_k: int = None) -> List[RAGSearchResult]:
154
+ """Tìm kiếm ngữ nghĩa với model phù hợp theo ngôn ngữ"""
155
+ if top_k is None:
156
+ top_k = settings.TOP_K_RESULTS
157
+
158
+ if not self.documents or self.index is None:
159
+ return self._fallback_keyword_search(query, top_k)
160
+
161
+ # Detect query language and get appropriate model
162
+ query_language = self.multilingual_manager.detect_language(query)
163
+ embedding_model = self.multilingual_manager.get_embedding_model(query_language)
164
+
165
+ if embedding_model is None:
166
+ return self._fallback_keyword_search(query, top_k)
167
+
168
+ try:
169
+ # Encode query with appropriate model
170
+ query_embedding = embedding_model.encode([query])
171
+
172
+ # Normalize query embedding for cosine similarity
173
+ faiss.normalize_L2(query_embedding)
174
+
175
+ # Search in FAISS index
176
+ similarities, indices = self.index.search(
177
+ query_embedding.astype(np.float32),
178
+ min(top_k, len(self.documents))
179
+ )
180
+
181
+ results = []
182
+ for i, (similarity, idx) in enumerate(zip(similarities[0], indices[0])):
183
+ if idx < len(self.documents):
184
+ results.append(RAGSearchResult(
185
+ id=str(idx),
186
+ text=self.documents[idx],
187
+ similarity=float(similarity),
188
+ metadata=self.metadatas[idx] if idx < len(self.metadatas) else {}
189
+ ))
190
+
191
+ # Filter results by language relevance
192
+ filtered_results = self._filter_by_language_relevance(results, query_language)
193
+
194
+ print(f"🔍 Tìm kiếm '{query[:50]}...' (ngôn ngữ: {query_language}) - Tìm thấy {len(filtered_results)} kết quả")
195
+ return filtered_results
196
+
197
+ except Exception as e:
198
+ print(f"❌ Lỗi tìm kiếm ngữ nghĩa: {e}")
199
+ return self._fallback_keyword_search(query, top_k)
200
+
201
+ def _filter_by_language_relevance(self, results: List[RAGSearchResult], query_language: str) -> List[RAGSearchResult]:
202
+ """Lọc kết quả theo độ liên quan ngôn ngữ"""
203
+ if not results:
204
+ return results
205
+
206
+ # Boost scores for documents in the same language
207
+ for result in results:
208
+ doc_language = result.metadata.get('language', 'vi')
209
+ if doc_language == query_language:
210
+ # Boost similarity score for same language documents
211
+ result.similarity = min(result.similarity * 1.2, 1.0)
212
+
213
+ # Re-sort by updated similarity scores
214
+ results.sort(key=lambda x: x.similarity, reverse=True)
215
+ return results
216
+
217
+ def _fallback_keyword_search(self, query: str, top_k: int) -> List[RAGSearchResult]:
218
+ """Tìm kiếm dự phòng dựa trên từ khóa"""
219
+ query_lower = query.lower()
220
+ results = []
221
+
222
+ for i, doc in enumerate(self.documents):
223
+ score = 0
224
+ doc_language = self.metadatas[i].get('language', 'vi') if i < len(self.metadatas) else 'vi'
225
+ query_language = self.multilingual_manager.detect_language(query)
226
+
227
+ # Language matching bonus
228
+ if doc_language == query_language:
229
+ score += 0.5
230
+
231
+ # Keyword matching
232
+ for word in query_lower.split():
233
+ if len(word) > 2 and word in doc.lower():
234
+ score += 1
235
+
236
+ if score > 0:
237
+ results.append(RAGSearchResult(
238
+ id=str(i),
239
+ text=doc,
240
+ similarity=min(score / 5, 1.0),
241
+ metadata=self.metadatas[i] if i < len(self.metadatas) else {}
242
+ ))
243
+
244
+ results.sort(key=lambda x: x.similarity, reverse=True)
245
+ return results[:top_k]
246
+
247
+ def get_collection_stats(self) -> Dict:
248
+ """Lấy thống kê collection với thông tin đa ngôn ngữ"""
249
+ language_stats = {}
250
+ for metadata in self.metadatas:
251
+ lang = metadata.get('language', 'unknown')
252
+ language_stats[lang] = language_stats.get(lang, 0) + 1
253
+
254
+ return {
255
+ 'total_documents': len(self.documents),
256
+ 'embedding_count': len(self.embeddings) if self.embeddings is not None else 0,
257
+ 'embedding_dimension': self.current_dimension,
258
+ 'language_distribution': language_stats,
259
+ 'name': 'multilingual_rag_system',
260
+ 'status': 'active',
261
+ 'has_embeddings': self.embeddings is not None
262
+ }
core/speechbrain_vad.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torchaudio
3
+ import numpy as np
4
+ from speechbrain.inference import VAD
5
+ from typing import List, Tuple, Optional
6
+ import queue
7
+ import threading
8
+ import time
9
+ from config.settings import settings
10
+
11
+ class SpeechBrainVAD:
12
+ def __init__(self):
13
+ self.vad_model = None
14
+ self.sample_rate = settings.SAMPLE_RATE
15
+ self.threshold = settings.VAD_THRESHOLD
16
+ self.min_silence_duration = settings.VAD_MIN_SILENCE_DURATION
17
+ self.speech_pad_duration = settings.VAD_SPEECH_PAD_DURATION
18
+ self.is_running = False
19
+ self.audio_queue = queue.Queue()
20
+ self.speech_buffer = []
21
+ self.silence_start_time = None
22
+ self.callback = None
23
+
24
+ self._initialize_model()
25
+
26
+ def _initialize_model(self):
27
+ """Khởi tạo mô hình VAD từ SpeechBrain"""
28
+ try:
29
+ print("🔄 Đang tải mô hình SpeechBrain VAD...")
30
+ self.vad_model = VAD.from_hparams(
31
+ source=settings.VAD_MODEL,
32
+ savedir=f"pretrained_models/{settings.VAD_MODEL}"
33
+ )
34
+ print("✅ Đã tải mô hình VAD thành công")
35
+ except Exception as e:
36
+ print(f"❌ Lỗi tải mô hình VAD: {e}")
37
+ self.vad_model = None
38
+
39
+ def preprocess_audio(self, audio_data: np.ndarray, original_sr: int) -> np.ndarray:
40
+ """Tiền xử lý audio cho VAD"""
41
+ if original_sr != self.sample_rate:
42
+ # Resample audio to VAD sample rate
43
+ audio_tensor = torch.from_numpy(audio_data).float()
44
+ if len(audio_tensor.shape) > 1:
45
+ audio_tensor = audio_tensor.mean(dim=0) # Convert to mono
46
+
47
+ resampler = torchaudio.transforms.Resample(
48
+ orig_freq=original_sr,
49
+ new_freq=self.sample_rate
50
+ )
51
+ audio_tensor = resampler(audio_tensor)
52
+ audio_data = audio_tensor.numpy()
53
+
54
+ # Normalize audio
55
+ if np.max(np.abs(audio_data)) > 0:
56
+ audio_data = audio_data / np.max(np.abs(audio_data))
57
+
58
+ return audio_data
59
+
60
+ def detect_voice_activity(self, audio_chunk: np.ndarray) -> bool:
61
+ """Phát hiện hoạt động giọng nói trong audio chunk"""
62
+ if self.vad_model is None:
63
+ # Fallback: simple energy-based VAD
64
+ return self._energy_based_vad(audio_chunk)
65
+
66
+ try:
67
+ # Convert to tensor and add batch dimension
68
+ audio_tensor = torch.from_numpy(audio_chunk).float().unsqueeze(0)
69
+
70
+ # Get VAD probabilities
71
+ with torch.no_grad():
72
+ prob = self.vad_model.get_speech_prob_chunk(audio_tensor)
73
+
74
+ return prob.item() > self.threshold
75
+
76
+ except Exception as e:
77
+ print(f"❌ Lỗi VAD detection: {e}")
78
+ return self._energy_based_vad(audio_chunk)
79
+
80
+ def _energy_based_vad(self, audio_chunk: np.ndarray) -> bool:
81
+ """Fallback VAD dựa trên năng lượng âm thanh"""
82
+ energy = np.mean(audio_chunk ** 2)
83
+ return energy > 0.01 # Simple threshold
84
+
85
+ def process_stream(self, audio_chunk: np.ndarray, original_sr: int):
86
+ """Xử lý audio stream real-time"""
87
+ if not self.is_running:
88
+ return
89
+
90
+ # Preprocess audio
91
+ processed_audio = self.preprocess_audio(audio_chunk, original_sr)
92
+
93
+ # Detect voice activity
94
+ is_speech = self.detect_voice_activity(processed_audio)
95
+
96
+ if is_speech:
97
+ self.silence_start_time = None
98
+ self.speech_buffer.extend(processed_audio)
99
+ print("🎤 Đang nói...")
100
+ else:
101
+ # Silence detected
102
+ if self.silence_start_time is None:
103
+ self.silence_start_time = time.time()
104
+ elif len(self.speech_buffer) > 0:
105
+ silence_duration = time.time() - self.silence_start_time
106
+ if silence_duration >= self.min_silence_duration:
107
+ # End of speech segment
108
+ self._process_speech_segment()
109
+
110
+ return is_speech
111
+
112
+ def _process_speech_segment(self):
113
+ """Xử lý segment giọng nói khi kết thúc"""
114
+ if len(self.speech_buffer) == 0:
115
+ return
116
+
117
+ # Convert buffer to numpy array
118
+ speech_audio = np.array(self.speech_buffer)
119
+
120
+ # Call callback with speech segment
121
+ if self.callback and callable(self.callback):
122
+ self.callback(speech_audio, self.sample_rate)
123
+
124
+ # Clear buffer
125
+ self.speech_buffer = []
126
+ self.silence_start_time = None
127
+
128
+ print("✅ Đã xử lý segment giọng nói")
129
+
130
+ def start_stream(self, callback: callable):
131
+ """Bắt đầu xử lý stream"""
132
+ self.is_running = True
133
+ self.callback = callback
134
+ self.speech_buffer = []
135
+ self.silence_start_time = None
136
+ print("🎙️ Bắt đầu stream VAD...")
137
+
138
+ def stop_stream(self):
139
+ """Dừng xử lý stream"""
140
+ self.is_running = False
141
+ # Process any remaining speech
142
+ if len(self.speech_buffer) > 0:
143
+ self._process_speech_segment()
144
+ print("🛑 Đã dừng stream VAD")
145
+
146
+ def get_audio_chunk_from_stream(self, stream, chunk_size: int = 1024):
147
+ """Lấy audio chunk từ stream (for microphone input)"""
148
+ try:
149
+ data = stream.read(chunk_size, exception_on_overflow=False)
150
+ audio_data = np.frombuffer(data, dtype=np.int16)
151
+ return audio_data.astype(np.float32) / 32768.0 # Normalize to [-1, 1]
152
+ except Exception as e:
153
+ print(f"❌ Lỗi đọc audio stream: {e}")
154
+ return None
core/tts_service.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import re
3
+ import time
4
+ import asyncio
5
+ from typing import List, Optional
6
+ from gtts import gTTS
7
+ import edge_tts
8
+ from config.settings import settings
9
+ from models.schemas import TTSRequest
10
+
11
+ class EnhancedTTSService:
12
+ def __init__(self):
13
+ self.supported_languages = settings.SUPPORTED_LANGUAGES
14
+ self.max_chunk_length = settings.MAX_CHUNK_LENGTH
15
+
16
+ def detect_language(self, text: str) -> str:
17
+ """Đơn giản phát hiện ngôn ngữ dựa trên ký tự"""
18
+ vietnamese_chars = set('àáâãèéêìíòóôõùúýăđĩũơưạảấầẩẫậắằẳẵặẹẻẽếềểễệỉịọỏốồổỗộớờởỡợụủứừửữựỳỵỷỹ')
19
+ if any(char in vietnamese_chars for char in text.lower()):
20
+ return 'vi'
21
+ elif any(char in text for char in 'あいうえお'):
22
+ return 'ja'
23
+ elif any(char in text for char in '你好'):
24
+ return 'zh'
25
+ elif any(char in text for char in '안녕'):
26
+ return 'ko'
27
+ else:
28
+ return 'en'
29
+
30
+ def split_text_into_chunks(self, text: str, max_length: int = None) -> List[str]:
31
+ """Chia văn bản thành các đoạn nhỏ cho TTS"""
32
+ if max_length is None:
33
+ max_length = self.max_chunk_length
34
+
35
+ sentences = re.split(r'[.!?]+', text)
36
+ chunks = []
37
+ current_chunk = ""
38
+
39
+ for sentence in sentences:
40
+ sentence = sentence.strip()
41
+ if not sentence:
42
+ continue
43
+
44
+ if len(sentence) > max_length:
45
+ parts = re.split(r'[,;:]', sentence)
46
+ for part in parts:
47
+ part = part.strip()
48
+ if not part:
49
+ continue
50
+ if len(current_chunk) + len(part) + 2 <= max_length:
51
+ if current_chunk:
52
+ current_chunk += ". " + part
53
+ else:
54
+ current_chunk = part
55
+ else:
56
+ if current_chunk:
57
+ chunks.append(current_chunk)
58
+ current_chunk = part
59
+ else:
60
+ if len(current_chunk) + len(sentence) + 2 <= max_length:
61
+ if current_chunk:
62
+ current_chunk += ". " + sentence
63
+ else:
64
+ current_chunk = sentence
65
+ else:
66
+ if current_chunk:
67
+ chunks.append(current_chunk)
68
+ current_chunk = sentence
69
+
70
+ if current_chunk:
71
+ chunks.append(current_chunk)
72
+
73
+ return chunks
74
+
75
+ def text_to_speech_gtts(self, text: str, language: str = 'vi') -> Optional[bytes]:
76
+ """Sử dụng gTTS (Google Text-to-Speech) library"""
77
+ try:
78
+ chunks = self.split_text_into_chunks(text)
79
+ audio_chunks = []
80
+
81
+ for chunk in chunks:
82
+ if not chunk.strip():
83
+ continue
84
+
85
+ tts = gTTS(text=chunk, lang=language, slow=False)
86
+ audio_buffer = io.BytesIO()
87
+ tts.write_to_fp(audio_buffer)
88
+ audio_buffer.seek(0)
89
+ audio_chunks.append(audio_buffer.read())
90
+
91
+ time.sleep(0.1)
92
+
93
+ if audio_chunks:
94
+ return b''.join(audio_chunks)
95
+ return None
96
+
97
+ except Exception as e:
98
+ print(f"❌ Lỗi gTTS: {e}")
99
+ return None
100
+
101
+ async def text_to_speech_edgetts(self, text: str, voice: str = 'vi-VN-NamMinhNeural') -> Optional[bytes]:
102
+ """Sử dụng Edge-TTS (Microsoft Edge) - async version"""
103
+ try:
104
+ communicate = edge_tts.Communicate(text, voice)
105
+ audio_buffer = io.BytesIO()
106
+
107
+ async for chunk in communicate.stream():
108
+ if chunk["type"] == "audio":
109
+ audio_buffer.write(chunk["data"])
110
+
111
+ audio_buffer.seek(0)
112
+ return audio_buffer.read()
113
+
114
+ except Exception as e:
115
+ print(f"❌ Lỗi Edge-TTS: {e}")
116
+ return None
117
+
118
+ def text_to_speech_edgetts_sync(self, text: str, voice: str = 'vi-VN-NamMinhNeural') -> Optional[bytes]:
119
+ """Sync wrapper for Edge-TTS"""
120
+ try:
121
+ return asyncio.run(self.text_to_speech_edgetts(text, voice))
122
+ except Exception as e:
123
+ print(f"❌ Lỗi Edge-TTS sync: {e}")
124
+ return None
125
+
126
+ def text_to_speech(self, text: str, language: str = None, provider: str = "auto") -> Optional[bytes]:
127
+ """Chuyển văn bản thành giọng nói với nhiều nhà cung cấp"""
128
+ if not text or len(text.strip()) == 0:
129
+ return None
130
+
131
+ if language is None:
132
+ language = self.detect_language(text)
133
+
134
+ text = self.clean_text(text)
135
+
136
+ try:
137
+ if provider == "auto" or provider == "gtts":
138
+ print(f"🔊 Đang sử dụng gTTS cho văn bản {len(text)} ký tự...")
139
+ audio_bytes = self.text_to_speech_gtts(text, language)
140
+ if audio_bytes:
141
+ return audio_bytes
142
+
143
+ if provider == "auto" or provider == "edgetts":
144
+ print(f"🔊 Đang thử Edge-TTS cho văn bản {len(text)} ký tự...")
145
+ voice_map = {
146
+ 'vi': 'vi-VN-NamMinhNeural',
147
+ 'en': 'en-US-AriaNeural',
148
+ 'fr': 'fr-FR-DeniseNeural',
149
+ 'es': 'es-ES-ElviraNeural',
150
+ 'de': 'de-DE-KatjaNeural',
151
+ 'ja': 'ja-JP-NanamiNeural',
152
+ 'ko': 'ko-KR-SunHiNeural',
153
+ 'zh': 'zh-CN-XiaoxiaoNeural'
154
+ }
155
+ voice = voice_map.get(language, 'vi-VN-NamMinhNeural')
156
+ audio_bytes = self.text_to_speech_edgetts_sync(text, voice)
157
+ if audio_bytes:
158
+ return audio_bytes
159
+
160
+ return self.text_to_speech_gtts(text, language)
161
+
162
+ except Exception as e:
163
+ print(f"❌ Lỗi TTS tổng hợp: {e}")
164
+ return None
165
+
166
+ def clean_text(self, text: str) -> str:
167
+ """Làm sạch văn bản trước khi chuyển thành giọng nói"""
168
+ text = re.sub(r'http\S+', '', text)
169
+ text = re.sub(r'[^\w\sàáâãèéêìíòóôõùúýăđĩũơưạảấầẩẫậắằẳẵặẹẻẽếềểễệỉịọỏốồổỗộớờởỡợụủứừửữựỳỵỷỹ.,!?;:()-]', '', text)
170
+ text = re.sub(r'\s+', ' ', text)
171
+ return text.strip()
172
+
173
+ def save_audio_to_file(self, audio_bytes: bytes, filename: str = None) -> str:
174
+ """Lưu audio bytes thành file tạm thời"""
175
+ if audio_bytes is None:
176
+ return None
177
+
178
+ if filename is None:
179
+ filename = f"tts_output_{int(time.time())}.mp3"
180
+
181
+ import os
182
+ temp_dir = "temp_audio"
183
+ os.makedirs(temp_dir, exist_ok=True)
184
+
185
+ filepath = os.path.join(temp_dir, filename)
186
+ with open(filepath, 'wb') as f:
187
+ f.write(audio_bytes)
188
+
189
+ return filepath
core/wikipedia_processor.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import pandas as pd
4
+ from typing import List
5
+ from models.schemas import RAGDocument
6
+
7
+ class WikipediaProcessor:
8
+ def __init__(self):
9
+ self.supported_formats = ['.txt', '.csv', '.json']
10
+
11
+ def process_uploaded_file(self, file_path: str) -> List[str]:
12
+ """Xử lý file Wikipedia uploaded"""
13
+ file_ext = os.path.splitext(file_path)[1].lower()
14
+
15
+ try:
16
+ if file_ext == '.txt':
17
+ return self._process_txt_file(file_path)
18
+ elif file_ext == '.csv':
19
+ return self._process_csv_file(file_path)
20
+ elif file_ext == '.json':
21
+ return self._process_json_file(file_path)
22
+ else:
23
+ raise ValueError(f"Định dạng file không được hỗ trợ: {file_ext}")
24
+ except Exception as e:
25
+ raise Exception(f"Lỗi xử lý file: {str(e)}")
26
+
27
+ def _process_txt_file(self, file_path: str) -> List[str]:
28
+ """Xử lý file text"""
29
+ with open(file_path, 'r', encoding='utf-8') as f:
30
+ content = f.read()
31
+
32
+ paragraphs = [p.strip() for p in content.split('\n\n') if p.strip() and len(p.strip()) > 20]
33
+ return paragraphs
34
+
35
+ def _process_csv_file(self, file_path: str) -> List[str]:
36
+ """Xử lý file CSV"""
37
+ try:
38
+ df = pd.read_csv(file_path)
39
+ documents = []
40
+
41
+ for _, row in df.iterrows():
42
+ doc_parts = []
43
+ for col in df.columns:
44
+ if pd.notna(row[col]) and str(row[col]).strip():
45
+ doc_parts.append(f"{col}: {row[col]}")
46
+ if doc_parts:
47
+ documents.append(" | ".join(doc_parts))
48
+
49
+ return documents
50
+ except Exception as e:
51
+ raise Exception(f"Lỗi đọc CSV: {str(e)}")
52
+
53
+ def _process_json_file(self, file_path: str) -> List[str]:
54
+ """Xử lý file JSON"""
55
+ try:
56
+ with open(file_path, 'r', encoding='utf-8') as f:
57
+ data = json.load(f)
58
+
59
+ documents = []
60
+
61
+ def extract_text(obj, current_path=""):
62
+ if isinstance(obj, dict):
63
+ for key, value in obj.items():
64
+ extract_text(value, f"{current_path}.{key}" if current_path else key)
65
+ elif isinstance(obj, list):
66
+ for item in obj:
67
+ extract_text(item, current_path)
68
+ elif isinstance(obj, str) and len(obj.strip()) > 10:
69
+ documents.append(f"{current_path}: {obj.strip()}")
70
+
71
+ extract_text(data)
72
+ return documents
73
+ except Exception as e:
74
+ raise Exception(f"Lỗi đọc JSON: {str(e)}")
main.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import groq
3
+ from config.settings import settings
4
+ from core.rag_system import EnhancedRAGSystem
5
+ from core.tts_service import EnhancedTTSService
6
+ from core.wikipedia_processor import WikipediaProcessor
7
+ from services.audio_service import AudioService
8
+ from services.chat_service import ChatService
9
+ from services.image_service import ImageService
10
+ from services.streaming_voice_service import StreamingVoiceService
11
+ from ui.components import create_custom_css, create_header
12
+ from ui.tabs import create_all_tabs
13
+
14
+ def main():
15
+ # Initialize clients and services
16
+ if not settings.GROQ_API_KEY:
17
+ raise ValueError("Please set the GROQ_API_KEY environment variable.")
18
+
19
+ client = groq.Client(api_key=settings.GROQ_API_KEY)
20
+
21
+ # Initialize core systems
22
+ rag_system = EnhancedRAGSystem()
23
+ tts_service = EnhancedTTSService()
24
+ wikipedia_processor = WikipediaProcessor()
25
+
26
+ # Initialize services
27
+ audio_service = AudioService(client, rag_system, tts_service)
28
+ chat_service = ChatService(client, rag_system, tts_service)
29
+ image_service = ImageService(client)
30
+ streaming_voice_service = StreamingVoiceService(client, rag_system, tts_service)
31
+
32
+ # Create Gradio interface
33
+ with gr.Blocks(css=create_custom_css(), theme=gr.themes.Soft(primary_hue="orange", neutral_hue="slate")) as demo:
34
+ create_header()
35
+ gr.Markdown("### 🌐 Hệ thống Đa ngôn ngữ - Tự động chuyển đổi model theo ngôn ngữ")
36
+ create_all_tabs(
37
+ audio_service=audio_service,
38
+ chat_service=chat_service,
39
+ image_service=image_service,
40
+ rag_system=rag_system,
41
+ tts_service=tts_service,
42
+ wikipedia_processor=wikipedia_processor,
43
+ streaming_voice_service=streaming_voice_service
44
+ )
45
+
46
+ return demo
47
+
48
+ if __name__ == "__main__":
49
+ demo = main()
50
+ demo.launch(share=True)
models/__pycache__/schemas.cpython-310.pyc ADDED
Binary file (1.22 kB). View file
 
models/schemas.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Dict, Any, Optional
2
+ from pydantic import BaseModel
3
+
4
+ class RAGDocument(BaseModel):
5
+ text: str
6
+ metadata: Dict[str, Any] = {}
7
+ similarity: Optional[float] = None
8
+
9
+ class RAGSearchResult(BaseModel):
10
+ id: str
11
+ text: str
12
+ similarity: float
13
+ metadata: Dict[str, Any]
14
+
15
+ class ChatMessage(BaseModel):
16
+ role: str
17
+ content: str
18
+
19
+ class TTSRequest(BaseModel):
20
+ text: str
21
+ language: str = 'vi'
22
+ provider: str = 'auto'
requirements.txt CHANGED
@@ -8,4 +8,4 @@ faiss-cpu
8
  edge-tts
9
  gtts
10
  groq
11
-
 
8
  edge-tts
9
  gtts
10
  groq
11
+ speechbrain
services/__pycache__/audio_service.cpython-310.pyc ADDED
Binary file (4.1 kB). View file
 
services/__pycache__/chat_service.cpython-310.pyc ADDED
Binary file (3.03 kB). View file
 
services/__pycache__/image_service.cpython-310.pyc ADDED
Binary file (1.81 kB). View file
 
services/__pycache__/streaming_voice_service.cpython-310.pyc ADDED
Binary file (6.17 kB). View file
 
services/audio_service.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import soundfile as sf
3
+ import io
4
+ from groq import Groq
5
+ from config.settings import settings
6
+ from core.rag_system import EnhancedRAGSystem
7
+ from core.tts_service import EnhancedTTSService
8
+ from core.multilingual_manager import MultilingualManager # NEW
9
+
10
+ class AudioService:
11
+
12
+ def __init__(self, groq_client: Groq, rag_system: EnhancedRAGSystem, tts_service: EnhancedTTSService):
13
+ self.groq_client = groq_client
14
+ self.rag_system = EnhancedRAGSystem()
15
+ self.tts_service = EnhancedTTSService()
16
+ self.multilingual_manager = MultilingualManager() # NEW
17
+
18
+
19
+ def transcribe_audio(self, audio: str) -> str:
20
+ """Chuyển đổi giọng nói thành văn bản sử dụng mô hình Whisper."""
21
+ if not audio:
22
+ raise ValueError("Audio input is empty.")
23
+ sr, y =audio
24
+
25
+ if y.ndim > 1:
26
+ y = np.mean(y, axis=1) # Chuyển đổi sang mono nếu cần
27
+ y = y.astype(np.float32)
28
+ y /= np.max(np.abs(y)) # Chuẩn hóa âm thanh
29
+
30
+ buffer = io.BytesIO()
31
+ sf.write(buffer, y, sr, format='WAV')
32
+ buffer.seek(0)
33
+
34
+ try:
35
+ completion = self.groq_client.audio.transcribe(
36
+ model=settings.WHISPER_MODEL,
37
+ audio=buffer,
38
+ response_format="text"
39
+ )
40
+ transcription = completion
41
+ except Exception as e:
42
+ transcription = f"Error trong quá trình chuyển đổi giọng nói thành văn bản: {e}"
43
+
44
+ language = self.multilingual_manager.detect_language(transcription)
45
+ respone = self._generate_response_with_rag(transcription, language)
46
+
47
+ tts_audio = None
48
+ if respone and respone.startswith("Error") is False:
49
+ tts_bytes = self.tts_service.text_to_speech(respone, language)
50
+ if tts_bytes:
51
+ tts_audio_path = self.tts_service.save_tts_audio(tts_bytes)
52
+ tts_audio = tts_audio_path
53
+ return transcription, respone, tts_audio, language
54
+
55
+ def _generate_response_with_rag(self, query: str, language: str) -> str:
56
+ """Tạo phản hồi sử dụng hệ thống RAG dựa trên truy vấn và ngôn ngữ."""
57
+ if not query or query.startswith("Error"):
58
+ return "Error: Truy vấn không hợp lệ để tạo phản hồi."
59
+ try:
60
+ rag_results = self.rag_system.semantic_search(query, top_k=3)
61
+ context_text = ""
62
+ if rag_results:
63
+ for result in rag_results:
64
+ context_text += result.document + "\n"
65
+ llm_model = self.multilingual_manager.get_llm_model(language)
66
+ if language == "vi":
67
+ system_prompt = """Bạn là trợ lý AI thông minh chuyên về tiếng Việt. Hãy sử dụng thông tin từ cơ sở kiến thức được cung cấp để trả lời câu hỏi một cách chính xác và hữu ích bằng tiếng Việt.
68
+ Thông tin tham khảo từ cơ sở kiến thức:
69
+ {context}
70
+ Nếu thông tin từ cơ sở kiến thức không đủ để trả lời, hãy dựa vào kiến thức chung của bạn. Luôn trả lời bằng tiếng Việt tự nhiên và dễ hiểu."""
71
+ else:
72
+ system_prompt = """You are a smart AI assistant. Please use the information from the provided knowledge base to answer questions accurately and helpfully in the same language as the user's question.
73
+
74
+ Reference information from knowledge base:
75
+ {context}
76
+
77
+ If the information from the knowledge base is not sufficient to answer, rely on your general knowledge. Always respond in natural and easy-to-understand language matching the user's language."""
78
+ message = [
79
+ {"role": "system", "content": system_prompt.format(context=context_text)},
80
+ {"role": "user", "content": query}
81
+ ]
82
+ completion = self.groq_client.chat.completions.create(
83
+ model=llm_model,
84
+ messages=message,
85
+ max_tokens=512,
86
+ temperature=0.7,
87
+ )
88
+ return completion.choices[0].message['content'].strip()
89
+ except Exception as e:
90
+ return f"Error trong quá trình tạo phản hồi với RAG: {e}"
91
+
services/chat_service.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Tuple, Optional
2
+ from groq import Groq
3
+ from config.settings import settings
4
+ from core.rag_system import EnhancedRAGSystem
5
+ from core.tts_service import EnhancedTTSService
6
+ from models.schemas import ChatMessage
7
+
8
+ class ChatService:
9
+ def __init__(self, groq_client: Groq, rag_system: EnhancedRAGSystem, tts_service: EnhancedTTSService):
10
+ self.groq_client = groq_client
11
+ self.rag_system = rag_system
12
+ self.tts_service = tts_service
13
+ self.multilingual_manager = rag_system.multilingual_manager
14
+
15
+ def respond(self, message: str,chat_history: List[Tuple[str, str]]) -> tuple:
16
+ """Tạo phản hồi cho tin nhắn đầu vào dựa trên lịch sử trò chuyện và ngôn ngữ."""
17
+ if chat_history is None:
18
+ chat_history = []
19
+
20
+ language = self.multilingual_manager.detect_language(message)
21
+ llm_model = self.multilingual_manager.get_llm_model(language)
22
+
23
+ messages = []
24
+ for user_msg, assistant_msg in chat_history:
25
+ messages.append({"role": "user", "content": user_msg})
26
+ messages.append({"role": "assistant", "content": assistant_msg})
27
+ messages.append({"role": "user", "content": message})
28
+
29
+ try:
30
+ rag_results = self.rag_system.semantic_search(message, top_k=3)
31
+ context_text = ""
32
+ if rag_results:
33
+ context_text = "\n Thông tin tham khảo:\n +".join([result.document for result in rag_results])
34
+
35
+ if language == 'vi':
36
+ system_message = {
37
+ "role": "system",
38
+ "content": f"Bạn là trợ lý AI hữu ích chuyên về tiếng Việt. Sử dụng thông tin từ cơ sở kiến thức khi có liên quan. Luôn trả lời bằng tiếng Việt tự nhiên.{context_text}"
39
+ }
40
+ else:
41
+ system_message = {
42
+ "role": "system",
43
+ "content": f"You are a helpful AI assistant. Use information from the knowledge base when relevant. Always respond in natural language matching the user's language.{context_text}"
44
+ }
45
+ messages_with_context = [system_message] + messages
46
+
47
+ completion = self.groq_client.chat.completions.create(
48
+ model=llm_model,
49
+ messages=messages_with_context,
50
+ max_tokens=512,
51
+ temperature=0.7,
52
+ )
53
+ assistant_message = completion.choices[0].message['content'].strip()
54
+
55
+ chat_history.append((message, assistant_message))
56
+
57
+ tts_audio_path = None
58
+ if assistant_message and not assistant_message.startswith("Error"):
59
+ tts_bytes = self.tts_service.text_to_speech(assistant_message, language)
60
+ if tts_bytes:
61
+ tts_audio_path = self.tts_service.save_tts_audio(tts_bytes)
62
+ except Exception as e:
63
+ assistant_message = f"Error trong quá trình tạo phản hồi: {e}"
64
+ chat_history.append((message, assistant_message))
65
+ tts_audio_path = None
66
+ return "", chat_history, tts_audio_path, language
67
+ def clear_chat_history(self, chat_history: List[Tuple[str,str]]) -> tuple:
68
+ """Xóa lịch sử trò chuyện (nếu lưu trữ lịch sử trong phiên làm việc)."""
69
+ return [],[]
services/image_service.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from groq import Groq
2
+ from config.settings import settings
3
+
4
+ class ImageService:
5
+ def __init__(self, groq_client: Groq):
6
+ self.client = groq_client
7
+
8
+ def analyze_image_with_description(self, image, user_description: str) -> str:
9
+ """Phân tích hình ảnh kết hợp với mô tả từ người dùng"""
10
+ if image is None:
11
+ return "No image uploaded."
12
+
13
+ try:
14
+ if user_description:
15
+ prompt = f"""Người dùng tải lên một hình ảnh và mô tả: "{user_description}"
16
+
17
+ Dựa trên mô tả này, hãy phân tích chi tiết bằng tiếng Việt:
18
+ 1. Mô tả chi tiết những gì có trong hình ảnh
19
+ 2. Phân tích các yếu tố liên quan đến mô tả của người dùng
20
+ 3. Đưa ra nhận xét và thông tin hữu ích"""
21
+ else:
22
+ prompt = """Hãy mô tả chi tiết bằng tiếng Việt những gì bạn nghĩ có thể có trong hình ảnh này.
23
+ Mô tả các đối tượng, màu sắc, bố cục và ngữ cảnh có thể có của hình ảnh."""
24
+
25
+ chat_completion = self.client.chat.completions.create(
26
+ messages=[{"role": "user", "content": prompt}],
27
+ model=settings.LLM_MODEL,
28
+ )
29
+ description = chat_completion.choices[0].message.content
30
+ except Exception as e:
31
+ description = f"Error in image analysis: {str(e)}"
32
+
33
+ return description
services/streaming_voice_service.py ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import numpy as np
3
+ import soundfile as sf
4
+ import threading
5
+ import time
6
+ import pyaudio
7
+ from groq import Groq
8
+ from typing import Optional, Callable
9
+ from config.settings import settings
10
+ from core.speechbrain_vad import SpeechBrainVAD
11
+ from core.rag_system import EnhancedRAGSystem
12
+ from core.tts_service import EnhancedTTSService
13
+
14
+ class StreamingVoiceService:
15
+ def __init__(self, groq_client: Groq, rag_system: EnhancedRAGSystem, tts_service: EnhancedTTSService):
16
+ self.client = groq_client
17
+ self.rag_system = rag_system
18
+ self.tts_service = tts_service
19
+ self.vad_processor = SpeechBrainVAD()
20
+
21
+ # Streaming state
22
+ self.is_listening = False
23
+ self.audio_stream = None
24
+ self.pyaudio_instance = None
25
+ self.callback_handler = None
26
+
27
+ # Conversation context
28
+ self.conversation_history = []
29
+ self.current_transcription = ""
30
+
31
+ def start_listening(self, callback_handler: Callable):
32
+ """Bắt đầu lắng nghe với VAD"""
33
+ if self.is_listening:
34
+ return False
35
+
36
+ try:
37
+ self.callback_handler = callback_handler
38
+ self.is_listening = True
39
+ self.conversation_history = []
40
+
41
+ # Initialize PyAudio
42
+ self.pyaudio_instance = pyaudio.PyAudio()
43
+
44
+ # Start audio stream
45
+ self.audio_stream = self.pyaudio_instance.open(
46
+ format=pyaudio.paInt16,
47
+ channels=1,
48
+ rate=settings.SAMPLE_RATE,
49
+ input=True,
50
+ frames_per_buffer=1024,
51
+ stream_callback=self._audio_callback
52
+ )
53
+
54
+ # Start VAD processing
55
+ self.vad_processor.start_stream(self._process_speech_segment)
56
+
57
+ print("🎙️ Bắt đầu lắng nghe...")
58
+ return True
59
+
60
+ except Exception as e:
61
+ print(f"❌ Lỗi khởi động stream: {e}")
62
+ self.stop_listening()
63
+ return False
64
+
65
+ def stop_listening(self):
66
+ """Dừng lắng nghe"""
67
+ self.is_listening = False
68
+
69
+ if self.audio_stream:
70
+ self.audio_stream.stop_stream()
71
+ self.audio_stream.close()
72
+
73
+ if self.pyaudio_instance:
74
+ self.pyaudio_instance.terminate()
75
+
76
+ self.vad_processor.stop_stream()
77
+ print("🛑 Đã dừng lắng nghe")
78
+
79
+ def _audio_callback(self, in_data, frame_count, time_info, status):
80
+ """Callback xử lý audio input real-time"""
81
+ if status:
82
+ print(f"Audio stream status: {status}")
83
+
84
+ if self.is_listening:
85
+ # Convert audio data to numpy array
86
+ audio_data = np.frombuffer(in_data, dtype=np.int16)
87
+ audio_float = audio_data.astype(np.float32) / 32768.0
88
+
89
+ # Process with VAD
90
+ self.vad_processor.process_stream(audio_float, settings.SAMPLE_RATE)
91
+
92
+ return (in_data, pyaudio.paContinue)
93
+
94
+ def _process_speech_segment(self, speech_audio: np.ndarray, sample_rate: int):
95
+ """Xử lý segment giọng nói đã được VAD phát hiện"""
96
+ if not self.is_listening or len(speech_audio) == 0:
97
+ return
98
+
99
+ print(f"🎯 Đang xử lý segment giọng nói ({len(speech_audio)} samples)...")
100
+
101
+ # Transcribe speech segment
102
+ transcription = self._transcribe_audio(speech_audio, sample_rate)
103
+ if transcription and len(transcription.strip()) > 0:
104
+ self.current_transcription = transcription
105
+ print(f"📝 Transcription: {transcription}")
106
+
107
+ # Generate AI response
108
+ response = self._generate_ai_response(transcription)
109
+
110
+ # Convert response to speech
111
+ tts_audio = self._text_to_speech(response)
112
+
113
+ # Call callback with results
114
+ if self.callback_handler:
115
+ self.callback_handler({
116
+ 'transcription': transcription,
117
+ 'response': response,
118
+ 'tts_audio': tts_audio,
119
+ 'speech_audio': speech_audio
120
+ })
121
+
122
+ def _transcribe_audio(self, audio_data: np.ndarray, sample_rate: int) -> Optional[str]:
123
+ """Chuyển đổi audio thành văn bản sử dụng Whisper"""
124
+ try:
125
+ # Convert numpy array to bytes buffer
126
+ buffer = io.BytesIO()
127
+ sf.write(buffer, audio_data, sample_rate, format='wav')
128
+ buffer.seek(0)
129
+
130
+ # Transcribe with Whisper
131
+ transcription = self.client.audio.transcriptions.create(
132
+ model=settings.WHISPER_MODEL,
133
+ file=("speech.wav", buffer.read()),
134
+ response_format="text",
135
+ language="vi" # Focus on Vietnamese
136
+ )
137
+
138
+ return transcription.strip()
139
+
140
+ except Exception as e:
141
+ print(f"❌ Lỗi transcription: {e}")
142
+ return None
143
+
144
+ def _generate_ai_response(self, user_input: str) -> str:
145
+ """Tạo phản hồi AI với RAG context"""
146
+ try:
147
+ # Add to conversation history
148
+ self.conversation_history.append({"role": "user", "content": user_input})
149
+
150
+ # Semantic search với RAG
151
+ rag_results = self.rag_system.semantic_search(user_input, top_k=2)
152
+ context_text = "\n".join([f"- {doc.text}" for doc in rag_results]) if rag_results else ""
153
+
154
+ # Prepare messages với conversation history
155
+ system_prompt = f"""Bạn là trợ lý AI thông minh chuyên về tiếng Việt. Hãy trả lời ngắn gọn, tự nhiên và hữu ích. Sử dụng thông tin từ cơ sở kiến thức khi có liên quan.
156
+
157
+ Thông tin tham khảo:
158
+ {context_text}
159
+
160
+ Hãy giữ câu trả lời ngắn gọn và tự nhiên như đang trò chuyện."""
161
+
162
+ messages = [{"role": "system", "content": system_prompt}]
163
+
164
+ # Add last 3 exchanges for context (to keep it manageable)
165
+ recent_history = self.conversation_history[-6:] # Last 3 user-assistant pairs
166
+ messages.extend(recent_history)
167
+
168
+ # Generate response
169
+ completion = self.client.chat.completions.create(
170
+ model=settings.LLM_MODEL,
171
+ messages=messages,
172
+ max_tokens=150, # Keep responses concise for streaming
173
+ temperature=0.7
174
+ )
175
+
176
+ response = completion.choices[0].message.content
177
+ self.conversation_history.append({"role": "assistant", "content": response})
178
+
179
+ # Keep conversation history manageable
180
+ if len(self.conversation_history) > 10:
181
+ self.conversation_history = self.conversation_history[-10:]
182
+
183
+ return response
184
+
185
+ except Exception as e:
186
+ return f"Xin lỗi, tôi gặp lỗi: {str(e)}"
187
+
188
+ def _text_to_speech(self, text: str) -> Optional[str]:
189
+ """Chuyển văn bản thành giọng nói"""
190
+ try:
191
+ tts_bytes = self.tts_service.text_to_speech(text, 'vi')
192
+ if tts_bytes:
193
+ return self.tts_service.save_audio_to_file(tts_bytes)
194
+ except Exception as e:
195
+ print(f"❌ Lỗi TTS: {e}")
196
+
197
+ return None
198
+
199
+ def get_conversation_state(self) -> dict:
200
+ """Lấy trạng thái cuộc hội thoại hiện tại"""
201
+ return {
202
+ 'is_listening': self.is_listening,
203
+ 'history_length': len(self.conversation_history),
204
+ 'current_transcription': self.current_transcription
205
+ }
ui/__pycache__/components.cpython-310.pyc ADDED
Binary file (4.65 kB). View file
 
ui/__pycache__/tabs.cpython-310.pyc ADDED
Binary file (11.6 kB). View file
 
ui/components.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ def create_custom_css() -> str:
4
+ return """
5
+ .gradio-container {
6
+ background-color: #1f1f1f;
7
+ }
8
+ .gr-markdown, .gr-markdown * {
9
+ color: #ffffff !important;
10
+ }
11
+
12
+ .streaming-active {
13
+ border: 3px solid #00ff00 !important;
14
+ animation: pulse 2s infinite;
15
+ }
16
+
17
+ .streaming-inactive {
18
+ border: 2px solid #ff4444 !important;
19
+ }
20
+
21
+ @keyframes pulse {
22
+ 0% { border-color: #00ff00; }
23
+ 50% { border-color: #00cc00; }
24
+ 100% { border-color: #00ff00; }
25
+ }
26
+
27
+ .vad-visualizer {
28
+ background: linear-gradient(90deg, #00ff00, #ffff00, #ff0000);
29
+ height: 20px;
30
+ border-radius: 10px;
31
+ margin: 10px 0;
32
+ transition: all 0.1s ease;
33
+ }
34
+
35
+ .conversation-bubble {
36
+ background: #2d2d2d;
37
+ border-radius: 15px;
38
+ padding: 10px 15px;
39
+ margin: 5px 0;
40
+ border-left: 4px solid #f55036;
41
+ }
42
+
43
+ .user-bubble {
44
+ border-left-color: #36a2f5;
45
+ }
46
+
47
+ .assistant-bubble {
48
+ border-left-color: #f55036;
49
+ }
50
+ """
51
+ def create_header() -> gr.Markdown:
52
+ return gr.Markdown("# 🎙️ Groq x Gradio Multi-Modal với RAG Wikipedia")
53
+ def create_audio_components() -> tuple:
54
+ with gr.Row():
55
+ audio_input = gr.Audio(type="numpy", label="Nói hoặc tải lên file âm thanh")
56
+
57
+ with gr.Row():
58
+ transcription_output = gr.Textbox(
59
+ label="Bản ghi âm",
60
+ lines=5,
61
+ interactive=True,
62
+ placeholder="Bản ghi âm sẽ hiển thị ở đây..."
63
+ )
64
+ response_output = gr.Textbox(
65
+ label="Phản hồi AI",
66
+ lines=5,
67
+ interactive=True,
68
+ placeholder="Phản hồi của AI sẽ hiển thị ở đây..."
69
+ )
70
+
71
+ with gr.Row():
72
+ tts_audio_output = gr.Audio(
73
+ label="Phản hồi bằng giọng nói",
74
+ interactive=False
75
+ )
76
+
77
+ process_button = gr.Button("Xử lý", variant="primary")
78
+
79
+ return audio_input, transcription_output, response_output, tts_audio_output, process_button
80
+
81
+ def create_chat_components() -> tuple:
82
+ chatbot = gr.Chatbot()
83
+ state = gr.State([])
84
+
85
+ with gr.Row():
86
+ user_input = gr.Textbox(
87
+ show_label=False,
88
+ placeholder="Nhập tin nhắn của bạn ở đây...",
89
+ container=False,
90
+ scale=4
91
+ )
92
+ send_button = gr.Button("Gửi", variant="primary", scale=1)
93
+ clear_button = gr.Button("Xóa Chat", variant="secondary", scale=1)
94
+
95
+ with gr.Row():
96
+ chat_tts_output = gr.Audio(
97
+ label="Phản hồi bằng giọng nói",
98
+ interactive=False
99
+ )
100
+
101
+ return chatbot, state, user_input, send_button, clear_button, chat_tts_output
102
+ def create_streaming_voice_components() -> tuple:
103
+ """Tạo components cho streaming voice với VAD"""
104
+ with gr.Group():
105
+ gr.Markdown("## 🎤 Trò chuyện giọng nói thời gian thực với VAD")
106
+ gr.Markdown("Hệ thống sẽ tự động phát hiện khi bạn nói và dừng, tạo cuộc hội thoại tự nhiên")
107
+
108
+ with gr.Row():
109
+ with gr.Column(scale=1):
110
+ # Voice activity visualization
111
+ vad_visualizer = gr.HTML(
112
+ value="<div class='vad-visualizer' style='width: 100%;'></div>",
113
+ label="Voice Activity"
114
+ )
115
+
116
+ # Controls
117
+ with gr.Row():
118
+ start_listening_btn = gr.Button(
119
+ "🎙️ Bắt đầu lắng nghe",
120
+ variant="primary",
121
+ size="sm"
122
+ )
123
+ stop_listening_btn = gr.Button(
124
+ "🛑 Dừng lắng nghe",
125
+ variant="secondary",
126
+ size="sm"
127
+ )
128
+
129
+ # Status
130
+ status_display = gr.Textbox(
131
+ label="Trạng thái",
132
+ value="Chưa lắng nghe",
133
+ interactive=False
134
+ )
135
+
136
+ # Conversation state
137
+ state_display = gr.JSON(
138
+ label="Thông tin hội thoại",
139
+ value={}
140
+ )
141
+
142
+ with gr.Column(scale=2):
143
+ # Real-time transcription
144
+ realtime_transcription = gr.Textbox(
145
+ label="🎯 Đang nói...",
146
+ lines=2,
147
+ interactive=False,
148
+ placeholder="Văn bản được chuyển đổi sẽ xuất hiện ở đây..."
149
+ )
150
+
151
+ # AI Response
152
+ ai_response = gr.Textbox(
153
+ label="🤖 Phản hồi AI",
154
+ lines=3,
155
+ interactive=False,
156
+ placeholder="Phản hồi của AI sẽ xuất hiện ở đây..."
157
+ )
158
+
159
+ # TTS Audio output
160
+ tts_output = gr.Audio(
161
+ label="🔊 Phản hồi bằng giọng nói",
162
+ interactive=False,
163
+ autoplay=True
164
+ )
165
+
166
+ # Hidden state components
167
+ streaming_state = gr.State(value=False)
168
+ conversation_history = gr.State(value=[])
169
+
170
+ return (
171
+ start_listening_btn, stop_listening_btn, status_display, state_display,
172
+ realtime_transcription, ai_response, tts_output, streaming_state,
173
+ conversation_history, vad_visualizer
174
+ )
ui/tabs.py ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import threading
3
+ import time
4
+ from services.audio_service import AudioService
5
+ from services.chat_service import ChatService
6
+ from services.image_service import ImageService
7
+ from services.streaming_voice_service import StreamingVoiceService
8
+ from core.rag_system import EnhancedRAGSystem
9
+ from core.tts_service import EnhancedTTSService
10
+ from core.wikipedia_processor import WikipediaProcessor
11
+ from ui.components import create_audio_components, create_chat_components, create_streaming_voice_components
12
+
13
+ def create_all_tabs(audio_service: AudioService, chat_service: ChatService,
14
+ image_service: ImageService, rag_system: EnhancedRAGSystem,
15
+ tts_service: EnhancedTTSService, wikipedia_processor: WikipediaProcessor,
16
+ streaming_voice_service: StreamingVoiceService):
17
+
18
+ with gr.Tab("🎙️ Streaming Voice (VAD)"):
19
+ create_streaming_voice_tab(streaming_voice_service)
20
+
21
+ with gr.Tab("🎙️ Audio"):
22
+ create_audio_tab(audio_service)
23
+
24
+ with gr.Tab("💬 Chat"):
25
+ create_chat_tab(chat_service)
26
+
27
+ with gr.Tab("🖼️ Image"):
28
+ create_image_tab(image_service)
29
+
30
+ with gr.Tab("📚 RAG Wikipedia"):
31
+ create_rag_tab(rag_system, wikipedia_processor)
32
+
33
+ with gr.Tab("🔊 Text-to-Speech"):
34
+ create_tts_tab(tts_service)
35
+
36
+ with gr.Tab("🌐 Language Info"): # NEW TAB
37
+ create_language_info_tab(rag_system.multilingual_manager)
38
+ def create_rag_tab(rag_system: EnhancedRAGSystem, wikipedia_processor: WikipediaProcessor):
39
+ gr.Markdown("## Upload data có sẵn")
40
+
41
+ with gr.Row():
42
+ with gr.Column(scale=1):
43
+ gr.Markdown("### 📤 Upload dữ liệu")
44
+ file_upload = gr.File(
45
+ label="Tải lên file",
46
+ file_types=['.txt', '.csv', '.json'],
47
+ file_count="single"
48
+ )
49
+ upload_btn = gr.Button("📤 Upload Data", variant="primary")
50
+ upload_status = gr.Textbox(label="Trạng thái Upload", interactive=False)
51
+
52
+ gr.Markdown("### 📊 Thống kê Database")
53
+ stats_btn = gr.Button("📊 Database Stats", variant="secondary")
54
+ stats_display = gr.Textbox(label="Thống kê", interactive=False)
55
+
56
+ gr.Markdown("### 🔍 Tìm kiếm Database")
57
+ search_query = gr.Textbox(
58
+ label="Tìm kiếm trong database",
59
+ placeholder="Nhập từ khóa để tìm kiếm..."
60
+ )
61
+ search_btn = gr.Button("🔍 Tìm kiếm", variant="secondary")
62
+
63
+ with gr.Column(scale=2):
64
+ gr.Markdown("### 📋 Kết quả tìm kiếm RAG")
65
+ rag_results = gr.JSON(label="Tài liệu tham khảo tìm được")
66
+
67
+ def upload_wikipedia_file(file):
68
+ if file is None:
69
+ return "Vui lòng chọn file để upload"
70
+
71
+ try:
72
+ documents = wikipedia_processor.process_uploaded_file(file.name)
73
+
74
+ if not documents:
75
+ return "Không tìm thấy dữ liệu nào trong file."
76
+
77
+ metadatas = [{"source": "wikipedia", "type": "knowledge", "file": file.name, "language": "vi"} for _ in documents]
78
+ rag_system.add_documents(documents, metadatas)
79
+
80
+ stats = rag_system.get_collection_stats()
81
+ return f"✅ Đã thêm {len(documents)} documents Wikipedia vào RAG database. Tổng số documents: {stats['count']}"
82
+
83
+ except Exception as e:
84
+ return f"❌ Lỗi xử lý file Wikipedia: {str(e)}"
85
+
86
+ upload_btn.click(upload_wikipedia_file, inputs=[file_upload], outputs=[upload_status])
87
+ stats_btn.click(rag_system.get_collection_stats, inputs=[], outputs=[stats_display])
88
+ search_btn.click(rag_system.semantic_search, inputs=[search_query], outputs=[rag_results])
89
+ def create_audio_tab(audio_service: AudioService):
90
+ gr.Markdown("## Nói chuyện với AI (Đa ngôn ngữ)")
91
+ audio_input, transcription_output, response_output, tts_audio_output, process_button = create_audio_components()
92
+
93
+ # NEW: Language display
94
+ language_display = gr.Textbox(
95
+ label="🌐 Ngôn ngữ phát hiện",
96
+ interactive=False,
97
+ placeholder="Ngôn ngữ sẽ hiển thị ở đây..."
98
+ )
99
+
100
+ process_button.click(
101
+ audio_service.transcribe_audio,
102
+ inputs=audio_input,
103
+ outputs=[transcription_output, response_output, tts_audio_output, language_display] # UPDATED
104
+ )
105
+ def create_image_tab(image_service: ImageService):
106
+ gr.Markdown("## Phân tích hình ảnh")
107
+ with gr.Row():
108
+ image_input = gr.Image(type="numpy", label="Tải lên hình ảnh")
109
+ with gr.Row():
110
+ image_description = gr.Textbox(
111
+ label="Mô tả hình ảnh của bạn (tùy chọn)",
112
+ placeholder="Mô tả ngắn về hình ảnh để AI phân tích chính xác hơn..."
113
+ )
114
+ with gr.Row():
115
+ image_output = gr.Textbox(label="Kết quả phân tích")
116
+ analyze_button = gr.Button("Phân tích hình ảnh", variant="primary")
117
+ analyze_button.click(
118
+ image_service.analyze_image_with_description,
119
+ inputs=[image_input, image_description],
120
+ outputs=[image_output]
121
+ )
122
+ def create_chat_tab(chat_service: ChatService):
123
+ gr.Markdown("## Trò chuyện với AI Assistant (Đa ngôn ngữ)")
124
+ chatbot, state, user_input, send_button, clear_button, chat_tts_output = create_chat_components()
125
+
126
+ # NEW: Language display
127
+ chat_language_display = gr.Textbox(
128
+ label="🌐 Ngôn ngữ phát hiện",
129
+ interactive=False,
130
+ placeholder="Ngôn ngữ sẽ hiển thị ở đây..."
131
+ )
132
+
133
+ send_button.click(
134
+ chat_service.respond,
135
+ inputs=[user_input, state],
136
+ outputs=[user_input, chatbot, state, chat_tts_output, chat_language_display] # UPDATED
137
+ )
138
+ clear_button.click(
139
+ chat_service.clear_chat_history,
140
+ inputs=[state],
141
+ outputs=[chatbot, state]
142
+ )
143
+
144
+ def create_language_info_tab(multilingual_manager): # NEW FUNCTION
145
+ """Tab hiển thị thông tin về hệ thống đa ngôn ngữ"""
146
+ gr.Markdown("## 🌐 Thông tin Hệ thống Đa ngôn ngữ")
147
+
148
+ with gr.Row():
149
+ with gr.Column():
150
+ gr.Markdown("### 🔧 Cấu hình Model")
151
+
152
+ vietnamese_info = multilingual_manager.get_language_info('vi')
153
+ multilingual_info = multilingual_manager.get_language_info('en')
154
+
155
+ gr.Markdown(f"""
156
+ **Tiếng Việt:**
157
+ - Embedding Model: `{vietnamese_info['embedding_model']}`
158
+ - LLM Model: `{vietnamese_info['llm_model']}`
159
+ - Trạng thái: {vietnamese_info['status']}
160
+
161
+ **Đa ngôn ngữ:**
162
+ - Embedding Model: `{multilingual_info['embedding_model']}`
163
+ - LLM Model: `{multilingual_info['llm_model']}`
164
+ - Trạng thái: {multilingual_info['status']}
165
+ """)
166
+
167
+ with gr.Column():
168
+ gr.Markdown("### 🎯 Ngôn ngữ được hỗ trợ")
169
+
170
+ supported_languages = """
171
+ - 🇻🇳 **Tiếng Việt**: Sử dụng model chuyên biệt
172
+ - 🇺🇸 **English**: Sử dụng model đa ngôn ngữ
173
+ - 🇫🇷 **French**: Sử dụng model đa ngôn ngữ
174
+ - 🇪🇸 **Spanish**: Sử dụng model đa ngôn ngữ
175
+ - 🇩🇪 **German**: Sử dụng model đa ngôn ngữ
176
+ - 🇯🇵 **Japanese**: Sử dụng model đa ngôn ngữ
177
+ - 🇰🇷 **Korean**: Sử dụng model đa ngôn ngữ
178
+ - 🇨🇳 **Chinese**: Sử dụng model đa ngôn ngữ
179
+ """
180
+ gr.Markdown(supported_languages)
181
+
182
+ with gr.Row():
183
+ with gr.Column():
184
+ gr.Markdown("### 🔍 Kiểm tra Ngôn ngữ")
185
+ test_text = gr.Textbox(
186
+ label="Nhập văn bản để kiểm tra ngôn ngữ",
187
+ placeholder="Nhập văn bản bằng bất kỳ ngôn ngữ nào..."
188
+ )
189
+ test_button = gr.Button("🔍 Kiểm tra", variant="primary")
190
+
191
+ test_result = gr.JSON(label="Kết quả phát hiện ngôn ngữ")
192
+
193
+ test_button.click(
194
+ lambda text: {
195
+ 'detected_language': multilingual_manager.detect_language(text),
196
+ 'language_info': multilingual_manager.get_language_info(multilingual_manager.detect_language(text)),
197
+ 'embedding_model': multilingual_manager.get_embedding_model(multilingual_manager.detect_language(text)) is not None,
198
+ 'llm_model': multilingual_manager.get_llm_model(multilingual_manager.detect_language(text))
199
+ },
200
+ inputs=[test_text],
201
+ outputs=[test_result]
202
+ )
203
+ def create_tts_tab(tts_service: EnhancedTTSService):
204
+ gr.Markdown("## 🎵 Chuyển văn bản thành giọng nói nâng cao")
205
+ gr.Markdown("Nhập văn bản và chọn ngôn ngữ để chuyển thành giọng nói")
206
+
207
+ with gr.Group():
208
+ with gr.Row():
209
+ tts_text_input = gr.Textbox(
210
+ label="Văn bản cần chuyển thành giọng nói",
211
+ lines=4,
212
+ placeholder="Nhập văn bản tại đây..."
213
+ )
214
+ with gr.Row():
215
+ tts_language = gr.Dropdown(
216
+ choices=["vi", "en", "fr", "es", "de", "ja", "ko", "zh"],
217
+ value="vi",
218
+ label="Ngôn ngữ"
219
+ )
220
+ tts_provider = gr.Dropdown(
221
+ choices=["auto", "gtts", "edgetts"],
222
+ value="auto",
223
+ label="Nhà cung cấp TTS"
224
+ )
225
+ with gr.Row():
226
+ tts_output_audio = gr.Audio(
227
+ label="Kết quả giọng nói",
228
+ interactive=False
229
+ )
230
+ tts_button = gr.Button("🔊 Chuyển thành giọng nói", variant="primary")
231
+
232
+ def text_to_speech_standalone(text, language, tts_provider):
233
+ if not text:
234
+ return None
235
+
236
+ try:
237
+ tts_audio_bytes = tts_service.text_to_speech(text, language, tts_provider)
238
+ if tts_audio_bytes:
239
+ temp_audio_file = tts_service.save_audio_to_file(tts_audio_bytes)
240
+ return temp_audio_file
241
+ except Exception as e:
242
+ print(f"❌ Lỗi TTS: {e}")
243
+
244
+ return None
245
+
246
+ tts_button.click(
247
+ text_to_speech_standalone,
248
+ inputs=[tts_text_input, tts_language, tts_provider],
249
+ outputs=[tts_output_audio]
250
+ )
251
+ def create_streaming_voice_tab(streaming_service: StreamingVoiceService):
252
+ """Tạo tab streaming voice với VAD"""
253
+
254
+ # Create components
255
+ (start_btn, stop_btn, status_display, state_display,
256
+ transcription, ai_response, tts_output, streaming_state,
257
+ conversation_history, vad_visualizer) = create_streaming_voice_components()
258
+
259
+ def start_streaming():
260
+ """Bắt đầu streaming với VAD"""
261
+ def callback_handler(result):
262
+ """Xử lý kết quả real-time"""
263
+ # Cập nhật UI với kết quả mới
264
+ # Note: Trong thực tế, cần sử dụng gr.update() và queue
265
+ print(f"🎯 Kết quả: {result['transcription']}")
266
+ print(f"🤖 Phản hồi: {result['response']}")
267
+
268
+ success = streaming_service.start_listening(callback_handler)
269
+ status = "✅ Đang lắng nghe..." if success else "❌ Lỗi khởi động"
270
+
271
+ # Start background thread for UI updates
272
+ if success:
273
+ threading.Thread(target=update_ui_loop, daemon=True).start()
274
+
275
+ return status, streaming_service.get_conversation_state()
276
+
277
+ def stop_streaming():
278
+ """Dừng streaming"""
279
+ streaming_service.stop_listening()
280
+ return "🛑 Đã dừng lắng nghe", streaming_service.get_conversation_state()
281
+
282
+ def update_ui_loop():
283
+ """Vòng lặp cập nhật UI real-time"""
284
+ while streaming_service.is_listening:
285
+ # Cập nhật trạng thái
286
+ state = streaming_service.get_conversation_state()
287
+
288
+ # Ở đây cần sử dụng gr.update() và queue để cập nhật UI
289
+ # Đây là phiên bản đơn giản, trong thực tế cần tích hợp với Gradio Queue
290
+
291
+ time.sleep(0.1)
292
+
293
+ # Event handlers
294
+ start_btn.click(
295
+ start_streaming,
296
+ outputs=[status_display, state_display]
297
+ )
298
+
299
+ stop_btn.click(
300
+ stop_streaming,
301
+ outputs=[status_display, state_display]
302
+ )
303
+
304
+ # Demo real-time updates (simplified)
305
+ def demo_update():
306
+ """Demo cập nhật real-time"""
307
+ if streaming_service.is_listening:
308
+ state = streaming_service.get_conversation_state()
309
+ return (
310
+ state['current_transcription'] or "Đang lắng nghe...",
311
+ "Phản hồi sẽ xuất hiện ở đây...",
312
+ state
313
+ )
314
+ return "Chưa lắng nghe", "Chưa có phản hồi", {}
315
+
utils/helpers.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import io
3
+ import soundfile as sf
4
+
5
+ def numpy_to_mp3(audio_array: np.ndarray, sampling_rate: int = 24000) -> bytes:
6
+ """Convert numpy array to MP3 bytes"""
7
+ buffer = io.BytesIO()
8
+ sf.write(buffer, audio_array, sampling_rate, format='mp3')
9
+ buffer.seek(0)
10
+ return buffer.read()
11
+
12
+ def validate_api_key(api_key: str) -> bool:
13
+ """Validate Groq API key"""
14
+ return api_key is not None and len(api_key.strip()) > 0