From 619cc2bf5bdeefea956088a1dd51857d90e7c7db Mon Sep 17 00:00:00 2001 From: 134 Date: Sun, 28 Jun 2026 10:44:54 +0800 Subject: [PATCH] fix: clarify FAL scale adjust guidance --- app/api/canvas/local-edit/route.ts | 36 ++++++++++++++++++++++++++---- lib/ai/providers/fal-image.ts | 13 +++++++++++ lib/canvas/generate-image.ts | 6 +++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/app/api/canvas/local-edit/route.ts b/app/api/canvas/local-edit/route.ts index 8a087308f..46b0d4f0b 100644 --- a/app/api/canvas/local-edit/route.ts +++ b/app/api/canvas/local-edit/route.ts @@ -97,8 +97,10 @@ const MAX_REFERENCE_IMAGES_PER_REGION = 3; const MAX_SCALE_ADJUST_CROP_REFERENCES = 4; const SCALE_ADJUST_CROP_PADDING_RATIO = 0.18; const SCALE_ADJUST_CROP_MAX_EDGE = 1536; -const SCALE_ADJUST_FAL_MOCKUP_PADDING_RATIO = 0.04; -const SCALE_ADJUST_FAL_MOCKUP_BLUR_PX = 38; +const SCALE_ADJUST_FAL_MOCKUP_PADDING_RATIO = 0.02; +const SCALE_ADJUST_FAL_MOCKUP_BLUR_PX = 30; +const SCALE_ADJUST_FAL_MOCKUP_SUBJECT_OPACITY = 0.82; +const SCALE_ADJUST_FAL_MOCKUP_WORKSPACE_OPACITY = 0.32; const MIN_REGION_EDGE_PX = 24; const SCALE_ADJUST_OPERATION = "scale-adjust"; type LocalEditOperation = "local-edit" | typeof SCALE_ADJUST_OPERATION; @@ -569,6 +571,7 @@ async function createFeatherAlphaMask(params: { height: number; feather: number; inset: number; + opacity?: number; }) { const { width, height } = params; const feather = Math.max(1, Math.round(params.feather)); @@ -576,12 +579,17 @@ async function createFeatherAlphaMask(params: { const inset = clamp(Math.round(params.inset), 1, maxInset); const innerWidth = Math.max(1, width - inset * 2); const innerHeight = Math.max(1, height - inset * 2); + const alpha = clamp( + Math.round((params.opacity ?? 1) * 255), + 0, + 255 + ); const svg = Buffer.from(` - + `); @@ -599,11 +607,13 @@ async function addFeatherAlpha(params: { height: number; feather: number; inset: number; + opacity?: number; }) { const mask = await createFeatherAlphaMask({ feather: params.feather, height: params.height, inset: params.inset, + opacity: params.opacity, width: params.width, }); @@ -657,6 +667,7 @@ async function buildScaleAdjustFalTargetMockup(params: { feather: workspaceFeather, height: workspacePixelRect.height, inset: Math.max(workspaceFeather * 2.6, workspaceFeather + 1), + opacity: SCALE_ADJUST_FAL_MOCKUP_WORKSPACE_OPACITY, width: workspacePixelRect.width, }); @@ -677,6 +688,7 @@ async function buildScaleAdjustFalTargetMockup(params: { feather: subjectFeather, height: targetPixelRect.height, inset: Math.max(subjectFeather * 1.6, subjectFeather + 1), + opacity: SCALE_ADJUST_FAL_MOCKUP_SUBJECT_OPACITY, width: targetPixelRect.width, }); @@ -1094,7 +1106,7 @@ function buildFalScaleAdjustMockupPrompt( return [ "这是一项室内家用场景里的家具/物体局部比例调整任务。输入图片1是一张粗略目标效果草图,不是最终结果,也不是标注图。", `最终输出必须是一张真实、干净、自然的单张照片,并保持原始画幅 ${sourceSize.width} x ${sourceSize.height} px。`, - "输入图片1已经把目标主体粗略放到了用户希望的最终大小和位置;请把它作为最终布局意图来理解,但不要直接复制其中的模糊、拼接边缘、软边、矩形块或局部贴片痕迹。", + "输入图片1用半透明软占位表达目标主体的最终大小和位置;请把它作为布局意图来理解,但不要直接复制其中的模糊、拼接边缘、软边、矩形块或局部贴片痕迹。", "核心目标:只调整被选中的家具/物体主体比例和位置;不要恢复成原来的大尺寸,也不要返回几乎未修改的原图。", "请用 AI 自然重建原主体移除后露出的背景,让地毯、地板、窗户、墙面、桌面、阴影、遮挡和透视连续自然。", "请保留目标主体的真实材质、结构、轮廓、透视和光照;主体不能透明、幽灵化、变形、被裁切,不能变成说明图或标注元素。", @@ -1107,6 +1119,17 @@ function buildFalScaleAdjustMockupPrompt( ].join("\n"); } +function buildFalScaleAdjustSystemPrompt() { + return [ + "You are an image editing model for local furniture scale adjustment.", + "Interpret the input image as a soft layout guide, not as a pixel layer to copy.", + "Generate a clean realistic final photo by editing the target furniture itself and naturally rebuilding only the tiny surrounding background that becomes visible.", + "Do not paste, composite, clone, crop, resize, or copy rectangular image patches from the guide.", + "Never output guide artifacts: no blurred blocks, seams, rectangular borders, arrows, labels, masks, UI marks, or color-coded annotations.", + "Keep the camera frame, lighting, wall, floor, rug, windows, background furniture, text, logos, and all non-target regions stable.", + ].join("\n"); +} + export async function POST(request: NextRequest) { try { const session = await auth(); @@ -1395,6 +1418,11 @@ export async function POST(request: NextRequest) { sourceWidth, }, displayPrompt, + falSafetyTolerance: useFalScaleAdjustMockup ? "6" : undefined, + falSystemPrompt: useFalScaleAdjustMockup + ? buildFalScaleAdjustSystemPrompt() + : undefined, + falThinkingLevel: useFalScaleAdjustMockup ? "high" : undefined, imageCount: useFalScaleAdjustMockup ? 4 : 1, imageSize: typeof body.imageSize === "string" && body.imageSize.trim() diff --git a/lib/ai/providers/fal-image.ts b/lib/ai/providers/fal-image.ts index 0de539b7e..b4e8f37fc 100644 --- a/lib/ai/providers/fal-image.ts +++ b/lib/ai/providers/fal-image.ts @@ -24,6 +24,8 @@ interface FalImageParams { quality?: string; maskImageUrl?: string; openaiApiKey?: string; + systemPrompt?: string; + thinkingLevel?: string; syncMode?: boolean; } @@ -655,6 +657,8 @@ function buildFalRequestPayload(params: { const safetyTolerance = normalizeSafetyTolerance(params.imageParams?.safetyTolerance); const seed = normalizeSeed(params.imageParams?.seed); const imageCount = Math.max(1, Math.min(6, Number(params.imageParams?.imageCount || 1))); + const systemPrompt = String(params.imageParams?.systemPrompt || "").trim(); + const thinkingLevel = String(params.imageParams?.thinkingLevel || "").trim().toLowerCase(); const activeModelId = params.overrideModelId || params.modelId; const isGptImage2Edit = isFalGptImage2EditModelId(activeModelId); const falEndpointId = isGptImage2Edit @@ -752,6 +756,15 @@ function buildFalRequestPayload(params: { } } + if (isFalNanoBananaModelId(falEndpointId)) { + if (systemPrompt) { + input.system_prompt = systemPrompt; + } + if (thinkingLevel === "minimal" || thinkingLevel === "high") { + input.thinking_level = thinkingLevel; + } + } + return { prompt, imageUrls, diff --git a/lib/canvas/generate-image.ts b/lib/canvas/generate-image.ts index 77cca6c9d..a9e76235a 100644 --- a/lib/canvas/generate-image.ts +++ b/lib/canvas/generate-image.ts @@ -67,6 +67,9 @@ export interface CanvasGenerateImageRequest { aspectRatio?: string; imageSize?: string; imageCount?: number; + falSafetyTolerance?: string | number; + falSystemPrompt?: string; + falThinkingLevel?: "minimal" | "high"; promptExtend?: boolean; referenceImage?: string; referenceImages?: string[]; @@ -1930,10 +1933,13 @@ export async function generateCanvasImage( const imageParams = { aspectRatio, + safetyTolerance: request.falSafetyTolerance, imageSize, imageCount: 1, negativePrompt, promptExtend, + systemPrompt: request.falSystemPrompt, + thinkingLevel: request.falThinkingLevel, }; let executeGenerateBatch: