114 lines
3 KiB
TypeScript
114 lines
3 KiB
TypeScript
import { useState, useEffect, useRef, useCallback } from "react";
|
|
import { submitContentJob } from "../api/contentJobs";
|
|
|
|
type JobStatus = "idle" | "queued" | "running" | "done" | "failed";
|
|
|
|
interface ContentJobState {
|
|
jobId: string | null;
|
|
status: JobStatus;
|
|
progress: number;
|
|
resultAssetId: string | null;
|
|
errorMessage: string | null;
|
|
}
|
|
|
|
const INITIAL_STATE: ContentJobState = {
|
|
jobId: null,
|
|
status: "idle",
|
|
progress: 0,
|
|
resultAssetId: null,
|
|
errorMessage: null,
|
|
};
|
|
|
|
function statusToProgress(status: string): number {
|
|
switch (status) {
|
|
case "queued":
|
|
return 5;
|
|
case "running":
|
|
return 50;
|
|
case "done":
|
|
return 100;
|
|
case "failed":
|
|
return 0;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
export function useContentJob(companyId: string | null) {
|
|
const [state, setState] = useState<ContentJobState>(INITIAL_STATE);
|
|
const eventSourceRef = useRef<EventSource | null>(null);
|
|
|
|
const closeEventSource = useCallback(() => {
|
|
if (eventSourceRef.current) {
|
|
eventSourceRef.current.close();
|
|
eventSourceRef.current = null;
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
closeEventSource();
|
|
};
|
|
}, [closeEventSource]);
|
|
|
|
const submit = useCallback(
|
|
async (jobType: string, input: Record<string, unknown>, sourceTaskId?: string | null) => {
|
|
if (!companyId) return;
|
|
|
|
closeEventSource();
|
|
setState({ ...INITIAL_STATE, status: "queued", progress: 5 });
|
|
|
|
const result = await submitContentJob(companyId, jobType, input, sourceTaskId);
|
|
const jobId = result.jobId;
|
|
|
|
setState((prev) => ({ ...prev, jobId, status: "queued", progress: 5 }));
|
|
|
|
const url = `/api/companies/${companyId}/content-jobs/${jobId}/events`;
|
|
const es = new EventSource(url, { withCredentials: true });
|
|
eventSourceRef.current = es;
|
|
|
|
es.addEventListener("status", (e: MessageEvent) => {
|
|
const data = JSON.parse(e.data as string) as {
|
|
status?: string;
|
|
progress?: number;
|
|
resultAssetId?: string | null;
|
|
errorMessage?: string | null;
|
|
};
|
|
const status = (data.status ?? "queued") as JobStatus;
|
|
const progress =
|
|
typeof data.progress === "number"
|
|
? data.progress
|
|
: statusToProgress(status);
|
|
|
|
setState((prev) => ({
|
|
...prev,
|
|
status,
|
|
progress,
|
|
resultAssetId: data.resultAssetId ?? prev.resultAssetId,
|
|
errorMessage: data.errorMessage ?? prev.errorMessage,
|
|
}));
|
|
|
|
if (status === "done" || status === "failed") {
|
|
closeEventSource();
|
|
}
|
|
});
|
|
|
|
es.addEventListener("error", () => {
|
|
closeEventSource();
|
|
setState((prev) => ({
|
|
...prev,
|
|
status: "failed",
|
|
errorMessage: prev.errorMessage ?? "Connection error",
|
|
}));
|
|
});
|
|
},
|
|
[companyId, closeEventSource],
|
|
);
|
|
|
|
const reset = useCallback(() => {
|
|
closeEventSource();
|
|
setState(INITIAL_STATE);
|
|
}, [closeEventSource]);
|
|
|
|
return { ...state, submit, reset };
|
|
}
|