Implement complete Eugen Twitch chatbot
This commit implements the full Eugen bot based on specifications in CLAUDE.md and eugen_claude.md. Features implemented: - Smart name recognition (@Eugen, Eugen:, etc.) - Persistent conversation memory per user (max 25 messages, 1 hour retention) - Perplexity Sonar API integration for AI responses - Live monitoring dashboard with PySimpleGUI - Setup wizard for first-time configuration - Comprehensive logging (main log + API debug log) Files added: - config.py: Configuration management from .env and config.json - utils.py: MentionDetector and Logger utility classes - memory.py: ConversationMemory for persistent chat history - ai_provider.py: PerplexityProvider for API integration - gui.py: Dashboard and SetupWizard GUI components - chatbot.py: Main EugenBot orchestrator with IRC handling - requirements.txt: Python dependencies - .env.example: Template for environment variables - .gitignore: Renamed from gitignore for proper Git usage Updated: - README.md: Complete usage instructions and documentation The bot is ready to use - users just need to add their API keys and run python chatbot.py
This commit is contained in:
parent
742c907a5a
commit
963a65536f
10 changed files with 1350 additions and 2 deletions
271
gui.py
Normal file
271
gui.py
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
"""
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue