From a17d2da133dd0b1052ea4e5e7c85e01a3c83602e Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 2 Jun 2026 21:55:22 +0800 Subject: [PATCH] fix: apply prompt packs to action detail cards --- .../api/chat/_lib/detail-replacement.ts | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/app/(chat)/api/chat/_lib/detail-replacement.ts b/app/(chat)/api/chat/_lib/detail-replacement.ts index 21155b94f..d4690833b 100644 --- a/app/(chat)/api/chat/_lib/detail-replacement.ts +++ b/app/(chat)/api/chat/_lib/detail-replacement.ts @@ -236,11 +236,16 @@ export async function normalizeReplacementResultImage(params: { const shouldCropTallComposite = Boolean(targetAspectRatio && generatedAspectRatio) && Number(generatedAspectRatio) < Number(targetAspectRatio) * 0.78; + const shouldFillSceneCanvas = + params.scenePlan?.promptPackPolicy !== "forbid" && + (params.scenePlan?.generationRoute === "prompt_pack_scene" || + params.scenePlan?.generationRoute === "people_relation_locked"); const shouldPreserveFullGeneratedCanvas = - params.scenePlan?.whiteBackgroundLikely === true || - params.scenePlan?.textPolicy === "lock" || - params.scenePlan?.generationRoute === "copy_locked_regenerate" || - params.scenePlan?.generationRoute === "layout_preserve_replace"; + !shouldFillSceneCanvas && + (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 @@ -349,7 +354,7 @@ const DETAIL_WHITE_LAYOUT_HINT_PATTERN = const DETAIL_STRONG_COPY_HINT_PATTERN = /白底|浅色底|产品说明|参数|规格|尺寸|卖点|标签|说明图|信息图|海报|版式|排版|文字|标题|编号|标注|材质标注|工艺标注|代名词|典雅|高贵/i; const DETAIL_PEOPLE_ACTION_HINT_PATTERN = - /自拍|合照|拍照|拿手机|手机自拍|聚会|好友|朋友|闺蜜|夫妻|情侣|家庭|亲子|陪伴|举杯|互动|模特|人物|人像|多人|人群|男士|女士|男人|女人|男生|女生|小孩|儿童|宝宝|顾客|吃饭的人|用餐的人|正在吃饭|坐在|站在|走动|行走|手持|试坐|倚靠/i; + /自拍|合照|拍照|拿手机|手机自拍|聚会|好友|朋友|闺蜜|夫妻|情侣|家庭|亲子|陪伴|举杯|互动|模特|人物|人像|多人|人群|男士|女士|男人|女人|男生|女生|小孩|儿童|宝宝|顾客|吃饭的人|用餐的人|正在吃饭|坐在|坐着|站在|走动|行走|手持|试坐|倚靠|承重|受力|测试|稳定|稳固|使用产品|产品使用|体验/i; export type ReplacementScenePlanningRuntime = | { @@ -615,15 +620,15 @@ function inferDetailGenerationRoute(params: { if (params.isProductOnly) { return "layout_preserve_replace"; } + if (params.containsMultiplePeople || params.containsPeople) { + return "people_relation_locked"; + } if (params.whiteBackgroundLikely) { return "layout_preserve_replace"; } if (params.copyImageCoupled || params.hasImportantText) { return "copy_locked_regenerate"; } - if (params.containsMultiplePeople || params.containsPeople) { - return "people_relation_locked"; - } return "prompt_pack_scene"; } @@ -732,7 +737,7 @@ function applyDetailReplacementScenePlanGuards( next = { ...next, generationRoute: "people_relation_locked", - promptPackPolicy: "forbid", + promptPackPolicy: "allow", textPolicy: next.textPolicy === "free" ? "preserve" : next.textPolicy, peoplePolicy: "preserve_relation", useModelImage: params.hasModelImage, @@ -743,16 +748,16 @@ function applyDetailReplacementScenePlanGuards( "当前参考页存在人物/自拍/互动需求;未提供用户模特图时仍需保留参考页人物数量、动作和关系,但不要复刻特定身份。", routeReason: appendUniqueInstruction( next.routeReason, - "命中人物/自拍/互动语义,禁止按普通场景词包重建。", + "命中人物/自拍/互动语义,锁定人物关系与文案层级,但允许前置词包重建背景/产品场景。", 220, ), generationPrompt: appendUniqueInstruction( next.generationPrompt, - "如果参考图文字或画面包含自拍、合照、聚会、用餐、互动、拿手机等动作语义,人物数量、相对位置、动作方向和出镜比例是硬约束;结果必须出现对应人物动作,不允许生成空场景。", + "如果参考图文字或画面包含自拍、合照、聚会、用餐、互动、拿手机、承重测试、使用产品等动作语义,人物数量、相对位置、动作方向和出镜比例是硬约束;同时必须按前置工作流词包替换旧背景、空间氛围、灯光、软装和产品场景,不允许只复刻参考图原背景。", ), avoidSummary: appendUniqueInstruction( next.avoidSummary, - "不要把有人物/自拍动作的参考页生成成无人物空间,不要把多人压成单人,不要忽略参考页文字描述的人物动作。", + "不要把有人物/自拍动作的参考页生成成无人物空间,不要把多人压成单人,不要忽略参考页文字描述的人物动作,也不要只原样保留参考图旧场景。", 260, ), }; @@ -1559,7 +1564,7 @@ export async function analyzeReplacementExecutionPlan(params: { ? `像素初筛:nearWhite=${referenceVisualProfile.nearWhitePixelRatio.toFixed(2)}, lightLowSaturation=${referenceVisualProfile.lightLowSaturationPixelRatio.toFixed(2)}, dark=${referenceVisualProfile.darkPixelRatio.toFixed(2)}, likelyWhiteLayout=${referenceVisualProfile.likelyWhiteLayout ? "true" : "false"}。${referenceVisualProfile.reason}` : "", referenceVisualProfile?.likelyWhiteLayout - ? "像素初筛已经命中白底/浅色说明页:除非你在画面中看到明确的大面积真实生活场景,否则 generationRoute 必须是 layout_preserve_replace 或 copy_locked_regenerate,promptPackPolicy 必须 forbid。" + ? "像素初筛已经命中白底/浅色说明页:如果画面是纯产品参数/说明/卖点排版且没有人物动作或真实使用展示,generationRoute 必须是 layout_preserve_replace 或 copy_locked_regenerate,promptPackPolicy 必须 forbid;如果画面包含人物、自拍、承重测试、使用产品、真实展示动作或明确生活场景,可以选择 people_relation_locked,并把 promptPackPolicy 设为 allow 或 soft。" : "", "产品图和模特图会在后续生图阶段单独提供给生图模型使用,你这里不要尝试理解它们的具体外观,也不要要求必须使用所有输入图。", params.modelImageUrl && !isProductOnlyDetailReplacement @@ -1591,7 +1596,7 @@ export async function analyzeReplacementExecutionPlan(params: { ? "3. 判断 generationRoute:prompt_pack_scene=通用场景图且可吃词包;layout_preserve_replace=锁定版式/白底/产品说明/材质说明,只做有限替换;copy_locked_regenerate=文案与画面强绑定,必须锁文案;people_relation_locked=多人/模特/互动关系图,必须锁人物关系。详情替换不允许 direct_passthrough。" : "3. 提炼 sceneSummary、cameraSummary、placementSummary、preserveSummary、styleSummary、avoidSummary。", isDetailReplacement - ? "4. 判断 promptPackPolicy:只有通用空间场景且没有强文案、白底、多人关系、自拍/动作文字、词图强绑定限制时才 allow;白底图、产品说明图、强文案图、多人关系图、自拍/人物动作图、图文匹配强的详情页必须 forbid。" + ? "4. 判断 promptPackPolicy:纯白底参数/说明/卖点页、强图文绑定且没有真实展示动作的详情页必须 forbid;通用空间场景必须 allow;人物/自拍/聚会/承重测试/使用产品等动作展示页可以 people_relation_locked + allow/soft,含义是保留人物动作与文字层级,但允许前置词包重建背景、灯光、软装和产品场景。" : "", isDetailReplacement ? "5. 判断 textPolicy:画面中存在营销标题、卖点、编号、参数、标签且与图匹配时必须 lock 或 preserve,避免后续生图改字、混色或重排。" @@ -1605,17 +1610,17 @@ export async function analyzeReplacementExecutionPlan(params: { isProductOnlyDetailReplacement ? "4. 输出 generationPrompt:这是一段给后续生图模型的本屏生成指令,要求只替换产品主体,保留其余背景、文案、排版、装饰和光影完全不变。" : isDetailReplacement - ? "8. 输出 generationPrompt:这是一段给后续生图模型的本屏生成指令。若 promptPackPolicy=forbid,必须明确“不使用前置词包,参考图语义优先”;若 textPolicy=lock,必须明确文字内容、颜色、层级和位置不可改;若 peoplePolicy=preserve_relation,必须明确人物数量、关系、动作和身份约束。" + ? "8. 输出 generationPrompt:这是一段给后续生图模型的本屏生成指令。若 promptPackPolicy=forbid,必须明确“不使用前置词包,参考图语义优先”;若 promptPackPolicy=allow/soft 且 peoplePolicy=preserve_relation,必须明确“保留人物数量/动作/关系与文字层级,同时使用前置词包重建背景/产品场景”;若 textPolicy=lock,必须明确文字内容、颜色、层级和位置不可改。" : "4. 输出 generationPrompt:这是一段给后续生图模型的本屏生成指令,描述这一屏需要的场景、机位、主体位置、周边环境重建方向以及必须保留的内容。", isProductOnlyDetailReplacement ? "5. sceneSummary、styleSummary 和 generationPrompt 必须强调‘只替换产品,不动其他内容’,不要引导重建场景。" : isDetailReplacement - ? "9. 如果 generationRoute 不是 prompt_pack_scene,sceneSummary、styleSummary 和 generationPrompt 必须优先服务参考图原有语义、版式、文案和人物关系,不要强行套前置词包。" + ? "9. 如果 generationRoute 是 layout_preserve_replace 或 copy_locked_regenerate,sceneSummary、styleSummary 和 generationPrompt 必须优先服务参考图原有语义、版式和文案;如果 generationRoute 是 people_relation_locked 且 promptPackPolicy=allow/soft,则必须同时服务人物动作保真和前置词包场景改造。" : "5. sceneSummary、styleSummary 和 generationPrompt 必须优先描述“按前置工作流重建后的目标场景”,而不是只复述参考图当前已有场景。", isProductOnlyDetailReplacement ? "6. avoidSummary 里要明确避免改背景、改文字、改按钮、改图标、改版式、改留白、改装饰、改光影氛围。" : isDetailReplacement - ? "10. 如果前置工作流风格与当前参考图原场景冲突,只有 promptPackPolicy=allow 时才以前置词包为主;其他情况必须以参考图语义和版式为主。" + ? "10. 如果前置工作流风格与当前参考图原场景冲突,promptPackPolicy=allow 时以前置词包为主;promptPackPolicy=soft 时以参考图人物/文案关系为骨架、以前置词包改造背景与产品场景;promptPackPolicy=forbid 时以参考图语义和版式为主。" : "6. 如果前置工作流风格与当前参考图原场景冲突,必须保留版式骨架,但放弃原场景物件本身,以前置工作流指定的空间风格、软装、灯光和氛围为准。", isProductOnlyDetailReplacement ? "7. 不要根据用户提示词或工作流风格改造场景,只服务于产品主体替换。" @@ -1752,11 +1757,13 @@ function shouldApplyPromptPackToDetailReplacement(params: { if ( scenePlan.generationRoute === "direct_passthrough" || scenePlan.generationRoute === "layout_preserve_replace" || - scenePlan.generationRoute === "copy_locked_regenerate" || - scenePlan.generationRoute === "people_relation_locked" + scenePlan.generationRoute === "copy_locked_regenerate" ) { return scenePlan.promptPackPolicy === "allow"; } + if (scenePlan.generationRoute === "people_relation_locked") { + return true; + } return true; } @@ -1828,7 +1835,9 @@ export function buildAgentReplacementPrompt(params: { ? `路由理由:${params.scenePlan.routeReason}` : "", params.scenePlan.whiteBackgroundLikely - ? "白底/说明图判断:是,禁止强行场景化。" + ? shouldApplyPromptPack + ? "白底/说明图判断:是,但本屏包含人物动作/真实展示语义,允许在保留版式和文字层级的前提下按词包改造背景/产品场景。" + : "白底/说明图判断:是,禁止强行场景化。" : "", params.scenePlan.copyImageCoupled ? "图文绑定判断:是,文案与画面语义必须保持匹配。" @@ -1936,7 +1945,7 @@ export function buildAgentReplacementPrompt(params: { : params.scenePlan?.peoplePolicy === "preserve_relation" ? "本屏包含人物关系:必须保留人物数量、相对位置、互动关系、动作方向和出镜比例;如果参考图文字写了自拍/合照/聚会/用餐/拿手机等动作,必须让人物执行该动作,不要把多人图改成单人图、无人图或无关模特图。" : "如果参考图没有明确人物展示需求,不要凭空新增人物主体。", - params.scenePlan?.whiteBackgroundLikely + params.scenePlan?.whiteBackgroundLikely && !shouldApplyPromptPack ? "本屏疑似白底/说明图:禁止场景化、生活化或套空间词包,只允许保留白底/说明图语义并做必要的局部替换。" : "", params.referenceGeometry