From 8b9d9d42e02b62bb5acb31c23584294b8c5884f4 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 27 Apr 2026 23:19:18 +0800 Subject: [PATCH] fix: gate detail references by selected brand --- .../agent-workflow/reference-search/route.ts | 79 +++++++++++++++---- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/app/api/agent-workflow/reference-search/route.ts b/app/api/agent-workflow/reference-search/route.ts index 1c35071b2..991123ef5 100644 --- a/app/api/agent-workflow/reference-search/route.ts +++ b/app/api/agent-workflow/reference-search/route.ts @@ -366,10 +366,12 @@ function isBrandNameMatched( const normalizedBrandName = normalizeComparableName(candidateBrandName); if (!normalizedBrandName) return false; + if (normalizedBrandName === normalizedRequestedBrand) return true; + + // 只允许“候选品牌包含当前品牌”的弱匹配,避免短品牌名反向包含导致串品牌。 return ( - normalizedBrandName === normalizedRequestedBrand || - normalizedBrandName.includes(normalizedRequestedBrand) || - normalizedRequestedBrand.includes(normalizedBrandName) + normalizedRequestedBrand.length >= 2 && + normalizedBrandName.includes(normalizedRequestedBrand) ); } @@ -452,11 +454,6 @@ export async function GET(request: NextRequest) { resourceType: "task-detail", [queryType]: normalizedKeyword, }); - if (requestedBrandName) { - upstreamParams.set("brandName", requestedBrandName); - upstreamParams.set("brand", requestedBrandName); - upstreamParams.set("productBrand", requestedBrandName); - } const upstreamUrl = `${normalizeBaseUrl( config.baseUrl )}/standard-model-white-background?${upstreamParams.toString()}`; @@ -475,6 +472,27 @@ export async function GET(request: NextRequest) { const errorMessage = String(upstreamResult.error?.message || "").trim() || `参考图搜索失败(${upstreamResponse.status})`; + if ( + requestedBrandName && + (errorCode === "TASK_DETAIL_NOT_FOUND" || errorCode === "FULL_SET_NOT_FOUND") + ) { + return jsonNoStore( + { + success: false, + error: { + code: "BRAND_REFERENCE_NOT_FOUND", + message: `当前品牌「${requestedBrandName}」下没有可用的${ + parsed.target === "main" + ? "主图参考图" + : parsed.target === "detail" + ? "详情参考图" + : "SKU参考图" + }`, + }, + }, + { status: 404 } + ); + } return jsonNoStore( { success: false, @@ -488,14 +506,44 @@ export async function GET(request: NextRequest) { } const upstreamBrandName = getExternalBrandName(upstreamResult.data); - const fallbackBrandName = - upstreamBrandName || - (verifiedGoodsBrandName && - isBrandNameMatched(verifiedGoodsBrandName, requestedBrandName) - ? verifiedGoodsBrandName - : null); + const rawCandidates = upstreamResult.data.candidates || []; + const candidateBrandSamples = Array.from( + new Set( + rawCandidates + .map((candidate) => getExternalBrandName(candidate)) + .filter((brandName): brandName is string => Boolean(brandName)) + ) + ).slice(0, 8); + if (requestedBrandName) { + console.info("[Agent Workflow Reference Search] Brand gate:", { + keyword: normalizedKeyword, + queryType, + target: parsed.target, + requestedBrandName, + verifiedGoodsBrandName, + upstreamBrandName, + candidateCount: rawCandidates.length, + candidateBrandSamples, + }); + } + const hasVerifiedRequestedBrand = + Boolean(verifiedGoodsBrandName) && + isBrandNameMatched(verifiedGoodsBrandName, requestedBrandName); + const hasUpstreamRequestedBrand = + Boolean(upstreamBrandName) && + isBrandNameMatched(upstreamBrandName, requestedBrandName); + let fallbackBrandName: string | null = null; + if (requestedBrandName) { + if (hasVerifiedRequestedBrand) { + fallbackBrandName = verifiedGoodsBrandName || null; + } else if (hasUpstreamRequestedBrand) { + fallbackBrandName = upstreamBrandName || null; + } + } else { + fallbackBrandName = upstreamBrandName; + } const sortedCandidates = sortCandidatesByUploadedAtDesc( - (upstreamResult.data.candidates || []).map((item, index) => + rawCandidates.map((item, index) => normalizeCandidate(item, index, fallbackBrandName) ) ); @@ -550,7 +598,6 @@ export async function GET(request: NextRequest) { brandName: selectedCandidate.brandName || fallbackBrandName || - requestedBrandName || null, categoryName: selectedCandidate.categoryName || String(upstreamResult.data.categoryName || "").trim() || null, shopName: selectedCandidate.shopName || String(upstreamResult.data.shopName || "").trim() || null,