fix: upload fal edit inputs to fal storage

This commit is contained in:
134 2026-07-08 20:56:03 +08:00
parent 3aa031a4e4
commit 6fe69de8de

View File

@ -654,6 +654,64 @@ function createFalClientFromApiKey(apiKey: string) {
);
}
async function uploadImageUrlToFalStorage(params: {
fal: Awaited<ReturnType<typeof createFalClientFromApiKey>>;
url: string;
}) {
const { fal, url } = params;
if (/fal\.(?:media|run)\//i.test(url) || /fal-ai/i.test(url)) return url;
const response = await fetch(url, { signal: AbortSignal.timeout(60_000) });
if (!response.ok) {
throw new Error(`FAL input image download failed before storage upload: ${response.status}`);
}
const contentType = response.headers.get("content-type") || "image/png";
const buffer = Buffer.from(await response.arrayBuffer());
const blob = new Blob([buffer], { type: contentType });
return fal.storage.upload(blob);
}
async function prepareFalInputImageUrls(params: {
fal: Awaited<ReturnType<typeof createFalClientFromApiKey>>;
input: Record<string, any>;
sourceImageUrls: string[];
}) {
const { fal, input, sourceImageUrls } = params;
const cache = new Map<string, Promise<string>>();
const upload = (url: string) => {
const normalized = String(url || "").trim();
if (!/^https?:\/\//i.test(normalized)) return Promise.resolve(normalized);
const existing = cache.get(normalized);
if (existing) return existing;
const uploaded = uploadImageUrlToFalStorage({ fal, url: normalized });
cache.set(normalized, uploaded);
return uploaded;
};
const preparedInput = { ...input };
if (typeof preparedInput.image_url === "string") {
preparedInput.image_url = await upload(preparedInput.image_url);
}
if (Array.isArray(preparedInput.image_urls)) {
preparedInput.image_urls = await Promise.all(
preparedInput.image_urls.map((url) => upload(String(url || "")))
);
}
if (typeof preparedInput.mask_url === "string") {
preparedInput.mask_url = await upload(preparedInput.mask_url);
}
const preparedSourceImageUrls = await Promise.all(
sourceImageUrls.map((url) => upload(url))
);
return {
input: preparedInput,
sourceImageUrls: preparedSourceImageUrls,
};
}
function buildFalRequestPayload(params: {
modelId: string;
overrideModelId?: string;
@ -834,18 +892,26 @@ export async function submitFalImageTask(params: {
}): Promise<FalSubmittedImageTask> {
const request = buildFalRequestPayload(params);
const fal = await createFalClientFromApiKey(params.apiKey);
const prepared = await prepareFalInputImageUrls({
fal,
input: request.input,
sourceImageUrls: request.imageUrls,
});
console.log("[Fal Image] Queue submit", {
model: request.activeModelId,
endpoint: request.falEndpointId,
imageCount: request.imageCount,
hasInputImages: request.imageUrls.length > 0,
hasInputImages: prepared.sourceImageUrls.length > 0,
resolution: request.outputSizeLabel,
inputKeys: Object.keys(request.input),
inputKeys: Object.keys(prepared.input),
storagePrepared: prepared.sourceImageUrls.some(
(url, index) => url !== request.imageUrls[index]
),
});
const enqueueResult = await fal.queue.submit(request.falEndpointId, {
input: request.input,
input: prepared.input,
});
return {
@ -855,7 +921,7 @@ export async function submitFalImageTask(params: {
imageCount: request.imageCount,
outputSizeLabel: request.outputSizeLabel,
prompt: request.prompt,
sourceImageUrls: request.imageUrls,
sourceImageUrls: prepared.sourceImageUrls,
};
}
@ -920,12 +986,17 @@ export function createFalImage({ apiKey, baseURL }: FalImageConfig) {
});
const fal = await createFalClientFromApiKey(apiKey);
const prepared = await prepareFalInputImageUrls({
fal,
input: request.input,
sourceImageUrls: request.imageUrls,
});
const timeoutMs = normalizeTimeoutMs(options.imageParams?.timeoutMs);
let result: any;
try {
result = await fal.subscribe(request.falEndpointId, {
input: request.input,
input: prepared.input,
logs: true,
timeout: timeoutMs,
onQueueUpdate: (update: any) => {
@ -945,7 +1016,7 @@ export function createFalImage({ apiKey, baseURL }: FalImageConfig) {
const text = await normalizeFalResultToMarkdown({
payload: result,
sourceImageUrls: request.imageUrls,
sourceImageUrls: prepared.sourceImageUrls,
imageCount: request.imageCount,
outputSizeLabel: request.outputSizeLabel,
});