diff --git a/app/api/canvas/local-edit/route.ts b/app/api/canvas/local-edit/route.ts index 1b793fed0..b9175152a 100644 --- a/app/api/canvas/local-edit/route.ts +++ b/app/api/canvas/local-edit/route.ts @@ -997,7 +997,7 @@ function buildScaleAdjustSourceFootprintWashSvg(params: { const top = Math.max(0, box.top - blur); const right = Math.min(width, box.left + box.width + blur); const bottom = Math.min(height, box.top + box.height + blur); - return ``; + return ``; }) .join(""); @@ -2046,8 +2046,18 @@ function buildScaleAdjustPatchPrompt( scalePercent: ScaleAdjustPercent | null | undefined, patchReference: ScaleAdjustPatchReference, sourceSize: { width: number; height: number }, - cropReferences: ScaleAdjustCropReference[] = [] + cropReferences: ScaleAdjustCropReference[] = [], + options: { targetDraftPrimary?: boolean } = {} ) { + const targetDraftPrimary = options.targetDraftPrimary === true; + const sourceInputIndex = targetDraftPrimary ? 2 : 1; + const draftInputIndex = targetDraftPrimary ? 1 : 2; + const firstInputRole = targetDraftPrimary + ? "Input image 1 is the rough edited target-layout crop. It is the hard layout target: the selected furniture size and center in the final output must match this draft, but the rough wash, ghosted old footprint, placeholder shadow, blur, and patch artifacts must be cleaned." + : "Input image 1 is the exact crop from the original photo and is the locked crop base."; + const secondInputRole = targetDraftPrimary + ? "Input image 2 is the original clean crop. Use it only for stable crop framing, background texture, lighting, perspective, and furniture identity. Do not restore the selected furniture to its old larger size from this image." + : "Input image 2 is a rough target-layout draft of the same crop. It is the hard layout target for the selected furniture size and position. Use it to understand the final footprint and newly visible background area, then remove any rough seams, washed areas, placeholder shadows, blur, or patch artifacts from that draft."; const cropLines = cropReferences .map( (reference, index) => @@ -2085,21 +2095,24 @@ function buildScaleAdjustPatchPrompt( cropIndex >= 0 ? ` Input image ${cropIndex + 3} is the clean identity reference for this exact furniture.` : ""; - return `Region ${region.index}: current search area inside input image 1 crop is x=${sourceInPatch.left}, y=${sourceInPatch.top}, w=${sourceInPatch.width}, h=${sourceInPatch.height}; final visual footprint inside the crop is x=${targetInPatch.left}, y=${targetInPatch.top}, w=${targetInPatch.width}, h=${targetInPatch.height}; requested scale width=${widthFactor.toFixed(2)}, height=${heightFactor.toFixed(2)}. User request: ${region.prompt || action}. Main action: ${action}.${cropNote}`; + return `Region ${region.index}: current search area inside input image ${sourceInputIndex} crop is x=${sourceInPatch.left}, y=${sourceInPatch.top}, w=${sourceInPatch.width}, h=${sourceInPatch.height}; final visual footprint inside input image ${draftInputIndex} / output crop is x=${targetInPatch.left}, y=${targetInPatch.top}, w=${targetInPatch.width}, h=${targetInPatch.height}; requested scale width=${widthFactor.toFixed(2)}, height=${heightFactor.toFixed(2)}. User request: ${region.prompt || action}. Main action: ${action}. The final object must be much closer to the final footprint than to the current search area.${cropNote}`; }) .join("\n"); return [ "Local furniture scale edit inside a cropped workspace.", `Return exactly one clean realistic crop with the same framing and aspect as input image 1. The crop represents full-photo pixels left=${patchReference.workspacePixelRect.left}, top=${patchReference.workspacePixelRect.top}, width=${patchReference.workspacePixelRect.width}, height=${patchReference.workspacePixelRect.height}.`, - "Input image 1 is the exact crop from the original photo and is the locked crop base.", - "Input image 2 is a rough target-layout draft of the same crop. It is not final artwork. Use it only to understand the desired furniture size, position, and newly visible background area. Remove any rough seams, washed areas, placeholder shadows, blur, or patch artifacts from that draft.", + firstInputRole, + secondInputRole, cropLines, "Only edit the selected movable furniture subject. Keep the crop borders, wall, window frame, floor, rug, sofa, lamps, decor, lighting, camera perspective, and all non-target furniture stable.", - "When the final footprint is smaller, the selected furniture must clearly shrink; the old larger footprint outside the final footprint must become natural background/floor/rug/wall/shadow that matches the original crop.", + "When the final footprint is smaller, the selected furniture must clearly shrink; erase the old larger furniture footprint outside the final footprint and replace it with natural background/floor/rug/wall/shadow that matches the original crop.", "Keep the same furniture identity, material, perspective, object details, contact shadows, and natural aspect ratio. Do not stretch, squash, duplicate, or replace it with a different item.", + "Hard failure condition: if the selected furniture remains near its original old size, the answer is wrong. The output must visibly reduce the furniture footprint to the target draft footprint.", "Do not output boxes, lines, labels, coordinate marks, UI marks, text, arrows, overlays, rough layout artifacts, rectangular patch edges, or a full-room image.", - "中文硬约束:只输出输入图1同一裁切范围的干净局部照片;输入图2只是目标大小/位置草稿,不是要照抄的图层。目标家具必须相对原图产生明显比例变化,非目标背景和裁切边缘要稳定自然。", + targetDraftPrimary + ? "中文硬约束:输入图1是目标大小/位置草稿,必须按它的小尺寸家具布局输出;输入图2只是原始质感和背景参考,绝对不要把家具恢复成输入图2里的旧大尺寸。只输出同一裁切范围的干净局部照片,去掉草稿中的泛白、残影和拼接感。" + : "中文硬约束:只输出输入图1同一裁切范围的干净局部照片;输入图2是目标大小/位置草稿,必须按它的小尺寸家具布局输出,不是要保留旧尺寸。目标家具必须相对原图产生明显比例变化,非目标背景和裁切边缘要稳定自然。", "", "Region instructions:", regionLines, @@ -2389,6 +2402,7 @@ export async function POST(request: NextRequest) { sourceWidth, }) : []; + const useScaleAdjustPatchTargetDraftPrimary = Boolean(scaleAdjustPatchReference); const prompt = isScaleAdjustOperation ? scaleAdjustPatchReference ? buildScaleAdjustPatchPrompt( @@ -2396,7 +2410,8 @@ export async function POST(request: NextRequest) { scalePercent, scaleAdjustPatchReference, { width: sourceWidth, height: sourceHeight }, - scaleAdjustCropReferences + scaleAdjustCropReferences, + { targetDraftPrimary: useScaleAdjustPatchTargetDraftPrimary } ) : useProviderTextOnlyCoordinatePrompt ? buildScaleAdjustTextOnlyPrompt( @@ -2442,12 +2457,19 @@ export async function POST(request: NextRequest) { ); const referenceImages = isScaleAdjustOperation ? scaleAdjustPatchReference - ? [ - scaleAdjustPatchReference.sourceCropUrl, - scaleAdjustPatchReference.targetDraftUrl, - ...scaleAdjustCropReferenceImageUrls, - ...manualRegionReferenceImageUrls, - ] + ? useScaleAdjustPatchTargetDraftPrimary + ? [ + scaleAdjustPatchReference.targetDraftUrl, + scaleAdjustPatchReference.sourceCropUrl, + ...scaleAdjustCropReferenceImageUrls, + ...manualRegionReferenceImageUrls, + ] + : [ + scaleAdjustPatchReference.sourceCropUrl, + scaleAdjustPatchReference.targetDraftUrl, + ...scaleAdjustCropReferenceImageUrls, + ...manualRegionReferenceImageUrls, + ] : [ sourceImageUrl, ...(sendScaleAdjustAnnotatedReferenceToModel @@ -2526,7 +2548,8 @@ export async function POST(request: NextRequest) { useScaleAdjustEditMask ? resolvedScaleAdjustModelEntry?.provider?.type || null : null, - cleanSourceReferenceIndex: 1, + cleanSourceReferenceIndex: + scaleAdjustPatchReference && useScaleAdjustPatchTargetDraftPrimary ? 2 : 1, configuredDetailModelId: configuredDetailModel || null, detailFixedModelApplied: Boolean(detailFixedModel), detailFixedModelId: detailFixedModel || null, @@ -2535,6 +2558,12 @@ export async function POST(request: NextRequest) { requestedModel: requestedModel || null, resolvedAspectRatio, scaleAdjustPatchWorkflowApplied: Boolean(scaleAdjustPatchReference), + scaleAdjustPatchReferenceOrder: + scaleAdjustPatchReference && useScaleAdjustPatchTargetDraftPrimary + ? "target-draft-source-identity" + : scaleAdjustPatchReference + ? "source-target-draft-identity" + : null, scaleAdjustPatchSourceCropUrl: scaleAdjustPatchReference?.sourceCropUrl || null, scaleAdjustPatchTargetDraftUrl: