restart
frdel committed
Dec 3, 2024 at 21:10 UTC
961dbc405af8a784ecadcfcbcd7652d1f8d9be28
7 files changed
+104
-21
python/api/restart.py
new
+9
@@ -0,0 +1,9 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from python.helpers import process
5
+
6
+class Restart(ApiHandler):
7
+ async def process(self, input: dict, request: Request) -> dict | Response:
8
+ process.reload()
9
+ return Response(status=200)
\ No newline at end of file
python/helpers/api.py
+3
@@ -6,6 +6,9 @@ from agent import AgentContext
6
from initialize import initialize
7
from python.helpers.print_style import PrintStyle
8
from python.helpers.errors import format_error
9
+from werkzeug.serving import make_server
10
+
11
+
12
13
14
class ApiHandler:
python/helpers/process.py
new
+35
@@ -0,0 +1,35 @@
1
+import os
2
+import sys
3
+from python.helpers import runtime
4
+
5
+_server = None
6
+
7
+def set_server(server):
8
+ global _server
9
+ _server = server
10
+
11
+def get_server(server):
12
+ global _server
13
+ return _server
14
+
15
+def stop_server():
16
+ global _server
17
+ if _server:
18
+ _server.shutdown()
19
+ _server = None
20
+
21
+def reload():
22
+ stop_server()
23
+ if runtime.is_dockerized():
24
+ exit_process()
25
+ else:
26
+ restart_process()
27
+
28
+def restart_process():
29
+ print("Restarting process...")
30
+ python = sys.executable
31
+ os.execv(python, [python] + sys.argv)
32
+
33
+def exit_process():
34
+ print("Exiting process...")
35
+ sys.exit(0)
\ No newline at end of file
run_ui.py
+13
-9
@@ -5,7 +5,7 @@ from flask import Flask, request, Response
5
from flask_basicauth import BasicAuth
6
from python.helpers import files, git
7
from python.helpers.files import get_abs_path
8
-from python.helpers import persist_chat, runtime, dotenv
8
+from python.helpers import persist_chat, runtime, dotenv, process
9
from python.helpers.cloudflare_tunnel import CloudflareTunnel
10
from python.helpers.extract_tools import load_classes_from_folder
11
from python.helpers.api import ApiHandler
@@ -51,20 +51,22 @@ async def serve_index():
51
version_no=gitinfo["version"],
52
version_time=gitinfo["commit_time"],
53
)
54
+
55
56
def run():
57
print("Initializing framework...")
58
59
# Suppress only request logs but keep the startup messages
60
from werkzeug.serving import WSGIRequestHandler
61
+ from werkzeug.serving import make_server
62
63
class NoRequestLoggingWSGIRequestHandler(WSGIRequestHandler):
64
def log_request(self, code="-", size="-"):
65
pass # Override to suppress request logging
66
67
# Get configuration from environment
66
- port = runtime.get_arg("port") or int(os.environ.get("WEB_UI_PORT", 0)) or None
67
- host = runtime.get_arg("host") or os.environ.get("WEB_UI_HOST") or None
68
+ port = runtime.get_arg("port") or int(os.environ.get("WEB_UI_PORT", 0)) or 5000
69
+ host = runtime.get_arg("host") or os.environ.get("WEB_UI_HOST") or "localhost"
70
use_cloudflare = (
71
runtime.get_arg("cloudflare_tunnel")
72
or os.environ.get("USE_CLOUDFLARE", "false").lower() == "true"
@@ -83,6 +85,8 @@ def run():
85
# initialize contexts from persisted chats
86
persist_chat.load_tmp_chats()
87
88
+ server = None
89
+
90
def register_api_handler(app, handler):
91
name = handler.__module__.split(".")[-1]
92
instance = handler(app, lock)
@@ -101,14 +105,14 @@ def run():
105
for handler in handlers:
106
register_api_handler(app, handler)
107
104
-
105
-
106
-
108
try:
109
+ server = make_server(host=host, port=port, app=app, request_handler=NoRequestLoggingWSGIRequestHandler, threaded=True)
110
+ process.set_server(server)
111
+ server.serve_forever()
112
# Run Flask app
109
- app.run(
110
- request_handler=NoRequestLoggingWSGIRequestHandler, port=port, host=host
111
- )
113
+ # app.run(
114
+ # request_handler=NoRequestLoggingWSGIRequestHandler, port=port, host=host
115
+ # )
116
finally:
117
# Clean up tunnel if it was started
118
if tunnel:
webui/index.css
+3
-1
@@ -780,7 +780,9 @@ font-size: var(--font-size-small)
780
margin-bottom: var(--spacing-xs);
781
padding: var(--spacing-sm) 0.75rem;
782
text-wrap: nowrap;
783
- width: 48%;
783
+ width: calc(50% - var(--spacing-xs));
784
+ float:left;
785
+ margin-right: var(--spacing-xs);
786
max-height: 2.3rem;
787
-webkit-transition: all var(--transition-speed), transform 0.1s ease-in-out;
788
transition: all var(--transition-speed), transform 0.1s ease-in-out;
webui/index.html
+13
-6
@@ -20,10 +20,10 @@
20
</script>
21
22
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.14.3/dist/cdn.min.js"></script>
23
- <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.3/dist/cdn.min.js"></script>
23
+ <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.3/dist/cdn.min.js"></script>
24
25
-<script src="https://cdn.jsdelivr.net/npm/ace-builds@1.36.5/src-noconflict/ace.js"></script>
26
-<link href="https://cdn.jsdelivr.net/npm/ace-builds@1.36.5/css/ace.min.css" rel="stylesheet">
25
+ <script src="https://cdn.jsdelivr.net/npm/ace-builds@1.36.5/src-noconflict/ace.js"></script>
26
+ <link href="https://cdn.jsdelivr.net/npm/ace-builds@1.36.5/css/ace.min.css" rel="stylesheet">
27
<!-- KaTeX CSS -->
28
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css" crossorigin="anonymous">
29
@@ -73,6 +73,7 @@
73
<button class="config-button" id="newChat" @click="newChat()">New Chat</button>
74
<button class="config-button" id="loadChats" @click="loadChats()">Load Chat</button>
75
<button class="config-button" id="loadChat" @click="saveChat()">Save Chat</button>
76
+ <button class="config-button" id="restart" @click="restart()">Restart</button>
77
<button class="config-button" id="settings" @click="settingsModalProxy.openModal()"><svg
78
xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-5.0 -17.0 110.0 135.0"
79
fill="currentColor" width="24" height="24">
@@ -83,6 +84,7 @@
84
d="m50.301 66.5c-9 0-16.398-7.3008-16.398-16.398 0-9.1016 7.3008-16.398 16.398-16.398 9.1016 0 16.398 7.3008 16.398 16.398 0 9.0977-7.3984 16.398-16.398 16.398zm0-25.5c-5 0-9.1016 4.1016-9.1016 9.1016s4.1016 9.1016 9.1016 9.1016 9.1016-4.1016 9.1016-9.1016c-0.003906-5-4.1016-9.1016-9.1016-9.1016z">
85
</path>
86
</svg>Settings</button>
87
+
88
</div>
89
<!-- Chats List -->
90
<div class="config-section" id="chats-section" x-data="{ contexts: [], selected: '' }"
@@ -389,9 +391,14 @@
391
</button>
392
393
<button class="text-button" id="nudges_window" @click="nudge()">
392
- <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 49 58" fill="currentColor">
393
- <path d="m11.97,16.32c-.46,0-.91-.25-1.15-.68-.9-1.63-1.36-3.34-1.36-5.1C9.45,4.73,14.18,0,20,0s10.55,4.73,10.55,10.55c0,.87-.13,1.75-.41,2.76-.19.7-.9,1.13-1.62.93-.7-.19-1.12-.92-.93-1.62.21-.79.31-1.44.31-2.07,0-4.36-3.55-7.91-7.91-7.91s-7.91,3.55-7.91,7.91c0,1.3.35,2.59,1.03,3.82.36.64.13,1.44-.51,1.79-.21.11-.42.17-.64.17Z" stroke-width="0.5" stroke="currentColor"/>
394
- <path d="m34.5,58h-6.18c-3.17,0-6.15-1.23-8.39-3.47L1.16,35.75c-1.54-1.54-1.54-4.05,0-5.59,2.4-2.4,6.27-2.68,8.99-.64l4.58,3.44V10.55c0-2.91,2.36-5.27,5.27-5.27s5.27,2.36,5.27,5.27v8.62c.78-.45,1.68-.71,2.64-.71,2.3,0,4.26,1.48,4.98,3.53.84-.56,1.85-.89,2.93-.89,2.3,0,4.26,1.48,4.98,3.53.84-.56,1.85-.89,2.93-.89,2.91,0,5.27,2.36,5.27,5.27v14.5c0,8-6.51,14.5-14.5,14.5ZM6.03,30.79c-1.1,0-2.19.42-3.01,1.23-.51.51-.51,1.35,0,1.86l18.77,18.78c1.74,1.74,4.06,2.7,6.53,2.7h6.18c6.54,0,11.86-5.32,11.86-11.86v-14.5c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v1.32c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32v-3.95c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v3.95c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32v-6.59c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v6.59c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32V10.55c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v25.05c0,.5-.28.95-.73,1.18s-.98.18-1.38-.12l-6.69-5.02c-.75-.56-1.65-.84-2.54-.84Z" stroke-width="0.5" stroke="currentColor"/>
394
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 49 58"
395
+ fill="currentColor">
396
+ <path
397
+ d="m11.97,16.32c-.46,0-.91-.25-1.15-.68-.9-1.63-1.36-3.34-1.36-5.1C9.45,4.73,14.18,0,20,0s10.55,4.73,10.55,10.55c0,.87-.13,1.75-.41,2.76-.19.7-.9,1.13-1.62.93-.7-.19-1.12-.92-.93-1.62.21-.79.31-1.44.31-2.07,0-4.36-3.55-7.91-7.91-7.91s-7.91,3.55-7.91,7.91c0,1.3.35,2.59,1.03,3.82.36.64.13,1.44-.51,1.79-.21.11-.42.17-.64.17Z"
398
+ stroke-width="0.5" stroke="currentColor" />
399
+ <path
400
+ d="m34.5,58h-6.18c-3.17,0-6.15-1.23-8.39-3.47L1.16,35.75c-1.54-1.54-1.54-4.05,0-5.59,2.4-2.4,6.27-2.68,8.99-.64l4.58,3.44V10.55c0-2.91,2.36-5.27,5.27-5.27s5.27,2.36,5.27,5.27v8.62c.78-.45,1.68-.71,2.64-.71,2.3,0,4.26,1.48,4.98,3.53.84-.56,1.85-.89,2.93-.89,2.3,0,4.26,1.48,4.98,3.53.84-.56,1.85-.89,2.93-.89,2.91,0,5.27,2.36,5.27,5.27v14.5c0,8-6.51,14.5-14.5,14.5ZM6.03,30.79c-1.1,0-2.19.42-3.01,1.23-.51.51-.51,1.35,0,1.86l18.77,18.78c1.74,1.74,4.06,2.7,6.53,2.7h6.18c6.54,0,11.86-5.32,11.86-11.86v-14.5c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v1.32c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32v-3.95c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v3.95c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32v-6.59c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v6.59c0,.73-.59,1.32-1.32,1.32s-1.32-.59-1.32-1.32V10.55c0-1.45-1.18-2.64-2.64-2.64s-2.64,1.18-2.64,2.64v25.05c0,.5-.28.95-.73,1.18s-.98.18-1.38-.12l-6.69-5.02c-.75-.56-1.65-.84-2.54-.84Z"
401
+ stroke-width="0.5" stroke="currentColor" />
402
</svg>
403
<p>Nudge</p>
404
</button>
webui/index.js
+28
-5
@@ -497,6 +497,27 @@ window.nudge = async function () {
497
}
498
}
499
500
+window.restart = async function () {
501
+ try {
502
+ const resp = await sendJsonData("/restart", {});
503
+ } catch (e) {
504
+ //error expected here
505
+ toast("Restarting...", "info", 0)
506
+ while (true) {
507
+ try {
508
+ // try health check until server is back up again
509
+ const resp = await sendJsonData("/health", {});
510
+ await new Promise(resolve => setTimeout(resolve, 250));
511
+ toast("Restarted", "success", 2000)
512
+ return;
513
+ } catch (e) {
514
+ continue;
515
+ }
516
+ }
517
+
518
+ }
519
+}
520
+
521
// Modify this part
522
document.addEventListener('DOMContentLoaded', () => {
523
const isDarkMode = localStorage.getItem('darkMode') !== 'false';
@@ -658,7 +679,7 @@ function removeClassFromElement(element, className) {
679
}
680
681
661
-function toast(text, type = 'info') {
682
+function toast(text, type = 'info', timeout = 5000) {
683
const toast = document.getElementById('toast');
684
685
// Update the toast content and type
@@ -694,12 +715,14 @@ function toast(text, type = 'info') {
715
};
716
717
// Clear any existing timeout
697
- clearTimeout(toast.timeoutId);
718
+ if (toast.timeoutId)
719
+ clearTimeout(toast.timeoutId);
720
721
// Automatically close the toast after 10 seconds
700
- toast.timeoutId = setTimeout(() => {
701
- hideToast();
702
- }, 10000);
722
+ if (timeout)
723
+ toast.timeoutId = setTimeout(() => {
724
+ hideToast();
725
+ }, timeout);
726
}
727
728
function hideToast() {