fix: ensure product detail channel is forwarded
This commit is contained in:
parent
e6d4c31120
commit
d73836c365
@ -22,12 +22,17 @@ import type {
|
||||
|
||||
export const maxDuration = 180;
|
||||
|
||||
const productDetailChannelSchema = z.preprocess((value) => {
|
||||
const normalized = typeof value === "string" ? value.trim() : "";
|
||||
return normalized || undefined;
|
||||
}, z.enum(["taobao", "1688", "douyin", "jd"]).optional());
|
||||
|
||||
const querySchema = z.object({
|
||||
keyword: z.string().trim().min(1).max(64),
|
||||
queryType: z.enum(["sku", "spu", "productId"]).default("spu"),
|
||||
target: z.enum(["main", "detail", "sku"]),
|
||||
brandName: z.string().trim().max(64).optional(),
|
||||
channel: z.enum(["taobao", "1688", "douyin", "jd"]).optional(),
|
||||
channel: productDetailChannelSchema,
|
||||
});
|
||||
|
||||
function jsonNoStore(
|
||||
@ -43,6 +48,31 @@ function jsonNoStore(
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeProductDetailChannel(
|
||||
value: unknown,
|
||||
fallback: AgentWorkflowProductDetailChannel = "taobao"
|
||||
): AgentWorkflowProductDetailChannel {
|
||||
const normalized = String(value || "").trim();
|
||||
if (
|
||||
normalized === "taobao" ||
|
||||
normalized === "1688" ||
|
||||
normalized === "douyin" ||
|
||||
normalized === "jd"
|
||||
) {
|
||||
return normalized;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function getProductDetailRequestLogUrl(value: string) {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
return `${url.origin}${url.pathname}?${url.searchParams.toString()}`;
|
||||
} catch {
|
||||
return value.slice(0, 500);
|
||||
}
|
||||
}
|
||||
|
||||
type ExternalTaskDetailCandidate = {
|
||||
materialId?: string;
|
||||
taskNo?: string;
|
||||
@ -482,6 +512,10 @@ function buildProductDetailRequestUrl(params: {
|
||||
config: ReturnType<typeof normalizeAgentWorkflowMaterialApiConfig>;
|
||||
}) {
|
||||
const baseUrl = normalizeBaseUrl(params.config.productDetailBaseUrl || "");
|
||||
const channel = normalizeProductDetailChannel(
|
||||
params.channel,
|
||||
normalizeProductDetailChannel(params.config.productDetailDefaultChannel)
|
||||
);
|
||||
const rawPath =
|
||||
String(params.config.productDetailPath || "").trim() ||
|
||||
(params.method === "POST"
|
||||
@ -495,7 +529,7 @@ function buildProductDetailRequestUrl(params: {
|
||||
? rawPath
|
||||
: `${baseUrl}/${rawPath.replace(/^\/+/, "")}`;
|
||||
const encodedProductId = encodeURIComponent(params.productId);
|
||||
const encodedChannel = encodeURIComponent(params.channel);
|
||||
const encodedChannel = encodeURIComponent(channel);
|
||||
const beforeReplace = requestUrl;
|
||||
requestUrl = requestUrl
|
||||
.replace(/\{\{\s*item_id\s*\}\}/gi, encodedProductId)
|
||||
@ -507,12 +541,13 @@ function buildProductDetailRequestUrl(params: {
|
||||
|
||||
if (params.method === "GET") {
|
||||
const url = new URL(requestUrl);
|
||||
if (!url.searchParams.has("item_id")) {
|
||||
if (!String(url.searchParams.get("item_id") || "").trim()) {
|
||||
url.searchParams.set("item_id", params.productId);
|
||||
}
|
||||
if (!url.searchParams.has("channel")) {
|
||||
url.searchParams.set("channel", params.channel);
|
||||
}
|
||||
url.searchParams.set(
|
||||
"channel",
|
||||
normalizeProductDetailChannel(url.searchParams.get("channel"), channel)
|
||||
);
|
||||
if (!url.searchParams.has("refresh")) {
|
||||
url.searchParams.set("refresh", "false");
|
||||
}
|
||||
@ -585,6 +620,11 @@ async function requestProductDetailReferences(params: {
|
||||
channel: AgentWorkflowProductDetailChannel;
|
||||
config: ReturnType<typeof normalizeAgentWorkflowMaterialApiConfig>;
|
||||
}) {
|
||||
const channel = normalizeProductDetailChannel(
|
||||
params.channel,
|
||||
normalizeProductDetailChannel(params.config.productDetailDefaultChannel)
|
||||
);
|
||||
|
||||
if (!params.config.productDetailEnabled) {
|
||||
return jsonNoStore(
|
||||
{
|
||||
@ -626,25 +666,55 @@ async function requestProductDetailReferences(params: {
|
||||
const method = params.config.productDetailMethod === "POST" ? "POST" : "GET";
|
||||
const requestUrl = buildProductDetailRequestUrl({
|
||||
...params,
|
||||
channel,
|
||||
method,
|
||||
});
|
||||
const requestBody = {
|
||||
item_id: params.productId,
|
||||
channel: params.channel,
|
||||
channel,
|
||||
refresh: false,
|
||||
sync_analysis: params.config.productDetailSyncAnalysis !== false,
|
||||
force_reanalyze: params.config.productDetailForceReanalyze === true,
|
||||
};
|
||||
const response = await fetch(requestUrl, {
|
||||
const startedAt = Date.now();
|
||||
console.info("[Agent Workflow Reference Search] Product detail request:", {
|
||||
method,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"X-API-KEY": apiKey,
|
||||
...(method === "POST" ? { "Content-Type": "application/json" } : {}),
|
||||
},
|
||||
body: method === "POST" ? JSON.stringify(requestBody) : undefined,
|
||||
cache: "no-store",
|
||||
productId: params.productId,
|
||||
channel,
|
||||
url: getProductDetailRequestLogUrl(requestUrl),
|
||||
});
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(requestUrl, {
|
||||
method,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"X-API-KEY": apiKey,
|
||||
...(method === "POST" ? { "Content-Type": "application/json" } : {}),
|
||||
},
|
||||
body: method === "POST" ? JSON.stringify(requestBody) : undefined,
|
||||
cache: "no-store",
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("[Agent Workflow Reference Search] Product detail request failed:", {
|
||||
method,
|
||||
productId: params.productId,
|
||||
channel,
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
return jsonNoStore(
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
code: "PRODUCT_DETAIL_API_FETCH_FAILED",
|
||||
message: "商品ID详情图接口访问失败,请稍后重试或检查接口网络",
|
||||
},
|
||||
},
|
||||
{ status: 502 }
|
||||
);
|
||||
}
|
||||
const result = await response.json().catch(() => ({}));
|
||||
const responseRecord =
|
||||
result && typeof result === "object" && !Array.isArray(result)
|
||||
@ -705,16 +775,16 @@ async function requestProductDetailReferences(params: {
|
||||
const shopName = sanitizeText(productData.shopName, 128) || null;
|
||||
const channelLabel =
|
||||
sanitizeText(productData.channelLabel, 32) ||
|
||||
(params.channel === "taobao"
|
||||
(channel === "taobao"
|
||||
? "淘宝"
|
||||
: params.channel === "douyin"
|
||||
: channel === "douyin"
|
||||
? "抖音"
|
||||
: params.channel === "jd"
|
||||
: channel === "jd"
|
||||
? "京东"
|
||||
: "1688");
|
||||
|
||||
const candidate: AgentModeReferenceCandidate = {
|
||||
id: `product-id-${params.channel}-${resolvedItemId}`,
|
||||
id: `product-id-${channel}-${resolvedItemId}`,
|
||||
materialId: resolvedItemId,
|
||||
taskNo: sanitizeText(responseRecord.request_id, 128) || null,
|
||||
productModel: title || resolvedItemId,
|
||||
@ -745,7 +815,7 @@ async function requestProductDetailReferences(params: {
|
||||
brandName: null,
|
||||
categoryName: null,
|
||||
shopName,
|
||||
channel: params.channel,
|
||||
channel,
|
||||
candidateCount: 1,
|
||||
candidates: [candidate],
|
||||
selectedCandidateId: candidate.id,
|
||||
@ -793,8 +863,10 @@ export async function GET(request: NextRequest) {
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
const productDetailChannel =
|
||||
parsed.channel || config.productDetailDefaultChannel || "taobao";
|
||||
const productDetailChannel = normalizeProductDetailChannel(
|
||||
parsed.channel,
|
||||
normalizeProductDetailChannel(config.productDetailDefaultChannel)
|
||||
);
|
||||
return requestProductDetailReferences({
|
||||
productId: normalizedKeyword,
|
||||
channel: productDetailChannel,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user