import { Communicate } from "edge-tts-ts"; import { NextResponse } from "next/server"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; const DEFAULT_VOICE = process.env.EDGE_TTS_VOICE || "zh-CN-XiaoxiaoNeural"; const MAX_TEXT_LENGTH = 12_000; const jsonError = (message: string, status: number) => NextResponse.json({ error: message }, { status }); export async function POST(request: Request) { let payload: { text?: unknown; voice?: unknown }; try { payload = await request.json() as { text?: unknown; voice?: unknown }; } catch { return jsonError("请求内容不是有效 JSON", 400); } const text = typeof payload.text === "string" ? payload.text.trim() : ""; if (!text) return jsonError("缺少待朗读文本", 400); if (text.length > MAX_TEXT_LENGTH) return jsonError(`单次文本不能超过 ${MAX_TEXT_LENGTH} 字`, 413); const voice = typeof payload.voice === "string" && payload.voice.trim() ? payload.voice.trim() : DEFAULT_VOICE; try { const communicate = new Communicate(text, { voice }); const chunks: Uint8Array[] = []; let byteLength = 0; for await (const chunk of communicate.stream()) { if (chunk.type !== "audio") continue; chunks.push(chunk.data); byteLength += chunk.data.byteLength; } if (!byteLength) return jsonError("语音服务返回了空音频", 502); const audio = new Uint8Array(byteLength); let offset = 0; for (const chunk of chunks) { audio.set(chunk, offset); offset += chunk.byteLength; } return new Response(audio.buffer.slice(audio.byteOffset, audio.byteOffset + audio.byteLength), { headers: { "Content-Type": "audio/mpeg", "Cache-Control": "no-store" } }); } catch (error) { console.error("[Agent TTS] 语音生成失败", error); return jsonError("语音生成失败", 502); } }