fix: refine agent workflow step navigation
This commit is contained in:
parent
9631c10812
commit
21a915cb13
@ -1750,14 +1750,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
transition={{ delay: 0.06 }}
|
||||
className="rounded-[28px] border border-white/12 bg-white/[0.04] p-5 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]"
|
||||
>
|
||||
<div className="mb-5 border-b border-white/10 pb-5">
|
||||
<div className="mb-4 space-y-1">
|
||||
<div className="text-xs uppercase tracking-[0.2em] text-white/35">Workflow Anchors</div>
|
||||
<h3 className="text-lg font-semibold text-white">单页生图流程</h3>
|
||||
<p className="text-sm text-white/45">
|
||||
在同一页面完成生图模式选择、SPU / SKU 选图、风格步骤配置、生成和结果预览。
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-5 border-b border-white/10 pb-4">
|
||||
<div className="agent-dark-scrollbar flex gap-2 overflow-x-auto pb-1">
|
||||
{unifiedAnchorSteps.map((step) => (
|
||||
<button
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Sparkles } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Plus, Sparkles } from "lucide-react";
|
||||
import { AgentWorkflowMaterialPicker } from "@/components/agents/AgentWorkflowMaterialPicker";
|
||||
import { createEmptyAgentWorkflowMaterialSelection } from "@/lib/agents/workflow-material-config";
|
||||
import { cn } from "@/lib/utils";
|
||||
@ -29,12 +29,17 @@ export function AgentWorkflowMaterialSelectionPanel({
|
||||
onInputsChange,
|
||||
materials,
|
||||
onMaterialsChange,
|
||||
title = "SKU / SPU 选图",
|
||||
description = "先确定生图模式,再搜索主 SKU 或搭配 SKU 的白底图。选中的图片会自动进入对应槽位。",
|
||||
title,
|
||||
description,
|
||||
disabled = false,
|
||||
disabledHint = "请先在下方选择一个生图模式,再启用 SKU / SPU 搜图。",
|
||||
theme = "light",
|
||||
}: AgentWorkflowMaterialSelectionPanelProps) {
|
||||
const hasSecondaryMaterialContent =
|
||||
Boolean(String(inputs.secondarySku || "").trim()) ||
|
||||
Boolean(materials.secondary.result) ||
|
||||
materials.secondary.selectedImageIds.length > 0;
|
||||
|
||||
const buildLoadingMaterialResult = (params: {
|
||||
keyword: string;
|
||||
currentResult?: AgentWorkflowMaterialSearchResult | null;
|
||||
@ -81,6 +86,15 @@ export function AgentWorkflowMaterialSelectionPanel({
|
||||
primary: "sku",
|
||||
secondary: "sku",
|
||||
});
|
||||
const [isSecondaryMaterialVisible, setIsSecondaryMaterialVisible] = useState(
|
||||
hasSecondaryMaterialContent
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSecondaryMaterialContent) {
|
||||
setIsSecondaryMaterialVisible(true);
|
||||
}
|
||||
}, [hasSecondaryMaterialContent]);
|
||||
|
||||
const updateMaterialSearchState = (
|
||||
source: "primary" | "secondary",
|
||||
@ -170,25 +184,31 @@ export function AgentWorkflowMaterialSelectionPanel({
|
||||
isDark ? "border-white/12 bg-white/[0.04]" : "border-[#dbe3ff] bg-[#f8faff]"
|
||||
)}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-2 text-sm font-medium",
|
||||
isDark ? "text-white/88" : "text-[#334155]"
|
||||
)}
|
||||
>
|
||||
<Sparkles className={cn("h-4 w-4", isDark ? "text-emerald-300" : "text-[#4f46e5]")} />
|
||||
<span>{title}</span>
|
||||
{title || description ? (
|
||||
<div className="space-y-1">
|
||||
{title ? (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-2 text-sm font-medium",
|
||||
isDark ? "text-white/88" : "text-[#334155]"
|
||||
)}
|
||||
>
|
||||
<Sparkles className={cn("h-4 w-4", isDark ? "text-emerald-300" : "text-[#4f46e5]")} />
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
) : null}
|
||||
{description ? (
|
||||
<div
|
||||
className={cn(
|
||||
"text-xs leading-5",
|
||||
isDark ? "text-white/55" : "text-[#5b6472]"
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"text-xs leading-5",
|
||||
isDark ? "text-white/55" : "text-[#5b6472]"
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<AgentWorkflowMaterialPicker
|
||||
title="主 SKU / SPU"
|
||||
@ -259,74 +279,110 @@ export function AgentWorkflowMaterialSelectionPanel({
|
||||
}
|
||||
/>
|
||||
|
||||
<AgentWorkflowMaterialPicker
|
||||
title="搭配 SKU"
|
||||
description="搭配商品图会优先自动填充到“产品图2”;品牌模特图会在上方单独选择并进入模特图槽位。"
|
||||
value={inputs.secondarySku}
|
||||
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}
|
||||
errorText={materialSearchState.secondary.errorText}
|
||||
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) => {
|
||||
const selectedOption = materials.secondary.result?.skuOptions?.find(
|
||||
(option) => option.skuName === skuName
|
||||
);
|
||||
const placeholderCount = selectedOption?.whiteBackgroundCount || 8;
|
||||
onInputsChange({
|
||||
...inputs,
|
||||
secondarySku: skuName,
|
||||
});
|
||||
onMaterialsChange({
|
||||
...materials,
|
||||
secondary: {
|
||||
result: buildLoadingMaterialResult({
|
||||
keyword: skuName,
|
||||
currentResult: materials.secondary.result,
|
||||
placeholderCount,
|
||||
}),
|
||||
selectedImageIds: [],
|
||||
},
|
||||
});
|
||||
setMaterialSearchMode((prev) => ({
|
||||
...prev,
|
||||
secondary: "sku",
|
||||
}));
|
||||
void handleSearchMaterials("secondary", {
|
||||
keyword: skuName,
|
||||
queryType: "sku",
|
||||
});
|
||||
}}
|
||||
theme={theme}
|
||||
onSelectedImageIdsChange={(next) =>
|
||||
onMaterialsChange({
|
||||
...materials,
|
||||
secondary: {
|
||||
...materials.secondary,
|
||||
selectedImageIds: next,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
{isSecondaryMaterialVisible ? (
|
||||
<AgentWorkflowMaterialPicker
|
||||
title="搭配 SKU"
|
||||
description="搭配商品图会优先自动填充到“产品图2”;品牌模特图会在上方单独选择并进入模特图槽位。"
|
||||
value={inputs.secondarySku}
|
||||
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}
|
||||
errorText={materialSearchState.secondary.errorText}
|
||||
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) => {
|
||||
const selectedOption = materials.secondary.result?.skuOptions?.find(
|
||||
(option) => option.skuName === skuName
|
||||
);
|
||||
const placeholderCount = selectedOption?.whiteBackgroundCount || 8;
|
||||
onInputsChange({
|
||||
...inputs,
|
||||
secondarySku: skuName,
|
||||
});
|
||||
onMaterialsChange({
|
||||
...materials,
|
||||
secondary: {
|
||||
result: buildLoadingMaterialResult({
|
||||
keyword: skuName,
|
||||
currentResult: materials.secondary.result,
|
||||
placeholderCount,
|
||||
}),
|
||||
selectedImageIds: [],
|
||||
},
|
||||
});
|
||||
setMaterialSearchMode((prev) => ({
|
||||
...prev,
|
||||
secondary: "sku",
|
||||
}));
|
||||
void handleSearchMaterials("secondary", {
|
||||
keyword: skuName,
|
||||
queryType: "sku",
|
||||
});
|
||||
}}
|
||||
theme={theme}
|
||||
onSelectedImageIdsChange={(next) =>
|
||||
onMaterialsChange({
|
||||
...materials,
|
||||
secondary: {
|
||||
...materials.secondary,
|
||||
selectedImageIds: next,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsSecondaryMaterialVisible(true)}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between rounded-[24px] border border-dashed px-4 py-3 text-left transition-colors",
|
||||
disabled
|
||||
? isDark
|
||||
? "cursor-not-allowed border-white/10 bg-white/[0.02] text-white/30"
|
||||
: "cursor-not-allowed border-[#dbe3ff] bg-[#f8faff] text-[#94a3b8]"
|
||||
: isDark
|
||||
? "border-white/14 bg-white/[0.03] text-white/78 hover:border-emerald-300/30 hover:bg-white/[0.05]"
|
||||
: "border-[#c7d2fe] bg-white text-[#334155] hover:border-[#818cf8] hover:bg-[#f8faff]"
|
||||
)}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 text-sm font-medium">
|
||||
<Plus className={cn("h-4 w-4", isDark ? "text-emerald-300" : "text-[#4f46e5]")} />
|
||||
<span>添加搭配SKU</span>
|
||||
</div>
|
||||
<div className={cn("text-xs leading-5", isDark ? "text-white/45" : "text-[#64748b]")}>
|
||||
需要补充第二个产品图时再展开这个模块,不使用时默认保持隐藏。
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className={cn(
|
||||
"rounded-full px-2.5 py-1 text-[11px] font-medium",
|
||||
isDark ? "bg-white/[0.06] text-white/72" : "bg-[#eef2ff] text-[#4f46e5]"
|
||||
)}
|
||||
>
|
||||
可选
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -235,6 +235,7 @@ export function AgentWorkflowPanelDarkV2({
|
||||
[resolvedConfig, selections]
|
||||
);
|
||||
const [activeEmbeddedStepKey, setActiveEmbeddedStepKey] = useState<string>("");
|
||||
const [pendingAutoAdvanceStepKey, setPendingAutoAdvanceStepKey] = useState<string | null>(null);
|
||||
const activeEmbeddedStepIndex = Math.max(
|
||||
flattenedResolvedSteps.findIndex(({ step }) => step.key === activeEmbeddedStepKey),
|
||||
0
|
||||
@ -269,6 +270,10 @@ export function AgentWorkflowPanelDarkV2({
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingAutoAdvanceStepKey !== activeEmbeddedStepEntry.step.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentStepComplete = isAgentWorkflowStepSelectionComplete({
|
||||
step: activeEmbeddedStepEntry.step,
|
||||
selectionValue: selections[activeEmbeddedStepEntry.step.key],
|
||||
@ -290,12 +295,15 @@ export function AgentWorkflowPanelDarkV2({
|
||||
if (nextStep && nextStep.step.key !== activeEmbeddedStepKey) {
|
||||
setActiveEmbeddedStepKey(nextStep.step.key);
|
||||
}
|
||||
|
||||
setPendingAutoAdvanceStepKey(null);
|
||||
}, [
|
||||
activeEmbeddedStepEntry,
|
||||
activeEmbeddedStepIndex,
|
||||
activeEmbeddedStepKey,
|
||||
embedded,
|
||||
flattenedResolvedSteps,
|
||||
pendingAutoAdvanceStepKey,
|
||||
selections,
|
||||
]);
|
||||
|
||||
@ -341,6 +349,7 @@ export function AgentWorkflowPanelDarkV2({
|
||||
const prefix = parentPath.slice(0, depth - 1);
|
||||
const nextKeys =
|
||||
currentSelectedKey === option.key ? prefix : [...prefix, option.key];
|
||||
setPendingAutoAdvanceStepKey(step.key);
|
||||
onSelectionsChange({
|
||||
...selections,
|
||||
[step.key]: nextKeys.length > 0 ? serializeAgentWorkflowSelectionPath(nextKeys) : undefined,
|
||||
@ -451,7 +460,10 @@ export function AgentWorkflowPanelDarkV2({
|
||||
<button
|
||||
key={step.key}
|
||||
type="button"
|
||||
onClick={() => setActiveEmbeddedStepKey(step.key)}
|
||||
onClick={() => {
|
||||
setPendingAutoAdvanceStepKey(null);
|
||||
setActiveEmbeddedStepKey(step.key);
|
||||
}}
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded-full border px-3 py-1.5 text-xs transition-colors",
|
||||
active
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user