import { Router } from 'express' import { config } from '../../config/index.js' interface AIMLAPISTTCreateResponse { generation_id?: string status?: string error?: { message: string } } interface AIMLAPISTTQueryResponse { status?: string result?: { results?: { channels?: Array<{ alternatives?: Array<{ transcript?: string }> }> } } error?: { message: string } } export function createVoiceRoutes(): Router { const router = Router() router.post('/stt', async (req, res) => { console.log('[Voice] Request received, body keys:', Object.keys(req.body || {})) try { const apiKey = config.minimaxApiKey console.log('[Voice] API Key exists:', !!apiKey) if (!apiKey) { res.status(500).json({ error: 'MiniMax API key not configured' }) return } if (!req.body.audio) { res.status(400).json({ error: 'No audio data provided' }) return } const audioBuffer = Buffer.from(req.body.audio, 'base64') console.log('[Voice] Audio buffer size:', audioBuffer.length) const formData = new FormData() const blob = new Blob([audioBuffer], { type: 'audio/webm' }) formData.append('file', blob, 'audio.webm') formData.append('model', '#g1_whisper-large') console.log('[Voice] Creating STT job via MiniMax...') const createResponse = await fetch('https://api.minimax.chat/v1/stt/create', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, }, body: formData, }) console.log('[Voice] Create response status:', createResponse.status) const createData: AIMLAPISTTCreateResponse = await createResponse.json() console.log('[Voice] Create response:', createData) if (!createResponse.ok || !createData.generation_id) { console.error('[Voice] Failed to create STT job:', createData.error?.message) res.status(500).json({ error: createData.error?.message || 'Failed to create STT job' }) return } const jobId = createData.generation_id console.log('[Voice] Job ID:', jobId) console.log('[Voice] Polling for result...') let resultText = '' for (let i = 0; i < 30; i++) { await new Promise(resolve => setTimeout(resolve, 1000)) const queryResponse = await fetch(`https://api.minimax.chat/v1/stt/${jobId}`, { headers: { 'Authorization': `Bearer ${apiKey}`, }, }) const queryData: AIMLAPISTTQueryResponse = await queryResponse.json() console.log('[Voice] Query response:', queryData) if (queryData.status === 'succeeded') { resultText = queryData.result?.results?.channels?.[0]?.alternatives?.[0]?.transcript || '' break } else if (queryData.status === 'failed') { console.error('[Voice] STT job failed:', queryData.error?.message) res.status(500).json({ error: queryData.error?.message || 'STT processing failed' }) return } } if (!resultText) { res.status(500).json({ error: 'STT processing timeout' }) return } console.log('[Voice] Final result:', resultText) res.json({ text: resultText }) } catch (error) { console.error('[Voice] STT error:', error) res.status(500).json({ error: 'Failed to process audio' }) } }) return router }