import { NextResponse } from "next/server"; import { ZodError } from "zod"; import { prisma } from "@/lib/db"; import { getActiveAccountId } from "@/lib/account-context"; import { generateOutreachForScanItem } from "@/lib/services/outreach"; import { trackAiTask } from "@/lib/jobs/track"; import { isPlacementGoal } from "@/lib/types/topic-goal"; export const maxDuration = 120; export async function POST(request: Request) { try { const body = (await request.json()) as { scanItemId?: string; scanId?: string; limit?: number; productBrief?: string | null; productContext?: string | null; topicId?: string; }; if (body.scanItemId) { const accountId = await getActiveAccountId(); const item = await prisma.scanItem.findUnique({ where: { id: body.scanItemId }, include: { scan: { include: { topic: true } } }, }); if (!item || (accountId && item.scan.accountId !== accountId)) { return NextResponse.json({ error: "找不到貼文" }, { status: 404 }); } if (!isPlacementGoal(item.scan.scanGoal ?? item.scan.topic.topicGoal)) { return NextResponse.json({ error: "這篇來自拷貝忍者,不會送到找 TA" }, { status: 400 }); } if (body.productContext !== undefined && body.productContext !== null) { await prisma.topic.update({ where: { id: item.scan.topicId }, data: { productContext: body.productContext.trim() || null }, }); } // 傳「原始」品牌資料(JSON),讓 service 自行格式化並取出 CTA/網址 const rawBrief = body.productBrief?.trim() || body.productContext?.trim() || undefined; const target = await trackAiTask("生成找 TA 話術", () => generateOutreachForScanItem(body.scanItemId!, { productBrief: rawBrief }) ); return NextResponse.json({ target }); } if (!body.scanId) { return NextResponse.json({ error: "缺少 scanItemId 或 scanId" }, { status: 400 }); } const accountId = await getActiveAccountId(); const scan = await prisma.scan.findUnique({ where: { id: body.scanId }, include: { topic: true, items: { where: { OR: [{ qualityTier: null }, { qualityTier: { not: "EXCLUDE" } }] }, orderBy: [{ combinedScore: "desc" }, { score: "desc" }], take: body.limit ?? 8, include: { replies: { orderBy: { likeCount: "desc" }, take: 5 } }, }, }, }); if (!scan || (accountId && scan.accountId !== accountId)) { return NextResponse.json({ error: "找不到海巡紀錄" }, { status: 404 }); } if (!isPlacementGoal(scan.scanGoal ?? scan.topic.topicGoal)) { return NextResponse.json({ error: "只有找 TA 任務可以批次生成留言" }, { status: 400 }); } const created = await trackAiTask("批次生成找 TA 話術", async () => { const targets = []; for (const item of scan.items) { targets.push(await generateOutreachForScanItem(item.id)); } return targets; }); return NextResponse.json({ targets: created }); } catch (error) { if (error instanceof ZodError) { return NextResponse.json( { error: "AI 回傳格式不完整(缺少留言正文),請再試一次" }, { status: 500 } ); } const message = error instanceof Error ? error.message : "生成獲客留言失敗"; return NextResponse.json({ error: message }, { status: 500 }); } }