import { useCallback, useEffect, useRef, useState } from "react"; import { Button } from "~/shared/components/ui/button"; import { api } from "~/shared/lib/api"; import { DoneColumn } from "./DoneColumn"; import { ProcessingColumn } from "./ProcessingColumn"; import { QueueColumn } from "./QueueColumn"; import { ReviewColumn } from "./ReviewColumn"; interface PipelineData { review: any[]; reviewTotal: number; queued: any[]; processing: any[]; done: any[]; doneCount: number; jellyfinUrl: string; } interface Progress { id: number; seconds: number; total: number; } interface QueueStatus { status: string; until?: string; seconds?: number; } export function PipelinePage() { const [data, setData] = useState(null); const [progress, setProgress] = useState(null); const [queueStatus, setQueueStatus] = useState(null); const [loading, setLoading] = useState(true); const load = useCallback(async () => { const pipelineRes = await api.get("/api/review/pipeline"); setData(pipelineRes); setLoading(false); }, []); const startQueue = useCallback(async () => { await api.post("/api/execute/start"); load(); }, [load]); useEffect(() => { load(); }, [load]); // SSE for live updates. job_update fires on every status change and per-line // stdout flush of the running job — without coalescing, the pipeline endpoint // (a 500-row review query + counts) would re-run several times per second. const reloadTimer = useRef | null>(null); useEffect(() => { const scheduleReload = () => { if (reloadTimer.current) return; reloadTimer.current = setTimeout(() => { reloadTimer.current = null; load(); }, 1000); }; const es = new EventSource("/api/execute/events"); es.addEventListener("job_update", (e) => { // When a job leaves 'running' (done / error / cancelled), drop any // stale progress so the bar doesn't linger on the next job's card. try { const upd = JSON.parse((e as MessageEvent).data) as { id: number; status: string }; if (upd.status !== "running") setProgress(null); } catch { /* ignore malformed events */ } scheduleReload(); }); es.addEventListener("job_progress", (e) => { setProgress(JSON.parse((e as MessageEvent).data)); }); es.addEventListener("queue_status", (e) => { setQueueStatus(JSON.parse((e as MessageEvent).data)); }); return () => { es.close(); if (reloadTimer.current) clearTimeout(reloadTimer.current); }; }, [load]); if (loading || !data) return
Loading pipeline...
; return (

Pipeline

{data.doneCount} files in desired state
); }