feat: move brand setup to first step
This commit is contained in:
parent
5c10380666
commit
2ae376bdfd
@ -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}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
// 嵌入的智能体选择面板
|
// 嵌入的智能体选择面板
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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 ? (
|
||||||
|
|||||||
@ -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" />
|
||||||
|
|||||||
@ -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 ? (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user