fix: use synchronous Fal.ai endpoint for FLUX img2img

The queue polling was timing out. Switch to fal.run synchronous
endpoint which waits for the result directly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2025-12-23 19:59:51 -05:00
parent 7862a6bc22
commit c6eb3e5363
1 changed files with 12 additions and 54 deletions

View File

@ -156,7 +156,7 @@ Return ONLY valid JSON (no markdown, no code blocks):
} }
} }
// FLUX img2img via Fal.ai // FLUX img2img via Fal.ai - using synchronous endpoint
async function generateWithFluxImg2Img( async function generateWithFluxImg2Img(
imageBase64: string, imageBase64: string,
prompt: string, prompt: string,
@ -167,10 +167,10 @@ async function generateWithFluxImg2Img(
throw new Error("FAL_KEY not configured"); throw new Error("FAL_KEY not configured");
} }
console.log("Calling Fal.ai FLUX img2img API..."); console.log(`Calling Fal.ai FLUX img2img API (strength: ${strength})...`);
// Submit the request // Use fal.run for synchronous execution (waits for result)
const submitResponse = await fetch("https://queue.fal.run/fal-ai/flux/dev/image-to-image", { const response = await fetch("https://fal.run/fal-ai/flux/dev/image-to-image", {
method: "POST", method: "POST",
headers: { headers: {
"Authorization": `Key ${falKey}`, "Authorization": `Key ${falKey}`,
@ -190,67 +190,25 @@ async function generateWithFluxImg2Img(
}), }),
}); });
if (!submitResponse.ok) { if (!response.ok) {
const errorText = await submitResponse.text(); const errorText = await response.text();
console.error("Fal.ai submit error:", submitResponse.status, errorText); console.error("Fal.ai error:", response.status, errorText);
throw new Error(`Fal.ai API error: ${submitResponse.status}`); throw new Error(`Fal.ai API error: ${response.status} - ${errorText}`);
} }
const result = await submitResponse.json(); const result = await response.json();
console.log("Fal.ai response received, extracting image...");
// Check if we got a direct result or need to poll
if (result.images && result.images.length > 0) { if (result.images && result.images.length > 0) {
// Direct result
const imageUrl = result.images[0].url; const imageUrl = result.images[0].url;
console.log("✅ Got image URL from Fal.ai, downloading...");
return await fetchImageAsBase64(imageUrl); return await fetchImageAsBase64(imageUrl);
} else if (result.request_id) {
// Need to poll for result
return await pollForResult(result.request_id, falKey);
} }
console.error("Fal.ai response:", JSON.stringify(result).slice(0, 500));
throw new Error("No image in Fal.ai response"); throw new Error("No image in Fal.ai response");
} }
async function pollForResult(requestId: string, falKey: string): Promise<string> {
const maxAttempts = 60;
const pollInterval = 2000;
for (let i = 0; i < maxAttempts; i++) {
await new Promise(resolve => setTimeout(resolve, pollInterval));
const statusResponse = await fetch(`https://queue.fal.run/fal-ai/flux/dev/image-to-image/requests/${requestId}/status`, {
headers: {
"Authorization": `Key ${falKey}`,
},
});
if (!statusResponse.ok) continue;
const status = await statusResponse.json();
if (status.status === "COMPLETED") {
// Get the result
const resultResponse = await fetch(`https://queue.fal.run/fal-ai/flux/dev/image-to-image/requests/${requestId}`, {
headers: {
"Authorization": `Key ${falKey}`,
},
});
if (resultResponse.ok) {
const result = await resultResponse.json();
if (result.images && result.images.length > 0) {
const imageUrl = result.images[0].url;
return await fetchImageAsBase64(imageUrl);
}
}
} else if (status.status === "FAILED") {
throw new Error("Fal.ai generation failed");
}
}
throw new Error("Fal.ai generation timed out");
}
async function fetchImageAsBase64(url: string): Promise<string> { async function fetchImageAsBase64(url: string): Promise<string> {
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {