From e7145199cd7fafcf745460fc037d474aa1c6ab01 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Sun, 19 Apr 2026 20:00:04 +1200 Subject: [PATCH] feat: add client-side polling to nanoBanana, audio, and 3D executors All executors that call /api/generate now check for the polling flag and enter the pollGenerateTask loop for long-running Kie tasks. Co-Authored-By: Claude Opus 4.6 --- src/store/execution/generate3dExecutor.ts | 24 ++++++++++++++++++- src/store/execution/generateAudioExecutor.ts | 25 +++++++++++++++++++- src/store/execution/nanoBananaExecutor.ts | 24 ++++++++++++++++++- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/store/execution/generate3dExecutor.ts b/src/store/execution/generate3dExecutor.ts index a6ad2658..fe4630c4 100644 --- a/src/store/execution/generate3dExecutor.ts +++ b/src/store/execution/generate3dExecutor.ts @@ -7,6 +7,7 @@ import type { Generate3DNodeData } from "@/types"; import { buildGenerateHeaders } from "@/store/utils/buildApiHeaders"; +import { pollGenerateTask } from "./pollTaskCompletion"; import type { NodeExecutionContext } from "./types"; export interface Generate3DOptions { @@ -106,7 +107,28 @@ export async function executeGenerate3D( throw new Error(errorMessage); } - const result = await response.json(); + let result = await response.json(); + + // Handle polling response (long-running Kie tasks) + if (result.polling) { + result = await pollGenerateTask({ + taskId: result.taskId, + provider: result.pollProvider, + modelId: result.pollModelId, + modelName: result.pollModelName, + mediaType: result.pollMediaType, + headers, + signal, + }); + + if (!result.success) { + updateNodeData(node.id, { + status: "error", + error: result.error || "3D generation failed", + }); + throw new Error(result.error || "3D generation failed"); + } + } if (result.success && result.model3dUrl) { updateNodeData(node.id, { diff --git a/src/store/execution/generateAudioExecutor.ts b/src/store/execution/generateAudioExecutor.ts index ee3d6351..5b9b454c 100644 --- a/src/store/execution/generateAudioExecutor.ts +++ b/src/store/execution/generateAudioExecutor.ts @@ -7,6 +7,7 @@ import type { GenerateAudioNodeData } from "@/types"; import { buildGenerateHeaders } from "@/store/utils/buildApiHeaders"; +import { pollGenerateTask } from "./pollTaskCompletion"; import type { NodeExecutionContext } from "./types"; export interface GenerateAudioOptions { @@ -118,7 +119,29 @@ export async function executeGenerateAudio( throw new Error(errorMessage); } - const result = await response.json(); + let result = await response.json(); + + // Handle polling response (long-running Kie tasks) + if (result.polling) { + result = await pollGenerateTask({ + taskId: result.taskId, + provider: result.pollProvider, + modelId: result.pollModelId, + modelName: result.pollModelName, + mediaType: result.pollMediaType, + headers, + signal, + }); + + if (!result.success) { + updateNodeData(node.id, { + status: "error", + error: result.error || "Audio generation failed", + }); + errorHandled = true; + throw new Error(result.error || "Audio generation failed"); + } + } // Handle audio response (audio or audioUrl field) const audioData = result.audio || result.audioUrl; diff --git a/src/store/execution/nanoBananaExecutor.ts b/src/store/execution/nanoBananaExecutor.ts index bd142993..aad6ac76 100644 --- a/src/store/execution/nanoBananaExecutor.ts +++ b/src/store/execution/nanoBananaExecutor.ts @@ -10,6 +10,7 @@ import type { } from "@/types"; import { calculateGenerationCost } from "@/utils/costCalculator"; import { buildGenerateHeaders } from "@/store/utils/buildApiHeaders"; +import { pollGenerateTask } from "./pollTaskCompletion"; import type { NodeExecutionContext } from "./types"; export interface NanoBananaOptions { @@ -141,7 +142,28 @@ export async function executeNanoBanana( throw new Error(errorMessage); } - const result = await response.json(); + let result = await response.json(); + + // Handle polling response (long-running Kie tasks) + if (result.polling) { + result = await pollGenerateTask({ + taskId: result.taskId, + provider: result.pollProvider, + modelId: result.pollModelId, + modelName: result.pollModelName, + mediaType: result.pollMediaType, + headers, + signal, + }); + + if (!result.success) { + updateNodeData(node.id, { + status: "error", + error: result.error || "Generation failed", + }); + throw new Error(result.error || "Generation failed"); + } + } if (result.success && result.image) { const timestamp = Date.now();