import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { getProvider, getAvailableProviders } from "@/lib/analysis/providers/registry"; 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(), provider: z.string().optional(), ollamaModel: z.string().optional(), }); /** * POST /api/analyze * * Accepts BPM detection results from the browser and uses the selected provider * to generate a draft CTP document for human review. * * Body (JSON): * { bpm, duration, title?, artist?, mbid?, contributed_by?, provider?, ollamaModel? } * * 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, provider: providerId, ollamaModel } = parsed.data; // Validate Ollama-specific requirement if (providerId === "ollama" && (!ollamaModel || ollamaModel.trim() === "")) { return NextResponse.json( { error: "ollamaModel is required when using the Ollama provider" }, { status: 400 } ); } // Resolve provider let provider; if (providerId) { try { provider = await getProvider(providerId); } catch (err) { return NextResponse.json( { error: err instanceof Error ? err.message : String(err) }, { status: 400 } ); } } else { const available = await getAvailableProviders(); provider = available[0]; if (!provider) { return NextResponse.json( { error: "No analysis providers are currently available" }, { status: 503 } ); } } const input = { bpm, duration, title, artist, mbid: mbid ?? null, contributed_by: contributed_by ?? "anonymous", ollamaModel, }; let ctpDoc; try { ctpDoc = await provider.generateCTP(input); } catch (err) { console.error(`[analyze] Provider '${provider.label}' failed:`, err); return NextResponse.json( { error: `Provider '${provider.label}' failed: ${err instanceof Error ? err.message : String(err)}`, }, { status: 502 } ); } // Validate the output against the CTP schema const validation = validateCTP(ctpDoc); const warnings: string[] = []; if (!validation.success) { warnings.push(...validation.errors.issues.map((i) => `${i.path.join(".")}: ${i.message}`)); } return NextResponse.json({ ctp: ctpDoc, warnings }); }