Initial commit: OmniAI Web Frontend
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { aiGenerationClient } from "./aiGenerationClient";
|
||||
|
||||
export interface TaskProgressEvent {
|
||||
taskId: string;
|
||||
status: string;
|
||||
progress: number;
|
||||
resultUrl?: string | null;
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
export interface WaitForTaskOptions {
|
||||
onProgress?: (event: TaskProgressEvent) => void;
|
||||
abortRef?: { current: boolean };
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
const POLL_INTERVAL = 3000;
|
||||
const DEFAULT_TIMEOUT = 30 * 60 * 1000;
|
||||
|
||||
export function waitForTask(
|
||||
taskId: string,
|
||||
options: WaitForTaskOptions = {},
|
||||
): Promise<string | null> {
|
||||
const { onProgress, abortRef, timeoutMs = DEFAULT_TIMEOUT } = options;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
let cleanup: (() => void) | null = null;
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
let sseConnected = false;
|
||||
let fallbackTimerId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const settle = (fn: () => void) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
if (fallbackTimerId) clearTimeout(fallbackTimerId);
|
||||
if (cleanup) cleanup();
|
||||
fn();
|
||||
};
|
||||
|
||||
timeoutId = setTimeout(
|
||||
() => settle(() => reject(new Error("等待任务结果超时,请稍后在任务历史中查看"))),
|
||||
timeoutMs,
|
||||
);
|
||||
|
||||
const handleUpdate = (event: TaskProgressEvent) => {
|
||||
if (settled) return;
|
||||
if (abortRef?.current) {
|
||||
settle(() => resolve(null));
|
||||
return;
|
||||
}
|
||||
onProgress?.(event);
|
||||
if (event.status === "completed") {
|
||||
settle(() => resolve(event.resultUrl || null));
|
||||
} else if (event.status === "failed" || event.status === "cancelled") {
|
||||
settle(() => reject(new Error(event.error || "任务失败")));
|
||||
}
|
||||
};
|
||||
|
||||
// Try SSE first
|
||||
cleanup = aiGenerationClient.subscribeTaskStatus(taskId, handleUpdate);
|
||||
sseConnected = true;
|
||||
|
||||
// Fallback: if SSE doesn't deliver any event within 5s, switch to polling
|
||||
fallbackTimerId = setTimeout(() => {
|
||||
if (settled || !sseConnected) return;
|
||||
if (cleanup) cleanup();
|
||||
startPolling();
|
||||
}, 5000);
|
||||
|
||||
function startPolling() {
|
||||
const poll = async () => {
|
||||
while (!settled) {
|
||||
if (abortRef?.current) { settle(() => resolve(null)); return; }
|
||||
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
||||
if (settled || abortRef?.current) return;
|
||||
try {
|
||||
const task = await aiGenerationClient.getTaskStatus(taskId);
|
||||
handleUpdate({
|
||||
taskId,
|
||||
status: task.status,
|
||||
progress: task.progress || 0,
|
||||
resultUrl: task.resultUrl,
|
||||
error: task.error,
|
||||
});
|
||||
} catch (e) {
|
||||
if (!settled) settle(() => reject(e));
|
||||
}
|
||||
}
|
||||
};
|
||||
poll();
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user