"use client"; import { useCallback, useEffect, useLayoutEffect, useRef, useState, } from "react"; import { cancelJobAction } from "../../actions"; type Props = { jobId: string; initiallyRunning: boolean; }; type LogResponse = { content: string; nextOffset: number; status: string; queueKey?: string; queuePosition?: number; }; export function JobLogTail({ jobId, initiallyRunning }: Props) { const [log, setLog] = useState(""); const [status, setStatus] = useState( initiallyRunning ? "running" : "loaded", ); const [queueKey, setQueueKey] = useState(); const [queuePosition, setQueuePosition] = useState(-1); const [cancelling, setCancelling] = useState(false); const offsetRef = useRef(0); const preRef = useRef(null); const stickToBottomRef = useRef(true); useLayoutEffect(() => { const el = preRef.current; if (!el || !stickToBottomRef.current) return; el.scrollTop = el.scrollHeight; }, [log]); const handleScroll = useCallback(() => { const el = preRef.current; if (!el) return; const dist = el.scrollHeight - el.scrollTop - el.clientHeight; stickToBottomRef.current = dist < 24; }, []); useEffect(() => { let cancelled = false; let timer: ReturnType | null = null; async function tick() { try { const res = await fetch( `/api/jobs/${encodeURIComponent(jobId)}/log?from=${offsetRef.current}`, { cache: "no-store" }, ); if (!res.ok) return; const data = (await res.json()) as LogResponse; if (cancelled) return; if (data.content) setLog((prev) => prev + data.content); offsetRef.current = data.nextOffset; setStatus(data.status); setQueueKey(data.queueKey); setQueuePosition( typeof data.queuePosition === "number" ? data.queuePosition : -1, ); if (data.status === "running" || data.status === "queued") { timer = setTimeout(tick, 600); } } catch { if (!cancelled) timer = setTimeout(tick, 1500); } } tick(); return () => { cancelled = true; if (timer) clearTimeout(timer); }; }, [jobId]); async function handleCancel() { setCancelling(true); try { await cancelJobAction(jobId); } finally { setCancelling(false); } } const isRunning = status === "running"; const isQueued = status === "queued"; return (
Status: {status}
{isQueued && queueKey && queuePosition > 0 && (
Queued in {queueKey}, position{" "} {queuePosition}
)} {(isRunning || isQueued) && ( )}
        {log || "(empty log)"}
      
); }