Fix model management: empty LiteLLM list, always reload panel, clear-all

- LITELLM_MODELS = [] — no hardcoded models, global selector now only
  shows what admin has actually added via Search API
- getAvailableModelsWithOverrides: for LiteLLM returns only custom list
- Remove toggle safety check — admin can disable any/all models freely
- Admin panel always reloads on tab open (was cached, showing stale data)
- Add 'Clear all models' button for LiteLLM to wipe and start fresh
- Add POST /config/models/clear-all endpoint
This commit is contained in:
Daniel Onyejesi 2026-03-29 19:32:09 -04:00
parent 683afeea0b
commit 58c8f1c549
3 changed files with 41 additions and 31 deletions

View file

@ -579,11 +579,9 @@
// ============================================================
(function() {
var modelsLoaded = false;
document.addEventListener('tabChanged', function(e) {
if (e.detail && e.detail.tab === 'admin') {
if (!modelsLoaded) { loadAdminModels(); modelsLoaded = true; }
loadAdminModels();
}
});
@ -591,6 +589,7 @@
if (e.target.closest('#btn-discover-models')) discoverModels();
if (e.target.closest('#btn-add-custom-model')) addCustomModel();
if (e.target.closest('#btn-save-default-model')) saveDefaultModel();
if (e.target.closest('#btn-clear-all-models')) clearAllModels();
});
// Allow Enter key to trigger search
@ -640,9 +639,11 @@
var container = document.getElementById('admin-builtin-models');
if (container) {
if (data.litellmHint) {
container.innerHTML = '<p style="color:var(--g500);font-size:13px;padding:8px;background:var(--g50);border-radius:6px;">' +
'<i class="fas fa-info-circle"></i> LiteLLM has no built-in model list — your available models depend on your proxy configuration. ' +
'Use <strong>Search API</strong> below to discover what models your LiteLLM proxy exposes, then add them.</p>';
container.innerHTML = '<div style="padding:10px 12px;background:var(--g50);border-radius:6px;font-size:13px;color:var(--g600);">' +
'<p style="margin:0 0 8px;"><i class="fas fa-info-circle" style="color:var(--blue);"></i> <strong>LiteLLM mode:</strong> No built-in models. ' +
'Use <strong>Search API</strong> below to discover models from your proxy, then add them.</p>' +
'<button id="btn-clear-all-models" class="btn-sm" style="background:var(--red-light);color:var(--red);border:none;border-radius:6px;padding:4px 12px;font-size:12px;cursor:pointer;">' +
'<i class="fas fa-trash"></i> Clear all added models</button></div>';
} else if (data.models.length === 0) {
container.innerHTML = '<p style="color:var(--g400);font-size:13px;">No built-in models for this provider.</p>';
} else {
@ -846,6 +847,21 @@
.catch(function() { showToast('Request failed', 'error'); });
}
function clearAllModels() {
if (!confirm('Remove all added models? You will need to re-add them via Search API.')) return;
Promise.all([
fetch('/api/admin/config/models/clear-all', { method: 'POST', headers: getAuthHeaders() })
.then(function(r) { return r.json(); })
]).then(function(results) {
if (results[0].success) {
showToast('All models cleared', 'info');
loadAdminModels();
} else {
showToast(results[0].error || 'Failed', 'error');
}
}).catch(function() { showToast('Request failed', 'error'); });
}
function saveDefaultModel() {
var sel = document.getElementById('admin-default-model');
if (!sel || !sel.value) { showToast('Select a model', 'error'); return; }

View file

@ -248,17 +248,6 @@ router.put('/config/models/toggle', async function(req, res) {
disabled = disabled.filter(function(id) { return id !== modelId; });
} else {
if (!disabled.includes(modelId)) disabled.push(modelId);
// Safety: prevent disabling all models
var { getAvailableModels } = require('../utils/models');
var allModels = getAvailableModels();
var customRaw = await db.getSetting('models.custom') || '[]';
var custom;
try { custom = JSON.parse(customRaw); } catch(e) { custom = []; }
var enabledCount = allModels.filter(function(m) { return !disabled.includes(m.id); }).length + custom.length;
if (enabledCount === 0) {
return res.status(400).json({ error: 'Cannot disable all models. At least one must remain enabled.' });
}
}
await db.setSetting('models.disabled', JSON.stringify(disabled));
@ -328,6 +317,17 @@ router.delete('/config/models/custom/:modelId(*)', async function(req, res) {
} catch (e) { res.status(500).json({ error: e.message }); }
});
// ── POST clear all custom/discovered models ───────────────────────────────
router.post('/config/models/clear-all', async function(req, res) {
try {
await db.setSetting('models.custom', '[]');
await db.setSetting('models.disabled', '[]');
await db.setSetting('models.default', '');
logger.audit(req.user.id, 'admin_models_clear_all', 'Cleared all custom models and disabled list', req, { category: 'admin' });
res.json({ success: true });
} catch (e) { res.status(500).json({ error: e.message }); }
});
// ── GET discover models from provider API ─────────────────────────────────
router.get('/config/models/discover', async function(req, res) {
try {

View file

@ -135,17 +135,9 @@ var VERTEX_MODELS = [
vertexId: 'meta/llama-3.1-405b-instruct-maas' },
];
// ============================================================
// LITELLM MODELS (default set — admin can discover more via API)
// ============================================================
var LITELLM_MODELS = [
{ id: 'gpt-4o', name: 'GPT-4o', cost: '~$0.01', tag: 'SMART', category: 'premium' },
{ id: 'gpt-4o-mini', name: 'GPT-4o Mini', cost: '~$0.003', tag: 'FAST', category: 'smart' },
{ id: 'vendor-model-sonnet-4-6-20250514', name: 'vendor model Sonnet 4.6', cost: '~$0.015', tag: 'PREMIUM', category: 'premium' },
{ id: 'vendor-model-haiku-4-5-20251001', name: 'vendor model Haiku 4.5', cost: '~$0.003', tag: 'FAST', category: 'smart' },
{ id: 'gemini/gemini-2.5-flash', name: 'Gemini 2.5 Flash', cost: '~$0.001', tag: 'VALUE', category: 'fast' },
{ id: 'gemini/gemini-2.5-pro', name: 'Gemini 2.5 Pro', cost: '~$0.01', tag: 'SMART', category: 'premium' },
];
// LiteLLM has NO built-in models — everything is discovered from the proxy via admin panel.
// This array is intentionally empty. Do not add models here.
var LITELLM_MODELS = [];
function getAvailableModels() {
switch (activeProvider) {
@ -167,7 +159,7 @@ function getDefaultModel() {
case 'bedrock': return 'anthropic/vendor-model-sonnet-4-6';
case 'azure': return process.env.AZURE_DEPLOYMENT_NAME || 'gpt-4o-mini';
case 'vertex': return 'gemini-2.5-flash';
case 'litellm': return 'gpt-4o-mini';
case 'litellm': return ''; // No default — set via admin panel or LITELLM_DEFAULT_MODEL env
case 'openrouter':
default: return 'google/gemini-2.5-flash';
}
@ -178,7 +170,7 @@ function getFallbackModel() {
case 'bedrock': return 'anthropic/vendor-model-haiku-4-5';
case 'azure': return process.env.AZURE_DEPLOYMENT_NAME || 'gpt-4o-mini';
case 'vertex': return 'gemini-2.0-flash';
case 'litellm': return 'gpt-4o';
case 'litellm': return ''; // No fallback — use whatever the user has configured
case 'openrouter':
default: return 'deepseek/deepseek-chat-v3-0324';
}
@ -217,8 +209,10 @@ async function getAvailableModelsWithOverrides(db) {
try { disabled = JSON.parse(disabledRaw); } catch(e) { disabled = []; }
try { custom = JSON.parse(customRaw); } catch(e) { custom = []; }
// For LiteLLM: no built-ins exist — the model list IS the custom/discovered list
if (activeProvider === 'litellm') return custom;
var result = baseModels.filter(function(m) { return !disabled.includes(m.id); });
// Add custom models
custom.forEach(function(m) {
if (!result.find(function(r) { return r.id === m.id; })) result.push(m);
});