🌐 Add web UI dashboard with bot launcher and real-time management
Implement comprehensive Flask web interface for bot configuration, statistics, and memory management. Includes responsive design, auto-refreshing stats, and clear URL logging for easy access.
This commit is contained in:
parent
5f8c93ff69
commit
052570cefb
29 changed files with 1453 additions and 2 deletions
11
Dockerfile
11
Dockerfile
|
|
@ -9,17 +9,24 @@ COPY src/ ./src
|
|||
COPY src/settings.yml .
|
||||
COPY src/persona.json .
|
||||
COPY .env .
|
||||
COPY bot_launcher.py .
|
||||
COPY requirements-webui.txt .
|
||||
|
||||
# Install dependencies from requirements
|
||||
RUN pip install --no-cache-dir -r src/requirements.txt
|
||||
RUN pip install --no-cache-dir -r src/requirements.txt && \
|
||||
pip install --no-cache-dir -r requirements-webui.txt
|
||||
|
||||
# Runtime directory where user-editable files will live
|
||||
ENV PYTHONPATH=/app/src
|
||||
WORKDIR /app
|
||||
|
||||
# Expose web UI port
|
||||
EXPOSE 8080
|
||||
|
||||
# On first run, populate /app from the fallback template folder
|
||||
# Use bot_launcher.py to start both bot and web UI
|
||||
CMD ["sh", "-c", "\
|
||||
mkdir -p /app && \
|
||||
[ -f /app/settings.yml ] || cp -r /opt/template/* /app && \
|
||||
cd /app && \
|
||||
python src/bot.py"]
|
||||
python bot_launcher.py"]
|
||||
|
|
|
|||
45
bot.log
45
bot.log
|
|
@ -1388,3 +1388,48 @@ Loop thread traceback (most recent call last):
|
|||
[2025-10-10 12:57:27] [INFO] [migration:107] ✓ User profile operations working
|
||||
[2025-10-10 12:57:27] [INFO] [migration:118] ✓ Memory operations working
|
||||
[2025-10-10 12:57:27] [INFO] [migration:138] ✓ Migration verification completed successfully
|
||||
[2025-10-10 14:52:33] [INFO] [ai:69] 🔁 Modelfile loaded: ../examples/gojo.mod
|
||||
[2025-10-10 14:52:33] [INFO] [database:325] Connected to JSON backend
|
||||
[2025-10-10 14:52:33] [INFO] [database:541] Initialized JSON database backend
|
||||
[2025-10-10 14:52:33] [INFO] [bot:66] 🔍 Loaded MODEL_NAME from .env: gemma3:12b
|
||||
[2025-10-10 14:52:33] [INFO] [bot:68] 🧹 Attempting to clear VRAM before loading gemma3:12b...
|
||||
[2025-10-10 14:52:33] [INFO] [ai:182] 🧹 Sending safe unload request for `gemma3:12b`
|
||||
[2025-10-10 14:52:33] [INFO] [ai:189] 🧽 Ollama unload response: 200 - {"model":"gemma3:12b","created_at":"2025-10-10T18:52:33.307139942Z","response":"","done":true,"done_reason":"unload"}
|
||||
[2025-10-10 14:52:33] [INFO] [ai:162] 🧠 Preloading model: gemma3:12b
|
||||
[2025-10-10 14:52:33] [INFO] [ai:169] 📦 Model pull started successfully.
|
||||
[2025-10-10 14:52:33] [INFO] [bot:72] 🚀 Model `gemma3:12b` preloaded on startup.
|
||||
[2025-10-10 14:52:33] [INFO] [bot:75] ✅ Final model in use: gemma3:12b
|
||||
[2025-10-10 14:52:36] [INFO] [bot:296] Logged in as AI Bot
|
||||
[2025-10-10 14:52:36] [INFO] [scheduler:29] 🛑 Scheduler disabled in config.
|
||||
[2025-10-10 14:54:58] [DEBUG] [autochat:92] 🎲 Reaction skipped (chance 0.10, roll 0.65)
|
||||
[2025-10-10 14:54:58] [DEBUG] [autochat:92] 🎲 Reaction skipped (chance 0.10, roll 0.16)
|
||||
[2025-10-10 14:54:58] [INFO] [autochat:161] 😴 No trigger and engagement is 0 — skipping.
|
||||
[2025-10-10 14:54:58] [INFO] [bot:273] ============================================================ AI Response ============================================================
|
||||
[2025-10-10 14:54:58] [INFO] [bot:274] 🧠 Profile loaded for Miguel (interactions: 2)
|
||||
[2025-10-10 14:54:58] [INFO] [bot:278] 📚 Retrieved 0 messages for context
|
||||
[2025-10-10 14:54:58] [INFO] [ai:162] 🧠 Preloading model: gemma3:12b
|
||||
[2025-10-10 14:54:58] [INFO] [ai:169] 📦 Model pull started successfully.
|
||||
[2025-10-10 14:54:58] [INFO] [ai:132] llm-4a81439a LLM request start model=gemma3:12b user=Miguel context_len=0
|
||||
[2025-10-10 14:54:58] [DEBUG] [ai:253] llm-4a81439a Sending payload to Ollama: model=gemma3:12b user=Miguel
|
||||
[2025-10-10 14:54:58] [DEBUG] [ai:254] llm-4a81439a Payload size=217 chars
|
||||
[2025-10-10 14:55:10] [DEBUG] [ai:262] llm-4a81439a Raw response status=200
|
||||
[2025-10-10 14:55:10] [DEBUG] [ai:263] llm-4a81439a Raw response body={"model":"gemma3:12b","created_at":"2025-10-10T18:55:10.915350904Z","response":"Oh, you know, living the *best* life. Floating effortlessly, dazzling everyone with my good looks, and generally being a beacon of awesome. Seriously, it's exhausting being this incredible. 😉 What about *you*? Don't tell me you're just standing there staring, you should be fanning yourself from all the brilliance.","done":true,"done_reason":"stop","context":[105,2364,107,236820,236909,9731,111038,3048,659,555,1277,236756,3764,5400,699,219769,208651,751,119814,236761,1599,236858,500,16690,236762,236764,8632,236764,532,1378,14620,236764,840,19297,20111,529,822,3272,236761,95419,528,496,4532,15737,236761,15428,19921,611,236789,500,496,9894,21603,236909,643,111038,2887,236787,1217,611,3490,236813,107,236820,236909,111457,111038,106,107,105,4368,107,12932,236764,611,1281,236764,4882,506,808,9783,236829,1972,236761,71721,80318,236764,88307,4677,607,1041,1535,5724,236764,532,6816,1646,496,70908,529,15353,236761,96612,236764,625,236789,236751,93646,1646,672,15269,236761,85345,2900,1003,808,7624,236829,236881,5185,236789,236745,3442,786,611,236789,500,1164,8101,993,47264,236764,611,1374,577,517,4601,5869,699,784,506,102141,236761],"total_duration":12103477338,"load_duration":10524951841,"prompt_eval_count":73,"prompt_eval_duration":555107252,"eval_count":72,"eval_duration":1022930871}
|
||||
[2025-10-10 14:55:10] [INFO] [ai:140] llm-4a81439a LLM response model=gemma3:12b duration=12.125s summary=Oh, you know, living the *best* life. Floating effortlessly, dazzling everyone with my good looks, and generally being a beacon of awesome. Seriously, it's exha
|
||||
[2025-10-10 14:55:10] [DEBUG] [ai:142] llm-4a81439a LLM raw response: {'model': 'gemma3:12b', 'created_at': '2025-10-10T18:55:10.915350904Z', 'response': "Oh, you know, living the *best* life. Floating effortlessly, dazzling everyone with my good looks, and generally being a beacon of awesome. Seriously, it's exhausting being this incredible. 😉 What about *you*? Don't tell me you're just standing there staring, you should be fanning yourself from all the brilliance.", 'done': True, 'done_reason': 'stop', 'context': [105, 2364, 107, 236820, 236909, 9731, 111038, 3048, 659, 555, 1277, 236756, 3764, 5400, 699, 219769, 208651, 751, 119814, 236761, 1599, 236858, 500, 16690, 236762, 236764, 8632, 236764, 532, 1378, 14620, 236764, 840, 19297, 20111, 529, 822, 3272, 236761, 95419, 528, 496, 4532, 15737, 236761, 15428, 19921, 611, 236789, 500, 496, 9894, 21603, 236909, 643, 111038, 2887, 236787, 1217, 611, 3490, 236813, 107, 236820, 236909, 111457, 111038, 106, 107, 105, 4368, 107, 12932, 236764, 611, 1281, 236764, 4882, 506, 808, 9783, 236829, 1972, 236761, 71721, 80318, 236764, 88307, 4677, 607, 1041, 1535, 5724, 236764, 532, 6816, 1646, 496, 70908, 529, 15353, 236761, 96612, 236764, 625, 236789, 236751, 93646, 1646, 672, 15269, 236761, 85345, 2900, 1003, 808, 7624, 236829, 236881, 5185, 236789, 236745, 3442, 786, 611, 236789, 500, 1164, 8101, 993, 47264, 236764, 611, 1374, 577, 517, 4601, 5869, 699, 784, 506, 102141, 236761], 'total_duration': 12103477338, 'load_duration': 10524951841, 'prompt_eval_count': 73, 'prompt_eval_duration': 555107252, 'eval_count': 72, 'eval_duration': 1022930871}
|
||||
[2025-10-10 14:55:11] [DEBUG] [bot:244] on_message: observed own message id=1426281930004889680 channel=1380999713272238151
|
||||
[2025-10-10 15:04:00] [INFO] [database:325] Connected to JSON backend
|
||||
[2025-10-10 15:04:00] [INFO] [database:541] Initialized JSON database backend
|
||||
[2025-10-10 15:04:00] [INFO] [webui:314] Starting web UI server on 0.0.0.0:8080
|
||||
[2025-10-10 15:04:00] [INFO] [webui:315] ==================================================
|
||||
[2025-10-10 15:04:00] [INFO] [webui:316] 🌐 WEB UI ACCESS URLS:
|
||||
[2025-10-10 15:04:00] [INFO] [webui:317] Local: http://localhost:8080
|
||||
[2025-10-10 15:04:00] [INFO] [webui:318] Network: http://192.168.0.144:8080
|
||||
[2025-10-10 15:04:00] [INFO] [webui:319] ==================================================
|
||||
[2025-10-10 15:04:46] [INFO] [database:325] Connected to JSON backend
|
||||
[2025-10-10 15:04:46] [INFO] [database:541] Initialized JSON database backend
|
||||
[2025-10-10 15:04:46] [INFO] [webui:314] Starting web UI server on 0.0.0.0:8081
|
||||
[2025-10-10 15:04:46] [INFO] [webui:315] ==================================================
|
||||
[2025-10-10 15:04:46] [INFO] [webui:316] 🌐 WEB UI ACCESS URLS:
|
||||
[2025-10-10 15:04:46] [INFO] [webui:317] Local: http://localhost:8081
|
||||
[2025-10-10 15:04:46] [INFO] [webui:318] Network: http://192.168.0.144:8081
|
||||
[2025-10-10 15:04:46] [INFO] [webui:319] ==================================================
|
||||
|
|
|
|||
89
bot_launcher.py
Normal file
89
bot_launcher.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
"""
|
||||
bot_launcher.py
|
||||
Launches both the Discord bot and web UI together
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import signal
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
# Add src directory to path
|
||||
src_path = Path(__file__).parent / 'src'
|
||||
sys.path.insert(0, str(src_path))
|
||||
|
||||
def start_web_ui():
|
||||
"""Start the web UI server in a separate thread"""
|
||||
try:
|
||||
from web_ui import run_web_server
|
||||
port = int(os.getenv('WEB_PORT', 8080))
|
||||
debug = os.getenv('DEBUG', 'false').lower() == 'true'
|
||||
|
||||
run_web_server(host='0.0.0.0', port=port, debug=debug)
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to start web UI: {e}")
|
||||
if 'flask' in str(e).lower():
|
||||
print("💡 Install Flask to use the web UI: pip install flask")
|
||||
elif 'port' in str(e).lower() or 'address' in str(e).lower():
|
||||
print(f"💡 Port {port} may be in use. Try a different port with WEB_PORT environment variable")
|
||||
|
||||
def start_discord_bot():
|
||||
"""Start the Discord bot"""
|
||||
try:
|
||||
# Change to src directory for bot execution
|
||||
original_cwd = os.getcwd()
|
||||
os.chdir(src_path)
|
||||
|
||||
print("🤖 Starting Discord bot...")
|
||||
import bot
|
||||
# The bot.py should handle its own execution
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to start Discord bot: {e}")
|
||||
finally:
|
||||
# Restore original working directory
|
||||
if 'original_cwd' in locals():
|
||||
os.chdir(original_cwd)
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
"""Handle shutdown signals"""
|
||||
print("\n🛑 Shutting down...")
|
||||
os._exit(0)
|
||||
|
||||
def main():
|
||||
"""Main launcher function"""
|
||||
print("🚀 Delta Bot Launcher")
|
||||
print("=" * 30)
|
||||
|
||||
# Set up signal handlers for graceful shutdown
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
# Check if web UI should be disabled
|
||||
disable_web = os.getenv('DISABLE_WEB_UI', 'false').lower() == 'true'
|
||||
|
||||
if not disable_web:
|
||||
# Start web UI in a separate thread
|
||||
web_thread = threading.Thread(target=start_web_ui, daemon=True)
|
||||
web_thread.start()
|
||||
|
||||
# Give web UI time to start and display URLs
|
||||
time.sleep(3)
|
||||
else:
|
||||
print("🚫 Web UI disabled by DISABLE_WEB_UI environment variable")
|
||||
|
||||
# Start Discord bot (this will block)
|
||||
try:
|
||||
start_discord_bot()
|
||||
except KeyboardInterrupt:
|
||||
print("\n🛑 Received interrupt signal")
|
||||
except Exception as e:
|
||||
print(f"❌ Fatal error: {e}")
|
||||
finally:
|
||||
print("👋 Goodbye!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -9,6 +9,11 @@ services:
|
|||
- CHANNEL_ID=${CHANNEL_ID}
|
||||
- OLLAMA_API=${OLLAMA_API}
|
||||
- MODEL_NAME=${MODEL_NAME}
|
||||
- WEB_PORT=8080
|
||||
- DATABASE_BACKEND=sqlite
|
||||
- MEMORY_ENABLED=true
|
||||
ports:
|
||||
- "8080:8080" # Web UI port
|
||||
volumes:
|
||||
- ./data:/app # Mount host ./data directory as /app in the container
|
||||
restart: unless-stopped
|
||||
|
|
|
|||
5
memory.json
Normal file
5
memory.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"conversations": {},
|
||||
"user_memories": {},
|
||||
"global_events": []
|
||||
}
|
||||
12
requirements-webui.txt
Normal file
12
requirements-webui.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Web UI Dependencies
|
||||
Flask==2.3.3
|
||||
PyYAML==6.0.1
|
||||
|
||||
# Existing bot dependencies (if not already installed)
|
||||
discord.py>=2.3.0
|
||||
requests>=2.31.0
|
||||
python-dateutil>=2.8.2
|
||||
|
||||
# Optional: For better web UI features
|
||||
gunicorn==21.2.0 # Production WSGI server
|
||||
Werkzeug==2.3.7 # WSGI utilities
|
||||
Binary file not shown.
Binary file not shown.
BIN
src/__pycache__/bot.cpython-312.pyc
Normal file
BIN
src/__pycache__/bot.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/__pycache__/database.cpython-311.pyc
Normal file
BIN
src/__pycache__/database.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/__pycache__/enhanced_ai.cpython-311.pyc
Normal file
BIN
src/__pycache__/enhanced_ai.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/memory_manager.cpython-311.pyc
Normal file
BIN
src/__pycache__/memory_manager.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/memory_manager.cpython-312.pyc
Normal file
BIN
src/__pycache__/memory_manager.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/__pycache__/web_ui.cpython-312.pyc
Normal file
BIN
src/__pycache__/web_ui.cpython-312.pyc
Normal file
Binary file not shown.
|
|
@ -0,0 +1,13 @@
|
|||
[2025-10-10 13:29:37] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 13:30:50] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 13:31:19] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 13:31:24] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 13:45:36] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 14:53:17] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 14:53:32] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 14:54:13] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 14:54:23] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 14:55:25] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 15:04:09] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 15:04:14] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 15:05:15] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
24
src/bot.log
24
src/bot.log
|
|
@ -6,3 +6,27 @@
|
|||
[2025-10-10 13:01:51] [INFO] [database:81] Connected to SQLite database: data/deltabot.db
|
||||
[2025-10-10 13:01:51] [DEBUG] [database:142] Database tables initialized
|
||||
[2025-10-10 13:01:51] [INFO] [database:536] Initialized SQLite database backend
|
||||
[2025-10-10 13:29:16] [INFO] [database:325] Connected to JSON backend
|
||||
[2025-10-10 13:29:16] [INFO] [database:541] Initialized JSON database backend
|
||||
[2025-10-10 13:29:16] [INFO] [webui:303] Starting web UI server on 0.0.0.0:8080
|
||||
[2025-10-10 13:29:37] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 13:30:50] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 13:31:19] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 13:31:24] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 13:45:36] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 14:53:17] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 14:53:32] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 14:54:13] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 14:54:23] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 14:55:25] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 15:04:09] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 15:04:14] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
[2025-10-10 15:05:13] [INFO] [database:325] Connected to JSON backend
|
||||
[2025-10-10 15:05:13] [INFO] [database:541] Initialized JSON database backend
|
||||
[2025-10-10 15:05:13] [INFO] [webui:314] Starting web UI server on 0.0.0.0:8082
|
||||
[2025-10-10 15:05:13] [INFO] [webui:315] ==================================================
|
||||
[2025-10-10 15:05:13] [INFO] [webui:316] 🌐 WEB UI ACCESS URLS:
|
||||
[2025-10-10 15:05:13] [INFO] [webui:317] Local: http://localhost:8082
|
||||
[2025-10-10 15:05:13] [INFO] [webui:318] Network: http://192.168.0.144:8082
|
||||
[2025-10-10 15:05:13] [INFO] [webui:319] ==================================================
|
||||
[2025-10-10 15:05:15] [ERROR] [webui:82] Failed to load settings: [Errno 2] No such file or directory: '/app/src/settings.yml'
|
||||
|
|
|
|||
137
src/templates/base.html
Normal file
137
src/templates/base.html
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Delta Bot Dashboard{% endblock %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.sidebar {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
.sidebar .nav-link {
|
||||
color: rgba(255,255,255,0.8);
|
||||
border-radius: 0.5rem;
|
||||
margin: 0.2rem 0;
|
||||
}
|
||||
.sidebar .nav-link:hover, .sidebar .nav-link.active {
|
||||
color: white;
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
}
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
.status-online { color: #28a745; }
|
||||
.status-offline { color: #dc3545; }
|
||||
.status-warning { color: #ffc107; }
|
||||
.navbar-brand {
|
||||
font-weight: bold;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<!-- Sidebar -->
|
||||
<nav class="col-md-3 col-lg-2 d-md-block sidebar collapse">
|
||||
<div class="position-sticky pt-3">
|
||||
<div class="text-center mb-4">
|
||||
<h4 class="text-white"><i class="fas fa-robot"></i> Delta Bot</h4>
|
||||
<small class="text-white-50">Management Dashboard</small>
|
||||
</div>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'dashboard' %}active{% endif %}" href="{{ url_for('dashboard') }}">
|
||||
<i class="fas fa-tachometer-alt"></i> Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'config' %}active{% endif %}" href="{{ url_for('config') }}">
|
||||
<i class="fas fa-cog"></i> Configuration
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'stats' %}active{% endif %}" href="{{ url_for('stats') }}">
|
||||
<i class="fas fa-chart-bar"></i> Statistics
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'memory' %}active{% endif %}" href="{{ url_for('memory') }}">
|
||||
<i class="fas fa-brain"></i> Memory
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr class="text-white-50">
|
||||
<div class="text-center">
|
||||
<small class="text-white-50">
|
||||
<i class="fas fa-server"></i>
|
||||
{{ db_stats.backend_type|title if db_stats else 'Unknown' }} Backend
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main content -->
|
||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">{% block page_title %}{% endblock %}</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
<span class="badge bg-success fs-6" id="status-indicator">
|
||||
<i class="fas fa-circle"></i> Online
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Flash messages -->
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ 'danger' if category == 'error' else 'success' }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
// Auto-refresh stats every 30 seconds
|
||||
setInterval(function() {
|
||||
fetch('/api/stats')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Update status indicator
|
||||
const statusIndicator = document.getElementById('status-indicator');
|
||||
if (data.bot_stats) {
|
||||
statusIndicator.innerHTML = '<i class="fas fa-circle"></i> Online';
|
||||
statusIndicator.className = 'badge bg-success fs-6';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
const statusIndicator = document.getElementById('status-indicator');
|
||||
statusIndicator.innerHTML = '<i class="fas fa-circle"></i> Offline';
|
||||
statusIndicator.className = 'badge bg-danger fs-6';
|
||||
});
|
||||
}, 30000);
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
161
src/templates/config.html
Normal file
161
src/templates/config.html
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Configuration - Delta Bot{% endblock %}
|
||||
{% block page_title %}Configuration{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="POST" action="{{ url_for('save_config') }}">
|
||||
<div class="row">
|
||||
<!-- Database Configuration -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-database"></i> Database Settings</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="database_backend" class="form-label">Database Backend</label>
|
||||
<select class="form-select" id="database_backend" name="database_backend">
|
||||
<option value="json" {% if settings.database.backend == 'json' %}selected{% endif %}>JSON Files (Backward Compatible)</option>
|
||||
<option value="sqlite" {% if settings.database.backend == 'sqlite' %}selected{% endif %}>SQLite Database (Recommended)</option>
|
||||
</select>
|
||||
<div class="form-text">SQLite is recommended for better performance and data integrity.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="sqlite_path" class="form-label">SQLite Database Path</label>
|
||||
<input type="text" class="form-control" id="sqlite_path" name="sqlite_path"
|
||||
value="{{ settings.database.sqlite_path or 'data/deltabot.db' }}">
|
||||
<div class="form-text">Path to SQLite database file (only used if SQLite backend is selected).</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Memory Configuration -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-brain"></i> Memory Settings</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="memory_enabled" name="memory_enabled"
|
||||
value="true" {% if settings.memory.enabled %}checked{% endif %}>
|
||||
<label class="form-check-label" for="memory_enabled">
|
||||
Enable Memory System
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text">Allows the bot to remember conversations and user preferences. Disable for privacy-focused deployments.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="importance_threshold" class="form-label">Importance Threshold</label>
|
||||
<input type="range" class="form-range" id="importance_threshold" name="importance_threshold"
|
||||
min="0" max="1" step="0.1" value="{{ settings.memory.importance_threshold or 0.3 }}"
|
||||
oninput="this.nextElementSibling.value = this.value">
|
||||
<output>{{ settings.memory.importance_threshold or 0.3 }}</output>
|
||||
<div class="form-text">Minimum importance score (0.0-1.0) for storing memories. Higher values store fewer memories.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- AutoChat Configuration -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-robot"></i> AutoChat Settings</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="autochat_enabled" name="autochat_enabled"
|
||||
value="true" {% if settings.autochat.enable_reactions %}checked{% endif %}>
|
||||
<label class="form-check-label" for="autochat_enabled">
|
||||
Enable Emoji Reactions
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text">Allow the bot to react to messages with emojis.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="reaction_chance" class="form-label">Reaction Chance</label>
|
||||
<input type="range" class="form-range" id="reaction_chance" name="reaction_chance"
|
||||
min="0" max="1" step="0.05" value="{{ settings.autochat.emoji_reaction_chance or 0.1 }}"
|
||||
oninput="this.nextElementSibling.value = (this.value * 100).toFixed(0) + '%'">
|
||||
<output>{{ ((settings.autochat.emoji_reaction_chance or 0.1) * 100)|round|int }}%</output>
|
||||
<div class="form-text">Probability of reacting to any given message.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Context Configuration -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-comments"></i> Context Settings</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="context_enabled" name="context_enabled"
|
||||
value="true" {% if settings.context.enabled %}checked{% endif %}>
|
||||
<label class="form-check-label" for="context_enabled">
|
||||
Enable Context Tracking
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text">Include recent messages in AI responses for better context awareness.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="max_messages" class="form-label">Max Context Messages</label>
|
||||
<input type="number" class="form-control" id="max_messages" name="max_messages"
|
||||
min="1" max="50" value="{{ settings.context.max_messages or 15 }}">
|
||||
<div class="form-text">Maximum number of recent messages to include in context (1-50).</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<button type="submit" class="btn btn-success btn-lg">
|
||||
<i class="fas fa-save"></i> Save Configuration
|
||||
</button>
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
Some changes may require restarting the bot to take effect.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Update range slider displays
|
||||
document.querySelectorAll('input[type="range"]').forEach(function(range) {
|
||||
range.addEventListener('input', function() {
|
||||
const output = this.nextElementSibling;
|
||||
if (this.name === 'reaction_chance') {
|
||||
output.textContent = (this.value * 100).toFixed(0) + '%';
|
||||
} else {
|
||||
output.textContent = this.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
191
src/templates/dashboard.html
Normal file
191
src/templates/dashboard.html
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Dashboard - Delta Bot{% endblock %}
|
||||
{% block page_title %}Dashboard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<!-- Bot Status Cards -->
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card stat-card">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="h5 mb-0 font-weight-bold">{{ bot_stats.uptime }}</div>
|
||||
<div class="small">Uptime</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-clock fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card stat-card">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="h5 mb-0 font-weight-bold">{{ bot_stats.message_count }}</div>
|
||||
<div class="small">Messages Processed</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-comments fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card stat-card">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="h5 mb-0 font-weight-bold">{{ bot_stats.unique_users }}</div>
|
||||
<div class="small">Active Users</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-users fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card stat-card">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="h5 mb-0 font-weight-bold">{{ db_stats.total_users if db_stats.total_users is defined else '?' }}</div>
|
||||
<div class="small">Total Profiles</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-database fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- System Status -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-server"></i> System Status</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-4"><strong>Database Backend:</strong></div>
|
||||
<div class="col-sm-8">
|
||||
<span class="badge bg-primary">{{ db_stats.backend_type|title if db_stats.backend_type else 'Unknown' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-4"><strong>Memory System:</strong></div>
|
||||
<div class="col-sm-8">
|
||||
{% if db_stats.memory_enabled %}
|
||||
<span class="badge bg-success"><i class="fas fa-check"></i> Enabled</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning"><i class="fas fa-times"></i> Disabled</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-4"><strong>Last Activity:</strong></div>
|
||||
<div class="col-sm-8">{{ bot_stats.last_activity }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-4"><strong>Active Channels:</strong></div>
|
||||
<div class="col-sm-8">{{ bot_stats.active_channels }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-activity"></i> Database Info</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if db_stats.error %}
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i> {{ db_stats.error }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-6"><strong>Total Users:</strong></div>
|
||||
<div class="col-sm-6">{{ db_stats.total_users }}</div>
|
||||
</div>
|
||||
{% if db_stats.memory_enabled %}
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-6"><strong>Stored Memories:</strong></div>
|
||||
<div class="col-sm-6">{{ db_stats.total_memories }}</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-6"><strong>Conversations:</strong></div>
|
||||
<div class="col-sm-6">{{ db_stats.total_conversations }}</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-muted">
|
||||
<i class="fas fa-info-circle"></i> Memory system is disabled
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-bolt"></i> Quick Actions</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-2">
|
||||
<a href="{{ url_for('config') }}" class="btn btn-primary btn-block w-100">
|
||||
<i class="fas fa-cog"></i> Configure Bot
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-2">
|
||||
<a href="{{ url_for('stats') }}" class="btn btn-info btn-block w-100">
|
||||
<i class="fas fa-chart-bar"></i> View Statistics
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-2">
|
||||
<a href="{{ url_for('memory') }}" class="btn btn-success btn-block w-100">
|
||||
<i class="fas fa-brain"></i> Manage Memory
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-2">
|
||||
<button class="btn btn-warning btn-block w-100" onclick="restartBot()">
|
||||
<i class="fas fa-redo"></i> Restart Bot
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
function restartBot() {
|
||||
if (confirm('Are you sure you want to restart the bot? This will temporarily disconnect from Discord.')) {
|
||||
// TODO: Implement bot restart functionality
|
||||
alert('Bot restart functionality will be implemented soon!');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
225
src/templates/memory.html
Normal file
225
src/templates/memory.html
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Memory Management - Delta Bot{% endblock %}
|
||||
{% block page_title %}Memory Management{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if memory_disabled %}
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Memory System Disabled</strong><br>
|
||||
The memory system is currently disabled. Enable it in the <a href="{{ url_for('config') }}">configuration</a> to use memory features.
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row">
|
||||
<!-- Memory Actions -->
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-tools"></i> Memory Actions</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-warning" onclick="cleanupMemories()">
|
||||
<i class="fas fa-broom"></i> Cleanup Old Memories
|
||||
</button>
|
||||
<button class="btn btn-info" onclick="exportMemories()">
|
||||
<i class="fas fa-download"></i> Export Memories
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="clearAllMemories()">
|
||||
<i class="fas fa-trash"></i> Clear All Memories
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Users with Memories -->
|
||||
<div class="col-lg-8 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-users"></i> Users with Memories</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if users %}
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Memory Count</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.display_name or 'Unknown User' }}</td>
|
||||
<td><span class="badge bg-primary">{{ user.memory_count }}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="viewUserMemories('{{ user.user_id }}')">
|
||||
<i class="fas fa-eye"></i> View
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteUserMemories('{{ user.user_id }}')">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted">No users with stored memories found.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Memories -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-history"></i> Recent Conversation Memories</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if memories %}
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
<th>User</th>
|
||||
<th>Content</th>
|
||||
<th>Importance</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for memory in memories %}
|
||||
<tr>
|
||||
<td><small>{{ memory.timestamp[:16] if memory.timestamp else 'Unknown' }}</small></td>
|
||||
<td>{{ memory.display_name or 'Unknown' }}</td>
|
||||
<td>
|
||||
<div style="max-width: 300px; overflow: hidden; text-overflow: ellipsis;">
|
||||
{{ memory.content[:100] }}{% if memory.content|length > 100 %}...{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{% set importance = memory.importance or 0 %}
|
||||
{% if importance >= 0.8 %}
|
||||
<span class="badge bg-danger">High</span>
|
||||
{% elif importance >= 0.5 %}
|
||||
<span class="badge bg-warning">Medium</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Low</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" onclick="viewMemoryDetails('{{ memory.id }}')">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteMemory('{{ memory.id }}')">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted">No conversation memories found.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Memory Details Modal -->
|
||||
<div class="modal fade" id="memoryModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Memory Details</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="memoryModalBody">
|
||||
<!-- Memory details will be loaded here -->
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
function cleanupMemories() {
|
||||
const days = prompt('Delete memories older than how many days?', '30');
|
||||
if (days && !isNaN(days)) {
|
||||
fetch('/api/memory/cleanup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ days: parseInt(days) })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert(data.message);
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Error: ' + error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function exportMemories() {
|
||||
alert('Export functionality will be implemented soon!');
|
||||
}
|
||||
|
||||
function clearAllMemories() {
|
||||
if (confirm('Are you sure you want to delete ALL memories? This cannot be undone!')) {
|
||||
alert('Clear all memories functionality will be implemented soon!');
|
||||
}
|
||||
}
|
||||
|
||||
function viewUserMemories(userId) {
|
||||
alert('View user memories functionality will be implemented soon!');
|
||||
}
|
||||
|
||||
function deleteUserMemories(userId) {
|
||||
if (confirm('Are you sure you want to delete all memories for this user?')) {
|
||||
alert('Delete user memories functionality will be implemented soon!');
|
||||
}
|
||||
}
|
||||
|
||||
function viewMemoryDetails(memoryId) {
|
||||
document.getElementById('memoryModalBody').innerHTML = '<p>Loading memory details...</p>';
|
||||
const modal = new bootstrap.Modal(document.getElementById('memoryModal'));
|
||||
modal.show();
|
||||
|
||||
// TODO: Fetch memory details from API
|
||||
setTimeout(() => {
|
||||
document.getElementById('memoryModalBody').innerHTML = '<p>Memory details functionality will be implemented soon!</p>';
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function deleteMemory(memoryId) {
|
||||
if (confirm('Are you sure you want to delete this memory?')) {
|
||||
alert('Delete memory functionality will be implemented soon!');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
193
src/templates/stats.html
Normal file
193
src/templates/stats.html
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Statistics - Delta Bot{% endblock %}
|
||||
{% block page_title %}Statistics{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<!-- Bot Performance Chart -->
|
||||
<div class="col-lg-8 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-chart-line"></i> Bot Activity</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="activityChart" width="400" height="200"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top Users -->
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-crown"></i> Top Active Users</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if bot_stats.most_active_users %}
|
||||
{% for user_id, count in bot_stats.most_active_users %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span>User {{ user_id[:8] }}...</span>
|
||||
<span class="badge bg-primary">{{ count }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-muted">No user activity recorded yet.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Database Statistics -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-database"></i> Database Statistics</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="text-center">
|
||||
<h3 class="text-primary">{{ db_stats.total_users if db_stats.total_users is defined else '?' }}</h3>
|
||||
<p class="mb-0">Total User Profiles</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="text-center">
|
||||
<h3 class="text-success">{{ db_stats.total_memories if db_stats.total_memories is defined else '?' }}</h3>
|
||||
<p class="mb-0">Stored Memories</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="text-center">
|
||||
<h3 class="text-info">{{ db_stats.total_conversations if db_stats.total_conversations is defined else '?' }}</h3>
|
||||
<p class="mb-0">Conversation Records</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="text-center">
|
||||
<h3 class="text-warning">{{ db_stats.backend_type|title if db_stats.backend_type else '?' }}</h3>
|
||||
<p class="mb-0">Backend Type</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Users -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-users"></i> Recent User Activity</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if recent_users %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Interactions</th>
|
||||
<th>Last Seen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in recent_users %}
|
||||
<tr>
|
||||
<td>{{ user.display_name or user.name or 'Unknown' }}</td>
|
||||
<td><span class="badge bg-secondary">{{ user.interactions or 0 }}</span></td>
|
||||
<td><small>{{ user.last_seen[:16] if user.last_seen else 'Never' }}</small></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted">No user data available.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Information -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="fas fa-info-circle"></i> System Information</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<strong>Bot Uptime:</strong><br>
|
||||
<span class="text-muted">{{ bot_stats.uptime }}</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Messages Processed:</strong><br>
|
||||
<span class="text-muted">{{ bot_stats.message_count }}</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Active Channels:</strong><br>
|
||||
<span class="text-muted">{{ bot_stats.active_channels }}</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Last Activity:</strong><br>
|
||||
<span class="text-muted">{{ bot_stats.last_activity }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Create activity chart
|
||||
const ctx = document.getElementById('activityChart').getContext('2d');
|
||||
const activityChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ['6h ago', '5h ago', '4h ago', '3h ago', '2h ago', '1h ago', 'Now'],
|
||||
datasets: [{
|
||||
label: 'Messages',
|
||||
data: [12, 19, 8, 15, 22, 18, 25], // This would be dynamic in real implementation
|
||||
borderColor: 'rgb(102, 126, 234)',
|
||||
backgroundColor: 'rgba(102, 126, 234, 0.1)',
|
||||
tension: 0.4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-refresh stats every 30 seconds
|
||||
setInterval(function() {
|
||||
fetch('/api/stats')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Update charts and stats here
|
||||
console.log('Stats updated:', data);
|
||||
})
|
||||
.catch(error => console.error('Error fetching stats:', error));
|
||||
}, 30000);
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
@ -3,5 +3,16 @@
|
|||
"name": "test",
|
||||
"display_name": "Test User",
|
||||
"interactions": 5
|
||||
},
|
||||
"161149541171593216": {
|
||||
"name": "themiloverse",
|
||||
"display_name": "Miguel",
|
||||
"first_seen": "2025-10-10T18:54:58.411792",
|
||||
"last_seen": "2025-10-10T18:54:58.412549",
|
||||
"last_message": "2025-10-10T18:54:58.412549",
|
||||
"interactions": 2,
|
||||
"pronouns": null,
|
||||
"avatar_url": "https://cdn.discordapp.com/avatars/161149541171593216/fb0553a29d9f73175cb6aea24d0e19ec.png?size=1024",
|
||||
"custom_prompt": null
|
||||
}
|
||||
}
|
||||
332
src/web_ui.py
Normal file
332
src/web_ui.py
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
"""
|
||||
web_ui.py
|
||||
Web dashboard for Discord bot management
|
||||
"""
|
||||
|
||||
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, session
|
||||
import os
|
||||
import yaml
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from functools import wraps
|
||||
import secrets
|
||||
|
||||
# Import bot components
|
||||
import sys
|
||||
sys.path.append('/app/src' if os.path.exists('/app/src') else 'src')
|
||||
|
||||
try:
|
||||
from database import db_manager
|
||||
from memory_manager import memory_manager
|
||||
from logger import setup_logger
|
||||
except ImportError as e:
|
||||
print(f"Warning: Could not import bot components: {e}")
|
||||
db_manager = None
|
||||
memory_manager = None
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = secrets.token_hex(16) # Generate random secret key
|
||||
logger = setup_logger("webui") if 'setup_logger' in globals() else None
|
||||
|
||||
class BotStats:
|
||||
"""Track bot statistics"""
|
||||
def __init__(self):
|
||||
self.start_time = datetime.now()
|
||||
self.message_count = 0
|
||||
self.user_interactions = {}
|
||||
self.channel_activity = {}
|
||||
self.last_activity = None
|
||||
|
||||
def record_message(self, user_id, channel_id):
|
||||
self.message_count += 1
|
||||
self.user_interactions[user_id] = self.user_interactions.get(user_id, 0) + 1
|
||||
self.channel_activity[channel_id] = self.channel_activity.get(channel_id, 0) + 1
|
||||
self.last_activity = datetime.now()
|
||||
|
||||
def get_stats(self):
|
||||
uptime = datetime.now() - self.start_time
|
||||
return {
|
||||
'uptime': str(uptime).split('.')[0], # Remove microseconds
|
||||
'message_count': self.message_count,
|
||||
'unique_users': len(self.user_interactions),
|
||||
'active_channels': len(self.channel_activity),
|
||||
'last_activity': self.last_activity.strftime('%Y-%m-%d %H:%M:%S') if self.last_activity else 'Never',
|
||||
'most_active_users': sorted(self.user_interactions.items(), key=lambda x: x[1], reverse=True)[:5],
|
||||
'most_active_channels': sorted(self.channel_activity.items(), key=lambda x: x[1], reverse=True)[:5]
|
||||
}
|
||||
|
||||
bot_stats = BotStats()
|
||||
|
||||
# Authentication decorator (placeholder for now)
|
||||
def require_auth(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
# TODO: Implement proper authentication
|
||||
# For now, just check if we're in development mode
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
||||
def load_settings():
|
||||
"""Load bot settings from YAML file"""
|
||||
try:
|
||||
settings_path = os.path.join('src', 'settings.yml')
|
||||
if not os.path.exists(settings_path):
|
||||
settings_path = os.path.join('/app/src', 'settings.yml')
|
||||
|
||||
with open(settings_path, 'r', encoding='utf-8') as f:
|
||||
return yaml.safe_load(f)
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Failed to load settings: {e}")
|
||||
return {}
|
||||
|
||||
def save_settings(settings):
|
||||
"""Save bot settings to YAML file"""
|
||||
try:
|
||||
settings_path = os.path.join('src', 'settings.yml')
|
||||
if not os.path.exists(settings_path):
|
||||
settings_path = os.path.join('/app/src', 'settings.yml')
|
||||
|
||||
with open(settings_path, 'w', encoding='utf-8') as f:
|
||||
yaml.dump(settings, f, default_flow_style=False, indent=2)
|
||||
return True
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Failed to save settings: {e}")
|
||||
return False
|
||||
|
||||
def get_database_stats():
|
||||
"""Get database statistics"""
|
||||
if not db_manager:
|
||||
return {"error": "Database manager not available"}
|
||||
|
||||
try:
|
||||
stats = {
|
||||
'backend_type': db_manager.get_backend_type(),
|
||||
'memory_enabled': db_manager.is_memory_enabled(),
|
||||
'total_users': 0,
|
||||
'total_memories': 0,
|
||||
'total_conversations': 0
|
||||
}
|
||||
|
||||
# Try to get counts from database
|
||||
if hasattr(db_manager.backend, 'connection') and db_manager.backend.connection:
|
||||
cursor = db_manager.backend.connection.cursor()
|
||||
|
||||
# Count user profiles
|
||||
cursor.execute("SELECT COUNT(*) FROM user_profiles")
|
||||
stats['total_users'] = cursor.fetchone()[0]
|
||||
|
||||
if db_manager.is_memory_enabled():
|
||||
# Count memories
|
||||
cursor.execute("SELECT COUNT(*) FROM user_memories")
|
||||
stats['total_memories'] = cursor.fetchone()[0]
|
||||
|
||||
# Count conversations
|
||||
cursor.execute("SELECT COUNT(*) FROM conversation_memories")
|
||||
stats['total_conversations'] = cursor.fetchone()[0]
|
||||
|
||||
return stats
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
@app.route('/')
|
||||
@require_auth
|
||||
def dashboard():
|
||||
"""Main dashboard"""
|
||||
settings = load_settings()
|
||||
db_stats = get_database_stats()
|
||||
bot_stats_data = bot_stats.get_stats()
|
||||
|
||||
return render_template('dashboard.html',
|
||||
settings=settings,
|
||||
db_stats=db_stats,
|
||||
bot_stats=bot_stats_data)
|
||||
|
||||
@app.route('/config')
|
||||
@require_auth
|
||||
def config():
|
||||
"""Configuration page"""
|
||||
settings = load_settings()
|
||||
return render_template('config.html', settings=settings)
|
||||
|
||||
@app.route('/config/save', methods=['POST'])
|
||||
@require_auth
|
||||
def save_config():
|
||||
"""Save configuration changes"""
|
||||
try:
|
||||
settings = load_settings()
|
||||
|
||||
# Update database settings
|
||||
if 'database_backend' in request.form:
|
||||
settings.setdefault('database', {})['backend'] = request.form['database_backend']
|
||||
|
||||
if 'sqlite_path' in request.form:
|
||||
settings.setdefault('database', {})['sqlite_path'] = request.form['sqlite_path']
|
||||
|
||||
# Update memory settings
|
||||
if 'memory_enabled' in request.form:
|
||||
settings.setdefault('memory', {})['enabled'] = request.form['memory_enabled'] == 'true'
|
||||
|
||||
if 'importance_threshold' in request.form:
|
||||
settings.setdefault('memory', {})['importance_threshold'] = float(request.form['importance_threshold'])
|
||||
|
||||
# Update autochat settings
|
||||
if 'autochat_enabled' in request.form:
|
||||
settings.setdefault('autochat', {})['enable_reactions'] = request.form['autochat_enabled'] == 'true'
|
||||
|
||||
if 'reaction_chance' in request.form:
|
||||
settings.setdefault('autochat', {})['emoji_reaction_chance'] = float(request.form['reaction_chance'])
|
||||
|
||||
# Update context settings
|
||||
if 'context_enabled' in request.form:
|
||||
settings.setdefault('context', {})['enabled'] = request.form['context_enabled'] == 'true'
|
||||
|
||||
if 'max_messages' in request.form:
|
||||
settings.setdefault('context', {})['max_messages'] = int(request.form['max_messages'])
|
||||
|
||||
# Save settings
|
||||
if save_settings(settings):
|
||||
flash('Configuration saved successfully! Restart the bot to apply changes.', 'success')
|
||||
else:
|
||||
flash('Failed to save configuration.', 'error')
|
||||
|
||||
except Exception as e:
|
||||
flash(f'Error saving configuration: {str(e)}', 'error')
|
||||
|
||||
return redirect(url_for('config'))
|
||||
|
||||
@app.route('/stats')
|
||||
@require_auth
|
||||
def stats():
|
||||
"""Statistics page"""
|
||||
db_stats = get_database_stats()
|
||||
bot_stats_data = bot_stats.get_stats()
|
||||
|
||||
# Get recent user activity if database is available
|
||||
recent_users = []
|
||||
if db_manager and hasattr(db_manager.backend, 'connection'):
|
||||
try:
|
||||
cursor = db_manager.backend.connection.cursor()
|
||||
cursor.execute("""
|
||||
SELECT name, display_name, interactions, last_seen
|
||||
FROM user_profiles
|
||||
ORDER BY last_seen DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
recent_users = [dict(row) for row in cursor.fetchall()]
|
||||
except:
|
||||
pass
|
||||
|
||||
return render_template('stats.html',
|
||||
db_stats=db_stats,
|
||||
bot_stats=bot_stats_data,
|
||||
recent_users=recent_users)
|
||||
|
||||
@app.route('/memory')
|
||||
@require_auth
|
||||
def memory():
|
||||
"""Memory management page"""
|
||||
if not db_manager or not db_manager.is_memory_enabled():
|
||||
return render_template('memory.html',
|
||||
memories=[],
|
||||
users=[],
|
||||
memory_disabled=True)
|
||||
|
||||
# Get recent memories
|
||||
recent_memories = []
|
||||
users_with_memories = []
|
||||
|
||||
try:
|
||||
if hasattr(db_manager.backend, 'connection'):
|
||||
cursor = db_manager.backend.connection.cursor()
|
||||
|
||||
# Get recent conversation memories
|
||||
cursor.execute("""
|
||||
SELECT cm.*, up.display_name
|
||||
FROM conversation_memories cm
|
||||
LEFT JOIN user_profiles up ON cm.user_id = up.user_id
|
||||
ORDER BY cm.timestamp DESC
|
||||
LIMIT 20
|
||||
""")
|
||||
recent_memories = [dict(row) for row in cursor.fetchall()]
|
||||
|
||||
# Get users with memories
|
||||
cursor.execute("""
|
||||
SELECT up.user_id, up.display_name, COUNT(um.id) as memory_count
|
||||
FROM user_profiles up
|
||||
LEFT JOIN user_memories um ON up.user_id = um.user_id
|
||||
GROUP BY up.user_id
|
||||
HAVING memory_count > 0
|
||||
ORDER BY memory_count DESC
|
||||
LIMIT 20
|
||||
""")
|
||||
users_with_memories = [dict(row) for row in cursor.fetchall()]
|
||||
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Error fetching memory data: {e}")
|
||||
|
||||
return render_template('memory.html',
|
||||
memories=recent_memories,
|
||||
users=users_with_memories,
|
||||
memory_disabled=False)
|
||||
|
||||
@app.route('/api/stats')
|
||||
@require_auth
|
||||
def api_stats():
|
||||
"""API endpoint for real-time stats"""
|
||||
return jsonify({
|
||||
'bot_stats': bot_stats.get_stats(),
|
||||
'db_stats': get_database_stats()
|
||||
})
|
||||
|
||||
@app.route('/api/memory/cleanup', methods=['POST'])
|
||||
@require_auth
|
||||
def api_memory_cleanup():
|
||||
"""API endpoint to cleanup old memories"""
|
||||
if not memory_manager or not memory_manager.is_enabled():
|
||||
return jsonify({'error': 'Memory system disabled'}), 400
|
||||
|
||||
try:
|
||||
days = int(request.json.get('days', 30))
|
||||
memory_manager.cleanup_old_memories(days)
|
||||
return jsonify({'success': True, 'message': f'Cleaned up memories older than {days} days'})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
def run_web_server(host='0.0.0.0', port=8080, debug=False):
|
||||
"""Run the web server"""
|
||||
import socket
|
||||
|
||||
# Get local IP for network access
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.connect(("8.8.8.8", 80))
|
||||
local_ip = s.getsockname()[0]
|
||||
s.close()
|
||||
except:
|
||||
local_ip = "localhost"
|
||||
|
||||
if logger:
|
||||
logger.info(f"Starting web UI server on {host}:{port}")
|
||||
logger.info("=" * 50)
|
||||
logger.info("🌐 WEB UI ACCESS URLS:")
|
||||
logger.info(f" Local: http://localhost:{port}")
|
||||
logger.info(f" Network: http://{local_ip}:{port}")
|
||||
logger.info("=" * 50)
|
||||
else:
|
||||
print("=" * 50)
|
||||
print("🌐 DELTA BOT WEB UI STARTED")
|
||||
print(f" Local: http://localhost:{port}")
|
||||
print(f" Network: http://{local_ip}:{port}")
|
||||
print("=" * 50)
|
||||
|
||||
app.run(host=host, port=port, debug=debug, threaded=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
port = int(os.getenv('WEB_PORT', 8080))
|
||||
debug = os.getenv('DEBUG', 'false').lower() == 'true'
|
||||
run_web_server(port=port, debug=debug)
|
||||
1
user_profiles.json
Normal file
1
user_profiles.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
Loading…
Reference in a new issue