diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index 56b9fedaa..ba722d3a9 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -2247,6 +2247,198 @@ export function AgentGenerationWorkspaceDarkV2({ + {isDetailReplacementPreset ? ( + +
+
+
+
参考详情搜索
+
+ 按 SPU 查询候选整套,并选择一套{detailReferenceSourceLabel}参考图进入下方结果区。 +
+
+
+ 当前已选 {detailReplacementReferenceCards.length} 张 +
+
+
+ +
+
+ 按SPU查询 +
+ +
+ + updateDetailReplacementState({ + query: e.target.value, + queryType: "spu", + }) + } + placeholder={`输入 SPU,查询任务整套中的${detailReferenceSourceLabel}参考图`} + className="h-12 rounded-[22px] border-white/10 bg-black/20 text-sm text-white placeholder:text-white/22 focus:border-emerald-400/30" + /> + + {detailReplacementReferenceCards.length > 0 ? ( + + ) : null} +
+ + {(detailReplacementSearchState.lastQuery || detailReplacementQueryInput.trim()) ? ( +
+ 当前参考图查询: + SPU / + {detailReplacementSearchState.lastQuery || + detailReplacementQueryInput.trim().toUpperCase()} + {workflowStoreName ? ( + {`店铺 / ${workflowStoreName}`} + ) : null} + + {`已接入真实${detailReferenceSourceLabel}参考图接口`} + +
+ ) : null} + + {detailReplacementCandidates.length > 0 ? ( +
+
+
+ 候选整套素材 {detailReplacementCandidates.length} 套 +
+
+ 默认载入最新一套,可随时切换 +
+
+
+ {detailReplacementCandidates.map((candidate) => { + const isSelected = detailReplacementSelectedCandidateId === candidate.id; + const previewUrls = getCandidatePreviewUrls( + candidate, + detailReferenceTarget, + 4 + ); + const primaryPreviewUrl = + previewUrls[0] || getCandidatePreviewUrl(candidate, detailReferenceTarget); + const uploadedAtText = (() => { + const raw = candidate.uploadedAt ? new Date(candidate.uploadedAt) : null; + if (!raw || Number.isNaN(raw.getTime())) return "未记录"; + return raw.toLocaleString("zh-CN", { + timeZone: "Asia/Shanghai", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + }); + })(); + const summaryBadges = [ + `${detailReferenceSourceLabel}`, + `${getCandidateTargetCount(candidate, detailReferenceTarget)}张`, + candidate.categoryName || null, + ].filter((item): item is string => Boolean(item)); + return ( + + ); + })} +
+
+ ) : null} +
+
+ ) : null} + -
-
- 按SPU查询 -
-
- -
- - updateDetailReplacementState({ - query: e.target.value, - queryType: "spu", - }) - } - placeholder={`输入 SPU,查询任务整套中的${detailReferenceSourceLabel}参考图`} - className="h-12 rounded-[22px] border-white/10 bg-black/20 text-sm text-white placeholder:text-white/22 focus:border-emerald-400/30" - /> - - {detailReplacementReferenceCards.length > 0 ? ( - - ) : null} -
- - {(detailReplacementSearchState.lastQuery || detailReplacementQueryInput.trim()) ? ( -
- 当前参考图查询: - SPU / - {detailReplacementSearchState.lastQuery || - detailReplacementQueryInput.trim().toUpperCase()} - {workflowStoreName ? ( - {`店铺 / ${workflowStoreName}`} - ) : null} - - {`已接入真实${detailReferenceSourceLabel}参考图接口`} - -
- ) : null} - - {detailReplacementCandidates.length > 0 ? ( -
-
-
- 候选整套素材 {detailReplacementCandidates.length} 套 -
-
- 默认载入最新一套,可随时切换 -
-
-
- {detailReplacementCandidates.map((candidate) => { - const isSelected = detailReplacementSelectedCandidateId === candidate.id; - const previewUrls = getCandidatePreviewUrls( - candidate, - detailReferenceTarget, - 4 - ); - const primaryPreviewUrl = previewUrls[0] || getCandidatePreviewUrl(candidate, detailReferenceTarget); - const uploadedAtText = (() => { - const raw = candidate.uploadedAt ? new Date(candidate.uploadedAt) : null; - if (!raw || Number.isNaN(raw.getTime())) return "未记录"; - return raw.toLocaleString("zh-CN", { - timeZone: "Asia/Shanghai", - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - }); - })(); - const summaryBadges = [ - `${detailReferenceSourceLabel}`, - `${getCandidateTargetCount(candidate, detailReferenceTarget)}张`, - candidate.categoryName || null, - ].filter((item): item is string => Boolean(item)); - return ( - - ); - })} -
-
- ) : null} -
{detailReplacementReferenceCards.length > 0 ? ( detailReplacementReferenceCards.map((card, index) => ( diff --git a/components/agents/AgentWorkflowMaterialPicker.tsx b/components/agents/AgentWorkflowMaterialPicker.tsx index 085933339..2e601f33b 100644 --- a/components/agents/AgentWorkflowMaterialPicker.tsx +++ b/components/agents/AgentWorkflowMaterialPicker.tsx @@ -1,7 +1,7 @@ "use client"; -import { useMemo } from "react"; -import { Check, ImageIcon, Loader2, RefreshCw, Search } from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; +import { Check, ChevronDown, ChevronUp, ImageIcon, Loader2, RefreshCw, Search } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -59,10 +59,19 @@ export function AgentWorkflowMaterialPicker({ theme = "light", }: AgentWorkflowMaterialPickerProps) { const isDark = theme === "dark"; + const [isImagePreviewExpanded, setIsImagePreviewExpanded] = useState(true); + const [hasManualSelectionInteraction, setHasManualSelectionInteraction] = useState(false); const selectedSet = useMemo( () => new Set((selectedImageIds || []).map((item) => String(item || "").trim()).filter(Boolean)), [selectedImageIds] ); + const recommendedImageIds = useMemo( + () => + Array.isArray(result?.recommendedImageIds) + ? result.recommendedImageIds.map((item) => String(item || "").trim()).filter(Boolean) + : [], + [result?.recommendedImageIds] + ); const selectedCount = result?.images ? result.images.filter((item) => selectedSet.has(item.id)).length : 0; @@ -102,8 +111,31 @@ export function AgentWorkflowMaterialPicker({ ].filter((item) => Boolean(item.value)); const remainingSelectableCount = resolvedSelectionLimit === null ? null : Math.max(resolvedSelectionLimit - selectedCount, 0); + const hasSystemRecommendedSelection = + result?.queryType === "sku" && + recommendedImageIds.length > 0 && + selectedImageIds.length === recommendedImageIds.length && + recommendedImageIds.every((id) => selectedSet.has(id)); + const isAutoSelectedResult = + Boolean(hasSystemRecommendedSelection) && !hasManualSelectionInteraction; + const previewResetKey = useMemo( + () => + JSON.stringify({ + queryType: result?.queryType || "", + matchedSku: result?.matchedSku || "", + imageIds: result?.images?.map((image) => image.id) || [], + recommendedImageIds, + }), + [recommendedImageIds, result?.images, result?.matchedSku, result?.queryType] + ); + + useEffect(() => { + setHasManualSelectionInteraction(false); + setIsImagePreviewExpanded(!hasSystemRecommendedSelection); + }, [hasSystemRecommendedSelection, previewResetKey]); const handleToggleImage = (imageId: string) => { + setHasManualSelectionInteraction(true); const next = new Set(selectedImageIds); if (next.has(imageId)) { next.delete(imageId); @@ -118,6 +150,7 @@ export function AgentWorkflowMaterialPicker({ }; const handleSelectAll = () => { + setHasManualSelectionInteraction(true); const allIds = result?.images ?.filter((item) => Boolean(String(item.imageUrl || "").trim())) @@ -324,7 +357,10 @@ export function AgentWorkflowMaterialPicker({ type="button" variant="outline" size="sm" - onClick={() => onSelectedImageIdsChange([])} + onClick={() => { + setHasManualSelectionInteraction(true); + onSelectedImageIdsChange([]); + }} disabled={selectedCount === 0 && !hasSelectableImages} className={cn( isDark && @@ -429,91 +465,133 @@ export function AgentWorkflowMaterialPicker({ ) : null} {result.images?.length ? ( -
- {result.images.map((image) => { - const selected = selectedSet.has(image.id); - const isPlaceholder = !String(image.imageUrl || "").trim(); - return ( -
-
-
- {isPlaceholder ? "正在获取 SKU 图片..." : image.fileName} -
-
- {isPlaceholder - ? "已先展示预览卡位,图片会逐步加载出来。" - : `系统会先按权重自动选图,你也可以点击${selected ? "取消" : "调整"}。`} -
-
- - ); - })} + {isPlaceholder ? ( +
+ +
+
+
+ ) : ( + {image.label} { + event.currentTarget.style.display = "none"; + }} + /> + )} +
+ + {isPlaceholder ? "加载中" : image.group === "fixed" ? "固定角度" : "其他图片"} + + {!isPlaceholder ? ( + + + + ) : null} +
+
+ {isPlaceholder ? "白底图加载中..." : image.label} +
+
+
+
+ {isPlaceholder ? "正在获取 SKU 图片..." : image.fileName} +
+
+ {isPlaceholder + ? "已先展示预览卡位,图片会逐步加载出来。" + : `系统会先按权重自动选图,你也可以点击${selected ? "取消" : "调整"}。`} +
+
+ + ); + })} +
+ ) : null}
) : null} diff --git a/components/agents/AgentWorkflowMaterialSelectionPanel.tsx b/components/agents/AgentWorkflowMaterialSelectionPanel.tsx index 70c9b6b74..980ef2891 100644 --- a/components/agents/AgentWorkflowMaterialSelectionPanel.tsx +++ b/components/agents/AgentWorkflowMaterialSelectionPanel.tsx @@ -102,38 +102,6 @@ export function AgentWorkflowMaterialSelectionPanel({ typeof productSelectionLimit === "number" && Number.isFinite(productSelectionLimit) && productSelectionLimit > 0 ? Math.max(productSelectionLimit - primarySelectedCount, secondarySelectedCount, 0) : null; - 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) { @@ -273,71 +241,6 @@ export function AgentWorkflowMaterialSelectionPanel({ ) : null} -
-
-
- 选图流程 -
-
- 按这 3 步操作就能完成输入图准备,避免来回试错。 -
-
- -
- {materialGuideSteps.map((step) => ( -
-
- - {step.badge} - -
- {step.label} -
-
-
- {step.description} -
-
- ))} -
-
-