fix: clarify FAL scale adjust guidance

This commit is contained in:
134 2026-06-28 10:44:54 +08:00
parent 1d0af8c2d8
commit 619cc2bf5b
3 changed files with 51 additions and 4 deletions

View File

@ -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(`
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="soft"><feGaussianBlur stdDeviation="${feather}" /></filter>
</defs>
<rect x="${inset}" y="${inset}" width="${innerWidth}" height="${innerHeight}" fill="#fff" filter="url(#soft)" />
<rect x="${inset}" y="${inset}" width="${innerWidth}" height="${innerHeight}" fill="rgb(${alpha},${alpha},${alpha})" filter="url(#soft)" />
</svg>
`);
@ -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()

View File

@ -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,

View File

@ -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: