feat: improve replacement workflow interactions
This commit is contained in:
parent
ea06999769
commit
5c10380666
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user