From c92cbf07fc4ccca4d2f5f1e25a0c620d30c98c61 Mon Sep 17 00:00:00 2001 From: Ethan Knox Date: Wed, 20 Nov 2024 12:14:09 -0500 Subject: [PATCH 01/33] works as POC with embedded pg+pgvector --- installable_apps/requirements.txt | 3 +++ installable_apps/startup.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 installable_apps/requirements.txt create mode 100644 installable_apps/startup.py diff --git a/installable_apps/requirements.txt b/installable_apps/requirements.txt new file mode 100644 index 0000000000..579712ec78 --- /dev/null +++ b/installable_apps/requirements.txt @@ -0,0 +1,3 @@ +pgserver~=0.1.4 +py2app~=0.28.8 +#py2exe - not currently supporting 3.12 TODO upgrade or downgrade \ No newline at end of file diff --git a/installable_apps/startup.py b/installable_apps/startup.py new file mode 100644 index 0000000000..57e58144cf --- /dev/null +++ b/installable_apps/startup.py @@ -0,0 +1,23 @@ +from sys import exit +import pgserver + +from letta.settings import settings +from letta.server.rest_api.app import start_server + +# TODO: pull from the app config stack +pgdata = settings.letta_dir / "pgdata" +pgdata.mkdir(parents=True, exist_ok=True) + +database = pgserver.get_server(pgdata) +# create pg vector extension +database.psql('CREATE EXTENSION IF NOT EXISTS pg_vector;') + +# feed database URI parts to the application +settings.pg_uri = database.get_uri() + +# start the server +try: + start_server() +except (KeyboardInterrupt, SystemExit): + # TODO: how does the application close signal manifest? SystemExit? Confirm this. + exit(0) From 7aeff195f5d13640523535ce1de50e36451616a4 Mon Sep 17 00:00:00 2001 From: Ethan Knox Date: Wed, 20 Nov 2024 16:11:34 -0500 Subject: [PATCH 02/33] this was working: icon appears in toolbar, app runs, app.letta.com opens, and you can shut down via 'quit letta' in the toolbar menu. Weirdly started getting pydantic errors from the app a minute ago --- installable_apps/dark_tray.png | Bin 0 -> 17181 bytes installable_apps/requirements.txt | 4 ++++ installable_apps/server.py | 27 ++++++++++++++++++++++ installable_apps/startup.py | 21 ++++++++--------- installable_apps/tray.png | Bin 0 -> 15425 bytes installable_apps/tray.py | 36 ++++++++++++++++++++++++++++++ 6 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 installable_apps/dark_tray.png create mode 100644 installable_apps/server.py create mode 100644 installable_apps/tray.png create mode 100644 installable_apps/tray.py diff --git a/installable_apps/dark_tray.png b/installable_apps/dark_tray.png new file mode 100644 index 0000000000000000000000000000000000000000..b516ce6088c50fc110f0a555efa5bc487a648829 GIT binary patch literal 17181 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 literal 0 HcmV?d00001 diff --git a/installable_apps/tray.py b/installable_apps/tray.py new file mode 100644 index 0000000000..db370c510f --- /dev/null +++ b/installable_apps/tray.py @@ -0,0 +1,36 @@ +import darkdetect +import webbrowser +from pathlib import Path +from pystray import Icon, Menu, MenuItem +from PIL import Image + + +class Tray: + icon_image: Path + + def __init__(self): + image_name = ("dark_" if darkdetect.isDark() else "") + "tray.png" + self.icon_image = Path(__file__).parent / image_name + + def create(self) -> None: + """creates tray icon in a thread""" + + def discord(icon, item): + webbrowser.open("https://discord.gg/letta") + + def _on_quit(icon, *args): + icon.stop() + + icon = Icon("Letta", + Image.open(self.icon_image), + menu=Menu( + MenuItem( + "Discord", + discord + ))) + menu=Menu( + MenuItem( + "Quit Letta", + _on_quit + ))) + icon.run() From 3090153d08de8df9c668c78734aa94425203d177 Mon Sep 17 00:00:00 2001 From: Ethan Knox Date: Fri, 22 Nov 2024 14:14:18 -0500 Subject: [PATCH 03/33] qt-based log viewer --- .../{dark_tray.png => assets/dark_icon.png} | Bin .../{tray.png => assets/icon.png} | Bin installable_apps/cutelog_overload.py | 44 ++++++++++++++++++ installable_apps/installable_image.py | 10 ++++ installable_apps/logviewer.py | 17 +++++++ installable_apps/requirements.txt | 1 + installable_apps/startup.py | 4 ++ installable_apps/tray.py | 26 ++++++++--- 8 files changed, 95 insertions(+), 7 deletions(-) rename installable_apps/{dark_tray.png => assets/dark_icon.png} (100%) rename installable_apps/{tray.png => assets/icon.png} (100%) create mode 100644 installable_apps/cutelog_overload.py create mode 100644 installable_apps/installable_image.py create mode 100644 installable_apps/logviewer.py mode change 100644 => 100755 installable_apps/startup.py diff --git a/installable_apps/dark_tray.png b/installable_apps/assets/dark_icon.png similarity index 100% rename from installable_apps/dark_tray.png rename to installable_apps/assets/dark_icon.png diff --git a/installable_apps/tray.png b/installable_apps/assets/icon.png similarity index 100% rename from installable_apps/tray.png rename to installable_apps/assets/icon.png diff --git a/installable_apps/cutelog_overload.py b/installable_apps/cutelog_overload.py new file mode 100644 index 0000000000..6faac0b0f9 --- /dev/null +++ b/installable_apps/cutelog_overload.py @@ -0,0 +1,44 @@ +## Overloading the entrypoint to cutelog control the flow +import sys +import signal +from cutelog.config import ROOT_LOG, CONFIG, parse_cmdline +from cutelog.main_window import MainWindow +from cutelog.resources import qCleanupResources +from qtpy.QtGui import QIcon +from qtpy.QtWidgets import QApplication +from qtpy import PYQT5, PYSIDE2 + +from installable_image import InstallableImage + +class CutelogOverload: + app: "QApplication" + window: "MainWindow" + exec = "QApplication.exec_" + + def __init__(self): + self.app = QApplication(sys.argv) + self.exec = self.app.exec_ + + def start_log_viewer(self): + if not PYQT5 and not PYSIDE2: + if sys.platform == 'linux': + sys.exit("Error: a compatible Qt library couldn't be imported.\n" + "Please install python3-pyqt5 (or just python-pyqt5) from your package manager.") + else: # this technically shouldn't ever happen + sys.exit("Error: a compatible Qt library couldn't be imported.\n" + "Please install it by running `pip install pyqt5") + if sys.platform == 'win32': + import ctypes + appid = 'busimus.cutelog' + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid) + image = str(InstallableImage.get_icon_path().absolute()) + self.app.setWindowIcon(QIcon(image)) + overrides, load_logfiles = parse_cmdline(ROOT_LOG) + CONFIG.set_overrides(overrides) + self.window = MainWindow(ROOT_LOG, self.app, load_logfiles) + self.window.setWindowTitle('Letta') + signal.signal(signal.SIGINT, self.window.signal_handler) + + def stop_log_viewer(self): + sys.exit(self.exec()) + qCleanupResources() \ No newline at end of file diff --git a/installable_apps/installable_image.py b/installable_apps/installable_image.py new file mode 100644 index 0000000000..e7228713d9 --- /dev/null +++ b/installable_apps/installable_image.py @@ -0,0 +1,10 @@ +from pathlib import Path +import darkdetect + + +class InstallableImage: + + @classmethod + def get_icon_path(cls) -> Path: + image_name = ("dark_" if darkdetect.isDark() else "") + "icon.png" + return (Path(__file__).parent / "assets") / image_name diff --git a/installable_apps/logviewer.py b/installable_apps/logviewer.py new file mode 100644 index 0000000000..6595f750a3 --- /dev/null +++ b/installable_apps/logviewer.py @@ -0,0 +1,17 @@ +import logging +from logging.handlers import SocketHandler + +from letta.server.server import logger as server_logger + +class CuteLogger: + + def apply_cuteness(self): + root_logger = logging.getLogger() + uvicorn_loggers = [ + logging.getLogger(name) + for name in logging.root.manager.loggerDict.keys() + if name.startswith("uvicorn.") + ] + socket_handler = SocketHandler("localhost", 19996) + for logger in (root_logger, server_logger,*uvicorn_loggers): + logger.addHandler(socket_handler) \ No newline at end of file diff --git a/installable_apps/requirements.txt b/installable_apps/requirements.txt index 74b4fb8062..75a4e3e7da 100644 --- a/installable_apps/requirements.txt +++ b/installable_apps/requirements.txt @@ -4,4 +4,5 @@ py2app~=0.28.8 pystray pillow darkdetect +cutelog #py2exe - not currently supporting 3.12 TODO upgrade or downgrade \ No newline at end of file diff --git a/installable_apps/startup.py b/installable_apps/startup.py old mode 100644 new mode 100755 index 19d61db61e..0598aef0bf --- a/installable_apps/startup.py +++ b/installable_apps/startup.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import pgserver import webbrowser @@ -5,6 +6,7 @@ from server import ThreadedServer from tray import Tray +from logviewer import CuteLogger pgdata = settings.letta_dir / "pgdata" pgdata.mkdir(parents=True, exist_ok=True) @@ -19,6 +21,8 @@ app_server = ThreadedServer.get_configured_server() with app_server.run_in_thread(): + cute = CuteLogger() + cute.apply_cuteness() tray = Tray() webbrowser.open("https://app.letta.com") tray.create() diff --git a/installable_apps/tray.py b/installable_apps/tray.py index db370c510f..7e35d60f35 100644 --- a/installable_apps/tray.py +++ b/installable_apps/tray.py @@ -1,35 +1,47 @@ +from typing import TYPE_CHECKING import darkdetect import webbrowser -from pathlib import Path from pystray import Icon, Menu, MenuItem from PIL import Image +from cutelog_overload import CutelogOverload +from installable_image import InstallableImage + +if TYPE_CHECKING: + from pathlib import Path class Tray: - icon_image: Path + icon_image: "Path" def __init__(self): - image_name = ("dark_" if darkdetect.isDark() else "") + "tray.png" - self.icon_image = Path(__file__).parent / image_name + self.icon_image = InstallableImage.get_icon_path() def create(self) -> None: """creates tray icon in a thread""" + log_viewer = CutelogOverload() def discord(icon, item): webbrowser.open("https://discord.gg/letta") def _on_quit(icon, *args): + log_viewer.stop_log_viewer() icon.stop() + def _start_log_viewer(icon, item): + log_viewer.start_log_viewer() + icon = Icon("Letta", Image.open(self.icon_image), menu=Menu( + MenuItem( + "View Logs", + _start_log_viewer + ), MenuItem( "Discord", discord - ))) - menu=Menu( - MenuItem( + ), + MenuItem( "Quit Letta", _on_quit ))) From bf66399574bc71c2f9713d705e00125b6ba17a08 Mon Sep 17 00:00:00 2001 From: Ethan Knox Date: Mon, 25 Nov 2024 15:46:26 -0500 Subject: [PATCH 04/33] temporarily using a log terminal, moving to a viewer shortly --- installable_apps/cutelog_overload.py | 44 --------------- installable_apps/installable_logger.py | 6 +++ installable_apps/logviewer.py | 60 ++++++++++++++++++--- installable_apps/macOS/log_terminal.command | 1 + installable_apps/startup.py | 4 +- installable_apps/tray.py | 13 ++--- 6 files changed, 67 insertions(+), 61 deletions(-) delete mode 100644 installable_apps/cutelog_overload.py create mode 100644 installable_apps/installable_logger.py create mode 100755 installable_apps/macOS/log_terminal.command diff --git a/installable_apps/cutelog_overload.py b/installable_apps/cutelog_overload.py deleted file mode 100644 index 6faac0b0f9..0000000000 --- a/installable_apps/cutelog_overload.py +++ /dev/null @@ -1,44 +0,0 @@ -## Overloading the entrypoint to cutelog control the flow -import sys -import signal -from cutelog.config import ROOT_LOG, CONFIG, parse_cmdline -from cutelog.main_window import MainWindow -from cutelog.resources import qCleanupResources -from qtpy.QtGui import QIcon -from qtpy.QtWidgets import QApplication -from qtpy import PYQT5, PYSIDE2 - -from installable_image import InstallableImage - -class CutelogOverload: - app: "QApplication" - window: "MainWindow" - exec = "QApplication.exec_" - - def __init__(self): - self.app = QApplication(sys.argv) - self.exec = self.app.exec_ - - def start_log_viewer(self): - if not PYQT5 and not PYSIDE2: - if sys.platform == 'linux': - sys.exit("Error: a compatible Qt library couldn't be imported.\n" - "Please install python3-pyqt5 (or just python-pyqt5) from your package manager.") - else: # this technically shouldn't ever happen - sys.exit("Error: a compatible Qt library couldn't be imported.\n" - "Please install it by running `pip install pyqt5") - if sys.platform == 'win32': - import ctypes - appid = 'busimus.cutelog' - ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid) - image = str(InstallableImage.get_icon_path().absolute()) - self.app.setWindowIcon(QIcon(image)) - overrides, load_logfiles = parse_cmdline(ROOT_LOG) - CONFIG.set_overrides(overrides) - self.window = MainWindow(ROOT_LOG, self.app, load_logfiles) - self.window.setWindowTitle('Letta') - signal.signal(signal.SIGINT, self.window.signal_handler) - - def stop_log_viewer(self): - sys.exit(self.exec()) - qCleanupResources() \ No newline at end of file diff --git a/installable_apps/installable_logger.py b/installable_apps/installable_logger.py new file mode 100644 index 0000000000..227c0310f9 --- /dev/null +++ b/installable_apps/installable_logger.py @@ -0,0 +1,6 @@ +from logging import getLogger + +def get_logger(name: str): + logger = getLogger("installable_apps") + logger.setLevel("DEBUG") + return logger.getChild(name) \ No newline at end of file diff --git a/installable_apps/logviewer.py b/installable_apps/logviewer.py index 6595f750a3..199987f4e4 100644 --- a/installable_apps/logviewer.py +++ b/installable_apps/logviewer.py @@ -1,17 +1,65 @@ +from pathlib import Path +import psutil import logging -from logging.handlers import SocketHandler +import subprocess +from letta.settings import settings from letta.server.server import logger as server_logger -class CuteLogger: +from installable_logger import get_logger - def apply_cuteness(self): +installable_logger = get_logger(__name__) + + +class LogViewer: + log_dir: "Path" + + def __init__(self): + self.log_dir = settings.letta_dir / "logs" + self.log_dir.mkdir(parents=True, exist_ok=True) + self.add_file_handler() + + def add_file_handler(self): + """adds the rotating file handler to all monitored loggers""" + # create a rotating file handler with 1mb size and no backup count + file_handler = logging.handlers.RotatingFileHandler( + maxBytes=10000, backupCount=2, filename=self.log_dir / "letta.log" + ) root_logger = logging.getLogger() uvicorn_loggers = [ logging.getLogger(name) for name in logging.root.manager.loggerDict.keys() if name.startswith("uvicorn.") ] - socket_handler = SocketHandler("localhost", 19996) - for logger in (root_logger, server_logger,*uvicorn_loggers): - logger.addHandler(socket_handler) \ No newline at end of file + for logger in (installable_logger, root_logger, server_logger, *uvicorn_loggers): + logger.addHandler(file_handler) + + + def start_log_terminal(self) -> None: + """Start the log terminal""" + # MacOS only TODO win/linux + log_terminal_path = Path(__file__).parent / "macOS" / "log_terminal.command" + self.log_terminal = subprocess.Popen( + ["open", str(log_terminal_path.absolute())], stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + return self.log_terminal + + def stop_log_terminal(self) -> None: + """Stop the log terminal""" + installable_logger.info("Stopping log terminal...") + for process in psutil.process_iter(): + try: + last_command = process.cmdline() + installable_logger.info("Checking process %s", last_command) + except (psutil.AccessDenied, psutil.NoSuchProcess,) as e: + installable_logger.info("Error checking process %s: %s", process, e) + continue + if "log_terminal.command" in last_command: + installable_logger.info("Killing log terminal process %s", process.pid) + try: + process.kill() + installable_logger.info("Log terminal process killed") + except Exception as e: + installable_logger.error("Error killing log terminal process: %s", e) + raise e + return \ No newline at end of file diff --git a/installable_apps/macOS/log_terminal.command b/installable_apps/macOS/log_terminal.command new file mode 100755 index 0000000000..afe73ccedd --- /dev/null +++ b/installable_apps/macOS/log_terminal.command @@ -0,0 +1 @@ +tail -f ~/.letta/logs/letta.log \ No newline at end of file diff --git a/installable_apps/startup.py b/installable_apps/startup.py index 0598aef0bf..130b67c5b6 100755 --- a/installable_apps/startup.py +++ b/installable_apps/startup.py @@ -6,7 +6,6 @@ from server import ThreadedServer from tray import Tray -from logviewer import CuteLogger pgdata = settings.letta_dir / "pgdata" pgdata.mkdir(parents=True, exist_ok=True) @@ -21,8 +20,7 @@ app_server = ThreadedServer.get_configured_server() with app_server.run_in_thread(): - cute = CuteLogger() - cute.apply_cuteness() tray = Tray() + tray.log_viewer.start_log_terminal() webbrowser.open("https://app.letta.com") tray.create() diff --git a/installable_apps/tray.py b/installable_apps/tray.py index 7e35d60f35..b7712f46c1 100644 --- a/installable_apps/tray.py +++ b/installable_apps/tray.py @@ -1,34 +1,31 @@ -from typing import TYPE_CHECKING -import darkdetect +from pathlib import Path import webbrowser from pystray import Icon, Menu, MenuItem from PIL import Image -from cutelog_overload import CutelogOverload from installable_image import InstallableImage +from logviewer import LogViewer -if TYPE_CHECKING: - from pathlib import Path class Tray: icon_image: "Path" def __init__(self): self.icon_image = InstallableImage.get_icon_path() + self.log_viewer = LogViewer() def create(self) -> None: """creates tray icon in a thread""" - log_viewer = CutelogOverload() def discord(icon, item): webbrowser.open("https://discord.gg/letta") def _on_quit(icon, *args): - log_viewer.stop_log_viewer() + self.log_viewer.stop_log_terminal() icon.stop() def _start_log_viewer(icon, item): - log_viewer.start_log_viewer() + self.log_viewer.start_log_terminal() icon = Icon("Letta", Image.open(self.icon_image), From 1dd6627ac9714b813e2ce15a93ceedf5ad2747b6 Mon Sep 17 00:00:00 2001 From: Ethan Knox Date: Tue, 26 Nov 2024 10:43:54 -0500 Subject: [PATCH 05/33] using a thin web viewer that is easy to style. This is the way here IMO. --- installable_apps/installable_image.py | 6 ++ installable_apps/installable_logger.py | 2 +- installable_apps/logserver/main.py | 51 +++++++++++++++ .../logserver/templates/index.html | 50 ++++++++++++++ installable_apps/logviewer.py | 65 ------------------- installable_apps/macOS/log_terminal.command | 1 - installable_apps/server.py | 16 +++-- installable_apps/startup.py | 16 +++-- installable_apps/tray.py | 10 +-- 9 files changed, 131 insertions(+), 86 deletions(-) create mode 100644 installable_apps/logserver/main.py create mode 100644 installable_apps/logserver/templates/index.html delete mode 100644 installable_apps/logviewer.py delete mode 100755 installable_apps/macOS/log_terminal.command diff --git a/installable_apps/installable_image.py b/installable_apps/installable_image.py index e7228713d9..de4408e902 100644 --- a/installable_apps/installable_image.py +++ b/installable_apps/installable_image.py @@ -1,10 +1,16 @@ from pathlib import Path import darkdetect +from installable_logger import get_logger + +logger = get_logger(__name__) + class InstallableImage: @classmethod def get_icon_path(cls) -> Path: + logger.debug("Determining icon path from system settings...") image_name = ("dark_" if darkdetect.isDark() else "") + "icon.png" + logger.debug(f"Icon path determined to be {image_name} based on system settings.") return (Path(__file__).parent / "assets") / image_name diff --git a/installable_apps/installable_logger.py b/installable_apps/installable_logger.py index 227c0310f9..fd5b297e72 100644 --- a/installable_apps/installable_logger.py +++ b/installable_apps/installable_logger.py @@ -1,6 +1,6 @@ from logging import getLogger def get_logger(name: str): - logger = getLogger("installable_apps") + logger = getLogger("letta_installable_apps") logger.setLevel("DEBUG") return logger.getChild(name) \ No newline at end of file diff --git a/installable_apps/logserver/main.py b/installable_apps/logserver/main.py new file mode 100644 index 0000000000..de4f2f6871 --- /dev/null +++ b/installable_apps/logserver/main.py @@ -0,0 +1,51 @@ +from pathlib import Path +from fastapi import FastAPI, WebSocket, Request +from fastapi.templating import Jinja2Templates +import asyncio + +from letta.settings import settings +from installable_image import InstallableImage +from installable_logger import get_logger + +logger = get_logger(__name__) + +target_file = settings.letta_dir / "logs" / "letta.log" + +app = FastAPI() + + +async def log_reader(n=5): + log_lines = [] + with open(target_file, "r") as file: + for line in file.readlines()[-n:]: + if line is None: + continue + if line.__contains__("ERROR"): + log_line = {"content": line, "color": "red"} + elif line.__contains__("WARNING"): + log_line = {"content": line, "color": "yellow"} + else: + log_line = {"content": line, "color": "green"} + log_lines.append(log_line) + return log_lines + +@app.websocket("/ws/log") +async def websocket_endpoint_log(websocket: WebSocket): + await websocket.accept() + + try: + while True: + await asyncio.sleep(1) + logs = await log_reader(30) + await websocket.send_json(logs) + except Exception as e: + logger.error(f"Error in log websocket: {e}") + + finally: + await websocket.close() + +@app.get("/") +async def get(request: Request): + context = {"log_file": target_file, "icon": InstallableImage().get_icon_path()} + templates = Jinja2Templates(directory=str((Path(__file__).parent / "templates").absolute())) + return templates.TemplateResponse("index.html", {"request": request, "context": context}) diff --git a/installable_apps/logserver/templates/index.html b/installable_apps/logserver/templates/index.html new file mode 100644 index 0000000000..b5c7b35136 --- /dev/null +++ b/installable_apps/logserver/templates/index.html @@ -0,0 +1,50 @@ + + + + + + Letta Logs +