export const dynamic = 'force-dynamic' export const runtime = 'nodejs' import { NextResponse } from 'next/server' const ELEVENLABS_BASE = 'https://api.elevenlabs.io/v1' const DEFAULT_MODEL = 'eleven_flash_v2_5' function getVoiceId(agent: string | undefined): string | null { if (agent === 'lusya') return process.env.LUSYA_TTS_VOICE || null return process.env.COSMO_TTS_VOICE || null } export async function POST(req: Request) { const apiKey = process.env.ELEVENLABS_API_KEY if (!apiKey) { return NextResponse.json({ error: 'tts_not_configured' }, { status: 503 }) } const body = await req.json().catch(() => null) const text = typeof body?.text === 'string' ? body.text.trim() : '' const agent = typeof body?.agent === 'string' ? body.agent : 'cosmo' if (!text) { return NextResponse.json({ error: 'text required' }, { status: 400 }) } if (text.length > 4000) { return NextResponse.json({ error: 'text too long (>4000)' }, { status: 400 }) } const voiceId = getVoiceId(agent) if (!voiceId) { return NextResponse.json({ error: `no voice configured for agent=${agent}` }, { status: 503 }) } const model = process.env.ELEVENLABS_MODEL || DEFAULT_MODEL const upstream = await fetch( `${ELEVENLABS_BASE}/text-to-speech/${encodeURIComponent(voiceId)}/stream?output_format=mp3_44100_64`, { method: 'POST', headers: { 'xi-api-key': apiKey, Accept: 'audio/mpeg', 'Content-Type': 'application/json', }, body: JSON.stringify({ text, model_id: model, voice_settings: { stability: 0.45, similarity_boost: 0.75, style: 0.25, use_speaker_boost: true, }, }), } ) if (!upstream.ok || !upstream.body) { const errText = await upstream.text().catch(() => '') return NextResponse.json( { error: `elevenlabs_${upstream.status}`, detail: errText.slice(0, 300) }, { status: 502 } ) } return new Response(upstream.body, { headers: { 'Content-Type': 'audio/mpeg', 'Cache-Control': 'no-store', 'X-Accel-Buffering': 'no', }, }) }