import { generationErrorMessage } from "@/lib/generation-error"; import { apiClient, request, type BackendEnvelope } from "@/services/api/request"; export type { BackendEnvelope } from "@/services/api/request"; export type TaskStatus = "queued" | "running" | "succeeded" | "failed" | "cancelled"; export type TaskBillingStatus = "reserved" | "running" | "settled" | "refunded" | "uncertain"; export type ProviderCancelStatus = "requested" | "confirmed" | "uncertain"; export type AgentSessionStatus = "active" | "completed" | "failed"; export type GenerationTask = { id: string; sessionId?: string; projectId?: string; type: string; status: TaskStatus; progress?: number; stage?: string; prompt: string; operation?: string; provider?: string; model?: string; providerRequestId?: string; providerCancelStatus?: ProviderCancelStatus; providerCancelError?: string; providerCancelAttempts?: number; providerCancelRequestedAt?: string; providerCancelledAt?: string; errorCode?: string; previewUrl?: string; previewKind?: "image" | "video"; inputJson?: string; resultJson?: string; textDraft?: string; error?: string; attempts: number; startedAt?: string; completedAt?: string; createdAt: string; updatedAt: string; billing?: { amountMicrocredits: number; status: TaskBillingStatus; }; clientContext?: { conversationId: string; messageId: string; batchIndex?: number; batchCount?: number; }; created_at?: string; updated_at?: string; }; export type ProviderTaskQueryResult = { task: GenerationTask; providerStatus: string; recovered: boolean; billingSettled: boolean; }; export type TaskTextDelta = { id: string; taskId: string; sequence: number; content: string; byteCount: number; createdAt: string; expiresAt: string; }; export type TaskTextReplay = { deltas: TaskTextDelta[]; textDraft?: string; finalText?: string; complete: boolean; }; export type AgentSession = { id: string; projectId?: string; status: AgentSessionStatus; prompt: string; canvasSnapshotJson?: string; canvasOpsJson?: string; createdAt: string; updatedAt: string; }; export type AgentMessage = { id: string; sessionId: string; role: "user" | "assistant" | "system" | "tool" | string; content: string; payload?: string; createdAt: string; }; export type TaskResult = { id: string; taskId: string; sessionId?: string; kind: string; url?: string; payload?: string; createdAt: string; }; export type SessionFile = { id: string; sessionId: string; fileName: string; mimeType: string; size: number; createdAt: string; }; export type TaskLog = { id: string; taskId: string; level: "info" | "warn" | "error" | string; message: string; payload?: string; createdAt: string; }; export type AgentSessionDetail = { session: AgentSession; messages: AgentMessage[]; tasks: GenerationTask[]; results: TaskResult[]; }; export type CreateSessionInput = { projectId?: string; prompt: string; canvasSnapshot?: Record; references?: string[]; projectStyle?: { presetId: string; title: string; prompt: string }; characters?: Array<{ assetId: string; versionId: string; name: string; definition: Record }>; config?: Record; }; export type CreateTaskInput = { sessionId?: string; projectId?: string; type?: string; operation?: string; prompt: string; provider?: string; model?: string; input?: Record; }; const api = apiClient; export function createAgentSession(input: CreateSessionInput) { return request(api.post("/sessions", input)).then((detail) => { detail.tasks.forEach((task) => notifyCanvasTaskCreated(task)); return detail; }); } export function queryAgentSession(id: string) { return request(api.get(`/sessions/${encodeURIComponent(id)}`)); } export function agentSessionFailureMessage(detail: AgentSessionDetail, fallback = "后端影视 Agent 会话失败") { for (let index = detail.tasks.length - 1; index >= 0; index -= 1) { const task = detail.tasks[index]; if ((task.status === "failed" || task.status === "cancelled") && task.error?.trim()) return generationErrorMessage(task.error.trim()); } for (let index = detail.messages.length - 1; index >= 0; index -= 1) { const message = detail.messages[index]; if (message.role === "assistant" && message.content.trim()) return generationErrorMessage(message.content.trim()); } return fallback; } export function downloadSessionResults(id: string) { return request(api.get(`/sessions/${encodeURIComponent(id)}/results`)); } export function uploadAgentFile(sessionId: string, file: File) { const formData = new FormData(); formData.append("sessionId", sessionId); formData.append("file", file); return request(api.post("/files", formData)); } export function createGenerationTask(input: CreateTaskInput) { return request(api.post("/tasks", input)).then((task) => { notifyCanvasTaskCreated(task); // 创建任务时积分已被预占,不能等任务结束后才刷新可用余额。 window.dispatchEvent(new CustomEvent("wallet:updated")); return task; }); } export function listGenerationTasks(limit = 30, options?: { projectId?: string; activeOnly?: boolean }) { return request(api.get("/tasks", { params: { limit, projectId: options?.projectId, activeOnly: options?.activeOnly || undefined } })); } export function queryGenerationTask(id: string, options?: { signal?: AbortSignal }) { return request(api.get(`/tasks/${encodeURIComponent(id)}`, { signal: options?.signal })); } export function appendTaskTextDelta(id: string, content: string) { return request(api.post(`/tasks/${encodeURIComponent(id)}/text-deltas`, { content })); } export function queryTaskTextReplay(id: string, after = 0) { return request(api.get(`/tasks/${encodeURIComponent(id)}/text-deltas`, { params: { after } })); } export function retryGenerationTask(id: string) { return request(api.post(`/tasks/${encodeURIComponent(id)}/retry`)); } export function queryFailedVideoProviderTask(id: string) { return request(api.post(`/tasks/${encodeURIComponent(id)}/query-provider`)); } export function cancelGenerationTask(id: string) { return request(api.post(`/tasks/${encodeURIComponent(id)}/cancel`)); } export function listTaskLogs(id: string) { return request(api.get(`/tasks/${encodeURIComponent(id)}/logs`)); } export async function waitForGenerationTask(id: string, options?: { signal?: AbortSignal; intervalMs?: number; timeoutMs?: number; initialTask?: GenerationTask; onTaskUpdate?: (task: GenerationTask) => void }) { const startedAt = Date.now(); const intervalMs = options?.intervalMs || 2000; let lastTask = options?.initialTask; let lastQueryError: unknown; try { while (Date.now() - startedAt < (options?.timeoutMs || taskWaitTimeoutMs(lastTask))) { if (options?.signal?.aborted) throw new DOMException("Aborted", "AbortError"); let task: GenerationTask; try { task = await queryGenerationTask(id, { signal: options?.signal }); lastTask = task; lastQueryError = undefined; options?.onTaskUpdate?.(task); } catch (error) { lastQueryError = error; await delay(intervalMs, options?.signal); continue; } if (task.status === "succeeded") { window.dispatchEvent(new CustomEvent("wallet:updated")); return task; } if (task.status === "failed" || task.status === "cancelled") { window.dispatchEvent(new CustomEvent("wallet:updated")); throw new Error(task.error ? generationErrorMessage(task.error) : `任务${task.status === "cancelled" ? "已取消" : "失败"}`); } await delay(intervalMs, options?.signal); } } catch (error) { if (options?.signal?.aborted) { await cancelGenerationTask(id).catch(() => undefined); window.dispatchEvent(new CustomEvent("wallet:updated")); throw new DOMException("Aborted", "AbortError"); } throw error; } throw new Error(lastQueryError instanceof Error ? `任务状态同步失败:${lastQueryError.message}` : "任务执行超时,请稍后重试"); } function taskWaitTimeoutMs(task?: GenerationTask) { const type = task?.type || ""; if (type.includes("storyboard")) return 13 * 60 * 1000; if (type.includes("video")) return 32 * 60 * 1000; if (type.includes("image")) return 10 * 60 * 1000; if (type.includes("text") || type.includes("audio")) return 12 * 60 * 1000; return 10 * 60 * 1000; } function delay(ms: number, signal?: AbortSignal) { return new Promise((resolve, reject) => { const timer = window.setTimeout(resolve, ms); signal?.addEventListener( "abort", () => { window.clearTimeout(timer); reject(new DOMException("Aborted", "AbortError")); }, { once: true }, ); }); } function notifyCanvasTaskCreated(task: GenerationTask) { if (typeof window === "undefined" || !task.projectId) return; window.dispatchEvent(new CustomEvent("canvas:task-created", { detail: { task } })); }