diff --git a/app/(chat)/api/chat/_lib/detail-replacement.ts b/app/(chat)/api/chat/_lib/detail-replacement.ts index b5e458e8b..21155b94f 100644 --- a/app/(chat)/api/chat/_lib/detail-replacement.ts +++ b/app/(chat)/api/chat/_lib/detail-replacement.ts @@ -187,6 +187,7 @@ export async function normalizeReplacementResultImage(params: { generatedThumbnailUrl?: string; referenceImageUrl: string; referenceGeometry?: ReplacementReferenceGeometry | null; + scenePlan?: ReplacementScenePlan | null; uploadPrefix: string; }): Promise<{ originalUrl: string; @@ -235,13 +236,23 @@ export async function normalizeReplacementResultImage(params: { const shouldCropTallComposite = Boolean(targetAspectRatio && generatedAspectRatio) && Number(generatedAspectRatio) < Number(targetAspectRatio) * 0.78; + const shouldPreserveFullGeneratedCanvas = + params.scenePlan?.whiteBackgroundLikely === true || + params.scenePlan?.textPolicy === "lock" || + params.scenePlan?.generationRoute === "copy_locked_regenerate" || + params.scenePlan?.generationRoute === "layout_preserve_replace"; + const resizeFit = shouldCropTallComposite + ? "cover" + : shouldPreserveFullGeneratedCanvas + ? "contain" + : "cover"; const normalizedBuffer = targetWidth && targetHeight ? await imagePipeline .resize(targetWidth, targetHeight, { - // Wide outputs need containment to avoid cropping edge text; overly tall outputs - // usually mean the model stacked two page variants, so crop to the top page. - fit: shouldCropTallComposite ? "cover" : "contain", + // Layout-locked copy pages keep containment to protect edge text. Scene pages + // fill the target frame so a wrong model aspect does not become a white panel. + fit: resizeFit, position: shouldCropTallComposite ? "top" : "centre", background: { r: 255, g: 255, b: 255, alpha: 1 }, withoutEnlargement: false, diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index 4a2d054c9..68e97de60 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -3530,6 +3530,7 @@ export async function POST(request: Request) { generatedThumbnailUrl: firstImage.thumbnailUrl, referenceImageUrl: reference.imageUrl, referenceGeometry, + scenePlan: replacementExecutionPlan.scenePlan, uploadPrefix: "agent-detail-replacement", }); finalImage = { @@ -4333,6 +4334,7 @@ export async function POST(request: Request) { generatedThumbnailUrl: firstImage.thumbnailUrl, referenceImageUrl: reference.imageUrl, referenceGeometry, + scenePlan: replacementExecutionPlan.scenePlan, uploadPrefix: isDetailReplacementPreset ? "agent-detail-replacement" : "agent-sku-replacement", diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index d2c1b8f91..a14d89b37 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -6685,7 +6685,7 @@ export function AgentGenerationWorkspaceDarkV2({ )} onClick={() => setActiveDetailReferenceCardId(card.id)} > -
+