From c0a8934c3019818435c3051cfe4864f38ab1bb25 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 18 Mar 2026 13:21:21 +0800 Subject: [PATCH] feat: refine agent workflow layout and settings --- .../agent-workflow-settings-client.tsx | 1681 +++++++++-------- .../agents/AgentGenerationWorkspaceDarkV2.tsx | 30 +- components/agents/AgentPresetPanelDarkV2.tsx | 12 +- 3 files changed, 973 insertions(+), 750 deletions(-) diff --git a/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx b/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx index 4d13b33a4..e76cb37d3 100644 --- a/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx +++ b/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx @@ -4,15 +4,26 @@ import Link from "next/link"; import { useEffect, useMemo, useRef, useState } from "react"; import useSWR from "swr"; import { toast } from "sonner"; +import { motion, AnimatePresence } from "framer-motion"; import { ArrowLeft, + ChevronDown, + ChevronRight, + GripVertical, Image as ImageIcon, + Layers, Loader2, + MonitorPlay, + Package, Plus, Save, + Search, + Settings2, Sparkles, Trash2, Upload, + UserCircle, + Video, } from "lucide-react"; import { defaultAgentWorkflowUiConfig, @@ -37,6 +48,11 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; +import { Badge } from "@/components/ui/badge"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import { Separator } from "@/components/ui/separator"; +import { cn } from "@/lib/utils"; import type { AgentWorkflowBrandModelConfig, AgentWorkflowMaterialApiConfig, @@ -48,6 +64,20 @@ import type { const fetcher = (url: string) => fetch(url, { cache: "no-store" }).then((res) => res.json()); +// Animation variants +const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { staggerChildren: 0.05 } + } +}; + +const itemVariants = { + hidden: { opacity: 0, y: 10 }, + visible: { opacity: 1, y: 0 } +}; + function normalizeConfigOrdering(config: AgentWorkflowUiConfig): AgentWorkflowUiConfig { return { ...config, @@ -151,6 +181,8 @@ export function AgentWorkflowSettingsClient({ ); const [isSaving, setIsSaving] = useState(false); const [uploadingKey, setUploadingKey] = useState(null); + const [expandedSteps, setExpandedSteps] = useState>(new Set()); + const [activeTab, setActiveTab] = useState("showcase"); const fileInputRefs = useRef>({}); useEffect(() => { @@ -165,6 +197,10 @@ export function AgentWorkflowSettingsClient({ setBrandModelConfig( normalizeAgentWorkflowBrandModelConfig(data.agentWorkflowBrandModelConfig) ); + // Expand first step by default + if (data.agentWorkflowUiConfig?.steps?.[0]) { + setExpandedSteps(new Set([data.agentWorkflowUiConfig.steps[0].key])); + } }, [data]); const configuredBrands = useMemo( @@ -185,6 +221,27 @@ export function AgentWorkflowSettingsClient({ [config.steps] ); + const enabledStepsCount = useMemo( + () => sortedSteps.filter((s) => s.enabled).length, + [sortedSteps] + ); + const totalOptionsCount = useMemo( + () => sortedSteps.reduce((acc, s) => acc + s.options.filter((o) => o.enabled).length, 0), + [sortedSteps] + ); + + const toggleStepExpansion = (stepKey: string) => { + setExpandedSteps((prev) => { + const next = new Set(prev); + if (next.has(stepKey)) { + next.delete(stepKey); + } else { + next.add(stepKey); + } + return next; + }); + }; + const updateStep = ( stepKey: string, updater: (step: AgentWorkflowUiConfig["steps"][number]) => AgentWorkflowUiConfig["steps"][number] @@ -213,15 +270,17 @@ export function AgentWorkflowSettingsClient({ }; const handleAddStep = () => { - setConfig((prev) => - normalizeConfigOrdering({ + setConfig((prev) => { + const newStep = createWorkflowStepDraft(prev.steps.length, prev.steps.map((step) => step.key)); + // Auto expand new step + setExpandedSteps((prevExpanded) => new Set([...prevExpanded, newStep.key])); + return normalizeConfigOrdering({ ...prev, - steps: [ - ...prev.steps, - createWorkflowStepDraft(prev.steps.length, prev.steps.map((step) => step.key)), - ], - }) - ); + steps: [...prev.steps, newStep], + }); + }); + // Switch to workflow tab + setActiveTab("workflow"); }; const handleRemoveStep = (stepKey: string) => { @@ -231,6 +290,11 @@ export function AgentWorkflowSettingsClient({ steps: prev.steps.filter((step) => step.key !== stepKey), }) ); + setExpandedSteps((prev) => { + const next = new Set(prev); + next.delete(stepKey); + return next; + }); }; const handleAddOption = (stepKey: string) => { @@ -445,788 +509,923 @@ export function AgentWorkflowSettingsClient({ } }; - return ( -
-
-
-
- - 智能体配置中心 + if (isLoading) { + return ( +
+
+
+ + 加载配置中...
-

智能体工作流配置

-

- 单独维护智能体页面左侧每个环节的功能按钮名称、说明、Prompt 片段和预览图。 -

+
+ ); + } -
- - + return ( +
+ {/* Header */} +
+
+
+
+
+ +
+
+

智能体工作流配置

+

+ 配置智能体页面的视频海报、素材搜索、品牌模特及工作流步骤 +

+
+
+ +
+ + +
+
+
+
+ + {/* Stats Bar */} +
+
+
+
+ + {enabledStepsCount} + + 已启用步骤 +
+
+ + {totalOptionsCount} + + 已启用选项 +
+
+ + {materialApiConfig.enabled ? "已开启" : "已关闭"} + + 素材搜索 +
+
+ + {showcaseConfig.enabled ? "已开启" : "已关闭"} + + 顶部视频 +
+
- - - 功能说明 - - 这里配置的是智能体工作流页面的功能卡片。网站设置页只保留入口,不再承载这些详细配置。 - - - + {/* Main Content */} +
+ + + + + 顶部海报 + + + + 基础配置 + + + + 品牌模特 + + {brandModelConfig.entries.reduce((acc, e) => acc + e.images.length, 0)} + + + + + 工作流步骤 + + {sortedSteps.length} + + + - {isLoading ? ( - - - - - - ) : ( -
- - - 顶部视频海报 - - 配置第一步顶部展示区的视频海报。前台会按固定 21:9 比例自动循环播放,并同步显示可自定义标题、按钮文案和海报叠加文字。 - - - -
-
-
启用顶部视频海报
-
- 关闭后前台会隐藏视频海报展示区,但保留下方工作流内容。 -
-
- - setShowcaseConfig((prev) => ({ - ...prev, - enabled: checked, - })) - } - /> -
- -
-
- - - setShowcaseConfig((prev) => ({ - ...prev, - title: event.target.value, - })) - } - placeholder="三倍速出片,电影级质感" - /> -
-
- - - setShowcaseConfig((prev) => ({ - ...prev, - subtitle: event.target.value, - })) - } - placeholder="第一步:风格选择 第二步:生图模式" - /> -
-
- - - setShowcaseConfig((prev) => ({ - ...prev, - actionText: event.target.value, - })) - } - placeholder="我要创作" - /> -
-
- - - setShowcaseConfig((prev) => ({ - ...prev, - footerText: event.target.value, - })) - } - placeholder="用户作品" - /> -
-
- -
- -