diff --git a/app/api/agent-workflow/material-search/route.ts b/app/api/agent-workflow/material-search/route.ts index 43640c112..45a82df4a 100644 --- a/app/api/agent-workflow/material-search/route.ts +++ b/app/api/agent-workflow/material-search/route.ts @@ -97,6 +97,14 @@ function sanitizeGoodsInfoText(value: unknown, maxLength = 255) { return String(value || "").trim().slice(0, maxLength); } +function deriveSpuFromSku(value: unknown) { + const normalized = String(value || "").trim().toUpperCase(); + if (!/[0-9]{2}$/.test(normalized) || normalized.length <= 2) { + return ""; + } + return normalized.slice(0, -2); +} + function createGoodsInfoSignature(timespan: string, secret: string) { const secretBuffer = Buffer.from(String(secret || "").trim(), "utf8"); if (secretBuffer.length !== 8) { @@ -817,7 +825,7 @@ export async function GET(request: NextRequest) { query: normalizedKeyword, queryType: "sku", matchedSku, - matchedSpu: goodsInfo?.matchedSpu || productModel, + matchedSpu: goodsInfo?.matchedSpu || productModel || deriveSpuFromSku(matchedSku), materialId: String(upstreamResult.data.materialId || "").trim() || null, productModel, zipUrl: String(upstreamResult.data.downloadUrls?.zip || "").trim() || null, diff --git a/app/api/agent-workflow/reference-search/route.ts b/app/api/agent-workflow/reference-search/route.ts index 89fc2384c..fa3b5464a 100644 --- a/app/api/agent-workflow/reference-search/route.ts +++ b/app/api/agent-workflow/reference-search/route.ts @@ -957,11 +957,11 @@ export async function GET(request: NextRequest) { materialQueryType === "spu" && goodsLookup?.matchedSpu ? goodsLookup.matchedSpu.trim().toUpperCase() : normalizedKeyword; - if (materialQueryType === "spu" && goodsLookup?.matchedSpu) { - console.info("[Agent Workflow Reference Search] Resolved SKU input to SPU:", { + if (materialQueryType === "spu" && resolvedMaterialKeyword !== normalizedKeyword) { + console.info("[Agent Workflow Reference Search] Resolved SPU query keyword:", { keyword: normalizedKeyword, resolvedSpu: resolvedMaterialKeyword, - matchedSku: goodsLookup.matchedSku || null, + matchedSku: goodsLookup?.matchedSku || null, }); } if ( diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index 2f4c7170a..de499049d 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -1610,9 +1610,22 @@ export function AgentGenerationWorkspaceDarkV2({ ); const workflowBrandName = getCurrentWorkflowBrandName(); const getCurrentWorkflowPrimarySpu = useCallback( - () => - String((getLatestWorkflowContext?.() ?? workflowContext)?.primarySku || "") - .trim(), + () => { + const latestWorkflowContext = getLatestWorkflowContext?.() ?? workflowContext; + const explicitSpu = String(latestWorkflowContext?.primarySpu || "").trim(); + if (explicitSpu) { + return explicitSpu; + } + + const primaryReferenceSpu = latestWorkflowContext?.referenceImages + ?.find((image) => image.source === "primary" && image.productModel) + ?.productModel?.trim(); + if (primaryReferenceSpu) { + return primaryReferenceSpu; + } + + return String(latestWorkflowContext?.primarySku || "").trim(); + }, [getLatestWorkflowContext, workflowContext] ); const workflowPrimarySpu = getCurrentWorkflowPrimarySpu(); @@ -4210,16 +4223,17 @@ export function AgentGenerationWorkspaceDarkV2({ modelSlotEnabled && AGENT_DETAIL_MODEL_SLOT_INDEX >= 0 ? getAttachmentIdentityKey(selectedModelAttachment) : null; + const latestWorkflowContext = getLatestWorkflowContext?.() ?? workflowContext; + const workflowPrimarySpu = getCurrentWorkflowPrimarySpu().trim().toUpperCase(); + const workflowPriceBand = String(latestWorkflowContext?.priceBand || "").trim(); updateDetailReplacementState((current) => { let changed = false; const nextBrief = { ...(current.brief || createEmptyAgentDetailBriefForm()), }; - const workflowPrimarySku = String(workflowContext?.primarySku || "").trim(); - const workflowPriceBand = String(workflowContext?.priceBand || "").trim(); - if (!nextBrief.productSpu && workflowPrimarySku) { - nextBrief.productSpu = workflowPrimarySku; + if (!nextBrief.productSpu && workflowPrimarySpu) { + nextBrief.productSpu = workflowPrimarySpu; changed = true; } if (!nextBrief.priceBand && workflowPriceBand) { @@ -4346,14 +4360,15 @@ export function AgentGenerationWorkspaceDarkV2({ }); }, [ detailGenerationProductSlotIndexes, + getCurrentWorkflowPrimarySpu, + getLatestWorkflowContext, isDetailReplacementPreset, modelSlotEnabled, referenceImages, selectedModelAttachment, selectedProductAttachments, updateDetailReplacementState, - workflowContext?.priceBand, - workflowContext?.primarySku, + workflowContext, ]); const updateDetailGenerationMethod = useCallback( (method: "reference_replace" | "ai_detail_generate") => { diff --git a/lib/agents/workflow-composer.ts b/lib/agents/workflow-composer.ts index 4c9c75c24..e5dd75c4a 100644 --- a/lib/agents/workflow-composer.ts +++ b/lib/agents/workflow-composer.ts @@ -219,6 +219,33 @@ function normalizeWorkflowText(value: string) { .trim(); } +function normalizeWorkflowProductCode(value: unknown) { + return String(value || "").trim().toUpperCase(); +} + +function deriveSpuFromSku(value: unknown) { + const normalized = normalizeWorkflowProductCode(value); + if (!/[0-9]{2}$/.test(normalized) || normalized.length <= 2) { + return ""; + } + return normalized.slice(0, -2); +} + +function resolveWorkflowSpu(params: { + inputValue: string; + result?: AgentWorkflowMaterialSelections["primary"]["result"] | null; +}) { + const result = params.result || null; + return ( + normalizeWorkflowProductCode(result?.matchedSpu) || + normalizeWorkflowProductCode(result?.productModel) || + (result?.queryType === "spu" ? normalizeWorkflowProductCode(result?.query) : "") || + (result?.queryType === "sku" + ? deriveSpuFromSku(result?.matchedSku || result?.query || params.inputValue) + : "") + ); +} + function sanitizeWorkflowPromptFragment(value: string) { return normalizeWorkflowText(value) .replace(/请按选项[\d一二三四五六七八九十]+执行[。.]?/g, "") @@ -416,11 +443,22 @@ export function buildAgentWorkflowComposerContext(params: { materials: params.materials, selectedBrandModel: params.selectedBrandModel, }); + const primaryWorkflowSpu = resolveWorkflowSpu({ + inputValue: params.inputs.primarySku, + result: params.materials?.primary?.result || null, + }); + const secondaryWorkflowSpu = resolveWorkflowSpu({ + inputValue: params.inputs.secondarySku, + result: params.materials?.secondary?.result || null, + }); const summaryParts: string[] = []; if (params.inputs.primarySku.trim()) { summaryParts.push(`主SKU:${params.inputs.primarySku.trim()}`); } + if (primaryWorkflowSpu && primaryWorkflowSpu !== params.inputs.primarySku.trim().toUpperCase()) { + summaryParts.push(`主SPU:${primaryWorkflowSpu}`); + } if (params.inputs.secondarySku.trim()) { summaryParts.push(`搭配SKU:${params.inputs.secondarySku.trim()}`); } @@ -592,7 +630,9 @@ export function buildAgentWorkflowComposerContext(params: { return { primarySku: params.inputs.primarySku.trim() || undefined, + primarySpu: primaryWorkflowSpu || undefined, secondarySku: params.inputs.secondarySku.trim() || undefined, + secondarySpu: secondaryWorkflowSpu || undefined, brand: params.inputs.brand.trim() || undefined, store: params.inputs.store.trim() || undefined, officialSecondCategory: params.inputs.officialSecondCategory.trim() || undefined, @@ -641,7 +681,7 @@ export function getSelectedWorkflowReferenceImages(params: { source, sourceLabel, sku: result.matchedSku || fallbackSku || undefined, - productModel: result.productModel || null, + productModel: result.productModel || result.matchedSpu || null, label: item.label, name: item.fileName || item.label, url: item.imageUrl, diff --git a/types/agent.ts b/types/agent.ts index 1a293004e..38cfa379b 100644 --- a/types/agent.ts +++ b/types/agent.ts @@ -348,7 +348,9 @@ export interface AgentWorkflowComposerSelection { export interface AgentWorkflowComposerContext { primarySku?: string; + primarySpu?: string; secondarySku?: string; + secondarySpu?: string; brand?: string; store?: string; officialSecondCategory?: string;