/** * @file SnapshotView.tsx * @description The read-only page `/snapshot/:token`: a session as it was frozen * by `POST /api/snapshots` — header, SessionOverview fed the frozen stats, the * agents, and the Replay / Timeline tabs over the frozen events. Nothing here * reads live data. * @author Nguyễn Ngọc Trí Vĩ */ import { useEffect, useMemo, useState } from "react"; import { Link, useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { Bot, Camera, Clock, DollarSign, Film, List, ShieldCheck } from "lucide-react"; import { api } from "../lib/api"; import type { AgentInfo } from "../lib/event-grouping"; import { formatDateTime, fmtCostFull } from "../lib/format"; import type { SessionSnapshotInfo, SessionSnapshotPayload } from "../lib/types"; import { SessionOverview } from "../components/SessionOverview"; import { AgentCard } from "../components/AgentCard"; import { EventTimeline } from "../components/EventTimeline"; import { EventReplay } from "../components/EventReplay"; import { SessionStatusBadge } from "../components/StatusBadge"; import { Skeleton } from "../components/Skeleton"; type Tab = "overview" | "replay" | "timeline"; export function SnapshotView() { const { token = "" } = useParams<{ token: string }>(); const { t } = useTranslation("sessions"); const [data, setData] = useState<{ snapshot: SessionSnapshotInfo; payload: SessionSnapshotPayload; } | null>(null); const [error, setError] = useState(null); const [tab, setTab] = useState("overview"); useEffect(() => { let live = true; setData(null); setError(null); api.snapshots .get(token) .then((r) => live && setData(r)) .catch((err) => live && setError(err instanceof Error ? err.message : String(err))); return () => { live = false; }; }, [token]); const payload = data?.payload; const agentInfoById = useMemo(() => { const map = new Map(); for (const a of payload?.agents ?? []) map.set(a.id, a); return map; }, [payload]); const sessionNameById = useMemo(() => { const s = payload?.session; return new Map(s ? [[s.id, s.name?.trim() || `Session ${s.id.slice(0, 8)}`]] : []); }, [payload]); if (error) { return (

{t("snapshot.notFound")}

{error}

); } if (!payload) { return (
); } const { session, agents, events, stats, cost } = payload; const tabs: { id: Tab; label: string; Icon: typeof Bot }[] = [ { id: "overview", label: `${t("detail.agents")} (${agents.length})`, Icon: Bot }, { id: "replay", label: t("replay.tab"), Icon: Film }, { id: "timeline", label: t("snapshot.timeline", { n: events.length }), Icon: List }, ]; return (
{t("snapshot.banner", { date: formatDateTime(payload.captured_at) })}
{t("snapshot.bannerBody")}
{payload.redacted.file_paths && ( {t("snapshot.redacted")} )} {t("snapshot.openLive")}

{session.name || `${t("defaultName")}${session.id.slice(0, 8)}`}

{session.id.slice(0, 16)} {formatDateTime(session.started_at)} {cost.total_cost > 0 && ( {fmtCostFull(cost.total_cost).slice(1)} )}
{tabs.map(({ id, label, Icon }) => ( ))}
{tab === "overview" && (
{/* Frozen: its duration ends at the capture, not at "now". */}
{[...agents] .sort((a, b) => (a.started_at || "").localeCompare(b.started_at || "")) .map((a) => ( undefined} /> ))}
)} {tab === "replay" && ( )} {tab === "timeline" && (events.length === 0 ? (

{t("detail.noEvents")}

) : ( ))}
); }