- Add try/except fallback for sg.theme() -> sg.set_options() - Resolves AttributeError when using PySimpleGUI 5.0.8.3 - Maintains backward compatibility with PySimpleGUI 4.x
279 lines
9.4 KiB
Python
279 lines
9.4 KiB
Python
"""
|
||
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
|
||
# PySimpleGUI 5.x compatibility
|
||
try:
|
||
sg.theme('DarkBlue3')
|
||
except AttributeError:
|
||
sg.set_options(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):
|
||
# PySimpleGUI 5.x compatibility
|
||
try:
|
||
sg.theme('DarkBlue3')
|
||
except AttributeError:
|
||
sg.set_options(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
|