diff --git a/src/routes/adminConfig.js b/src/routes/adminConfig.js index 573ede3..de63d5b 100644 --- a/src/routes/adminConfig.js +++ b/src/routes/adminConfig.js @@ -10,6 +10,7 @@ var PROMPTS = require('../utils/prompts'); var logger = require('../utils/logger'); var { gatewayUrl } = require('../utils/errors'); var { GOOGLE_TTS_VOICES, OPENAI_TTS_VOICES, getTTSEnvProvider, getLiteLLMHeaders, getLiteLLMTTSModels, getTTSProvider, getTTSVoiceLists } = require('../utils/ttsProvider'); +var { getSTTDependencies, getSTTModelLists, getSTTProvider } = require('../utils/sttProvider'); router.use(authMiddleware); @@ -626,21 +627,9 @@ router.post('/config/tts/test', async function(req, res) { // ── GET STT provider status and model list ───────────────────────────────── router.get('/config/stt', async function(req, res) { try { - var { isGoogleSTTConfigured } = require('../utils/transcribeGoogle'); - var { isAWSTranscribeConfigured } = require('../utils/transcribeAWS'); - var { isLocalWhisperConfigured } = require('../utils/transcribeLocal'); - var { whisperClient, litellmClient } = require('../utils/ai'); - var envProvider = process.env.TRANSCRIBE_PROVIDER || 'auto'; - var activeProvider = envProvider; - if (envProvider === 'auto') { - if (isGoogleSTTConfigured()) activeProvider = 'google'; - else if (isAWSTranscribeConfigured()) activeProvider = 'aws'; - else if (isLocalWhisperConfigured()) activeProvider = 'local'; - else if (litellmClient) activeProvider = 'litellm'; - else if (whisperClient) activeProvider = 'openai'; - else activeProvider = 'none'; - } + var configured = getSTTDependencies(); + var activeProvider = getSTTProvider(configured); var dbModel = await db.getSetting('stt.model') || ''; var envModel = process.env.GOOGLE_STT_MODEL || process.env.LITELLM_STT_MODEL || ''; res.json({ @@ -650,20 +639,8 @@ router.get('/config/stt', async function(req, res) { currentModel: dbModel || envModel, dbModel: dbModel, envModel: envModel, - configured: { - google: isGoogleSTTConfigured(), - aws: isAWSTranscribeConfigured(), - local: isLocalWhisperConfigured(), - litellm: !!litellmClient, - openai: !!whisperClient - }, - models: { - google: ['gemini-2.0-flash', 'gemini-2.5-flash', 'gemini-2.0-flash-lite'], - aws: ['standard', 'medical'], - openai: ['whisper-1'], - litellm: ['whisper-1', 'gemini-2.0-flash', 'gemini-2.5-flash'], - local: ['tiny', 'base', 'small', 'medium', 'large'] - } + configured: configured, + models: getSTTModelLists() }); } catch (e) { res.status(500).json({ error: 'Request failed' }); } }); @@ -673,22 +650,10 @@ router.get('/config/stt/discover', async function(req, res) { try { var search = (req.query.q || '').toLowerCase().trim(); var axios = require('axios'); - var { isGoogleSTTConfigured } = require('../utils/transcribeGoogle'); - var { isAWSTranscribeConfigured } = require('../utils/transcribeAWS'); - var { isLocalWhisperConfigured } = require('../utils/transcribeLocal'); var { whisperClient, litellmClient } = require('../utils/ai'); var discovered = []; - var envProvider = process.env.TRANSCRIBE_PROVIDER || 'auto'; - var provider = envProvider; - if (envProvider === 'auto') { - if (isGoogleSTTConfigured()) provider = 'google'; - else if (isAWSTranscribeConfigured()) provider = 'aws'; - else if (isLocalWhisperConfigured()) provider = 'local'; - else if (litellmClient) provider = 'litellm'; - else if (whisperClient) provider = 'openai'; - else provider = 'none'; - } + var provider = getSTTProvider(); if (provider === 'google') { ['gemini-2.5-flash','gemini-2.0-flash','gemini-2.0-flash-lite','gemini-1.5-pro','gemini-1.5-flash'].forEach(function(m) { @@ -757,22 +722,14 @@ router.post('/config/stt/test', async function(req, res) { var audioBuffer = Buffer.from(audioBase64, 'base64'); var mime = mimeType || 'audio/webm'; - var { isGoogleSTTConfigured, transcribeWithGemini } = require('../utils/transcribeGoogle'); - var { isAWSTranscribeConfigured, transcribeWithAWS } = require('../utils/transcribeAWS'); - var { isLocalWhisperConfigured, transcribeWithLocal } = require('../utils/transcribeLocal'); + var { transcribeWithGemini } = require('../utils/transcribeGoogle'); + var { transcribeWithAWS } = require('../utils/transcribeAWS'); + var { transcribeWithLocal } = require('../utils/transcribeLocal'); var { whisperClient, litellmClient } = require('../utils/ai'); var axios = require('axios'); - var envProvider = process.env.TRANSCRIBE_PROVIDER || 'auto'; - var provider = envProvider; - if (envProvider === 'auto') { - if (isGoogleSTTConfigured()) provider = 'google'; - else if (isAWSTranscribeConfigured()) provider = 'aws'; - else if (isLocalWhisperConfigured()) provider = 'local'; - else if (litellmClient) provider = 'litellm'; - else if (whisperClient) provider = 'openai'; - else return res.json({ success: false, error: 'No STT provider configured' }); - } + var provider = getSTTProvider(); + if (provider === 'none') return res.json({ success: false, error: 'No STT provider configured' }); var start = Date.now(); var text = ''; diff --git a/src/utils/sttProvider.js b/src/utils/sttProvider.js new file mode 100644 index 0000000..e3aed8f --- /dev/null +++ b/src/utils/sttProvider.js @@ -0,0 +1,53 @@ +const GOOGLE_STT_MODELS = ['gemini-2.0-flash', 'gemini-2.5-flash', 'gemini-2.0-flash-lite']; +const AWS_STT_MODES = ['standard', 'medical']; +const OPENAI_STT_MODELS = ['whisper-1']; +const LITELLM_STT_MODELS = ['whisper-1', 'gemini-2.0-flash', 'gemini-2.5-flash']; +const LOCAL_WHISPER_MODELS = ['tiny', 'base', 'small', 'medium', 'large']; + +function getSTTDependencies() { + var { isGoogleSTTConfigured } = require('./transcribeGoogle'); + var { isAWSTranscribeConfigured } = require('./transcribeAWS'); + var { isLocalWhisperConfigured } = require('./transcribeLocal'); + var { whisperClient, litellmClient } = require('./ai'); + return { + google: isGoogleSTTConfigured(), + aws: isAWSTranscribeConfigured(), + local: isLocalWhisperConfigured(), + litellm: !!litellmClient, + openai: !!whisperClient + }; +} + +function getSTTProvider(deps) { + var env = process.env.TRANSCRIBE_PROVIDER || 'auto'; + if (env !== 'auto') return env; + + var configured = deps || getSTTDependencies(); + if (configured.google) return 'google'; + if (configured.aws) return 'aws'; + if (configured.local) return 'local'; + if (configured.litellm) return 'litellm'; + if (configured.openai) return 'openai'; + return 'none'; +} + +function getSTTModelLists() { + return { + google: GOOGLE_STT_MODELS, + aws: AWS_STT_MODES, + openai: OPENAI_STT_MODELS, + litellm: LITELLM_STT_MODELS, + local: LOCAL_WHISPER_MODELS + }; +} + +module.exports = { + AWS_STT_MODES, + GOOGLE_STT_MODELS, + LITELLM_STT_MODELS, + LOCAL_WHISPER_MODELS, + OPENAI_STT_MODELS, + getSTTDependencies, + getSTTModelLists, + getSTTProvider +}; diff --git a/test/stt-provider.test.js b/test/stt-provider.test.js new file mode 100644 index 0000000..7bfd279 --- /dev/null +++ b/test/stt-provider.test.js @@ -0,0 +1,48 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); + +const ENV_KEYS = ['TRANSCRIBE_PROVIDER']; + +function withEnv(overrides, fn) { + const previous = {}; + ENV_KEYS.forEach((key) => { previous[key] = process.env[key]; delete process.env[key]; }); + Object.entries(overrides).forEach(([key, value]) => { process.env[key] = value; }); + try { + return fn(); + } finally { + ENV_KEYS.forEach((key) => { + if (previous[key] === undefined) delete process.env[key]; + else process.env[key] = previous[key]; + }); + } +} + +test('STT provider respects explicit provider override', () => { + const sttProvider = require('../src/utils/sttProvider'); + withEnv({ TRANSCRIBE_PROVIDER: 'litellm' }, () => { + assert.equal(sttProvider.getSTTProvider({ google: true }), 'litellm'); + }); +}); + +test('STT provider auto-detects in existing priority order', () => { + const sttProvider = require('../src/utils/sttProvider'); + withEnv({}, () => { + assert.equal(sttProvider.getSTTProvider({ google: true, aws: true, local: true, litellm: true, openai: true }), 'google'); + assert.equal(sttProvider.getSTTProvider({ aws: true, local: true, litellm: true, openai: true }), 'aws'); + assert.equal(sttProvider.getSTTProvider({ local: true, litellm: true, openai: true }), 'local'); + assert.equal(sttProvider.getSTTProvider({ litellm: true, openai: true }), 'litellm'); + assert.equal(sttProvider.getSTTProvider({ openai: true }), 'openai'); + assert.equal(sttProvider.getSTTProvider({}), 'none'); + }); +}); + +test('STT model lists preserve admin defaults', () => { + const sttProvider = require('../src/utils/sttProvider'); + assert.deepEqual(sttProvider.getSTTModelLists(), { + google: ['gemini-2.0-flash', 'gemini-2.5-flash', 'gemini-2.0-flash-lite'], + aws: ['standard', 'medical'], + openai: ['whisper-1'], + litellm: ['whisper-1', 'gemini-2.0-flash', 'gemini-2.5-flash'], + local: ['tiny', 'base', 'small', 'medium', 'large'] + }); +});