"use client"; import Link from "next/link"; import { startTransition, useDeferredValue, useEffect, useMemo, useRef, useState, type ChangeEvent, type ComponentProps, type DragEvent, type ReactNode, } from "react"; import useSWR from "swr"; import { toast } from "sonner"; import { motion, AnimatePresence } from "framer-motion"; import { ArrowDown, ArrowLeft, ArrowUp, ChevronDown, ChevronRight, ClipboardPaste, Copy, Download, GripVertical, Image as ImageIcon, Layers, Loader2, MonitorPlay, Package, Plus, Save, Search, Settings2, Sparkles, Trash2, Upload, UserCircle, Video, } from "lucide-react"; import { AGENT_WORKFLOW_PROMPT_FRAGMENT_MAX_LENGTH, defaultAgentWorkflowConstraintSettings, defaultAgentWorkflowPageFeatureSettings, AGENT_WORKFLOW_MAX_OPTION_DEPTH, AGENT_WORKFLOW_MAX_TREE_DEPTH, countAgentWorkflowOptions, countAgentWorkflowOptionTree, countAgentWorkflowSteps, defaultAgentWorkflowCategoryLabels, defaultAgentWorkflowUiConfig, findAgentWorkflowOptionByKey, findAgentWorkflowStepByKey, flattenAgentWorkflowOptionTree, getAgentWorkflowSpecialModuleRootOptions, getEnabledAgentWorkflowOptionTree, getAgentWorkflowCategoryLabels, getAgentWorkflowBusinessModeSettings, getAgentWorkflowConstraintSettings, getAgentWorkflowOfficialCategoryEntries, getAgentWorkflowOfficialCategoryKeyLabels, getAgentWorkflowOfficialSecondCategoryKey, getAgentWorkflowOfficialThirdCategoryKey, getAgentWorkflowMissingPreviewOptions, getAgentWorkflowPageFeatureSettings, normalizeAgentWorkflowBusinessModeSettings, normalizeAgentWorkflowOptionOrdering, flattenAgentWorkflowSteps, normalizeAgentWorkflowStepOrdering, normalizeAgentWorkflowUiConfig, } from "@/lib/agents/workflow-ui-config"; import { defaultAgentWorkflowBrandModelConfig, normalizeAgentWorkflowBrandModelConfig, } from "@/lib/agents/workflow-brand-model-config"; import { AGENT_PRESET_LIBRARY_DEFAULT_SECTION_ID, AGENT_PRESET_LIBRARY_MAX_DEPTH, defaultAgentPresetLibraryConfig, normalizeAgentPresetLibraryConfig, } from "@/lib/agents/preset-library-config"; import { defaultAgentWorkflowShowcaseConfig, normalizeAgentWorkflowShowcaseConfig, } from "@/lib/agents/workflow-showcase-config"; import { defaultAgentWorkflowMaterialApiConfig, normalizeAgentWorkflowMaterialApiConfig, } from "@/lib/agents/workflow-material-config"; import { defaultAgentWorkflowGoodsInfoApiConfig, normalizeAgentWorkflowGoodsInfoApiConfig, } from "@/lib/agents/workflow-goods-info-config"; import { defaultAgentPsdTextColorConfig, normalizeAgentPsdTextColorConfig, resolveAgentPsdTextColorConfigForBrand, } from "@/lib/agents/psd-text-color-config"; import { defaultAgentPsdTemplateSecondaryCategoryConfig, type AgentPsdTemplateSecondaryCategoryConfig, normalizeAgentPsdTemplateSecondaryCategoryConfig, } from "@/lib/agents/psd-template-secondary-category-config"; import { AGENT_WORKFLOW_BRAND_IMPORT_ACCEPT, downloadAgentWorkflowBrandImportTemplate, parseAgentWorkflowBrandImportFile, } from "@/lib/agents/workflow-brand-import"; import { normalizeHexColor } from "@/lib/agents/psd-text-layout"; import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Separator } from "@/components/ui/separator"; import { SearchableSelect } from "@/components/ui/searchable-select"; import { cn } from "@/lib/utils"; import type { AgentPresetLibraryCategoryNode, AgentPresetLibraryConfig, AgentPsdTextColorBrandConfig, AgentPresetLibrarySection, AgentWorkflowBusinessModeConfig, AgentWorkflowBusinessModeKey, AgentWorkflowBrandModelConfig, AgentWorkflowBrandStoreEntry, AgentWorkflowCategoryLabels, AgentWorkflowFourthCategoryEntry, AgentWorkflowGoodsInfoApiConfig, AgentWorkflowMaterialApiConfig, AgentWorkflowProductDetailChannel, AgentWorkflowOptionUiConfig, AgentPsdTextColorConfig, AgentWorkflowStoreEntry, AgentWorkflowThirdCategoryEntry, AgentWorkflowShowcaseConfig, AgentWorkflowStepUiConfig, AgentWorkflowUiConfig, } from "@/types/agent"; 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 } }; const CHINESE_LEVEL_NUMERALS = ["一", "二", "三", "四", "五", "六", "七", "八"]; const WORKFLOW_LEVEL_LABELS = CHINESE_LEVEL_NUMERALS.map((item) => `${item}级模块`); const WORKFLOW_OPTION_LEVEL_LABELS = CHINESE_LEVEL_NUMERALS.map((item) => `${item}级卡片`); const PRESET_LIBRARY_LEVEL_LABELS = ["一级分类", "二级分类", "三级分类"]; const WORKFLOW_STEP_DRAG_DATA_TYPE = "application/x-agent-workflow-step"; const WORKFLOW_SETTINGS_DRAFT_STORAGE_KEY = "agent-workflow-settings-draft:v1"; const WORKFLOW_SETTINGS_STALE_DRAFT_STORAGE_KEY = "agent-workflow-settings-stale-draft:v1"; const WORKFLOW_SETTINGS_DRAFT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; const AGENT_WORKFLOW_SETTINGS_ENDPOINT = "/api/site-settings?scope=agent-workflow-admin"; const AGENT_WORKFLOW_SAVE_TIMEOUT_MS = 30_000; const DELETE_WORKFLOW_STEP_PASSWORD = "123456"; const BUSINESS_MODE_DEFAULT_MODEL_EMPTY_VALUE = "__agent_workflow_mode_default_model_empty__"; type WorkflowStepDropPosition = "before" | "after" | "inside"; interface WorkflowStepDropTarget { stepKey: string | null; position: WorkflowStepDropPosition; } interface AgentWorkflowSettingsDraftState { agentWorkflowDefaultModelId: string | null; presetLibraryConfig: AgentPresetLibraryConfig; config: AgentWorkflowUiConfig; materialApiConfig: AgentWorkflowMaterialApiConfig; goodsInfoApiConfig: AgentWorkflowGoodsInfoApiConfig; brandModelConfig: AgentWorkflowBrandModelConfig; showcaseConfig: AgentWorkflowShowcaseConfig; psdTextColorConfig: AgentPsdTextColorConfig; psdTemplateSecondaryCategoryConfig: AgentPsdTemplateSecondaryCategoryConfig; } interface AgentWorkflowSettingsDraft { version: 1 | 2; updatedAt: number; baseSignature?: string; baseSignatures?: Partial>; state: AgentWorkflowSettingsDraftState; } interface WorkflowOptionContentSummary { total: number; withPrompt: number; withDescription: number; withPreview: number; disabled: number; withMutualExclusion: number; } interface WorkflowStepAnalysis { key: string; title: string; path: string; depth: number; childStepCount: number; directRootOptionCount: number; optionStats: WorkflowOptionContentSummary; subtreeStepCount: number; subtreeOptionCount: number; hasOwnContent: boolean; hasSubtreeContent: boolean; enabled: boolean; } interface WorkflowOptionGapAnalysis { key: string; label: string; path: string; stepKey: string; stepPath: string; depth: number; missingFields: string[]; disabled: boolean; childCount: number; } type BufferedInputProps = Omit< ComponentProps, "defaultValue" | "onChange" | "value" > & { commitDelayMs?: number; onValueCommit: (value: string) => void; value: string; }; type BufferedTextareaProps = Omit< ComponentProps, "defaultValue" | "onChange" | "value" > & { commitDelayMs?: number; onValueCommit: (value: string) => void; value: string; }; function scheduleWorkflowIdleTask(callback: () => void, timeoutMs = 2000) { if (typeof window === "undefined") { return () => {}; } if ("requestIdleCallback" in window && "cancelIdleCallback" in window) { const idleId = window.requestIdleCallback(callback, { timeout: timeoutMs }); return () => window.cancelIdleCallback(idleId); } const timer = globalThis.setTimeout(callback, timeoutMs); return () => globalThis.clearTimeout(timer); } async function fetchAgentWorkflowSettingsWithTimeout( input: RequestInfo | URL, init: RequestInit = {}, timeoutMs = AGENT_WORKFLOW_SAVE_TIMEOUT_MS ) { const controller = new AbortController(); const timeoutId = globalThis.setTimeout(() => controller.abort(), timeoutMs); try { return await fetch(input, { ...init, signal: controller.signal, }); } finally { globalThis.clearTimeout(timeoutId); } } function BufferedInput({ commitDelayMs = 450, onBlur, onCompositionEnd, onCompositionStart, onFocus, onValueCommit, value, ...props }: BufferedInputProps) { const [draftValue, setDraftValue] = useState(value); const committedValueRef = useRef(value); const draftValueRef = useRef(value); const isFocusedRef = useRef(false); const isComposingRef = useRef(false); useEffect(() => { if (isFocusedRef.current && draftValueRef.current !== committedValueRef.current) { return; } setDraftValue(value); draftValueRef.current = value; committedValueRef.current = value; }, [value]); const commitValue = (nextValue: string, options?: { immediate?: boolean }) => { if (nextValue === committedValueRef.current) { return; } committedValueRef.current = nextValue; if (options?.immediate) { onValueCommit(nextValue); return; } startTransition(() => { onValueCommit(nextValue); }); }; useEffect(() => { if (draftValue === committedValueRef.current || isComposingRef.current) { return; } const timer = window.setTimeout(() => { commitValue(draftValue); }, commitDelayMs); return () => window.clearTimeout(timer); }, [commitDelayMs, draftValue]); return ( { const nextValue = event.target.value; draftValueRef.current = nextValue; setDraftValue(nextValue); }} onFocus={(event) => { isFocusedRef.current = true; onFocus?.(event); }} onCompositionStart={(event) => { isComposingRef.current = true; onCompositionStart?.(event); }} onCompositionEnd={(event) => { isComposingRef.current = false; const nextValue = event.currentTarget.value; draftValueRef.current = nextValue; setDraftValue(nextValue); commitValue(nextValue); onCompositionEnd?.(event); }} onBlur={(event) => { isFocusedRef.current = false; commitValue(draftValueRef.current, { immediate: true }); onBlur?.(event); }} /> ); } function BufferedTextarea({ commitDelayMs = 450, onBlur, onCompositionEnd, onCompositionStart, onFocus, onValueCommit, value, ...props }: BufferedTextareaProps) { const [draftValue, setDraftValue] = useState(value); const committedValueRef = useRef(value); const draftValueRef = useRef(value); const isFocusedRef = useRef(false); const isComposingRef = useRef(false); useEffect(() => { if (isFocusedRef.current && draftValueRef.current !== committedValueRef.current) { return; } setDraftValue(value); draftValueRef.current = value; committedValueRef.current = value; }, [value]); const commitValue = (nextValue: string, options?: { immediate?: boolean }) => { if (nextValue === committedValueRef.current) { return; } committedValueRef.current = nextValue; if (options?.immediate) { onValueCommit(nextValue); return; } startTransition(() => { onValueCommit(nextValue); }); }; useEffect(() => { if (draftValue === committedValueRef.current || isComposingRef.current) { return; } const timer = window.setTimeout(() => { commitValue(draftValue); }, commitDelayMs); return () => window.clearTimeout(timer); }, [commitDelayMs, draftValue]); return (