feat: move brand setup to first step
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 19:03:12 +08:00
parent 5c10380666
commit 2ae376bdfd
5 changed files with 159 additions and 83 deletions

View File

@ -68,7 +68,10 @@ import { AgentWorkflowMaterialSelectionPanel } from "@/components/agents/AgentWo
import { AgentPresetPanelNew } from "@/components/agents/AgentPresetPanelNew";
// 深色主题组件 V2 (新版布局)
import { AgentPresetPanelDarkV2 } from "@/components/agents/AgentPresetPanelDarkV2";
import { AgentWorkflowPanelDarkV2 } from "@/components/agents/AgentWorkflowPanelDarkV2";
import {
AgentWorkflowBrandStorePanel,
AgentWorkflowPanelDarkV2,
} from "@/components/agents/AgentWorkflowPanelDarkV2";
import { AgentGenerationWorkspaceDarkV2 } from "@/components/agents/AgentGenerationWorkspaceDarkV2";
import { AgentProjectListSheet } from "@/components/agents/AgentProjectListSheet";
import { CanvasWorkspace } from "@/components/canvas";
@ -1488,6 +1491,14 @@ export default function CanvasEditorPage({ params }: PageProps) {
});
}}
onClearPreset={() => setSelectedAgentPresetId(null)}
brandStorePanel={
<AgentWorkflowBrandStorePanel
config={resolvedWorkflowUiConfig}
inputs={workflowInputs}
onInputsChange={setWorkflowInputs}
theme="dark"
/>
}
workflowStylePanel={
<AgentWorkflowPanelDarkV2
embedded
@ -1498,6 +1509,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
onMaterialsChange={setWorkflowMaterials}
selections={workflowSelections}
onSelectionsChange={setWorkflowSelections}
showBrandStoreSection={false}
/>
}
// 嵌入的智能体选择面板

View File

@ -68,6 +68,7 @@ interface AgentGenerationWorkspaceDarkV2Props {
workflowContext?: AgentWorkflowComposerContext | null;
onBack?: () => void;
presetPanel?: ReactNode;
brandStorePanel?: ReactNode;
materialSelectionPanel?: ReactNode;
brandModelPanel?: ReactNode;
onClearPreset?: () => void;
@ -351,6 +352,7 @@ export function AgentGenerationWorkspaceDarkV2({
workflowContext = null,
onBack,
presetPanel,
brandStorePanel,
materialSelectionPanel,
brandModelPanel,
onClearPreset,
@ -1900,7 +1902,7 @@ export function AgentGenerationWorkspaceDarkV2({
<span className="text-sm font-semibold text-white">选择生图模式</span>
</div>
<p className="text-sm text-white/45">
先确定本次使用的模式,后续的商品图、参考图和结果展示都会随模式切换。
先确定本次使用的模式,并完成品牌和店铺选择,后续的商品图、品牌模特、参考图和结果展示都会随之联动。
</p>
{isReplacementPreset ? (
<p className="text-xs text-amber-300/80">
@ -1953,6 +1955,12 @@ export function AgentGenerationWorkspaceDarkV2({
暂无可用的生图模式配置。
</div>
)}
{brandStorePanel ? (
<div className="mt-4 border-t border-white/10 pt-4">
{brandStorePanel}
</div>
) : null}
</section>
{hasMaterialStep ? (
@ -1982,7 +1990,7 @@ export function AgentGenerationWorkspaceDarkV2({
<span className="text-sm font-semibold text-white">风格步骤卡片</span>
</div>
<p className="text-sm text-white/45">
在同一页完成品牌、店铺、风格卡片、品牌模特和 Prompt 组合,不再跳转第二个页面。
在同一页完成风格卡片、品牌模特和 Prompt 组合,不再跳转第二个页面。
</p>
</div>

View File

@ -99,7 +99,7 @@ export function AgentPresetCardDark({
whileHover={{ y: -3, scale: 1.01 }}
whileTap={{ scale: 0.98 }}
className={cn(
"group relative cursor-pointer overflow-hidden rounded-2xl border p-2.5 transition-all duration-300",
"group relative cursor-pointer overflow-hidden rounded-2xl border p-3 transition-all duration-300",
selected
? "border-emerald-400/40 bg-emerald-500/[0.08] shadow-[0_0_24px_rgba(16,185,129,0.14)]"
: "border-white/10 bg-white/[0.03] hover:border-white/20 hover:bg-white/[0.06]"
@ -142,29 +142,43 @@ export function AgentPresetCardDark({
>
<Copy className="h-3 w-3" />
</button>
) : null}
) : null}
</div>
<div className="relative mx-auto h-[70px] w-[70px] overflow-hidden rounded-2xl border border-white/10 bg-black/30">
{previewUrl ? (
<img
src={previewUrl}
alt={preset.name}
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
) : (
<div className={cn("flex h-full w-full items-center justify-center bg-gradient-to-br", typeInfo.gradient)}>
<TypeIcon className="h-7 w-7 text-white" />
<div className="flex items-center gap-3">
<div className="relative h-[76px] w-[76px] shrink-0 overflow-hidden rounded-2xl border border-white/10 bg-black/30">
{previewUrl ? (
<img
src={previewUrl}
alt={preset.name}
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
) : (
<div className={cn("flex h-full w-full items-center justify-center bg-gradient-to-br", typeInfo.gradient)}>
<TypeIcon className="h-7 w-7 text-white" />
</div>
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/35 to-transparent" />
<div className="absolute left-2 top-2 rounded-full border border-white/10 bg-black/45 px-2 py-0.5 text-[10px] text-white/78">
{imageModeLabel || typeInfo.label}
</div>
</div>
<div className="min-w-0 flex-1 pr-8">
<div className="line-clamp-2 text-sm font-semibold leading-5 text-white">
{preset.name}
</div>
<div className="mt-2 flex flex-col gap-1.5 text-[11px] text-white/50">
<div className="flex items-center gap-1.5">
<User className="h-3.5 w-3.5 text-white/34" />
<span className="truncate">贡献者:{ownerName}</span>
</div>
<div className="flex items-center gap-1.5">
<Clock className="h-3.5 w-3.5 text-white/34" />
<span className="truncate">贡献时间:{publishedAt || "未记录"}</span>
</div>
</div>
)}
<div className="absolute inset-0 bg-gradient-to-t from-black/35 to-transparent" />
</div>
<div className="mt-2 space-y-1 text-center">
<div className="line-clamp-2 min-h-[2.5rem] text-[12px] font-medium leading-5 text-white">
{preset.name}
</div>
<div className="text-[10px] text-white/45">{imageModeLabel || typeInfo.label}</div>
</div>
{selected ? (

View File

@ -338,7 +338,7 @@ export function AgentPresetPanelDarkV2({
) : null}
{/* 卡片网格 */}
<div className="grid grid-cols-[repeat(auto-fill,minmax(96px,1fr))] gap-2.5">
<div className="grid grid-cols-1 gap-3 xl:grid-cols-2">
{isLoading ? (
<div className="col-span-full flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-violet-500" />

View File

@ -58,6 +58,16 @@ interface AgentWorkflowPanelDarkV2Props {
showcaseConfig?: AgentWorkflowShowcaseConfig | null;
topNavRightSlot?: ReactNode;
embedded?: boolean;
showBrandStoreSection?: boolean;
}
interface AgentWorkflowBrandStorePanelProps {
config?: AgentWorkflowUiConfig | null;
inputs: AgentWorkflowInputs;
onInputsChange: (next: AgentWorkflowInputs) => void;
theme?: "light" | "dark";
title?: string;
description?: string;
}
function buildSelectionSummary(params: {
@ -170,6 +180,77 @@ function StepOptionCard({
);
}
export function AgentWorkflowBrandStorePanel({
config,
inputs,
onInputsChange,
theme = "dark",
title = "品牌和店铺选择",
description = "先选择品牌和店铺,后续品牌模特与风格步骤会按这里的选择联动。",
}: AgentWorkflowBrandStorePanelProps) {
const brandOptions = useMemo(
() => config?.inputOptions?.brands || defaultAgentWorkflowUiConfig.inputOptions?.brands || [],
[config?.inputOptions?.brands]
);
const brandStoreEntries = useMemo(
() => getAgentWorkflowBrandStoreEntries(config),
[config]
);
const storeOptions = useMemo(
() => getAgentWorkflowStoreOptionsByBrand({ config, brand: inputs.brand }),
[config, inputs.brand]
);
const isDark = theme === "dark";
return (
<div
className={cn(
"rounded-[24px] border p-4",
isDark ? "border-white/12 bg-black/20" : "border-border bg-background"
)}
>
<div className="mb-4 space-y-1">
<div className={cn("text-sm font-semibold", isDark ? "text-white" : "text-foreground")}>
{title}
</div>
<div className={cn("text-xs leading-5", isDark ? "text-white/48" : "text-muted-foreground")}>
{description}
</div>
</div>
<div className="grid max-w-2xl grid-cols-1 gap-4 md:grid-cols-2">
<SearchableSelect
value={inputs.brand}
options={brandOptions}
placeholder="选择品牌"
searchPlaceholder="输入品牌名筛选"
emptyText="暂无品牌可选"
theme={theme}
onValueChange={(value) =>
onInputsChange({
...inputs,
brand: value,
store:
value.trim().toLowerCase() === inputs.brand.trim().toLowerCase()
? inputs.store
: "",
})
}
/>
<SearchableSelect
value={inputs.store}
options={storeOptions}
placeholder="选择店铺"
searchPlaceholder="输入店铺名筛选"
emptyText={inputs.brand ? "当前品牌下暂无店铺可选" : "请先选择品牌"}
theme={theme}
disabled={!inputs.brand || (brandStoreEntries.length > 0 && storeOptions.length === 0)}
onValueChange={(value) => onInputsChange({ ...inputs, store: value })}
/>
</div>
</div>
);
}
export function AgentWorkflowPanelDarkV2({
config,
inputs,
@ -186,20 +267,8 @@ export function AgentWorkflowPanelDarkV2({
showcaseConfig,
topNavRightSlot,
embedded = false,
showBrandStoreSection = true,
}: AgentWorkflowPanelDarkV2Props) {
const brandOptions = useMemo(
() => config?.inputOptions?.brands || defaultAgentWorkflowUiConfig.inputOptions?.brands || [],
[config?.inputOptions?.brands]
);
const brandStoreEntries = useMemo(
() => getAgentWorkflowBrandStoreEntries(config),
[config]
);
const storeOptions = useMemo(
() => getAgentWorkflowStoreOptionsByBrand({ config, brand: inputs.brand }),
[config, inputs.brand]
);
const resolvedConfig = useMemo(() => {
const source = config || defaultAgentWorkflowUiConfig;
return {
@ -385,54 +454,27 @@ export function AgentWorkflowPanelDarkV2({
const workflowContent = (
<div className="space-y-8">
{/* 品牌和店铺选择 */}
<motion.section
className="rounded-2xl border border-white/12 bg-white/[0.04] p-5 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
>
<h2 className="mb-4 flex items-center gap-2 text-lg font-semibold text-white">
<span className="flex h-6 w-6 items-center justify-center rounded-md bg-emerald-500/20 text-xs font-bold text-emerald-400">
1
</span>
品牌和店铺选择
</h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 max-w-2xl">
<SearchableSelect
value={inputs.brand}
options={brandOptions}
placeholder="选择品牌"
searchPlaceholder="输入品牌名筛选"
emptyText="暂无品牌可选"
{showBrandStoreSection ? (
<motion.section
className="rounded-2xl border border-white/12 bg-white/[0.04] p-5 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
>
<h2 className="mb-4 flex items-center gap-2 text-lg font-semibold text-white">
<span className="flex h-6 w-6 items-center justify-center rounded-md bg-emerald-500/20 text-xs font-bold text-emerald-400">
1
</span>
品牌和店铺选择
</h2>
<AgentWorkflowBrandStorePanel
config={config}
inputs={inputs}
onInputsChange={onInputsChange}
theme="dark"
onValueChange={(value) =>
onInputsChange({
...inputs,
brand: value,
store:
value.trim().toLowerCase() === inputs.brand.trim().toLowerCase()
? inputs.store
: "",
})
}
/>
<SearchableSelect
value={inputs.store}
options={storeOptions}
placeholder="选择店铺"
searchPlaceholder="输入店铺名筛选"
emptyText={
inputs.brand
? "当前品牌下暂无店铺可选"
: "请先选择品牌"
}
theme="dark"
disabled={!inputs.brand || (brandStoreEntries.length > 0 && storeOptions.length === 0)}
onValueChange={(value) => onInputsChange({ ...inputs, store: value })}
/>
</div>
</motion.section>
</motion.section>
) : null}
{/* 工作流步骤 */}
{embedded ? (