From 052570cefba185c276dbc8a81fcb9bbfadfdbe1c Mon Sep 17 00:00:00 2001 From: milo Date: Fri, 10 Oct 2025 15:07:54 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=90=20Add=20web=20UI=20dashboard=20wit?= =?UTF-8?q?h=20bot=20launcher=20and=20real-time=20management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- Dockerfile | 11 +- bot.log | 45 +++ bot_launcher.py | 89 +++++ docker-compose.yml | 5 + memory.json | 5 + requirements-webui.txt | 12 + src/__pycache__/ai.cpython-311.pyc | Bin 20563 -> 20563 bytes src/__pycache__/autochat.cpython-311.pyc | Bin 10822 -> 10947 bytes src/__pycache__/bot.cpython-312.pyc | Bin 0 -> 34968 bytes src/__pycache__/context.cpython-311.pyc | Bin 3150 -> 3150 bytes src/__pycache__/cooldown.cpython-311.pyc | Bin 5857 -> 5857 bytes src/__pycache__/database.cpython-311.pyc | Bin 0 -> 36037 bytes src/__pycache__/database.cpython-312.pyc | Bin 30762 -> 31145 bytes src/__pycache__/enhanced_ai.cpython-311.pyc | Bin 0 -> 3026 bytes .../memory_manager.cpython-311.pyc | Bin 0 -> 8635 bytes .../memory_manager.cpython-312.pyc | Bin 0 -> 7652 bytes src/__pycache__/modelfile.cpython-311.pyc | Bin 7788 -> 7788 bytes src/__pycache__/personality.cpython-311.pyc | Bin 3661 -> 3661 bytes src/__pycache__/web_ui.cpython-312.pyc | Bin 0 -> 16883 bytes src/bot.error.log | 13 + src/bot.log | 24 ++ src/templates/base.html | 137 ++++++++ src/templates/config.html | 161 +++++++++ src/templates/dashboard.html | 191 ++++++++++ src/templates/memory.html | 225 ++++++++++++ src/templates/stats.html | 193 ++++++++++ src/user_profiles.json | 11 + src/web_ui.py | 332 ++++++++++++++++++ user_profiles.json | 1 + 29 files changed, 1453 insertions(+), 2 deletions(-) create mode 100644 bot_launcher.py create mode 100644 memory.json create mode 100644 requirements-webui.txt create mode 100644 src/__pycache__/bot.cpython-312.pyc create mode 100644 src/__pycache__/database.cpython-311.pyc create mode 100644 src/__pycache__/enhanced_ai.cpython-311.pyc create mode 100644 src/__pycache__/memory_manager.cpython-311.pyc create mode 100644 src/__pycache__/memory_manager.cpython-312.pyc create mode 100644 src/__pycache__/web_ui.cpython-312.pyc create mode 100644 src/templates/base.html create mode 100644 src/templates/config.html create mode 100644 src/templates/dashboard.html create mode 100644 src/templates/memory.html create mode 100644 src/templates/stats.html create mode 100644 src/web_ui.py create mode 100644 user_profiles.json 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 93f9780ed1a1d37efcfff6d045240ce8dcdc409b..1771a18c06aa6997c07ac8560a4caad95a44c28c 100644 GIT binary patch delta 35 ocmcb-fbsGIMy}<&yj%=Gpv$z8YmF}Bug%+aGZ}%DqP~$L0K(}C&Hw-a delta 35 ocmcb-fbsGIMy}<&yj%=Gkif8!YmF}BtWayXQ4IWI340}v!UewLvowUKYWG?NVL=JnDREFvi^Df~GSxstVB%3NMJD?y#4`$R zUaL^UsK9tjIz6={J~1=CD7CmCKd(47zC5!eBR)4ZH@~P-lV!4&k^*~CI?y~#naMs% z^W<)Er{-lO<|U`505ugg0VRrzK|~2i3HRjRN@4t9!Au~(m>WnaOpa1kOewT zn>&@cnCiJ$4L&dsBpz^BUqD4S1hg**Xy1^KpTmDcL}qU8jJO%`Gvcp^=-!Z2xFI6_ zfs<8`>mvh@_yQt2SikZxa7tg~kh#JkbAdzVhJxw^e*KQp{>krEdFn+m?f(dJ(-*=H zBf{-CmHhbe11yI-0E&fyA$3B^k bC(l-wWt_fwr+Oiij2R=)JWQfUAE+Jxh0>p^ delta 484 zcmX>cdMt!*IWI340}!xWKbPSsxsh+aG}AAZ&FiHtSPV)GK@t#9!;l452IAFl)Ud4K zU&hS9uo{RVpq8`5c(Ndu1Q#Pi4QG)^4d()>$sd_TCg&@}GYV`zuTaCt$UHe)Nr6dI zdh#Ttd4ff(Kz&8VAfg0FXmU;VQx4~Bb3TI1{zBLSBHVye z$&VjDz;gOHRTK*WeSAX7)>Di5q!yQ_3HwQNMo*K;YU)*NZ9ti#*2&A%3mJPSOK8Y4 WPTg##QOG1?%m@q@Orl61s2%`V*m6Ap diff --git a/src/__pycache__/bot.cpython-312.pyc b/src/__pycache__/bot.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64270422949a38ee9d487a8926c7ee0a043b9277 GIT binary patch literal 34968 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 delta 30 lcmX>naZZAJIWI340}%Ybb8aK|KX#U1j9kAq^Kv+{005m<3LO9d diff --git a/src/__pycache__/cooldown.cpython-311.pyc b/src/__pycache__/cooldown.cpython-311.pyc index 422a80f2790511576527e823f670e39d496a0074..5674a28c7506b8bcdbb3855a9fded3b834ab568f 100644 GIT binary patch delta 20 acmaE;`%ssAIWI340}$vjo!`iPO$-1&9tD~J delta 20 acmaE;`%ssAIWI340}!;nJGYVhniv2^0tVaw diff --git a/src/__pycache__/database.cpython-311.pyc b/src/__pycache__/database.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b7acb38c57994b4902345472ff8e42a9be80ff1 GIT binary patch literal 36037 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 Vl{8F8<>J*!@#;$f`eR7+e*jB*H=Y0h literal 0 HcmV?d00001 diff --git a/src/__pycache__/database.cpython-312.pyc b/src/__pycache__/database.cpython-312.pyc index 2cfb9353f235ab64746cf37bb4b48997e77f06c1..b5dafb0fa0111daceb07b687fe0dbccf0fa6aaca 100644 GIT binary patch delta 997 zcmZ`#ZAepL6n@X0w>h_aH(h>UsPk&{VNrsi)e>gAn$C}=t0Aj~?%wA7s@D7pqx{nc z1SJQGhzKLH)F0Uj6oLx+km|?m3W7!Try}~J83h&5dz%X>dSC8y?(>}IyytL+X5sog z@J~4|fzUH*{m^vVJ<30AFli~(&~JW(;yI#2+vaX;XgqERf$CIGa}xF?nL&$3eW^H< zsZSVY781z_1?g$M#H)`Ag#2BF&oXs9nX*G}CzAWcbDS44*jJ?>y=8$}H z5V{y3^#tfW1E-R@HT9$ma!AM$^W~CY>0ve`#CY>^bu-SCUiJc!QZH(T7j_0j(kHe@}%8Ynb%7HX|B2`z!7~ehM>u zl~`3%TkjRCZ6%eWeI_mx?2v=E)Rb0kZ)j|lCD)9;&RJRR6204Pu5Fkv9|ALu$Q!gv z{BlTc@5DFqGI)nS_j-2Ia%L_Xs93?&GBmiDoy@!{mb#-JPt@a4le-vM4SuF2>S<-v_E~gN zi*Tvue9yIvG2LBmoV(W++2L1;nv|>n7W>y5GJsn#1OO3V2fsrFzBm|$8ay3x z!$%z0V#aTw0;t7x9T&Af)eZzu=~&OEv-zKj!ttG{0PoS#)dwtI?rKkO1jwEvzp+w7 O_zZ>1>`Mry(!yVm2?-$p delta 691 zcmYjLT}V@582(;Qr#W}dbes5>y5m_`%O4DkLKiKaB7v6C4;E%y-_iNgTx+@+N_mrB z1SM|}5hA;CHVWc&;Z2aRAo3<#ph(^r8Qra)8}X*^QxxBehwpiw?|I*6?k~JdgLzSw zvk05DQ@{MnSKgQxy?CaT+ z75XeVZncTVUXsYbDQ43wYT8Y(uOYt*tQhjai`{4sRK(8nl4dH=xl=SkrwEteEINaY!5(jA)@%$>+V* zuLWpVcqFKW)_7zcg`O#69jo<#qz~lbbkFtH6~#F#$K-hLd|pa%FDuPUN^?qSne=>> zN>A!fEmn7M`IjhbsM%Z(kON8HfdA9ahqo0)~ypOMT|Cp7KOlwW19hg zv_lH11$yB_7w};ZITWaYYyq9JUF72JrM)bW{`lt2n>XM4IDah`^9a)4mmhEcoI~hetdk~w(0FnHjc<{LG;AOf*Eus&&zO9j z$83x<1XHLBaXVv(X11P<+q@x}xq8mb*Yl=am(4=G5RVH+v0j9Ef-zPfi|g_FIEP41 z6YpV!?!%j;x}+7f?AN$Hp%pa=>fdNNQquCIw8?Ao*L?lAny`VYV~?45H4}|*5x1f0 z4N9DrZ8;SijFjY(4A{0gn@%I*?j%}$`J}NlG zZMPbRy#?QydW%riC9uFB8oCyZ!-s}pn5^F>jvGxJ4I~!SBAHm*s?{VK7}iYv$?O&2 z&A|Jl3;PA%;$7a$(AyqI+wRZ<2zi-X!`>~9O=M}*PrJf1Gnn2uy>N!;!da)u!)}a$ zq<;@qxj$Tqavrwu7TV3a#Z(@lTiLYjWxVX@42(u3rJcVxH0R4aW$tAN+_b5wx{bkvY9Y!vE{#OB?QYvN;s9gWCcwucMwHhb-Ev%p zrHD6lkP|^kGy~u@b~>b?T3W+#ZAuzuvQAVPXdl2FdW{yf&6W#F6AS4$Q!#l{bx7lx z*s10tx$Tg23av27LOQak8zjo63#EaS?mHWcf${l)@hn&-247S2oM>Vg8pq9X7!;LZ zdU|1T+cwFfsT=m*+)X|Gu$i8`pTulEhr!dg-bpR6lOtfanUa<9M7@n zwL|OBdT=ASupVAm_myjqgTghK0mc=SuI!(E|0geyuZHqfpTGJ9{DhiALVymVF%}$8 z-h$#=bew#S$O-}pj|M7`KZa1^I+rgem{kC?N{m@Wh1iLs1A-|HBX(eR+f@d%t6N*c zxT{sU82+>j7C?IzDyl%?Fgq26z7AD%B8`n0kAnpoEDi=RI0aG*Y!;iphyKl%LVo`H z8~%ll1AaB+SN$~8IS`<)LJ^4+{t47-4p|un85_n9c?yjeJ7ko9cCm*Yj=2Qb6QJ?{2&5Z!~;{b7&&H?+*r9L9ZrI;LY+@VG9s?zyCiG3#bM0l1JK zwGN{{f1*iRuFDSlxdaRe`#<_qcR&J;Hs4uTB`zGM(>6_&?kM)Aa+@*aZ30IJPQOX0 zqTXbOsw(VII@Ox3I4#oDH}z(6?mF;jBI0iAPAsMD5O*Qs-JKSYPRtdSTYId*njP+} z(&3hr&TBW=GK`omTQEjz|rA7QLTeqrWa^S@Q zdVn~D;nGO$xz2hzn*y6DIG zk-T^~_ha>k>QC#zxwY`zS|G3a{8}6Y)l98ctKwR9GU8O7GF(xHJ<4!G*`+4&u{^j% z8M?beS$srda*d+wLO|K=gEFS4GW#YCZcq-KvJMwD&URvsrYu6~1S^=A{)Uo3WYPUp zT85il+aMp%_o2(e(fKbF{S3yqpGSBD`{??Mat~Gg(cD9G{%G!@4gde;9va_^pB|F; z;-`mld-3xqH?==|#|(0FVQy|u?DMbSb=*hKl^<~U6BvK4lnVF??r!uEtG^%AN$*8v H?4ADsKO-PZ literal 0 HcmV?d00001 diff --git a/src/__pycache__/memory_manager.cpython-311.pyc b/src/__pycache__/memory_manager.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..960a1eeadfc8622cfb44885c420b88acff06b10b GIT binary patch literal 8635 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 literal 0 HcmV?d00001 diff --git a/src/__pycache__/memory_manager.cpython-312.pyc b/src/__pycache__/memory_manager.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b38682d27ce4cc538567d93c7f3587669e689f7a GIT binary patch literal 7652 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 E7oBlK7XSbN literal 0 HcmV?d00001 diff --git a/src/__pycache__/modelfile.cpython-311.pyc b/src/__pycache__/modelfile.cpython-311.pyc index 3a59b5c00f11c87a5add6fdc32565cab4d7a2d52..b5a462b26c74fb99c445310b6e3a35497e2cd905 100644 GIT binary patch delta 20 acmaE3^TvjIIWI340}$vjo!`ivCrb5@3HIWI340}$vjZRB$10{}3d1LObz delta 19 ZcmX>rb5@3HIWI340}#w(*vRF~2LLlP1X2J1 diff --git a/src/__pycache__/web_ui.cpython-312.pyc b/src/__pycache__/web_ui.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d6322bb79f94a2a5609bbf840f4d41a1ba476b5 GIT binary patch literal 16883 zcmb_@X;2(jnqX#S7L|2CaU&2E1?T|8q017N4sK*4WC?VYWmlIf6DU*%&Mb_mMQ(c9 zGe$F-ksS7n&{z)PSa%qQwbOLWOtd>n&TiJzOI2*@0Vzd$Ke^}Cn-Rd8^ z-}kaIs}QB5w>zO;zL)vE@15WIzWle$ObZ3qz{&4M|93q_{W})4Dq}EpQ&KGF-g{^$yE0l&uySwThQm-hb z-i9>w>K{_Cm@gTjZ*J6j53QD_son69+GQ!NI?~i@d`P|WlzPrI^|n8xUPVg1$~5(M zJfvRLx>0UQQ+p?0&u@8!cI^_J{MK{T6eae-TJDCQWo#_DMp}-q<~M8eruECU=b_Lw ze60pO7SeDe@7fCU+puL<6X4k;>=Bx7)@ik9YKoa+swkms6D5|o8~{ei30Bv1us0B5dChztr&-)@~C2x1WEFRf&(=Mh2x=i*cT8K zGp4-Y4}0FD72~An8JF&Jjh^xGMoIK43?wzQV&Tua)n18E$_fa9kT{j{#3l*hiE+0- zG%_NHk=m0UG3X19v^YCM6Mo(qgfaO7;~_CjhUX1IJ_w_eTAbfy!}!EJNT6-u?1ayY z1JG3+L*k{N)tf=%G|h9-hxC1*l{RrdwM z0t_k)YbYt%-ce65DEQs@;3Z#pO38#(4(ml&Csq>t!x9e5d4EUaXeb~w27LZdV^_#K zfxAm;Y(G?=*m(6Yak%1*`X*I4H?-&ryWNo-ZB`q!N654#lp!O7r>@F@4T8IEtDEL9LNA;DHzE>rT^c>N|a@rKPB zwlSh+XeITYvcGf;uuqtQ?{NLJ&9V{mrI0Ahh!7Ubu!tK-EXKqENev^G!wra^>=<6L zX#2#+OE}L?r&DwyL>4d!;5hEYPKy-ji@f#)>KRfPk{N25v&FfR-*P2$9+@kLiPb(E9UPKthi2J0=a4za+@#EHU9Q_6uWOg<+T(TIa$WaF zW6@)SvAUt?$y0LODTFMOxw5%2nX6eT+kE59wKH$NK=zAR2@S8IS{yFfFDqofJSo+V z>68ie6ITS*;Q}2tY2|5JW9no2T50bX-4u$jOKJ0dCPUvAXQ@>xKB5bogc~Be7b&fpnfy&+}zi)`EbkTLB%*Ayd;Qg zwgz%k6eV|UkRU7JX&lWU^&YU>O^|z!0vSi2S6L#Vg8N*X-xD~?d-g{*>H8l@%Z)Na zYH0I^ydJ-_A2KRb6JU{WiD#(jiIdS&r{xo_8H()EArPy~IbjNAGW1LmzC%{@WE~Vy zaTgmfL8hA0L2(C^pR8}qSToVLQ>UY@=j2mo5`C*NDH#*vC|N{gA&PAx=mWjMjZ8vP z>_mistWhP3L>P&Z69{SIL<7uEeWXFxWTvp762#6(VNg1xalbFDHplpdAYof7$3i}( zZ~!z+WL<^?nt{M8Mjxm(QjLKyFmVr#23J($S7v=#BDw3AYCD4eH%LGr3i`%bMh}rf75jQrY3CZ+xX<>jE3E z-Y-}0k5_le)m^dbo-YQX{OD4JFFNLrp7Td50xO$p7k0od5(Ja-A$f)RnswYU7>$*8vRGr?U7N+I zwoJg*Y+WRI!gdkqt83JyiS?W@MAH^ha{hm`4m!D}WkKt@1O+zC^2VDP zn$&m-WiCo_a*ZLce&9;VVXuKV;h%<=24-GG?EnTjX`rdo6b*kGs3@&S9Tl5V7_9Xc>{W4O0QXXop7%E#5aygoo>!Kp1gC&>P~R_CTb##{;!^ zXE@~aBWkT;i?ha}7($Z5j(ftR3Og3^1(gipJXkdFEHo|z6?V!K@GEA?Gc14&=HV4{ z_j#`X{5}Mbs!^sG1yKx%KvJ0+VmIO=dt0G}wYp&@VsUGLA}Zaz@RP1VGDEGh7Gv(p zhLRhmYo;5vYqt603j?tYt+Sac_MFR;7bh>jeDUSEz>>XgrPOhw_ge3b{%idUC5tb_ zO1owc-_FdrQa_)!Fu9buca@^en?KmGl2>$f|7-i_D`I(@eqi9y5BU5JCV#kX&A&r2 z#@uhLIV-l@%l#MoqZ@ZG9$oB=7B?-~nwRZ4e{253stF+OOQ>c4MSFcm3H5PfK}QyI zi#0?3R+h1&$aE`@!F&-(OU#{_%&n^OPLBD+Ktui$&IsumhKKY)=<%ID4?WV4p{R89 zsB4^mGCk5M^axtu1N3Nk3_ViQNQc}_^ZP8!3S8jyu;5>kE~l*P=+c^&E~_svNm_g? zUD~FZwR8zIX}Q3>%uJcodkRfvMjV5lmju=1*Ju)`X)Q&H1JM5jAx?_VV&NzxJ>MZ~ z2Ne!v%CKj`A9fG>LzAEePWc5T!v|iMV0d6Cl}IIQOCu2^QgING4PkN|lM|Sr$5A{5 ziNf*|fpO7=<>xRt14#`FdQhUP;`8{Fh?*B5C*f3sv6c`vnb=&CB1hmQHX^+Z2~woh zSn%hf$X*&q(w2Ss(8WVnM(628;iL91Z9Pbsz4W*CydR8-%pX=%+AMeoZLZk!31t@C zmr$8Q%5(r_mUQGYx6B;mZ{-?0HkyzoA-|EN4s)lSxwX~NX<J-vDb|t(R2z;wmZh9)>8AlQKJg+ z-yRK_&VCes^~cv4Lp9}rOB(^IU}GqD9=MXuej4e?D-&#@zioy0vfE{0H3k;HQ_$BzQkS`+;USeq7OY7fAv1Ri4Oo)~}%12t9= zH<*%*Cfu5#=J+HPcFL`d)->vYVSoi`N!{wN1{h`_`U6Yx|M#a@tq{W51p^cl6D|sNMM`=lsJe0}t;@XtH1G%Izwr zJ}ozQ9bi7Kwso~IpYEnH-(rOHKHZp}SC2#0RPVaRSAe79+K@|sMu&B7Jf`zo4Jyf# zK)2WJ3-rQI)6<5V22DEw3zCUs9rpNw$wQV%mVVySFd7c{!B092c;O)Athx^r^Vv|s z?NehU8ZX%y;(q`r)tttjC8~+&L{_r&8e~tcRiVv#d*THEeieRF3nVktJuo)Q{^qAM zeJl2gn7s<_1&(+@wOmjgE2y0rxNR+oZrrw9S&RQwJMNhn+pbkmfp-y`F=A>2LL2st zteI@=8cycj?T|cfvT?#-vXJ$_MJ6k^li-{HTN+q~a~w_{B388>GF$J_WVXN*MpHn< z=ER>ulH#ZW0wp-a8F>B%zS{sv>S(PcbNd#XmaJ`2woM(2EislxRBjaZ*&~BPaBN`; z2;tEXFRd9NZU!>Ko6tBhk99GxZyxaXg%hKqsB{irI8)y8Xb9C~2(HmIn-3HWN}DyF zK|uoU)IYU7Ru!&~ZI31G86VqT&7&cBBeut9;ECr$MrleJB}ZB{KN=^@YGaZd9}UZ! zv3*T!KS2+O_~$K;#tJ=n+_)`|9k(@Q+%_`q%*VF+ow z9yjLGA+*PK7))3=pG0a(JWy-_&w1@gQHpeSu8}Lq=E1Qw;q}7l_V9$?KULv82GI*} z?ClJXs+Kz#>=1n5pK>N@)C!TZq=yVKIoHf+gQ{?scB7li3GczFwicqrIY5L{ zjZKwe=!aHV2#^TC(Yp{%*!>tS5%Po;22R_>5daZU(@5%=B1#4k?LLr(s6wb}Chj@S zR<~mB;N%{~v^WPZ-h`iI1Q4*)xh=+n^h%!d^#ijUC}=jvJ4Nr7zFjI?>sR)5eR$?K zXQEG^k@r16+ZVT$%eL})%R)!Y*0^YvZ7ttgH(V#qG}&6c(%$!3>Bps?2juoM55i{3 zw*82@^!ntD>1)%nb=yj7$A@jdX^Zxql3PzlpZChGUcC~xP#Lq;FZg6z)3?@&cTT>0 z=It}Gb;nBI;r~AJKm1?#qtCr44~Vfo>46&2hA!FG{lE}<4u9tV*dHAftew$`mY^X$^mQ}LtE%SWG&z7Ud+j>n#Pab2xm+17_` zl_l%7b$;0YoBrq_m)!PT)Gf$uf?mtKurp?BSeTM+&EHy!ub;#dW!YM@(%$vy$LHc&nd*^Gw2aV3W3k7fu$u05u6>0nZpN zZAgT086);V-Ml9{GOE$9Q$l)EJ&RCyCgR1R>~L zhhx2!z8>btv99i8&W_V5c)Gd=JJ$pDA3A(!$hl+tnpPqha0G|k33CWd8e;7zaIobC zcc?xySDZho{dQCbj|?R0R7p!v7;CXrC*L z7@CxDx01;do%rG-w@?$|CnwlCH#6&_eM zSj;&OH@27IAOTG6RmxbJeUCC`=B(PNolUBJGZJcXU?Xl%8JJBTDjV9Ud-`_D)rCs!os|MPf^R+GezElo9{@BoN?Rbj1m6_YI zoxN3V!2EV2rcW6glS1)K zijtDj zcvPWQhBFd9pDvb$H%+ta&Pccm?91$>Zl2szlu=8h@GuG9ObIegnEUFUB-~&K9BCXW zqleL$h!Rl&+`!L>TT*3y_tvl!B;*A1^9CAf>zotg4XONZJ!c|p4pb%Gd8xY&o!-DY z-2L4>L(Zp<92!{bzl3I-M+TrmLn7+Uxer@T6xLzU*(OxL2ozw0fpO=fIOoo_oYUii zUAhi!V&KV!*A2fe;e?$CH2C-$r$=%o=9ENvu0xnc1Wb&tgL+_2v}JQn1aMB0y}>!r zCukj(-eX6OKZ~LySz#SaU;Bwe1HEhd?A-5MJ2kii^!ZI49hu*CC{@Aq!1P8EN<4A! zq8hK_AzpS+;(H+Ch{z!HEULb!he;z5HE;1cCN4~#!{iJkHAal3CQ?qDFeQ)dbN8e# zJgRR(Rj|E<)rdFw0ckahFEF_cKWQWEiW%zvfV3#Q?uwT+%Vo_=WvwfA$J~YGE!E3a zo0sd+otKU7Jo7FPIz`1ZN0v)UqihknnfI+&bJsYJA1=W_@~Q6PRVo8zSO&Z+ zzSMKTy&uqvQna{r$=3FNe_S=fTlEz1m+hN7o2Xm)xt$H{tu2`U#G2c=i~Xd`fcXXs zrcLIq8s<~l(Y1;BbdwSC5er15`_gE}xE{nUcnmVBnsJI-Lg&T+lDIS}l9Ytn6P~`V zYn>Rn6mhmcd&mh#Gvg3;5hkJfU?cnMk#CUG9t>C`K4R2xYFH69;}m6y=uQ|jMLeEF z$5G5Mmc-%1FW^l9Y#RyuajK?c%S&|!ujFru4J*h5$Q3#rTq2YxyfkX@7 zz}2I9G|cF-)F8pRqpvHU0WuJbY@U0I;mxV*m&UP^tVO5j4Eo4Y#%LKfkh4E4I(8ri zh}=_TsV%Z;&E5ddMPeD8;CWG*79N2iztP36G$lclBN_!ni2AO;ctKP4OiAD28Ou$o}e>g1U@*0yY6*>M`tOLM}eFz_D7wFQjhB ztMF9Bp+o!-d?+Gr#%z9~yKX?A8vFEVyFGHz z*_hoM<-FPf7NCQc-pQLk`X39UMb$BT&BCszz2Qr)ftcTo>QUDF?e_Lo>f=^(#}X(oyPpv7;C<(j1 zr^$B@>s1mWb?%Uq$(1gwUv*sYwwsxni3g#O26S2Q>;)sZE$$`U7Fm(pV-rE*Uxjab zAYw@bU7G|Rz=vQFQ&R)^`wk@6#=jo%{ai zYXT1hS8rUp;yl^iff(C6JG%!5oyU*$54J?gKymEHX!{n(jE2MGEsc$OZ(1UG@N_^3 zPlm*EE!yh|{QiX??CS0xYIk-V8PZS>4z(W}>h2nX04o-U1HqFJWUd%__{MBR;m|+s z;RU|NszCHPgkHeca_v3tLj&DIie>Of=QHlE-ec{D6?AK39ED{{ zs7xxXFE|`hjN>9`2MPn8Yw&7J1Zy(He}=c>zhLsOnEV=nX-VP8nB2Z`33&<(3NBSE z$bx!q{Q4@fLZ86B7T;eL1^YX9mdu`wA2j+J`D8ejm&~A@a#<@})@AEOYqW6tV*X;r zqUQs4@ug_~{w3}};?0#l_-0Fk%5PZW8nr^0uq-#6c~aC9NOFbp2k zC2kKq#ag#n)2qEd>%Edc*R^2$fSu|6lHK>?Y84IhkmvYo?!JV)?62BOy0fVNn#FeK zgWU>Y@^JDGLGm1IGxxJcjtwcsuI`TGz2Mo0Z#e|6;(tM?4op5o0CPA5aYgP?;XIykU?XnwiOPsSbQUH<{U(5@ul-oZjmZcmsJALc zF$9q|Bz{4h!KZkbp}OdZC>cO0LhvOry^0ZHD1q4!3RtTI!-qHO z*9`l`ub>(VQRyfMO^7$4>F*5;&HiAa=ojcX^?Zza{&$r5ca)X!X$f@h2mFqiYvF&)V#LIn3udnE!u9Q4+8K literal 0 HcmV?d00001 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