import { generateText } from "ai"; import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { inlineVisionImages } from "@/lib/ai/vision-inline"; import { createLogger } from "@/lib/observability/logger"; import { PRODUCT_PLANNING_ADVANCED_DISABLED, PRODUCT_PLANNING_ADVANCED_DISABLED_MESSAGE, } from "@/lib/product-planning/advanced-disabled"; import { resolveProductPlanningVisionModel } 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 { ProductPlanningAnalyzeResponse } from "@/lib/product-planning/types"; export const runtime = "nodejs"; export const maxDuration = 120; const logger = createLogger("product-planning-analyze-image-api"); const requestSchema = z.object({ itemId: z.string().trim().min(1), imageUrl: z.string().trim().url(), title: z.string().trim().optional(), }); function jsonError(status: number, error: string) { const payload: ProductPlanningAnalyzeResponse = { success: false, error, }; return NextResponse.json(payload, { status }); } function buildImageAnalyzePrompt(params: { itemId: string; title?: string }) { return [ "你是一名资深电商视觉策划顾问,请分析这张竞品主图,并输出结构化 markdown。", `商品ID:${params.itemId}`, params.title ? `商品标题:${params.title}` : "", "", "请至少包含以下部分:", "1. 画面场景与拍摄方式", "2. 产品外观与结构要点", "3. 当前主图正在强调的核心卖点", "4. 用户感知风险或信息缺口", "5. 给我们做同类商品主图时可复用的优化建议", "", "要求:", "- 用中文输出", "- 结论明确,避免空泛形容词", "- 结合电商转化场景给出建议", ] .filter(Boolean) .join("\n"); } export async function POST(request: NextRequest) { if (PRODUCT_PLANNING_ADVANCED_DISABLED) { return jsonError(503, PRODUCT_PLANNING_ADVANCED_DISABLED_MESSAGE); } 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, "请求参数不合法"); } const resolvedModel = await resolveProductPlanningVisionModel(); if (!resolvedModel.model) { return jsonError(500, resolvedModel.error || "当前站点没有可用的图片分析模型"); } try { const { inlineDataUrls } = await inlineVisionImages([parsed.data.imageUrl], { maxImages: 1, maxSide: 900, timeoutMs: 12000, retries: 1, loggerPrefix: "[ProductPlanning][AnalyzeImage]", }); const effectiveImage = inlineDataUrls[0] || parsed.data.imageUrl; const { text } = await generateText({ model: resolvedModel.model, messages: [ { role: "user", content: [ { type: "text", text: buildImageAnalyzePrompt(parsed.data), }, { type: "image", image: effectiveImage, }, ], }, ], temperature: 0.2, maxTokens: 1200, } as any); return NextResponse.json({ success: true, markdown: text, } satisfies ProductPlanningAnalyzeResponse); } catch (error) { logger.error("request_failed", error); return jsonError(502, "图片分析失败"); } }