/** * Generic single-task keep-alive for tool pages. * Persists task state to localStorage so in-progress tasks survive page switches. */ const KEEPALIVE_PREFIX = "omniai:tool-task:"; interface ToolTaskKeepalive { taskId: string; resultUrl: string; resultPreview: string; status: string; progress: number; sourceName: string; sourceUrl: string; savedAt: number; } export function saveToolTaskState(key: string, state: { taskId: string; resultUrl?: string; resultPreview?: string; status?: string; progress?: number; sourceName?: string; sourceUrl?: string; }): void { if (!state.taskId) return; try { const entry: ToolTaskKeepalive = { taskId: state.taskId, resultUrl: state.resultUrl || "", resultPreview: state.resultPreview || "", status: state.status || "", progress: state.progress || 0, sourceName: state.sourceName || "", sourceUrl: state.sourceUrl || "", savedAt: Date.now(), }; window.localStorage.setItem(KEEPALIVE_PREFIX + key, JSON.stringify(entry)); } catch { /* quota */ } } export function loadToolTaskState(key: string): ToolTaskKeepalive | null { try { const raw = window.localStorage.getItem(KEEPALIVE_PREFIX + key); if (!raw) return null; const parsed = JSON.parse(raw) as ToolTaskKeepalive; if (Date.now() - (parsed.savedAt || 0) > 2 * 60 * 60 * 1000) { clearToolTaskState(key); return null; } if (!parsed.taskId) return null; return parsed; } catch { return null; } } export function clearToolTaskState(key: string): void { try { window.localStorage.removeItem(KEEPALIVE_PREFIX + key); } catch { /* ignore */ } } const TASK_POLL_INTERVAL = 3000; const TASK_POLL_TIMEOUT = 30 * 60 * 1000; export async function pollTaskUntilDone( taskId: string, onProgress?: (progress: number) => void, abortRef?: { current: boolean }, ): Promise { const startTime = Date.now(); const { aiGenerationClient } = await import("../../api/aiGenerationClient"); while (true) { if (abortRef?.current) return null; if (Date.now() - startTime > TASK_POLL_TIMEOUT) return null; try { const task = await aiGenerationClient.getTaskStatus(taskId); if (!task) return null; const progress = Math.min(99, task.progress || 0); onProgress?.(progress); if (task.status === "completed") { return task.resultUrl || null; } if (task.status === "failed" || task.status === "cancelled") { return null; } } catch { // retry on next poll } await new Promise((r) => setTimeout(r, TASK_POLL_INTERVAL)); } }