./kaisetsu-app/src/app/api/tts/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface DictionaryEntry {
word: string;
reading: string;
}
// Apply dictionary replacements to text
function applyDictionary(text: string, dictionary: DictionaryEntry[]): string {
let result = text;
for (const entry of dictionary) {
// Replace all occurrences of the word with its reading
result = result.split(entry.word).join(entry.reading);
}
return result;
}
// Convert custom pause tags to SSML
function convertToSSML(text: string): string {
// Convert <3s> style tags to SSML break tags
let ssmlText = text.replace(/<(\d+\.?\d*)s>/g, (_, seconds) => {
return `<break time="${seconds}s"/>`;
});
// Also support [breaktime="3s"] style tags for backward compatibility
ssmlText = ssmlText.replace(/\[breaktime="(\d+\.?\d*)s"\]/g, (_, seconds) => {
return `<break time="${seconds}s"/>`;
});
// Wrap in speak tags
return `<speak>${ssmlText}</speak>`;
}
export async function POST(request: NextRequest) {
try {
const { text, voiceType = 'mayuko', dictionary = [] } = await request.json();
if (!text) {
return NextResponse.json({ error: 'No text provided' }, { status: 400 });
}
const apiKey = process.env.ELEVENLABS_API_KEY;
if (!apiKey) {
return NextResponse.json(
{ error: 'ElevenLabs API key not configured' },
{ status: 500 }
);
}
// voiceType is now the actual ElevenLabs voice ID from the CSV
const voiceId = voiceType;
// Apply dictionary replacements first
const processedText = applyDictionary(text, dictionary);
const ssmlText = convertToSSML(processedText);
// Call ElevenLabs API
const response = await fetch(
`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,
{
method: 'POST',
headers: {
'Accept': 'audio/mpeg',
'Content-Type': 'application/json',
'xi-api-key': apiKey,
},
body: JSON.stringify({
text: ssmlText,
model_id: 'eleven_v3',
voice_settings: {
stability: 1.0,
similarity_boost: 0.75,
style: 0.0,
use_speaker_boost: true,
},
}),
}
);
if (!response.ok) {
const errorText = await response.text();
console.error('ElevenLabs API error:', errorText);
return NextResponse.json(
{ error: `TTS generation failed: ${response.status}` },
{ status: response.status }
);
}
// Return audio as blob
const audioBuffer = await response.arrayBuffer();
return new NextResponse(audioBuffer, {
headers: {
'Content-Type': 'audio/mpeg',
'Content-Length': audioBuffer.byteLength.toString(),
},
});
} catch (error) {
console.error('TTS error:', error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'TTS failed' },
{ status: 500 }
);
}
}