feat: improve replacement workflow interactions
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-21 18:36:12 +08:00
parent ea06999769
commit 5c10380666
3 changed files with 358 additions and 66 deletions

View File

@ -279,6 +279,70 @@ function formatCandidateMeta(candidate: AgentModeReferenceCandidate, target: "de
return parts.join(" · ");
}
function getCandidatePreviewUrl(
candidate: AgentModeReferenceCandidate,
target: "detail" | "sku"
) {
const targetUrls =
target === "detail" ? candidate.detailImageUrls || [] : candidate.skuImageUrls || [];
return (
targetUrls[0] ||
candidate.mainImageUrls[0] ||
candidate.allImageUrls[0] ||
null
);
}
type ReplacementCardStatus = "waiting" | "queued" | "generating" | "completed" | "missing";
function getReplacementCardStatus(params: {
index: number;
resultUrl: string | null;
generatedCount: number;
isGenerating: boolean;
}) : ReplacementCardStatus {
if (params.resultUrl) {
return "completed";
}
if (params.isGenerating) {
return params.index === params.generatedCount ? "generating" : "queued";
}
if (params.generatedCount > 0 && params.index >= params.generatedCount) {
return "missing";
}
return "waiting";
}
function getReplacementCardStatusMeta(status: ReplacementCardStatus) {
switch (status) {
case "completed":
return {
label: "已生成",
className: "border-emerald-400/30 bg-emerald-500/[0.10] text-emerald-200",
};
case "generating":
return {
label: "生成中",
className: "border-sky-400/30 bg-sky-500/[0.10] text-sky-200",
};
case "queued":
return {
label: "排队中",
className: "border-white/12 bg-white/[0.05] text-white/60",
};
case "missing":
return {
label: "未回传",
className: "border-amber-400/30 bg-amber-500/[0.10] text-amber-200",
};
default:
return {
label: "待生成",
className: "border-white/12 bg-white/[0.05] text-white/60",
};
}
}
export function AgentGenerationWorkspaceDarkV2({
chatId,
projectId,
@ -360,6 +424,7 @@ export function AgentGenerationWorkspaceDarkV2({
isLoading: false,
lastQuery: null,
});
const [activeDetailReferenceCardId, setActiveDetailReferenceCardId] = useState<string | null>(null);
const [skuReplacementSearchState, setSkuReplacementSearchState] = useState<{
isLoading: boolean;
lastQuery: string | null;
@ -367,6 +432,7 @@ export function AgentGenerationWorkspaceDarkV2({
isLoading: false,
lastQuery: null,
});
const [activeSkuReferenceCardId, setActiveSkuReferenceCardId] = useState<string | null>(null);
const [imageParams, setImageParams] = useState<ImageGenerationParams>({
aspectRatio: normalizedIncomingWorkspaceState.imageParams?.aspectRatio || "auto",
imageSize: normalizedIncomingWorkspaceState.imageParams?.imageSize || "2K",
@ -770,6 +836,42 @@ export function AgentGenerationWorkspaceDarkV2({
const latestReplacementResultUrls = latestGeneratedGroup?.imageUrls || [];
const displayedResultUrl = selectedResultUrl || latestGeneratedGroup?.imageUrls[0] || null;
const isGenerating = status === "streaming" || status === "submitted";
const detailResultCards = useMemo(
() =>
detailReplacementReferenceCards.map((card, index) => {
const resultUrl = latestReplacementResultUrls[index] || null;
return {
...card,
index,
resultUrl,
status: getReplacementCardStatus({
index,
resultUrl,
generatedCount: latestReplacementResultUrls.length,
isGenerating,
}),
};
}),
[detailReplacementReferenceCards, isGenerating, latestReplacementResultUrls]
);
const skuResultCards = useMemo(
() =>
skuReplacementReferenceCards.map((card, index) => {
const resultUrl = latestReplacementResultUrls[index] || null;
return {
...card,
index,
resultUrl,
status: getReplacementCardStatus({
index,
resultUrl,
generatedCount: latestReplacementResultUrls.length,
isGenerating,
}),
};
}),
[isGenerating, latestReplacementResultUrls, skuReplacementReferenceCards]
);
const sceneResultCardCount = Math.max(
allGeneratedImages.length,
imageParams.imageCount || 1,
@ -897,6 +999,22 @@ export function AgentGenerationWorkspaceDarkV2({
selectedAgentPresetId,
]
);
useEffect(() => {
setActiveDetailReferenceCardId((current) =>
detailReplacementReferenceCards.some((card) => card.id === current)
? current
: detailReplacementReferenceCards[0]?.id || null
);
}, [detailReplacementReferenceCards]);
useEffect(() => {
setActiveSkuReferenceCardId((current) =>
skuReplacementReferenceCards.some((card) => card.id === current)
? current
: skuReplacementReferenceCards[0]?.id || null
);
}, [skuReplacementReferenceCards]);
const activeToolStepIndex = Math.max(
toolSteps.findIndex((step) => step.key === activeToolStep),
0
@ -2220,29 +2338,66 @@ export function AgentGenerationWorkspaceDarkV2({
{detailReplacementCandidates.length > 0 ? (
<div className="mb-3">
<div className="mb-2 text-xs text-white/42">
候选整套素材 {detailReplacementCandidates.length} 套
<div className="mb-2 flex items-center justify-between gap-3">
<div className="text-xs text-white/42">
候选整套素材 {detailReplacementCandidates.length} 套
</div>
<div className="text-[11px] text-emerald-200/70">
默认载入最新一套,可随时切换
</div>
</div>
<div className="agent-dark-scrollbar flex gap-2 overflow-x-auto pb-1">
{detailReplacementCandidates.map((candidate) => {
const isSelected = detailReplacementSelectedCandidateId === candidate.id;
const previewUrl = getCandidatePreviewUrl(candidate, "detail");
return (
<button
key={candidate.id}
type="button"
onClick={() => handleSelectDetailReplacementCandidate(candidate.id)}
className={cn(
"min-w-[220px] rounded-[20px] border px-3 py-2 text-left transition-colors",
"min-w-[280px] overflow-hidden rounded-[20px] border text-left transition-colors",
isSelected
? "border-emerald-400/45 bg-emerald-500/[0.10]"
: "border-white/10 bg-white/[0.03] hover:border-white/18"
)}
>
<div className="truncate text-sm font-medium text-white">
{candidate.sceneName || candidate.taskNo || candidate.productModel || "候选整套"}
<div className="relative aspect-[16/9] overflow-hidden bg-[radial-gradient(circle_at_top,rgba(16,185,129,0.18),transparent_58%),linear-gradient(180deg,#0b0d0f_0%,#11161a_100%)]">
{previewUrl ? (
<img
src={buildPreviewImageUrl(previewUrl, 640)}
alt={candidate.sceneName || candidate.taskNo || candidate.productModel || "候选整套"}
className="h-full w-full object-cover"
/>
) : null}
<div className="absolute left-3 top-3 rounded-full border border-white/10 bg-black/45 px-2.5 py-1 text-[11px] text-white/78">
{candidate.detailImageUrls.length} 张详情图
</div>
{isSelected ? (
<div className="absolute right-3 top-3 rounded-full border border-emerald-300/35 bg-emerald-500/[0.14] px-2.5 py-1 text-[11px] text-emerald-100">
当前使用
</div>
) : null}
</div>
<div className="mt-1 line-clamp-2 text-[11px] text-white/42">
{formatCandidateMeta(candidate, "detail") || "点击切换这套详情参考图"}
<div className="space-y-2 px-3 py-3">
<div className="truncate text-sm font-medium text-white">
{candidate.sceneName || candidate.taskNo || candidate.productModel || "候选整套"}
</div>
<div className="line-clamp-2 text-[11px] text-white/42">
{formatCandidateMeta(candidate, "detail") || "点击切换这套详情参考图"}
</div>
<div className="flex flex-wrap gap-1.5 text-[11px] text-white/48">
{candidate.shopName ? (
<span className="rounded-full bg-white/[0.05] px-2 py-1">
{candidate.shopName}
</span>
) : null}
{candidate.categoryName ? (
<span className="rounded-full bg-white/[0.05] px-2 py-1">
{candidate.categoryName}
</span>
) : null}
</div>
</div>
</button>
);
@ -2254,9 +2409,18 @@ export function AgentGenerationWorkspaceDarkV2({
<div className="agent-dark-scrollbar max-h-[620px] space-y-3 overflow-y-auto pr-1">
{detailReplacementReferenceCards.length > 0 ? (
detailReplacementReferenceCards.map((card, index) => (
(() => {
const isActive = activeDetailReferenceCardId === card.id;
return (
<div
key={card.id}
className="overflow-hidden rounded-[22px] border border-white/10 bg-black/20"
className={cn(
"cursor-pointer overflow-hidden rounded-[22px] border bg-black/20 transition-colors",
isActive
? "border-emerald-400/40 shadow-[0_0_0_1px_rgba(52,211,153,0.15)]"
: "border-white/10"
)}
onClick={() => setActiveDetailReferenceCardId(card.id)}
>
<div className="relative aspect-[5/3] overflow-hidden bg-[radial-gradient(circle_at_top,rgba(16,185,129,0.12),transparent_55%),linear-gradient(180deg,#0b0d0f_0%,#101316_100%)]">
<button
@ -2273,6 +2437,14 @@ export function AgentGenerationWorkspaceDarkV2({
className="h-full w-full object-cover"
/>
) : null}
<div className="absolute left-3 top-3 rounded-full border border-white/10 bg-black/45 px-2.5 py-1 text-[11px] text-white/78">
参考图 {index + 1}
</div>
{isActive ? (
<div className="absolute left-3 top-12 rounded-full border border-emerald-300/35 bg-emerald-500/[0.14] px-2.5 py-1 text-[11px] text-emerald-100">
当前联动
</div>
) : null}
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/85 via-black/35 to-transparent px-4 py-3">
<div className="text-sm font-medium text-white/88">{card.label}</div>
<div className="text-xs text-white/45">
@ -2284,6 +2456,8 @@ export function AgentGenerationWorkspaceDarkV2({
删除某张后,后面的参考图会自动上移补位,不留空位。
</div>
</div>
);
})()
))
) : (
<div className="flex min-h-[220px] flex-col items-center justify-center rounded-[22px] border border-dashed border-white/12 bg-black/20 px-5 text-center">
@ -2311,24 +2485,44 @@ export function AgentGenerationWorkspaceDarkV2({
</div>
<div className="agent-dark-scrollbar max-h-[720px] space-y-3 overflow-y-auto pr-1">
{detailReplacementReferenceCards.length > 0 ? (
detailReplacementReferenceCards.map((card, index) => (
{detailResultCards.length > 0 ? (
detailResultCards.map((card) => (
(() => {
const isActive = activeDetailReferenceCardId === card.id;
const statusMeta = getReplacementCardStatusMeta(card.status);
return (
<div
key={`${card.id}-result`}
className="overflow-hidden rounded-[22px] border border-white/10 bg-black/20"
className={cn(
"cursor-pointer overflow-hidden rounded-[22px] border bg-black/20 transition-colors",
isActive
? "border-emerald-400/40 shadow-[0_0_0_1px_rgba(52,211,153,0.15)]"
: "border-white/10"
)}
>
{latestReplacementResultUrls[index] ? (
{card.resultUrl ? (
<div className="relative aspect-[5/3] overflow-hidden bg-black/30">
<img
src={buildPreviewImageUrl(latestReplacementResultUrls[index]!, 720)}
alt={`结果图 ${index + 1}`}
src={buildPreviewImageUrl(card.resultUrl, 720)}
alt={`结果图 ${card.index + 1}`}
className="h-full w-full cursor-zoom-in object-cover"
onClick={() => setFullscreenPreview(latestReplacementResultUrls[index]!)}
onClick={() => {
setActiveDetailReferenceCardId(card.id);
setFullscreenPreview(card.resultUrl!);
}}
/>
<div className={cn("absolute left-3 top-3 rounded-full border px-2.5 py-1 text-[11px]", statusMeta.className)}>
{statusMeta.label}
</div>
{isActive ? (
<div className="absolute left-3 top-12 rounded-full border border-emerald-300/35 bg-emerald-500/[0.14] px-2.5 py-1 text-[11px] text-emerald-100">
当前联动
</div>
) : null}
<button
type="button"
onClick={() => {
void handleDownloadOriginalImage(latestReplacementResultUrls[index]!);
void handleDownloadOriginalImage(card.resultUrl!);
}}
className="absolute bottom-3 right-3 rounded-full border border-white/10 bg-black/55 px-3 py-1 text-[11px] text-white transition-colors hover:border-white/20"
>
@ -2338,8 +2532,8 @@ export function AgentGenerationWorkspaceDarkV2({
type="button"
onClick={() =>
handleSendResultToCanvasTextEdit({
imageUrl: latestReplacementResultUrls[index]!,
title: `${selectedAgentPreset?.name || "详情替换"} 结果图 ${index + 1}`,
imageUrl: card.resultUrl!,
title: `${selectedAgentPreset?.name || "详情替换"} 结果图 ${card.index + 1}`,
})
}
className="absolute bottom-3 left-3 rounded-full border border-white/10 bg-black/55 px-3 py-1 text-[11px] text-white transition-colors hover:border-white/20"
@ -2348,23 +2542,37 @@ export function AgentGenerationWorkspaceDarkV2({
</button>
</div>
) : (
<div className="flex aspect-[5/3] items-center justify-center bg-white/[0.02]">
<div
className="flex aspect-[5/3] items-center justify-center bg-white/[0.02]"
onClick={() => setActiveDetailReferenceCardId(card.id)}
>
<div className="space-y-2 px-4 text-center">
<div className={cn("mx-auto inline-flex rounded-full border px-2.5 py-1 text-[11px]", statusMeta.className)}>
{statusMeta.label}
</div>
<div className="text-sm font-medium text-white/70">
结果图 {index + 1}
结果图 {card.index + 1}
</div>
<div className="text-xs text-white/35">
将按“输入图组 + 第 {index + 1} 张详情参考图”生成
将按“输入图组 + 第 {card.index + 1} 张详情参考图”生成
</div>
</div>
</div>
)}
<div className="border-t border-white/10 px-3 py-2 text-[11px] text-white/45">
{latestReplacementResultUrls[index]
{card.resultUrl
? "已生成,可继续逐屏调整文字或重新执行。"
: "生成完成后,这里会接入现有无限画布的文字编辑能力。"}
: card.status === "generating"
? "当前正在生成这一屏,请稍候。"
: card.status === "queued"
? "前面的屏生成完成后,会继续处理这一屏。"
: card.status === "missing"
? "当前这屏还没有回传结果,可重新执行整套生成。"
: "生成完成后,这里会接入现有无限画布的文字编辑能力。"}
</div>
</div>
);
})()
))
) : (
<div className="flex min-h-[220px] flex-col items-center justify-center rounded-[22px] border border-dashed border-white/12 bg-black/20 px-5 text-center">
@ -2527,29 +2735,66 @@ export function AgentGenerationWorkspaceDarkV2({
{skuReplacementCandidates.length > 0 ? (
<div className="mb-3">
<div className="mb-2 text-xs text-white/42">
候选整套素材 {skuReplacementCandidates.length} 套
<div className="mb-2 flex items-center justify-between gap-3">
<div className="text-xs text-white/42">
候选整套素材 {skuReplacementCandidates.length} 套
</div>
<div className="text-[11px] text-emerald-200/70">
默认载入最新一套,可随时切换
</div>
</div>
<div className="agent-dark-scrollbar flex gap-2 overflow-x-auto pb-1">
{skuReplacementCandidates.map((candidate) => {
const isSelected = skuReplacementSelectedCandidateId === candidate.id;
const previewUrl = getCandidatePreviewUrl(candidate, "sku");
return (
<button
key={candidate.id}
type="button"
onClick={() => handleSelectSkuReplacementCandidate(candidate.id)}
className={cn(
"min-w-[220px] rounded-[20px] border px-3 py-2 text-left transition-colors",
"min-w-[280px] overflow-hidden rounded-[20px] border text-left transition-colors",
isSelected
? "border-emerald-400/45 bg-emerald-500/[0.10]"
: "border-white/10 bg-white/[0.03] hover:border-white/18"
)}
>
<div className="truncate text-sm font-medium text-white">
{candidate.sceneName || candidate.taskNo || candidate.productModel || "候选整套"}
<div className="relative aspect-[16/9] overflow-hidden bg-[radial-gradient(circle_at_top,rgba(16,185,129,0.18),transparent_58%),linear-gradient(180deg,#0b0d0f_0%,#11161a_100%)]">
{previewUrl ? (
<img
src={buildPreviewImageUrl(previewUrl, 640)}
alt={candidate.sceneName || candidate.taskNo || candidate.productModel || "候选整套"}
className="h-full w-full object-cover"
/>
) : null}
<div className="absolute left-3 top-3 rounded-full border border-white/10 bg-black/45 px-2.5 py-1 text-[11px] text-white/78">
{candidate.skuImageUrls.length} 张SKU图
</div>
{isSelected ? (
<div className="absolute right-3 top-3 rounded-full border border-emerald-300/35 bg-emerald-500/[0.14] px-2.5 py-1 text-[11px] text-emerald-100">
当前使用
</div>
) : null}
</div>
<div className="mt-1 line-clamp-2 text-[11px] text-white/42">
{formatCandidateMeta(candidate, "sku") || "点击切换这套 SKU 参考图"}
<div className="space-y-2 px-3 py-3">
<div className="truncate text-sm font-medium text-white">
{candidate.sceneName || candidate.taskNo || candidate.productModel || "候选整套"}
</div>
<div className="mt-1 line-clamp-2 text-[11px] text-white/42">
{formatCandidateMeta(candidate, "sku") || "点击切换这套 SKU 参考图"}
</div>
<div className="flex flex-wrap gap-1.5 text-[11px] text-white/48">
{candidate.shopName ? (
<span className="rounded-full bg-white/[0.05] px-2 py-1">
{candidate.shopName}
</span>
) : null}
{candidate.categoryName ? (
<span className="rounded-full bg-white/[0.05] px-2 py-1">
{candidate.categoryName}
</span>
) : null}
</div>
</div>
</button>
);
@ -2563,7 +2808,13 @@ export function AgentGenerationWorkspaceDarkV2({
skuReplacementReferenceCards.map((card, index) => (
<div
key={card.id}
className="overflow-hidden rounded-[22px] border border-white/10 bg-black/20"
className={cn(
"cursor-pointer overflow-hidden rounded-[22px] border bg-black/20 transition-colors",
activeSkuReferenceCardId === card.id
? "border-emerald-400/40 shadow-[0_0_0_1px_rgba(52,211,153,0.15)]"
: "border-white/10"
)}
onClick={() => setActiveSkuReferenceCardId(card.id)}
>
{(() => {
const resolvedAssignedProductIndex =
@ -2592,6 +2843,14 @@ export function AgentGenerationWorkspaceDarkV2({
className="h-full w-full object-cover"
/>
) : null}
<div className="absolute left-3 top-3 rounded-full border border-white/10 bg-black/45 px-2.5 py-1 text-[11px] text-white/78">
参考图 {index + 1}
</div>
{activeSkuReferenceCardId === card.id ? (
<div className="absolute left-3 top-12 rounded-full border border-emerald-300/35 bg-emerald-500/[0.14] px-2.5 py-1 text-[11px] text-emerald-100">
当前联动
</div>
) : null}
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/85 via-black/35 to-transparent px-4 py-3">
<div className="text-sm font-medium text-white/88">{card.label}</div>
<div className="text-xs text-white/45">
@ -2664,24 +2923,37 @@ export function AgentGenerationWorkspaceDarkV2({
</div>
<div className="agent-dark-scrollbar max-h-[720px] space-y-3 overflow-y-auto pr-1">
{skuReplacementReferenceCards.length > 0 ? (
skuReplacementReferenceCards.map((card, index) => (
{skuResultCards.length > 0 ? (
skuResultCards.map((card) => {
const statusMeta = getReplacementCardStatusMeta(card.status);
return (
<div
key={`${card.id}-result`}
className="overflow-hidden rounded-[22px] border border-white/10 bg-black/20"
className={cn(
"cursor-pointer overflow-hidden rounded-[22px] border bg-black/20 transition-colors",
activeSkuReferenceCardId === card.id
? "border-emerald-400/40 shadow-[0_0_0_1px_rgba(52,211,153,0.15)]"
: "border-white/10"
)}
>
{latestReplacementResultUrls[index] ? (
{card.resultUrl ? (
<div className="relative aspect-[5/3] overflow-hidden bg-black/30">
<img
src={buildPreviewImageUrl(latestReplacementResultUrls[index]!, 720)}
alt={`结果图 ${index + 1}`}
src={buildPreviewImageUrl(card.resultUrl, 720)}
alt={`结果图 ${card.index + 1}`}
className="h-full w-full cursor-zoom-in object-cover"
onClick={() => setFullscreenPreview(latestReplacementResultUrls[index]!)}
onClick={() => {
setActiveSkuReferenceCardId(card.id);
setFullscreenPreview(card.resultUrl!);
}}
/>
<div className={cn("absolute left-3 top-3 rounded-full border px-2.5 py-1 text-[11px]", statusMeta.className)}>
{statusMeta.label}
</div>
<button
type="button"
onClick={() => {
void handleDownloadOriginalImage(latestReplacementResultUrls[index]!);
void handleDownloadOriginalImage(card.resultUrl!);
}}
className="absolute bottom-3 right-3 rounded-full border border-white/10 bg-black/55 px-3 py-1 text-[11px] text-white transition-colors hover:border-white/20"
>
@ -2691,8 +2963,8 @@ export function AgentGenerationWorkspaceDarkV2({
type="button"
onClick={() =>
handleSendResultToCanvasTextEdit({
imageUrl: latestReplacementResultUrls[index]!,
title: `${selectedAgentPreset?.name || "SKU替换"} 结果图 ${index + 1}`,
imageUrl: card.resultUrl!,
title: `${selectedAgentPreset?.name || "SKU替换"} 结果图 ${card.index + 1}`,
})
}
className="absolute bottom-3 left-3 rounded-full border border-white/10 bg-black/55 px-3 py-1 text-[11px] text-white transition-colors hover:border-white/20"
@ -2701,24 +2973,37 @@ export function AgentGenerationWorkspaceDarkV2({
</button>
</div>
) : (
<div className="flex aspect-[5/3] items-center justify-center bg-white/[0.02]">
<div
className="flex aspect-[5/3] items-center justify-center bg-white/[0.02]"
onClick={() => setActiveSkuReferenceCardId(card.id)}
>
<div className="space-y-2 px-4 text-center">
<div className={cn("mx-auto inline-flex rounded-full border px-2.5 py-1 text-[11px]", statusMeta.className)}>
{statusMeta.label}
</div>
<div className="text-sm font-medium text-white/70">
结果图 {index + 1}
结果图 {card.index + 1}
</div>
<div className="text-xs text-white/35">
将按“输入图 + 第 {index + 1} 张 SKU 参考图”生成
将按“输入图 + 第 {card.index + 1} 张 SKU 参考图”生成
</div>
</div>
</div>
)}
<div className="border-t border-white/10 px-3 py-2 text-[11px] text-white/45">
{latestReplacementResultUrls[index]
{card.resultUrl
? "已生成,可继续逐张检查并后续批量下载。"
: "生成完成后,这里会接入统一文字编辑和批量下载能力。"}
: card.status === "generating"
? "当前正在生成这一张,请稍候。"
: card.status === "queued"
? "前面的图生成完成后,会继续处理这一张。"
: card.status === "missing"
? "当前这张还没有回传结果,可重新执行整套生成。"
: "生成完成后,这里会接入统一文字编辑和批量下载能力。"}
</div>
</div>
))
);
})
) : (
<div className="flex min-h-[220px] flex-col items-center justify-center rounded-[22px] border border-dashed border-white/12 bg-black/20 px-5 text-center">
<div className="text-sm font-medium text-white/72">等待替换结果</div>

View File

@ -554,11 +554,12 @@ export function AgentPresetDialog({
level2: selectedPresetLevel2,
level3: selectedPresetLevel3,
});
const createCategoryPath = nextCategoryPath || undefined;
const basePayload: AgentCreatePayload = {
name: name.trim(),
type,
visibility,
categoryPath: nextCategoryPath,
categoryPath: createCategoryPath,
description: description.trim() || undefined,
defaultModelId,
defaultParams: nextDefaultParams || undefined,
@ -577,6 +578,7 @@ export function AgentPresetDialog({
const result = initialPreset
? await agentService.updatePreset(initialPreset.id, {
...basePayload,
categoryPath: nextCategoryPath,
secondaryType: null,
} satisfies AgentUpdatePayload)
: await agentService.createPreset(basePayload);
@ -625,8 +627,7 @@ export function AgentPresetDialog({
<ScrollArea className="max-h-[72vh] pr-3">
<div className="space-y-4">
<div className={cn("grid grid-cols-1 gap-5", isDetailReplacementModeBuilder ? "md:grid-cols-1" : "md:grid-cols-2")}>
{!isDetailReplacementModeBuilder ? (
<div className={cn("grid grid-cols-1 gap-5 md:grid-cols-2")}>
<div className="space-y-2.5">
<div className={cn("text-sm font-semibold", isDark ? "text-white" : "text-foreground")}>预览图</div>
<div className="flex items-center gap-3">
@ -684,7 +685,6 @@ export function AgentPresetDialog({
</label>
</div>
</div>
) : null}
<div className="space-y-2.5">
<div className={cn("text-sm font-semibold", isDark ? "text-white" : "text-foreground")}>
{entityNameLabel}
@ -1284,7 +1284,7 @@ export function AgentPresetDialog({
<div className={cn("rounded-xl border border-dashed px-3 py-3 text-xs", isDark ? "border-white/10 text-white/45" : "text-muted-foreground")}>
{isDetailReplacementModeBuilder
? "详情替换模式不需要上传模板预览图,第二步会通过 SPU 获取详情参考图,再把输入图逐张替换到详情参考图中。"
? "详情替换模式不需要上传模板参考图,第二步会通过 SPU 获取详情参考图,再把输入图逐张替换到详情参考图中。"
: "SKU替换模式会把用户选中的白底图与参考图建立一对一关系,后续在第二步专用工作台中执行替换。"}
</div>
</div>

View File

@ -318,18 +318,25 @@ export function buildAgentPresetCategoryPath(params: {
return null;
}
return {
sectionId: section?.id || null,
sectionName: section?.label || null,
level1Id: level1?.id || null,
level1Name: level1?.name || null,
level2Id: level2?.id || null,
level2Name: level2?.name || null,
level3Id: level3?.id || null,
level3Name: level3?.name || null,
productCategory: level1?.name || null,
audienceTag: level2?.name || null,
usageScenario: level3?.name || null,
brand: null,
};
const nextPath: AgentCategoryPath = {};
if (section?.id) nextPath.sectionId = section.id;
if (section?.label) nextPath.sectionName = section.label;
if (level1?.id) nextPath.level1Id = level1.id;
if (level1?.name) {
nextPath.level1Name = level1.name;
nextPath.productCategory = level1.name;
}
if (level2?.id) nextPath.level2Id = level2.id;
if (level2?.name) {
nextPath.level2Name = level2.name;
nextPath.audienceTag = level2.name;
}
if (level3?.id) nextPath.level3Id = level3.id;
if (level3?.name) {
nextPath.level3Name = level3.name;
nextPath.usageScenario = level3.name;
}
return Object.keys(nextPath).length > 0 ? nextPath : null;
}