diff --git a/app/(chat)/api/chat/_lib/detail-replacement.ts b/app/(chat)/api/chat/_lib/detail-replacement.ts index a09968cf2..460efaf32 100644 --- a/app/(chat)/api/chat/_lib/detail-replacement.ts +++ b/app/(chat)/api/chat/_lib/detail-replacement.ts @@ -216,7 +216,7 @@ export async function resolveReplacementReferenceVisualProfile( reason: likelyWhiteLayout ? "像素初筛显示大面积白底/浅色低饱和版式区域,按白底说明图保护" : likelyWhiteScene - ? "像素初筛显示白/浅色低饱和场景,按白场景保护" + ? "像素初筛显示白/浅色低饱和真实场景,按白场景基调软重造" : "像素初筛未达到白底/白场景阈值", }; } catch (error) { @@ -763,14 +763,19 @@ function applyDetailReplacementScenePlanGuards( scenePlan.promptPackPolicy !== "forbid"; const hasWhiteLayoutHint = DETAIL_WHITE_LAYOUT_HINT_PATTERN.test(planText); const hasPeopleActionHint = DETAIL_PEOPLE_ACTION_HINT_PATTERN.test(planText); + const visualProfileSuggestsWhiteLayout = params.visualProfile?.likelyWhiteLayout === true; const visualProfileSuggestsWhiteScene = - params.visualProfile?.likelyWhiteScene === true || - params.visualProfile?.likelyWhiteLayout === true; + params.visualProfile?.likelyWhiteScene === true && + !visualProfileSuggestsWhiteLayout; + const scenePlanSuggestsWhiteLayout = + scenePlan.whiteBackgroundLikely && + !visualProfileSuggestsWhiteScene && + (scenePlan.textPolicy === "lock" || scenePlan.copyImageCoupled); const shouldProtectWhiteLayout = !isProductOnly && - (scenePlan.whiteBackgroundLikely || - params.visualProfile?.likelyWhiteLayout === true || - hasWhiteLayoutHint); + (visualProfileSuggestsWhiteLayout || + scenePlanSuggestsWhiteLayout || + (hasWhiteLayoutHint && !visualProfileSuggestsWhiteScene)); const shouldProtectWhiteScene = !isProductOnly && !shouldProtectWhiteLayout && @@ -790,42 +795,65 @@ function applyDetailReplacementScenePlanGuards( let next: ReplacementScenePlan = { ...scenePlan }; - if (shouldProtectPeople && (shouldProtectWhiteLayout || shouldProtectWhiteScene)) { + if (shouldProtectPeople && shouldProtectWhiteLayout) { next = { ...next, - generationRoute: shouldProtectWhiteLayout - ? "layout_preserve_replace" - : "people_relation_locked", + generationRoute: "layout_preserve_replace", promptPackPolicy: "forbid", textPolicy: next.textPolicy === "free" ? "preserve" : next.textPolicy, peoplePolicy: shouldProtectPeople ? "preserve_relation" : next.peoplePolicy, useModelImage: params.hasModelImage && shouldProtectPeople, modelImageReason: params.hasModelImage ? next.modelImageReason || - "当前参考页有人物/动作,但参考图为白底/浅色场景,模特只用于保持人物身份、动作和姿态,不用于重建复杂空间。" + "当前参考页有人物/动作,但参考图为白底/浅色排版页,模特只用于保持人物身份、动作和姿态,不用于重建复杂空间。" : next.modelImageReason || - "当前参考页有人物/动作,但参考图为白底/浅色场景;应保留人物数量、动作和关系,不要生成复杂家居场景。", + "当前参考页有人物/动作,但参考图为白底/浅色排版页;应保留人物数量、动作和关系,不要生成复杂家居场景。", routeReason: appendUniqueInstruction( next.routeReason, - shouldProtectWhiteLayout - ? "命中白底排版 + 人物/动作语义,锁定白底版式和人物关系,禁止套用场景词包。" - : "命中白/浅色场景 + 人物/动作语义,锁定白场景和人物关系,禁止套用复杂空间词包。", + "命中白底排版 + 人物/动作语义,锁定白底版式和人物关系,禁止套用场景词包。", 220, ), generationPrompt: appendUniqueInstruction( next.generationPrompt, - shouldProtectWhiteLayout - ? "本屏是白底/浅色详情排版页:必须保持白底或浅色底、标题区、标签、图片框、留白、人物数量/动作/关系和产品位置;不使用前置词包,不生成厨房、客厅、黑墙、吊灯或复杂软装场景。" - : "本屏是白/浅色低饱和场景页:最终结果也必须保持白色、米白、浅灰或浅木色的干净场景基调;人物数量、动作、朝向和产品关系保真,但不要生成厨房、客厅、黑墙、吊灯、复杂柜体或强软装背景。", + "本屏是白底/浅色详情排版页:必须保持白底或浅色底、标题区、标签、图片框、留白、人物数量/动作/关系和产品位置;不使用前置词包,不生成厨房、客厅、黑墙、吊灯或复杂软装场景。", ), avoidSummary: appendUniqueInstruction( next.avoidSummary, - "不要把白底/白场景人物页改成复杂室内场景,不要让前置词包覆盖白色背景,不要把人物动作变成走动路人或无关模特。", + "不要把白底排版人物页改成复杂室内场景,不要让前置词包覆盖白底版式,不要把人物动作变成走动路人或无关模特。", 280, ), whiteBackgroundLikely: true, copyImageCoupled: next.copyImageCoupled || shouldProtectWhiteLayout, }; + } else if (shouldProtectPeople && shouldProtectWhiteScene) { + next = { + ...next, + generationRoute: "people_relation_locked", + promptPackPolicy: "soft", + textPolicy: next.textPolicy === "free" ? "preserve" : next.textPolicy, + peoplePolicy: "preserve_relation", + useModelImage: params.hasModelImage, + modelImageReason: params.hasModelImage + ? next.modelImageReason || + "当前参考页为白/浅色人物场景,模特用于保持人物身份、动作和姿态,词包用于重建白场景内的产品与空间。" + : next.modelImageReason || + "当前参考页为白/浅色人物场景;需保留人物数量、动作和关系,同时按词包重建白场景内的产品与空间。", + routeReason: appendUniqueInstruction( + next.routeReason, + "命中白/浅色真实场景 + 人物/动作语义,允许词包重造场景,但必须保持白/浅色基调和原版式。", + 240, + ), + generationPrompt: appendUniqueInstruction( + next.generationPrompt, + "本屏是白/浅色真实场景页:必须使用前置工作流词包重造图片框内的产品、空间、软装和灯光,使差异清晰可见;但最终仍保持白色、米白、浅灰、浅木色的干净低饱和基调,保留人物数量、动作、朝向、互动关系、文字层级和原图片框边界。", + ), + avoidSummary: appendUniqueInstruction( + next.avoidSummary, + "不要只小幅改动原图,不要原样保留旧场景;也不要把白场景改成深色厨房、黑墙、复杂吊灯或强软装背景。", + 300, + ), + whiteBackgroundLikely: true, + }; } else if (shouldProtectPeople) { next = { ...next, @@ -885,7 +913,7 @@ function applyDetailReplacementScenePlanGuards( whiteBackgroundLikely: next.whiteBackgroundLikely || shouldProtectWhiteLayout, copyImageCoupled: true, }; - } else if (shouldProtectWhiteLayout || shouldProtectWhiteScene) { + } else if (shouldProtectWhiteLayout) { next = { ...next, generationRoute: "layout_preserve_replace", @@ -899,9 +927,30 @@ function applyDetailReplacementScenePlanGuards( ), generationPrompt: appendUniqueInstruction( next.generationPrompt, - shouldProtectWhiteLayout - ? "本屏是白底/浅色产品说明版式:不要套用空间、人群、灯光、机位等前置词包,只保留参考图白底排版并做必要局部替换。" - : "本屏是白/浅色低饱和场景:最终结果也必须保持白场景、浅色背景、干净留白和低饱和氛围,不要改成厨房、客厅、黑墙、吊灯或复杂软装场景。", + "本屏是白底/浅色产品说明版式:不要套用空间、人群、灯光、机位等前置词包,只保留参考图白底排版并做必要局部替换。", + ), + whiteBackgroundLikely: true, + }; + } else if (shouldProtectWhiteScene) { + next = { + ...next, + generationRoute: "prompt_pack_scene", + promptPackPolicy: "soft", + textPolicy: next.textPolicy === "free" ? "preserve" : next.textPolicy, + useModelImage: false, + routeReason: appendUniqueInstruction( + next.routeReason, + "命中白/浅色真实场景,允许词包重造场景,但必须保持白场景基调。", + 240, + ), + generationPrompt: appendUniqueInstruction( + next.generationPrompt, + "本屏是白/浅色低饱和真实场景:必须使用前置工作流词包重造图片框内的背景、产品、材质、软装和灯光,使新旧差异清晰;同时保持白色、米白、浅灰、浅木色的干净低饱和场景基调,不要变成深色厨房、黑墙、复杂吊灯或强软装。", + ), + avoidSummary: appendUniqueInstruction( + next.avoidSummary, + "不要只复刻原图或小幅改动,不要把白场景改成深色复杂空间,不要移动标题、标签和图片框。", + 300, ), whiteBackgroundLikely: true, }; @@ -1096,12 +1145,18 @@ export function buildHeuristicReplacementScenePlan(params: { const isProductOnly = params.replacementStrategy === "product_only"; const productImageSelection = buildDefaultProductImageSelection(params.productImageCount); const fallbackText = String(params.reason || ""); - const shouldProtectFallbackLayout = + const shouldProtectFallbackWhiteLayout = isDetailReplacement && !isProductOnly && (Boolean(params.visualProfile?.likelyWhiteLayout) || - Boolean(params.visualProfile?.likelyWhiteScene) || DETAIL_STRONG_COPY_HINT_PATTERN.test(fallbackText)); + const shouldRebuildFallbackWhiteScene = + isDetailReplacement && + !isProductOnly && + !shouldProtectFallbackWhiteLayout && + Boolean(params.visualProfile?.likelyWhiteScene); + const shouldProtectFallbackLayout = + shouldProtectFallbackWhiteLayout; const generationRoute = params.shouldReplace ? isProductOnly ? "layout_preserve_replace" @@ -1128,7 +1183,9 @@ export function buildHeuristicReplacementScenePlan(params: { ? "兜底按仅替换产品执行,不使用词包。" : isDetailReplacement ? shouldProtectFallbackLayout - ? "兜底命中白底/白场景/说明/强文案特征,按详情版式或白场景保护执行,不使用词包。" + ? "兜底命中白底说明/强文案版式,按详情版式保护执行,不使用词包。" + : shouldRebuildFallbackWhiteScene + ? "兜底命中白/浅色真实场景,允许词包软重造,但保持白场景基调。" : "兜底未命中白底、强文案或真人互动硬约束,按通用场景图执行词包场景替换。" : "兜底未检测到强约束,允许使用词包生成场景。" : params.reason, @@ -1144,7 +1201,9 @@ export function buildHeuristicReplacementScenePlan(params: { ? isProductOnly ? "严格保留参考图的场景、文案和版式,只把产品展示区中的旧产品替换成输入产品。" : shouldProtectFallbackLayout - ? "保留参考图白底/白场景、版式骨架、构图、文字和主体位置,只做必要的产品或视觉区替换。" + ? "保留参考图白底说明版式、构图、文字和主体位置,只做必要的产品或视觉区替换。" + : shouldRebuildFallbackWhiteScene + ? "保留参考图白/浅色低饱和基调与版式骨架,但按工作流目标明显重建图片框内产品、空间、软装和灯光。" : "保留参考图版式骨架、构图和主体位置,但把旧场景视觉内容按工作流目标风格重建。" : "当前参考图不满足替换条件。", cameraSummary: "保持参考图原有机位、构图和景别,不做整体重画。", @@ -1157,18 +1216,24 @@ export function buildHeuristicReplacementScenePlan(params: { styleSummary: isProductOnly ? "延续参考图原有风格、背景和氛围,不引入新的场景改造要求。" : shouldProtectFallbackLayout - ? "延续参考图原有白底/白场景、版式和图文语义,不引入新的空间场景。" + ? "延续参考图原有白底说明版式和图文语义,不引入新的空间场景。" + : shouldRebuildFallbackWhiteScene + ? "按前置词包重建为白/浅色低饱和真实场景,保持干净浅色基调,不走深色复杂空间。" : "除版式骨架外,墙面、地面、背景家具、软装、灯光、摆件和空间氛围都要按工作流风格重建。", avoidSummary: isProductOnly ? "不要改动背景、文案、按钮、图标、排版和装饰,不要重建整个场景,不要把多个产品角度同时塞进一张图。" : shouldProtectFallbackLayout - ? "不要把白底/白场景/说明/强文案页场景化,不要新增厨房/客厅/黑墙/吊灯/复杂软装,不要让文字和图片失配。" + ? "不要把白底说明/强文案页场景化,不要新增厨房/客厅/黑墙/吊灯/复杂软装,不要让文字和图片失配。" + : shouldRebuildFallbackWhiteScene + ? "不要只小幅复刻原图,不要把白场景改成深色厨房、黑墙、复杂吊灯或强软装,不要破坏原版式。" : "不要只换产品不换场景,不要把参考图原有背景物件和空间氛围原样保留下来,不要把所有输入产品角度图同时强行出现在一张图中。", generationPrompt: params.shouldReplace ? isProductOnly ? `仅替换参考图中的产品主体,严格保留其余背景、文案、排版与光影关系。${params.reason}` : isDetailReplacement && shouldProtectFallbackLayout - ? `保留参考图的版式、文案、白底/白场景/留白和图文语义,不使用前置词包;只在产品、材质、板材、原材料或视觉图片区做必要替换。${params.reason}` + ? `保留参考图的版式、文案、白底/留白和图文语义,不使用前置词包;只在产品、材质、板材、原材料或视觉图片区做必要替换。${params.reason}` + : isDetailReplacement && shouldRebuildFallbackWhiteScene + ? `当前参考图按白/浅色真实场景详情页处理:必须使用前置工作流词包明显重建图片框内旧背景、产品、空间、软装和灯光;同时保持白色、米白、浅灰、浅木色低饱和基调,保留参考图版式骨架、文字区、机位和信息层级。${params.reason}` : isDetailReplacement ? `当前参考图按通用空间/场景详情页处理:必须使用前置工作流词包重建旧背景、空间、软装、灯光和产品场景,只保留参考图版式骨架、文字区、机位和信息层级。${params.reason}` : `以工作流目标风格重建当前这一屏的场景,只保留版式、机位、文字区与留白结构。${params.reason}` @@ -1665,7 +1730,7 @@ export async function analyzeReplacementExecutionPlan(params: { ? "像素初筛已经命中白底/浅色说明页:只要画面存在标题、标签、参数、尺寸、卖点、图标、白底图片区、圆角图片区或明显留白排版,generationRoute 必须是 layout_preserve_replace 或 copy_locked_regenerate,promptPackPolicy 必须 forbid;即使包含人物/动作,也只能保留人物关系与白底版式,不允许套用厨房、客厅、灯光、软装等场景词包。" : "", !referenceVisualProfile?.likelyWhiteLayout && referenceVisualProfile?.likelyWhiteScene - ? "像素初筛已经命中白/浅色低饱和场景:如果参考图主体是白底、米白、浅灰、浅木色、干净棚拍或轻生活场景,最终结果也必须保持白/浅色场景基调;不要把它改造成深色厨房、客厅、吊灯、黑墙、复杂柜体或强软装场景。" + ? "像素初筛已经命中白/浅色低饱和真实场景:这不是白底说明页。若画面存在真实空间、人物或产品照片,promptPackPolicy 应为 soft 或 allow,必须使用前置工作流词包重建图片框内产品、空间、软装和灯光,使差异清晰可见;但最终仍保持白/浅色低饱和基调,不要改造成深色厨房、黑墙、复杂吊灯或强软装。" : "", "产品图和模特图会在后续生图阶段单独提供给生图模型使用,你这里不要尝试理解它们的具体外观,也不要要求必须使用所有输入图。", params.modelImageUrl && !isProductOnlyDetailReplacement @@ -1700,7 +1765,7 @@ export async function analyzeReplacementExecutionPlan(params: { ? "4. 判断 promptPackPolicy:纯白底参数/说明/卖点页、强图文绑定且没有真实展示动作的详情页必须 forbid;通用空间场景必须 allow;人物/自拍/聚会/承重测试/使用产品等动作展示页可以 people_relation_locked + allow/soft,含义是保留人物动作与文字层级,但允许前置词包重建背景、灯光、软装和产品场景。" : "", isDetailReplacement - ? "4a. 白场景优先规则:如果参考图是白底/浅色说明页或白/浅色低饱和场景,promptPackPolicy 必须 forbid;人物动作可以保留,但不能使用前置词包把白场景改成厨房、客厅、黑墙、吊灯或复杂软装。" + ? "4a. 白底/白场景分流规则:白底参数、规格、卖点、强文案、图标说明或纯版式页必须 forbid;白/浅色真实场景页不能 forbid,应使用 soft/allow 词包重造图片框内产品、空间、软装和灯光,但保持白/浅色低饱和基调,禁止变成深色厨房、黑墙、复杂吊灯或强软装。" : "", isDetailReplacement ? "5. 判断 textPolicy:画面中存在营销标题、卖点、编号、参数、标签且与图匹配时必须 lock 或 preserve,避免后续生图改字、混色或重排。" @@ -1949,7 +2014,7 @@ export function buildAgentReplacementPrompt(params: { : "", params.scenePlan.whiteBackgroundLikely ? shouldApplyPromptPack - ? "白底/说明图判断:是,但本屏包含人物动作/真实展示语义,允许在保留版式和文字层级的前提下按词包改造背景/产品场景。" + ? "白底/白场景判断:是,本屏按白/浅色真实场景处理;允许词包重造图片框内背景、产品、空间、软装和灯光,但必须保留白/浅色低饱和基调、原版式层和文字层级。" : "白底/白场景判断:是,禁止强行场景化;最终结果必须保持白底、浅色底或干净低饱和场景基调。" : "", params.scenePlan.copyImageCoupled @@ -2064,7 +2129,10 @@ export function buildAgentReplacementPrompt(params: { : params.scenePlan?.peoplePolicy === "preserve_relation" ? "本屏包含人物关系:必须保留人物数量、相对位置、互动关系、动作方向和出镜比例;如果参考图文字写了自拍/合照/聚会/用餐/拿手机等动作,必须让人物执行该动作,不要把多人图改成单人图、无人图或无关模特图。" : "如果参考图没有明确人物展示需求,不要凭空新增人物主体。", - params.scenePlan?.whiteBackgroundLikely && !shouldApplyPromptPack + params.scenePlan?.whiteBackgroundLikely && shouldApplyPromptPack + ? "本屏是白/浅色真实场景且词包已启用:必须让图片框内旧背景、产品、软装、灯光和空间发生清晰可见的重建;不能只微调原图或原样复刻,但也不能破坏外层白底/浅色版式和文字区。" + : "", + params.scenePlan?.whiteBackgroundLikely && !shouldApplyPromptPack ? "本屏疑似白底/白场景/浅色说明图:禁止场景化、生活化或套空间词包,只允许保留白底、浅色底、干净留白、原图片框和原说明图语义并做必要的局部替换;最终不能变成厨房、客厅、黑墙、吊灯或复杂软装场景。" : "", params.referenceGeometry