fix: filter detail references by brand
This commit is contained in:
parent
55bed5087c
commit
807aba1e85
@ -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,
|
||||
|
||||
@ -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 ? (
|
||||
<span className="ml-2 text-white/40">{`店铺 / ${workflowStoreName}`}</span>
|
||||
{workflowBrandName ? (
|
||||
<span className="ml-2 text-white/40">{`品牌 / ${workflowBrandName}`}</span>
|
||||
) : null}
|
||||
<span className="ml-2 text-emerald-200/70">
|
||||
{`已接入真实${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({
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-1.5 flex flex-col gap-0.5 text-[9px] text-white/50">
|
||||
{candidate.brandName ? (
|
||||
<div className="truncate">品牌:{candidate.brandName}</div>
|
||||
) : null}
|
||||
<div className="truncate">店铺:{candidate.shopName || "未记录"}</div>
|
||||
<div className="truncate">更新时间:{uploadedAtText}</div>
|
||||
</div>
|
||||
@ -5867,8 +5872,8 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
{skuReplacementQueryType === "spu" ? "SPU" : "SKU"} /
|
||||
{skuReplacementSearchState.lastQuery ||
|
||||
skuReplacementQueryInput.trim().toUpperCase()}
|
||||
{workflowStoreName ? (
|
||||
<span className="ml-2 text-white/40">{`店铺 / ${workflowStoreName}`}</span>
|
||||
{workflowBrandName ? (
|
||||
<span className="ml-2 text-white/40">{`品牌 / ${workflowBrandName}`}</span>
|
||||
) : null}
|
||||
<span className="ml-2 text-emerald-200/70">
|
||||
{`已接入真实${skuReferenceSourceLabel}参考图接口`}
|
||||
@ -5930,6 +5935,11 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
{formatCandidateMeta(candidate, skuReferenceTarget) || `点击切换这套${skuReferenceSourceLabel}参考图`}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5 text-[11px] text-white/48">
|
||||
{candidate.brandName ? (
|
||||
<span className="rounded-full bg-white/[0.05] px-2 py-1">
|
||||
品牌:{candidate.brandName}
|
||||
</span>
|
||||
) : null}
|
||||
{candidate.shopName ? (
|
||||
<span className="rounded-full bg-white/[0.05] px-2 py-1">
|
||||
{candidate.shopName}
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user