fix litellm tts search fallbacks
This commit is contained in:
parent
0e9ae8e1af
commit
413b9bc353
4 changed files with 67 additions and 7 deletions
|
|
@ -1275,12 +1275,12 @@ function adminFlashButtonBackground(btn, color) {
|
||||||
}
|
}
|
||||||
var items = data.voices || [];
|
var items = data.voices || [];
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
container.innerHTML = '<p style="font-size:13px;color:var(--g400);">No voices found' + (search ? ' matching "' + esc(search) + '"' : '') + '</p>';
|
container.innerHTML = '<p style="font-size:13px;color:var(--g400);">No voices/models found' + (search ? ' matching "' + esc(search) + '"' : '') + '</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
container.innerHTML = '<p style="font-size:12px;color:var(--g500);margin:0 0 6px;">Found ' + data.count + ' voices (provider: ' + esc(data.provider) + ')</p>' +
|
container.innerHTML = '<p style="font-size:12px;color:var(--g500);margin:0 0 6px;">Found ' + data.count + ' voices/models (provider: ' + esc(data.provider) + ')</p>' +
|
||||||
items.slice(0, 100).map(function(v) {
|
items.slice(0, 100).map(function(v) {
|
||||||
var isModel = (v.source || '').indexOf('gateway') !== -1 || (v.source || '').indexOf('builtin-model') !== -1;
|
var isModel = v.kind === 'model' || (v.source || '').indexOf('gateway') !== -1 || (v.source || '').indexOf('builtin-model') !== -1 || (v.source || '').indexOf('configured-model') !== -1;
|
||||||
var setType = isModel ? 'model' : 'voice';
|
var setType = isModel ? 'model' : 'voice';
|
||||||
var badge = isModel ? '<span style="font-size:9px;padding:1px 5px;border-radius:4px;background:var(--blue);color:white;margin-left:4px;">MODEL</span>' : '<span style="font-size:9px;padding:1px 5px;border-radius:4px;background:var(--green);color:white;margin-left:4px;">VOICE</span>';
|
var badge = isModel ? '<span style="font-size:9px;padding:1px 5px;border-radius:4px;background:var(--blue);color:white;margin-left:4px;">MODEL</span>' : '<span style="font-size:9px;padding:1px 5px;border-radius:4px;background:var(--green);color:white;margin-left:4px;">VOICE</span>';
|
||||||
return '<div style="display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;background:var(--g50);font-size:13px;">' +
|
return '<div style="display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;background:var(--g50);font-size:13px;">' +
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ var { authMiddleware, adminMiddleware } = require('../middleware/auth');
|
||||||
var PROMPTS = require('../utils/prompts');
|
var PROMPTS = require('../utils/prompts');
|
||||||
var logger = require('../utils/logger');
|
var logger = require('../utils/logger');
|
||||||
var { gatewayUrl } = require('../utils/errors');
|
var { gatewayUrl } = require('../utils/errors');
|
||||||
var { GOOGLE_TTS_VOICES, getTTSEnvProvider, getLiteLLMTTSModels, getTTSProvider, getTTSVoiceLists } = require('../utils/ttsProvider');
|
var { GOOGLE_TTS_VOICES, getTTSEnvProvider, getLiteLLMTTSDiscoveryItems, getLiteLLMTTSModels, getTTSProvider, getTTSVoiceLists } = require('../utils/ttsProvider');
|
||||||
var { getLiteLLMHeaders } = require('../utils/litellm');
|
var { getLiteLLMHeaders } = require('../utils/litellm');
|
||||||
var { getSTTDependencies, getLiteLLMSTTModels, getSTTModelLists, getSTTProvider } = require('../utils/sttProvider');
|
var { getSTTDependencies, getLiteLLMSTTModels, getSTTModelLists, getSTTProvider } = require('../utils/sttProvider');
|
||||||
var { getLiteLLMEmbeddingModels } = require('../utils/embeddings');
|
var { getLiteLLMEmbeddingModels } = require('../utils/embeddings');
|
||||||
|
|
@ -520,13 +520,19 @@ router.get('/config/tts/discover', async function(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider === 'litellm' && process.env.LITELLM_API_BASE) {
|
if (provider === 'litellm' && process.env.LITELLM_API_BASE) {
|
||||||
|
var dbVoice = await db.getSetting('tts.voice') || '';
|
||||||
|
var dbModel = await db.getSetting('tts.model') || '';
|
||||||
|
var currentVoice = dbVoice || process.env.LITELLM_TTS_VOICE || '';
|
||||||
|
var currentModel = dbModel || process.env.LITELLM_TTS_MODEL || '';
|
||||||
|
var modelInfo = [];
|
||||||
try {
|
try {
|
||||||
var liteLLMBase = (process.env.LITELLM_API_BASE || '').replace(/\/+$/, '').replace(/\/v1\/?$/, '');
|
var liteLLMBase = (process.env.LITELLM_API_BASE || '').replace(/\/+$/, '').replace(/\/v1\/?$/, '');
|
||||||
var lResp = await axios.get(liteLLMBase + '/model/info', { headers: getLiteLLMHeaders(), timeout: 10000 });
|
var lResp = await axios.get(liteLLMBase + '/model/info', { headers: getLiteLLMHeaders(), timeout: 10000 });
|
||||||
getLiteLLMTTSModels(lResp.data && lResp.data.data).forEach(function(id) {
|
modelInfo = lResp.data && lResp.data.data ? lResp.data.data : [];
|
||||||
discovered.push({ id: id, name: id, source: 'gateway-api' });
|
|
||||||
});
|
|
||||||
} catch (e) { logger.warn('LiteLLM TTS model list failed: ' + e.message); }
|
} catch (e) { logger.warn('LiteLLM TTS model list failed: ' + e.message); }
|
||||||
|
getLiteLLMTTSDiscoveryItems(modelInfo, { currentModel: currentModel, currentVoice: currentVoice }).forEach(function(item) {
|
||||||
|
discovered.push(item);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider === 'elevenlabs' && process.env.ELEVENLABS_API_KEY) {
|
if (provider === 'elevenlabs' && process.env.ELEVENLABS_API_KEY) {
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,36 @@ function getLiteLLMTTSModels(models) {
|
||||||
.map(function(model) { return model && (model.id || model.model_name) ? (model.id || model.model_name) : String(model || ''); });
|
.map(function(model) { return model && (model.id || model.model_name) ? (model.id || model.model_name) : String(model || ''); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pushUniqueTTSItem(items, item) {
|
||||||
|
if (!item || !item.id) return;
|
||||||
|
if (items.some(function(existing) { return existing.id === item.id && existing.kind === item.kind; })) return;
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLiteLLMTTSDiscoveryItems(models, opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
var items = [];
|
||||||
|
getLiteLLMTTSModels(models).forEach(function(id) {
|
||||||
|
pushUniqueTTSItem(items, { id: id, name: id, source: 'gateway-api', kind: 'model' });
|
||||||
|
});
|
||||||
|
if (opts.currentModel) {
|
||||||
|
pushUniqueTTSItem(items, { id: opts.currentModel, name: opts.currentModel, source: 'configured-model', kind: 'model' });
|
||||||
|
}
|
||||||
|
if (opts.currentVoice) {
|
||||||
|
pushUniqueTTSItem(items, { id: opts.currentVoice, name: opts.currentVoice, source: 'configured-voice', kind: 'voice' });
|
||||||
|
}
|
||||||
|
OPENAI_TTS_VOICES.forEach(function(voice) {
|
||||||
|
pushUniqueTTSItem(items, { id: voice, name: voice, source: 'openai-compatible', kind: 'voice' });
|
||||||
|
});
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ELEVENLABS_DEFAULT_VOICES,
|
ELEVENLABS_DEFAULT_VOICES,
|
||||||
GOOGLE_TTS_VOICES,
|
GOOGLE_TTS_VOICES,
|
||||||
OPENAI_TTS_VOICES,
|
OPENAI_TTS_VOICES,
|
||||||
getTTSEnvProvider,
|
getTTSEnvProvider,
|
||||||
|
getLiteLLMTTSDiscoveryItems,
|
||||||
getLiteLLMHeaders,
|
getLiteLLMHeaders,
|
||||||
getLiteLLMTTSModels,
|
getLiteLLMTTSModels,
|
||||||
getTTSProvider,
|
getTTSProvider,
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,35 @@ test('LiteLLM TTS model extraction filters gateway model objects', () => {
|
||||||
]), ['local-chatterbox-turbo', 'custom-provider-model']);
|
]), ['local-chatterbox-turbo', 'custom-provider-model']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('LiteLLM TTS discovery includes metadata models and configured fallbacks', () => {
|
||||||
|
const ttsProvider = require('../src/utils/ttsProvider');
|
||||||
|
const items = ttsProvider.getLiteLLMTTSDiscoveryItems([
|
||||||
|
{ model_name: 'local-kokoro-tts', model_info: { mode: 'audio_speech' } },
|
||||||
|
{ model_name: 'not-tts-by-name-only' },
|
||||||
|
{ model_name: 'local-parakeet-v3', model_info: { mode: 'audio_transcription' } }
|
||||||
|
], {
|
||||||
|
currentModel: 'local-kokoro-tts',
|
||||||
|
currentVoice: 'sherpa/kokoro:am_adam'
|
||||||
|
});
|
||||||
|
assert.deepEqual(items.slice(0, 3), [
|
||||||
|
{ id: 'local-kokoro-tts', name: 'local-kokoro-tts', source: 'gateway-api', kind: 'model' },
|
||||||
|
{ id: 'sherpa/kokoro:am_adam', name: 'sherpa/kokoro:am_adam', source: 'configured-voice', kind: 'voice' },
|
||||||
|
{ id: 'alloy', name: 'alloy', source: 'openai-compatible', kind: 'voice' }
|
||||||
|
]);
|
||||||
|
assert.equal(items.some(function(item) { return item.id === 'not-tts-by-name-only'; }), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('LiteLLM TTS discovery still shows configured model if metadata lookup fails', () => {
|
||||||
|
const ttsProvider = require('../src/utils/ttsProvider');
|
||||||
|
assert.deepEqual(ttsProvider.getLiteLLMTTSDiscoveryItems([], {
|
||||||
|
currentModel: 'local-kokoro-tts',
|
||||||
|
currentVoice: 'sherpa/kokoro:am_adam'
|
||||||
|
}).slice(0, 2), [
|
||||||
|
{ id: 'local-kokoro-tts', name: 'local-kokoro-tts', source: 'configured-model', kind: 'model' },
|
||||||
|
{ id: 'sherpa/kokoro:am_adam', name: 'sherpa/kokoro:am_adam', source: 'configured-voice', kind: 'voice' }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test('LiteLLM headers include optional content type without exposing key value', () => {
|
test('LiteLLM headers include optional content type without exposing key value', () => {
|
||||||
const ttsProvider = require('../src/utils/ttsProvider');
|
const ttsProvider = require('../src/utils/ttsProvider');
|
||||||
withEnv({ LITELLM_API_KEY: 'secret-token' }, () => {
|
withEnv({ LITELLM_API_KEY: 'secret-token' }, () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue