80 lines
3.5 KiB
JavaScript
80 lines
3.5 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const multer = require('multer');
|
|
const { litellmClient } = require('../utils/ai');
|
|
const { authMiddleware } = require('../middleware/auth');
|
|
var logger = require('../utils/logger');
|
|
var { gatewayUrl } = require('../utils/errors');
|
|
var { getLiteLLMHeaders } = require('../utils/litellm');
|
|
|
|
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 25 * 1024 * 1024 } });
|
|
|
|
// STT is intentionally routed only through LiteLLM.
|
|
function getTranscribeProvider() {
|
|
var env = process.env.TRANSCRIBE_PROVIDER;
|
|
if (env === 'litellm') return 'litellm';
|
|
if (litellmClient) return 'litellm';
|
|
return 'none';
|
|
}
|
|
|
|
function isTranscribeAvailable() {
|
|
if (litellmClient) return true;
|
|
return false;
|
|
}
|
|
|
|
var provider = getTranscribeProvider();
|
|
var available = isTranscribeAvailable();
|
|
console.log('🎙️ Transcribe provider:', provider + (available ? '' : ' (NOT CONFIGURED — browser speech only)'));
|
|
|
|
router.get('/transcribe/status', authMiddleware, (req, res) => {
|
|
res.json({ available: available, provider: available ? provider : 'none' });
|
|
});
|
|
|
|
router.post('/transcribe', authMiddleware, upload.single('audio'), async (req, res) => {
|
|
try {
|
|
if (!req.file) return res.status(400).json({ error: 'No audio' });
|
|
var startTime = Date.now();
|
|
var fileSize = req.file.size;
|
|
|
|
// Get user's preferred STT model (if set), then admin DB default, then env
|
|
var db = require('../db/database');
|
|
var userPrefs = await db.get('SELECT stt_model FROM users WHERE id = ?', [req.user.id]);
|
|
var userModel = userPrefs?.stt_model;
|
|
var adminSttModel = await db.getSetting('stt.model') || '';
|
|
|
|
console.log('[Transcribe] Received ' + (fileSize / 1024).toFixed(0) + 'KB audio (' + (req.file.mimetype || 'unknown') + ') via ' + provider + (userModel ? ' (user model: ' + userModel + ')' : ''));
|
|
|
|
if (provider !== 'litellm' || !process.env.LITELLM_API_BASE) return res.status(400).json({ error: 'LITELLM_API_BASE not set.' });
|
|
var sttModel = userModel || adminSttModel || process.env.LITELLM_STT_MODEL || '';
|
|
if (!sttModel) return res.status(400).json({ error: 'No LiteLLM STT model configured.' });
|
|
var mimeType = req.file.mimetype || 'audio/webm';
|
|
var ext = mimeType.split('/')[1] || 'webm';
|
|
|
|
var file = new File([req.file.buffer], 'audio.' + ext, { type: mimeType });
|
|
var form = new FormData();
|
|
form.append('file', file);
|
|
form.append('model', sttModel);
|
|
|
|
var sttResp = await fetch(gatewayUrl('/audio/transcriptions'), {
|
|
method: 'POST', headers: getLiteLLMHeaders(), body: form,
|
|
});
|
|
if (!sttResp.ok) {
|
|
var errBody = await sttResp.text();
|
|
throw new Error('LiteLLM /audio/transcriptions ' + sttResp.status + ': ' + errBody.substring(0, 500));
|
|
}
|
|
var data = await sttResp.json();
|
|
var text = (data && data.text) ? String(data.text).trim() : '';
|
|
console.log('[Transcribe] LiteLLM/' + sttModel + ' done in ' + (Date.now() - startTime) + 'ms');
|
|
logger.audit(req.user.id, 'transcribe', 'Transcribed audio via litellm', req, { category: 'clinical' });
|
|
return res.json({ success: true, text: text, provider: 'litellm/' + sttModel, duration: Date.now() - startTime });
|
|
|
|
} catch (err) {
|
|
var detail = err.response && err.response.data
|
|
? JSON.stringify(err.response.data).substring(0, 500)
|
|
: err.message;
|
|
console.error('[Transcribe] Error (' + provider + '):', detail);
|
|
res.status(500).json({ error: detail });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|