Deploy Gradio VoiceBot lên Hugging Face
Browse files- app.py +1211 -0
- 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 |
+
|