ai-chatbot/lib/client/long-task.ts
Your Name 5e155ecf3c
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
fix: reduce 504 via async polling and chat keepalive
2026-03-04 20:00:37 +08:00

92 lines
2.7 KiB
TypeScript

type LongTaskResponse = {
success?: boolean;
processing?: boolean;
status?: string;
taskId?: string;
pollAfterMs?: number;
error?: string;
detail?: string;
[key: string]: any;
};
function isProcessingPayload(payload: LongTaskResponse | null | undefined) {
if (!payload) return false;
if (payload.processing === true) return true;
const status = String(payload.status || "").toLowerCase();
return status === "queued" || status === "running" || status === "pending";
}
function buildTaskError(payload: LongTaskResponse | null | undefined, fallback: string) {
const message = payload?.error || payload?.detail || fallback;
return new Error(String(message || fallback));
}
async function safeParseJson(response: Response) {
try {
return (await response.json()) as LongTaskResponse;
} catch {
return null;
}
}
export async function requestLongTaskResult(params: {
endpoint: string;
payload: Record<string, any>;
pollingParams?: Record<string, string | number | null | undefined>;
timeoutMs?: number;
}) {
const response = await fetch(params.endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(params.payload),
});
const result = await safeParseJson(response);
if (!response.ok) {
throw buildTaskError(result, "请求失败");
}
if (!isProcessingPayload(result)) {
return result as LongTaskResponse;
}
const taskId = String(result?.taskId || "").trim();
if (!taskId) {
throw buildTaskError(result, "任务正在处理中,但缺少 taskId");
}
const pollingParams = new URLSearchParams();
pollingParams.set("taskId", taskId);
for (const [key, value] of Object.entries(params.pollingParams || {})) {
if (value === null || value === undefined || value === "") continue;
pollingParams.set(key, String(value));
}
const timeoutMs = Math.max(10_000, params.timeoutMs ?? 8 * 60 * 1000);
const startedAt = Date.now();
let pollAfterMs = Math.max(800, Number(result?.pollAfterMs || 1500));
while (Date.now() - startedAt < timeoutMs) {
await new Promise((resolve) => setTimeout(resolve, pollAfterMs));
const statusResponse = await fetch(`${params.endpoint}?${pollingParams.toString()}`, {
method: "GET",
cache: "no-store",
});
const statusPayload = await safeParseJson(statusResponse);
if (!statusResponse.ok) {
throw buildTaskError(statusPayload, "查询任务状态失败");
}
if (!isProcessingPayload(statusPayload)) {
return statusPayload as LongTaskResponse;
}
pollAfterMs = Math.max(800, Number(statusPayload?.pollAfterMs || pollAfterMs));
}
throw new Error("任务处理超时,请稍后再试");
}