| import gradio as gr |
| import threading |
| import time |
| import os |
| import queue |
| import traceback |
| from typing import List, Tuple |
| from services.audio_service import AudioService |
| from services.chat_service import ChatService |
| from services.image_service import ImageService |
| from services.streaming_voice_service import StreamingVoiceService |
| from services.openai_realtime_service import HybridStreamingService |
| from services.stream_object_detection_service import StreamObjectDetection |
| from services.voice_coding_service import VoiceCodingService |
| from services.sambanova_voice_service import SambanovaVoiceService |
| from services.gemini_realtime_service import GeminiRealtimeService |
| from core.conversational_agent import ConversationalAgent |
| from core.multilingual_manager import MultilingualManager |
| from core.rag_system import EnhancedRAGSystem |
| from core.tts_service import EnhancedTTSService |
| from core.wikipedia_processor import WikipediaProcessor |
| from ui.components import create_audio_components, create_chat_components,create_streaming_voice_components |
|
|
| def create_all_tabs(audio_service: AudioService, chat_service: ChatService, |
| image_service: ImageService, rag_system: EnhancedRAGSystem, |
| tts_service: EnhancedTTSService, wikipedia_processor: WikipediaProcessor, |
| streaming_voice_service: StreamingVoiceService, |
| hybrid_service: HybridStreamingService, |
| voice_coding_service: VoiceCodingService, |
| sambanova_voice_service: SambanovaVoiceService |
| ): |
| multilingual_manager = rag_system.multilingual_manager if hasattr(rag_system, 'multilingual_manager') else None |
| with gr.Tab("🎙️ Streaming Voice "): |
| create_streaming_voice_tab(streaming_voice_service) |
| with gr.Tab("OpenAI Realtime"): |
| create_openai_realtime_tab(hybrid_service) |
| with gr.Tab("GemeniAI RealTime"): |
| create_gemini_realtime_tab() |
| with gr.Tab("SambonovaAI Realtime"): |
| create_sambanova_voice_tab() |
| with gr.Tab("Generation Code"): |
| create_voice_coding_tab(voice_coding_service) |
| with gr.Tab("🎙️ Audio"): |
| create_audio_tab(audio_service) |
| |
| with gr.Tab("💬 Chat"): |
| create_chat_tab(chat_service) |
| |
| |
| |
| |
| with gr.Tab("📚 RAG Wikipedia"): |
| create_rag_tab(rag_system, wikipedia_processor) |
| with gr.Tab("🤖 CAG Chatbot"): |
| |
| if multilingual_manager: |
| create_cag_tab(rag_system, multilingual_manager) |
| else: |
| gr.Markdown("⚠️ Không thể khởi tạo CAG: thiếu multilingual manager") |
| with gr.Tab("🔊 Text-to-Speech"): |
| create_tts_tab(tts_service) |
| |
| with gr.Tab("🌐 Language Info"): |
| create_language_info_tab(rag_system.multilingual_manager) |
| with gr.Tab("Stream Object Detection"): |
| create_streaming_object_detection() |
| def create_cag_tab(rag_system, multilingual_manager): |
| """Tạo tab Cache-Augmented Generation""" |
| |
| |
| from core.cag_system import CAGService |
| |
| |
| cag_service = CAGService(rag_system, multilingual_manager) |
| |
| with gr.Blocks() as cag_tab: |
| gr.Markdown("# 🔄 Cache-Augmented Generation (CAG)") |
| gr.Markdown(""" |
| **CAG** tối ưu hóa RAG bằng caching thông minh: |
| - 🚀 **Tăng tốc độ** 10-100x với cache hit |
| - 💰 **Giảm chi phí** LLM API calls |
| - 🔍 **Semantic cache** cho queries tương tự |
| - 📊 **Performance tracking** chi tiết |
| """) |
| |
| with gr.Row(): |
| with gr.Column(scale=2): |
| |
| gr.Markdown("### 🔍 Tìm kiếm với Cache") |
| |
| cag_query = gr.Textbox( |
| label="Nhập truy vấn", |
| placeholder="Ví dụ: thông tin về Hà Nội hoặc cách cải thiện sức khỏe...", |
| lines=2 |
| ) |
| |
| with gr.Row(): |
| search_btn = gr.Button("🔍 Tìm kiếm (với Cache)", variant="primary") |
| batch_btn = gr.Button("📊 Batch Search", variant="secondary") |
| clear_cache_btn = gr.Button("🗑️ Clear Cache", variant="stop") |
| |
| |
| with gr.Accordion("⚙️ Cấu hình Cache", open=False): |
| with gr.Row(): |
| use_cache = gr.Checkbox(label="Sử dụng Cache", value=True) |
| top_k_slider = gr.Slider(1, 10, value=5, step=1, label="Số kết quả (Top K)") |
| |
| with gr.Row(): |
| semantic_cache = gr.Checkbox(label="Semantic Cache", value=True) |
| cache_ttl = gr.Slider(300, 86400, value=3600, step=300, |
| label="Cache TTL (giây)") |
| |
| with gr.Column(scale=1): |
| |
| gr.Markdown("### 📊 Thống kê Hiệu suất") |
| stats_btn = gr.Button("🔄 Cập nhật Thống kê", variant="secondary") |
| stats_output = gr.JSON(label="Thống kê Cache", show_label=True) |
| |
| |
| with gr.Tab("📝 Kết quả Tìm kiếm"): |
| search_results = gr.JSON(label="Kết quả với Cache Info", show_label=True) |
| |
| with gr.Tab("📈 Performance Analysis"): |
| with gr.Row(): |
| response_time_chart = gr.Plot(label="Thời gian Phản hồi") |
| hit_rate_chart = gr.Plot(label="Cache Hit Rate") |
| |
| performance_table = gr.Dataframe( |
| headers=["Query", "Cache Hit", "Response Time", "Language"], |
| label="Performance Log", |
| interactive=False |
| ) |
| |
| |
| with gr.Accordion("📊 Batch Search Mode", open=False): |
| batch_input = gr.Textbox( |
| label="Nhập nhiều queries (mỗi dòng một query)", |
| placeholder="Query 1\nQuery 2\nQuery 3\n...", |
| lines=6 |
| ) |
| batch_output = gr.JSON(label="Kết quả Batch") |
| |
| |
| with gr.Accordion("🛠️ Quản lý Cache", open=False): |
| with gr.Row(): |
| cache_type = gr.Radio( |
| choices=["all", "memory", "semantic", "disk"], |
| value="memory", |
| label="Loại Cache cần xóa" |
| ) |
| preload_btn = gr.Button("🔥 Pre-load Frequent Queries", variant="secondary") |
| |
| cache_status = gr.Textbox(label="Trạng thái Cache", interactive=False) |
| |
| |
| performance_log = gr.State([]) |
| |
| |
| def perform_cag_search(query, use_cache_flag, top_k): |
| """Thực hiện tìm kiếm với CAG""" |
| if not query.strip(): |
| return {"error": "Vui lòng nhập truy vấn"} |
| |
| |
| cag_service.config.SEMANTIC_SIMILARITY_THRESHOLD = 0.85 if semantic_cache.value else 1.0 |
| |
| start_time = time.time() |
| result = cag_service.search_with_cache( |
| query=query, |
| top_k=top_k, |
| use_cache=use_cache_flag |
| ) |
| elapsed_time = time.time() - start_time |
| |
| |
| if 'performance_log' not in locals(): |
| performance_log = [] |
| |
| performance_log.append({ |
| "query": query[:50], |
| "cache_hit": result["cache_hit"], |
| "hit_type": result["hit_type"], |
| "response_time": result["response_time_ms"], |
| "language": result.get("language", "vi"), |
| "timestamp": time.strftime("%H:%M:%S") |
| }) |
| |
| |
| if len(performance_log) > 20: |
| performance_log.pop(0) |
| |
| return result, performance_log[-10:] |
| |
| def update_cache_stats(): |
| """Cập nhật thống kê cache""" |
| stats = cag_service.get_cache_stats() |
| |
| |
| formatted_stats = { |
| "📊 Tổng quan": { |
| "Tổng queries": stats["total_queries"], |
| "Cache hits": stats["cache_hits"], |
| "Cache misses": stats["cache_misses"], |
| "Hit rate": f"{stats['hit_rate']}%", |
| "Tiết kiệm ước tính": f"${stats['estimated_cost_savings_usd']}" |
| }, |
| "⚡ Hiệu suất": { |
| "Thời gian phản hồi TB": f"{stats['avg_response_time_ms']}ms", |
| "P95 response time": f"{stats['p95_response_time_ms']}ms", |
| "Exact hits": stats["exact_hits"], |
| "Semantic hits": stats["semantic_hits"] |
| }, |
| "💾 Cache Storage": { |
| "Memory cache size": stats["memory_cache_size"], |
| "Semantic cache size": stats["semantic_cache_size"] |
| } |
| } |
| |
| return formatted_stats |
| |
| def clear_cag_cache(cache_type_str): |
| """Xóa cache""" |
| try: |
| cag_service.clear_cache(cache_type_str) |
| return f"✅ Đã xóa {cache_type_str} cache!" |
| except Exception as e: |
| return f"❌ Lỗi: {str(e)}" |
| |
| def batch_cag_search(queries_text, use_cache_flag): |
| """Batch search với CAG""" |
| if not queries_text.strip(): |
| return {"error": "Vui lòng nhập queries"} |
| |
| queries = [q.strip() for q in queries_text.split('\n') if q.strip()] |
| if len(queries) > 20: |
| return {"error": "Tối đa 20 queries mỗi lần"} |
| |
| results = cag_service.batch_search_with_cache(queries, top_k=3) |
| |
| |
| formatted_results = [] |
| cache_hits = 0 |
| |
| for result in results: |
| formatted_result = { |
| "query": result["query"], |
| "cache_hit": result["cache_hit"], |
| "hit_type": result.get("hit_type", "none"), |
| "result_count": len(result.get("results", [])) |
| } |
| |
| if result["cache_hit"]: |
| cache_hits += 1 |
| |
| formatted_results.append(formatted_result) |
| |
| summary = { |
| "total_queries": len(queries), |
| "cache_hits": cache_hits, |
| "cache_miss": len(queries) - cache_hits, |
| "hit_rate": f"{(cache_hits/len(queries))*100:.1f}%" |
| } |
| |
| return { |
| "summary": summary, |
| "results": formatted_results |
| } |
| |
| def preload_frequent_queries(): |
| """Pre-load các queries phổ biến""" |
| frequent_queries = [ |
| "Hà Nội thủ đô Việt Nam", |
| "cách cải thiện sức khỏe", |
| "ăn uống lành mạnh", |
| "tập thể dục đúng cách", |
| "vitamin và khoáng chất", |
| "du lịch Việt Nam", |
| "học tiếng Anh hiệu quả" |
| ] |
| |
| for query in frequent_queries: |
| cag_service.search_with_cache(query, top_k=3, use_cache=False) |
| |
| return f"✅ Đã pre-load {len(frequent_queries)} queries thường dùng" |
| |
| def create_performance_charts(performance_data): |
| """Tạo biểu đồ performance""" |
| import matplotlib.pyplot as plt |
| import matplotlib |
| matplotlib.use('Agg') |
| |
| if not performance_data: |
| return None, None |
| |
| |
| queries = [p["query"] for p in performance_data] |
| response_times = [p["response_time"] for p in performance_data] |
| cache_hits = [1 if p["cache_hit"] else 0 for p in performance_data] |
| |
| |
| fig1, ax1 = plt.subplots(figsize=(10, 4)) |
| bars = ax1.bar(queries, response_times, |
| color=['green' if hit else 'red' for hit in cache_hits]) |
| ax1.set_xlabel('Query') |
| ax1.set_ylabel('Response Time (ms)') |
| ax1.set_title('Response Time with Cache Hits') |
| ax1.set_xticklabels(queries, rotation=45, ha='right') |
| |
| |
| for bar, time_val in zip(bars, response_times): |
| ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 5, |
| f'{time_val:.0f}ms', ha='center', va='bottom', fontsize=8) |
| |
| plt.tight_layout() |
| |
| |
| fig2, ax2 = plt.subplots(figsize=(6, 4)) |
| hit_rate = sum(cache_hits) / len(cache_hits) * 100 if cache_hits else 0 |
| |
| labels = ['Cache Hits', 'Cache Misses'] |
| sizes = [hit_rate, 100 - hit_rate] |
| colors = ['lightgreen', 'lightcoral'] |
| |
| ax2.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90) |
| ax2.axis('equal') |
| ax2.set_title(f'Cache Hit Rate: {hit_rate:.1f}%') |
| |
| plt.tight_layout() |
| |
| return fig1, fig2 |
| |
| |
| search_btn.click( |
| fn=perform_cag_search, |
| inputs=[cag_query, use_cache, top_k_slider], |
| outputs=[search_results, performance_table] |
| ) |
| |
| stats_btn.click( |
| fn=update_cache_stats, |
| inputs=[], |
| outputs=[stats_output] |
| ) |
| |
| clear_cache_btn.click( |
| fn=clear_cag_cache, |
| inputs=[cache_type], |
| outputs=[cache_status] |
| ) |
| |
| batch_btn.click( |
| fn=batch_cag_search, |
| inputs=[batch_input, use_cache], |
| outputs=[batch_output] |
| ) |
| |
| preload_btn.click( |
| fn=preload_frequent_queries, |
| inputs=[], |
| outputs=[cache_status] |
| ) |
| |
| |
| performance_table.change( |
| fn=create_performance_charts, |
| inputs=[performance_table], |
| outputs=[response_time_chart, hit_rate_chart] |
| ) |
| |
| return cag_tab |
| def create_gemini_realtime_tab(): |
| """Tạo tab cho Gemini Realtime API với Audio Streaming""" |
| |
| with gr.Blocks() as gemini_tab: |
| gr.Markdown(""" |
| # 🎯 Gemini Audio Streaming |
| **Trò chuyện thời gian thực bằng giọng nói với Google Gemini** |
| """) |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| |
| with gr.Group(): |
| gr.Markdown("### 🔗 Kết nối") |
| |
| api_key = gr.Textbox( |
| label="Gemini API Key", |
| type="password", |
| placeholder="Nhập API key của bạn...", |
| value=os.getenv("GEMINI_API_KEY", ""), |
| info="Lấy từ https://aistudio.google.com/" |
| ) |
| |
| voice_select = gr.Dropdown( |
| choices=["Puck", "Charon", "Kore", "Fenrir", "Aoede"], |
| value="Puck", |
| label="Giọng nói AI", |
| info="Chọn giọng nói cho Gemini" |
| ) |
| |
| with gr.Row(): |
| connect_btn = gr.Button("🔗 Kết nối Audio", variant="primary") |
| disconnect_btn = gr.Button("🔌 Ngắt kết nối", variant="secondary") |
| |
| |
| with gr.Group(): |
| gr.Markdown("### 📊 Trạng thái") |
| |
| status_display = gr.Textbox( |
| label="Trạng thái", |
| value="Chưa kết nối", |
| interactive=False |
| ) |
| |
| connection_info = gr.Textbox( |
| label="Thông tin", |
| interactive=False, |
| lines=2 |
| ) |
| |
| with gr.Column(scale=2): |
| |
| with gr.Group(): |
| gr.Markdown("### 🎤 Audio Streaming") |
| |
| |
| audio_input = gr.Audio( |
| label="🎤 Nhấn để nói chuyện với Gemini", |
| sources=["microphone"], |
| type="numpy", |
| interactive=True, |
| show_download_button=False |
| ) |
| |
| |
| audio_output = gr.Audio( |
| label="🔊 Gemini trả lời", |
| interactive=False, |
| autoplay=True |
| ) |
| |
| transcription_display = gr.Textbox( |
| label="💬 Nội dung hội thoại", |
| interactive=False, |
| lines=3, |
| placeholder="Nội dung cuộc trò chuyện sẽ hiển thị ở đây..." |
| ) |
| |
| |
| connection_state = gr.State(value=False) |
| gemini_service_state = gr.State(value=None) |
| |
| async def connect_gemini(api_key, voice_name): |
| """Kết nối Gemini Audio Streaming""" |
| try: |
| if not api_key: |
| return False, "❌ Vui lòng nhập API Key", "Chưa kết nối", None |
| |
| service = GeminiRealtimeService(api_key) |
| |
| |
| async def handle_gemini_callback(data): |
| if data['type'] == 'status': |
| gr.Info(data['message']) |
| elif data['type'] == 'text': |
| gr.Info(f"Gemini: {data['content']}") |
| elif data['type'] == 'error': |
| gr.Warning(data['message']) |
| |
| success = await service.start_session( |
| voice_name=voice_name, |
| callback=handle_gemini_callback |
| ) |
| |
| if success: |
| info_msg = f"✅ Đã kết nối Audio Streaming\nGiọng: {voice_name}\nHãy sử dụng micro để trò chuyện" |
| return True, "✅ Đã kết nối Audio", info_msg, service |
| else: |
| return False, "❌ Không thể kết nối audio", "Lỗi kết nối", None |
| |
| except Exception as e: |
| error_msg = f"❌ Lỗi kết nối: {str(e)}" |
| return False, error_msg, f"Lỗi: {str(e)}", None |
| |
| async def disconnect_gemini(service): |
| """Ngắt kết nối""" |
| if service: |
| await service.close() |
| return False, "🔌 Đã ngắt kết nối", "Đã ngắt kết nối audio streaming", None |
| |
| async def process_audio_input(audio_data, sample_rate, service): |
| """Xử lý audio input từ user và trả lời bằng audio""" |
| if not service or not service.is_active: |
| return None, "❌ Chưa kết nối. Vui lòng kết nối audio trước.", "Chưa kết nối" |
| |
| if audio_data is None: |
| return None, "⚠️ Không có audio input", "Không có audio" |
| |
| try: |
| |
| success = await service.send_audio_chunk(audio_data, sample_rate) |
| |
| if not success: |
| return None, "❌ Lỗi gửi audio đến Gemini", "Lỗi gửi audio" |
| |
| |
| audio_response = None |
| max_attempts = 50 |
| |
| for attempt in range(max_attempts): |
| audio_response = await service.receive_audio() |
| if audio_response is not None: |
| break |
| await asyncio.sleep(0.1) |
| |
| if audio_response: |
| resp_sample_rate, resp_audio_data = audio_response |
| |
| |
| with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f: |
| import scipy.io.wavfile as wavfile |
| wavfile.write(f.name, resp_sample_rate, resp_audio_data) |
| audio_path = f.name |
| |
| info_msg = f"✅ Đã nhận phản hồi audio từ Gemini ({(len(resp_audio_data) / resp_sample_rate):.1f}s)" |
| return audio_path, info_msg, "Thành công" |
| else: |
| return None, "⏳ Không nhận được phản hồi audio từ Gemini", "Timeout" |
| |
| except Exception as e: |
| error_msg = f"❌ Lỗi xử lý audio: {str(e)}" |
| return None, error_msg, f"Lỗi: {str(e)}" |
| |
| |
| connect_btn.click( |
| connect_gemini, |
| inputs=[api_key, voice_select], |
| outputs=[connection_state, status_display, connection_info, gemini_service_state] |
| ) |
| |
| disconnect_btn.click( |
| disconnect_gemini, |
| inputs=[gemini_service_state], |
| outputs=[connection_state, status_display, connection_info, gemini_service_state] |
| ) |
| |
| |
| audio_input.stop_recording( |
| process_audio_input, |
| inputs=[audio_input, audio_input, gemini_service_state], |
| outputs=[audio_output, connection_info, transcription_display] |
| ) |
| |
| |
| with gr.Accordion("📖 Hướng dẫn sử dụng Audio Streaming", open=True): |
| gr.Markdown(""" |
| ### 🎯 Cách sử dụng Audio Streaming: |
| |
| 1. **Kết nối**: |
| - Nhập API Key Gemini |
| - Chọn giọng nói |
| - Nhấn **"Kết nối Audio"** |
| |
| 2. **Trò chuyện bằng giọng nói**: |
| - Nhấn nút **Micro** để bắt đầu ghi âm |
| - Nói câu hỏi của bạn |
| - Nhấn **Dừng** để kết thúc ghi âm |
| - Gemini sẽ trả lời bằng giọng nói ngay lập tức |
| |
| ### 🔊 Tính năng: |
| - 🎙️ Real-time voice recognition |
| - 🔊 Real-time audio response |
| - ⚡ Ultra low latency |
| - 🎯 Multiple voice options |
| |
| ### 💡 Mẹo sử dụng: |
| - Sử dụng headset để chất lượng tốt hơn |
| - Nói rõ ràng, không nói quá nhanh |
| - Môi trường yên tĩnh cho kết quả tốt nhất |
| - Mỗi lần ghi âm nên ngắn hơn 30 giây |
| |
| ### 🔧 Lưu ý kỹ thuật: |
| - Cần API Key Gemini có quyền Realtime API |
| - Audio được stream real-time đến Gemini |
| - Phản hồi audio được stream về và phát tự động |
| """) |
| |
| return gemini_tab |
| def setup_gemini_routes(app): |
| """Thiết lập routes FastAPI cho Gemini""" |
| |
| @app.get("/gemini/status") |
| async def get_gemini_status(): |
| return {"status": "active", "service": "gemini_realtime"} |
| |
| @app.post("/gemini/connect") |
| async def connect_gemini(): |
| return {"message": "Gemini connection endpoint"} |
| def create_sambanova_voice_tab(): |
| """Tạo tab Sambanova AI với Voice Input/Output""" |
| |
| |
| try: |
| tts_service = EnhancedTTSService() |
| sambanova_service = SambanovaVoiceService(tts_service=tts_service) |
| print("✅ Tất cả services đã được khởi tạo") |
| except Exception as e: |
| print(f"❌ Lỗi khởi tạo services: {e}") |
| |
| sambanova_service = SambanovaVoiceService() |
| tts_service = None |
| |
| with gr.Blocks() as sambanova_tab: |
| gr.Markdown("## 🤖 Sambanova AI - Voice & Text") |
| gr.Markdown("Trò chuyện với AI - Hỗ trợ voice input/output") |
| |
| |
| chatbot = gr.Chatbot( |
| type="messages", |
| value=[], |
| label="💬 Hội thoại", |
| height=400 |
| ) |
| conversation_state = gr.State(value=[]) |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| |
| model_dropdown = gr.Dropdown( |
| choices=sambanova_service.get_available_models(), |
| value="Meta-Llama-3.1-8B-Instruct", |
| label="Chọn Model" |
| ) |
| |
| |
| language_dropdown = gr.Dropdown( |
| choices=['vi', 'en', 'ja', 'ko', 'zh', 'fr', 'es', 'de'], |
| value='vi', |
| label="Ngôn ngữ TTS", |
| visible=tts_service is not None |
| ) |
| |
| |
| text_input = gr.Textbox( |
| label="Tin nhắn của bạn", |
| placeholder="Nhập tin nhắn hoặc sử dụng voice...", |
| lines=3 |
| ) |
| |
| |
| with gr.Group(): |
| gr.Markdown("**🎤 Voice Input**") |
| audio_input = gr.Audio( |
| sources=["microphone"], |
| type="numpy", |
| label="Nói tin nhắn của bạn", |
| show_download_button=False |
| ) |
| |
| |
| with gr.Row(): |
| temperature = gr.Slider(0, 1, value=0.1, label="Temperature") |
| top_p = gr.Slider(0, 1, value=0.1, label="Top-P") |
| |
| |
| voice_output_toggle = gr.Checkbox( |
| label="🔊 Bật Voice Output", |
| value=True, |
| visible=tts_service is not None |
| ) |
| |
| |
| with gr.Row(): |
| send_text_btn = gr.Button("🚀 Gửi Text", variant="primary") |
| send_voice_btn = gr.Button("🎤 Gửi Voice", variant="primary") |
| clear_btn = gr.Button("🗑️ Xóa", variant="secondary") |
| |
| |
| status = gr.Textbox( |
| label="Trạng thái", |
| value="✅ Sẵn sàng", |
| interactive=False |
| ) |
| |
| with gr.Column(scale=1): |
| |
| if tts_service is not None: |
| gr.Markdown("### 🔊 Voice Output") |
| audio_output = gr.Audio( |
| label="Giọng nói AI", |
| autoplay=False, |
| visible=True |
| ) |
| else: |
| audio_output = gr.Audio(visible=False) |
| gr.Markdown("### ℹ️ Thông tin") |
| gr.Markdown(""" |
| **Voice output tạm thời không khả dụng** |
| - Vẫn có thể sử dụng voice input |
| - Vẫn có thể chat bằng text |
| """) |
| |
| |
| response_display = gr.Textbox( |
| label="Phản hồi từ AI", |
| lines=6, |
| interactive=False |
| ) |
| |
| |
| def process_text_message(text, history, state, model, language, temp, top_p_val, voice_enabled): |
| """Xử lý tin nhắn text""" |
| if not text or not text.strip(): |
| return history, state, "❌ Vui lòng nhập tin nhắn", "", gr.update(visible=False) |
| |
| try: |
| |
| user_msg = {"role": "user", "content": text} |
| new_history = history + [user_msg] |
| new_state = state + [user_msg] |
| |
| |
| yield new_history, new_state, "⏳ Đang xử lý...", "", gr.update(visible=False) |
| |
| |
| ai_text = sambanova_service.generate_response(new_state, model, temp, top_p_val) |
| ai_msg = {"role": "assistant", "content": ai_text} |
| |
| final_history = new_history + [ai_msg] |
| final_state = new_state + [ai_msg] |
| |
| |
| audio_update = gr.update(visible=False) |
| if voice_enabled and tts_service is not None: |
| audio_file = sambanova_service.text_to_speech(ai_text, language) |
| if audio_file: |
| audio_update = gr.update(value=audio_file, visible=True) |
| |
| yield final_history, final_state, "✅ Hoàn thành", ai_text, audio_update |
| |
| except Exception as e: |
| error_msg = f"❌ Lỗi: {str(e)}" |
| yield history, state, error_msg, "", gr.update(visible=False) |
| |
| def process_voice_message(audio, history, state, model, language, temp, top_p_val, voice_enabled): |
| """Xử lý tin nhắn voice""" |
| if audio is None: |
| return history, state, "❌ Vui lòng ghi âm tin nhắn", "", gr.update(visible=False) |
| |
| try: |
| |
| yield history, state, "🎤 Đang chuyển speech thành text...", "", gr.update(visible=False) |
| |
| |
| text = sambanova_service.speech_to_text(audio) |
| if not text: |
| yield history, state, "❌ Không nhận dạng được giọng nói", "", gr.update(visible=False) |
| return |
| |
| |
| user_audio_msg = {"role": "user", "content": gr.Audio(audio)} |
| user_text_msg = {"role": "user", "content": text} |
| |
| new_history = history + [user_audio_msg] |
| new_state = state + [user_text_msg] |
| |
| yield new_history, new_state, "⏳ Đang xử lý voice message...", "", gr.update(visible=False) |
| |
| |
| ai_text = sambanova_service.generate_response(new_state, model, temp, top_p_val) |
| ai_msg = {"role": "assistant", "content": ai_text} |
| |
| final_history = new_history + [ai_msg] |
| final_state = new_state + [ai_msg] |
| |
| |
| audio_update = gr.update(visible=False) |
| if voice_enabled and tts_service is not None: |
| audio_file = sambanova_service.text_to_speech(ai_text, language) |
| if audio_file: |
| audio_update = gr.update(value=audio_file, visible=True) |
| |
| yield final_history, final_state, "✅ Voice message hoàn thành", ai_text, audio_update |
| |
| except Exception as e: |
| error_msg = f"❌ Lỗi voice: {str(e)}" |
| yield history, state, error_msg, "", gr.update(visible=False) |
| |
| def clear_conversation(): |
| """Xóa hội thoại""" |
| return [], [], "🔄 Đã xóa hội thoại", "", gr.update(visible=False) |
| |
| |
| send_text_btn.click( |
| fn=process_text_message, |
| inputs=[ |
| text_input, chatbot, conversation_state, |
| model_dropdown, language_dropdown, temperature, top_p, voice_output_toggle |
| ], |
| outputs=[chatbot, conversation_state, status, response_display, audio_output] |
| ).then( |
| lambda: "", |
| outputs=[text_input] |
| ) |
| |
| send_voice_btn.click( |
| fn=process_voice_message, |
| inputs=[ |
| audio_input, chatbot, conversation_state, |
| model_dropdown, language_dropdown, temperature, top_p, voice_output_toggle |
| ], |
| outputs=[chatbot, conversation_state, status, response_display, audio_output] |
| ).then( |
| lambda: None, |
| outputs=[audio_input] |
| ) |
| |
| clear_btn.click( |
| fn=clear_conversation, |
| outputs=[chatbot, conversation_state, status, response_display, audio_output] |
| ) |
| |
| return sambanova_tab |
|
|
| def check_environment(): |
| """Kiểm tra môi trường trước khi chạy""" |
| print("🔍 Kiểm tra môi trường...") |
| |
| |
| api_key = os.environ.get("SAMBANOVA_API_KEY") |
| if not api_key: |
| print("❌ SAMBANOVA_API_KEY không được tìm thấy") |
| print("💡 Hãy set environment variable: export SAMBANOVA_API_KEY=your_key") |
| return False |
| else: |
| print("✅ SAMBANOVA_API_KEY: OK") |
| |
| |
| try: |
| import fastrtc |
| print("✅ FastRTC: OK") |
| except ImportError: |
| print("❌ FastRTC chưa được cài đặt") |
| return False |
| |
| try: |
| import gtts |
| print("✅ gTTS: OK") |
| except ImportError: |
| print("❌ gTTS chưa được cài đặt") |
| |
| try: |
| import edge_tts |
| print("✅ edge-tts: OK") |
| except ImportError: |
| print("❌ edge-tts chưa được cài đặt") |
| |
| return True |
| def create_voice_coding_tab(voice_coding_service): |
| """Tạo tab Voice Coding đơn giản - Text-based trước""" |
| |
| with gr.Blocks() as coding_tab: |
| gr.Markdown("## 🦙 Voice Coding - Lập trình bằng AI") |
| gr.Markdown("Tạo và chỉnh sửa ứng dụng HTML single-file với AI Assistant") |
| |
| |
| history = gr.State([{"role": "system", "content": "You are an AI coding assistant. Help create HTML applications."}]) |
| current_code = gr.State("") |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| gr.Markdown("### 🎯 Hướng dẫn sử dụng:") |
| gr.Markdown(""" |
| **Nhập yêu cầu lập trình:** |
| - "Tạo trang web hello world" |
| - "Tạo calculator bằng HTML/CSS/JS" |
| - "Tạo đồng hồ digital" |
| - "Tạo form đăng ký với validation" |
| |
| **Chức năng voice đang được phát triển** |
| """) |
| |
| |
| text_input = gr.Textbox( |
| label="Yêu cầu lập trình", |
| placeholder="Ví dụ: Tạo trang web hello world với màu nền xanh và chữ màu trắng...", |
| lines=3 |
| ) |
| |
| with gr.Row(): |
| generate_btn = gr.Button("🚀 Generate Code", variant="primary", scale=2) |
| clear_btn = gr.Button("🗑️ Clear", variant="secondary", scale=1) |
| |
| |
| status_display = gr.Textbox( |
| label="Trạng thái", |
| value="Sẵn sàng...", |
| interactive=False |
| ) |
| |
| |
| with gr.Accordion("🎤 Voice Input (Experimental)", open=False): |
| gr.Markdown("Chức năng voice đang được phát triển...") |
| |
| |
| with gr.Column(scale=2): |
| with gr.Tabs(): |
| with gr.Tab("🎮 Sandbox Preview"): |
| sandbox = gr.HTML( |
| value=voice_coding_service.sandbox_html, |
| label="Live Preview" |
| ) |
| |
| with gr.Tab("📄 Code Editor"): |
| code_display = gr.Code( |
| language="html", |
| label="Generated HTML Code", |
| lines=25, |
| interactive=True, |
| value="" |
| ) |
| |
| with gr.Tab("💬 Chat History"): |
| chat_display = gr.Chatbot( |
| type="messages", |
| label="Lịch sử hội thoại", |
| height=400 |
| ) |
| |
| |
| def generate_code(text, current_history, current_code_value): |
| """Generate code từ text input""" |
| if not text.strip(): |
| return current_history, current_code_value, current_history, "❌ Vui lòng nhập yêu cầu", voice_coding_service.sandbox_html |
| |
| try: |
| |
| user_prompt = f"Create a single-file HTML application for: {text}. Current code: {current_code_value}. Respond with complete HTML code only." |
| |
| |
| new_history = current_history + [ |
| {"role": "user", "content": user_prompt} |
| ] |
| |
| |
| response = voice_coding_service.groq_client.chat.completions.create( |
| model="llama-3.1-8b-instant", |
| messages=new_history, |
| temperature=0.7, |
| max_tokens=1024, |
| top_p=0.9, |
| stream=False, |
| ) |
| |
| output = response.choices[0].message.content |
| |
| |
| html_code = voice_coding_service.extract_html_content(output) |
| |
| |
| new_history.append({"role": "assistant", "content": output}) |
| |
| |
| sandbox_html = voice_coding_service.display_in_sandbox(html_code) |
| |
| return new_history, html_code, new_history, "✅ Đã generate code thành công!", sandbox_html |
| |
| except Exception as e: |
| error_msg = f"❌ Lỗi: {str(e)}" |
| return current_history, current_code_value, current_history, error_msg, voice_coding_service.sandbox_html |
| |
| def update_sandbox(code): |
| """Cập nhật sandbox khi code thay đổi""" |
| return voice_coding_service.display_in_sandbox(code) |
| |
| def clear_all(): |
| """Xóa tất cả""" |
| empty_history = [{"role": "system", "content": "You are an AI coding assistant."}] |
| return empty_history, "", empty_history, "Đã xóa tất cả", voice_coding_service.sandbox_html |
| |
| def clear_text(): |
| """Xóa text input""" |
| return "" |
| |
| |
| generate_btn.click( |
| generate_code, |
| inputs=[text_input, history, current_code], |
| outputs=[history, current_code, chat_display, status_display, sandbox] |
| ).then( |
| clear_text, |
| outputs=[text_input] |
| ) |
| |
| code_display.change( |
| update_sandbox, |
| inputs=[code_display], |
| outputs=[sandbox] |
| ) |
| |
| clear_btn.click( |
| clear_all, |
| outputs=[history, current_code, chat_display, status_display, sandbox] |
| ) |
| |
| return coding_tab |
| def create_openai_realtime_tab(hybrid_service: HybridStreamingService): |
| """Tạo tab cho OpenAI Realtime API""" |
| |
| with gr.Blocks() as openai_tab: |
| gr.Markdown("## OpenAI Realtime API - Streaming Chất Lượng Cao") |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| |
| mode_selector = gr.Radio( |
| choices=["local", "openai", "auto"], |
| value="auto", |
| label="Chế độ nhận diện", |
| info="Local: VOSK (nhanh), OpenAI: Chất lượng cao" |
| ) |
| |
| start_btn = gr.Button("🎙️ Bắt đầu Streaming", variant="primary") |
| stop_btn = gr.Button("🛑 Dừng", variant="secondary") |
| |
| status_display = gr.Textbox( |
| label="Trạng thái", |
| value="Chưa kết nối", |
| interactive=False |
| ) |
| |
| |
| with gr.Accordion("⚙️ Cài đặt OpenAI", open=False): |
| api_key = gr.Textbox( |
| label="OpenAI API Key", |
| type="password", |
| placeholder="Nhập API key...", |
| info="Cần cho chế độ OpenAI Realtime" |
| ) |
| |
| language_select = gr.Dropdown( |
| choices=["vi", "en", "fr", "es", "de", "ja", "zh"], |
| value="vi", |
| label="Ngôn ngữ" |
| ) |
| |
| with gr.Column(scale=2): |
| chatbot = gr.Chatbot( |
| label="💬 Hội thoại", |
| type="messages", |
| height=400 |
| ) |
| |
| transcription_display = gr.Textbox( |
| label="🎤 Bạn nói", |
| interactive=False, |
| lines=2 |
| ) |
| |
| audio_output = gr.Audio( |
| label="🔊 Phản hồi AI", |
| interactive=False, |
| autoplay=True |
| ) |
| |
| |
| connection_state = gr.State(value=False) |
| |
| async def start_streaming(mode, api_key, language, history): |
| """Bắt đầu streaming với mode đã chọn""" |
| try: |
| |
| if api_key and not hybrid_service.openai_service: |
| hybrid_service.openai_service = OpenAIRealtimeService(api_key) |
| |
| success = await hybrid_service.start_listening( |
| speech_callback=lambda x: None, |
| mode=mode |
| ) |
| |
| if success: |
| return True, f"✅ Đã kết nối - Chế độ: {mode}", history |
| else: |
| return False, "❌ Không thể kết nối", history |
| |
| except Exception as e: |
| return False, f"❌ Lỗi: {str(e)}", history |
| |
| def stop_streaming(): |
| """Dừng streaming""" |
| hybrid_service.stop_listening() |
| return False, "🛑 Đã dừng streaming", [] |
| |
| def update_chat(history, message, role="user"): |
| """Cập nhật chat history""" |
| if role == "user": |
| history.append({"role": "user", "content": message}) |
| else: |
| history.append({"role": "assistant", "content": message}) |
| return history |
| |
| |
| start_btn.click( |
| start_streaming, |
| inputs=[mode_selector, api_key, language_select, chatbot], |
| outputs=[connection_state, status_display, chatbot] |
| ) |
| |
| stop_btn.click( |
| stop_streaming, |
| outputs=[connection_state, status_display, chatbot] |
| ) |
| |
| |
| openai_tab.load( |
| fn=None, |
| inputs=[], |
| outputs=[], |
| js=""" |
| function setupEventSource() { |
| const eventSource = new EventSource('/outputs'); |
| eventSource.onmessage = function(event) { |
| const data = JSON.parse(event.data); |
| // Handle real-time updates from OpenAI |
| console.log('OpenAI event:', data); |
| }; |
| } |
| setupEventSource(); |
| """ |
| ) |
| |
| return openai_tab |
| def create_streaming_object_detection(): |
| with gr.Blocks() as object_detection_tab: |
| gr.HTML( |
| """ |
| <h1 style='text-align:center'> |
| 🎥 Real-time Video Object Detection with <a href='https://huggingface.co/PekingU/rtdetr_r50vd' target='_blank'>RT-DETR</a> |
| </h1> |
| <p style='text-align:center'>Upload a short video and watch detection stream in real-time!</p> |
| """ |
| ) |
| |
| with gr.Row(): |
| with gr.Column(): |
| video = gr.Video(label="Video Input") |
| conf = gr.Slider( |
| minimum=0.0, |
| maximum=1.0, |
| value=0.3, |
| step=0.05, |
| label="Confidence Threshold" |
| ) |
| with gr.Column(): |
| output = gr.Video(label="Processed Video", streaming=True, autoplay=True) |
| |
| video.upload( |
| fn=StreamObjectDetection.stream_object_detection, |
| inputs=[video, conf], |
| outputs=[output], |
| ) |
| return object_detection_tab |
| |
| def create_rag_tab(rag_system: EnhancedRAGSystem, wikipedia_processor: WikipediaProcessor): |
| """Tạo tab RAG với debug chi tiết""" |
| |
| |
| if rag_system is None: |
| rag_system = EnhancedRAGSystem() |
| if wikipedia_processor is None: |
| wikipedia_processor = WikipediaProcessor() |
| |
| with gr.Blocks() as rag_tab: |
| gr.Markdown("## 📚 Upload Dữ Liệu Wikipedia") |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| gr.Markdown("### 📤 Upload Dữ Liệu") |
| file_upload = gr.File( |
| label="Tải lên file (TXT, CSV, JSON)", |
| file_types=['.txt', '.csv', '.json'], |
| file_count="single" |
| ) |
| upload_btn = gr.Button("📤 Upload Data", variant="primary") |
| upload_status = gr.Textbox( |
| label="Trạng thái Upload", |
| interactive=False, |
| lines=5 |
| ) |
| |
| gr.Markdown("### 📊 Thống kê Database") |
| stats_btn = gr.Button("📊 Database Stats", variant="secondary") |
| stats_display = gr.Textbox( |
| label="Thống kê", |
| interactive=False, |
| lines=6 |
| ) |
| |
| with gr.Column(scale=2): |
| gr.Markdown("### 🔍 Tìm kiếm & Kiểm tra") |
| search_query = gr.Textbox( |
| label="Tìm kiếm trong database", |
| placeholder="Nhập từ khóa để kiểm tra dữ liệu..." |
| ) |
| search_btn = gr.Button("🔍 Tìm kiếm", variant="secondary") |
| rag_results = gr.JSON( |
| label="Kết quả tìm kiếm", |
| show_label=True |
| ) |
| |
| def upload_wikipedia_file(file): |
| """Xử lý upload file với debug đầy đủ""" |
| if file is None: |
| return "❌ Vui lòng chọn file để upload" |
| |
| try: |
| print(f"🔄 Bắt đầu upload file: {file.name}") |
| |
| |
| if not os.path.exists(file.name): |
| return f"❌ File không tồn tại: {file.name}" |
| |
| |
| documents = wikipedia_processor.process_uploaded_file(file.name) |
| |
| if not documents: |
| return "❌ Không thể trích xuất dữ liệu từ file. File có thể trống hoặc định dạng không đúng." |
| |
| print(f"✅ Đã xử lý {len(documents)} documents") |
| |
| |
| metadatas = [] |
| for i, doc in enumerate(documents): |
| metadata = { |
| "source": "uploaded_file", |
| "type": "knowledge", |
| "file_name": os.path.basename(file.name), |
| "language": "vi", |
| "doc_id": i, |
| "length": len(doc) |
| } |
| metadatas.append(metadata) |
| |
| |
| old_stats = rag_system.get_collection_stats() |
| old_count = old_stats['total_documents'] |
| |
| rag_system.add_documents(documents, metadatas) |
| |
| |
| new_stats = rag_system.get_collection_stats() |
| new_count = new_stats['total_documents'] |
| |
| success_msg = f""" |
| ✅ UPLOAD THÀNH CÔNG! |
| 📁 File: {os.path.basename(file.name)} |
| 📄 Documents xử lý: {len(documents)} |
| 📊 Documents thêm vào: {new_count - old_count} |
| 🏷️ Tổng documents: {new_count} |
| 🔤 Embeddings: {new_stats['embedding_count']} |
| 🌐 Ngôn ngữ: {new_stats['language_distribution']} |
| 💡 Bạn có thể tìm kiếm ngay để kiểm tra dữ liệu! |
| """ |
| return success_msg |
| |
| except Exception as e: |
| error_msg = f"❌ LỖI UPLOAD: {str(e)}" |
| print(f"UPLOAD ERROR: {traceback.format_exc()}") |
| return error_msg |
| |
| def get_rag_stats(): |
| """Lấy thống kê chi tiết""" |
| try: |
| stats = rag_system.get_collection_stats() |
| return f""" |
| 📊 THỐNG KÊ RAG DATABASE: |
| • 📄 Tổng documents: {stats['total_documents']} |
| • 🔤 Số embeddings: {stats['embedding_count']} |
| • 📐 Dimension: {stats['embedding_dimension']} |
| • 🌐 Phân bố ngôn ngữ: {stats['language_distribution']} |
| • ✅ Trạng thái: {stats['status']} |
| • 🏷️ Tên: {stats['name']} |
| 💡 Embeddings: {'Có' if stats['has_embeddings'] else 'Không'} |
| """ |
| except Exception as e: |
| return f"❌ Lỗi lấy thống kê: {str(e)}" |
| |
| def search_rag_database(query): |
| """Tìm kiếm để kiểm tra dữ liệu""" |
| if not query.strip(): |
| return [{"message": "Nhập từ khóa để tìm kiếm"}] |
| |
| try: |
| results = rag_system.semantic_search(query, top_k=3) |
| |
| if not results: |
| return [{"message": "Không tìm thấy kết quả nào", "query": query}] |
| |
| return results |
| |
| except Exception as e: |
| return [{"error": f"Lỗi tìm kiếm: {str(e)}"}] |
| |
| |
| upload_btn.click(upload_wikipedia_file, inputs=[file_upload], outputs=[upload_status]) |
| stats_btn.click(get_rag_stats, inputs=[], outputs=[stats_display]) |
| search_btn.click(search_rag_database, inputs=[search_query], outputs=[rag_results]) |
| |
| return rag_tab |
| def create_audio_tab(audio_service: AudioService): |
| gr.Markdown("## Nói chuyện với AI (Đa ngôn ngữ)") |
| audio_input, transcription_output, response_output, tts_audio_output, process_button = create_audio_components() |
| |
| |
| language_display = gr.Textbox( |
| label="🌐 Ngôn ngữ phát hiện", |
| interactive=False, |
| placeholder="Ngôn ngữ sẽ hiển thị ở đây..." |
| ) |
| |
| process_button.click( |
| audio_service.transcribe_audio, |
| inputs=audio_input, |
| outputs=[transcription_output, response_output, tts_audio_output, language_display] |
| ) |
| def create_streaming_voice_tab(streaming_service: StreamingVoiceService): |
| """Tạo tab streaming voice với VAD optimized - FIXED VERSION""" |
| |
| with gr.Blocks() as streaming_tab: |
| gr.Markdown("## 🎤 Trò chuyện giọng nói thời gian thực - Tối ưu hóa") |
| |
| |
| vad_result_state = gr.State(value=None) |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| |
| with gr.Row(): |
| start_btn = gr.Button("🎙️ Bắt đầu VAD", variant="primary") |
| stop_btn = gr.Button("🛑 Dừng VAD", variant="secondary") |
| clear_btn = gr.Button("🗑️ Xóa hội thoại") |
| |
| gr.Markdown("### Chế độ tự động (VAD)") |
| gr.Markdown("Hệ thống tự động nhận diện khi bạn bắt đầu nói") |
| |
| with gr.Row(): |
| vad_status = gr.Textbox( |
| label="Trạng thái VAD", |
| value="Chưa bắt đầu", |
| interactive=False |
| ) |
| |
| |
| status_display = gr.Textbox( |
| label="🎯 Trạng thái hiện tại", |
| value="Đang chờ...", |
| interactive=False, |
| lines=2 |
| ) |
| |
| gr.Markdown("### Chế độ thủ công") |
| microphone = gr.Microphone( |
| label="🎤 Nhấn để nói thủ công", |
| type="numpy", |
| streaming=True |
| ) |
| |
| with gr.Accordion("📊 Performance Metrics", open=False): |
| latency_display = gr.JSON( |
| label="Latency Statistics", |
| value={} |
| ) |
| refresh_latency_btn = gr.Button("🔄 Refresh Metrics", size="sm") |
| |
| with gr.Column(scale=2): |
| |
| transcription_box = gr.Textbox( |
| label="📝 Bạn đang nói (real-time)", |
| lines=3, |
| interactive=False, |
| value="Nói gì đó để bắt đầu..." |
| ) |
| |
| |
| response_box = gr.Textbox( |
| label="🤖 Phản hồi AI", |
| lines=5, |
| interactive=False, |
| value="Tôi sẽ trả lời bạn ở đây..." |
| ) |
| |
| audio_output = gr.Audio( |
| label="🔊 Giọng nói AI", |
| interactive=False, |
| autoplay=True |
| ) |
| |
| |
| is_vad_active = gr.State(value=False) |
| last_vad_update = gr.State(value=0) |
| |
| |
| vad_results_queue = queue.Queue() |
| |
| def vad_callback(result): |
| """Callback khi VAD phát hiện speech""" |
| print(f"🎯 VAD Callback: {result.get('transcription', 'No text')}") |
| vad_results_queue.put(result) |
| |
| def start_vad(): |
| """Bắt đầu VAD""" |
| try: |
| |
| success = streaming_service.start_listening(vad_callback) |
| |
| if success: |
| is_vad_active.value = True |
| status = "✅ VAD đang chạy - Hãy nói gì đó!" |
| |
| |
| if streaming_service.speech_callback: |
| streaming_service.speech_callback({ |
| 'transcription': "VAD đã sẵn sàng! Hãy nói...", |
| 'response': "", |
| 'tts_audio': None, |
| 'status': 'listening' |
| }) |
| |
| state = streaming_service.get_conversation_state() |
| state_text = f"✅ VAD: Đang hoạt động\nQueue: {state['queue_size']}\nThreads: {state['worker_threads']}" |
| status_msg = "🎤 Đang lắng nghe... nói đi!" |
| |
| else: |
| status = "❌ Không thể khởi động VAD" |
| state_text = "Lỗi khởi động" |
| status_msg = "Lỗi!" |
| |
| return status, state_text, status_msg |
| |
| except Exception as e: |
| print(f"❌ Lỗi start_vad: {e}") |
| return "❌ Lỗi khởi động", f"Lỗi: {e}", "Lỗi!" |
| |
| def stop_vad(): |
| """Dừng VAD""" |
| try: |
| streaming_service.stop_listening() |
| is_vad_active.value = False |
| |
| |
| while not vad_results_queue.empty(): |
| try: |
| vad_results_queue.get_nowait() |
| except: |
| pass |
| |
| state = streaming_service.get_conversation_state() |
| state_text = f"🛑 VAD: Đã dừng\nHistory: {state['history_length']} messages" |
| status_msg = "Đã dừng lắng nghe" |
| |
| return "🛑 VAD đã dừng", state_text, status_msg |
| |
| except Exception as e: |
| print(f"❌ Lỗi stop_vad: {e}") |
| return "Lỗi!", f"Lỗi: {e}", "Lỗi!" |
| |
| def process_microphone(audio_data): |
| """Xử lý microphone input manual mode""" |
| if audio_data is None: |
| return "Chưa có âm thanh", "Hãy nói gì đó...", None, "VAD: Tắt", "Manual mode" |
| |
| try: |
| print(f"🎤 Manual audio: {len(audio_data[1])} samples") |
| |
| |
| result = streaming_service.process_streaming_audio(audio_data) |
| |
| state = streaming_service.get_conversation_state() |
| state_text = f"Manual mode\nHistory: {state['history_length']} messages" |
| |
| |
| if result['status'] == 'processing': |
| status_msg = "⏳ Đang xử lý..." |
| elif result['status'] == 'listening': |
| status_msg = "🎤 Đang nghe..." |
| else: |
| status_msg = result['status'] |
| |
| return result['transcription'], result['response'], result['tts_audio'], state_text, status_msg |
| |
| except Exception as e: |
| print(f"❌ Lỗi process_microphone: {e}") |
| return f"Lỗi: {e}", "Xin lỗi, có lỗi xảy ra", None, "Lỗi xử lý", "Lỗi!" |
| |
| def check_vad_results(): |
| """Kiểm tra và hiển thị kết quả VAD""" |
| try: |
| |
| if not is_vad_active.value: |
| return gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip() |
| |
| |
| try: |
| result = vad_results_queue.get_nowait() |
| |
| print(f"📥 Got VAD result: {result.get('transcription', 'No text')}") |
| |
| state = streaming_service.get_conversation_state() |
| state_text = f"VAD mode\nQueue: {state['queue_size']}\nThreads: {state['worker_threads']}" |
| |
| |
| if result.get('status') == 'processing': |
| status_msg = "⏳ Đang xử lý VAD..." |
| elif result.get('status') == 'partial': |
| status_msg = "🎤 Đang nhận diện..." |
| elif result.get('status') == 'completed': |
| status_msg = "✅ Đã xử lý xong" |
| else: |
| status_msg = result.get('status', 'Đang lắng nghe') |
| |
| return ( |
| result.get('transcription', ''), |
| result.get('response', ''), |
| result.get('tts_audio', None), |
| state_text, |
| status_msg |
| ) |
| |
| except queue.Empty: |
| |
| return gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip() |
| |
| except Exception as e: |
| print(f"❌ Lỗi check_vad_results: {e}") |
| return gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip() |
| |
| def clear_chat(): |
| """Xóa hội thoại""" |
| streaming_service.clear_conversation() |
| state = streaming_service.get_conversation_state() |
| state_text = f"✅ Đã xóa hội thoại\nHistory: {state['history_length']} messages" |
| status_msg = "Sẵn sàng" |
| return "", "", None, state_text, status_msg |
| |
| def refresh_latency(): |
| """Làm mới latency metrics""" |
| try: |
| stats = streaming_service.get_latency_stats() |
| return stats |
| except Exception as e: |
| print(f"❌ Lỗi refresh_latency: {e}") |
| return {} |
| |
| def update_status_info(): |
| """Cập nhật thông tin trạng thái""" |
| try: |
| state = streaming_service.get_conversation_state() |
| |
| formatted_state = f"🎯 VAD: {'✅ Đang chạy' if state['is_listening'] else '❌ Dừng'}\n" |
| formatted_state += f"📊 Queue: {state['queue_size']}\n" |
| formatted_state += f"📝 History: {state['history_length']} messages\n" |
| formatted_state += f"🧵 Threads: {state['worker_threads']}\n" |
| formatted_state += f"⏰ Last: {state['last_update']}" |
| |
| |
| latency_info = streaming_service.get_latency_stats() |
| |
| |
| if state['is_listening']: |
| current_status = "🎤 Đang lắng nghe... nói đi!" |
| else: |
| current_status = "🛑 Đã dừng" |
| |
| return formatted_state, latency_info, current_status |
| |
| except Exception as e: |
| print(f"❌ Lỗi update_status_info: {e}") |
| return f"Lỗi: {e}", {}, "Lỗi!" |
|
|
| |
| start_btn.click( |
| start_vad, |
| outputs=[vad_status, status_display, transcription_box] |
| ) |
| |
| stop_btn.click( |
| stop_vad, |
| outputs=[vad_status, status_display, transcription_box] |
| ) |
| |
| |
| microphone.stream( |
| process_microphone, |
| inputs=[microphone], |
| outputs=[transcription_box, response_box, audio_output, status_display, vad_status] |
| ) |
| |
| clear_btn.click( |
| clear_chat, |
| outputs=[transcription_box, response_box, audio_output, status_display, vad_status] |
| ) |
|
|
| refresh_latency_btn.click( |
| refresh_latency, |
| outputs=[latency_display] |
| ) |
| |
| |
| timer_component = gr.Timer(0.5) |
| |
| |
| timer_component.tick( |
| fn=check_vad_results, |
| outputs=[transcription_box, response_box, audio_output, status_display, vad_status] |
| ) |
| |
| |
| info_timer = gr.Timer(2.0) |
| |
| info_timer.tick( |
| fn=update_status_info, |
| outputs=[status_display, latency_display, vad_status] |
| ) |
| |
| return streaming_tab |
| def create_image_tab(image_service: ImageService): |
| """Tạo tab phân tích hình ảnh với OCR và LLM""" |
| |
| with gr.Blocks() as image_tab: |
| gr.Markdown("## 🖼️ Phân tích hình ảnh & Trích xuất văn bản") |
| gr.Markdown(""" |
| ### 🔍 Chức năng: |
| - **OCR đa ngôn ngữ**: Trích xuất văn bản từ ảnh (Tiếng Việt, Anh, Nhật, Hàn, Trung, ...) |
| - **Phân tích AI**: Sử dụng LLM để phân tích nội dung và ngữ cảnh |
| - **Hỗ trợ nhiều định dạng**: Tài liệu, ảnh chụp, meme, screenshot |
| |
| ### 📝 Hướng dẫn: |
| 1. Tải lên hình ảnh có chứa văn bản |
| 2. (Tùy chọn) Mô tả hình ảnh để AI phân tích chính xác hơn |
| 3. Nhấn "Phân tích hình ảnh" để xem kết quả |
| """) |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| |
| image_input = gr.Image( |
| type="numpy", |
| label="🖼️ Tải lên hình ảnh", |
| height=300 |
| ) |
| |
| |
| image_description = gr.Textbox( |
| label="📝 Mô tả hình ảnh (tùy chọn)", |
| placeholder="Ví dụ: Đây là hóa đơn mua hàng, ảnh chụp menu nhà hàng, văn bản tiếng Việt...", |
| lines=3 |
| ) |
| |
| |
| analyze_button = gr.Button( |
| "🔍 Phân tích hình ảnh", |
| variant="primary", |
| size="lg" |
| ) |
| |
| |
| clear_button = gr.Button("🗑️ Xóa", variant="secondary") |
| |
| with gr.Column(scale=2): |
| |
| image_output = gr.Textbox( |
| label="📊 Kết quả phân tích", |
| lines=15, |
| max_lines=20, |
| show_copy_button=True |
| ) |
| |
| def analyze_image(image, description): |
| """Xử lý phân tích ảnh""" |
| if image is None: |
| return "❌ Vui lòng tải lên hình ảnh trước khi phân tích." |
| |
| return image_service.analyze_image_with_description(image, description) |
| |
| def clear_all(): |
| """Xóa tất cả input và output""" |
| return None, "", "" |
| |
| |
| analyze_button.click( |
| analyze_image, |
| inputs=[image_input, image_description], |
| outputs=[image_output] |
| ) |
| |
| clear_button.click( |
| clear_all, |
| outputs=[image_input, image_description, image_output] |
| ) |
| |
| return image_tab |
| def create_chat_tab(chat_service: ChatService): |
| gr.Markdown("## Trò chuyện với AI Assistant (Đa ngôn ngữ)") |
| |
| chatbot, state, user_input, send_button, clear_button, chat_tts_output = create_chat_components() |
| |
| |
| chat_language_display = gr.Textbox( |
| label="🌐 Ngôn ngữ phát hiện", |
| interactive=False, |
| placeholder="Ngôn ngữ sẽ hiển thị ở đây..." |
| ) |
| |
| |
| send_button.click( |
| fn=chat_service.respond, |
| inputs=[user_input, state], |
| outputs=[user_input, chatbot, state, chat_tts_output, chat_language_display] |
| ) |
| |
| clear_button.click( |
| fn=chat_service.clear_chat_history, |
| inputs=[state], |
| outputs=[chatbot, state] |
| ) |
| |
| |
| user_input.submit( |
| fn=chat_service.respond, |
| inputs=[user_input, state], |
| outputs=[user_input, chatbot, state, chat_tts_output, chat_language_display] |
| ) |
|
|
| def create_language_info_tab(multilingual_manager): |
| """Tab hiển thị thông tin về hệ thống đa ngôn ngữ""" |
| gr.Markdown("## 🌐 Thông tin Hệ thống Đa ngôn ngữ") |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### 🔧 Cấu hình Model") |
| |
| vietnamese_info = multilingual_manager.get_language_info('vi') |
| multilingual_info = multilingual_manager.get_language_info('en') |
| |
| |
| gr.Markdown(f""" |
| **Tiếng Việt:** |
| - Embedding Model: `{vietnamese_info['embedding_model']}` |
| - LLM Model: `{vietnamese_info['llm_model']}` |
| - Trạng thái: {vietnamese_info['embedding_status']} |
| |
| **Đa ngôn ngữ:** |
| - Embedding Model: `{multilingual_info['embedding_model']}` |
| - LLM Model: `{multilingual_info['llm_model']}` |
| - Trạng thái: {multilingual_info['embedding_status']} |
| """) |
| |
| with gr.Column(): |
| gr.Markdown("### 🎯 Ngôn ngữ được hỗ trợ") |
| |
| supported_languages = """ |
| - 🇻🇳 **Tiếng Việt**: Sử dụng model chuyên biệt |
| - 🇺🇸 **English**: Sử dụng model đa ngôn ngữ |
| - 🇫🇷 **French**: Sử dụng model đa ngôn ngữ |
| - 🇪🇸 **Spanish**: Sử dụng model đa ngôn ngữ |
| - 🇩🇪 **German**: Sử dụng model đa ngôn ngữ |
| - 🇯🇵 **Japanese**: Sử dụng model đa ngôn ngữ |
| - 🇰🇷 **Korean**: Sử dụng model đa ngôn ngữ |
| - 🇨🇳 **Chinese**: Sử dụng model đa ngôn ngữ |
| """ |
| gr.Markdown(supported_languages) |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.Markdown("### 🔍 Kiểm tra Ngôn ngữ") |
| test_text = gr.Textbox( |
| label="Nhập văn bản để kiểm tra ngôn ngữ", |
| placeholder="Nhập văn bản bằng bất kỳ ngôn ngữ nào..." |
| ) |
| test_button = gr.Button("🔍 Kiểm tra", variant="primary") |
| |
| test_result = gr.JSON(label="Kết quả phát hiện ngôn ngữ") |
| |
| test_button.click( |
| lambda text: { |
| 'detected_language': multilingual_manager.detect_language(text), |
| 'language_info': multilingual_manager.get_language_info(multilingual_manager.detect_language(text)), |
| 'embedding_model': multilingual_manager.get_embedding_model(multilingual_manager.detect_language(text)) is not None, |
| 'llm_model': multilingual_manager.get_llm_model(multilingual_manager.detect_language(text)) |
| }, |
| inputs=[test_text], |
| outputs=[test_result] |
| ) |
| def create_tts_tab(tts_service: EnhancedTTSService): |
| gr.Markdown("## 🎵 Chuyển văn bản thành giọng nói nâng cao") |
| gr.Markdown("Nhập văn bản và chọn ngôn ngữ để chuyển thành giọng nói") |
| |
| with gr.Group(): |
| with gr.Row(): |
| tts_text_input = gr.Textbox( |
| label="Văn bản cần chuyển thành giọng nói", |
| lines=4, |
| placeholder="Nhập văn bản tại đây..." |
| ) |
| with gr.Row(): |
| tts_language = gr.Dropdown( |
| choices=["vi", "en", "fr", "es", "de", "ja", "ko", "zh"], |
| value="vi", |
| label="Ngôn ngữ" |
| ) |
| tts_provider = gr.Dropdown( |
| choices=["auto", "gtts", "edgetts"], |
| value="auto", |
| label="Nhà cung cấp TTS" |
| ) |
| with gr.Row(): |
| tts_output_audio = gr.Audio( |
| label="Kết quả giọng nói", |
| interactive=False |
| ) |
| tts_button = gr.Button("🔊 Chuyển thành giọng nói", variant="primary") |
| |
| def text_to_speech_standalone(text, language, tts_provider): |
| if not text: |
| return None |
| |
| try: |
| tts_audio_bytes = tts_service.text_to_speech(text, language, tts_provider) |
| if tts_audio_bytes: |
| temp_audio_file = tts_service.save_audio_to_file(tts_audio_bytes) |
| return temp_audio_file |
| except Exception as e: |
| print(f"❌ Lỗi TTS: {e}") |
| |
| return None |
| |
| tts_button.click( |
| text_to_speech_standalone, |
| inputs=[tts_text_input, tts_language, tts_provider], |
| outputs=[tts_output_audio] |
| ) |
|
|
| |