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

Deploy Gradio VoiceBot lên Hugging Face

Browse files
Files changed (2) hide show
  1. app.py +1211 -0
  2. requirements.txt +11 -0
app.py ADDED
@@ -0,0 +1,1211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ numpy
3
+ soundfile
4
+ Pillow
5
+ pandas
6
+ sentence-transformers
7
+ faiss-cpu
8
+ edge-tts
9
+ gtts
10
+ groq
11
+