84c4f75e03
Turn the inert "Show Work" thinking panel into a real live activity log: - lyra/logbus.py: thread-safe in-memory ring buffer other modules publish to - chat.respond logs backend/model/embed per turn, recall counts, reply size; web layer logs chat errors - server: replace the keep-alive /stream/thinking stub with /stream/logs, an SSE endpoint that replays the recent buffer then streams new events - UI: repurpose the panel as a global "Live Log" — connects on load, renders level/time/msg/fields, drops the old per-session localStorage + dead popup Every turn now shows its backend + model in-app, so local-vs-cloud (free vs paid) is visible at a glance. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
32 lines
909 B
Python
32 lines
909 B
Python
"""In-memory live log bus.
|
|
|
|
A thread-safe ring buffer that any part of Lyra can publish to and the web
|
|
server streams to the browser over SSE. Deliberately process-local and
|
|
ephemeral — it's an activity feed, not durable logging.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import threading
|
|
import time
|
|
from collections import deque
|
|
|
|
_LOCK = threading.Lock()
|
|
_EVENTS: deque[dict] = deque(maxlen=500)
|
|
_SEQ = 0
|
|
|
|
|
|
def log(level: str, msg: str, **fields) -> None:
|
|
"""Publish an event. `level` is info/debug/error/system; fields are extras."""
|
|
global _SEQ
|
|
with _LOCK:
|
|
_SEQ += 1
|
|
_EVENTS.append(
|
|
{"seq": _SEQ, "ts": time.time(), "level": level, "msg": msg, "fields": fields}
|
|
)
|
|
|
|
|
|
def since(seq: int) -> list[dict]:
|
|
"""All buffered events with seq greater than `seq` (for SSE catch-up/polling)."""
|
|
with _LOCK:
|
|
return [e for e in _EVENTS if e["seq"] > seq]
|