import { useEffect, useState } from "react"; import { useLoaderData } from "react-router"; import Navbar from "../../components/Navbar"; export const meta = () => [{ title: "Job Detail - AITrader" }]; export async function loader({ params, request }: { params: { jobId: string }; request: Request }) { const jobId = params.jobId; if (!jobId) return Response.json({ error: "jobId required" }, { status: 400 }); const reqUrl = new URL(request.url); const host = request.headers.get("host") || reqUrl.host; const protocol = reqUrl.protocol; const baseUrl = `${protocol}//${host}`; try { const res = await fetch(`${baseUrl}/api/jobs/${jobId}`); const body = res.ok ? await res.json() : null; return Response.json(body); } catch (err) { console.error("/jobs loader error:", err); return Response.json({ error: "internal" }, { status: 500 }); } } export default function JobDetail() { const initial = useLoaderData() as any; const [job, setJob] = useState(initial); useEffect(() => { let cancelled = false; const poll = async () => { try { const id = job?.id || initial?.id; if (!id) return; const res = await fetch(`/api/jobs/${id}`); if (!res.ok) return; const j = await res.json(); if (cancelled) return; setJob(j); if (j.state === "completed" || j.state === "failed") return; } catch (e) { // ignore } setTimeout(poll, 3000); }; poll(); return () => { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return (

Job {job?.id}

State: {job?.state}
{job?.failedReason && (
Failed: {job.failedReason}
)}
{job?.state === 'waiting' || job?.state === 'queued' ? ( ) : null} Open API
Data:
{JSON.stringify(job?.data || job?.returnValue || job, null, 2)}
); }