Skip to content

Commit

Permalink
using a thin web viewer that is easy to style. This is the way here IMO.
Browse files Browse the repository at this point in the history
  • Loading branch information
norton120 committed Nov 26, 2024
1 parent 1c7884b commit 813f83c
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 86 deletions.
6 changes: 6 additions & 0 deletions installable_apps/installable_image.py
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion installable_apps/installable_logger.py
Original file line number Diff line number Diff line change
@@ -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)
51 changes: 51 additions & 0 deletions installable_apps/logserver/main.py
Original file line number Diff line number Diff line change
@@ -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})
50 changes: 50 additions & 0 deletions installable_apps/logserver/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!DOCTYPE html>

<html lang="en" class="dark">
<head>
<script src="https://cdn.tailwindcss.com"></script>
<title>Letta Logs</title>
<icon rel="icon" src="{{context.icon}}" />
</head>

<body class="dark:bg-gray-900">


<div class="flex items-center py-2 px-3">
<h1 class="text-3xl text-slate-300">{{context.title}}</h1>
</div>
<br />

<div class="flex items-center py-2 px-3">
<i>Logs stored at
<a href="file://{{context.log_file}}">{{context.log_file}}</a>
</i>
</div>

<div class="flex items-center py-2 px-3">
<div
id="logs"
class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
reading logs...
</div>
</div>

<script>
var ws_log = new WebSocket("ws://" + window.location.host + "/ws/log");

ws_log.onmessage = function (event) {
var logs = document.getElementById("logs");
var log_data = JSON.parse(event.data);
logs.innerHTML = ""; // Clear existing logs
log_data.forEach(function (log) {
var span = document.createElement("span");
span.classList.add("block");
span.style.color = log.color;
span.innerHTML = log.content;
logs.appendChild(span);
});
};
</script>
</body>
</html>
65 changes: 0 additions & 65 deletions installable_apps/logviewer.py

This file was deleted.

1 change: 0 additions & 1 deletion installable_apps/macOS/log_terminal.command

This file was deleted.

16 changes: 10 additions & 6 deletions installable_apps/server.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from typing import TYPE_CHECKING
from uvicorn import Server, Config
from time import sleep
import threading
from contextlib import contextmanager
from letta.server.rest_api.app import app
from letta.server.constants import REST_DEFAULT_PORT

if TYPE_CHECKING:
from fastapi import WSGIApplication

class ThreadedServer(Server):
#def install_signal_handlers(self):
# pass

@contextmanager
def run_in_thread(self):
Expand All @@ -22,6 +22,10 @@ def run_in_thread(self):
thread.join()

@classmethod
def get_configured_server(cls):
config = Config(app, host="localhost", port=REST_DEFAULT_PORT, log_level="info")
def get_configured_server(cls,
app: "WSGIApplication",
port: int,
host: str,
log_level: str = "info") -> "ThreadedServer":
config = Config(app, host=host, port=port, log_level="info")
return cls(config=config)
16 changes: 10 additions & 6 deletions installable_apps/startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import webbrowser

from letta.settings import settings
from letta.server.rest_api.app import app as letta_app
from letta.server.constants import REST_DEFAULT_PORT

from server import ThreadedServer
from logserver.main import app as log_app
from tray import Tray

pgdata = settings.letta_dir / "pgdata"
Expand All @@ -16,11 +19,12 @@

# feed database URI parts to the application
settings.pg_uri = database.get_uri()
# start the server
app_server = ThreadedServer.get_configured_server()
# start the servers

app_server = ThreadedServer.get_configured_server(letta_app, host="localhost", port=REST_DEFAULT_PORT)
log_server = ThreadedServer.get_configured_server(log_app, host="localhost", port=13774)
with app_server.run_in_thread():
tray = Tray()
tray.log_viewer.start_log_terminal()
webbrowser.open("https://app.letta.com")
tray.create()
with log_server.run_in_thread():
tray = Tray()
webbrowser.open("https://app.letta.com")
tray.create()
10 changes: 3 additions & 7 deletions installable_apps/tray.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@
from PIL import Image

from installable_image import InstallableImage
from logviewer import LogViewer


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"""
Expand All @@ -21,18 +18,17 @@ def discord(icon, item):
webbrowser.open("https://discord.gg/letta")

def _on_quit(icon, *args):
self.log_viewer.stop_log_terminal()
icon.stop()

def _start_log_viewer(icon, item):
self.log_viewer.start_log_terminal()
def _log_viewer(icon, item):
webbrowser.open("http://localhost:13774")

icon = Icon("Letta",
Image.open(self.icon_image),
menu=Menu(
MenuItem(
"View Logs",
_start_log_viewer
_log_viewer
),
MenuItem(
"Discord",
Expand Down

0 comments on commit 813f83c

Please sign in to comment.