From 0b1a3a715b06205d8db3cdbc3053fad70d97d1a0 Mon Sep 17 00:00:00 2001 From: 134 Date: Sat, 20 Jun 2026 00:09:42 +0800 Subject: [PATCH] fix: fallback scale adjust on provider failure --- lib/canvas/generate-image.ts | 201 ++++++++++++++++++++++++++++------- 1 file changed, 160 insertions(+), 41 deletions(-) diff --git a/lib/canvas/generate-image.ts b/lib/canvas/generate-image.ts index 33700518e..73c7d239f 100644 --- a/lib/canvas/generate-image.ts +++ b/lib/canvas/generate-image.ts @@ -514,6 +514,81 @@ async function buildScaleAdjustFallbackImage(params: { return workingBuffer; } +async function loadScaleAdjustSourceImage( + verification: CanvasScaleAdjustVerificationConfig +) { + const sourceBuffer = await sharp( + await fetchImageBufferFromUrl(verification.sourceImageUrl), + { failOn: "none" } + ) + .rotate() + .png({ compressionLevel: 6 }) + .toBuffer(); + const sourceMetadata = await sharp(sourceBuffer, { failOn: "none" }).metadata(); + const sourceWidth = Number(sourceMetadata.width || 0); + const sourceHeight = Number(sourceMetadata.height || 0); + if (sourceWidth <= 0 || sourceHeight <= 0) { + return null; + } + + return { + sourceBuffer, + sourceHeight, + sourceWidth, + }; +} + +async function createScaleAdjustFallbackUpload(params: { + sourceBuffer: Buffer; + sourceWidth: number; + sourceHeight: number; + verification: CanvasScaleAdjustVerificationConfig; + debugLabel: string; + imageIndex?: number; + reason: string; + diffScores?: number[]; +}) { + const fallbackBuffer = await buildScaleAdjustFallbackImage({ + sourceBuffer: params.sourceBuffer, + sourceHeight: params.sourceHeight, + sourceWidth: params.sourceWidth, + verification: params.verification, + }); + const thumbnailBuffer = await sharp(fallbackBuffer, { failOn: "none" }) + .resize(512, 512, { fit: "inside", withoutEnlargement: true }) + .png({ compressionLevel: 6 }) + .toBuffer(); + const suffix = `${Date.now()}-${params.imageIndex || 1}-${Math.random() + .toString(36) + .slice(2, 8)}`; + const [originalUpload, thumbnailUpload] = await Promise.all([ + uploadFile( + `canvas-scale-adjust-fallback-${suffix}.png`, + bufferToArrayBuffer(fallbackBuffer) + ), + uploadFile( + `canvas-scale-adjust-fallback-thumb-${suffix}.png`, + bufferToArrayBuffer(thumbnailBuffer) + ), + ]); + + console.warn("[Canvas Generate Image] Scale adjust fallback applied:", { + debugLabel: params.debugLabel, + diffScores: params.diffScores?.map((score) => Math.round(score * 10) / 10), + imageIndex: params.imageIndex, + reason: params.reason, + regionCount: params.verification.regions.length, + scalePercent: params.verification.scalePercent, + sourceHeight: params.sourceHeight, + sourceWidth: params.sourceWidth, + }); + + return { + originalUrl: originalUpload.url, + thumbnailUrl: thumbnailUpload.url, + }; +} + async function applyScaleAdjustVerificationToImages(params: { images: ParsedImage[]; verification: CanvasScaleAdjustVerificationConfig | null; @@ -523,19 +598,11 @@ async function applyScaleAdjustVerificationToImages(params: { if (!verification || images.length === 0) return images; try { - const sourceBuffer = await sharp( - await fetchImageBufferFromUrl(verification.sourceImageUrl), - { failOn: "none" } - ) - .rotate() - .png({ compressionLevel: 6 }) - .toBuffer(); - const sourceMetadata = await sharp(sourceBuffer, { failOn: "none" }).metadata(); - const sourceWidth = Number(sourceMetadata.width || 0); - const sourceHeight = Number(sourceMetadata.height || 0); - if (sourceWidth <= 0 || sourceHeight <= 0) { + const sourceImage = await loadScaleAdjustSourceImage(verification); + if (!sourceImage) { return images; } + const { sourceBuffer, sourceHeight, sourceWidth } = sourceImage; const checkedImages: ParsedImage[] = []; for (const [index, image] of images.entries()) { @@ -563,41 +630,17 @@ async function applyScaleAdjustVerificationToImages(params: { continue; } - const fallbackBuffer = await buildScaleAdjustFallbackImage({ + const fallbackImage = await createScaleAdjustFallbackUpload({ + debugLabel, + diffScores, + imageIndex: index + 1, + reason: "unchanged-output", sourceBuffer, sourceHeight, sourceWidth, verification, }); - const thumbnailBuffer = await sharp(fallbackBuffer, { failOn: "none" }) - .resize(512, 512, { fit: "inside", withoutEnlargement: true }) - .png({ compressionLevel: 6 }) - .toBuffer(); - const suffix = `${Date.now()}-${index + 1}-${Math.random().toString(36).slice(2, 8)}`; - const [originalUpload, thumbnailUpload] = await Promise.all([ - uploadFile( - `canvas-scale-adjust-fallback-${suffix}.png`, - bufferToArrayBuffer(fallbackBuffer) - ), - uploadFile( - `canvas-scale-adjust-fallback-thumb-${suffix}.png`, - bufferToArrayBuffer(thumbnailBuffer) - ), - ]); - - console.warn("[Canvas Generate Image] Scale adjust fallback applied:", { - debugLabel, - diffScores: diffScores.map((score) => Math.round(score * 10) / 10), - imageIndex: index + 1, - regionCount: verification.regions.length, - scalePercent: verification.scalePercent, - sourceHeight, - sourceWidth, - }); - checkedImages.push({ - originalUrl: originalUpload.url, - thumbnailUrl: thumbnailUpload.url, - }); + checkedImages.push(fallbackImage); } return checkedImages; @@ -1483,6 +1526,82 @@ export async function generateCanvasImage( await slot.release(); } + if (scaleAdjustVerification) { + try { + const sourceImage = await loadScaleAdjustSourceImage(scaleAdjustVerification); + if (sourceImage) { + const fallbackImage = await createScaleAdjustFallbackUpload({ + debugLabel, + imageIndex: 1, + reason: "provider-error", + sourceBuffer: sourceImage.sourceBuffer, + sourceHeight: sourceImage.sourceHeight, + sourceWidth: sourceImage.sourceWidth, + verification: scaleAdjustVerification, + }); + + await recordAiModelHealthFailure({ + model: selectedModel.model, + provider, + error: caughtError, + context: debugLabel, + elapsedMs: Date.now() - startedAt, + metadata: { + aspectRatio, + imageSize, + imageCount, + requestedReferenceImageCount: normalizedReferenceImages.length, + reachableReferenceImageCount: reachableReferenceImages.length, + effectiveReferenceImageCount: effectiveReferenceImages.length, + droppedReferenceImageCount: unreachableReferenceImages.length, + scaleAdjustFallbackReason: "provider-error", + ...debugMeta, + }, + }).catch((healthError) => { + console.warn("[Canvas Generate Image] Record model health failure failed:", { + selectedModelRef: selectedModel.model.id, + selectedModelId: selectedModel.model.modelId, + error: healthError instanceof Error ? healthError.message : String(healthError), + }); + }); + + recordCanvasImageModelExecutionDuration( + resolvedModelKey, + Date.now() - startedAt + ); + + console.warn("[Canvas Generate Image] Scale adjust provider failed; returned deterministic fallback:", { + debugLabel, + elapsedMs: Date.now() - startedAt, + providerType: provider.type, + selectedModelRef: selectedModel.model.id, + selectedModelId: selectedModel.model.modelId, + error: caughtError instanceof Error ? caughtError.message : String(caughtError), + ...debugMeta, + }); + + return { + images: [fallbackImage], + model: selectedModel.model.displayName, + modelRef: selectedModel.model.id, + prompt: resultPrompt, + }; + } + } catch (fallbackError) { + console.warn("[Canvas Generate Image] Scale adjust provider-error fallback failed:", { + debugLabel, + error: + fallbackError instanceof Error + ? fallbackError.message + : String(fallbackError), + originalError: + caughtError instanceof Error ? caughtError.message : String(caughtError), + regionCount: scaleAdjustVerification.regions.length, + scalePercent: scaleAdjustVerification.scalePercent, + }); + } + } + await recordAiModelHealthFailure({ model: selectedModel.model, provider,