Spaces:
Running
on
Zero
Running
on
Zero
| """ | |
| TTS Server Manager - Giao diện quản lý server với điều chỉnh thông số real-time | |
| """ | |
| import tkinter as tk | |
| from tkinter import ttk, messagebox | |
| import requests | |
| import threading | |
| import time | |
| import json | |
| from datetime import datetime | |
| import subprocess | |
| import os | |
| import signal | |
| class ServerManager: | |
| def __init__(self, root): | |
| self.root = root | |
| self.root.title("VieNeu-TTS Server Manager") | |
| self.root.geometry("800x600") | |
| # Server config | |
| self.server_url = "http://127.0.0.1:8000" | |
| self.server_process = None | |
| # Current settings | |
| self.current_settings = { | |
| "gpu_semaphore": 2, | |
| "cpu_semaphore": 4, | |
| "io_semaphore": 6, | |
| "thread_pool": 6 | |
| } | |
| self.setup_ui() | |
| self.start_monitoring() | |
| def setup_ui(self): | |
| # Main notebook | |
| notebook = ttk.Notebook(self.root) | |
| notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) | |
| # Server Control Tab | |
| self.setup_server_tab(notebook) | |
| # Settings Tab | |
| self.setup_settings_tab(notebook) | |
| # Monitor Tab | |
| self.setup_monitor_tab(notebook) | |
| def setup_server_tab(self, notebook): | |
| """Tab điều khiển server""" | |
| server_frame = ttk.Frame(notebook) | |
| notebook.add(server_frame, text="Server Control") | |
| # Server status | |
| status_frame = ttk.LabelFrame(server_frame, text="Server Status", padding="10") | |
| status_frame.pack(fill=tk.X, pady=(0, 10)) | |
| self.status_label = ttk.Label(status_frame, text="Checking server...", font=("Arial", 12)) | |
| self.status_label.pack() | |
| # Server controls | |
| control_frame = ttk.LabelFrame(server_frame, text="Server Controls", padding="10") | |
| control_frame.pack(fill=tk.X, pady=(0, 10)) | |
| button_frame = ttk.Frame(control_frame) | |
| button_frame.pack() | |
| self.start_btn = ttk.Button(button_frame, text="Start Server", command=self.start_server) | |
| self.start_btn.pack(side=tk.LEFT, padx=(0, 10)) | |
| self.stop_btn = ttk.Button(button_frame, text="Stop Server", command=self.stop_server, state="disabled") | |
| self.stop_btn.pack(side=tk.LEFT, padx=(0, 10)) | |
| self.restart_btn = ttk.Button(button_frame, text="Restart Server", command=self.restart_server) | |
| self.restart_btn.pack(side=tk.LEFT) | |
| # Quick actions | |
| quick_frame = ttk.LabelFrame(server_frame, text="Quick Actions", padding="10") | |
| quick_frame.pack(fill=tk.X, pady=(0, 10)) | |
| ttk.Button(quick_frame, text="Open API Docs", command=self.open_api_docs).pack(side=tk.LEFT, padx=(0, 10)) | |
| ttk.Button(quick_frame, text="Test Server", command=self.test_server).pack(side=tk.LEFT, padx=(0, 10)) | |
| ttk.Button(quick_frame, text="Clear Cache", command=self.clear_cache).pack(side=tk.LEFT) | |
| def setup_settings_tab(self, notebook): | |
| """Tab cài đặt async parameters""" | |
| settings_frame = ttk.Frame(notebook) | |
| notebook.add(settings_frame, text="Async Settings") | |
| # Current settings display | |
| current_frame = ttk.LabelFrame(settings_frame, text="Current Settings", padding="10") | |
| current_frame.pack(fill=tk.X, pady=(0, 10)) | |
| self.current_settings_text = tk.Text(current_frame, height=4, width=60, state="disabled") | |
| self.current_settings_text.pack() | |
| # Settings controls | |
| control_frame = ttk.LabelFrame(settings_frame, text="Adjust Settings", padding="10") | |
| control_frame.pack(fill=tk.X, pady=(0, 10)) | |
| # GPU Semaphore | |
| gpu_frame = ttk.Frame(control_frame) | |
| gpu_frame.pack(fill=tk.X, pady=(0, 5)) | |
| ttk.Label(gpu_frame, text="GPU Semaphore (1-4):").pack(side=tk.LEFT) | |
| self.gpu_var = tk.StringVar(value="2") | |
| gpu_spin = ttk.Spinbox(gpu_frame, from_=1, to=4, width=10, textvariable=self.gpu_var) | |
| gpu_spin.pack(side=tk.RIGHT) | |
| # CPU Semaphore | |
| cpu_frame = ttk.Frame(control_frame) | |
| cpu_frame.pack(fill=tk.X, pady=(0, 5)) | |
| ttk.Label(cpu_frame, text="CPU Semaphore (2-8):").pack(side=tk.LEFT) | |
| self.cpu_var = tk.StringVar(value="4") | |
| cpu_spin = ttk.Spinbox(cpu_frame, from_=2, to=8, width=10, textvariable=self.cpu_var) | |
| cpu_spin.pack(side=tk.RIGHT) | |
| # I/O Semaphore | |
| io_frame = ttk.Frame(control_frame) | |
| io_frame.pack(fill=tk.X, pady=(0, 5)) | |
| ttk.Label(io_frame, text="I/O Semaphore (3-10):").pack(side=tk.LEFT) | |
| self.io_var = tk.StringVar(value="6") | |
| io_spin = ttk.Spinbox(io_frame, from_=3, to=10, width=10, textvariable=self.io_var) | |
| io_spin.pack(side=tk.RIGHT) | |
| # Thread Pool | |
| thread_frame = ttk.Frame(control_frame) | |
| thread_frame.pack(fill=tk.X, pady=(0, 10)) | |
| ttk.Label(thread_frame, text="Thread Pool (2-12):").pack(side=tk.LEFT) | |
| self.thread_var = tk.StringVar(value="6") | |
| thread_spin = ttk.Spinbox(thread_frame, from_=2, to=12, width=10, textvariable=self.thread_var) | |
| thread_spin.pack(side=tk.RIGHT) | |
| # Apply buttons | |
| apply_frame = ttk.Frame(control_frame) | |
| apply_frame.pack(fill=tk.X) | |
| ttk.Button(apply_frame, text="Apply Settings", command=self.apply_settings).pack(side=tk.LEFT, padx=(0, 10)) | |
| ttk.Button(apply_frame, text="Reset to Default", command=self.reset_settings).pack(side=tk.LEFT, padx=(0, 10)) | |
| ttk.Button(apply_frame, text="Load Presets", command=self.show_presets).pack(side=tk.LEFT) | |
| # Presets | |
| presets_frame = ttk.LabelFrame(settings_frame, text="Performance Presets", padding="10") | |
| presets_frame.pack(fill=tk.X) | |
| preset_buttons = ttk.Frame(presets_frame) | |
| preset_buttons.pack() | |
| ttk.Button(preset_buttons, text="Light (1,2,3,3)", command=lambda: self.apply_preset(1,2,3,3)).pack(side=tk.LEFT, padx=(0, 5)) | |
| ttk.Button(preset_buttons, text="Balanced (2,4,6,6)", command=lambda: self.apply_preset(2,4,6,6)).pack(side=tk.LEFT, padx=(0, 5)) | |
| ttk.Button(preset_buttons, text="Performance (3,6,8,8)", command=lambda: self.apply_preset(3,6,8,8)).pack(side=tk.LEFT, padx=(0, 5)) | |
| ttk.Button(preset_buttons, text="Max (4,8,10,12)", command=lambda: self.apply_preset(4,8,10,12)).pack(side=tk.LEFT) | |
| def setup_monitor_tab(self, notebook): | |
| """Tab monitor server""" | |
| monitor_frame = ttk.Frame(notebook) | |
| notebook.add(monitor_frame, text="Monitor") | |
| # Resource usage | |
| resource_frame = ttk.LabelFrame(monitor_frame, text="Resource Usage", padding="10") | |
| resource_frame.pack(fill=tk.X, pady=(0, 10)) | |
| self.resource_text = tk.Text(resource_frame, height=8, width=70, state="disabled") | |
| self.resource_text.pack() | |
| # Recent requests | |
| requests_frame = ttk.LabelFrame(monitor_frame, text="Recent Activity", padding="10") | |
| requests_frame.pack(fill=tk.BOTH, expand=True) | |
| self.activity_text = tk.Text(requests_frame, height=15, width=70, state="disabled") | |
| scrollbar = ttk.Scrollbar(requests_frame, orient="vertical", command=self.activity_text.yview) | |
| self.activity_text.configure(yscrollcommand=scrollbar.set) | |
| self.activity_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) | |
| scrollbar.pack(side=tk.RIGHT, fill=tk.Y) | |
| # Auto refresh | |
| refresh_frame = ttk.Frame(monitor_frame) | |
| refresh_frame.pack(fill=tk.X, pady=(10, 0)) | |
| self.auto_refresh_var = tk.BooleanVar(value=True) | |
| ttk.Checkbutton(refresh_frame, text="Auto Refresh", variable=self.auto_refresh_var).pack(side=tk.LEFT) | |
| ttk.Button(refresh_frame, text="Refresh Now", command=self.refresh_monitor).pack(side=tk.RIGHT) | |
| def start_server(self): | |
| """Start TTS server""" | |
| try: | |
| if self.server_process and self.server_process.poll() is None: | |
| messagebox.showwarning("Warning", "Server is already running!") | |
| return | |
| self.log_activity("Starting TTS server...") | |
| # Start server process | |
| self.server_process = subprocess.Popen( | |
| ["python", "tts_server.py"], | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.STDOUT, | |
| text=True, | |
| bufsize=1, | |
| universal_newlines=True | |
| ) | |
| self.start_btn.config(state="disabled") | |
| self.stop_btn.config(state="normal") | |
| # Monitor server output | |
| threading.Thread(target=self.monitor_server_output, daemon=True).start() | |
| self.log_activity("Server starting... Please wait for initialization.") | |
| except Exception as e: | |
| messagebox.showerror("Error", f"Failed to start server: {str(e)}") | |
| def stop_server(self): | |
| """Stop TTS server""" | |
| try: | |
| if self.server_process: | |
| self.log_activity("Stopping TTS server...") | |
| self.server_process.terminate() | |
| self.server_process.wait(timeout=10) | |
| self.server_process = None | |
| self.start_btn.config(state="normal") | |
| self.stop_btn.config(state="disabled") | |
| self.log_activity("Server stopped.") | |
| except Exception as e: | |
| messagebox.showerror("Error", f"Failed to stop server: {str(e)}") | |
| def restart_server(self): | |
| """Restart server""" | |
| self.stop_server() | |
| time.sleep(2) | |
| self.start_server() | |
| def monitor_server_output(self): | |
| """Monitor server console output""" | |
| if not self.server_process: | |
| return | |
| try: | |
| for line in iter(self.server_process.stdout.readline, ''): | |
| if line: | |
| # Only log important messages, not all console output | |
| if any(keyword in line for keyword in ['✅', '❌', '🎤', '📊', 'ERROR', 'WARNING']): | |
| self.log_activity(f"Server: {line.strip()}") | |
| if self.server_process.poll() is not None: | |
| break | |
| except Exception as e: | |
| self.log_activity(f"Error monitoring server: {str(e)}") | |
| def apply_settings(self): | |
| """Apply new async settings""" | |
| try: | |
| new_settings = { | |
| "gpu_semaphore": int(self.gpu_var.get()), | |
| "cpu_semaphore": int(self.cpu_var.get()), | |
| "io_semaphore": int(self.io_var.get()), | |
| "thread_pool": int(self.thread_var.get()) | |
| } | |
| # Send settings to server via API | |
| response = requests.post(f"{self.server_url}/admin/update_settings", json=new_settings, timeout=5) | |
| if response.status_code == 200: | |
| self.current_settings = new_settings | |
| self.update_current_settings_display() | |
| self.log_activity(f"Settings applied: GPU({new_settings['gpu_semaphore']}) CPU({new_settings['cpu_semaphore']}) I/O({new_settings['io_semaphore']}) Threads({new_settings['thread_pool']})") | |
| messagebox.showinfo("Success", "Settings applied successfully!") | |
| else: | |
| messagebox.showerror("Error", f"Failed to apply settings: {response.text}") | |
| except requests.exceptions.RequestException: | |
| messagebox.showwarning("Warning", "Server not responding. Settings will be applied on next restart.") | |
| except Exception as e: | |
| messagebox.showerror("Error", f"Failed to apply settings: {str(e)}") | |
| def apply_preset(self, gpu, cpu, io, threads): | |
| """Apply preset configuration""" | |
| self.gpu_var.set(str(gpu)) | |
| self.cpu_var.set(str(cpu)) | |
| self.io_var.set(str(io)) | |
| self.thread_var.set(str(threads)) | |
| self.apply_settings() | |
| def reset_settings(self): | |
| """Reset to default settings""" | |
| self.gpu_var.set("2") | |
| self.cpu_var.set("4") | |
| self.io_var.set("6") | |
| self.thread_var.set("6") | |
| def show_presets(self): | |
| """Show preset explanations""" | |
| presets_info = """ | |
| Performance Presets: | |
| • Light (1,2,3,3): Minimal resource usage, good for testing | |
| • Balanced (2,4,6,6): Default configuration, good performance | |
| • Performance (3,6,8,8): High performance, more resource usage | |
| • Max (4,8,10,12): Maximum performance, highest resource usage | |
| Note: Higher values = better performance but more resource usage | |
| """ | |
| messagebox.showinfo("Presets Info", presets_info) | |
| def check_server_status(self): | |
| """Check server status""" | |
| try: | |
| response = requests.get(f"{self.server_url}/health", timeout=3) | |
| if response.status_code == 200: | |
| data = response.json() | |
| status_text = f"✅ Server Online | Model: {data.get('model_status', 'unknown')}" | |
| if 'async_resources' in data: | |
| async_info = data['async_resources'] | |
| status_text += f" | GPU: {async_info.get('gpu_available', 0)}" | |
| status_text += f" | CPU: {async_info.get('cpu_available', 0)}" | |
| status_text += f" | I/O: {async_info.get('io_available', 0)}" | |
| self.status_label.config(text=status_text, foreground="green") | |
| return True | |
| else: | |
| self.status_label.config(text="❌ Server Error", foreground="red") | |
| return False | |
| except Exception: | |
| self.status_label.config(text="❌ Server Offline", foreground="red") | |
| return False | |
| def update_current_settings_display(self): | |
| """Update current settings display""" | |
| settings_text = f"""GPU Semaphore: {self.current_settings['gpu_semaphore']} (concurrent GPU tasks) | |
| CPU Semaphore: {self.current_settings['cpu_semaphore']} (concurrent CPU tasks) | |
| I/O Semaphore: {self.current_settings['io_semaphore']} (concurrent I/O tasks) | |
| Thread Pool: {self.current_settings['thread_pool']} (worker threads)""" | |
| self.current_settings_text.config(state="normal") | |
| self.current_settings_text.delete(1.0, tk.END) | |
| self.current_settings_text.insert(1.0, settings_text) | |
| self.current_settings_text.config(state="disabled") | |
| def refresh_monitor(self): | |
| """Refresh monitor data""" | |
| try: | |
| # Get detailed status | |
| response = requests.get(f"{self.server_url}/status", timeout=3) | |
| if response.status_code == 200: | |
| data = response.json() | |
| resource_info = "=== RESOURCE USAGE ===\n" | |
| if 'async_processing' in data: | |
| async_data = data['async_processing'] | |
| for resource, info in async_data.items(): | |
| usage_pct = (info['in_use'] / info['max_capacity']) * 100 | |
| resource_info += f"{resource}: {info['in_use']}/{info['max_capacity']} ({usage_pct:.0f}% used)\n" | |
| if 'model_info' in data: | |
| model_info = data['model_info'] | |
| resource_info += f"\nModel: {model_info.get('device', 'unknown')}\n" | |
| resource_info += f"Cache: {model_info.get('reference_cache_size', 0)} voices cached\n" | |
| resource_info += f"\nLast Updated: {datetime.now().strftime('%H:%M:%S')}" | |
| self.resource_text.config(state="normal") | |
| self.resource_text.delete(1.0, tk.END) | |
| self.resource_text.insert(1.0, resource_info) | |
| self.resource_text.config(state="disabled") | |
| except requests.exceptions.RequestException: | |
| # Don't log connection errors to avoid spam | |
| pass | |
| except Exception as e: | |
| self.log_activity(f"Monitor error: {str(e)}") | |
| def clear_monitor_display(self): | |
| """Clear monitor display when server is offline""" | |
| offline_info = "=== SERVER OFFLINE ===\n\nServer is not running.\nUse 'Start Server' button to launch the server.\n\nMonitoring will resume when server is online." | |
| self.resource_text.config(state="normal") | |
| self.resource_text.delete(1.0, tk.END) | |
| self.resource_text.insert(1.0, offline_info) | |
| self.resource_text.config(state="disabled") | |
| def log_activity(self, message): | |
| """Log activity to monitor""" | |
| timestamp = datetime.now().strftime("%H:%M:%S") | |
| log_message = f"[{timestamp}] {message}\n" | |
| self.activity_text.config(state="normal") | |
| self.activity_text.insert(tk.END, log_message) | |
| self.activity_text.see(tk.END) | |
| self.activity_text.config(state="disabled") | |
| # Keep only last 100 lines | |
| lines = self.activity_text.get(1.0, tk.END).split('\n') | |
| if len(lines) > 100: | |
| self.activity_text.config(state="normal") | |
| self.activity_text.delete(1.0, f"{len(lines)-100}.0") | |
| self.activity_text.config(state="disabled") | |
| def start_monitoring(self): | |
| """Start monitoring loop""" | |
| def monitor_loop(): | |
| while True: | |
| server_online = self.check_server_status() | |
| # Only refresh monitor if server is online and auto refresh is enabled | |
| if server_online and self.auto_refresh_var.get(): | |
| self.refresh_monitor() | |
| elif not server_online: | |
| # Clear monitor display when server is offline | |
| self.clear_monitor_display() | |
| time.sleep(10) # Update every 10 seconds to reduce spam | |
| threading.Thread(target=monitor_loop, daemon=True).start() | |
| # Initial updates | |
| self.update_current_settings_display() | |
| def open_api_docs(self): | |
| """Open API documentation""" | |
| import webbrowser | |
| webbrowser.open(f"{self.server_url}/docs") | |
| def test_server(self): | |
| """Quick server test""" | |
| try: | |
| test_data = { | |
| "text": "Test server connection", | |
| "voice_choice": "Tuyên (nam miền Bắc)", | |
| "speed_factor": 1.0 | |
| } | |
| self.log_activity("Testing server...") | |
| response = requests.post(f"{self.server_url}/tts", json=test_data, timeout=30) | |
| if response.status_code == 200: | |
| result = response.json() | |
| self.log_activity(f"✅ Server test successful! Processing time: {result.get('processing_time', 0):.2f}s") | |
| else: | |
| self.log_activity(f"❌ Server test failed: HTTP {response.status_code}") | |
| except Exception as e: | |
| self.log_activity(f"❌ Server test error: {str(e)}") | |
| def clear_cache(self): | |
| """Clear server cache""" | |
| try: | |
| response = requests.post(f"{self.server_url}/admin/clear_cache", timeout=5) | |
| if response.status_code == 200: | |
| self.log_activity("✅ Cache cleared successfully") | |
| else: | |
| self.log_activity("❌ Failed to clear cache") | |
| except Exception as e: | |
| self.log_activity(f"❌ Cache clear error: {str(e)}") | |
| def main(): | |
| root = tk.Tk() | |
| app = ServerManager(root) | |
| root.mainloop() | |
| if __name__ == "__main__": | |
| main() |