"use client"; import { useEffect, useMemo, useState } from "react"; import { Check, ImageIcon, Loader2, RefreshCw, Search } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; import type { AgentWorkflowMaterialQueryType, AgentWorkflowMaterialSearchResult, } from "@/types/agent"; interface AgentWorkflowMaterialPickerProps { title?: string; description?: string; value: string; queryMode: AgentWorkflowMaterialQueryType; placeholder: string; buttonLabel: string; searchNote?: string; disabled?: boolean; disabledHint?: string; isLoading?: boolean; errorText?: string | null; result?: AgentWorkflowMaterialSearchResult | null; selectedImageIds: string[]; selectionLimit?: number | null; selectionLimitLabel?: string; onValueChange: (value: string) => void; onQueryModeChange: (value: AgentWorkflowMaterialQueryType) => void; onSearch: () => void; onSkuOptionSelect?: (skuName: string) => void; onSelectedImageIdsChange: (next: string[]) => void; theme?: "light" | "dark"; } export function AgentWorkflowMaterialPicker({ title, description, value, queryMode, placeholder, buttonLabel, searchNote, disabled = false, disabledHint, isLoading = false, errorText, result, selectedImageIds, selectionLimit = null, selectionLimitLabel = "当前生图模式", onValueChange, onQueryModeChange, onSearch, onSkuOptionSelect, onSelectedImageIdsChange, theme = "light", }: AgentWorkflowMaterialPickerProps) { const isDark = theme === "dark"; const [previewDisplayMode, setPreviewDisplayMode] = useState<"selected" | "all">("all"); 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; const resolvedSelectionLimit = typeof selectionLimit === "number" && Number.isFinite(selectionLimit) && selectionLimit > 0 ? Math.max(1, Math.round(selectionLimit)) : null; const hasSelectableImages = Boolean( result?.images?.some((item) => Boolean(String(item.imageUrl || "").trim())) ); const goodsInfoItems = [ { label: "商品名称", value: result?.goodsInfo?.productName || null, fullWidth: true, }, { label: "尺寸信息", value: result?.goodsInfo?.sizeInfo || null, }, { label: "材质信息", value: result?.goodsInfo?.materialInfo || null, }, { label: "设计师", value: result?.goodsInfo?.designer || null, }, { label: "采购负责人", value: result?.goodsInfo?.buyer || null, }, { label: "板材类型", value: result?.goodsInfo?.boardType || null, }, ].filter((item) => Boolean(item.value)); const goodsInfoMissingHint = result?.queryType === "sku" && Boolean(String(result?.matchedSku || result?.query || "").trim()) && goodsInfoItems.length === 0 ? `当前 SKU「${result?.matchedSku || result?.query}」暂未从商品信息接口匹配到商品名称、尺寸或材质信息。` : ""; const remainingSelectableCount = resolvedSelectionLimit === null ? null : Math.max(resolvedSelectionLimit - selectedCount, 0); const totalImageCount = result?.images?.length || 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 canTogglePreviewMode = totalImageCount > 0 && selectedCount > 0 && selectedCount < totalImageCount; const visibleImages = useMemo(() => { if (!result?.images?.length) { return []; } if (previewDisplayMode === "selected" && selectedCount > 0) { return result.images.filter((image) => selectedSet.has(image.id)); } return result.images; }, [previewDisplayMode, result?.images, selectedCount, selectedSet]); 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); }, [previewResetKey]); useEffect(() => { if (hasManualSelectionInteraction) { return; } setPreviewDisplayMode(hasSystemRecommendedSelection ? "selected" : "all"); }, [hasManualSelectionInteraction, hasSystemRecommendedSelection, previewResetKey]); const hasFetchSuccess = !disabled && !isLoading && !errorText && Boolean(result); const handleToggleImage = (imageId: string) => { setHasManualSelectionInteraction(true); const next = new Set(selectedImageIds); if (next.has(imageId)) { next.delete(imageId); } else { if (resolvedSelectionLimit !== null && next.size >= resolvedSelectionLimit) { toast.error(`${selectionLimitLabel}最多可选择 ${resolvedSelectionLimit} 张图片`); return; } next.add(imageId); } onSelectedImageIdsChange(Array.from(next)); }; const handleSelectAll = () => { setHasManualSelectionInteraction(true); const allIds = result?.images ?.filter((item) => Boolean(String(item.imageUrl || "").trim())) .map((item) => item.id) || []; const nextIds = resolvedSelectionLimit !== null ? allIds.slice(0, resolvedSelectionLimit) : allIds; onSelectedImageIdsChange(nextIds); if (resolvedSelectionLimit !== null && allIds.length > resolvedSelectionLimit) { toast.message(`${selectionLimitLabel}最多可选择 ${resolvedSelectionLimit} 张图片,已按上限选取`); } }; return (
{title || description ? (
{title ? (
{title}
) : null} {description ? (
{description}
) : null}
) : null}
{(["sku", "spu"] as const).map((mode) => ( ))}
onValueChange(event.target.value)} placeholder={placeholder} disabled={disabled} className={cn( hasFetchSuccess && "pr-11", isDark && "border-white/12 bg-white/[0.04] text-white placeholder:text-white/25 focus-visible:ring-emerald-400/30" )} onKeyDown={(event) => { if (event.key === "Enter") { event.preventDefault(); if (disabled) return; onSearch(); } }} /> {hasFetchSuccess ? ( ) : null}
{searchNote ? (
{searchNote}
) : null} {resolvedSelectionLimit !== null ? (
{selectionLimitLabel}最多可选择 {resolvedSelectionLimit} 张图片 {remainingSelectableCount !== null ? `,当前还可选择 ${remainingSelectableCount} 张。` : "。"}
) : null} {disabled && disabledHint ? (
{disabledHint}
) : null} {!disabled && errorText ? (
{errorText}
) : null} {!disabled && result ? (
命中:{result.matchedSku || result.query} {result.matchedSpu ? ( SPU:{result.matchedSpu} ) : null} {result.productModel ? ( 型号:{result.productModel} ) : null} 共 {result.totalCount} 张 已选 {selectedCount} 张 {resolvedSelectionLimit !== null ? ( 最多 {resolvedSelectionLimit} 张 ) : null}
{canTogglePreviewMode ? (
) : null}
{goodsInfoItems.length > 0 ? (
{goodsInfoItems.map((item) => (
{item.label}
{item.value}
))}
) : null} {goodsInfoMissingHint ? (
{goodsInfoMissingHint}
) : null} {result.skuOptions?.length ? (
{result.skuOptions.map((option) => (
{option.previewImageUrl ? ( {option.previewFileName ) : (
)}
{option.skuName}
{option.hasWhiteBackground ? `已有 ${option.whiteBackgroundCount} 张白底图` : "暂无白底图"}
))}
) : null} {result.images?.length ? (
{isAutoSelectedResult || canTogglePreviewMode ? (
{previewDisplayMode === "selected" ? `当前优先展示已选中的 ${selectedCount} 张图片,便于你快速确认结果。` : `当前展示全部 ${totalImageCount} 张候选图片,可继续手动微调选图。`}
{canTogglePreviewMode ? (
在上方切换“已选 / 全部”即可,无需再单独收起预览。
) : null}
) : null} {visibleImages.length > 0 ? (
{visibleImages.map((image) => { const selected = selectedSet.has(image.id); const isPlaceholder = !String(image.imageUrl || "").trim(); return ( ); })}
) : (
当前没有可展示的已选图片
切换到“全部 {totalImageCount}”可查看全部候选图片并重新调整。
)}
) : null}
) : (
{disabled ? ( <> 先选择生图模式,再搜索并选择素材图。 ) : isLoading ? ( <> 正在搜索并导入图片,请稍候... ) : ( <> {value.trim() ? "输入后点击搜索,从素材库拉取白底图。" : "先输入关键词,再搜索素材图。"} )}
)} {(result?.images?.length || result?.skuOptions?.length) ? (
{result?.skuOptions?.length ? "当前是 SPU 命中结果,请先选择具体 SKU,再继续拉取白底图。" : "白底图会先按权重自动选入槽位,如需微调仍可手动调整。"}
) : null}
); }