import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { generateCTPWithAI } from "@/lib/analysis/ai-ctp"; import { validateCTP } from "@/lib/ctp/validate"; // ─── Request schema ─────────────────────────────────────────────────────────── const AnalyzeRequestSchema = z.object({ bpm: z.number().min(20).max(400), duration: z.number().positive(), title: z.string().min(1).max(256).optional(), artist: z.string().min(1).max(256).optional(), mbid: z.string().uuid().optional().nullable(), contributed_by: z.string().min(1).max(64).optional(), }); /** * POST /api/analyze * * Accepts BPM detection results from the browser and uses Claude to generate * a draft CTP document for human review. * * Body (JSON): * { bpm, duration, title?, artist?, mbid?, contributed_by? } * * Returns: * { ctp: CTPDocument, warnings: string[] } */ export async function POST(req: NextRequest) { let body: unknown; try { body = await req.json(); } catch { return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); } const parsed = AnalyzeRequestSchema.safeParse(body); if (!parsed.success) { return NextResponse.json( { error: "Invalid request", details: parsed.error.flatten() }, { status: 400 } ); } const { bpm, duration, title, artist, mbid, contributed_by } = parsed.data; if (!process.env.ANTHROPIC_API_KEY) { return NextResponse.json( { error: "ANTHROPIC_API_KEY is not configured on this server" }, { status: 503 } ); } let ctpDoc; try { ctpDoc = await generateCTPWithAI({ bpm, duration, title, artist, mbid: mbid ?? null, contributedBy: contributed_by ?? "anonymous", }); } catch (err) { console.error("[analyze] AI generation failed:", err); return NextResponse.json( { error: "Failed to generate CTP document", detail: String(err) }, { status: 500 } ); } // Validate the AI output against the CTP schema const validation = validateCTP(ctpDoc); const warnings: string[] = []; if (!validation.success) { // Rather than 500-ing, return the draft with validation warnings so the user // can still see and manually correct it. warnings.push(...validation.errors.issues.map((i) => `${i.path.join(".")}: ${i.message}`)); } return NextResponse.json({ ctp: ctpDoc, warnings }); }