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),
|
keyword: z.string().trim().min(1).max(64),
|
||||||
queryType: z.enum(["sku", "spu"]).default("spu"),
|
queryType: z.enum(["sku", "spu"]).default("spu"),
|
||||||
target: z.enum(["main", "detail", "sku"]),
|
target: z.enum(["main", "detail", "sku"]),
|
||||||
|
brandName: z.string().trim().max(64).optional(),
|
||||||
storeName: z.string().trim().max(64).optional(),
|
storeName: z.string().trim().max(64).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -25,6 +26,10 @@ type ExternalTaskDetailCandidate = {
|
|||||||
materialId?: string;
|
materialId?: string;
|
||||||
taskNo?: string;
|
taskNo?: string;
|
||||||
productModel?: string;
|
productModel?: string;
|
||||||
|
brandName?: string;
|
||||||
|
brand?: string;
|
||||||
|
productBrand?: string;
|
||||||
|
brand_name?: string;
|
||||||
categoryName?: string;
|
categoryName?: string;
|
||||||
shopName?: string;
|
shopName?: string;
|
||||||
sceneName?: string;
|
sceneName?: string;
|
||||||
@ -44,6 +49,9 @@ type ExternalTaskDetailResponse = {
|
|||||||
resourceType?: string;
|
resourceType?: string;
|
||||||
spu?: string | null;
|
spu?: string | null;
|
||||||
sku?: string | null;
|
sku?: string | null;
|
||||||
|
brandName?: string | null;
|
||||||
|
brand?: string | null;
|
||||||
|
productBrand?: string | null;
|
||||||
categoryName?: string | null;
|
categoryName?: string | null;
|
||||||
shopName?: string | null;
|
shopName?: string | null;
|
||||||
candidateCount?: number;
|
candidateCount?: number;
|
||||||
@ -59,7 +67,7 @@ function normalizeBaseUrl(value: string) {
|
|||||||
return String(value || "").trim().replace(/\/+$/, "");
|
return String(value || "").trim().replace(/\/+$/, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeShopName(value: unknown) {
|
function normalizeComparableName(value: unknown) {
|
||||||
return String(value || "")
|
return String(value || "")
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@ -104,12 +112,21 @@ function normalizeCandidate(
|
|||||||
const materialId = String(value.materialId || "").trim() || null;
|
const materialId = String(value.materialId || "").trim() || null;
|
||||||
const sceneName = String(value.sceneName || "").trim() || null;
|
const sceneName = String(value.sceneName || "").trim() || null;
|
||||||
const id = taskNo || materialId || `candidate-${index + 1}`;
|
const id = taskNo || materialId || `candidate-${index + 1}`;
|
||||||
|
const brandName =
|
||||||
|
String(
|
||||||
|
value.brandName ||
|
||||||
|
value.brand ||
|
||||||
|
value.productBrand ||
|
||||||
|
value.brand_name ||
|
||||||
|
""
|
||||||
|
).trim() || null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
materialId,
|
materialId,
|
||||||
taskNo,
|
taskNo,
|
||||||
productModel: String(value.productModel || "").trim() || null,
|
productModel: String(value.productModel || "").trim() || null,
|
||||||
|
brandName,
|
||||||
categoryName: String(value.categoryName || "").trim() || null,
|
categoryName: String(value.categoryName || "").trim() || null,
|
||||||
shopName: String(value.shopName || "").trim() || null,
|
shopName: String(value.shopName || "").trim() || null,
|
||||||
sceneName,
|
sceneName,
|
||||||
@ -171,23 +188,39 @@ function sortCandidatesByUploadedAtDesc(candidates: AgentModeReferenceCandidate[
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCandidateMatchedStore(
|
function isCandidateMatchedBrand(
|
||||||
candidate: AgentModeReferenceCandidate,
|
candidate: AgentModeReferenceCandidate,
|
||||||
storeName?: string
|
brandName?: string
|
||||||
) {
|
) {
|
||||||
const normalizedRequestedStore = normalizeShopName(storeName);
|
const normalizedRequestedBrand = normalizeComparableName(brandName);
|
||||||
if (!normalizedRequestedStore) return true;
|
if (!normalizedRequestedBrand) return true;
|
||||||
|
|
||||||
const normalizedShopName = normalizeShopName(candidate.shopName);
|
const normalizedBrandName = normalizeComparableName(candidate.brandName);
|
||||||
const normalizedCategoryName = normalizeShopName(candidate.categoryName);
|
if (!normalizedBrandName) return false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
normalizedShopName === normalizedRequestedStore ||
|
normalizedBrandName === normalizedRequestedBrand ||
|
||||||
normalizedCategoryName === normalizedRequestedStore ||
|
normalizedBrandName.includes(normalizedRequestedBrand) ||
|
||||||
(Boolean(normalizedShopName) && normalizedShopName.includes(normalizedRequestedStore)) ||
|
normalizedRequestedBrand.includes(normalizedBrandName)
|
||||||
(Boolean(normalizedShopName) && normalizedRequestedStore.includes(normalizedShopName)) ||
|
);
|
||||||
(Boolean(normalizedCategoryName) && normalizedCategoryName.includes(normalizedRequestedStore)) ||
|
}
|
||||||
(Boolean(normalizedCategoryName) && normalizedRequestedStore.includes(normalizedCategoryName))
|
|
||||||
|
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"),
|
keyword: request.nextUrl.searchParams.get("keyword"),
|
||||||
queryType: request.nextUrl.searchParams.get("queryType") || "spu",
|
queryType: request.nextUrl.searchParams.get("queryType") || "spu",
|
||||||
target: request.nextUrl.searchParams.get("target"),
|
target: request.nextUrl.searchParams.get("target"),
|
||||||
|
brandName: request.nextUrl.searchParams.get("brandName") || undefined,
|
||||||
storeName: request.nextUrl.searchParams.get("storeName") || 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 requestedBrandName = String(parsed.brandName || "").trim();
|
||||||
const normalizedCandidates = sortCandidatesByUploadedAtDesc(
|
const sortedCandidates = sortCandidatesByUploadedAtDesc(
|
||||||
(upstreamResult.data.candidates || []).map((item, index) => normalizeCandidate(item, index))
|
(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);
|
.filter((candidate) => getRenderableCandidateTargetUrls(candidate, parsed.target).length > 0);
|
||||||
|
|
||||||
if (normalizedCandidates.length === 0) {
|
if (normalizedCandidates.length === 0) {
|
||||||
@ -266,15 +300,15 @@ export async function GET(request: NextRequest) {
|
|||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
error: {
|
error: {
|
||||||
code: requestedStoreName
|
code: requestedBrandName
|
||||||
? "STORE_REFERENCE_NOT_FOUND"
|
? "BRAND_REFERENCE_NOT_FOUND"
|
||||||
: parsed.target === "main"
|
: parsed.target === "main"
|
||||||
? "MAIN_REFERENCE_NOT_FOUND"
|
? "MAIN_REFERENCE_NOT_FOUND"
|
||||||
: parsed.target === "detail"
|
: parsed.target === "detail"
|
||||||
? "DETAIL_REFERENCE_NOT_FOUND"
|
? "DETAIL_REFERENCE_NOT_FOUND"
|
||||||
: "SKU_REFERENCE_NOT_FOUND",
|
: "SKU_REFERENCE_NOT_FOUND",
|
||||||
message: requestedStoreName
|
message: requestedBrandName
|
||||||
? `当前店铺「${requestedStoreName}」下没有可用的${
|
? `当前品牌「${requestedBrandName}」下没有可用的${
|
||||||
parsed.target === "main"
|
parsed.target === "main"
|
||||||
? "主图参考图"
|
? "主图参考图"
|
||||||
: parsed.target === "detail"
|
: parsed.target === "detail"
|
||||||
@ -306,6 +340,16 @@ export async function GET(request: NextRequest) {
|
|||||||
target: parsed.target,
|
target: parsed.target,
|
||||||
matchedSpu: String(upstreamResult.data.spu || "").trim() || null,
|
matchedSpu: String(upstreamResult.data.spu || "").trim() || null,
|
||||||
matchedSku: String(upstreamResult.data.sku || "").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,
|
categoryName: selectedCandidate.categoryName || String(upstreamResult.data.categoryName || "").trim() || null,
|
||||||
shopName: selectedCandidate.shopName || String(upstreamResult.data.shopName || "").trim() || null,
|
shopName: selectedCandidate.shopName || String(upstreamResult.data.shopName || "").trim() || null,
|
||||||
candidateCount: normalizedCandidates.length,
|
candidateCount: normalizedCandidates.length,
|
||||||
|
|||||||
@ -244,6 +244,7 @@ interface AgentModeReferenceSearchResponse {
|
|||||||
target: AgentTaskDetailReferenceSource;
|
target: AgentTaskDetailReferenceSource;
|
||||||
matchedSpu?: string | null;
|
matchedSpu?: string | null;
|
||||||
matchedSku?: string | null;
|
matchedSku?: string | null;
|
||||||
|
brandName?: string | null;
|
||||||
categoryName?: string | null;
|
categoryName?: string | null;
|
||||||
shopName?: string | null;
|
shopName?: string | null;
|
||||||
candidateCount: number;
|
candidateCount: number;
|
||||||
@ -1346,7 +1347,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
const skuReferenceTarget = presetModeConfig.sku.referenceImageSource;
|
const skuReferenceTarget = presetModeConfig.sku.referenceImageSource;
|
||||||
const detailReferenceSourceLabel = getTaskDetailReferenceSourceLabel(detailReferenceTarget);
|
const detailReferenceSourceLabel = getTaskDetailReferenceSourceLabel(detailReferenceTarget);
|
||||||
const skuReferenceSourceLabel = getTaskDetailReferenceSourceLabel(skuReferenceTarget);
|
const skuReferenceSourceLabel = getTaskDetailReferenceSourceLabel(skuReferenceTarget);
|
||||||
const workflowStoreName = String(workflowContext?.store || "").trim();
|
const workflowBrandName = String(workflowContext?.brand || "").trim();
|
||||||
const shouldAllowModelOverride =
|
const shouldAllowModelOverride =
|
||||||
isDirectModelGenerationMode || !isSceneGenerationPreset || presetModeConfig.scene.allowModelOverride;
|
isDirectModelGenerationMode || !isSceneGenerationPreset || presetModeConfig.scene.allowModelOverride;
|
||||||
const brandModelAttachmentKeys = useMemo(() => {
|
const brandModelAttachmentKeys = useMemo(() => {
|
||||||
@ -3968,8 +3969,8 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
}
|
}
|
||||||
const handleDetailReplacementSearch = useCallback(async () => {
|
const handleDetailReplacementSearch = useCallback(async () => {
|
||||||
const normalized = detailReplacementQueryInput.trim().toUpperCase();
|
const normalized = detailReplacementQueryInput.trim().toUpperCase();
|
||||||
if (!workflowStoreName) {
|
if (!workflowBrandName) {
|
||||||
toast.error("请先在第一步完成品牌和店铺选择后再查询参考图");
|
toast.error("请先在第一步完成品牌选择后再查询参考图");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
@ -3988,8 +3989,8 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
queryType: "spu",
|
queryType: "spu",
|
||||||
target: detailReferenceTarget,
|
target: detailReferenceTarget,
|
||||||
});
|
});
|
||||||
if (workflowStoreName) {
|
if (workflowBrandName) {
|
||||||
searchParams.set("storeName", workflowStoreName);
|
searchParams.set("brandName", workflowBrandName);
|
||||||
}
|
}
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/agent-workflow/reference-search?${searchParams.toString()}`,
|
`/api/agent-workflow/reference-search?${searchParams.toString()}`,
|
||||||
@ -4026,7 +4027,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
detailReferenceTarget,
|
detailReferenceTarget,
|
||||||
detailReplacementQueryInput,
|
detailReplacementQueryInput,
|
||||||
updateDetailReplacementState,
|
updateDetailReplacementState,
|
||||||
workflowStoreName,
|
workflowBrandName,
|
||||||
]);
|
]);
|
||||||
const handleSelectDetailReplacementCandidate = useCallback((candidateId: string) => {
|
const handleSelectDetailReplacementCandidate = useCallback((candidateId: string) => {
|
||||||
updateDetailReplacementState((current) => {
|
updateDetailReplacementState((current) => {
|
||||||
@ -4068,8 +4069,8 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
);
|
);
|
||||||
const handleSkuReplacementSearch = useCallback(async () => {
|
const handleSkuReplacementSearch = useCallback(async () => {
|
||||||
const normalized = skuReplacementQueryInput.trim().toUpperCase();
|
const normalized = skuReplacementQueryInput.trim().toUpperCase();
|
||||||
if (!workflowStoreName) {
|
if (!workflowBrandName) {
|
||||||
toast.error("请先在第一步完成品牌和店铺选择后再查询参考图");
|
toast.error("请先在第一步完成品牌选择后再查询参考图");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
@ -4088,8 +4089,8 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
queryType: skuReplacementQueryType,
|
queryType: skuReplacementQueryType,
|
||||||
target: skuReferenceTarget,
|
target: skuReferenceTarget,
|
||||||
});
|
});
|
||||||
if (workflowStoreName) {
|
if (workflowBrandName) {
|
||||||
searchParams.set("storeName", workflowStoreName);
|
searchParams.set("brandName", workflowBrandName);
|
||||||
}
|
}
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/agent-workflow/reference-search?${searchParams.toString()}`,
|
`/api/agent-workflow/reference-search?${searchParams.toString()}`,
|
||||||
@ -4135,7 +4136,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
skuReplacementQueryInput,
|
skuReplacementQueryInput,
|
||||||
skuReplacementQueryType,
|
skuReplacementQueryType,
|
||||||
updateSkuReplacementState,
|
updateSkuReplacementState,
|
||||||
workflowStoreName,
|
workflowBrandName,
|
||||||
]);
|
]);
|
||||||
const handleSelectSkuReplacementCandidate = useCallback((candidateId: string) => {
|
const handleSelectSkuReplacementCandidate = useCallback((candidateId: string) => {
|
||||||
updateSkuReplacementState((current) => {
|
updateSkuReplacementState((current) => {
|
||||||
@ -5115,8 +5116,8 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
SPU /
|
SPU /
|
||||||
{detailReplacementSearchState.lastQuery ||
|
{detailReplacementSearchState.lastQuery ||
|
||||||
detailReplacementQueryInput.trim().toUpperCase()}
|
detailReplacementQueryInput.trim().toUpperCase()}
|
||||||
{workflowStoreName ? (
|
{workflowBrandName ? (
|
||||||
<span className="ml-2 text-white/40">{`店铺 / ${workflowStoreName}`}</span>
|
<span className="ml-2 text-white/40">{`品牌 / ${workflowBrandName}`}</span>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="ml-2 text-emerald-200/70">
|
<span className="ml-2 text-emerald-200/70">
|
||||||
{`已接入真实${detailReferenceSourceLabel}参考图接口`}
|
{`已接入真实${detailReferenceSourceLabel}参考图接口`}
|
||||||
@ -5158,6 +5159,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
const summaryBadges = [
|
const summaryBadges = [
|
||||||
`${detailReferenceSourceLabel}`,
|
`${detailReferenceSourceLabel}`,
|
||||||
`${getCandidateTargetCount(candidate, detailReferenceTarget)}张`,
|
`${getCandidateTargetCount(candidate, detailReferenceTarget)}张`,
|
||||||
|
candidate.brandName || null,
|
||||||
candidate.categoryName || null,
|
candidate.categoryName || null,
|
||||||
].filter((item): item is string => Boolean(item));
|
].filter((item): item is string => Boolean(item));
|
||||||
return (
|
return (
|
||||||
@ -5221,6 +5223,9 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1.5 flex flex-col gap-0.5 text-[9px] text-white/50">
|
<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">店铺:{candidate.shopName || "未记录"}</div>
|
||||||
<div className="truncate">更新时间:{uploadedAtText}</div>
|
<div className="truncate">更新时间:{uploadedAtText}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -5867,8 +5872,8 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
{skuReplacementQueryType === "spu" ? "SPU" : "SKU"} /
|
{skuReplacementQueryType === "spu" ? "SPU" : "SKU"} /
|
||||||
{skuReplacementSearchState.lastQuery ||
|
{skuReplacementSearchState.lastQuery ||
|
||||||
skuReplacementQueryInput.trim().toUpperCase()}
|
skuReplacementQueryInput.trim().toUpperCase()}
|
||||||
{workflowStoreName ? (
|
{workflowBrandName ? (
|
||||||
<span className="ml-2 text-white/40">{`店铺 / ${workflowStoreName}`}</span>
|
<span className="ml-2 text-white/40">{`品牌 / ${workflowBrandName}`}</span>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="ml-2 text-emerald-200/70">
|
<span className="ml-2 text-emerald-200/70">
|
||||||
{`已接入真实${skuReferenceSourceLabel}参考图接口`}
|
{`已接入真实${skuReferenceSourceLabel}参考图接口`}
|
||||||
@ -5930,6 +5935,11 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
{formatCandidateMeta(candidate, skuReferenceTarget) || `点击切换这套${skuReferenceSourceLabel}参考图`}
|
{formatCandidateMeta(candidate, skuReferenceTarget) || `点击切换这套${skuReferenceSourceLabel}参考图`}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-1.5 text-[11px] text-white/48">
|
<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 ? (
|
{candidate.shopName ? (
|
||||||
<span className="rounded-full bg-white/[0.05] px-2 py-1">
|
<span className="rounded-full bg-white/[0.05] px-2 py-1">
|
||||||
{candidate.shopName}
|
{candidate.shopName}
|
||||||
|
|||||||
@ -385,6 +385,7 @@ export interface AgentModeReferenceCandidate {
|
|||||||
materialId?: string | null;
|
materialId?: string | null;
|
||||||
taskNo?: string | null;
|
taskNo?: string | null;
|
||||||
productModel?: string | null;
|
productModel?: string | null;
|
||||||
|
brandName?: string | null;
|
||||||
categoryName?: string | null;
|
categoryName?: string | null;
|
||||||
shopName?: string | null;
|
shopName?: string | null;
|
||||||
sceneName?: string | null;
|
sceneName?: string | null;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user