import { type MouseEvent, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import type { SessionListItem } from "./protocol"; import { Route } from "./protocol"; import { fetchJson } from "./api"; import { classNames, relativeTime } from "./store"; interface Props { sessions: SessionListItem[]; onChanged: () => void; pushToast: (text: string) => void; } export default function SessionsView({ sessions, onChanged, pushToast, }: Props) { const navigate = useNavigate(); const open = useCallback( (id: string): void => { navigate(`/s/${id}`); }, [navigate], ); const stop = async (e: MouseEvent, s: SessionListItem): Promise => { e.preventDefault(); e.stopPropagation(); try { await fetchJson(Route.SessionContainer(s.id), { method: "DELETE" }); pushToast(`stopped ${s.name ?? s.id}`); onChanged(); } catch (err) { pushToast( `stop failed: ${err instanceof Error ? err.message : String(err)}`, ); } }; // Only active (online) pi sessions are shown; offline transcripts stay // reachable by direct URL and are dropped from the default list. const sorted = [...sessions] .filter((s) => s.online) .sort( (a, b) => (b.lastEventAt ?? b.startedAt) - (a.lastEventAt ?? a.startedAt), ); return (

Active sessions

{sorted.length === 0 && (

No active sessions. Spawn one from the sidebar.

)} {sorted.map((s) => (
open(s.id)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") open(s.id); }} > {s.name ?? s.repo ?? s.cwd} {s.repo !== null && {s.repo}} {s.model} {relativeTime(s.lastEventAt)} {s.agent && agent} {s.agent && ( )}
))}
); }