+ {!isDetailReplacementModeBuilder ? (
预览图
@@ -449,6 +562,7 @@ export function AgentPresetDialog({
+ ) : null}
{entityNameLabel}
@@ -465,7 +579,7 @@ export function AgentPresetDialog({
-
+
+ {type === "image" ? (
+
+
模式类型
+
+
+ ) : null}
+ {type === "image" ? (
+
+
+ 当前模式:{modeConfig.modeType === "legacy" ? "旧版兼容" : selectedImageModeMeta.label}
+
+
+ {modeConfig.modeType === "legacy"
+ ? "当前模板沿用旧版多槽位执行逻辑。切换到新模式后,将按新的业务流程配置。"
+ : selectedImageModeMeta.description}
+
+
+ ) : null}
+
+ {type === "image" && modeConfig.modeType !== "legacy" ? (
+
+
+
+ {selectedImageModeMeta.label}配置
+
+
+ {isSceneModeBuilder
+ ? "第一步提示词组合 + 自定义提示词 + 输入图片 + 模板参考图,共同组成生图输入。"
+ : isDetailReplacementModeBuilder
+ ? "详情替换会使用 SPU 获取到的详情参考图逐张替换,不走原来的详情页生成规划流程。"
+ : "SKU替换会按输入图与参考图一对一关系执行替换,并保留后续批量下载与文字编辑能力。"}
+
+
+
+ {isSceneModeBuilder ? (
+
+
+
产品图槽位数量
+
+
+
+
+
+
+
模特图槽位
+
+ 开启后会额外保留 1 个模特图槽位
+
+
+
+ setModeConfig((prev) => ({
+ ...prev,
+ modeType: "scene_generation",
+ scene: {
+ ...prev.scene,
+ includeModelSlot: checked,
+ },
+ }))
+ }
+ />
+
+
+
+
+
允许模板参考图
+
+ 不强制,但可以预置风格参考图
+
+
+
+ setModeConfig((prev) => ({
+ ...prev,
+ modeType: "scene_generation",
+ scene: {
+ ...prev.scene,
+ allowTemplateReferenceImages: checked,
+ },
+ }))
+ }
+ />
+
+
+
+
+
允许用户切换模型
+
+ 关闭后第二步直接锁定模板模型
+
+
+
+ setModeConfig((prev) => ({
+ ...prev,
+ modeType: "scene_generation",
+ scene: {
+ ...prev.scene,
+ allowModelOverride: checked,
+ },
+ }))
+ }
+ />
+
+
+
+ ) : (
+
+
+
+
+ 产品图槽位数量
+
+
+
+
+
+
+
模特图槽位
+
+ 开启后额外保留 1 个模特图槽位
+
+
+
+ setModeConfig((prev) => ({
+ ...prev,
+ modeType: isDetailReplacementModeBuilder
+ ? "detail_replacement"
+ : "sku_replacement",
+ detail: {
+ ...prev.detail,
+ includeModelSlot: isDetailReplacementModeBuilder
+ ? checked
+ : prev.detail.includeModelSlot,
+ },
+ sku: {
+ ...prev.sku,
+ includeModelSlot: isSkuReplacementModeBuilder
+ ? checked
+ : prev.sku.includeModelSlot,
+ },
+ }))
+ }
+ />
+
+
+
+
+
+
启用后续文字编辑
+
+ 结果图生成后,可调用现有文字编辑能力逐屏修改。
+
+
+
+ setModeConfig((prev) => ({
+ ...prev,
+ modeType: isDetailReplacementModeBuilder
+ ? "detail_replacement"
+ : "sku_replacement",
+ detail: {
+ ...prev.detail,
+ enableTextEditing: isDetailReplacementModeBuilder
+ ? checked
+ : prev.detail.enableTextEditing,
+ },
+ sku: {
+ ...prev.sku,
+ enableTextEditing: isSkuReplacementModeBuilder
+ ? checked
+ : prev.sku.enableTextEditing,
+ },
+ }))
+ }
+ />
+
+
+
+ {isDetailReplacementModeBuilder
+ ? "详情替换模式不需要上传模板预览图,第二步会通过 SPU 获取详情参考图,再把输入图逐张替换到详情参考图中。"
+ : "SKU替换模式会把用户选中的白底图与参考图建立一对一关系,后续在第二步专用工作台中执行替换。"}
+
+
+ )}
+
+ ) : null}
+
+ {isStructuredImageModeBuilder ? (
+
+
+
+ {selectedImageModeMeta.label}提示词
+
+
+ {isSceneModeBuilder
+ ? "这里定义这个模式的基础风格、镜头、氛围和结构规则。第二步会自动叠加第一步组合提示词与用户补充提示词。"
+ : isDetailReplacementModeBuilder
+ ? "这里定义详情替换时的统一替换规则,例如保留版式、替换主体、保证细节一致等。"
+ : "这里定义 SKU 替换时的一对一替换规则,例如保留场景、替换主体、统一光影和风格。"}
+
+
+
+
+ ) : (
@@ -708,6 +1243,7 @@ export function AgentPresetDialog({
))}
+ )}
diff --git a/components/agents/AgentWorkflowMaterialPicker.tsx b/components/agents/AgentWorkflowMaterialPicker.tsx
index ed168c51d..4f4d98b55 100644
--- a/components/agents/AgentWorkflowMaterialPicker.tsx
+++ b/components/agents/AgentWorkflowMaterialPicker.tsx
@@ -5,12 +5,16 @@ import { Check, ImageIcon, Loader2, RefreshCw, Search } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
-import type { AgentWorkflowMaterialSearchResult } from "@/types/agent";
+import type {
+ AgentWorkflowMaterialQueryType,
+ AgentWorkflowMaterialSearchResult,
+} from "@/types/agent";
interface AgentWorkflowMaterialPickerProps {
title: string;
description: string;
value: string;
+ queryMode: AgentWorkflowMaterialQueryType;
placeholder: string;
buttonLabel: string;
searchNote?: string;
@@ -21,7 +25,9 @@ interface AgentWorkflowMaterialPickerProps {
result?: AgentWorkflowMaterialSearchResult | null;
selectedImageIds: string[];
onValueChange: (value: string) => void;
+ onQueryModeChange: (value: AgentWorkflowMaterialQueryType) => void;
onSearch: () => void;
+ onSkuOptionSelect?: (skuName: string) => void;
onSelectedImageIdsChange: (next: string[]) => void;
theme?: "light" | "dark";
}
@@ -30,6 +36,7 @@ export function AgentWorkflowMaterialPicker({
title,
description,
value,
+ queryMode,
placeholder,
buttonLabel,
searchNote,
@@ -40,7 +47,9 @@ export function AgentWorkflowMaterialPicker({
result,
selectedImageIds,
onValueChange,
+ onQueryModeChange,
onSearch,
+ onSkuOptionSelect,
onSelectedImageIdsChange,
theme = "light",
}: AgentWorkflowMaterialPickerProps) {
@@ -81,6 +90,30 @@ export function AgentWorkflowMaterialPicker({
+
+ {(["sku", "spu"] as const).map((mode) => (
+
+ ))}
+
+
命中:{result.matchedSku || result.query}
+ {result.matchedSpu ? (
+
+ SPU:{result.matchedSpu}
+
+ ) : null}
{result.productModel ? (
-
- {result.images.map((image) => {
+ {result.skuOptions?.length ? (
+
+ {result.skuOptions.map((option) => (
+
+
+
+ {option.previewImageUrl ? (
+

+ ) : (
+
+
+
+ )}
+
+
+
+ {option.skuName}
+
+
+ {option.hasWhiteBackground
+ ? `已有 ${option.whiteBackgroundCount} 张白底图`
+ : "暂无白底图"}
+
+
+
+
+
+ ))}
+
+ ) : null}
+
+ {result.images?.length ? (
+
+ {result.images.map((image) => {
const selected = selectedSet.has(image.id);
return (
);
- })}
-
+ })}
+
+ ) : null}
)}
- {result?.images?.length ? (
+ {(result?.images?.length || result?.skuOptions?.length) ? (
- 已选中的图片会在下一步优先带入右侧“生图模式”槽位,你也可以进入后继续替换。
+ {result?.skuOptions?.length
+ ? "当前是 SPU 命中结果,请先选择具体 SKU,再继续拉取白底图。"
+ : "已选中的图片会在下一步优先带入右侧“生图模式”槽位,你也可以进入后继续替换。"}
) : null}
diff --git a/components/agents/AgentWorkflowMaterialSelectionPanel.tsx b/components/agents/AgentWorkflowMaterialSelectionPanel.tsx
index 2fee387b4..f29a817c7 100644
--- a/components/agents/AgentWorkflowMaterialSelectionPanel.tsx
+++ b/components/agents/AgentWorkflowMaterialSelectionPanel.tsx
@@ -6,7 +6,10 @@ import { AgentWorkflowMaterialPicker } from "@/components/agents/AgentWorkflowMa
import { createEmptyAgentWorkflowMaterialSelection } from "@/lib/agents/workflow-material-config";
import { cn } from "@/lib/utils";
import type { AgentWorkflowInputs } from "@/components/agents/AgentWorkflowPanel";
-import type { AgentWorkflowMaterialSelections } from "@/types/agent";
+import type {
+ AgentWorkflowMaterialQueryType,
+ AgentWorkflowMaterialSelections,
+} from "@/types/agent";
interface AgentWorkflowMaterialSelectionPanelProps {
inputs: AgentWorkflowInputs;
@@ -38,6 +41,13 @@ export function AgentWorkflowMaterialSelectionPanel({
primary: { isLoading: false, errorText: null },
secondary: { isLoading: false, errorText: null },
});
+ const [materialSearchMode, setMaterialSearchMode] = useState<{
+ primary: AgentWorkflowMaterialQueryType;
+ secondary: AgentWorkflowMaterialQueryType;
+ }>({
+ primary: "sku",
+ secondary: "sku",
+ });
const updateMaterialSearchState = (
source: "primary" | "secondary",
@@ -77,8 +87,15 @@ export function AgentWorkflowMaterialSelectionPanel({
}
};
- const handleSearchMaterials = async (source: "primary" | "secondary") => {
- const keyword = (source === "primary" ? inputs.primarySku : inputs.secondarySku).trim();
+ const handleSearchMaterials = async (
+ source: "primary" | "secondary",
+ options?: {
+ keyword?: string;
+ queryType?: AgentWorkflowMaterialQueryType;
+ }
+ ) => {
+ const keyword = String(options?.keyword || (source === "primary" ? inputs.primarySku : inputs.secondarySku)).trim();
+ const queryType = options?.queryType || materialSearchMode[source];
if (!keyword) {
updateMaterialSearchState(source, { errorText: "请先输入搜索关键词" });
return;
@@ -87,7 +104,7 @@ export function AgentWorkflowMaterialSelectionPanel({
try {
updateMaterialSearchState(source, { isLoading: true, errorText: null });
const response = await fetch(
- `/api/agent-workflow/material-search?keyword=${encodeURIComponent(keyword)}&source=${source}`,
+ `/api/agent-workflow/material-search?keyword=${encodeURIComponent(keyword)}&source=${source}&queryType=${queryType}`,
{ cache: "no-store" }
);
const result = await response.json().catch(() => ({}));
@@ -144,9 +161,14 @@ export function AgentWorkflowMaterialSelectionPanel({
title="主 SKU / SPU"
description="主商品图会优先自动填充到“产品图1”;如果没有搭配图,再继续补到“产品图2”。"
value={inputs.primarySku}
- placeholder="输入主 SKU / SPU"
- buttonLabel="搜索主图"
- searchNote="当前上游接口实际按可命中的 SKU 查图;若提供了 SPU 接口,后续可直接无缝切换。"
+ queryMode={materialSearchMode.primary}
+ placeholder={materialSearchMode.primary === "spu" ? "输入主 SPU" : "输入主 SKU"}
+ buttonLabel={materialSearchMode.primary === "spu" ? "查SKU列表" : "搜索主图"}
+ searchNote={
+ materialSearchMode.primary === "spu"
+ ? "按 SPU 搜索时会先返回该 SPU 下的 SKU 列表,点选某个 SKU 后再继续拉取白底图。"
+ : "按 SKU 搜索会直接拉取白底图,适合你已经知道具体 SKU 的情况。"
+ }
disabled={disabled}
disabledHint={disabledHint}
isLoading={materialSearchState.primary.isLoading}
@@ -154,9 +176,29 @@ export function AgentWorkflowMaterialSelectionPanel({
result={materials.primary.result}
selectedImageIds={materials.primary.selectedImageIds}
onValueChange={(value) => handleMaterialInputChange("primary", value)}
+ onQueryModeChange={(nextMode) =>
+ setMaterialSearchMode((prev) => ({
+ ...prev,
+ primary: nextMode,
+ }))
+ }
onSearch={() => {
void handleSearchMaterials("primary");
}}
+ onSkuOptionSelect={(skuName) => {
+ onInputsChange({
+ ...inputs,
+ primarySku: skuName,
+ });
+ setMaterialSearchMode((prev) => ({
+ ...prev,
+ primary: "sku",
+ }));
+ void handleSearchMaterials("primary", {
+ keyword: skuName,
+ queryType: "sku",
+ });
+ }}
theme={theme}
onSelectedImageIdsChange={(next) =>
onMaterialsChange({
@@ -173,9 +215,14 @@ export function AgentWorkflowMaterialSelectionPanel({
title="搭配 SKU"
description="搭配商品图会优先自动填充到“产品图2”;品牌模特图会在上方单独选择并进入模特图槽位。"
value={inputs.secondarySku}
- placeholder="输入搭配 SKU"
- buttonLabel="搜索搭配图"
- searchNote="选中的搭配图会进入第二个产品槽位,你也可以在下面继续手动替换。"
+ queryMode={materialSearchMode.secondary}
+ placeholder={materialSearchMode.secondary === "spu" ? "输入搭配 SPU" : "输入搭配 SKU"}
+ buttonLabel={materialSearchMode.secondary === "spu" ? "查SKU列表" : "搜索搭配图"}
+ searchNote={
+ materialSearchMode.secondary === "spu"
+ ? "按 SPU 搜索时会先返回该 SPU 下的 SKU 列表,再选具体 SKU 获取白底图。"
+ : "选中的搭配图会进入第二个产品槽位,你也可以在下面继续手动替换。"
+ }
disabled={disabled}
disabledHint={disabledHint}
isLoading={materialSearchState.secondary.isLoading}
@@ -183,9 +230,29 @@ export function AgentWorkflowMaterialSelectionPanel({
result={materials.secondary.result}
selectedImageIds={materials.secondary.selectedImageIds}
onValueChange={(value) => handleMaterialInputChange("secondary", value)}
+ onQueryModeChange={(nextMode) =>
+ setMaterialSearchMode((prev) => ({
+ ...prev,
+ secondary: nextMode,
+ }))
+ }
onSearch={() => {
void handleSearchMaterials("secondary");
}}
+ onSkuOptionSelect={(skuName) => {
+ onInputsChange({
+ ...inputs,
+ secondarySku: skuName,
+ });
+ setMaterialSearchMode((prev) => ({
+ ...prev,
+ secondary: "sku",
+ }));
+ void handleSearchMaterials("secondary", {
+ keyword: skuName,
+ queryType: "sku",
+ });
+ }}
theme={theme}
onSelectedImageIdsChange={(next) =>
onMaterialsChange({
diff --git a/components/agents/AgentWorkflowPanelDarkV2.tsx b/components/agents/AgentWorkflowPanelDarkV2.tsx
index 2b65d5c88..95a2b9bdf 100644
--- a/components/agents/AgentWorkflowPanelDarkV2.tsx
+++ b/components/agents/AgentWorkflowPanelDarkV2.tsx
@@ -389,26 +389,14 @@ export function AgentWorkflowPanelDarkV2({