""" Replay API — start/step/seek/reset replay sessions against the timeline journal. """ from fastapi import APIRouter from pydantic import BaseModel from typing import Optional, Any, Dict from backend.analysis.timeline_store import STORE as TIMELINE_STORE from backend.session.db import SessionDB router = APIRouter(prefix="/replay", tags=["replay"]) # Process-wide session DB (now SQLite-backed) SESSION_DB = SessionDB() class StartReplayBody(BaseModel): journal: Optional[str] = None # session id or "latest" session_id: Optional[str] = None # ------------------------- # START REPLAY SESSION # ------------------------- @router.post("/start") def start_replay(body: StartReplayBody): """Start a new replay session. Snapshots the current live timeline into a new session id, so the session has its own immutable event list.""" import uuid # Create the session first with a placeholder, then patch the source sid = SESSION_DB.create_session(body.journal or "live") # Snapshot the current live timeline into the session events = TIMELINE_STORE.all() # Log each event into the session so SESSION_DB.get_replay_stream(sid) # returns them (and the autopsy endpoint can find them) for event in events: # Don't double-store — just create a reference event SESSION_DB.log_event(sid, event.get("type", "event"), event) return { "session_id": sid, "event_count": len(events), "events_available": len(events), } # ------------------------- # GET EVENTS (full journal snapshot) # ------------------------- @router.get("/events") def get_events(): """Return the full current journal. Used by the replay UI on load.""" return {"events": TIMELINE_STORE.all()} # ------------------------- # GET EVENTS FOR A SPECIFIC SESSION # ------------------------- @router.get("/events/{session_id}") def get_session_events(session_id: str): return {"session_id": session_id, "events": SESSION_DB.get_replay_stream(session_id)} # ------------------------- # STEP FORWARD # ------------------------- @router.post("/step") def replay_step(session_id: str): events = SESSION_DB.get_replay_stream(session_id) return { "session_id": session_id, "event_count": len(events), "events": events[:1], } # ------------------------- # SEEK # ------------------------- @router.post("/seek") def replay_seek(session_id: str, timestamp: float): events = SESSION_DB.get_replay_stream(session_id) best = None for e in events: ts = e.get("ts") or e.get("timestamp") or 0 if ts <= timestamp: best = e else: break return {"event": best, "session_id": session_id} # ------------------------- # RESET # ------------------------- @router.post("/reset") def replay_reset(session_id: str): events = SESSION_DB.get_replay_stream(session_id) return { "session_id": session_id, "event": events[0] if events else None, "event_count": len(events), } # ------------------------- # Convenience: replay_endpoint wrapper for api.py facade # ------------------------- def replay_endpoint(session_id, registry=None): """Legacy wrapper — returns the session events for a given id.""" return { "session_id": session_id, "events": SESSION_DB.get_replay_stream(session_id), }