""" Dashboard GUI for Eugen Bot Live monitoring interface using PySimpleGUI """ import PySimpleGUI as sg import threading from datetime import datetime from queue import Queue class Dashboard: """Live monitoring dashboard for bot activity""" def __init__(self, bot=None): """ Initialize dashboard Args: bot: Reference to the main bot instance """ self.bot = bot sg.theme('DarkBlue3') # Event queue for thread-safe updates self.event_queue = Queue() self.window = None self.is_running = False # Statistics self.stats = { "messages": 0, "api_calls": 0, "errors": 0, "start_time": datetime.now() } def log_event(self, event_type, data): """ Add event to queue for display Args: event_type (str): Type of event (chat_message, api_call, etc.) data (dict): Event data """ timestamp = datetime.now().strftime("%H:%M:%S") if event_type == "chat_message": msg = f"{timestamp} | {data['username']}: {data['content']}" self.stats["messages"] += 1 elif event_type == "mention_detected": msg = f"{timestamp} | [MENTION] Bot addressed by {data['username']}" elif event_type == "api_call": msg = f"{timestamp} | [API] → Perplexity ({data.get('model', 'sonar-pro')})" self.stats["api_calls"] += 1 elif event_type == "api_response": preview = data['content'][:60] + "..." if len(data['content']) > 60 else data['content'] msg = f"{timestamp} | [RESPONSE] {preview}" elif event_type == "bot_response": preview = data['content'][:60] + "..." if len(data['content']) > 60 else data['content'] msg = f"{timestamp} | Eugen: @{data['username']} {preview}" elif event_type == "error": msg = f"{timestamp} | ❌ ERROR: {data['error']}" self.stats["errors"] += 1 elif event_type == "info": msg = f"{timestamp} | ℹ {data['message']}" elif event_type == "warning": msg = f"{timestamp} | ⚠ {data['message']}" elif event_type == "context_loaded": msg = f"{timestamp} | [CONTEXT] Loaded {data['count']} messages for {data['username']}" else: msg = f"{timestamp} | {event_type}: {data}" self.event_queue.put(msg) def create_layout(self): """Create the GUI layout""" # Header header = [ [sg.Text("EUGEN BOT - LIVE DASHBOARD", font=("Arial", 16, "bold"))], [sg.Text("Status: 🟢 RUNNING", key="-STATUS-", font=("Arial", 10))], [sg.Text("Uptime: 00:00:00", key="-UPTIME-", size=(20, 1)), sg.Text("Messages: 0", key="-MSG-COUNT-", size=(20, 1)), sg.Text("API Calls: 0", key="-API-COUNT-", size=(20, 1)), sg.Text("Errors: 0", key="-ERROR-COUNT-", size=(20, 1))], [sg.HorizontalSeparator()] ] # Live feed feed = [ [sg.Text("LIVE ACTIVITY FEED", font=("Arial", 12, "bold"))], [sg.Multiline( size=(140, 25), key="-LOG-", disabled=True, autoscroll=True, font=("Courier New", 9) )] ] # Control buttons controls = [ [sg.HorizontalSeparator()], [ sg.Button("Clear Log", key="-CLEAR-"), sg.Button("Reset Stats", key="-RESET-"), sg.Button("Stop Bot", key="-STOP-", button_color=("white", "red")) ] ] # Combine all sections layout = header + feed + controls return layout def run(self): """Run the dashboard GUI in main thread""" layout = self.create_layout() self.window = sg.Window( "Eugen Bot Dashboard", layout, finalize=True, size=(1000, 650) ) self.is_running = True # Main event loop while self.is_running: event, values = self.window.read(timeout=100) # Handle window close if event == sg.WINDOW_CLOSED or event == "-STOP-": self.is_running = False if self.bot: self.bot.stop() break # Handle clear log if event == "-CLEAR-": self.window["-LOG-"].update("") # Handle reset stats if event == "-RESET-": self.stats = { "messages": 0, "api_calls": 0, "errors": 0, "start_time": datetime.now() } # Update log with queued events while not self.event_queue.empty(): try: msg = self.event_queue.get_nowait() self.window["-LOG-"].print(msg) except: break # Update statistics self._update_stats() self.window.close() def _update_stats(self): """Update statistics display""" if not self.window: return # Calculate uptime uptime = datetime.now() - self.stats["start_time"] hours, remainder = divmod(int(uptime.total_seconds()), 3600) minutes, seconds = divmod(remainder, 60) uptime_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}" # Update displays self.window["-UPTIME-"].update(f"Uptime: {uptime_str}") self.window["-MSG-COUNT-"].update(f"Messages: {self.stats['messages']}") self.window["-API-COUNT-"].update(f"API Calls: {self.stats['api_calls']}") self.window["-ERROR-COUNT-"].update(f"Errors: {self.stats['errors']}") def show_error(self, title, message): """Show error popup""" sg.popup_error(message, title=title) def show_info(self, title, message): """Show info popup""" sg.popup(message, title=title) def stop(self): """Stop the dashboard""" self.is_running = False if self.window: self.window.close() class SetupWizard: """Configuration wizard for first-time setup""" def __init__(self): sg.theme('DarkBlue3') def run(self): """ Run the setup wizard Returns: dict: Configuration values or None if cancelled """ layout = [ [sg.Text("EUGEN CONFIGURATION WIZARD", font=("Arial", 16, "bold"))], [sg.HorizontalSeparator()], [sg.Text("TWITCH CONFIGURATION", font=("Arial", 12, "bold"))], [sg.Text("Bot Nickname:", size=(20, 1)), sg.Input("Eugen", key="-BOT-NAME-")], [sg.Text("OAuth Token:", size=(20, 1)), sg.Input("oauth:", key="-OAUTH-", password_char="*")], [sg.Text("Channel:", size=(20, 1)), sg.Input("#", key="-CHANNEL-")], [sg.HorizontalSeparator()], [sg.Text("PERPLEXITY CONFIGURATION", font=("Arial", 12, "bold"))], [sg.Text("API Key:", size=(20, 1)), sg.Input("pplx-", key="-API-KEY-", password_char="*")], [sg.Text("Model:", size=(20, 1)), sg.Combo(["sonar-pro", "sonar"], default_value="sonar-pro", key="-MODEL-")], [sg.Text("Max Tokens:", size=(20, 1)), sg.Input("450", key="-TOKENS-")], [sg.HorizontalSeparator()], [sg.Checkbox("Enable Debug Mode", default=True, key="-DEBUG-")], [sg.Checkbox("Auto Reconnect", default=True, key="-RECONNECT-")], [sg.HorizontalSeparator()], [sg.Button("Save & Start", key="-SAVE-"), sg.Button("Cancel", key="-CANCEL-")] ] window = sg.Window("Eugen Setup", layout) config = None while True: event, values = window.read() if event == sg.WINDOW_CLOSED or event == "-CANCEL-": break if event == "-SAVE-": # Validate inputs if not values["-OAUTH-"].startswith("oauth:"): sg.popup_error("Twitch OAuth token must start with 'oauth:'") continue if not values["-CHANNEL-"].startswith("#"): sg.popup_error("Channel must start with '#'") continue if not values["-API-KEY-"].startswith("pplx-"): sg.popup_error("Perplexity API key must start with 'pplx-'") continue # Build config config = { "TWITCH_BOT_NICKNAME": values["-BOT-NAME-"], "TWITCH_OAUTH_TOKEN": values["-OAUTH-"], "TWITCH_CHANNEL": values["-CHANNEL-"], "PERPLEXITY_API_KEY": values["-API-KEY-"], "PERPLEXITY_MODEL": values["-MODEL-"], "MAX_TOKENS": values["-TOKENS-"], "DEBUG_MODE": "true" if values["-DEBUG-"] else "false", "AUTO_RECONNECT": "true" if values["-RECONNECT-"] else "false" } break window.close() return config