'use client' import { useState, useEffect, useCallback } from 'react' import Link from 'next/link' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Badge } from '@/components/ui/badge' import { PaginationControls } from '@/components/pagination-controls' import { usePagination } from '@/hooks/use-pagination' import { AnalyzeConfirmDialog } from '@/components/analyze-confirm-dialog' import { Eye, Sparkles, Loader2 } from 'lucide-react' import type { SessionSummary } from '@/lib/parse-logs' import { formatCost, formatTokens, formatDuration, formatNumber } from '@/lib/format' export function SessionsTable({ sessions }: { sessions: SessionSummary[] }) { const [projectFilter, setProjectFilter] = useState('all') const [analyzedIds, setAnalyzedIds] = useState>(new Set()) const [analyzingIds, setAnalyzingIds] = useState>(new Set()) const [hasApiKey, setHasApiKey] = useState(false) const [bulkAnalyzing, setBulkAnalyzing] = useState(false) const [confirmOpen, setConfirmOpen] = useState(false) const [bulkEstimate, setBulkEstimate] = useState<{ sessionCount: number messageCount: number estimatedCostUSD: number } | null>(null) const [bulkSessionIds, setBulkSessionIds] = useState([]) const loadAnalysisStatus = useCallback(async () => { try { const [analyzeRes, configRes] = await Promise.all([ fetch('/api/analyze?status=true'), fetch('/api/config'), ]) const analyzeData = await analyzeRes.json() const configData = await configRes.json() if (analyzeData.analyses) { setAnalyzedIds(new Set(analyzeData.analyses.map((a: { sessionId: string }) => a.sessionId))) } setHasApiKey(configData.hasOpenAIKey || false) } catch { // ignore } }, []) useEffect(() => { loadAnalysisStatus() }, [loadAnalysisStatus]) async function handleAnalyzeOne(sessionId: string) { setAnalyzingIds((prev) => new Set(prev).add(sessionId)) try { const res = await fetch('/api/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId }), }) if (res.ok) { setAnalyzedIds((prev) => new Set(prev).add(sessionId)) } } catch { // ignore } finally { setAnalyzingIds((prev) => { const next = new Set(prev) next.delete(sessionId) return next }) } } async function handleBulkClick() { const unanalyzed = filtered .filter((s) => !analyzedIds.has(s.sessionId)) .map((s) => s.sessionId) if (unanalyzed.length === 0) return setBulkSessionIds(unanalyzed) setBulkEstimate(null) setConfirmOpen(true) try { const res = await fetch('/api/analyze/estimate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionIds: unanalyzed }), }) const data = await res.json() setBulkEstimate({ sessionCount: unanalyzed.length, messageCount: data.messageCount, estimatedCostUSD: data.estimatedCostUSD, }) } catch { setConfirmOpen(false) } } async function handleBulkConfirm() { setConfirmOpen(false) setBulkAnalyzing(true) for (const sessionId of bulkSessionIds) { setAnalyzingIds((prev) => new Set(prev).add(sessionId)) try { const res = await fetch('/api/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId }), }) if (res.ok) { setAnalyzedIds((prev) => new Set(prev).add(sessionId)) } } catch { // continue with next } finally { setAnalyzingIds((prev) => { const next = new Set(prev) next.delete(sessionId) return next }) } } setBulkAnalyzing(false) } const projects = [...new Set(sessions.map((s) => s.project))].sort() const filtered = projectFilter === 'all' ? sessions : sessions.filter((s) => s.project === projectFilter) const pagination = usePagination(filtered, 20) return (
Sessions {filtered.length} sessions recorded
{hasApiKey && ( )}
Date Project Model Messages Tokens Cost Duration Tools AI {pagination.pageItems.map((s) => ( {s.startTime ? new Date(s.startTime).toLocaleDateString() : 'N/A'} {s.project} {s.model} {formatNumber(s.userMessages)} / {formatNumber(s.assistantMessages)} {formatTokens(s.totalTokens)} {formatCost(s.costUSD)} {formatDuration(s.durationMinutes)} {formatNumber(s.toolCallsTotal)} {analyzedIds.has(s.sessionId) ? ( Done ) : analyzingIds.has(s.sessionId) ? ( ) : hasApiKey ? ( ) : ( )} ))}
) }