"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { canvasTaskRequestSchema, type CanvasTaskRequest, } from "@/lib/video-canvas/task-contract"; import { toast } from "sonner"; export function useCanvasGroupRuns(projectId: string, refresh: () => void) { const [submitting, setSubmitting] = useState(false), active = useRef(projectId), refreshRef = useRef(refresh), sending = useRef(false); active.current = projectId; refreshRef.current = refresh; const key = `video-canvas-group-outbox:${projectId}`; const read = useCallback(() => { try { const parsed = canvasTaskRequestSchema .array() .min(1) .max(40) .safeParse(JSON.parse(localStorage.getItem(key) || "null")); return parsed.success && parsed.data.every((r) => r.projectId === projectId && r.workflow) ? parsed.data : null; } catch { return null; } }, [key, projectId]); const send = useCallback( async (requests: CanvasTaskRequest[]) => { if (sending.current) return; sending.current = true; setSubmitting(true); try { const response = await fetch("/api/canvas/group-runs", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(requests), signal: AbortSignal.timeout(20000), }); if (!response.ok) { const body = await response.json().catch(() => ({})); if ((response.status < 500 && response.status !== 429) || body.code === "CANVAS_SERVICE_UNAVAILABLE") { localStorage.removeItem(key); throw Error(body.error || "编组请求被拒绝"); } throw Error("编组提交尚未确认,已保存,将查询同一批任务"); } localStorage.removeItem(key); if (active.current === projectId) refreshRef.current(); } finally { sending.current = false; if (active.current === projectId) setSubmitting(false); } }, [key, projectId] ); const submit = useCallback( async (requests: CanvasTaskRequest[]) => { const pending = read(); if (pending && pending[0].workflow?.runId !== requests[0].workflow?.runId) throw Error("上一批编组提交仍在确认,请稍后重试"); localStorage.setItem(key, JSON.stringify(requests)); await send(requests); }, [key, read, send] ); useEffect(() => { setSubmitting(false); const retry = () => { const requests = read(); if (requests && !sending.current) void send(requests).catch((error) => { if (!read()) toast.error(error.message); }); }; retry(); const timer = setInterval(retry, 10000); window.addEventListener("online", retry); window.addEventListener("focus", retry); return () => { clearInterval(timer); window.removeEventListener("online", retry); window.removeEventListener("focus", retry); }; }, [read, send]); return { submit, submitting }; }