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

View File

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

View File

@ -99,7 +99,7 @@ export function AgentPresetCardDark({
whileHover={{ y: -3, scale: 1.01 }} whileHover={{ y: -3, scale: 1.01 }}
whileTap={{ scale: 0.98 }} whileTap={{ scale: 0.98 }}
className={cn( 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 selected
? "border-emerald-400/40 bg-emerald-500/[0.08] shadow-[0_0_24px_rgba(16,185,129,0.14)]" ? "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]" : "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" /> <Copy className="h-3 w-3" />
</button> </button>
) : null} ) : null}
</div> </div>
<div className="relative mx-auto h-[70px] w-[70px] overflow-hidden rounded-2xl border border-white/10 bg-black/30"> <div className="flex items-center gap-3">
{previewUrl ? ( <div className="relative h-[76px] w-[76px] shrink-0 overflow-hidden rounded-2xl border border-white/10 bg-black/30">
<img {previewUrl ? (
src={previewUrl} <img
alt={preset.name} src={previewUrl}
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105" 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={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>
)}
<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>
<div className="text-[10px] text-white/45">{imageModeLabel || typeInfo.label}</div>
</div> </div>
{selected ? ( {selected ? (

View File

@ -338,7 +338,7 @@ export function AgentPresetPanelDarkV2({
) : null} ) : 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 ? ( {isLoading ? (
<div className="col-span-full flex items-center justify-center py-8"> <div className="col-span-full flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-violet-500" /> <Loader2 className="h-6 w-6 animate-spin text-violet-500" />

View File

@ -58,6 +58,16 @@ interface AgentWorkflowPanelDarkV2Props {
showcaseConfig?: AgentWorkflowShowcaseConfig | null; showcaseConfig?: AgentWorkflowShowcaseConfig | null;
topNavRightSlot?: ReactNode; topNavRightSlot?: ReactNode;
embedded?: boolean; 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: { 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({ export function AgentWorkflowPanelDarkV2({
config, config,
inputs, inputs,
@ -186,20 +267,8 @@ export function AgentWorkflowPanelDarkV2({
showcaseConfig, showcaseConfig,
topNavRightSlot, topNavRightSlot,
embedded = false, embedded = false,
showBrandStoreSection = true,
}: AgentWorkflowPanelDarkV2Props) { }: 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 resolvedConfig = useMemo(() => {
const source = config || defaultAgentWorkflowUiConfig; const source = config || defaultAgentWorkflowUiConfig;
return { return {
@ -385,54 +454,27 @@ export function AgentWorkflowPanelDarkV2({
const workflowContent = ( const workflowContent = (
<div className="space-y-8"> <div className="space-y-8">
{/* 品牌和店铺选择 */} {showBrandStoreSection ? (
<motion.section <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)]" 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 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }} transition={{ delay: 0.1 }}
> >
<h2 className="mb-4 flex items-center gap-2 text-lg font-semibold text-white"> <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"> <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 1
</span> </span>
品牌和店铺选择 品牌和店铺选择
</h2> </h2>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 max-w-2xl"> <AgentWorkflowBrandStorePanel
<SearchableSelect config={config}
value={inputs.brand} inputs={inputs}
options={brandOptions} onInputsChange={onInputsChange}
placeholder="选择品牌"
searchPlaceholder="输入品牌名筛选"
emptyText="暂无品牌可选"
theme="dark" theme="dark"
onValueChange={(value) =>
onInputsChange({
...inputs,
brand: value,
store:
value.trim().toLowerCase() === inputs.brand.trim().toLowerCase()
? inputs.store
: "",
})
}
/> />
<SearchableSelect </motion.section>
value={inputs.store} ) : null}
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>
{/* 工作流步骤 */} {/* 工作流步骤 */}
{embedded ? ( {embedded ? (