const express = require('express'); const router = express.Router(); const axios = require('axios'); const { synthesizeWithGoogleTTS, isGoogleTTSConfigured } = require('../utils/ttsGoogle'); const { authMiddleware } = require('../middleware/auth'); // Provider priority (auto-detect): // TTS_PROVIDER=google → Google Cloud TTS direct (HIPAA eligible) // TTS_PROVIDER=litellm → LiteLLM proxy // TTS_PROVIDER=elevenlabs → ElevenLabs (not HIPAA) // Auto: google > litellm > elevenlabs function getTTSProvider() { var env = process.env.TTS_PROVIDER; if (env === 'google') return 'google'; if (env === 'litellm') return 'litellm'; if (env === 'elevenlabs') return 'elevenlabs'; // Auto-detect: litellm > google > elevenlabs // LiteLLM Vertex TTS works correctly via alias (tts-1) if (process.env.LITELLM_API_BASE) return 'litellm'; if (isGoogleTTSConfigured()) return 'google'; if (process.env.ELEVENLABS_API_KEY) return 'elevenlabs'; return 'none'; } var ttsProvider = getTTSProvider(); console.log('🔊 TTS provider:', ttsProvider + (ttsProvider === 'google' ? ' (voice: ' + (process.env.GOOGLE_TTS_VOICE || 'en-US-Journey-F') + ')' : '')); router.post('/text-to-speech', authMiddleware, async (req, res) => { try { var text = (req.body.text || '').substring(0, 5000); if (!text) return res.status(400).json({ error: 'No text provided' }); if (ttsProvider === 'google') { var buffer = await synthesizeWithGoogleTTS(text); res.set('Content-Type', 'audio/mpeg'); res.set('X-TTS-Provider', 'google-tts'); return res.send(buffer); } if (ttsProvider === 'litellm') { if (!process.env.LITELLM_API_BASE) return res.status(400).json({ error: 'LITELLM_API_BASE not set.' }); // Use the full model path (e.g. vertex_ai/google-tts) or the model_name alias // from your LiteLLM model_list. Full path is safer if your config uses it directly. // Prefix with openai/ so LiteLLM routes correctly to the TTS endpoint var rawModel = process.env.LITELLM_TTS_MODEL || 'tts-1'; var ttsModel = rawModel.indexOf('/') === -1 ? 'openai/' + rawModel : rawModel; var ttsVoice = process.env.LITELLM_TTS_VOICE || 'en-US-Journey-F'; var base = process.env.LITELLM_API_BASE.replace(/\/+$/, ''); var ttsHeaders = { 'Content-Type': 'application/json' }; if (process.env.LITELLM_API_KEY) ttsHeaders['Authorization'] = 'Bearer ' + process.env.LITELLM_API_KEY; var ttsResp = await axios.post(base + '/v1/audio/speech', { model: ttsModel, voice: ttsVoice, input: text }, { headers: ttsHeaders, responseType: 'arraybuffer', timeout: 60000 } ); res.set('Content-Type', 'audio/mpeg'); res.set('X-TTS-Provider', 'litellm/' + ttsModel); return res.send(Buffer.from(ttsResp.data)); } if (ttsProvider === 'elevenlabs') { if (!process.env.ELEVENLABS_API_KEY) return res.status(400).json({ error: 'ElevenLabs not configured' }); var resp = await axios({ method: 'POST', url: 'https://api.elevenlabs.io/v1/text-to-speech/pNInz6obpgDQGcFmaJgB', headers: { 'xi-api-key': process.env.ELEVENLABS_API_KEY, 'Content-Type': 'application/json' }, data: { text: text, model_id: 'eleven_turbo_v2_5', voice_settings: { stability: 0.5, similarity_boost: 0.75 } }, responseType: 'arraybuffer' }); res.set('Content-Type', 'audio/mpeg'); res.set('X-TTS-Provider', 'elevenlabs'); return res.send(Buffer.from(resp.data)); } res.status(400).json({ error: 'TTS not configured. Set GOOGLE_VERTEX_PROJECT, LITELLM_API_BASE, or ELEVENLABS_API_KEY.' }); } catch (err) { var detail = err.response && err.response.data ? JSON.stringify(err.response.data).substring(0, 500) : err.message; console.error('[TTS] Error (' + ttsProvider + '):', detail); res.status(500).json({ error: 'TTS failed: ' + detail }); } }); module.exports = router;