From a215ec1cbb34140a624b8154633cb6d35c88a760 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 26 Mar 2026 19:33:56 +0800 Subject: [PATCH] show agent scene system lock prompts --- app/(chat)/api/chat/route.ts | 80 +------------------ .../agents/AgentGenerationWorkspaceDarkV2.tsx | 56 +++++++++++++ lib/agents/scene-image-role-prompt.ts | 78 ++++++++++++++++++ 3 files changed, 135 insertions(+), 79 deletions(-) create mode 100644 lib/agents/scene-image-role-prompt.ts diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index 81cd9eb64..573f170ee 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -58,6 +58,7 @@ import { getAgentRequiredUploadCount, normalizeAgentPresetModeConfig, } from "@/lib/agents/preset-mode-config"; +import { buildAgentSceneImageRolePrompt } from "@/lib/agents/scene-image-role-prompt"; import { getEnabledTools } from "@/lib/ai/tools-registry"; import { isProductionEnvironment } from "@/lib/constants"; import { @@ -3168,85 +3169,6 @@ function buildAgentSceneWorkflowPrompt(params: { return lines.join("\n\n").trim(); } -function buildAgentSceneImageRolePrompt(params: { - uploadedImageCount: number; - productImageCount: number; - includeModelImage: boolean; -}) { - if (params.uploadedImageCount <= 0) { - return ""; - } - - const lines = ["【输入图片角色说明】"]; - const resolvedProductCount = Math.max( - 1, - Math.min(params.productImageCount, params.uploadedImageCount) - ); - const hasModelImage = - params.includeModelImage && params.uploadedImageCount > resolvedProductCount; - - for (let index = 0; index < params.uploadedImageCount; index += 1) { - if (index < resolvedProductCount) { - lines.push( - `图片${index + 1}:产品白底参考图,用于锁定产品的外观、比例、材质、颜色、结构和细节。` - ); - continue; - } - - if (hasModelImage && index === resolvedProductCount) { - lines.push( - `图片${index + 1}:固定模特图。若画面需要人物,必须复用这张图中的同一模特(脸型、发型、体型、肤色、穿搭风格保持一致),不得替换成其他人物。` - ); - continue; - } - - lines.push( - `图片${index + 1}:补充参考图,只用于补充产品角度、细节或辅助信息,不可替代主产品外观。` - ); - } - - lines.push( - `必须以图片1${resolvedProductCount > 1 ? `到图片${resolvedProductCount}` : ""}中的产品信息为最高优先级,严格保持同一产品。` - ); - lines.push( - "【产品锁定约束】生成结果中的产品必须与产品白底参考图保持同一产品身份,禁止改成另一款产品。" - ); - lines.push( - "严禁改变产品的主体结构、外轮廓、长宽高比例、层板数量与位置、抽屉数量与位置、柜门/开孔/隔断布局、弧度造型、柜腿样式、把手/五金位置。" - ); - lines.push( - "严禁改变产品主色、木纹方向、材质类型、表面纹理、明暗层次和细节做工;不得把深色木纹改成浅色、白色、金属色或其他颜色体系。" - ); - lines.push( - "如果场景风格与产品外观产生冲突,必须优先保留产品本体不变,只允许调整周边场景、软装、灯光、背景和道具,不允许为了适配风格而修改产品。" - ); - lines.push( - "产品白底图是产品身份锁定图,不是灵感参考图。输出时必须让人一眼看出是同一个产品,只能换场景,不能换产品本体。" - ); - lines.push("不要把多张输入图拼贴成多个主体,也不要把所有输入图元素强行同时融入结果。"); - lines.push( - hasModelImage - ? `如果当前场景有人物展示需求,只允许使用图片${resolvedProductCount + 1}里的模特;如果不需要人物,则不要强行加入人物。` - : "当前没有固定模特图,不要凭空生成强人物主体。" - ); - if (hasModelImage) { - lines.push( - "【模特锁定约束】固定模特图是人物身份锁定图。结果中出现的人物必须与模特图为同一人,不得换脸、换人、换性别、换年龄段,不得改变五官特征、脸型、发际线、发型主体、肤色、身材和辨识度。" - ); - lines.push( - "允许根据场景构图调整模特的站姿、坐姿、手势、视线和轻微表情,但必须让人一眼看出是同一个模特。" - ); - lines.push( - "只有当工作流设定或补充要求里明确要求修改模特衣服、穿搭或服装颜色时,才允许更换或调整服饰;如果没有明确要求,默认保持模特原有服装风格和穿搭关系一致。" - ); - lines.push( - "如果模特衣服被允许调整,也只能改衣服,不得借机改变模特本人;人物身份稳定性始终高于服装变化。" - ); - } - - return lines.join("\n"); -} - type AgentReplacementModePayload = NonNullable; type ReplacementReferenceDecision = { diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index 3c8480d8a..378a5576b 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -74,6 +74,7 @@ import { getAgentRequiredUploadCount, normalizeAgentPresetModeConfig, } from "@/lib/agents/preset-mode-config"; +import { buildAgentSceneImageRolePrompt } from "@/lib/agents/scene-image-role-prompt"; import type { Attachment, ChatMessage } from "@/lib/types"; import { cn, fetchWithErrorHandlers, fetcher, generateUUID } from "@/lib/utils"; import { AgentCanvasTopNavDark } from "@/components/agents/AgentCanvasTopNavDark"; @@ -931,6 +932,7 @@ export function AgentGenerationWorkspaceDarkV2({ ) ); const [isAttachmentSlotExpanded, setIsAttachmentSlotExpanded] = useState(false); + const [isScenePromptExpanded, setIsScenePromptExpanded] = useState(false); const [slotUploading, setSlotUploading] = useState(null); const [selectedModelId, setSelectedModelId] = useState( normalizedIncomingWorkspaceState.selectedModelId || "auto" @@ -1178,6 +1180,22 @@ export function AgentGenerationWorkspaceDarkV2({ : null; const attachmentSummaryReady = hasAgentUploadSlots && (requiredUploadCount === 0 || filledAttachmentCount >= requiredUploadCount); + const sceneImageRolePromptPreview = useMemo(() => { + if (!isSceneGenerationPreset || filledAttachmentCount <= 0) { + return ""; + } + + return buildAgentSceneImageRolePrompt({ + uploadedImageCount: filledAttachmentCount, + productImageCount: productSlotCount, + includeModelImage: modelSlotEnabled, + }); + }, [ + filledAttachmentCount, + isSceneGenerationPreset, + modelSlotEnabled, + productSlotCount, + ]); const detailReplacementQueryInput = detailReplacementState.query; const detailReplacementCandidates = detailReplacementState.candidates; const detailReplacementSelectedCandidateId = detailReplacementState.selectedCandidateId; @@ -4303,6 +4321,44 @@ export function AgentGenerationWorkspaceDarkV2({ {slotRequirementMessage} ) : null} + {sceneImageRolePromptPreview ? ( + +
+
+
系统预设约束
+
+ 最终发送给模型时,会自动追加产品锁定约束 + {modelSlotEnabled ? "和模特锁定约束" : ""}。 +
+
+ + + +
+ +
+
+                                {sceneImageRolePromptPreview}
+                              
+
+
+
+ ) : null} diff --git a/lib/agents/scene-image-role-prompt.ts b/lib/agents/scene-image-role-prompt.ts new file mode 100644 index 000000000..e783851af --- /dev/null +++ b/lib/agents/scene-image-role-prompt.ts @@ -0,0 +1,78 @@ +export function buildAgentSceneImageRolePrompt(params: { + uploadedImageCount: number; + productImageCount: number; + includeModelImage: boolean; +}) { + if (params.uploadedImageCount <= 0) { + return ""; + } + + const lines = ["【输入图片角色说明】"]; + const resolvedProductCount = Math.max( + 1, + Math.min(params.productImageCount, params.uploadedImageCount) + ); + const hasModelImage = + params.includeModelImage && params.uploadedImageCount > resolvedProductCount; + + for (let index = 0; index < params.uploadedImageCount; index += 1) { + if (index < resolvedProductCount) { + lines.push( + `图片${index + 1}:产品白底参考图,用于锁定产品的外观、比例、材质、颜色、结构和细节。` + ); + continue; + } + + if (hasModelImage && index === resolvedProductCount) { + lines.push( + `图片${index + 1}:固定模特图。若画面需要人物,必须复用这张图中的同一模特(脸型、发型、体型、肤色、穿搭风格保持一致),不得替换成其他人物。` + ); + continue; + } + + lines.push( + `图片${index + 1}:补充参考图,只用于补充产品角度、细节或辅助信息,不可替代主产品外观。` + ); + } + + lines.push( + `必须以图片1${resolvedProductCount > 1 ? `到图片${resolvedProductCount}` : ""}中的产品信息为最高优先级,严格保持同一产品。` + ); + lines.push( + "【产品锁定约束】生成结果中的产品必须与产品白底参考图保持同一产品身份,禁止改成另一款产品。" + ); + lines.push( + "严禁改变产品的主体结构、外轮廓、长宽高比例、层板数量与位置、抽屉数量与位置、柜门/开孔/隔断布局、弧度造型、柜腿样式、把手/五金位置。" + ); + lines.push( + "严禁改变产品主色、木纹方向、材质类型、表面纹理、明暗层次和细节做工;不得把深色木纹改成浅色、白色、金属色或其他颜色体系。" + ); + lines.push( + "如果场景风格与产品外观产生冲突,必须优先保留产品本体不变,只允许调整周边场景、软装、灯光、背景和道具,不允许为了适配风格而修改产品。" + ); + lines.push( + "产品白底图是产品身份锁定图,不是灵感参考图。输出时必须让人一眼看出是同一个产品,只能换场景,不能换产品本体。" + ); + lines.push("不要把多张输入图拼贴成多个主体,也不要把所有输入图元素强行同时融入结果。"); + lines.push( + hasModelImage + ? `如果当前场景有人物展示需求,只允许使用图片${resolvedProductCount + 1}里的模特;如果不需要人物,则不要强行加入人物。` + : "当前没有固定模特图,不要凭空生成强人物主体。" + ); + if (hasModelImage) { + lines.push( + "【模特锁定约束】固定模特图是人物身份锁定图。结果中出现的人物必须与模特图为同一人,不得换脸、换人、换性别、换年龄段,不得改变五官特征、脸型、发际线、发型主体、肤色、身材和辨识度。" + ); + lines.push( + "允许根据场景构图调整模特的站姿、坐姿、手势、视线和轻微表情,但必须让人一眼看出是同一个模特。" + ); + lines.push( + "只有当工作流设定或补充要求里明确要求修改模特衣服、穿搭或服装颜色时,才允许更换或调整服饰;如果没有明确要求,默认保持模特原有服装风格和穿搭关系一致。" + ); + lines.push( + "如果模特衣服被允许调整,也只能改衣服,不得借机改变模特本人;人物身份稳定性始终高于服装变化。" + ); + } + + return lines.join("\n"); +}