import { Router } from 'express'; import multer from 'multer'; import { join } from 'path'; import { tmpdir } from 'os'; import { rm, writeFile } from 'fs'; import { promisify } from 'util'; import { whisperService } from '../services/whisper.service.js'; import { raspiService } from '../services/raspi.service.js'; import { llamacppService } from '../services/llama.service.js'; const unlinkAsync = promisify(rm); const writeFileAsync = promisify(writeFile); const router = Router(); const upload = multer({ storage: multer.memoryStorage() }); router.get('/incoming', async (_req, res) => { try { const result = await raspiService.listIncomingAudio(); res.json(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Unknown error'; res.status(500).json({ error: `List incoming failed: ${message}` }); } }); router.post('/lock/:filename', async (req, res) => { try { const { filename } = req.params; const result = await raspiService.lockAudio({ filename }); res.json(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Unknown error'; res.status(500).json({ error: `Lock audio failed: ${message}` }); } }); router.post('/unlock/:filename', async (req, res) => { try { const { filename } = req.params; const result = await raspiService.unlockAudio({ filename }); res.json(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Unknown error'; res.status(500).json({ error: `Unlock audio failed: ${message}` }); } }); router.post('/cancel/:filename', async (req, res) => { try { const { filename } = req.params; const result = await raspiService.cancelAudio({ filename }); res.json(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Unknown error'; res.status(500).json({ error: `Cancel audio failed: ${message}` }); } }); router.post('/process/:filename', async (req, res) => { try { const { filename } = req.params; const result = await raspiService.processAudio({ filename }); res.json(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Unknown error'; res.status(500).json({ error: `Process audio failed: ${message}` }); } }); router.post('/upload', upload.single('file'), async (req, res) => { let tmpFile: string | undefined; let tmpTxt: string | undefined; try { if (!req.file) { return res.status(400).json({ error: 'No audio file provided' }); } const ext = req.file.originalname.split('.').pop()?.toLowerCase() || 'wav'; tmpFile = join(tmpdir(), `quibot-audio-${Date.now()}.${ext}`); await writeFileAsync(tmpFile, req.file.buffer); const transcription = await whisperService.transcribe(tmpFile); console.log(transcription); const txtPath = join(tmpdir(), `quibot-audio-${Date.now()}.txt`); tmpTxt = txtPath; await writeFileAsync(txtPath, transcription); const llmResponse = await llamacppService.chatWithPreamble(transcription).catch( (err: unknown) => { const msg = err instanceof Error ? err.message : String(err); console.error(`[audio] llama.cpp failed: ${msg}`); return undefined; }, ); res.json({ transcription, llmResponse, originalFilename: req.file.originalname, }); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Unknown error'; res.status(500).json({ error: `Audio transcription failed: ${message}` }); } finally { if (tmpFile) { try { await unlinkAsync(tmpFile); } catch { // ignore cleanup errors } } if (tmpTxt) { try { await unlinkAsync(tmpTxt); } catch { // ignore cleanup errors } } } }); export default router;