125 lines
3.8 KiB
TypeScript
125 lines
3.8 KiB
TypeScript
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;
|