diff --git a/Dockerfile b/Dockerfile index 8cbf272..45a774f 100644 --- a/Dockerfile +++ b/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"] diff --git a/bot.log b/bot.log index fda1384..11f00ee 100644 --- a/bot.log +++ b/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] ================================================== diff --git a/bot_launcher.py b/bot_launcher.py new file mode 100644 index 0000000..48b3252 --- /dev/null +++ b/bot_launcher.py @@ -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() \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 94779c8..096c93f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/memory.json b/memory.json new file mode 100644 index 0000000..70d2d2d --- /dev/null +++ b/memory.json @@ -0,0 +1,5 @@ +{ + "conversations": {}, + "user_memories": {}, + "global_events": [] +} \ No newline at end of file diff --git a/requirements-webui.txt b/requirements-webui.txt new file mode 100644 index 0000000..013decd --- /dev/null +++ b/requirements-webui.txt @@ -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 \ No newline at end of file diff --git a/src/__pycache__/ai.cpython-311.pyc b/src/__pycache__/ai.cpython-311.pyc index 93f9780..1771a18 100644 Binary files a/src/__pycache__/ai.cpython-311.pyc and b/src/__pycache__/ai.cpython-311.pyc differ diff --git a/src/__pycache__/autochat.cpython-311.pyc b/src/__pycache__/autochat.cpython-311.pyc index 3bb1c7b..2d03a53 100644 Binary files a/src/__pycache__/autochat.cpython-311.pyc and b/src/__pycache__/autochat.cpython-311.pyc differ diff --git a/src/__pycache__/bot.cpython-312.pyc b/src/__pycache__/bot.cpython-312.pyc new file mode 100644 index 0000000..6427042 Binary files /dev/null and b/src/__pycache__/bot.cpython-312.pyc differ diff --git a/src/__pycache__/context.cpython-311.pyc b/src/__pycache__/context.cpython-311.pyc index 1202321..42837d3 100644 Binary files a/src/__pycache__/context.cpython-311.pyc and b/src/__pycache__/context.cpython-311.pyc differ diff --git a/src/__pycache__/cooldown.cpython-311.pyc b/src/__pycache__/cooldown.cpython-311.pyc index 422a80f..5674a28 100644 Binary files a/src/__pycache__/cooldown.cpython-311.pyc and b/src/__pycache__/cooldown.cpython-311.pyc differ diff --git a/src/__pycache__/database.cpython-311.pyc b/src/__pycache__/database.cpython-311.pyc new file mode 100644 index 0000000..6b7acb3 Binary files /dev/null and b/src/__pycache__/database.cpython-311.pyc differ diff --git a/src/__pycache__/database.cpython-312.pyc b/src/__pycache__/database.cpython-312.pyc index 2cfb935..b5dafb0 100644 Binary files a/src/__pycache__/database.cpython-312.pyc and b/src/__pycache__/database.cpython-312.pyc differ diff --git a/src/__pycache__/enhanced_ai.cpython-311.pyc b/src/__pycache__/enhanced_ai.cpython-311.pyc new file mode 100644 index 0000000..74ac26f Binary files /dev/null and b/src/__pycache__/enhanced_ai.cpython-311.pyc differ diff --git a/src/__pycache__/memory_manager.cpython-311.pyc b/src/__pycache__/memory_manager.cpython-311.pyc new file mode 100644 index 0000000..960a1ee Binary files /dev/null and b/src/__pycache__/memory_manager.cpython-311.pyc differ diff --git a/src/__pycache__/memory_manager.cpython-312.pyc b/src/__pycache__/memory_manager.cpython-312.pyc new file mode 100644 index 0000000..b38682d Binary files /dev/null and b/src/__pycache__/memory_manager.cpython-312.pyc differ diff --git a/src/__pycache__/modelfile.cpython-311.pyc b/src/__pycache__/modelfile.cpython-311.pyc index 3a59b5c..b5a462b 100644 Binary files a/src/__pycache__/modelfile.cpython-311.pyc and b/src/__pycache__/modelfile.cpython-311.pyc differ diff --git a/src/__pycache__/personality.cpython-311.pyc b/src/__pycache__/personality.cpython-311.pyc index 08c5eb8..e7981b7 100644 Binary files a/src/__pycache__/personality.cpython-311.pyc and b/src/__pycache__/personality.cpython-311.pyc differ diff --git a/src/__pycache__/web_ui.cpython-312.pyc b/src/__pycache__/web_ui.cpython-312.pyc new file mode 100644 index 0000000..9d6322b Binary files /dev/null and b/src/__pycache__/web_ui.cpython-312.pyc differ diff --git a/src/bot.error.log b/src/bot.error.log index e69de29..bae8bb3 100644 --- a/src/bot.error.log +++ b/src/bot.error.log @@ -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' diff --git a/src/bot.log b/src/bot.log index 0893743..3355ca6 100644 --- a/src/bot.log +++ b/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' diff --git a/src/templates/base.html b/src/templates/base.html new file mode 100644 index 0000000..f82f359 --- /dev/null +++ b/src/templates/base.html @@ -0,0 +1,137 @@ + + + + + + {% block title %}Delta Bot Dashboard{% endblock %} + + + + + +
+
+ + + + +
+
+

{% block page_title %}{% endblock %}

+
+
+ + Online + +
+
+
+ + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} + + {% block content %}{% endblock %} +
+
+
+ + + + + {% block scripts %}{% endblock %} + + \ No newline at end of file diff --git a/src/templates/config.html b/src/templates/config.html new file mode 100644 index 0000000..d116cd3 --- /dev/null +++ b/src/templates/config.html @@ -0,0 +1,161 @@ +{% extends "base.html" %} + +{% block title %}Configuration - Delta Bot{% endblock %} +{% block page_title %}Configuration{% endblock %} + +{% block content %} +
+
+ +
+
+
+
Database Settings
+
+
+
+ + +
SQLite is recommended for better performance and data integrity.
+
+ +
+ + +
Path to SQLite database file (only used if SQLite backend is selected).
+
+
+
+
+ + +
+
+
+
Memory Settings
+
+
+
+
+ + +
+
Allows the bot to remember conversations and user preferences. Disable for privacy-focused deployments.
+
+ +
+ + + {{ settings.memory.importance_threshold or 0.3 }} +
Minimum importance score (0.0-1.0) for storing memories. Higher values store fewer memories.
+
+
+
+
+
+ +
+ +
+
+
+
AutoChat Settings
+
+
+
+
+ + +
+
Allow the bot to react to messages with emojis.
+
+ +
+ + + {{ ((settings.autochat.emoji_reaction_chance or 0.1) * 100)|round|int }}% +
Probability of reacting to any given message.
+
+
+
+
+ + +
+
+
+
Context Settings
+
+
+
+
+ + +
+
Include recent messages in AI responses for better context awareness.
+
+ +
+ + +
Maximum number of recent messages to include in context (1-50).
+
+
+
+
+
+ + +
+
+
+
+ +
+ + + Some changes may require restarting the bot to take effect. + +
+
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/src/templates/dashboard.html b/src/templates/dashboard.html new file mode 100644 index 0000000..85af1fa --- /dev/null +++ b/src/templates/dashboard.html @@ -0,0 +1,191 @@ +{% extends "base.html" %} + +{% block title %}Dashboard - Delta Bot{% endblock %} +{% block page_title %}Dashboard{% endblock %} + +{% block content %} +
+ +
+
+
+
+
+
{{ bot_stats.uptime }}
+
Uptime
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
{{ bot_stats.message_count }}
+
Messages Processed
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
{{ bot_stats.unique_users }}
+
Active Users
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
{{ db_stats.total_users if db_stats.total_users is defined else '?' }}
+
Total Profiles
+
+
+ +
+
+
+
+
+
+ +
+ +
+
+
+
System Status
+
+
+
+
Database Backend:
+
+ {{ db_stats.backend_type|title if db_stats.backend_type else 'Unknown' }} +
+
+
+
Memory System:
+
+ {% if db_stats.memory_enabled %} + Enabled + {% else %} + Disabled + {% endif %} +
+
+
+
Last Activity:
+
{{ bot_stats.last_activity }}
+
+
+
Active Channels:
+
{{ bot_stats.active_channels }}
+
+
+
+
+ + +
+
+
+
Database Info
+
+
+ {% if db_stats.error %} +
+ {{ db_stats.error }} +
+ {% else %} +
+
Total Users:
+
{{ db_stats.total_users }}
+
+ {% if db_stats.memory_enabled %} +
+
Stored Memories:
+
{{ db_stats.total_memories }}
+
+
+
Conversations:
+
{{ db_stats.total_conversations }}
+
+ {% else %} +
+ Memory system is disabled +
+ {% endif %} + {% endif %} +
+
+
+
+ + +
+
+
+
+
Quick Actions
+
+
+
+ + + +
+ +
+
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/src/templates/memory.html b/src/templates/memory.html new file mode 100644 index 0000000..6391d5e --- /dev/null +++ b/src/templates/memory.html @@ -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 %} +
+ + Memory System Disabled
+ The memory system is currently disabled. Enable it in the configuration to use memory features. +
+{% else %} +
+ +
+
+
+
Memory Actions
+
+
+
+ + + +
+
+
+
+ + +
+
+
+
Users with Memories
+
+
+ {% if users %} +
+ + + + + + + + + + {% for user in users %} + + + + + + {% endfor %} + +
UserMemory CountActions
{{ user.display_name or 'Unknown User' }}{{ user.memory_count }} + + +
+
+ {% else %} +

No users with stored memories found.

+ {% endif %} +
+
+
+
+ + +
+
+
+
+
Recent Conversation Memories
+
+
+ {% if memories %} +
+ + + + + + + + + + + + {% for memory in memories %} + + + + + + + + {% endfor %} + +
TimestampUserContentImportanceActions
{{ memory.timestamp[:16] if memory.timestamp else 'Unknown' }}{{ memory.display_name or 'Unknown' }} +
+ {{ memory.content[:100] }}{% if memory.content|length > 100 %}...{% endif %} +
+
+ {% set importance = memory.importance or 0 %} + {% if importance >= 0.8 %} + High + {% elif importance >= 0.5 %} + Medium + {% else %} + Low + {% endif %} + + + +
+
+ {% else %} +

No conversation memories found.

+ {% endif %} +
+
+
+
+{% endif %} + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/src/templates/stats.html b/src/templates/stats.html new file mode 100644 index 0000000..14bb8db --- /dev/null +++ b/src/templates/stats.html @@ -0,0 +1,193 @@ +{% extends "base.html" %} + +{% block title %}Statistics - Delta Bot{% endblock %} +{% block page_title %}Statistics{% endblock %} + +{% block content %} +
+ +
+
+
+
Bot Activity
+
+
+ +
+
+
+ + +
+
+
+
Top Active Users
+
+
+ {% if bot_stats.most_active_users %} + {% for user_id, count in bot_stats.most_active_users %} +
+ User {{ user_id[:8] }}... + {{ count }} +
+ {% endfor %} + {% else %} +

No user activity recorded yet.

+ {% endif %} +
+
+
+
+ +
+ +
+
+
+
Database Statistics
+
+
+
+
+
+

{{ db_stats.total_users if db_stats.total_users is defined else '?' }}

+

Total User Profiles

+
+
+
+
+

{{ db_stats.total_memories if db_stats.total_memories is defined else '?' }}

+

Stored Memories

+
+
+
+
+
+
+
+

{{ db_stats.total_conversations if db_stats.total_conversations is defined else '?' }}

+

Conversation Records

+
+
+
+
+

{{ db_stats.backend_type|title if db_stats.backend_type else '?' }}

+

Backend Type

+
+
+
+
+
+
+ + +
+
+
+
Recent User Activity
+
+
+ {% if recent_users %} +
+ + + + + + + + + + {% for user in recent_users %} + + + + + + {% endfor %} + +
UserInteractionsLast Seen
{{ user.display_name or user.name or 'Unknown' }}{{ user.interactions or 0 }}{{ user.last_seen[:16] if user.last_seen else 'Never' }}
+
+ {% else %} +

No user data available.

+ {% endif %} +
+
+
+
+ + +
+
+
+
+
System Information
+
+
+
+
+ Bot Uptime:
+ {{ bot_stats.uptime }} +
+
+ Messages Processed:
+ {{ bot_stats.message_count }} +
+
+ Active Channels:
+ {{ bot_stats.active_channels }} +
+
+ Last Activity:
+ {{ bot_stats.last_activity }} +
+
+
+
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/src/user_profiles.json b/src/user_profiles.json index ecff58d..22aadbd 100644 --- a/src/user_profiles.json +++ b/src/user_profiles.json @@ -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 } } \ No newline at end of file diff --git a/src/web_ui.py b/src/web_ui.py new file mode 100644 index 0000000..e7cb6ff --- /dev/null +++ b/src/web_ui.py @@ -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) \ No newline at end of file diff --git a/user_profiles.json b/user_profiles.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/user_profiles.json @@ -0,0 +1 @@ +{} \ No newline at end of file