"use client"; import { useState, useMemo } from "react"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { ResponsiveTable, ResponsiveTableCardList, ResponsiveTableCard, ResponsiveTableCardRow, } from "@/components/ui/responsive-table"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { HelpTip } from "@/components/ui/help-tip"; import { ChevronUp, ChevronDown } from "lucide-react"; import type { UsageSession } from "@/types"; const PAGE_SIZE = 10; type SortKey = keyof Pick< UsageSession, | "agent_slug" | "started_at" | "total_tokens" | "tokens_input" | "tokens_output" | "tokens_cache" | "cost" | "model" >; type SortDir = "asc" | "desc"; interface Column { key: SortKey; label: string; tip: string; } const COLUMNS: Column[] = [ { key: "agent_slug", label: "Agent", tip: "The agent slug that ran this session — click to sort" }, { key: "model", label: "Model", tip: "Claude/Grok model used for this session — click to sort" }, { key: "started_at", label: "Started", tip: "Session start time, local timezone — click to sort oldest/newest", }, { key: "total_tokens", label: "Total", tip: "Input + output + cache tokens combined — click to sort", }, { key: "tokens_input", label: "Input", tip: "Prompt tokens sent to the model — click to sort", }, { key: "tokens_output", label: "Output", tip: "Completion tokens returned by the model — click to sort", }, { key: "tokens_cache", label: "Cache", tip: "Tokens served from Anthropic's prompt cache — click to sort", }, { key: "cost", label: "Cost", tip: "Provider-priced dollar cost (local/Ollama sessions are $0) — click to sort", }, ]; function formatTime(ts: string): string { return new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", }); } function fmtK(n: number): string { if (n >= 1_000) return (n / 1_000).toFixed(1) + "k"; return String(n); } interface SessionsTableProps { data: UsageSession[] | undefined; isLoading: boolean; } export function SessionsTable({ data, isLoading }: SessionsTableProps) { const [sortKey, setSortKey] = useState("started_at"); const [sortDir, setSortDir] = useState("desc"); const [page, setPage] = useState(0); const sorted = useMemo(() => { const rows = [...(data ?? [])]; rows.sort((a, b) => { const av = a[sortKey]; const bv = b[sortKey]; const cmp = typeof av === "number" && typeof bv === "number" ? av - bv : String(av).localeCompare(String(bv)); return sortDir === "asc" ? cmp : -cmp; }); return rows; }, [data, sortKey, sortDir]); const totalPages = Math.max(1, Math.ceil(sorted.length / PAGE_SIZE)); const visible = sorted.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE); function toggleSort(key: SortKey) { if (sortKey === key) { setSortDir((d) => (d === "asc" ? "desc" : "asc")); } else { setSortKey(key); setSortDir("desc"); } setPage(0); } function SortIcon({ col }: { col: SortKey }) { if (sortKey !== col) return ; return sortDir === "asc" ? ( ) : ( ); } return ( Recent Sessions {isLoading ? (
{Array.from({ length: PAGE_SIZE }).map((_, i) => ( ))}
) : ( <> {COLUMNS.map((col) => ( toggleSort(col.key)} > {col.label} ))} {visible.length === 0 ? ( No sessions recorded yet ) : ( visible.map((s) => ( {s.agent_slug} {s.model} {formatTime(s.started_at)} {fmtK(s.total_tokens)} {fmtK(s.tokens_input)} {fmtK(s.tokens_output)} {fmtK(s.tokens_cache)} ${s.cost.toFixed(4)} )) )}
} cards={ visible.length === 0 ? (

No sessions recorded yet

) : ( {visible.map((s) => (
{s.agent_slug} {s.model}
{formatTime(s.started_at)} {fmtK(s.total_tokens)} {fmtK(s.tokens_input)} {fmtK(s.tokens_output)} {fmtK(s.tokens_cache)} ${s.cost.toFixed(4)}
))}
) } /> {/* Pagination */}
{sorted.length === 0 ? "No sessions" : `${page * PAGE_SIZE + 1}–${Math.min((page + 1) * PAGE_SIZE, sorted.length)} of ${sorted.length}`}
)}
); }