fix: upload fal edit inputs to fal storage
This commit is contained in:
parent
3aa031a4e4
commit
6fe69de8de
@ -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: {
|
function buildFalRequestPayload(params: {
|
||||||
modelId: string;
|
modelId: string;
|
||||||
overrideModelId?: string;
|
overrideModelId?: string;
|
||||||
@ -834,18 +892,26 @@ export async function submitFalImageTask(params: {
|
|||||||
}): Promise<FalSubmittedImageTask> {
|
}): Promise<FalSubmittedImageTask> {
|
||||||
const request = buildFalRequestPayload(params);
|
const request = buildFalRequestPayload(params);
|
||||||
const fal = await createFalClientFromApiKey(params.apiKey);
|
const fal = await createFalClientFromApiKey(params.apiKey);
|
||||||
|
const prepared = await prepareFalInputImageUrls({
|
||||||
|
fal,
|
||||||
|
input: request.input,
|
||||||
|
sourceImageUrls: request.imageUrls,
|
||||||
|
});
|
||||||
|
|
||||||
console.log("[Fal Image] Queue submit", {
|
console.log("[Fal Image] Queue submit", {
|
||||||
model: request.activeModelId,
|
model: request.activeModelId,
|
||||||
endpoint: request.falEndpointId,
|
endpoint: request.falEndpointId,
|
||||||
imageCount: request.imageCount,
|
imageCount: request.imageCount,
|
||||||
hasInputImages: request.imageUrls.length > 0,
|
hasInputImages: prepared.sourceImageUrls.length > 0,
|
||||||
resolution: request.outputSizeLabel,
|
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, {
|
const enqueueResult = await fal.queue.submit(request.falEndpointId, {
|
||||||
input: request.input,
|
input: prepared.input,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -855,7 +921,7 @@ export async function submitFalImageTask(params: {
|
|||||||
imageCount: request.imageCount,
|
imageCount: request.imageCount,
|
||||||
outputSizeLabel: request.outputSizeLabel,
|
outputSizeLabel: request.outputSizeLabel,
|
||||||
prompt: request.prompt,
|
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 fal = await createFalClientFromApiKey(apiKey);
|
||||||
|
const prepared = await prepareFalInputImageUrls({
|
||||||
|
fal,
|
||||||
|
input: request.input,
|
||||||
|
sourceImageUrls: request.imageUrls,
|
||||||
|
});
|
||||||
const timeoutMs = normalizeTimeoutMs(options.imageParams?.timeoutMs);
|
const timeoutMs = normalizeTimeoutMs(options.imageParams?.timeoutMs);
|
||||||
|
|
||||||
let result: any;
|
let result: any;
|
||||||
try {
|
try {
|
||||||
result = await fal.subscribe(request.falEndpointId, {
|
result = await fal.subscribe(request.falEndpointId, {
|
||||||
input: request.input,
|
input: prepared.input,
|
||||||
logs: true,
|
logs: true,
|
||||||
timeout: timeoutMs,
|
timeout: timeoutMs,
|
||||||
onQueueUpdate: (update: any) => {
|
onQueueUpdate: (update: any) => {
|
||||||
@ -945,7 +1016,7 @@ export function createFalImage({ apiKey, baseURL }: FalImageConfig) {
|
|||||||
|
|
||||||
const text = await normalizeFalResultToMarkdown({
|
const text = await normalizeFalResultToMarkdown({
|
||||||
payload: result,
|
payload: result,
|
||||||
sourceImageUrls: request.imageUrls,
|
sourceImageUrls: prepared.sourceImageUrls,
|
||||||
imageCount: request.imageCount,
|
imageCount: request.imageCount,
|
||||||
outputSizeLabel: request.outputSizeLabel,
|
outputSizeLabel: request.outputSizeLabel,
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user