Browse Source

feat: return polling response for Kie tasks instead of blocking

The /api/generate route now submits Kie tasks and returns immediately
with polling metadata (taskId, pollProvider, etc.) instead of holding
the HTTP connection open for 2-10 minutes. Export buildMediaResponse
for reuse by the poll endpoint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 3 months ago
parent
commit
011381a801
  1. 33
      src/app/api/generate/route.ts

33
src/app/api/generate/route.ts

@ -16,7 +16,7 @@ import { GenerationInput, ModelCapability } from "@/lib/providers/types";
import { generateWithGemini, generateWithGeminiVideo } from "./providers/gemini"; import { generateWithGemini, generateWithGeminiVideo } from "./providers/gemini";
import { generateWithReplicate } from "./providers/replicate"; import { generateWithReplicate } from "./providers/replicate";
import { clearFalInputMappingCache as _clearFalInputMappingCache, generateWithFalQueue } from "./providers/fal"; import { clearFalInputMappingCache as _clearFalInputMappingCache, generateWithFalQueue } from "./providers/fal";
import { generateWithKie } from "./providers/kie"; import { submitKieTask } from "./providers/kie";
import { generateWithWaveSpeed } from "./providers/wavespeed"; import { generateWithWaveSpeed } from "./providers/wavespeed";
// Re-export for backward compatibility (test file imports from route) // Re-export for backward compatibility (test file imports from route)
@ -37,7 +37,7 @@ interface MultiProviderGenerateRequest extends GenerateRequest {
} }
function buildMediaResponse(output: { type: string; data: string; url?: string }): NextResponse { export function buildMediaResponse(output: { type: string; data: string; url?: string }): NextResponse {
if (output.type === "3d") { if (output.type === "3d") {
return NextResponse.json<GenerateResponse>({ return NextResponse.json<GenerateResponse>({
success: true, success: true,
@ -341,28 +341,27 @@ export async function POST(request: NextRequest) {
dynamicInputs: processedDynamicInputs, dynamicInputs: processedDynamicInputs,
}; };
const result = await generateWithKie(requestId, kieApiKey, genInput); // Submit task and return immediately — client polls for completion
try {
if (!result.success) { const { taskId, isVeo } = await submitKieTask(requestId, kieApiKey, genInput);
return NextResponse.json<GenerateResponse>({
success: true,
polling: true,
taskId,
pollProvider: 'kie',
pollModelId: selectedModel.modelId,
pollModelName: selectedModel.displayName,
pollMediaType: mediaType || 'image',
});
} catch (error) {
return NextResponse.json<GenerateResponse>( return NextResponse.json<GenerateResponse>(
{ {
success: false, success: false,
error: result.error || "Generation failed", error: error instanceof Error ? error.message : "Task submission failed",
}, },
{ status: 500 } { status: 500 }
); );
} }
// Return first output
const output = result.outputs?.[0];
if (!output?.data && !output?.url) {
return NextResponse.json<GenerateResponse>(
{ success: false, error: "No output in generation result" },
{ status: 500 }
);
}
return buildMediaResponse(output);
} }
if (provider === "wavespeed") { if (provider === "wavespeed") {

Loading…
Cancel
Save