118 lines
3.4 KiB
TypeScript
118 lines
3.4 KiB
TypeScript
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
|
|
}
|