- Increase logo size (48x48 desktop, 56x56 mobile) for better visibility - Add logo as favicon - Add logo to mobile header - Move user menu to navigation bars (sidebar on desktop, bottom bar on mobile) - Fix desktop chat layout - container structure prevents voice controls cutoff - Fix mobile bottom bar - use icon-only ActionIcons instead of truncated text buttons - Hide Create Node/New Conversation buttons on mobile to save header space - Make fixed header and voice controls work properly with containers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
84 lines
2.1 KiB
TypeScript
84 lines
2.1 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { createClient } from '@deepgram/sdk';
|
|
|
|
/**
|
|
* Text-to-Speech API route using Deepgram Aura
|
|
*
|
|
* Converts text to natural-sounding speech using Deepgram's Aura-2 model.
|
|
* Returns audio data that can be played in the browser.
|
|
*/
|
|
export async function POST(request: NextRequest) {
|
|
const deepgramApiKey = process.env.DEEPGRAM_API_KEY;
|
|
|
|
if (!deepgramApiKey) {
|
|
return NextResponse.json(
|
|
{ error: 'Deepgram API key not configured' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
|
|
try {
|
|
const { text } = await request.json();
|
|
|
|
if (!text || typeof text !== 'string') {
|
|
return NextResponse.json(
|
|
{ error: 'Text parameter is required' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
console.log('[TTS] Generating speech for text:', text.substring(0, 50) + '...');
|
|
|
|
const deepgram = createClient(deepgramApiKey);
|
|
|
|
// Generate speech using Deepgram Aura
|
|
const response = await deepgram.speak.request(
|
|
{ text },
|
|
{
|
|
model: 'aura-2-thalia-en', // Natural female voice
|
|
encoding: 'linear16',
|
|
container: 'wav',
|
|
}
|
|
);
|
|
|
|
// Get the audio stream
|
|
const stream = await response.getStream();
|
|
|
|
if (!stream) {
|
|
throw new Error('No audio stream returned from Deepgram');
|
|
}
|
|
|
|
// Convert stream to buffer
|
|
const chunks: Uint8Array[] = [];
|
|
const reader = stream.getReader();
|
|
|
|
try {
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
if (value) chunks.push(value);
|
|
}
|
|
} finally {
|
|
reader.releaseLock();
|
|
}
|
|
|
|
const buffer = Buffer.concat(chunks);
|
|
|
|
console.log('[TTS] ✓ Generated', buffer.length, 'bytes of audio');
|
|
|
|
// Return audio with proper headers
|
|
return new NextResponse(buffer, {
|
|
headers: {
|
|
'Content-Type': 'audio/wav',
|
|
'Content-Length': buffer.length.toString(),
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error('[TTS] Error generating speech:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to generate speech' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|