import { TaskStatus, type TaskPhase, type TaskResult } from '@/types'; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter, } from './ui/sheet'; import { Button } from './ui/button'; import { CheckCircle2, Circle, Loader2, XCircle } from 'lucide-react'; import { useEffect, useRef } from 'react'; interface TaskProgressDrawerProps { open: boolean; onOpenChange: (open: boolean) => void; taskResult: TaskResult | null; taskStatus: TaskStatus | null; taskID: string | null; onCancel: () => void; isCancelling: boolean; } const PHASES: { key: TaskPhase; label: string }[] = [ { key: 'splitting', label: 'Splitting queries' }, { key: 'fetching', label: 'Fetching & processing' }, { key: 'processing', label: 'Processing remaining' }, ]; function getPhaseIndex(phase: TaskPhase | undefined): number { if (!phase) return -1; if (phase === 'splitting_complete') return 1; // splitting done, fetching is next if (phase === 'completed') return PHASES.length; return PHASES.findIndex((p) => p.key === phase); } function formatEta(seconds: number | undefined): string { if (seconds === undefined || seconds <= 0) return ''; const mins = Math.floor(seconds / 60); const secs = Math.round(seconds % 60); if (mins > 0) { return `~${mins}m ${secs}s remaining`; } return `~${secs}s remaining`; } function StatusBadge({ status }: { status: TaskStatus | null }) { if (!status) return null; const isInProgress = status !== TaskStatus.SUCCESS && status !== TaskStatus.FAILURE && status !== TaskStatus.REVOKED; if (isInProgress) { return ( Running ); } if (status === TaskStatus.SUCCESS) { return ( Complete ); } if (status === TaskStatus.REVOKED) { return ( Cancelled ); } return ( Failed ); } function PhaseTimeline({ currentPhase, taskStatus, }: { currentPhase: TaskPhase | undefined; taskStatus: TaskStatus | null; }) { const isTerminal = taskStatus === TaskStatus.SUCCESS || taskStatus === TaskStatus.FAILURE || taskStatus === TaskStatus.REVOKED; const activeIdx = isTerminal ? PHASES.length : getPhaseIndex(currentPhase); return (
{PHASES.map((phase, idx) => { const isCompleted = idx < activeIdx; const isActive = idx === activeIdx && !isTerminal; const isFuture = idx > activeIdx; return (
{isCompleted && ( )} {isActive && ( )} {isFuture && ( )} {phase.label}
); })}
); } function CounterRow({ label, value, total }: { label: string; value?: number; total?: number }) { if (value === undefined) return null; return (
{label} {value} {total !== undefined && ` / ${total}`}
); } function PhaseDetails({ result }: { result: TaskResult }) { const phase = result.phase; if (phase === 'splitting' || phase === 'splitting_complete') { return (

Splitting

{result.subqueries_total !== undefined && ( )} {result.estimated_results !== undefined && ( )}
); } if (phase === 'fetching') { return (

{result.fetching_done ? 'Fetching complete' : 'Fetching & processing'}

{(result.details_fetched !== undefined && result.details_fetched > 0) && ( <>
{(result.failed ?? 0) > 0 && (
Failed {result.failed}
)} )}
); } if (phase === 'processing') { return (

Processing

{(result.failed ?? 0) > 0 && (
Failed {result.failed}
)}
); } return null; } function LogViewer({ logs }: { logs: string[] }) { const scrollRef = useRef(null); const isAutoScrolling = useRef(true); const handleScroll = () => { const el = scrollRef.current; if (!el) return; const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 30; isAutoScrolling.current = atBottom; }; useEffect(() => { if (isAutoScrolling.current && scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [logs]); return (
{logs.length === 0 ? ( Waiting for logs... ) : ( logs.map((line, i) => (
{line}
)) )}
); } export function TaskProgressDrawer({ open, onOpenChange, taskResult, taskStatus, taskID, onCancel, isCancelling, }: TaskProgressDrawerProps) { const isInProgress = taskStatus !== null && taskStatus !== TaskStatus.SUCCESS && taskStatus !== TaskStatus.FAILURE && taskStatus !== TaskStatus.REVOKED; const progressPercent = taskResult ? Math.min((taskResult.progress ?? 0) * 100, 100) : 0; return (
Crawl Job Progress
{taskID && ( Task ID: {taskID.slice(0, 8)}... )}
{/* Fixed top section: timeline + counters + progress */}
{taskResult && } {taskResult && (taskResult.phase === 'processing' || taskResult.phase === 'fetching') && (taskResult.total ?? 0) > 0 && (
{taskResult.processed ?? 0} / {taskResult.total ?? '?'} {formatEta(taskResult.eta_seconds)}
)} {taskResult?.message && (

{taskResult.message}

)}
{/* Log viewer fills remaining space */}

Worker Logs

{isInProgress && ( )} ); }