From 807aba1e85eb89c27effe8eb96d88d84b6f225dd Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 27 Apr 2026 21:08:33 +0800 Subject: [PATCH] fix: filter detail references by brand --- .../agent-workflow/reference-search/route.ts | 86 ++++++++++++++----- .../agents/AgentGenerationWorkspaceDarkV2.tsx | 40 +++++---- types/agent.ts | 1 + 3 files changed, 91 insertions(+), 36 deletions(-) diff --git a/app/api/agent-workflow/reference-search/route.ts b/app/api/agent-workflow/reference-search/route.ts index 077c9e363..db7174cee 100644 --- a/app/api/agent-workflow/reference-search/route.ts +++ b/app/api/agent-workflow/reference-search/route.ts @@ -18,6 +18,7 @@ const querySchema = z.object({ keyword: z.string().trim().min(1).max(64), queryType: z.enum(["sku", "spu"]).default("spu"), target: z.enum(["main", "detail", "sku"]), + brandName: z.string().trim().max(64).optional(), storeName: z.string().trim().max(64).optional(), }); @@ -25,6 +26,10 @@ type ExternalTaskDetailCandidate = { materialId?: string; taskNo?: string; productModel?: string; + brandName?: string; + brand?: string; + productBrand?: string; + brand_name?: string; categoryName?: string; shopName?: string; sceneName?: string; @@ -44,6 +49,9 @@ type ExternalTaskDetailResponse = { resourceType?: string; spu?: string | null; sku?: string | null; + brandName?: string | null; + brand?: string | null; + productBrand?: string | null; categoryName?: string | null; shopName?: string | null; candidateCount?: number; @@ -59,7 +67,7 @@ function normalizeBaseUrl(value: string) { return String(value || "").trim().replace(/\/+$/, ""); } -function normalizeShopName(value: unknown) { +function normalizeComparableName(value: unknown) { return String(value || "") .trim() .toLowerCase() @@ -104,12 +112,21 @@ function normalizeCandidate( const materialId = String(value.materialId || "").trim() || null; const sceneName = String(value.sceneName || "").trim() || null; const id = taskNo || materialId || `candidate-${index + 1}`; + const brandName = + String( + value.brandName || + value.brand || + value.productBrand || + value.brand_name || + "" + ).trim() || null; return { id, materialId, taskNo, productModel: String(value.productModel || "").trim() || null, + brandName, categoryName: String(value.categoryName || "").trim() || null, shopName: String(value.shopName || "").trim() || null, sceneName, @@ -171,23 +188,39 @@ function sortCandidatesByUploadedAtDesc(candidates: AgentModeReferenceCandidate[ }); } -function isCandidateMatchedStore( +function isCandidateMatchedBrand( candidate: AgentModeReferenceCandidate, - storeName?: string + brandName?: string ) { - const normalizedRequestedStore = normalizeShopName(storeName); - if (!normalizedRequestedStore) return true; + const normalizedRequestedBrand = normalizeComparableName(brandName); + if (!normalizedRequestedBrand) return true; - const normalizedShopName = normalizeShopName(candidate.shopName); - const normalizedCategoryName = normalizeShopName(candidate.categoryName); + const normalizedBrandName = normalizeComparableName(candidate.brandName); + if (!normalizedBrandName) return false; return ( - normalizedShopName === normalizedRequestedStore || - normalizedCategoryName === normalizedRequestedStore || - (Boolean(normalizedShopName) && normalizedShopName.includes(normalizedRequestedStore)) || - (Boolean(normalizedShopName) && normalizedRequestedStore.includes(normalizedShopName)) || - (Boolean(normalizedCategoryName) && normalizedCategoryName.includes(normalizedRequestedStore)) || - (Boolean(normalizedCategoryName) && normalizedRequestedStore.includes(normalizedCategoryName)) + normalizedBrandName === normalizedRequestedBrand || + normalizedBrandName.includes(normalizedRequestedBrand) || + normalizedRequestedBrand.includes(normalizedBrandName) + ); +} + +function filterCandidatesByBrand( + candidates: AgentModeReferenceCandidate[], + brandName?: string +) { + const normalizedRequestedBrand = normalizeComparableName(brandName); + if (!normalizedRequestedBrand) return candidates; + + const hasBrandMetadata = candidates.some((candidate) => + Boolean(normalizeComparableName(candidate.brandName)) + ); + if (!hasBrandMetadata) { + return candidates; + } + + return candidates.filter((candidate) => + isCandidateMatchedBrand(candidate, normalizedRequestedBrand) ); } @@ -202,6 +235,7 @@ export async function GET(request: NextRequest) { keyword: request.nextUrl.searchParams.get("keyword"), queryType: request.nextUrl.searchParams.get("queryType") || "spu", target: request.nextUrl.searchParams.get("target"), + brandName: request.nextUrl.searchParams.get("brandName") || undefined, storeName: request.nextUrl.searchParams.get("storeName") || undefined, }); @@ -254,11 +288,11 @@ export async function GET(request: NextRequest) { ); } - const requestedStoreName = String(parsed.storeName || "").trim(); - const normalizedCandidates = sortCandidatesByUploadedAtDesc( + const requestedBrandName = String(parsed.brandName || "").trim(); + const sortedCandidates = sortCandidatesByUploadedAtDesc( (upstreamResult.data.candidates || []).map((item, index) => normalizeCandidate(item, index)) - ) - .filter((candidate) => isCandidateMatchedStore(candidate, requestedStoreName)) + ); + const normalizedCandidates = filterCandidatesByBrand(sortedCandidates, requestedBrandName) .filter((candidate) => getRenderableCandidateTargetUrls(candidate, parsed.target).length > 0); if (normalizedCandidates.length === 0) { @@ -266,15 +300,15 @@ export async function GET(request: NextRequest) { { success: false, error: { - code: requestedStoreName - ? "STORE_REFERENCE_NOT_FOUND" + code: requestedBrandName + ? "BRAND_REFERENCE_NOT_FOUND" : parsed.target === "main" ? "MAIN_REFERENCE_NOT_FOUND" : parsed.target === "detail" ? "DETAIL_REFERENCE_NOT_FOUND" : "SKU_REFERENCE_NOT_FOUND", - message: requestedStoreName - ? `当前店铺「${requestedStoreName}」下没有可用的${ + message: requestedBrandName + ? `当前品牌「${requestedBrandName}」下没有可用的${ parsed.target === "main" ? "主图参考图" : parsed.target === "detail" @@ -306,6 +340,16 @@ export async function GET(request: NextRequest) { target: parsed.target, matchedSpu: String(upstreamResult.data.spu || "").trim() || null, matchedSku: String(upstreamResult.data.sku || "").trim() || null, + brandName: + selectedCandidate.brandName || + String( + upstreamResult.data.brandName || + upstreamResult.data.brand || + upstreamResult.data.productBrand || + "" + ).trim() || + requestedBrandName || + null, categoryName: selectedCandidate.categoryName || String(upstreamResult.data.categoryName || "").trim() || null, shopName: selectedCandidate.shopName || String(upstreamResult.data.shopName || "").trim() || null, candidateCount: normalizedCandidates.length, diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index 87fd48d14..da43320d1 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -244,6 +244,7 @@ interface AgentModeReferenceSearchResponse { target: AgentTaskDetailReferenceSource; matchedSpu?: string | null; matchedSku?: string | null; + brandName?: string | null; categoryName?: string | null; shopName?: string | null; candidateCount: number; @@ -1346,7 +1347,7 @@ export function AgentGenerationWorkspaceDarkV2({ const skuReferenceTarget = presetModeConfig.sku.referenceImageSource; const detailReferenceSourceLabel = getTaskDetailReferenceSourceLabel(detailReferenceTarget); const skuReferenceSourceLabel = getTaskDetailReferenceSourceLabel(skuReferenceTarget); - const workflowStoreName = String(workflowContext?.store || "").trim(); + const workflowBrandName = String(workflowContext?.brand || "").trim(); const shouldAllowModelOverride = isDirectModelGenerationMode || !isSceneGenerationPreset || presetModeConfig.scene.allowModelOverride; const brandModelAttachmentKeys = useMemo(() => { @@ -3968,8 +3969,8 @@ export function AgentGenerationWorkspaceDarkV2({ } const handleDetailReplacementSearch = useCallback(async () => { const normalized = detailReplacementQueryInput.trim().toUpperCase(); - if (!workflowStoreName) { - toast.error("请先在第一步完成品牌和店铺选择后再查询参考图"); + if (!workflowBrandName) { + toast.error("请先在第一步完成品牌选择后再查询参考图"); return; } if (!normalized) { @@ -3988,8 +3989,8 @@ export function AgentGenerationWorkspaceDarkV2({ queryType: "spu", target: detailReferenceTarget, }); - if (workflowStoreName) { - searchParams.set("storeName", workflowStoreName); + if (workflowBrandName) { + searchParams.set("brandName", workflowBrandName); } const response = await fetch( `/api/agent-workflow/reference-search?${searchParams.toString()}`, @@ -4026,7 +4027,7 @@ export function AgentGenerationWorkspaceDarkV2({ detailReferenceTarget, detailReplacementQueryInput, updateDetailReplacementState, - workflowStoreName, + workflowBrandName, ]); const handleSelectDetailReplacementCandidate = useCallback((candidateId: string) => { updateDetailReplacementState((current) => { @@ -4068,8 +4069,8 @@ export function AgentGenerationWorkspaceDarkV2({ ); const handleSkuReplacementSearch = useCallback(async () => { const normalized = skuReplacementQueryInput.trim().toUpperCase(); - if (!workflowStoreName) { - toast.error("请先在第一步完成品牌和店铺选择后再查询参考图"); + if (!workflowBrandName) { + toast.error("请先在第一步完成品牌选择后再查询参考图"); return; } if (!normalized) { @@ -4088,8 +4089,8 @@ export function AgentGenerationWorkspaceDarkV2({ queryType: skuReplacementQueryType, target: skuReferenceTarget, }); - if (workflowStoreName) { - searchParams.set("storeName", workflowStoreName); + if (workflowBrandName) { + searchParams.set("brandName", workflowBrandName); } const response = await fetch( `/api/agent-workflow/reference-search?${searchParams.toString()}`, @@ -4135,7 +4136,7 @@ export function AgentGenerationWorkspaceDarkV2({ skuReplacementQueryInput, skuReplacementQueryType, updateSkuReplacementState, - workflowStoreName, + workflowBrandName, ]); const handleSelectSkuReplacementCandidate = useCallback((candidateId: string) => { updateSkuReplacementState((current) => { @@ -5115,8 +5116,8 @@ export function AgentGenerationWorkspaceDarkV2({ SPU / {detailReplacementSearchState.lastQuery || detailReplacementQueryInput.trim().toUpperCase()} - {workflowStoreName ? ( - {`店铺 / ${workflowStoreName}`} + {workflowBrandName ? ( + {`品牌 / ${workflowBrandName}`} ) : null} {`已接入真实${detailReferenceSourceLabel}参考图接口`} @@ -5158,6 +5159,7 @@ export function AgentGenerationWorkspaceDarkV2({ const summaryBadges = [ `${detailReferenceSourceLabel}`, `${getCandidateTargetCount(candidate, detailReferenceTarget)}张`, + candidate.brandName || null, candidate.categoryName || null, ].filter((item): item is string => Boolean(item)); return ( @@ -5221,6 +5223,9 @@ export function AgentGenerationWorkspaceDarkV2({
+ {candidate.brandName ? ( +
品牌:{candidate.brandName}
+ ) : null}
店铺:{candidate.shopName || "未记录"}
更新时间:{uploadedAtText}
@@ -5867,8 +5872,8 @@ export function AgentGenerationWorkspaceDarkV2({ {skuReplacementQueryType === "spu" ? "SPU" : "SKU"} / {skuReplacementSearchState.lastQuery || skuReplacementQueryInput.trim().toUpperCase()} - {workflowStoreName ? ( - {`店铺 / ${workflowStoreName}`} + {workflowBrandName ? ( + {`品牌 / ${workflowBrandName}`} ) : null} {`已接入真实${skuReferenceSourceLabel}参考图接口`} @@ -5930,6 +5935,11 @@ export function AgentGenerationWorkspaceDarkV2({ {formatCandidateMeta(candidate, skuReferenceTarget) || `点击切换这套${skuReferenceSourceLabel}参考图`}
+ {candidate.brandName ? ( + + 品牌:{candidate.brandName} + + ) : null} {candidate.shopName ? ( {candidate.shopName} diff --git a/types/agent.ts b/types/agent.ts index 59750c1a9..3cc211715 100644 --- a/types/agent.ts +++ b/types/agent.ts @@ -385,6 +385,7 @@ export interface AgentModeReferenceCandidate { materialId?: string | null; taskNo?: string | null; productModel?: string | null; + brandName?: string | null; categoryName?: string | null; shopName?: string | null; sceneName?: string | null;