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() {