diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx
index 8a034a140..052819aad 100644
--- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx
+++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx
@@ -886,6 +886,97 @@ export function AgentGenerationWorkspaceDarkV2({
(image) => image.source === "primary" || image.source === "secondary"
);
const hasSatisfiedSlotRequirement = !hasAgentUploadSlots || attachmentCount >= requiredUploadCount;
+ const workflowSelectionCount = workflowContext?.selections?.length || 0;
+ const replacementReferenceSummary = isDetailReplacementPreset
+ ? `${detailReplacementReferenceCards.length} 张详情图`
+ : isSkuReplacementPreset
+ ? `${skuReplacementReferenceCards.length} 张 SKU 图`
+ : "当前模式不需要";
+ const taskStatus = !selectedAgentPresetId
+ ? {
+ label: "待选模式",
+ className: "border-white/10 bg-white/[0.06] text-white/72",
+ }
+ : hasAgentUploadSlots && !hasSatisfiedSlotRequirement
+ ? {
+ label: "待补图",
+ className: "border-amber-400/20 bg-amber-500/[0.08] text-amber-100",
+ }
+ : isDetailReplacementPreset && detailReplacementReferenceCards.length === 0
+ ? {
+ label: "待载入详情图",
+ className: "border-amber-400/20 bg-amber-500/[0.08] text-amber-100",
+ }
+ : isSkuReplacementPreset && skuReplacementReferenceCards.length === 0
+ ? {
+ label: "待载入SKU图",
+ className: "border-amber-400/20 bg-amber-500/[0.08] text-amber-100",
+ }
+ : {
+ label: "可开始生成",
+ className: "border-emerald-400/20 bg-emerald-500/[0.08] text-emerald-100",
+ };
+ const taskSummaryItems = [
+ {
+ label: "当前模式",
+ value: selectedAgentPreset?.name || "未选择",
+ },
+ {
+ label: "品牌 / 店铺",
+ value:
+ workflowContext?.brand || workflowContext?.store
+ ? `${workflowContext?.brand || "未选品牌"} / ${workflowContext?.store || "未选店铺"}`
+ : "未选择",
+ },
+ {
+ label: "产品图",
+ value:
+ hasAgentUploadSlots && selectedAgentPreset?.type === "image"
+ ? `${selectedProductAttachments.length}/${productSlotCount} 张`
+ : "当前模式无需",
+ },
+ {
+ label: "模特图",
+ value: modelSlotEnabled ? (selectedModelAttachment ? "已带入" : "待补充") : "当前模式无需",
+ },
+ {
+ label: "风格卡片",
+ value: workflowSelectionCount > 0 ? `${workflowSelectionCount} 项已选` : "待选择",
+ },
+ {
+ label: "参考图",
+ value: replacementReferenceSummary,
+ },
+ ];
+ const taskDirective = !selectedAgentPresetId
+ ? {
+ title: "先选择一个生图模式",
+ description: "模式会决定后续的输入槽位、参考图区和结果执行方式。",
+ }
+ : hasMaterialStep && hasAgentUploadSlots && !hasSatisfiedSlotRequirement
+ ? {
+ title: `先补齐剩余 ${missingAttachmentCount} 张输入图`,
+ description: "输入图没有准备完成前,系统不会开放生成入口,避免用户走到后面才返工。",
+ }
+ : hasWorkflowStyleStep && workflowSelectionCount === 0
+ ? {
+ title: "继续选择风格步骤卡片",
+ description: "把风格卡片补齐后,组合 Prompt 会更完整,结果风格也会更稳定。",
+ }
+ : isDetailReplacementPreset && detailReplacementReferenceCards.length === 0
+ ? {
+ title: "先载入详情参考图",
+ description: "详情替换需要一整套详情参考图,右侧结果会按相同顺序逐屏生成。",
+ }
+ : isSkuReplacementPreset && skuReplacementReferenceCards.length === 0
+ ? {
+ title: "先载入 SKU 参考图",
+ description: "SKU 替换需要先确定一组参考图,系统才会执行一对一替换。",
+ }
+ : {
+ title: "当前已经可以开始生成",
+ description: "现在只需要检查补充要求和生成参数,就可以直接开始执行。",
+ };
const slotRequirementMessage =
hasAgentUploadSlots && !hasSatisfiedSlotRequirement
? `当前还缺少 ${missingAttachmentCount} 张输入图,请先在“选择 SPU / SKU”步骤补齐后再继续。`
@@ -1455,6 +1546,53 @@ export function AgentGenerationWorkspaceDarkV2({
}
target.scrollIntoView({ behavior: "smooth", block: "start" });
}, [hasAgentUploadSlots, hasMaterialStep, hasSatisfiedSlotRequirement, slotRequirementMessage]);
+ const taskSummaryAction = useMemo(() => {
+ if (!selectedAgentPresetId) {
+ return {
+ label: "去选模式",
+ sectionId: "agent-anchor-mode",
+ };
+ }
+ if (hasMaterialStep && hasAgentUploadSlots && !hasSatisfiedSlotRequirement) {
+ return {
+ label: "去补图",
+ sectionId: "agent-anchor-materials",
+ };
+ }
+ if (hasWorkflowStyleStep && workflowSelectionCount === 0) {
+ return {
+ label: "去选风格",
+ sectionId: "agent-anchor-style",
+ };
+ }
+ if (isDetailReplacementPreset && detailReplacementReferenceCards.length === 0) {
+ return {
+ label: "去查详情图",
+ sectionId: "agent-anchor-results",
+ };
+ }
+ if (isSkuReplacementPreset && skuReplacementReferenceCards.length === 0) {
+ return {
+ label: "去查SKU参考图",
+ sectionId: "agent-anchor-results",
+ };
+ }
+ return {
+ label: "前往生成区",
+ sectionId: "agent-anchor-generate",
+ };
+ }, [
+ detailReplacementReferenceCards.length,
+ hasAgentUploadSlots,
+ hasMaterialStep,
+ hasSatisfiedSlotRequirement,
+ hasWorkflowStyleStep,
+ isDetailReplacementPreset,
+ isSkuReplacementPreset,
+ selectedAgentPresetId,
+ skuReplacementReferenceCards.length,
+ workflowSelectionCount,
+ ]);
const updateDetailReplacementState = useCallback(
(
updater:
@@ -1897,6 +2035,59 @@ export function AgentGenerationWorkspaceDarkV2({
+
+
+
+
+
+ 下一步建议
+
+
+ {taskStatus.label}
+
+
+
+
{taskDirective.title}
+
+ {taskDirective.description}
+
+
+
+ {taskSummaryItems.map((item) => (
+
+
{item.label}
+
+ {item.value}
+
+
+ ))}
+
+
+
+
+
+ {slotRequirementMessage || taskDirective.description}
+
+
+
+
+
+
diff --git a/components/agents/AgentPresetCardDark.tsx b/components/agents/AgentPresetCardDark.tsx
index 6b70bad97..14f9693eb 100644
--- a/components/agents/AgentPresetCardDark.tsx
+++ b/components/agents/AgentPresetCardDark.tsx
@@ -3,7 +3,10 @@
import { motion } from "framer-motion";
import { Play, Copy, Pencil, Sparkles, Trash2, Video, Wand2, FileText, Clock, User } from "lucide-react";
import { cn } from "@/lib/utils";
-import { getAgentPresetModeTypeFromDefaultParams } from "@/lib/agents/preset-mode-config";
+import {
+ getAgentPresetModeTypeFromDefaultParams,
+ normalizeAgentPresetModeConfig,
+} from "@/lib/agents/preset-mode-config";
import type { AgentPreset } from "@/types/agent";
interface AgentPresetCardDarkProps {
@@ -75,6 +78,7 @@ export function AgentPresetCardDark({
const typeInfo = typeConfig[preset.type] || typeConfig.image;
const TypeIcon = typeInfo.icon;
const imageModeType = getAgentPresetModeTypeFromDefaultParams(preset.defaultParams);
+ const modeConfig = normalizeAgentPresetModeConfig(preset.defaultParams);
const imageModeLabel =
imageModeType === "detail_replacement"
? "详情替换"
@@ -83,6 +87,36 @@ export function AgentPresetCardDark({
: imageModeType === "scene_generation"
? "场景图"
: null;
+ const requiresImageCount =
+ preset.type === "image" ? Math.max(0, preset.requiredUploadCount || preset.slotCount || 0) : 0;
+ const includesModelSlot =
+ preset.type === "image"
+ ? imageModeType === "scene_generation"
+ ? modeConfig.scene.includeModelSlot
+ : imageModeType === "detail_replacement"
+ ? modeConfig.detail.includeModelSlot
+ : imageModeType === "sku_replacement"
+ ? modeConfig.sku.includeModelSlot
+ : preset.requiredUploadCount > preset.slotCount
+ : false;
+ const modelControlLabel =
+ preset.type === "image"
+ ? imageModeType === "scene_generation" && modeConfig.scene.allowModelOverride
+ ? "可切模型"
+ : "锁定模型"
+ : null;
+ const categoryHint =
+ preset.categoryPath?.level3Name ||
+ preset.categoryPath?.level2Name ||
+ preset.categoryPath?.level1Name ||
+ preset.categoryPath?.sectionName ||
+ null;
+ const compactSummaryBadges = [
+ imageModeLabel || typeInfo.label,
+ preset.type === "image" && requiresImageCount > 0 ? `需${requiresImageCount}张图` : null,
+ preset.type === "image" ? (includesModelSlot ? "含模特槽" : "无模特槽") : null,
+ modelControlLabel,
+ ].filter((item): item is string => Boolean(item));
const showEdit = Boolean(onEdit);
const showDelete = Boolean(onDelete);
const showFork = Boolean(onFork) && !showEdit && !showDelete;
@@ -169,7 +203,32 @@ export function AgentPresetCardDark({
{preset.name}
-
+
+ {compactSummaryBadges.map((badge, badgeIndex) => (
+
+ {badge}
+
+ ))}
+
+
+
+ 默认模型:{preset.defaultModel?.displayName || "未配置"}
+
+ {preset.description ? (
+
{preset.description}
+ ) : categoryHint ? (
+
适用分类:{categoryHint}
+ ) : null}
+
+
贡献者:{ownerName}
@@ -339,6 +398,22 @@ export function AgentPresetCardDark({
{preset.name}
+
+
+ {compactSummaryBadges.slice(0, 4).map((badge, badgeIndex) => (
+
+ {badge}
+
+ ))}
+
{/* 元信息 */}
@@ -347,12 +422,12 @@ export function AgentPresetCardDark({
{ownerName}
- {preset.slotCount > 0 && (
+ {preset.defaultModel?.displayName ? (
- {preset.slotCount}槽
+ {preset.defaultModel.displayName}
- )}
+ ) : null}
{publishedAt && (
diff --git a/components/agents/AgentWorkflowMaterialSelectionPanel.tsx b/components/agents/AgentWorkflowMaterialSelectionPanel.tsx
index c069edf29..ab87a927f 100644
--- a/components/agents/AgentWorkflowMaterialSelectionPanel.tsx
+++ b/components/agents/AgentWorkflowMaterialSelectionPanel.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useEffect, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
import { Plus, Sparkles } from "lucide-react";
import { AgentWorkflowMaterialPicker } from "@/components/agents/AgentWorkflowMaterialPicker";
import { createEmptyAgentWorkflowMaterialSelection } from "@/lib/agents/workflow-material-config";
@@ -101,6 +101,84 @@ export function AgentWorkflowMaterialSelectionPanel({
typeof productSelectionLimit === "number" && Number.isFinite(productSelectionLimit) && productSelectionLimit > 0
? Math.max(productSelectionLimit - primarySelectedCount, secondarySelectedCount, 0)
: null;
+ const primarySelectedImages = useMemo(
+ () =>
+ (materials.primary.result?.images || []).filter((image) =>
+ materials.primary.selectedImageIds.includes(image.id)
+ ),
+ [materials.primary.result?.images, materials.primary.selectedImageIds]
+ );
+ const secondarySelectedImages = useMemo(
+ () =>
+ (materials.secondary.result?.images || []).filter((image) =>
+ materials.secondary.selectedImageIds.includes(image.id)
+ ),
+ [materials.secondary.result?.images, materials.secondary.selectedImageIds]
+ );
+ const slotPreviewCount = useMemo(() => {
+ if (
+ typeof productSelectionLimit === "number" &&
+ Number.isFinite(productSelectionLimit) &&
+ productSelectionLimit > 0
+ ) {
+ return Math.round(productSelectionLimit);
+ }
+ return primarySelectedImages.length + secondarySelectedImages.length;
+ }, [primarySelectedImages.length, productSelectionLimit, secondarySelectedImages.length]);
+ const slotPreviewItems = useMemo(() => {
+ const orderedSources = [
+ ...primarySelectedImages.map((image, index) => ({
+ source: "主商品" as const,
+ image,
+ sourceIndex: index,
+ })),
+ ...secondarySelectedImages.map((image, index) => ({
+ source: "搭配商品" as const,
+ image,
+ sourceIndex: index,
+ })),
+ ];
+
+ return Array.from({ length: slotPreviewCount }, (_, index) => {
+ const assigned = orderedSources[index] || null;
+ return {
+ slotLabel: `产品图${index + 1}`,
+ assigned,
+ };
+ });
+ }, [primarySelectedImages, secondarySelectedImages, slotPreviewCount]);
+ const hasAnyQueryInput =
+ Boolean(String(inputs.primarySku || "").trim()) ||
+ Boolean(String(inputs.secondarySku || "").trim());
+ const hasSkuSelectionPending =
+ Boolean(materials.primary.result?.skuOptions?.length) ||
+ Boolean(materials.secondary.result?.skuOptions?.length);
+ const hasAnyMaterialResult =
+ Boolean(materials.primary.result?.images?.length) ||
+ Boolean(materials.secondary.result?.images?.length) ||
+ hasSkuSelectionPending;
+ const hasAnySelectedImages =
+ materials.primary.selectedImageIds.length > 0 || materials.secondary.selectedImageIds.length > 0;
+ const materialGuideSteps = [
+ {
+ badge: "01",
+ label: "输入型号",
+ description: "先选择按 SPU 或 SKU 搜索,再输入商品型号。",
+ status: hasAnyQueryInput ? "completed" : "current",
+ },
+ {
+ badge: "02",
+ label: "选择SKU",
+ description: "如果是 SPU 查询,先从返回的 SKU 列表里点选具体 SKU。",
+ status: hasSkuSelectionPending ? "current" : hasAnyMaterialResult ? "completed" : "pending",
+ },
+ {
+ badge: "03",
+ label: "勾选图片",
+ description: "勾选你要带入生图的白底图,系统会自动映射到产品槽位。",
+ status: hasAnySelectedImages ? "completed" : hasAnyMaterialResult ? "current" : "pending",
+ },
+ ] as const;
useEffect(() => {
if (hasSecondaryMaterialContent) {
@@ -222,6 +300,157 @@ export function AgentWorkflowMaterialSelectionPanel({
) : null}
+
+
+
+ 选图流程
+
+
+ 按这 3 步操作就能完成输入图准备,避免来回试错。
+
+
+
+
+ {materialGuideSteps.map((step) => (
+
+
+
+ {step.badge}
+
+
+ {step.label}
+
+
+
+ {step.description}
+
+
+ ))}
+
+
+
+ {slotPreviewItems.length > 0 ? (
+
+
+
+ 产品图槽位映射
+
+
+ 当前已选图片会按顺序自动带入后续生图槽位,先主商品,后搭配商品。
+
+
+
+
+ {slotPreviewItems.map((item, index) => {
+ const source = item.assigned?.source || null;
+ const previewUrl = item.assigned?.image.imageUrl || "";
+ const isAssigned = Boolean(item.assigned && previewUrl);
+
+ return (
+
+
+ {isAssigned ? (
+

+ ) : (
+
+ 待选择
+
+ )}
+
+ {item.slotLabel}
+
+
+ {source || "空槽位"}
+
+
+
+
+ {isAssigned
+ ? item.assigned?.image.label || `已映射到${item.slotLabel}`
+ : `${item.slotLabel}待补图`}
+
+
+ {isAssigned
+ ? `${source}第 ${(item.assigned?.sourceIndex || 0) + 1} 张`
+ : "先在下方搜索并选择图片"}
+
+
+
+ );
+ })}
+
+
+ ) : null}
+
option.buttonName).join(" / ");
+}
+
function StepOptionCard({
option,
step,
@@ -303,6 +319,16 @@ export function AgentWorkflowPanelDarkV2({
() => 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(
@@ -497,6 +523,7 @@ export function AgentWorkflowPanelDarkV2({
selectionValue: selections[step.key],
});
const active = step.key === activeEmbeddedStepEntry.step.key;
+ const stepSummary = stepSelectionSummaryMap[step.key] || "";
return (
);
})}
@@ -545,6 +586,11 @@ export function AgentWorkflowPanelDarkV2({
) : null}
+
+ {stepSelectionSummaryMap[activeEmbeddedStepEntry.step.key]
+ ? `已选:${stepSelectionSummaryMap[activeEmbeddedStepEntry.step.key]}`
+ : "待选择:完成当前卡片后会自动推进到下一步"}
+
{activeEmbeddedStepEntry.step.description ? (
{activeEmbeddedStepEntry.step.description}
@@ -582,6 +628,11 @@ export function AgentWorkflowPanelDarkV2({
) : null}
+
+ {stepSelectionSummaryMap[step.key]
+ ? `已选:${stepSelectionSummaryMap[step.key]}`
+ : "待选择:先确定这一组风格卡片,系统再继续组合 Prompt"}
+
{step.description && (
{step.description}
)}