"use client"; import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { MessageSquare, Search } from "lucide-react"; import { PageHeader } from "@/components/common/page-header"; import { EmptyState } from "@/components/common/empty-state"; import { DeleteAllButton } from "@/components/common/delete-all-button"; import { LearningsBadge } from "@/components/common/learnings-badge"; import { HostBadge } from "@/components/common/host-badge"; import { Input } from "@/components/ui/input"; import { dayBucket, formatRelative, truncateId } from "@/lib/format"; import type { SessionSummary } from "@/lib/types"; export default function SessionsPage() { const router = useRouter(); const [sessions, setSessions] = useState(null); const [filter, setFilter] = useState(""); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; fetch("/api/sessions", { cache: "no-store" }) .then((r) => r.json()) .then((data) => { if (!cancelled) setSessions(data.sessions ?? []); }) .catch((e) => { if (!cancelled) setError(e instanceof Error ? e.message : String(e)); }); return () => { cancelled = true; }; }, []); const filtered = useMemo(() => { const q = filter.trim().toLowerCase(); if (!q) return sessions ?? []; return (sessions ?? []).filter((s) => { const hay = `${s.session_id} ${s.preview ?? ""}`.toLowerCase(); return hay.includes(q); }); }, [sessions, filter]); const grouped = useMemo(() => { const buckets = new Map(); for (const s of filtered) { const key = dayBucket(s.last_activity); const arr = buckets.get(key); if (arr) arr.push(s); else buckets.set(key, [s]); } return Array.from(buckets.entries()); }, [filtered]); return (
setFilter(e.target.value)} placeholder="Search sessions…" className="h-9 w-64 pl-8 text-xs bg-background/80" />
0 ? ` (${sessions.length})` : ""}`} confirmMessage={`Delete ALL ${sessions?.length ?? 0} local session buffers? This cannot be undone.`} disabled={!sessions || sessions.length === 0} onConfirm={async () => { const res = await fetch("/api/sessions", { method: "DELETE" }); if (!res.ok) throw new Error(`delete failed: ${res.status}`); setSessions([]); }} />
} />
{error && (
{error}
)} {sessions === null && !error ? (
Loading…
) : sessions && sessions.length > 0 && filtered.length === 0 ? ( ) : filtered.length === 0 ? ( ) : (
{grouped.map(([label, items]) => (

{label}

· {items.length}
{items.map((s) => (
router.push( `/sessions/${encodeURIComponent(s.session_id)}`, ) } onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); router.push( `/sessions/${encodeURIComponent(s.session_id)}`, ); } }} className="flex cursor-pointer flex-col gap-2 px-4 py-3 transition-colors hover:bg-accent/45 focus:bg-accent/45 focus:outline-none sm:flex-row sm:items-center" >

{s.preview ?? ( (no user turns yet) )}

{truncateId(s.session_id, 10, 6)} · {s.turn_count} turn{s.turn_count === 1 ? "" : "s"} {s.published_up_to > 0 && s.published_up_to < s.turn_count && ( <> · {s.published_up_to} published )}
{formatRelative(s.last_activity)}
))}
))}
)}
); }