2026-05-27 13:16:37 +08:00

111 lines
3.1 KiB
TypeScript

import { generateText } from "ai";
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { createLogger } from "@/lib/observability/logger";
import { buildProductPlanningAnalyzePrompt } from "@/lib/product-planning/analyze";
import { resolveProductPlanningTextModel } from "@/lib/product-planning/model-runtime";
import {
isProductPlanningEnabled,
PRODUCT_PLANNING_DISABLED_MESSAGE,
} from "@/lib/product-planning/server-feature";
import { getProductPlanningToken } from "@/lib/product-planning/server-auth";
import type {
ProductPlanningAnalyzeRequest,
ProductPlanningAnalyzeResponse,
} from "@/lib/product-planning/types";
export const runtime = "nodejs";
export const maxDuration = 120;
const logger = createLogger("product-planning-analyze-api");
const requestSchema = z.union([
z.object({
kind: z.literal("review"),
rows: z
.array(
z.object({
itemId: z.string(),
date: z.string(),
content: z.string(),
appendComment: z.string().optional(),
sku: z.string().optional(),
userId: z.string().optional(),
images: z.array(z.string()),
videoUrls: z.array(z.string()),
})
)
.min(1),
}),
z.object({
kind: z.literal("qa"),
rows: z
.array(
z.object({
itemId: z.string(),
question: z.string(),
questionDetail: z.string().optional(),
answer: z.string(),
answerUser: z.string().optional(),
answerDate: z.string().optional(),
isOfficial: z.boolean(),
})
)
.min(1),
}),
]);
function jsonError(status: number, error: string) {
const payload: ProductPlanningAnalyzeResponse = {
success: false,
error,
};
return NextResponse.json(payload, { status });
}
export async function POST(request: NextRequest) {
const token = await getProductPlanningToken(request);
if (!token?.sub) {
return jsonError(401, "未登录");
}
if (!(await isProductPlanningEnabled())) {
return jsonError(403, PRODUCT_PLANNING_DISABLED_MESSAGE);
}
let body: unknown;
try {
body = await request.json();
} catch {
return jsonError(400, "请求体不是合法 JSON");
}
const parsed = requestSchema.safeParse(body);
if (!parsed.success) {
return jsonError(400, "请求参数不合法或 rows 为空");
}
const resolvedModel = await resolveProductPlanningTextModel();
if (!resolvedModel.model) {
return jsonError(500, resolvedModel.error || "当前站点没有可用的文本分析模型");
}
try {
const analyzeRequest = parsed.data as ProductPlanningAnalyzeRequest;
const prompt = buildProductPlanningAnalyzePrompt(analyzeRequest);
const { text } = await generateText({
model: resolvedModel.model,
prompt,
temperature: 0.3,
});
const payload: ProductPlanningAnalyzeResponse = {
success: true,
markdown: text,
};
return NextResponse.json(payload);
} catch (error) {
logger.error("request_failed", error);
return jsonError(502, "AI 分析失败");
}
}