fix: sshfs polling, WebSocket broadcast, prompt progress bar, resizable stats panel
- log_watcher: add --reopen-log mode (close/reopen each poll) for sshfs/network filesystems; fix nonlocal clients bug causing UnboundLocalError on first broadcast; fix list(clients) copy to prevent RuntimeError on concurrent set mutation - log_parser: capture progress=X.XX from prompt timing lines into Metrics.prompt_progress - frontend: show progress bar during processing_prompt state with PROMPT N% label; widen stats panel to 300px; remove model name truncation; add drag handle on right border of stats panel to resize between 280px–840px Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
20
server.py
20
server.py
@@ -17,7 +17,7 @@ FRONTEND_DIR = Path(__file__).parent / "frontend"
|
||||
|
||||
# ── App factory ───────────────────────────────────────────────────────────────
|
||||
|
||||
def create_app(log_path: str) -> FastAPI:
|
||||
def create_app(log_path: str, reopen_log: bool = False) -> FastAPI:
|
||||
app = FastAPI()
|
||||
app.mount("/static", StaticFiles(directory=FRONTEND_DIR), name="static")
|
||||
|
||||
@@ -41,11 +41,11 @@ def create_app(log_path: str) -> FastAPI:
|
||||
clients.discard(ws)
|
||||
|
||||
async def broadcast(state: ServerState):
|
||||
nonlocal last_state
|
||||
nonlocal last_state, clients
|
||||
last_state = state.to_dict()
|
||||
payload = json.dumps(last_state)
|
||||
dead: set[WebSocket] = set()
|
||||
for ws in clients:
|
||||
for ws in list(clients):
|
||||
try:
|
||||
await ws.send_text(payload)
|
||||
except Exception:
|
||||
@@ -63,7 +63,7 @@ def create_app(log_path: str) -> FastAPI:
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
watcher = LogWatcher(log_path, on_line, on_offline=on_offline)
|
||||
watcher = LogWatcher(log_path, on_line, on_offline=on_offline, reopen=reopen_log)
|
||||
watcher.start()
|
||||
|
||||
return app
|
||||
@@ -71,6 +71,13 @@ def create_app(log_path: str) -> FastAPI:
|
||||
|
||||
# ── CLI entry point ───────────────────────────────────────────────────────────
|
||||
|
||||
def _config_bool(key: str) -> bool:
|
||||
config_file = Path("config.json")
|
||||
if config_file.exists():
|
||||
return bool(json.loads(config_file.read_text()).get(key, False))
|
||||
return False
|
||||
|
||||
|
||||
def resolve_log_path(cli_log: Optional[str]) -> str:
|
||||
path = cli_log or os.environ.get("LLAMAGOCHI_LOG")
|
||||
if not path:
|
||||
@@ -92,9 +99,12 @@ 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)")
|
||||
ap.add_argument("--reopen-log", action="store_true",
|
||||
help="Close and reopen log file each poll cycle (for sshfs/network filesystems)")
|
||||
args = ap.parse_args()
|
||||
log_path = resolve_log_path(args.log)
|
||||
app = create_app(log_path)
|
||||
reopen_log = args.reopen_log or _config_bool("reopen_log")
|
||||
app = create_app(log_path, reopen_log=reopen_log)
|
||||
uvicorn.run(app, host="0.0.0.0", port=args.port)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user