API separation
frdel committed
Nov 21, 2024 at 18:47 UTC
c0947e30c757ecdfd08117b12ffc4d1d08543c9e
28 files changed
+681
-768
docker/exe/fs/exe/node_eval.js
+16
-11
@@ -4,20 +4,25 @@ const vm = require('vm');
4
const path = require('path');
5
const Module = require('module');
6
7
-// Enhance `require` to search CWD first, then globally
7
+ // Enhance `require` to search CWD and parent directories first, then globally
8
function customRequire(moduleName) {
9
- try {
10
- // Try resolving from CWD's node_modules
11
- const cwdPath = path.resolve(process.cwd(), 'node_modules', moduleName);
12
- return require(cwdPath);
13
- } catch (cwdErr) {
9
+ let currentDir = process.cwd();
10
+ const root = path.parse(currentDir).root;
11
+
12
+ do {
13
try {
15
- // Try resolving as a global module
16
- return require(moduleName);
17
- } catch (globalErr) {
18
- console.error(`Cannot find module: ${moduleName}`);
19
- throw globalErr;
14
+ const modulePath = path.join(currentDir, 'node_modules', moduleName);
15
+ return require(modulePath);
16
+ } catch (err) {
17
+ currentDir = path.dirname(currentDir);
18
}
19
+ } while (currentDir !== root);
20
+
21
+ try {
22
+ return require(moduleName);
23
+ } catch (globalErr) {
24
+ console.error(`Cannot find module: ${moduleName}`);
25
+ throw globalErr;
26
}
27
}
28
python/api/chat_export.py
new
+19
@@ -0,0 +1,19 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from python.helpers import persist_chat
5
+
6
+class ExportChat(ApiHandler):
7
+ async def process(self, input: dict, request: Request) -> dict | Response:
8
+ ctxid = input.get("ctxid", "")
9
+ if not ctxid:
10
+ raise Exception("No context id provided")
11
+
12
+ context = self.get_context(ctxid)
13
+ content = persist_chat.export_json_chat(context)
14
+
15
+ return {
16
+ "message": "Chats exported.",
17
+ "ctxid": context.id,
18
+ "content": content,
19
+ }
\ No newline at end of file
python/api/chat_load.py
new
+17
@@ -0,0 +1,17 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from python.helpers import persist_chat
5
+
6
+class LoadChats(ApiHandler):
7
+ async def process(self, input: dict, request: Request) -> dict | Response:
8
+ chats = input.get("chats", [])
9
+ if not chats:
10
+ raise Exception("No chats provided")
11
+
12
+ ctxids = persist_chat.load_json_chats(chats)
13
+
14
+ return {
15
+ "message": "Chats loaded.",
16
+ "ctxids": ctxids,
17
+ }
python/api/chat_remove.py
new
+18
@@ -0,0 +1,18 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from agent import AgentContext
5
+from python.helpers import persist_chat
6
+
7
+
8
+class RemoveChat(ApiHandler):
9
+ async def process(self, input: dict, request: Request) -> dict | Response:
10
+ ctxid = input.get("context", "")
11
+
12
+ # context instance - get or create
13
+ AgentContext.remove(ctxid)
14
+ persist_chat.remove_chat(ctxid)
15
+
16
+ return {
17
+ "message": "Context removed.",
18
+ }
python/api/chat_reset.py
new
+17
@@ -0,0 +1,17 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from python.helpers import persist_chat
5
+
6
+class Reset(ApiHandler):
7
+ async def process(self, input: dict, request: Request) -> dict | Response:
8
+ ctxid = input.get("context", "")
9
+
10
+ # context instance - get or create
11
+ context = self.get_context(ctxid)
12
+ context.reset()
13
+ persist_chat.save_tmp_chat(context)
14
+
15
+ return {
16
+ "message": "Agent restarted.",
17
+ }
python/api/delete_work_dir_file.py
new
+23
@@ -0,0 +1,23 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from python.helpers.file_browser import FileBrowser
5
+from python.helpers import files
6
+
7
+
8
+class DeleteWorkDirFile(ApiHandler):
9
+ async def process(self, input: dict, request: Request) -> dict | Response:
10
+ file_path = input.get('path', '')
11
+ current_path = input.get('currentPath', '')
12
+
13
+ work_dir = files.get_abs_path("work_dir")
14
+ browser = FileBrowser(work_dir)
15
+
16
+ if browser.delete_file(file_path):
17
+ # Get updated file list
18
+ result = browser.get_files(current_path)
19
+ return {
20
+ "data": result
21
+ }
22
+ else:
23
+ raise Exception("File not found or could not be deleted")
\ No newline at end of file
python/api/download_work_dir_file.py
new
+27
@@ -0,0 +1,27 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response, send_file
3
+
4
+from python.helpers.file_browser import FileBrowser
5
+from python.helpers import files
6
+import os
7
+
8
+
9
+
10
+
11
+class DownloadWorkDirFile(ApiHandler):
12
+ async def process(self, input: dict, request: Request) -> dict | Response:
13
+ file_path = request.args.get('path', '')
14
+ if not file_path:
15
+ raise ValueError("No file path provided")
16
+
17
+ work_dir = files.get_abs_path("work_dir")
18
+ browser = FileBrowser(work_dir)
19
+
20
+ full_path = browser.get_file_path(file_path)
21
+ if full_path:
22
+ return send_file(
23
+ full_path,
24
+ as_attachment=True,
25
+ download_name=os.path.basename(file_path)
26
+ )
27
+ raise Exception("File not found")
\ No newline at end of file
python/api/get_work_dir_files.py
new
+15
@@ -0,0 +1,15 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from python.helpers.file_browser import FileBrowser
5
+from python.helpers import files
6
+
7
+
8
+class GetWorkDirFiles(ApiHandler):
9
+ async def process(self, input: dict, request: Request) -> dict | Response:
10
+ current_path = request.args.get("path", "")
11
+ work_dir = files.get_abs_path("work_dir")
12
+ browser = FileBrowser(work_dir)
13
+ result = browser.get_files(current_path)
14
+
15
+ return {"data": result}
python/api/health.py
new
+10
@@ -0,0 +1,10 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from python.helpers import git
5
+
6
+class HealthCheck(ApiHandler):
7
+
8
+ async def process(self, input: dict, request: Request) -> dict | Response:
9
+ gitinfo = git.get_git_info()
10
+ return {"gitinfo": gitinfo}
python/api/import_knowledge.py
new
+26
@@ -0,0 +1,26 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from python.helpers.file_browser import FileBrowser
5
+from python.helpers import files
6
+import os
7
+from werkzeug.utils import secure_filename
8
+
9
+
10
+class ImportKnowledge(ApiHandler):
11
+ async def process(self, input: dict, request: Request) -> dict | Response:
12
+ if "files[]" not in request.files:
13
+ raise Exception("No files part")
14
+
15
+ file_list = request.files.getlist("files[]")
16
+ KNOWLEDGE_FOLDER = files.get_abs_path("knowledge/custom/main")
17
+
18
+ saved_filenames = []
19
+
20
+ for file in file_list:
21
+ if file:
22
+ filename = secure_filename(file.filename) # type: ignore
23
+ file.save(os.path.join(KNOWLEDGE_FOLDER, filename))
24
+ saved_filenames.append(filename)
25
+
26
+ return {"message": "Knowledge Imported", "filenames": saved_filenames}
python/api/message.py
new
+87
@@ -0,0 +1,87 @@
1
+from agent import AgentContext
2
+from python.helpers.api import ApiHandler
3
+from flask import Request, Response
4
+
5
+from python.helpers import files
6
+import os
7
+from werkzeug.utils import secure_filename
8
+from python.helpers.defer import DeferredTask
9
+from python.helpers.print_style import PrintStyle
10
+
11
+
12
+class Message(ApiHandler):
13
+ async def process(self, input: dict, request: Request) -> dict | Response:
14
+ task, context = await self.communicate(input=input, request=request)
15
+ return await self.respond(task, context)
16
+
17
+ async def respond(self, task: DeferredTask, context: AgentContext):
18
+ result = await task.result() # type: ignore
19
+ return {
20
+ "message": result,
21
+ "context": context.id,
22
+ }
23
+
24
+ async def communicate(self, input: dict, request: Request):
25
+ # Handle both JSON and multipart/form-data
26
+ if request.content_type.startswith("multipart/form-data"):
27
+ text = request.form.get("text", "")
28
+ ctxid = request.form.get("context", "")
29
+ message_id = request.form.get("message_id", None)
30
+ attachments = request.files.getlist("attachments")
31
+ attachment_paths = []
32
+
33
+ upload_folder = files.get_abs_path("work_dir/uploads")
34
+
35
+ if attachments:
36
+ os.makedirs(upload_folder, exist_ok=True)
37
+ for attachment in attachments:
38
+ if attachment.filename is None:
39
+ continue
40
+ filename = secure_filename(attachment.filename)
41
+ save_path = files.get_abs_path(upload_folder, filename)
42
+ attachment.save(save_path)
43
+ attachment_paths.append(save_path)
44
+ else:
45
+ # Handle JSON request as before
46
+ input_data = request.get_json()
47
+ text = input_data.get("text", "")
48
+ ctxid = input_data.get("context", "")
49
+ message_id = input_data.get("message_id", None)
50
+ attachment_paths = []
51
+
52
+ # Now process the message
53
+ message = text
54
+
55
+ # Obtain agent context
56
+ context = self.get_context(ctxid)
57
+
58
+ # Store attachments in agent data
59
+ context.agent0.set_data("attachments", attachment_paths)
60
+
61
+ # Prepare attachment filenames for logging
62
+ attachment_filenames = (
63
+ [os.path.basename(path) for path in attachment_paths]
64
+ if attachment_paths
65
+ else []
66
+ )
67
+
68
+ # Print to console and log
69
+ PrintStyle(
70
+ background_color="#6C3483", font_color="white", bold=True, padding=True
71
+ ).print(f"User message:")
72
+ PrintStyle(font_color="white", padding=False).print(f"> {message}")
73
+ if attachment_filenames:
74
+ PrintStyle(font_color="white", padding=False).print("Attachments:")
75
+ for filename in attachment_filenames:
76
+ PrintStyle(font_color="white", padding=False).print(f"- {filename}")
77
+
78
+ # Log the message with message_id and attachments
79
+ context.log.log(
80
+ type="user",
81
+ heading="User message",
82
+ content=message,
83
+ kvps={"attachments": attachment_filenames},
84
+ id=message_id,
85
+ )
86
+
87
+ return context.communicate(message), context
\ No newline at end of file
python/api/message_async.py
new
+17
@@ -0,0 +1,17 @@
1
+from agent import AgentContext
2
+from python.helpers.api import ApiHandler
3
+from flask import Request, Response
4
+
5
+from python.helpers import files
6
+import os
7
+from werkzeug.utils import secure_filename
8
+from python.helpers.defer import DeferredTask
9
+from python.api.message import Message
10
+
11
+
12
+class MessageAsync(Message):
13
+ async def respond(self, task: DeferredTask, context: AgentContext):
14
+ return {
15
+ "message": "Message received.",
16
+ "context": context.id,
17
+ }
python/api/pause.py
new
+19
@@ -0,0 +1,19 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+
5
+class Pause(ApiHandler):
6
+ async def process(self, input: dict, request: Request) -> dict | Response:
7
+ # input data
8
+ paused = input.get("paused", False)
9
+ ctxid = input.get("context", "")
10
+
11
+ # context instance - get or create
12
+ context = self.get_context(ctxid)
13
+
14
+ context.paused = paused
15
+
16
+ return {
17
+ "message": "Agent paused." if paused else "Agent unpaused.",
18
+ "pause": paused,
19
+ }
python/api/poll.py
new
+39
@@ -0,0 +1,39 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from agent import AgentContext
5
+
6
+class Poll(ApiHandler):
7
+ async def process(self, input: dict, request: Request) -> dict | Response:
8
+ ctxid = input.get("context", None)
9
+ from_no = input.get("log_from", 0)
10
+
11
+ # context instance - get or create
12
+ context = self.get_context(ctxid)
13
+
14
+ logs = context.log.output(start=from_no)
15
+
16
+ # loop AgentContext._contexts
17
+ ctxs = []
18
+ for ctx in AgentContext._contexts.values():
19
+ ctxs.append(
20
+ {
21
+ "id": ctx.id,
22
+ "no": ctx.no,
23
+ "log_guid": ctx.log.guid,
24
+ "log_version": len(ctx.log.updates),
25
+ "log_length": len(ctx.log.logs),
26
+ "paused": ctx.paused,
27
+ }
28
+ )
29
+
30
+ # data from this server
31
+ return {
32
+ "context": context.id,
33
+ "contexts": ctxs,
34
+ "logs": logs,
35
+ "log_guid": context.log.guid,
36
+ "log_version": len(context.log.updates),
37
+ "log_progress": context.log.progress,
38
+ "paused": context.paused,
39
+ }
\ No newline at end of file
python/api/rfc.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 runtime
5
+
6
+class RFC(ApiHandler):
7
+ async def process(self, input: dict, request: Request) -> dict | Response:
8
+ result = await runtime.handle_rfc(input) # type: ignore
9
+ return result
python/api/settings_get.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 settings
5
+
6
+class GetSettings(ApiHandler):
7
+ async def process(self, input: dict, request: Request) -> dict | Response:
8
+ set = settings.convert_out(settings.get_settings())
9
+ return {"settings": set}
python/api/settings_set.py
new
+11
@@ -0,0 +1,11 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from python.helpers import settings
5
+
6
+
7
+class SetSettings(ApiHandler):
8
+ async def process(self, input: dict, request: Request) -> dict | Response:
9
+ set = settings.convert_in(input)
10
+ set = settings.set_settings(set)
11
+ return {"settings": set}
python/api/transcribe.py
new
+10
@@ -0,0 +1,10 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from python.helpers import runtime, whisper
5
+
6
+class Transcribe(ApiHandler):
7
+ async def process(self, input: dict, request: Request) -> dict | Response:
8
+ audio = input.get("audio")
9
+ result = await whisper.transcribe(audio) # type: ignore
10
+ return result
python/api/upload.py
new
+28
@@ -0,0 +1,28 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response
3
+
4
+from python.helpers.file_browser import FileBrowser
5
+from python.helpers import files
6
+from werkzeug.utils import secure_filename
7
+
8
+
9
+class UploadFile(ApiHandler):
10
+ async def process(self, input: dict, request: Request) -> dict | Response:
11
+ if "file" not in request.files:
12
+ raise Exception("No file part")
13
+
14
+ file_list = request.files.getlist("file") # Handle multiple files
15
+ saved_filenames = []
16
+
17
+ for file in file_list:
18
+ if file and self.allowed_file(file.filename): # Check file type
19
+ filename = secure_filename(file.filename) # type: ignore
20
+ file.save(files.get_abs_path("work_dir/upload", filename))
21
+ saved_filenames.append(filename)
22
+
23
+ return {"filenames": saved_filenames} # Return saved filenames
24
+
25
+
26
+ def allowed_file(self,filename):
27
+ ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "txt", "pdf", "csv", "html", "json", "md"}
28
+ return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
\ No newline at end of file
python/api/upload_work_dir_files.py
new
+34
@@ -0,0 +1,34 @@
1
+from python.helpers.api import ApiHandler
2
+from flask import Request, Response, send_file
3
+
4
+from python.helpers.file_browser import FileBrowser
5
+from python.helpers import files
6
+import os
7
+
8
+
9
+
10
+
11
+class UploadWorkDirFiles(ApiHandler):
12
+ async def process(self, input: dict, request: Request) -> dict | Response:
13
+ if "files[]" not in request.files:
14
+ raise Exception("No files uploaded")
15
+
16
+ current_path = request.form.get('path', '')
17
+ uploaded_files = request.files.getlist("files[]")
18
+
19
+ work_dir = files.get_abs_path("work_dir")
20
+ browser = FileBrowser(work_dir)
21
+
22
+ successful, failed = browser.save_files(uploaded_files, current_path)
23
+
24
+ if not successful and failed:
25
+ raise Exception("All uploads failed")
26
+
27
+ result = browser.get_files(current_path)
28
+
29
+ return {
30
+ "message": "Files uploaded successfully" if not failed else "Some files failed to upload",
31
+ "data": result,
32
+ "successful": successful,
33
+ "failed": failed
34
+ }
\ No newline at end of file
python/helpers/api.py
new
+57
@@ -0,0 +1,57 @@
1
+from abc import abstractmethod
2
+import json
3
+import threading
4
+from flask import Request, Response, jsonify, Flask
5
+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
+
10
+
11
+class ApiHandler:
12
+ def __init__(self, app: Flask, thread_lock: threading.Lock):
13
+ self.app = app
14
+ self.thread_lock = thread_lock
15
+
16
+ @abstractmethod
17
+ async def process(self, input: dict, request: Request) -> dict | Response:
18
+ pass
19
+
20
+ async def handle_request(self, request: Request) -> Response:
21
+ try:
22
+ # input data from request based on type
23
+ if request.is_json:
24
+ input = request.get_json()
25
+ else:
26
+ input = {"data": request.get_data(as_text=True)}
27
+
28
+ # process via handler
29
+ output = await self.process(input, request)
30
+
31
+ # return output based on type
32
+ if isinstance(output, Response):
33
+ return output
34
+ else:
35
+ response_json = json.dumps(output)
36
+ return Response(response=response_json, status=200, mimetype="application/json")
37
+
38
+ # return exceptions with 500
39
+ except Exception as e:
40
+ error = format_error(e)
41
+ PrintStyle.error(error)
42
+ return Response(response=error, status=500, mimetype="text/plain")
43
+
44
+
45
+
46
+ # get context to run agent zero in
47
+ def get_context(self, ctxid: str):
48
+ with self.thread_lock:
49
+ if not ctxid:
50
+ first = AgentContext.first()
51
+ if first:
52
+ return first
53
+ return AgentContext(config=initialize())
54
+ got = AgentContext.get(ctxid)
55
+ if got:
56
+ return got
57
+ return AgentContext(config=initialize(), id=ctxid)
\ No newline at end of file
python/helpers/extract_tools.py
+5
-2
@@ -51,7 +51,7 @@ def fix_json_string(json_string):
51
52
T = TypeVar('T') # Define a generic type variable
53
54
-def load_classes_from_folder(folder: str, name_pattern: str, base_class: Type[T]) -> list[Type[T]]:
54
+def load_classes_from_folder(folder: str, name_pattern: str, base_class: Type[T], one_per_file: bool = True) -> list[Type[T]]:
55
classes = []
56
abs_folder = get_abs_path(folder)
57
@@ -70,8 +70,11 @@ def load_classes_from_folder(folder: str, name_pattern: str, base_class: Type[T]
70
class_list = inspect.getmembers(module, inspect.isclass)
71
72
# Filter for classes that are subclasses of the given base_class
73
- for cls in class_list:
73
+ # iterate backwards to skip imported superclasses
74
+ for cls in reversed(class_list):
75
if cls[1] is not base_class and issubclass(cls[1], base_class):
76
classes.append(cls[1])
77
+ if one_per_file:
78
+ break
79
80
return classes
\ No newline at end of file
run_ui.py
+29
-625
@@ -16,6 +16,8 @@ from python.helpers import persist_chat, settings, whisper, rfc, runtime, dotenv
16
import base64
17
from werkzeug.utils import secure_filename
18
from python.helpers.cloudflare_tunnel import CloudflareTunnel
19
+from python.helpers.extract_tools import load_classes_from_folder
20
+from python.helpers.api import ApiHandler
21
from python.helpers.file_browser import FileBrowser
22
23
@@ -29,21 +31,7 @@ lock = threading.Lock()
31
basic_auth = BasicAuth(app)
32
33
32
-# get context to run agent zero in
33
-def get_context(ctxid: str):
34
- with lock:
35
- if not ctxid:
36
- first = AgentContext.first()
37
- if first:
38
- return first
39
- return AgentContext(config=initialize())
40
- got = AgentContext.get(ctxid)
41
- if got:
42
- return got
43
- return AgentContext(config=initialize(), id=ctxid)
44
-
45
-
46
-# Now you can use @requires_auth function decorator to require login on certain pages
34
+# require authentication for handlers
35
def requires_auth(f):
36
@wraps(f)
37
async def decorated(*args, **kwargs):
@@ -63,621 +51,16 @@ def requires_auth(f):
51
return decorated
52
53
66
-UPLOAD_FOLDER = os.path.join(os.getcwd(), "work_dir", "uploads")
67
-
68
-
69
-@app.route("/upload", methods=["POST"])
70
-@requires_auth
71
-async def upload_file():
72
- if "file" not in request.files:
73
- return jsonify({"ok": False, "message": "No file part"}), 400
74
-
75
- files = request.files.getlist("file") # Handle multiple files
76
- saved_filenames = []
77
-
78
- for file in files:
79
- if file and allowed_file(file.filename): # Check file type
80
- filename = secure_filename(file.filename) # type: ignore
81
- file.save(os.path.join(UPLOAD_FOLDER, filename))
82
- saved_filenames.append(filename)
83
-
84
- return jsonify({"ok": True, "filenames": saved_filenames}) # Return saved filenames
85
-
86
-
87
-ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "txt", "pdf", "csv", "html", "json", "md"}
88
-
89
-
90
-def allowed_file(filename):
91
- return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
92
-
93
-
94
-@app.route("/import_knowledge", methods=["POST"])
95
-@requires_auth
96
-async def import_knowledge():
97
- if "files[]" not in request.files:
98
- return jsonify({"ok": False, "message": "No files part"}), 400
99
-
100
- files = request.files.getlist("files[]")
101
- KNOWLEDGE_FOLDER = os.path.join(os.getcwd(), "knowledge", "custom", "main")
102
-
103
- saved_filenames = []
104
-
105
- for file in files:
106
- if file:
107
- filename = secure_filename(file.filename) # type: ignore
108
- file.save(os.path.join(KNOWLEDGE_FOLDER, filename))
109
- saved_filenames.append(filename)
110
-
111
- return jsonify(
112
- {"ok": True, "message": "Knowledge Imported", "filenames": saved_filenames}
113
- )
114
-
115
-
116
-@app.route("/getWorkDirFiles", methods=["GET"])
117
-@requires_auth
118
-async def get_work_dir_files():
119
- try:
120
- current_path = request.args.get('path', '')
121
- work_dir = files.get_abs_path("work_dir")
122
- browser = FileBrowser(work_dir)
123
- result = browser.get_files(current_path)
124
-
125
- response = {
126
- "ok": True,
127
- "data": result
128
- }
129
-
130
- except Exception as e:
131
- response = {
132
- "ok": False,
133
- "message": str(e)
134
- }
135
- PrintStyle.error(str(e))
136
-
137
- return jsonify(response)
138
-
139
-
140
-@app.route("/uploadWorkDirFiles", methods=["POST"])
141
-@requires_auth
142
-async def upload_work_dir_files():
143
- try:
144
- if "files[]" not in request.files:
145
- return jsonify({"ok": False, "message": "No files uploaded"}), 400
146
-
147
- current_path = request.form.get('path', '')
148
- uploaded_files = request.files.getlist("files[]")
149
-
150
- work_dir = files.get_abs_path("work_dir")
151
- browser = FileBrowser(work_dir)
152
-
153
- successful, failed = browser.save_files(uploaded_files, current_path)
154
-
155
- if not successful and failed:
156
- return jsonify({
157
- "ok": False,
158
- "message": "All uploads failed",
159
- "failed": failed
160
- }), 400
161
-
162
- result = browser.get_files(current_path)
163
-
164
- response = {
165
- "ok": True,
166
- "message": "Files uploaded successfully" if not failed else "Some files failed to upload",
167
- "data": result,
168
- "successful": successful,
169
- "failed": failed
170
- }
171
-
172
- except Exception as e:
173
- response = {
174
- "ok": False,
175
- "message": str(e)
176
- }
177
- PrintStyle.error(str(e))
178
-
179
- return jsonify(response)
180
-
181
-
182
-@app.route("/downloadWorkDirFile", methods=["GET"])
183
-@requires_auth
184
-async def download_work_dir_file():
185
- try:
186
- file_path = request.args.get('path', '')
187
- if not file_path:
188
- raise ValueError("No file path provided")
189
-
190
- work_dir = files.get_abs_path("work_dir")
191
- browser = FileBrowser(work_dir)
192
-
193
- full_path = browser.get_file_path(file_path)
194
- if full_path:
195
- return send_file(
196
- full_path,
197
- as_attachment=True,
198
- download_name=os.path.basename(file_path)
199
- )
200
-
201
- return jsonify({
202
- "ok": False,
203
- "message": "File not found"
204
- }), 404
205
-
206
- except Exception as e:
207
- return jsonify({
208
- "ok": False,
209
- "message": str(e)
210
- }), 500
211
-
212
-
213
-@app.route("/deleteWorkDirFile", methods=["POST"])
214
-@requires_auth
215
-async def delete_work_dir_file():
216
- try:
217
- data = request.get_json()
218
- file_path = data.get('path', '')
219
- current_path = data.get('currentPath', '')
220
-
221
- work_dir = files.get_abs_path("work_dir")
222
- browser = FileBrowser(work_dir)
223
-
224
- if browser.delete_file(file_path):
225
- # Get updated file list
226
- result = browser.get_files(current_path)
227
- response = {
228
- "ok": True,
229
- "data": result
230
- }
231
- else:
232
- response = {
233
- "ok": False,
234
- "message": "File not found or could not be deleted"
235
- }
236
-
237
- except Exception as e:
238
- response = {
239
- "ok": False,
240
- "message": str(e)
241
- }
242
- PrintStyle.error(str(e))
243
-
244
- return jsonify(response)
245
-
246
-
54
# handle default address, load index
55
@app.route("/", methods=["GET"])
56
@requires_auth
57
async def serve_index():
58
gitinfo = git.get_git_info()
252
- return files.read_file("./webui/index.html", version_no=gitinfo["version"], version_time=gitinfo["commit_time"])
253
-
254
-
255
-# simple health check, just return OK to see the server is running
256
-@app.route("/ok", methods=["GET", "POST"])
257
-async def health_check():
258
- gitinfo = git.get_git_info()
259
- return jsonify({"ok": True, "gitinfo": gitinfo})
260
-
261
-
262
-# send message to agent (async UI)
263
-@app.route("/msg", methods=["POST"])
264
-@requires_auth
265
-async def handle_message_async():
266
- return await handle_message(False)
267
-
268
-
269
-# send message to agent (synchronous API)
270
-@app.route("/msg_sync", methods=["POST"])
271
-@requires_auth
272
-async def handle_msg_sync():
273
- return await handle_message(True)
274
-
275
-
276
-async def handle_message(sync: bool):
277
- try:
278
- # Handle both JSON and multipart/form-data
279
- if request.content_type.startswith("multipart/form-data"):
280
- text = request.form.get("text", "")
281
- ctxid = request.form.get("context", "")
282
- message_id = request.form.get("message_id", None)
283
- attachments = request.files.getlist("attachments")
284
- attachment_paths = []
285
-
286
- upload_folder = files.get_abs_path("work_dir/uploads")
287
-
288
- if attachments:
289
- os.makedirs(upload_folder, exist_ok=True)
290
- for attachment in attachments:
291
- if attachment.filename is None:
292
- continue
293
- filename = secure_filename(attachment.filename)
294
- save_path = files.get_abs_path(upload_folder, filename)
295
- attachment.save(save_path)
296
- attachment_paths.append(save_path)
297
- else:
298
- # Handle JSON request as before
299
- input_data = request.get_json()
300
- text = input_data.get("text", "")
301
- ctxid = input_data.get("context", "")
302
- message_id = input_data.get("message_id", None)
303
- attachment_paths = []
304
-
305
- # Now process the message
306
- message = text
307
-
308
- # Obtain agent context
309
- context = get_context(ctxid)
310
-
311
- # Store attachments in agent data
312
- context.agent0.set_data("attachments", attachment_paths)
313
-
314
- # Prepare attachment filenames for logging
315
- attachment_filenames = (
316
- [os.path.basename(path) for path in attachment_paths]
317
- if attachment_paths
318
- else []
319
- )
320
-
321
- # Print to console and log
322
- PrintStyle(
323
- background_color="#6C3483", font_color="white", bold=True, padding=True
324
- ).print(f"User message:")
325
- PrintStyle(font_color="white", padding=False).print(f"> {message}")
326
- if attachment_filenames:
327
- PrintStyle(font_color="white", padding=False).print("Attachments:")
328
- for filename in attachment_filenames:
329
- PrintStyle(font_color="white", padding=False).print(f"- {filename}")
330
-
331
- # Log the message with message_id and attachments
332
- context.log.log(
333
- type="user",
334
- heading="User message",
335
- content=message,
336
- kvps={"attachments": attachment_filenames},
337
- id=message_id,
338
- )
339
-
340
- if sync:
341
- context.communicate(message)
342
- result = await context.process.result() # type: ignore
343
- response = {
344
- "ok": True,
345
- "message": result,
346
- "context": context.id,
347
- }
348
- else:
349
- context.communicate(message)
350
- response = {
351
- "ok": True,
352
- "message": "Message received.",
353
- "context": context.id,
354
- }
355
-
356
- except Exception as e:
357
- response = {
358
- "ok": False,
359
- "message": str(e),
360
- }
361
- PrintStyle.error(str(e))
362
-
363
- # respond with json
364
- return jsonify(response)
365
-
366
-
367
-# pausing/unpausing the agent
368
-@app.route("/pause", methods=["POST"])
369
-@requires_auth
370
-async def pause():
371
- try:
372
-
373
- # data sent to the server
374
- input = request.get_json()
375
- paused = input.get("paused", False)
376
- ctxid = input.get("context", "")
377
-
378
- # context instance - get or create
379
- context = get_context(ctxid)
380
-
381
- context.paused = paused
382
-
383
- response = {
384
- "ok": True,
385
- "message": "Agent paused." if paused else "Agent unpaused.",
386
- "pause": paused,
387
- }
388
-
389
- except Exception as e:
390
- response = {
391
- "ok": False,
392
- "message": str(e),
393
- }
394
- PrintStyle.error(str(e))
395
-
396
- # respond with json
397
- return jsonify(response)
398
-
399
-
400
-# load chats from json
401
-@app.route("/loadChats", methods=["POST"])
402
-@requires_auth
403
-async def load_chats():
404
- try:
405
- # data sent to the server
406
- input = request.get_json()
407
- chats = input.get("chats", [])
408
- if not chats:
409
- raise Exception("No chats provided")
410
-
411
- ctxids = persist_chat.load_json_chats(chats)
412
-
413
- response = {
414
- "ok": True,
415
- "message": "Chats loaded.",
416
- "ctxids": ctxids,
417
- }
418
-
419
- except Exception as e:
420
- response = {
421
- "ok": False,
422
- "message": str(e),
423
- }
424
- PrintStyle.error(str(e))
425
-
426
- # respond with json
427
- return jsonify(response)
428
-
429
-
430
-# save chats to json
431
-@app.route("/exportChat", methods=["POST"])
432
-@requires_auth
433
-async def export_chat():
434
- try:
435
- # data sent to the server
436
- input = request.get_json()
437
- ctxid = input.get("ctxid", "")
438
- if not ctxid:
439
- raise Exception("No context id provided")
440
-
441
- context = get_context(ctxid)
442
- content = persist_chat.export_json_chat(context)
443
-
444
- response = {
445
- "ok": True,
446
- "message": "Chats exported.",
447
- "ctxid": context.id,
448
- "content": content,
449
- }
450
-
451
- except Exception as e:
452
- response = {
453
- "ok": False,
454
- "message": str(e),
455
- }
456
- PrintStyle.error(str(e))
457
-
458
- # respond with json
459
- return jsonify(response)
460
-
461
-
462
-# restarting with new agent0
463
-@app.route("/reset", methods=["POST"])
464
-@requires_auth
465
-async def reset():
466
- try:
467
-
468
- # data sent to the server
469
- input = request.get_json()
470
- ctxid = input.get("context", "")
471
-
472
- # context instance - get or create
473
- context = get_context(ctxid)
474
- context.reset()
475
- persist_chat.save_tmp_chat(context)
476
-
477
- response = {
478
- "ok": True,
479
- "message": "Agent restarted.",
480
- }
481
-
482
- except Exception as e:
483
- response = {
484
- "ok": False,
485
- "message": str(e),
486
- }
487
- PrintStyle.error(str(e))
488
-
489
- # respond with json
490
- return jsonify(response)
491
-
492
-
493
-# killing context
494
-@app.route("/remove", methods=["POST"])
495
-@requires_auth
496
-async def remove():
497
- try:
498
-
499
- # data sent to the server
500
- input = request.get_json()
501
- ctxid = input.get("context", "")
502
-
503
- # context instance - get or create
504
- AgentContext.remove(ctxid)
505
- persist_chat.remove_chat(ctxid)
506
-
507
- response = {
508
- "ok": True,
509
- "message": "Context removed.",
510
- }
511
-
512
- except Exception as e:
513
- response = {
514
- "ok": False,
515
- "message": str(e),
516
- }
517
- PrintStyle.error(str(e))
518
-
519
- # respond with json
520
- return jsonify(response)
521
-
522
-
523
-# Web UI polling
524
-@app.route("/poll", methods=["POST"])
525
-@requires_auth
526
-async def poll():
527
- try:
528
-
529
- # data sent to the server
530
- input = request.get_json()
531
- ctxid = input.get("context", None)
532
- from_no = input.get("log_from", 0)
533
-
534
- # context instance - get or create
535
- context = get_context(ctxid)
536
-
537
- logs = context.log.output(start=from_no)
538
-
539
- # loop AgentContext._contexts
540
- ctxs = []
541
- for ctx in AgentContext._contexts.values():
542
- ctxs.append(
543
- {
544
- "id": ctx.id,
545
- "no": ctx.no,
546
- "log_guid": ctx.log.guid,
547
- "log_version": len(ctx.log.updates),
548
- "log_length": len(ctx.log.logs),
549
- "paused": ctx.paused,
550
- }
551
- )
552
-
553
- # data from this server
554
- response = {
555
- "ok": True,
556
- "context": context.id,
557
- "contexts": ctxs,
558
- "logs": logs,
559
- "log_guid": context.log.guid,
560
- "log_version": len(context.log.updates),
561
- "log_progress": context.log.progress,
562
- "paused": context.paused,
563
- }
564
-
565
- except Exception as e:
566
- response = {
567
- "ok": False,
568
- "message": str(e),
569
- }
570
- PrintStyle.error(str(e))
571
-
572
- # serialize json with json.dumps to preserve OrderedDict order
573
- response_json = json.dumps(response)
574
- return Response(response=response_json, status=200, mimetype="application/json")
575
- # return jsonify(response)
576
-
577
-
578
-# get current settings
579
-@app.route("/getSettings", methods=["POST"])
580
-@requires_auth
581
-async def get_settings():
582
- try:
583
-
584
- # data sent to the server
585
- input = request.get_json()
586
-
587
- set = settings.convert_out(settings.get_settings())
588
-
589
- response = {"ok": True, "settings": set}
590
-
591
- except Exception as e:
592
- response = {
593
- "ok": False,
594
- "message": str(e),
595
- }
596
- PrintStyle.error(str(e))
597
-
598
- # respond with json
599
- return jsonify(response)
600
-
601
-
602
-# set current settings
603
-@app.route("/setSettings", methods=["POST"])
604
-@requires_auth
605
-async def set_settings():
606
- try:
607
-
608
- # data sent to the server
609
- input = request.get_json()
610
-
611
- set = settings.convert_in(input)
612
- set = settings.set_settings(set)
613
-
614
- response = {"ok": True, "settings": set}
615
-
616
- except Exception as e:
617
- response = {
618
- "ok": False,
619
- "message": str(e),
620
- }
621
- PrintStyle.error(str(e))
622
-
623
- # respond with json
624
- return jsonify(response)
625
-
626
-
627
-# transcribe audio
628
-@app.route("/transcribe", methods=["POST"])
629
-@requires_auth
630
-async def transcribe():
631
- try:
632
-
633
- # data sent to the server
634
- input = request.get_json()
635
- audio = input.get("audio")
636
-
637
- # transcribe audio
638
- result = await whisper.transcribe(audio)
639
-
640
- response = {
641
- "ok": True,
642
- "text": result["text"],
643
- }
644
-
645
- except Exception as e:
646
- response = {
647
- "ok": False,
648
- "message": str(e),
649
- }
650
- PrintStyle.error(str(e))
651
-
652
- # respond with json
653
- return jsonify(response)
654
-
655
-
656
-# remote function call
657
-@app.route("/rfc", methods=["POST"])
658
-@requires_auth
659
-async def handle_rfc():
660
- try:
661
- # data sent to the server
662
- input = json.loads(request.get_json())
663
-
664
- # handle RFC
665
- result = await runtime.handle_rfc(input)
666
-
667
- response = {
668
- "ok": True,
669
- "result": result,
670
- }
671
-
672
- return jsonify(response)
673
- except Exception as e:
674
- response = {
675
- "ok": False,
676
- "message": str(e),
677
- }
678
- PrintStyle.error(str(e))
679
- return jsonify(response), 500
680
-
59
+ return files.read_file(
60
+ "./webui/index.html",
61
+ version_no=gitinfo["version"],
62
+ version_time=gitinfo["commit_time"],
63
+ )
64
65
def run():
66
print("Initializing framework...")
@@ -710,6 +93,27 @@ def run():
93
# initialize contexts from persisted chats
94
persist_chat.load_tmp_chats()
95
96
+ def register_api_handler(app, handler):
97
+ name = handler.__module__.split(".")[-1]
98
+ instance = handler(app, lock)
99
+ @requires_auth
100
+ async def handle_request():
101
+ return await instance.handle_request(request=request)
102
+ app.add_url_rule(
103
+ f"/{name}",
104
+ f"/{name}",
105
+ handle_request,
106
+ methods=["POST", "GET"],
107
+ )
108
+
109
+ # initialize and register API handlers
110
+ handlers = load_classes_from_folder("python/api", "*.py", ApiHandler)
111
+ for handler in handlers:
112
+ register_api_handler(app, handler)
113
+
114
+
115
+
116
+
117
try:
118
# Run Flask app
119
app.run(
webui/file_browser.js
+71
-66
@@ -17,16 +17,16 @@ const fileBrowserModalProxy = {
17
async openModal() {
18
const modalEl = document.getElementById('fileBrowserModal');
19
const modalAD = Alpine.$data(modalEl);
20
-
20
+
21
modalAD.isOpen = true;
22
modalAD.isLoading = true;
23
modalAD.history = []; // reset history when opening modal
24
-
24
+
25
// Initialize currentPath to root if it's empty
26
if (!modalAD.browser.currentPath) {
27
modalAD.browser.currentPath = "";
28
}
29
-
29
+
30
await modalAD.fetchFiles(modalAD.browser.currentPath);
31
},
32
@@ -39,15 +39,15 @@ const fileBrowserModalProxy = {
39
async fetchFiles(path = "") {
40
this.isLoading = true;
41
try {
42
- const response = await fetch(`/getWorkDirFiles?path=${encodeURIComponent(path)}`);
43
- const data = await response.json();
44
-
45
- if (data.ok) {
42
+ const response = await fetch(`/get_work_dir_files?path=${encodeURIComponent(path)}`);
43
+
44
+ if (response.ok) {
45
+ const data = await response.json();
46
this.browser.entries = data.data.entries;
47
this.browser.currentPath = data.data.current_path;
48
this.browser.parentPath = data.data.parent_path;
49
} else {
50
- console.error('Error fetching files:', data.message);
50
+ console.error('Error fetching files:', await response.text());
51
this.browser.entries = [];
52
}
53
} catch (error) {
@@ -108,7 +108,7 @@ const fileBrowserModalProxy = {
108
if (!confirm(`Are you sure you want to delete ${file.name}?`)) {
109
return;
110
}
111
-
111
+
112
try {
113
const response = await fetch('/deleteWorkDirFile', {
114
method: 'POST',
@@ -121,45 +121,46 @@ const fileBrowserModalProxy = {
121
})
122
});
123
124
- const data = await response.json();
125
- if (data.ok) {
124
+ if (response.ok) {
125
+ const data = await response.json();
126
this.browser.entries = this.browser.entries.filter(entry => entry.path !== file.path);
127
alert('File deleted successfully.');
128
} else {
129
- alert(`Error deleting file: ${data.message}`);
129
+ alert(`Error deleting file: ${await response.text()}`);
130
}
131
} catch (error) {
132
console.error('Error deleting file:', error);
133
alert('Error deleting file');
134
}
135
},
136
-
137
- handleFileUpload(event) {
138
- const files = event.target.files;
139
- if (!files.length) return;
140
-
141
- const formData = new FormData();
142
- formData.append('path', this.browser.currentPath);
143
-
144
- for (let i = 0; i < files.length; i++) {
145
- const ext = files[i].name.split('.').pop().toLowerCase();
146
- if (!['zip', 'tar', 'gz', 'rar', '7z'].includes(ext)) {
147
- if (files[i].size > 100 * 1024 * 1024) { // 100MB
148
- alert(`File ${files[i].name} exceeds the maximum allowed size of 100MB.`);
149
- continue;
136
+
137
+ async handleFileUpload(event) {
138
+ try {
139
+ const files = event.target.files;
140
+ if (!files.length) return;
141
+
142
+ const formData = new FormData();
143
+ formData.append('path', this.browser.currentPath);
144
+
145
+ for (let i = 0; i < files.length; i++) {
146
+ const ext = files[i].name.split('.').pop().toLowerCase();
147
+ if (!['zip', 'tar', 'gz', 'rar', '7z'].includes(ext)) {
148
+ if (files[i].size > 100 * 1024 * 1024) { // 100MB
149
+ alert(`File ${files[i].name} exceeds the maximum allowed size of 100MB.`);
150
+ continue;
151
+ }
152
}
153
+ formData.append('files[]', files[i]);
154
}
152
- formData.append('files[]', files[i]);
153
- }
154
-
155
- // Proceed with upload after validation
156
- fetch('/uploadWorkDirFiles', {
157
- method: 'POST',
158
- body: formData
159
- })
160
- .then(response => response.json())
161
- .then(data => {
162
- if (data.ok) {
155
+
156
+ // Proceed with upload after validation
157
+ const response = await fetch('/upload_work_dir_files', {
158
+ method: 'POST',
159
+ body: formData
160
+ });
161
+
162
+ if (response.ok) {
163
+ const data = await response.json();
164
// Update the file list with new data
165
this.browser.entries = data.data.entries.map(entry => ({
166
...entry,
@@ -167,48 +168,52 @@ const fileBrowserModalProxy = {
168
}));
169
this.browser.currentPath = data.data.current_path;
170
this.browser.parentPath = data.data.parent_path;
170
-
171
+
172
// Show success message
173
if (data.failed && data.failed.length > 0) {
174
const failedFiles = data.failed.map(file => `${file.name}: ${file.error}`).join('\n');
175
alert(`Some files failed to upload:\n${failedFiles}`);
176
}
177
} else {
178
+
179
alert(data.message);
180
}
179
- })
180
- .catch(error => {
181
+
182
+ } catch (error) {
183
console.error('Error uploading files:', error);
184
alert('Error uploading files');
183
- });
185
+ }
186
},
187
186
- downloadFile(file) {
188
+ async downloadFile(file) {
189
if (file.is_dir) return;
188
-
189
- const downloadUrl = `/downloadWorkDirFile?path=${encodeURIComponent(file.path)}`;
190
-
191
- fetch(downloadUrl)
192
-
193
- .then(response => {
194
- if (!response.ok) {
195
- throw new Error('Network response was not ok');
196
- }
197
- return response.blob();
198
- })
199
- .then(blob => {
200
- const link = document.createElement('a');
201
- link.href = window.URL.createObjectURL(blob);
202
- link.download = file.name;
203
- document.body.appendChild(link);
204
- link.click();
205
- document.body.removeChild(link);
206
- window.URL.revokeObjectURL(link.href);
207
- })
208
- .catch(error => {
209
- console.error('Error downloading file:', error);
210
- alert('Error downloading file');
211
- });
190
+
191
+ try {
192
+
193
+ const downloadUrl = `/download_work_dir_file?path=${encodeURIComponent(file.path)}`;
194
+
195
+ const response = await fetch(downloadUrl)
196
+
197
+
198
+ if (!response.ok) {
199
+ throw new Error('Network response was not ok');
200
+ }
201
+
202
+ const blob = await response.blob();
203
+
204
+ const link = document.createElement('a');
205
+ link.href = window.URL.createObjectURL(blob);
206
+ link.download = file.name;
207
+ document.body.appendChild(link);
208
+ link.click();
209
+ document.body.removeChild(link);
210
+ window.URL.revokeObjectURL(link.href);
211
+
212
+ } catch (error) {
213
+
214
+ console.error('Error downloading file:', error);
215
+ alert('Error downloading file');
216
+ }
217
},
218
219
// Helper Functions
webui/index.css
+1
@@ -356,6 +356,7 @@ h4 {
356
color: var(--color-text);
357
opacity: 0.7;
358
font-size: 0.7rem;
359
+ user-select: all;
360
}
361
362
pre {
webui/index.js
+64
-61
@@ -114,7 +114,7 @@ export async function sendMessage() {
114
formData.append('attachments', attachments[i].file);
115
}
116
117
- response = await fetch('/msg', {
117
+ response = await fetch('/message_async', {
118
method: 'POST',
119
body: formData
120
});
@@ -125,7 +125,7 @@ export async function sendMessage() {
125
context,
126
message_id: messageId
127
};
128
- response = await fetch('/msg', {
128
+ response = await fetch('/message_async', {
129
method: 'POST',
130
headers: {
131
'Content-Type': 'application/json'
@@ -138,13 +138,15 @@ export async function sendMessage() {
138
const jsonResponse = await response.json();
139
if (!jsonResponse) {
140
toast("No response returned.", "error");
141
- } else if (!jsonResponse.ok) {
142
- if (jsonResponse.message) {
143
- toast(jsonResponse.message, "error");
144
- } else {
145
- toast("Undefined error.", "error");
146
- }
147
- } else {
141
+ }
142
+ // else if (!jsonResponse.ok) {
143
+ // if (jsonResponse.message) {
144
+ // toast(jsonResponse.message, "error");
145
+ // } else {
146
+ // toast("Undefined error.", "error");
147
+ // }
148
+ // }
149
+ else {
150
setContext(jsonResponse.context);
151
}
152
@@ -257,10 +259,10 @@ window.loadKnowledge = async function () {
259
body: formData,
260
});
261
260
- const data = await response.json();
261
- if (!data.ok) {
262
- toast(data.message, "error");
262
+ if (!response.ok) {
263
+ toast(await response.text(), "error");
264
} else {
265
+ const data = await response.json();
266
toast("Knowledge files imported: " + data.filenames.join(", "), "success");
267
}
268
};
@@ -274,7 +276,7 @@ function adjustTextareaHeight() {
276
chatInput.style.height = (chatInput.scrollHeight) + 'px';
277
}
278
277
-window.sendJsonData = async function (url, data) {
279
+export const sendJsonData = async function (url, data) {
280
const response = await fetch(url, {
281
method: 'POST',
282
headers: {
@@ -284,11 +286,13 @@ window.sendJsonData = async function (url, data) {
286
});
287
288
if (!response.ok) {
287
- throw new Error('Network response was not ok');
289
+ const error = await response.text();
290
+ throw new Error(error);
291
}
292
const jsonResponse = await response.json();
293
return jsonResponse;
294
}
295
+window.sendJsonData = sendJsonData
296
297
function generateGUID() {
298
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
@@ -308,40 +312,35 @@ async function poll() {
312
const response = await sendJsonData("/poll", { log_from: lastLogVersion, context });
313
//console.log(response)
314
311
- if (response.ok) {
312
-
313
- if (!context) setContext(response.context)
314
- if (response.context != context) return //skip late polls after context change
315
+ if (!context) setContext(response.context)
316
+ if (response.context != context) return //skip late polls after context change
317
316
- if (lastLogGuid != response.log_guid) {
317
- chatHistory.innerHTML = ""
318
- lastLogVersion = 0
319
- }
318
+ if (lastLogGuid != response.log_guid) {
319
+ chatHistory.innerHTML = ""
320
+ lastLogVersion = 0
321
+ }
322
321
- if (lastLogVersion != response.log_version) {
322
- updated = true
323
- for (const log of response.logs) {
324
- const messageId = log.id || log.no; // Use log.id if available
325
- setMessage(messageId, log.type, log.heading, log.content, log.temp, log.kvps);
326
- }
327
- afterMessagesUpdate(response.logs)
323
+ if (lastLogVersion != response.log_version) {
324
+ updated = true
325
+ for (const log of response.logs) {
326
+ const messageId = log.id || log.no; // Use log.id if available
327
+ setMessage(messageId, log.type, log.heading, log.content, log.temp, log.kvps);
328
}
329
+ afterMessagesUpdate(response.logs)
330
+ }
331
330
- updateProgress(response.log_progress)
331
-
332
- //set ui model vars from backend
333
- const inputAD = Alpine.$data(inputSection);
334
- inputAD.paused = response.paused;
335
- const statusAD = Alpine.$data(statusSection);
336
- statusAD.connected = response.ok;
337
- const chatsAD = Alpine.$data(chatsSection);
338
- chatsAD.contexts = response.contexts;
339
-
340
- lastLogVersion = response.log_version;
341
- lastLogGuid = response.log_guid;
332
+ updateProgress(response.log_progress)
333
334
+ //set ui model vars from backend
335
+ const inputAD = Alpine.$data(inputSection);
336
+ inputAD.paused = response.paused;
337
+ const statusAD = Alpine.$data(statusSection);
338
+ statusAD.connected = true;
339
+ const chatsAD = Alpine.$data(chatsSection);
340
+ chatsAD.contexts = response.contexts;
341
344
- }
342
+ lastLogVersion = response.log_version;
343
+ lastLogGuid = response.log_guid;
344
345
} catch (error) {
346
console.error('Error:', error);
@@ -375,7 +374,7 @@ function speakMessages(logs) {
374
function updateProgress(progress) {
375
const defaultText = "Waiting for input"
376
if (!progress) progress = defaultText
378
-
377
+
378
if (progress == defaultText) {
379
removeClassFromElement(progressBar, "shiny-text")
380
} else {
@@ -406,7 +405,7 @@ window.pauseAgent = async function (paused) {
405
}
406
407
window.resetChat = async function () {
409
- const resp = await sendJsonData("/reset", { context });
408
+ const resp = await sendJsonData("/chat_reset", { context });
409
updateAfterScroll()
410
}
411
@@ -434,7 +433,7 @@ window.killChat = async function (id) {
433
else setContext(generateGUID())
434
}
435
437
- if (found) sendJsonData("/remove", { context: id });
436
+ if (found) sendJsonData("/chat_remove", { context: id });
437
438
updateAfterScroll()
439
}
@@ -534,17 +533,19 @@ function toggleCssProperty(selector, property, value) {
533
window.loadChats = async function () {
534
try {
535
const fileContents = await readJsonFiles();
537
- const response = await sendJsonData("/loadChats", { chats: fileContents });
536
+ const response = await sendJsonData("/chat_load", { chats: fileContents });
537
538
if (!response) {
539
toast("No response returned.", "error")
541
- } else if (!response.ok) {
542
- if (response.message) {
543
- toast(response.message, "error")
544
- } else {
545
- toast("Undefined error.", "error")
546
- }
547
- } else {
540
+ }
541
+ // else if (!response.ok) {
542
+ // if (response.message) {
543
+ // toast(response.message, "error")
544
+ // } else {
545
+ // toast("Undefined error.", "error")
546
+ // }
547
+ // }
548
+ else {
549
setContext(response.ctxids[0])
550
toast("Chats loaded.", "success")
551
}
@@ -556,17 +557,19 @@ window.loadChats = async function () {
557
558
window.saveChat = async function () {
559
try {
559
- const response = await sendJsonData("/exportChat", { ctxid: context });
560
+ const response = await sendJsonData("/chat_export", { ctxid: context });
561
562
if (!response) {
563
toast("No response returned.", "error")
563
- } else if (!response.ok) {
564
- if (response.message) {
565
- toast(response.message, "error")
566
- } else {
567
- toast("Undefined error.", "error")
568
- }
569
- } else {
564
+ }
565
+ // else if (!response.ok) {
566
+ // if (response.message) {
567
+ // toast(response.message, "error")
568
+ // } else {
569
+ // toast("Undefined error.", "error")
570
+ // }
571
+ // }
572
+ else {
573
downloadFile(response.ctxid + ".json", response.content)
574
toast("Chat file downloaded.", "success")
575
}
webui/settings.js
+2
-2
@@ -10,7 +10,7 @@ const settingsModalProxy = {
10
const modalAD = Alpine.$data(modalEl);
11
12
//get settings from backend
13
- const set = await sendJsonData("/getSettings", null);
13
+ const set = await sendJsonData("/settings_get", null);
14
15
const settings = {
16
"title": "Settings page",
@@ -43,7 +43,7 @@ const settingsModalProxy = {
43
44
const modalEl = document.getElementById('settingsModal');
45
const modalAD = Alpine.$data(modalEl);
46
- resp = await window.sendJsonData("/setSettings", modalAD.settings);
46
+ resp = await window.sendJsonData("/settings_set", modalAD.settings);
47
48
this.resolvePromise({
49
status: 'saved',
webui/speech.js
+1
-1
@@ -1,4 +1,4 @@
1
-import { pipeline, read_audio } from './transformers@3.0.2.js';
1
+// import { pipeline, read_audio } from './transformers@3.0.2.js';
2
import { updateChatInput, sendMessage } from './index.js';
3
4
const microphoneButton = document.getElementById('microphone-button');