Setup wizard improvements: - Expanded model dropdown with 6 Perplexity models: * sonar-pro (recommended default) * sonar * sonar-reasoning * llama-3.1-sonar-small-128k-online * llama-3.1-sonar-large-128k-online * llama-3.1-sonar-huge-128k-online - Added info button (?) next to model selector - Info popup explains each model's characteristics: * Performance * Use cases * Speed/quality tradeoffs * Cost considerations Created MODEL_CHANGE.md documentation: - How to change model in .env file - How to re-run setup wizard - Detailed model descriptions - Example configuration Users can now easily choose the right model for their needs and change it anytime without reconfiguring everything.
319 lines
11 KiB
Python
319 lines
11 KiB
Python
"""
|
||
Dashboard GUI for Eugen Bot
|
||
Live monitoring interface using FreeSimpleGUI
|
||
"""
|
||
import FreeSimpleGUI 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",
|
||
"sonar-reasoning",
|
||
"llama-3.1-sonar-small-128k-online",
|
||
"llama-3.1-sonar-large-128k-online",
|
||
"llama-3.1-sonar-huge-128k-online"
|
||
], default_value="sonar-pro", key="-MODEL-", size=(35, 1)),
|
||
sg.Button("?", key="-MODEL-INFO-", size=(2, 1))],
|
||
[sg.Text("", size=(20, 1)),
|
||
sg.Text("Recommended: sonar-pro (best quality)", font=("Arial", 8), text_color="gray")],
|
||
[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
|
||
|
||
# Show model info
|
||
if event == "-MODEL-INFO-":
|
||
model_info = """PERPLEXITY MODEL OVERVIEW:
|
||
|
||
🔵 sonar-pro (RECOMMENDED)
|
||
- Best quality and reasoning capabilities
|
||
- Ideal for chat bots and complex queries
|
||
- Good balance of speed and intelligence
|
||
|
||
🟢 sonar
|
||
- Faster, lower cost
|
||
- Good for simple queries
|
||
- Less complex reasoning
|
||
|
||
🧠 sonar-reasoning
|
||
- Enhanced reasoning for complex problems
|
||
- Slower but more thoughtful responses
|
||
|
||
📚 llama-3.1-sonar-small-128k-online
|
||
- Smallest, fastest Llama model
|
||
- 128k context window
|
||
- Best for simple, quick responses
|
||
|
||
📚 llama-3.1-sonar-large-128k-online
|
||
- Larger Llama model
|
||
- Better quality than small
|
||
- Good balance
|
||
|
||
📚 llama-3.1-sonar-huge-128k-online
|
||
- Largest Llama model
|
||
- Best quality but slower
|
||
- Higher cost
|
||
|
||
For most chat bots: Use sonar-pro"""
|
||
sg.popup_scrolled(model_info, title="Model Information", size=(60, 25))
|
||
continue
|
||
|
||
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
|