fix: stabilize scale adjust fal timeout and uploads

This commit is contained in:
134 2026-07-07 22:50:06 +08:00
parent 134411f7f8
commit 0405113d52
3 changed files with 63 additions and 17 deletions

View File

@ -28,6 +28,7 @@ interface FalImageParams {
systemPrompt?: string;
thinkingLevel?: string;
syncMode?: boolean;
timeoutMs?: number;
}
interface UploadedImage {
@ -50,6 +51,8 @@ const DEFAULT_RESOLUTION = "1K";
const DEFAULT_OUTPUT_FORMAT = "png";
const DEFAULT_SAFETY_TOLERANCE = "4";
const DEFAULT_TIMEOUT_MS = 600000;
const MIN_TIMEOUT_MS = 30_000;
const MAX_TIMEOUT_MS = 600_000;
const FAL_GENERIC_MAX_EDGE = 4096;
const FAL_GENERIC_MAX_PIXELS = 4096 * 4096;
const GPT_IMAGE_2_MAX_EDGE = 3840;
@ -434,6 +437,12 @@ function normalizeOptionalHttpUrl(value?: string) {
return /^https?:\/\//i.test(normalized) ? normalized : "";
}
function normalizeTimeoutMs(value: unknown) {
const numeric = Number(value);
if (!Number.isFinite(numeric) || numeric <= 0) return DEFAULT_TIMEOUT_MS;
return Math.max(MIN_TIMEOUT_MS, Math.min(MAX_TIMEOUT_MS, Math.round(numeric)));
}
function detectImageExtension(imageUrl: string, contentType?: string | null) {
const type = (contentType || "").toLowerCase();
if (type.includes("jpeg") || type.includes("jpg")) return "jpg";
@ -911,13 +920,14 @@ export function createFalImage({ apiKey, baseURL }: FalImageConfig) {
});
const fal = await createFalClientFromApiKey(apiKey);
const timeoutMs = normalizeTimeoutMs(options.imageParams?.timeoutMs);
let result: any;
try {
result = await fal.subscribe(request.falEndpointId, {
input: request.input,
logs: true,
timeout: DEFAULT_TIMEOUT_MS,
timeout: timeoutMs,
onQueueUpdate: (update: any) => {
if (update?.status === "IN_PROGRESS" && Array.isArray(update.logs)) {
for (const log of update.logs) {

View File

@ -292,6 +292,15 @@ function getCanvasProviderRuntimeParams(params: {
};
}
if (
params.providerType === "fal-image" &&
isCanvasScaleAdjustDebugRequest(params.debugLabel, params.debugMeta)
) {
return {
timeoutMs: CANVAS_SCALE_ADJUST_FINAL_PROVIDER_BATCH_TIMEOUT_MS,
};
}
return {};
}
@ -3059,6 +3068,13 @@ async function applyScaleAdjustPatchCompositeToImages(params: {
touchesTop,
width: workspaceBox.width,
});
const staticPatchMaskRaw = staticPatchMask
? await normalizeOpacityMaskToRaw({
height: workspaceBox.height,
maskBuffer: staticPatchMask,
width: workspaceBox.width,
})
: null;
const regionClipPatchMask = maskRegionBoxes?.length
? buildScaleAdjustPatchCompositeMaskSvg({
featherPx,
@ -3177,12 +3193,22 @@ async function applyScaleAdjustPatchCompositeToImages(params: {
imageBuffer: resizedGeneratedBuffer,
width: workspaceBox.width,
})
: await sharp(resizedGeneratedBuffer, { failOn: "none" })
.ensureAlpha()
.composite([{ input: patchMask, blend: "dest-in" }])
.png({ compressionLevel: 6 })
.toBuffer();
const mergedBuffer = await sharp(sourceBuffer, { failOn: "none" })
: activeClipPatchMaskRaw || staticPatchMaskRaw
? await applyRawAlphaToImage({
alphaMaskRaw:
activeClipPatchMaskRaw && staticPatchMaskRaw
? multiplyAlphaMasks(staticPatchMaskRaw, activeClipPatchMaskRaw)
: activeClipPatchMaskRaw || staticPatchMaskRaw!,
height: workspaceBox.height,
imageBuffer: resizedGeneratedBuffer,
width: workspaceBox.width,
})
: await sharp(resizedGeneratedBuffer, { failOn: "none" })
.ensureAlpha()
.composite([{ input: patchMask, blend: "dest-in" }])
.png({ compressionLevel: 6 })
.toBuffer();
const mergedPngBuffer = await sharp(sourceBuffer, { failOn: "none" })
.ensureAlpha()
.composite([
{
@ -3193,20 +3219,25 @@ async function applyScaleAdjustPatchCompositeToImages(params: {
])
.png({ compressionLevel: 6 })
.toBuffer();
const thumbnailBuffer = await sharp(mergedBuffer, { failOn: "none" })
const mergedBuffer = await sharp(mergedPngBuffer, { failOn: "none" })
.flatten({ background: "#ffffff" })
.jpeg({ quality: 94, mozjpeg: true })
.toBuffer();
const thumbnailBuffer = await sharp(mergedPngBuffer, { failOn: "none" })
.flatten({ background: "#ffffff" })
.resize(512, 512, { fit: "inside", withoutEnlargement: true })
.png({ compressionLevel: 6 })
.jpeg({ quality: 88, mozjpeg: true })
.toBuffer();
const suffix = `${Date.now()}-${index + 1}-${Math.random()
.toString(36)
.slice(2, 8)}`;
const [originalUpload, thumbnailUpload] = await Promise.all([
uploadFile(
`canvas-scale-adjust-ai-patch-composited-${suffix}.png`,
`canvas-scale-adjust-ai-patch-composited-${suffix}.jpg`,
bufferToArrayBuffer(mergedBuffer)
),
uploadFile(
`canvas-scale-adjust-ai-patch-composited-thumb-${suffix}.png`,
`canvas-scale-adjust-ai-patch-composited-thumb-${suffix}.jpg`,
bufferToArrayBuffer(thumbnailBuffer)
),
]);
@ -3293,26 +3324,31 @@ async function applyScaleAdjustProductLockCompositeToImages(params: {
width: generatedWidth,
})
: resizedOverlayBaseBuffer;
const mergedBuffer = await sharp(normalizedGeneratedBuffer, {
const mergedPngBuffer = await sharp(normalizedGeneratedBuffer, {
failOn: "none",
})
.composite([{ input: resizedOverlayBuffer, left: 0, top: 0 }])
.png({ compressionLevel: 6 })
.toBuffer();
const thumbnailBuffer = await sharp(mergedBuffer, { failOn: "none" })
const mergedBuffer = await sharp(mergedPngBuffer, { failOn: "none" })
.flatten({ background: "#ffffff" })
.jpeg({ quality: 94, mozjpeg: true })
.toBuffer();
const thumbnailBuffer = await sharp(mergedPngBuffer, { failOn: "none" })
.flatten({ background: "#ffffff" })
.resize(512, 512, { fit: "inside", withoutEnlargement: true })
.png({ compressionLevel: 6 })
.jpeg({ quality: 88, mozjpeg: true })
.toBuffer();
const suffix = `${Date.now()}-${index + 1}-${Math.random()
.toString(36)
.slice(2, 8)}`;
const [originalUpload, thumbnailUpload] = await Promise.all([
uploadFile(
`canvas-scale-adjust-product-lock-${suffix}.png`,
`canvas-scale-adjust-product-lock-${suffix}.jpg`,
bufferToArrayBuffer(mergedBuffer)
),
uploadFile(
`canvas-scale-adjust-product-lock-thumb-${suffix}.png`,
`canvas-scale-adjust-product-lock-thumb-${suffix}.jpg`,
bufferToArrayBuffer(thumbnailBuffer)
),
]);

View File

@ -59,7 +59,7 @@ interface QueueState {
}
const DEFAULT_RUNNING_TIMEOUT_MS = 10 * 60 * 1000;
const SCALE_ADJUST_RUNNING_TIMEOUT_MS = 15 * 60 * 1000;
const SCALE_ADJUST_RUNNING_TIMEOUT_MS = 8 * 60 * 1000;
const LOCAL_ACTIVE_RUNNING_HARD_TIMEOUT_MS = 30 * 60 * 1000;
const PENDING_LOOKAHEAD_LIMIT = 100;
const QUEUE_STATUS_PENDING_LOOKAHEAD_LIMIT = 500;