"use client"; import { Fragment, useEffect, useMemo, useState, type ReactNode } from "react"; import { motion } from "framer-motion"; import { ArrowRight, Check, Copy, Sparkles, ChevronRight } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { SearchableSelect } from "@/components/ui/searchable-select"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import { defaultAgentWorkflowUiConfig, flattenAgentWorkflowSteps, flattenVisibleAgentWorkflowSteps, getAgentWorkflowBrandStoreEntries, getAgentWorkflowPriceBandOptions, getAgentWorkflowSelectedOptionPath, getAgentWorkflowStoreOptionsByBrand, getEnabledAgentWorkflowOptionTree, getEnabledAgentWorkflowStepTree, isAgentWorkflowStepSelectionComplete, serializeAgentWorkflowSelectionPath, } from "@/lib/agents/workflow-ui-config"; import { AgentCanvasTopNavDark } from "@/components/agents/AgentCanvasTopNavDark"; import { AgentWorkflowShowcaseBanner } from "@/components/agents/AgentWorkflowShowcaseBanner"; import { buildAgentWorkflowComposerContext, getAgentWorkflowProgress, } from "@/lib/agents/workflow-composer"; import type { AgentWorkflowComposerSelection, AgentWorkflowMaterialSelections, AgentWorkflowShowcaseConfig, AgentWorkflowStepKey, AgentWorkflowUiConfig, } from "@/types/agent"; export interface AgentWorkflowInputs { primarySku: string; secondarySku: string; brand: string; store: string; priceBand: string; } interface AgentWorkflowPanelDarkV2Props { config?: AgentWorkflowUiConfig | null; inputs: AgentWorkflowInputs; onInputsChange: (next: AgentWorkflowInputs) => void; materials: AgentWorkflowMaterialSelections; onMaterialsChange: (next: AgentWorkflowMaterialSelections) => void; selections: Partial>; onSelectionsChange: (next: Partial>) => void; onReset?: () => void; onNextStep?: () => void; onSkipStep?: () => void; siteName?: string | null; siteLogo?: string | null; siteLogoDark?: string | null; 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: { config: AgentWorkflowUiConfig; selections: Partial>; }) { const result: AgentWorkflowComposerSelection[] = []; for (const { step } of flattenVisibleAgentWorkflowSteps(params.config.steps, params.selections)) { const selectedOptions = getAgentWorkflowSelectedOptionPath({ step, selectionValue: params.selections[step.key], }); selectedOptions.forEach((option) => { result.push({ stepKey: step.key, stepTitle: step.title, optionKey: option.key, optionName: option.buttonName, promptFragment: option.promptFragment || "", }); }); } return result; } function getWorkflowStepSelectionSummary(params: { step: AgentWorkflowUiConfig["steps"][number]; selectionValue?: string; }) { const selectedOptions = getAgentWorkflowSelectedOptionPath({ step: params.step, selectionValue: params.selectionValue, }); if (selectedOptions.length === 0) { return ""; } return selectedOptions.map((option) => option.buttonName).join(" / "); } function StepOptionCard({ option, step, selected, onClick, index, }: { option: { key: string; buttonName: string; description?: string | null; previewImageUrl?: string | null }; step: { title: string }; selected: boolean; onClick: () => void; index: number; }) { return ( {/* 背景 */}
{option.previewImageUrl ? ( <> {option.buttonName}
) : (
)}
{/* 选中标记 */} {selected && ( )} {/* 步骤标签 */}
{step.title}
{/* 底部信息 */}

{option.buttonName}

{option.description && (

{option.description}

)}
{/* 选中边框 */} {selected && (
)} ); } 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 priceBandOptions = useMemo( () => getAgentWorkflowPriceBandOptions(config), [config] ); const isDark = theme === "dark"; return (
{title}
{description}
onInputsChange({ ...inputs, brand: value, store: value.trim().toLowerCase() === inputs.brand.trim().toLowerCase() ? inputs.store : "", }) } /> 0 && storeOptions.length === 0)} onValueChange={(value) => onInputsChange({ ...inputs, store: value })} /> onInputsChange({ ...inputs, priceBand: value })} />
); } export function AgentWorkflowPanelDarkV2({ config, inputs, onInputsChange, materials, onMaterialsChange, selections, onSelectionsChange, onNextStep, onSkipStep, siteName, siteLogo, siteLogoDark, showcaseConfig, topNavRightSlot, embedded = false, showBrandStoreSection = true, }: AgentWorkflowPanelDarkV2Props) { const resolvedConfig = useMemo(() => { const source = config || defaultAgentWorkflowUiConfig; return { steps: getEnabledAgentWorkflowStepTree(source.steps || []), }; }, [config]); const flattenedResolvedSteps = useMemo( () => flattenVisibleAgentWorkflowSteps(resolvedConfig.steps, selections).filter( ({ step }) => Array.isArray(step.options) && step.options.length > 0 ), [resolvedConfig.steps, selections] ); const summary = useMemo( () => buildSelectionSummary({ config: resolvedConfig, selections }), [resolvedConfig, selections] ); const composerContext = useMemo( () => buildAgentWorkflowComposerContext({ config: resolvedConfig, inputs, materials, selections, }), [inputs, materials, resolvedConfig, selections] ); const progress = useMemo( () => getAgentWorkflowProgress({ config: resolvedConfig, selections }), [resolvedConfig, selections] ); const stepSelectionSummaryMap = useMemo(() => { const entries = flattenAgentWorkflowSteps(resolvedConfig.steps).map(({ step }) => [ step.key, getWorkflowStepSelectionSummary({ step, selectionValue: selections[step.key], }), ]); return Object.fromEntries(entries) as Record; }, [resolvedConfig.steps, selections]); const [activeEmbeddedStepKey, setActiveEmbeddedStepKey] = useState(""); const [pendingAutoAdvanceStepKey, setPendingAutoAdvanceStepKey] = useState(null); const activeEmbeddedStepIndex = Math.max( flattenedResolvedSteps.findIndex(({ step }) => step.key === activeEmbeddedStepKey), 0 ); const activeEmbeddedStepEntry = flattenedResolvedSteps[activeEmbeddedStepIndex] || flattenedResolvedSteps[0] || null; useEffect(() => { if (!embedded) { return; } if (flattenedResolvedSteps.length === 0) { if (activeEmbeddedStepKey) { setActiveEmbeddedStepKey(""); } return; } if ( activeEmbeddedStepKey && flattenedResolvedSteps.some(({ step }) => step.key === activeEmbeddedStepKey) ) { return; } setActiveEmbeddedStepKey(flattenedResolvedSteps[0]?.step.key || ""); }, [activeEmbeddedStepKey, embedded, flattenedResolvedSteps]); useEffect(() => { if (!embedded || !activeEmbeddedStepEntry) { return; } if (pendingAutoAdvanceStepKey !== activeEmbeddedStepEntry.step.key) { return; } const currentStepComplete = isAgentWorkflowStepSelectionComplete({ step: activeEmbeddedStepEntry.step, selectionValue: selections[activeEmbeddedStepEntry.step.key], }); if (!currentStepComplete) { return; } const nextStep = flattenedResolvedSteps .slice(activeEmbeddedStepIndex + 1) .find( ({ step }) => !isAgentWorkflowStepSelectionComplete({ step, selectionValue: selections[step.key], }) ); if (nextStep && nextStep.step.key !== activeEmbeddedStepKey) { setActiveEmbeddedStepKey(nextStep.step.key); } setPendingAutoAdvanceStepKey(null); }, [ activeEmbeddedStepEntry, activeEmbeddedStepIndex, activeEmbeddedStepKey, embedded, flattenedResolvedSteps, pendingAutoAdvanceStepKey, selections, ]); const renderOptionLevels = ( step: AgentWorkflowUiConfig["steps"][number], options: AgentWorkflowUiConfig["steps"][number]["options"], depth = 1, parentPath: string[] = [] ): ReactNode => { const selectedPathKeys = getAgentWorkflowSelectedOptionPath({ step, selectionValue: selections[step.key], }).map((option) => option.key); const currentSelectedKey = selectedPathKeys[depth - 1] || ""; const currentSelectedOption = options.find((option) => option.key === currentSelectedKey) || null; const visibleChildren = getEnabledAgentWorkflowOptionTree( currentSelectedOption?.children || [] ); const levelLabel = depth === 1 ? "主卡片" : depth === 2 ? "二级卡片" : "三级卡片"; return (
{levelLabel} {depth > 1 ? ( 该层卡片从上一级已选卡片中继续展开 ) : null}
{options.map((option, optionIndex) => ( { 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, }); }} index={optionIndex} /> ))}
{currentSelectedOption && visibleChildren.length > 0 ? renderOptionLevels(step, visibleChildren, depth + 1, [ ...parentPath.slice(0, depth - 1), currentSelectedOption.key, ]) : null}
); }; const handleCopyPrompt = async () => { if (!composerContext?.promptText) { toast.error("当前还没有可复制的 Prompt"); return; } try { await navigator.clipboard.writeText(composerContext.promptText); toast.success("Prompt 已复制"); } catch { toast.error("复制失败"); } }; const workflowContent = (
{showBrandStoreSection ? (

1 品牌和店铺选择

) : null} {/* 工作流步骤 */} {embedded ? ( activeEmbeddedStepEntry ? ( 1 && "border-l-4 border-l-emerald-500/30" )} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} >
{flattenedResolvedSteps.map(({ step }, stepIndex) => { const completed = isAgentWorkflowStepSelectionComplete({ step, selectionValue: selections[step.key], }); const active = step.key === activeEmbeddedStepEntry.step.key; const stepSummary = stepSelectionSummaryMap[step.key] || ""; return ( ); })}
{renderOptionLevels( activeEmbeddedStepEntry.step, getEnabledAgentWorkflowOptionTree(activeEmbeddedStepEntry.step.options || []) )}
) : null ) : ( flattenedResolvedSteps.map(({ step, depth }, stepIndex) => ( 1 && "ml-3 border-l-4 border-l-emerald-500/30 sm:ml-6" )} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.15 + stepIndex * 0.05 }} >

{stepIndex + 2} {step.title} {depth > 1 ? ( {depth === 2 ? "二级模块" : "三级模块"} ) : null}

{stepSelectionSummaryMap[step.key] ? `已选:${stepSelectionSummaryMap[step.key]}` : "待选择:先确定这一组风格卡片,系统再继续组合 Prompt"}
{step.description && (

{step.description}

)} {renderOptionLevels(step, getEnabledAgentWorkflowOptionTree(step.options || []))}
)) )} {/* Prompt 预览 */}

Prompt 预览