import { NextRequest } from "next/server"; import { requireMembership } from "@/lib/blindbox"; import { runPlanGeneration } from "@/lib/blindboxPlanGen"; import { getAuthUserId } from "@/lib/auth"; function encodeSSE(event: string, data: string): string { return `event: ${event}\ndata: ${data}\n\n`; } export async function POST(req: NextRequest): Promise { let roomId: string; let userId: string; let availableTime: { date: string; startHour: number; endHour: number }; try { userId = await getAuthUserId(req); const body = await req.json(); roomId = body.roomId; availableTime = body.availableTime; if (!roomId) { return new Response( JSON.stringify({ error: "roomId 不能为空" }), { status: 400, headers: { "Content-Type": "application/json" } }, ); } await requireMembership(roomId, userId); const at = availableTime; if ( !at?.date || typeof at.startHour !== "number" || typeof at.endHour !== "number" || at.endHour <= at.startHour ) { return new Response( JSON.stringify({ error: "请选择有效的可用时间" }), { status: 400, headers: { "Content-Type": "application/json" } }, ); } } catch (e) { const message = e instanceof Error ? e.message : "请求参数错误"; const status = e instanceof Error && "status" in e ? (e as { status: number }).status : 400; return new Response(JSON.stringify({ error: message }), { status, headers: { "Content-Type": "application/json" }, }); } const signal = req.signal; const stream = new ReadableStream({ async start(controller) { const encoder = new TextEncoder(); let closed = false; const push = (event: string, data: string) => { if (closed) return; controller.enqueue(encoder.encode(encodeSSE(event, data))); }; const cleanup = () => { if (closed) return; closed = true; clearInterval(heartbeatId); try { controller.close(); } catch {} }; // 30s heartbeat to prevent proxy disconnects const heartbeatId = setInterval(() => { push("heartbeat", "ping"); }, 30_000); // Clean up when client disconnects signal.addEventListener("abort", cleanup); try { const result = await runPlanGeneration(roomId, userId, availableTime, (message) => { push("status", message); }); push("plan", JSON.stringify({ id: result.id, days: result.days, createdAt: result.createdAt })); } catch (e) { const message = e instanceof Error ? e.message : "生成计划失败"; push("error", message); } finally { cleanup(); } }, }); return new Response(stream, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", }, }); }