show agent scene system lock prompts
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-26 19:33:56 +08:00
parent bd1dbc4406
commit a215ec1cbb
3 changed files with 135 additions and 79 deletions

View File

@ -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<PostRequestBody["agentModePayload"]>;
type ReplacementReferenceDecision = {

View File

@ -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<number | null>(null);
const [selectedModelId, setSelectedModelId] = useState<string>(
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}
</div>
) : null}
{sceneImageRolePromptPreview ? (
<Collapsible
open={isScenePromptExpanded}
onOpenChange={setIsScenePromptExpanded}
className="rounded-[22px] border border-white/10 bg-black/25"
>
<div className="flex flex-wrap items-start justify-between gap-3 px-4 py-3">
<div>
<div className="text-sm font-medium text-white">系统预设约束</div>
<div className="mt-1 text-xs leading-6 text-white/55">
最终发送给模型时,会自动追加产品锁定约束
{modelSlotEnabled ? "和模特锁定约束" : ""}。
</div>
</div>
<CollapsibleTrigger asChild>
<Button
type="button"
variant="outline"
className="h-9 rounded-[18px] border-white/12 bg-black/20 px-3 text-white/80 hover:border-white/22 hover:bg-white/[0.06] hover:text-white"
>
{isScenePromptExpanded ? "收起约束" : "查看约束"}
{isScenePromptExpanded ? (
<ChevronUp className="ml-2 h-4 w-4" />
) : (
<ChevronDown className="ml-2 h-4 w-4" />
)}
</Button>
</CollapsibleTrigger>
</div>
<CollapsibleContent>
<div className="border-t border-white/8 px-4 py-4">
<pre className="whitespace-pre-wrap text-xs leading-6 text-white/78">
{sceneImageRolePromptPreview}
</pre>
</div>
</CollapsibleContent>
</Collapsible>
) : null}
</div>
</div>
</div>

View File

@ -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");
}