Files
Llamagochi/server.py
2026-06-22 18:58:49 +03:00

103 lines
3.2 KiB
Python

import argparse
import asyncio
import json
import os
from pathlib import Path
from typing import Optional
import uvicorn
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from log_parser import LogParser, ServerState
from log_watcher import LogWatcher
FRONTEND_DIR = Path(__file__).parent / "frontend"
# ── App factory ───────────────────────────────────────────────────────────────
def create_app(log_path: str) -> FastAPI:
app = FastAPI()
app.mount("/static", StaticFiles(directory=FRONTEND_DIR), name="static")
clients: set[WebSocket] = set()
parser = LogParser()
last_state: dict = parser.state.to_dict()
@app.get("/")
async def index():
return FileResponse(FRONTEND_DIR / "index.html")
@app.websocket("/ws")
async def ws_endpoint(ws: WebSocket):
await ws.accept()
clients.add(ws)
await ws.send_text(json.dumps(last_state))
try:
while True:
await ws.receive_text()
except WebSocketDisconnect:
clients.discard(ws)
async def broadcast(state: ServerState):
nonlocal last_state
last_state = state.to_dict()
payload = json.dumps(last_state)
dead: set[WebSocket] = set()
for ws in clients:
try:
await ws.send_text(payload)
except Exception:
dead.add(ws)
clients -= dead
async def on_line(line: str):
result = parser.feed(line)
if result is not None:
await broadcast(result)
async def on_offline():
parser._state.state = "offline"
await broadcast(parser.state)
@app.on_event("startup")
async def startup():
watcher = LogWatcher(log_path, on_line, on_offline=on_offline)
watcher.start()
return app
# ── CLI entry point ───────────────────────────────────────────────────────────
def resolve_log_path(cli_log: Optional[str]) -> str:
path = cli_log or os.environ.get("LLAMAGOCHI_LOG")
if not path:
config_file = Path("config.json")
if config_file.exists():
data = json.loads(config_file.read_text())
path = data.get("log")
if not path:
raise SystemExit(
"Error: log file path required.\n"
" Use --log /path/to/llama-server.log\n"
" or set LLAMAGOCHI_LOG env var\n"
" or set \"log\" key in config.json"
)
return path
def main():
ap = argparse.ArgumentParser(description="Llamagochi — llama-server virtual pet")
ap.add_argument("--log", help="Path to llama-server log file")
ap.add_argument("--port", type=int, default=8080, help="HTTP port (default: 8080)")
args = ap.parse_args()
log_path = resolve_log_path(args.log)
app = create_app(log_path)
uvicorn.run(app, host="0.0.0.0", port=args.port)
if __name__ == "__main__":
main()