diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index 9a37d0fca..4580e8e93 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -8,6 +8,8 @@ import { motion, AnimatePresence } from "framer-motion"; import JSZip from "jszip"; import { ArrowUp, + ChevronDown, + ChevronUp, Download, ImagePlus, Loader2, @@ -20,6 +22,11 @@ import { } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; import { Textarea } from "@/components/ui/textarea"; import { Select, @@ -411,6 +418,7 @@ export function AgentGenerationWorkspaceDarkV2({ : null ) ); + const [isAttachmentSlotExpanded, setIsAttachmentSlotExpanded] = useState(false); const [slotUploading, setSlotUploading] = useState(null); const [selectedModelId, setSelectedModelId] = useState( normalizedIncomingWorkspaceState.selectedModelId || "auto" @@ -533,6 +541,16 @@ export function AgentGenerationWorkspaceDarkV2({ ); const selectedModelAttachment = modelSlotIndex >= 0 && attachments[modelSlotIndex] ? attachments[modelSlotIndex] : null; + const filledAttachmentCount = attachments.filter(Boolean).length; + const missingAttachmentCount = Math.max(requiredUploadCount - filledAttachmentCount, 0); + const productAttachmentSummary = `${selectedProductAttachments.length}/${productSlotCount} 张产品图`; + const modelAttachmentSummary = modelSlotEnabled + ? selectedModelAttachment + ? "模特图已带入" + : "模特图待补充" + : null; + const attachmentSummaryReady = + hasAgentUploadSlots && (requiredUploadCount === 0 || filledAttachmentCount >= requiredUploadCount); const resolvedAgentModeState = agentModeState || localAgentModeState; const emitAgentModeState = onAgentModeStateChange || setLocalAgentModeState; const detailReplacementState = resolvedAgentModeState.detailReplacement; @@ -869,6 +887,10 @@ export function AgentGenerationWorkspaceDarkV2({ (image) => image.source === "primary" || image.source === "secondary" ); const hasSatisfiedSlotRequirement = !hasAgentUploadSlots || attachmentCount >= requiredUploadCount; + const slotRequirementMessage = + hasAgentUploadSlots && !hasSatisfiedSlotRequirement + ? `当前还缺少 ${missingAttachmentCount} 张输入图,请先在“选择 SPU / SKU”步骤补齐后再继续。` + : null; const mergedSettingsStepLabel = hasMaterialStep ? "选图与生成" : "生成参数"; const mergedSettingsStepDescription = hasMaterialStep ? "搜索素材、补充要求、确认槽位并开始生成" @@ -1077,6 +1099,26 @@ export function AgentGenerationWorkspaceDarkV2({ } }, [latestGeneratedGroup?.imageUrls, selectedResultUrl]); + useEffect(() => { + if (!selectedAgentPresetId || !hasAgentUploadSlots) { + setIsAttachmentSlotExpanded(true); + return; + } + if (filledAttachmentCount < requiredUploadCount) { + setIsAttachmentSlotExpanded(true); + return; + } + if (hasMaterialStep) { + setIsAttachmentSlotExpanded(false); + } + }, [ + filledAttachmentCount, + hasMaterialStep, + hasAgentUploadSlots, + requiredUploadCount, + selectedAgentPresetId, + ]); + useEffect(() => { if (!selectedAgentPresetId) { setActiveToolStep("preset"); @@ -1392,6 +1434,19 @@ export function AgentGenerationWorkspaceDarkV2({ [toolSteps] ); const scrollToSection = useCallback((sectionId: string) => { + if ( + (sectionId === "agent-anchor-generate" || sectionId === "agent-anchor-results") && + hasAgentUploadSlots && + !hasSatisfiedSlotRequirement + ) { + toast.error(slotRequirementMessage || "请先补齐输入图后再继续"); + const fallbackTarget = hasMaterialStep ? "agent-anchor-materials" : "agent-anchor-generate"; + const fallbackElement = typeof document !== "undefined" + ? document.getElementById(fallbackTarget) + : null; + fallbackElement?.scrollIntoView({ behavior: "smooth", block: "start" }); + return; + } if (typeof document === "undefined") { return; } @@ -1400,7 +1455,7 @@ export function AgentGenerationWorkspaceDarkV2({ return; } target.scrollIntoView({ behavior: "smooth", block: "start" }); - }, []); + }, [hasAgentUploadSlots, hasMaterialStep, hasSatisfiedSlotRequirement, slotRequirementMessage]); const updateDetailReplacementState = useCallback( ( updater: @@ -1610,90 +1665,160 @@ export function AgentGenerationWorkspaceDarkV2({ }, [updateSkuReplacementState]); const attachmentSlotPanel = ( -
-

- - 输入图槽位 -

-
- 上一步已选的白底图和品牌模特图会自动带入对应槽位,这里只保留最终检查与单张上传替换,不再重复展示已选素材列表。 -
- {agentUploadSlots.length > 0 ? ( -
-
- {agentUploadSlots.map((slot, index) => { - return ( - - { - fileInputRefs.current[index] = el; - }} - type="file" - accept="image/*" - className="hidden" - onChange={(e) => handleSlotFileChange(index, e)} - /> - {attachments[index] ? ( - <> - {attachments[index]!.name -
- {slot.label} + +
+
+
+
+ + 输入图槽位 +
+ + {agentUploadSlots.length > 0 ? ( + <> +
+ + {productAttachmentSummary} + + {modelAttachmentSummary ? ( + + {modelAttachmentSummary} + + ) : null} + + 共 {agentUploadSlots.length} 个输入槽位 + +
+ +

+ {missingAttachmentCount > 0 + ? `当前还需补充 ${missingAttachmentCount} 张输入素材,展开后可继续检查或替换。` + : "素材已自动带入并准备完成,默认折叠显示;需要时再展开检查或替换即可。"} +

+ + ) : ( +

+ 先选择一个生图模式,系统会按模板配置展示对应的产品图和模特图槽位。 +

+ )} +
+ + {agentUploadSlots.length > 0 ? ( + + + + ) : null} +
+ + {agentUploadSlots.length > 0 ? ( + +
+ 上一步已选的白底图和品牌模特图会自动带入对应槽位,这里只保留最终检查与单张上传替换,不再重复展示已选素材列表。 +
+ +
+ {agentUploadSlots.map((slot, index) => { + return ( + + { + fileInputRefs.current[index] = el; + }} + type="file" + accept="image/*" + className="hidden" + onChange={(e) => handleSlotFileChange(index, e)} + /> + {attachments[index] ? ( + <> + {attachments[index]!.name +
+ {slot.label} +
+ + + ) : ( +
+ {slotUploading === index ? ( + + ) : ( + <> + + {slot.label} + + )}
+ )} + +
- - ) : ( -
- {slotUploading === index ? ( - - ) : ( - <> - - {slot.label} - - )}
- )} - -
- -
- - ); - })} + + ); + })} +
+ + ) : ( +
+ + 先选择一个生图模式,系统会按模板配置展示对应的产品图和模特图槽位。
-
- ) : ( -
- - 先选择一个生图模式,系统会按模板配置展示对应的产品图和模特图槽位。 -
- )} -
+ )} +
+ ); return ( @@ -1734,22 +1859,42 @@ export function AgentGenerationWorkspaceDarkV2({ >
- {unifiedAnchorSteps.map((step) => ( - - ))} + {unifiedAnchorSteps.map((step) => { + const isGenerateAnchor = step.id === "agent-anchor-generate"; + const isBlockedGenerateAnchor = + isGenerateAnchor && hasAgentUploadSlots && !hasSatisfiedSlotRequirement; + + return ( + + ); + })}
@@ -1890,7 +2035,30 @@ export function AgentGenerationWorkspaceDarkV2({ />
- {attachmentSlotPanel} + {hasAgentUploadSlots && hasMaterialStep && hasSatisfiedSlotRequirement ? ( +
+
+
输入图已在上一步确认
+
+ {productAttachmentSummary} + {modelAttachmentSummary ? `,${modelAttachmentSummary}` : ""} + 。如需微调,再手动展开编辑即可。 +
+
+ +
+ ) : null} + + {(!hasMaterialStep || !hasSatisfiedSlotRequirement || isAttachmentSlotExpanded) + ? attachmentSlotPanel + : null}

@@ -1945,7 +2113,11 @@ export function AgentGenerationWorkspaceDarkV2({ + {slotRequirementMessage ? ( +
+ {slotRequirementMessage} +
+ ) : null}

diff --git a/components/agents/AgentPresetPanelDarkV2.tsx b/components/agents/AgentPresetPanelDarkV2.tsx index 854f0b65a..17b47060b 100644 --- a/components/agents/AgentPresetPanelDarkV2.tsx +++ b/components/agents/AgentPresetPanelDarkV2.tsx @@ -42,6 +42,8 @@ interface AgentPresetPanelDarkV2Props { embeddedEntityLabel?: string; } +const AGENT_PRESET_LIBRARY_ALL_MODES_ID = "__all_modes__"; + export function AgentPresetPanelDarkV2({ selectedPresetId, onSelectPreset, @@ -50,7 +52,7 @@ export function AgentPresetPanelDarkV2({ embeddedEntityLabel = "智能体", }: AgentPresetPanelDarkV2Props) { const { data: session } = useSession(); - const [activeTab, setActiveTab] = useState(AGENT_PRESET_LIBRARY_DEFAULT_SECTION_ID); + const [activeTab, setActiveTab] = useState(AGENT_PRESET_LIBRARY_ALL_MODES_ID); const [keyword, setKeyword] = useState(""); const [showCreateDialog, setShowCreateDialog] = useState(false); const [editingPreset, setEditingPreset] = useState(null); @@ -87,24 +89,33 @@ export function AgentPresetPanelDarkV2({ [presetLibraryConfig] ); const navTabs = useMemo(() => { + const allModesTab = { + id: AGENT_PRESET_LIBRARY_ALL_MODES_ID, + label: "所有模式", + icon: Grid3X3, + isMyWorks: false, + isAllModes: true, + }; const dynamicTabs = presetLibrarySections.map((section) => ({ id: section.id, label: section.label, icon: LayoutGrid, isMyWorks: false, + isAllModes: false, })); const myWorksTab = { id: AGENT_PRESET_LIBRARY_MY_WORKS_ID, label: "我的模式", icon: FolderOpen, isMyWorks: true, + isAllModes: false, }; if (dynamicTabs.length === 0) { - return [myWorksTab]; + return [allModesTab, myWorksTab]; } - return [...dynamicTabs, myWorksTab]; + return [allModesTab, ...dynamicTabs, myWorksTab]; }, [presetLibrarySections]); const activeSection = useMemo( () => presetLibrarySections.find((section) => section.id === activeTab) || null, @@ -130,6 +141,7 @@ export function AgentPresetPanelDarkV2({ () => getEnabledAgentPresetLibraryNodes(selectedLevel2?.children || []), [selectedLevel2] ); + const isAllModesTab = activeTab === AGENT_PRESET_LIBRARY_ALL_MODES_ID; const isMyWorksTab = activeTab === AGENT_PRESET_LIBRARY_MY_WORKS_ID; const canManagePublicPresets = session?.user?.type === "admin" || session?.user?.type === "agent_admin"; @@ -168,7 +180,7 @@ export function AgentPresetPanelDarkV2({ const filteredPresets = useMemo(() => { let presets: AgentPreset[] = isMyWorksTab ? data?.private || [] : data?.public || []; - if (!isMyWorksTab) { + if (!isMyWorksTab && !isAllModesTab) { const fallbackSectionId = presetLibrarySections.find((section) => section.id === AGENT_PRESET_LIBRARY_DEFAULT_SECTION_ID)?.id || presetLibrarySections[0]?.id || @@ -210,6 +222,7 @@ export function AgentPresetPanelDarkV2({ }, [ activeTab, data, + isAllModesTab, isMyWorksTab, keyword, presetLibraryConfig, @@ -248,6 +261,7 @@ export function AgentPresetPanelDarkV2({ // 获取当前标签标题 const getCurrentTitle = () => { + if (isAllModesTab) return "所有模式"; if (isMyWorksTab) return "我的模式"; return activeSection?.label || "生图模式"; }; @@ -303,7 +317,7 @@ export function AgentPresetPanelDarkV2({ ))} - {!isMyWorksTab ? ( + {!isMyWorksTab && !isAllModesTab ? (
- {!isMyWorksTab ? ( + {!isMyWorksTab && !isAllModesTab ? (