diff --git a/chat/public/admin.html b/chat/public/admin.html index c4ff33c..0a98f97 100644 --- a/chat/public/admin.html +++ b/chat/public/admin.html @@ -243,44 +243,6 @@ -
-
-

OpenCode Ultimate Backup Model

-
Fallback
-
-

Configure the ultimate fallback model that will be used when all providers fail. This is the last-resort backup for reliability.

-
- -
- -
-
-
-
- -
-
-

Auto Model for Hobby/Free Plan

-
Free Plan
-
-

Select which model Hobby and Free plan users will automatically use. Paid plan users can select their own models.

-
- -
- -
-
-
-
-
diff --git a/chat/public/admin.js b/chat/public/admin.js index 9cc872c..deddd8d 100644 --- a/chat/public/admin.js +++ b/chat/public/admin.js @@ -12,10 +12,9 @@ accounts: [], affiliates: [], withdrawals: [], - planSettings: { provider: 'openrouter', freePlanModel: '', planningChain: [] }, + planSettings: { provider: 'openrouter', planningChain: [] }, providerLimits: {}, providerUsage: [], - opencodeBackupModel: '', providerOptions: [], providerModels: {}, tokenRates: {}, @@ -69,12 +68,8 @@ orBackup2: document.getElementById('or-backup2'), orBackup3: document.getElementById('or-backup3'), orStatus: document.getElementById('or-status'), - autoModelForm: document.getElementById('auto-model-form'), - autoModelSelect: document.getElementById('auto-model-select'), - autoModelStatus: document.getElementById('auto-model-status'), planProviderForm: document.getElementById('plan-provider-form'), planProvider: document.getElementById('plan-provider'), - freePlanModel: document.getElementById('free-plan-model'), planProviderStatus: document.getElementById('plan-provider-status'), planPriorityList: document.getElementById('plan-priority-list'), addPlanRow: document.getElementById('add-plan-row'), @@ -120,9 +115,6 @@ // Cancel messages UI cancelAllMessages: document.getElementById('cancel-all-messages'), cancelMessagesStatus: document.getElementById('cancel-messages-status'), - opencodeBackupForm: document.getElementById('opencode-backup-form'), - opencodeBackup: document.getElementById('opencode-backup'), - opencodeBackupStatus: document.getElementById('opencode-backup-status'), externalTestingRun: document.getElementById('external-testing-run'), externalTestingStatus: document.getElementById('external-testing-status'), externalTestingOutput: document.getElementById('external-testing-output'), @@ -131,9 +123,6 @@ ollamaTestStatus: document.getElementById('ollama-test-status'), ollamaTestOutput: document.getElementById('ollama-test-output'), }; - console.log('Element check - opencodeBackupForm:', el.opencodeBackupForm); - console.log('Element check - opencodeBackup:', el.opencodeBackup); - console.log('Element check - opencodeBackupStatus:', el.opencodeBackupStatus); function ensureAvailableDatalist() { if (el.availableModelDatalist) return el.availableModelDatalist; @@ -205,12 +194,6 @@ el.providerLimitStatus.style.color = isError ? 'var(--danger)' : 'inherit'; } - function setAutoModelStatus(msg, isError = false) { - if (!el.autoModelStatus) return; - el.autoModelStatus.textContent = msg || ''; - el.autoModelStatus.style.color = isError ? 'var(--danger)' : 'inherit'; - } - function setPublicModelStatus(msg, isError = false) { if (!el.publicModelStatus) return; el.publicModelStatus.textContent = msg || ''; @@ -229,12 +212,6 @@ el.providerChainStatus.style.color = isError ? 'var(--danger)' : 'inherit'; } - function setOpencodeBackupStatus(msg, isError = false) { - if (!el.opencodeBackupStatus) return; - el.opencodeBackupStatus.textContent = msg || ''; - el.opencodeBackupStatus.style.color = isError ? 'var(--danger)' : 'inherit'; - } - function setExternalTestingStatus(msg, isError = false) { if (!el.externalTestingStatus) return; el.externalTestingStatus.textContent = msg || ''; @@ -1566,11 +1543,6 @@ renderAvailable(); syncAvailableModelDatalist(); - const selectedAutoModel = state.planSettings && typeof state.planSettings.freePlanModel === 'string' - ? state.planSettings.freePlanModel - : (el.autoModelSelect ? el.autoModelSelect.value : ''); - populateAutoModelOptions(selectedAutoModel); - if (el.limitProvider) renderLimitModelOptions(el.limitProvider.value || 'openrouter'); } @@ -1588,11 +1560,6 @@ state.configured = data.models || []; // Legacy support renderOpencodeModels(); renderPublicModels(); - const selectedAutoModel = state.planSettings && typeof state.planSettings.freePlanModel === 'string' - ? state.planSettings.freePlanModel - : (el.autoModelSelect ? el.autoModelSelect.value : ''); - populateAutoModelOptions(selectedAutoModel); - populateFreePlanModelOptions(selectedAutoModel); syncAvailableModelDatalist(); if (el.limitProvider) renderLimitModelOptions(el.limitProvider.value || 'openrouter'); } @@ -1678,7 +1645,6 @@ if (el.limitRpm) el.limitRpm.value = target.requestsPerMinute ?? ''; if (el.limitRph) el.limitRph.value = target.requestsPerHour ?? ''; if (el.limitRpd) el.limitRpd.value = target.requestsPerDay ?? ''; - if (el.limitBackup && state.opencodeBackupModel !== undefined) el.limitBackup.value = state.opencodeBackupModel || ''; } async function loadProviderLimits() { @@ -1695,12 +1661,9 @@ if (!state.providerLimits[p]) state.providerLimits[p] = {}; }); state.providerUsage = data.usage || []; - state.opencodeBackupModel = data.opencodeBackupModel || ''; renderProviderOptions(); populateLimitForm(el.limitProvider ? el.limitProvider.value : 'openrouter', el.limitScope ? el.limitScope.value : 'provider'); renderProviderUsage(); - if (el.limitBackup && state.opencodeBackupModel !== undefined) el.limitBackup.value = state.opencodeBackupModel || ''; - populateOpencodeBackupOptions(state.opencodeBackupModel); // refresh datalist with provider-specific models syncAvailableModelDatalist(); renderPlanPriority(); @@ -1836,187 +1799,22 @@ } async function loadPlanProviderSettings() { - if (!el.planProviderForm && !el.autoModelForm && !el.planPriorityList) return; + if (!el.planProviderForm && !el.planPriorityList) return; try { const data = await api('/api/admin/plan-settings'); state.planSettings = { provider: 'openrouter', - freePlanModel: '', planningChain: [], ...(data || {}), }; if (el.planProvider) el.planProvider.value = state.planSettings.provider || 'openrouter'; - populateAutoModelOptions(state.planSettings.freePlanModel || ''); - populateFreePlanModelOptions(state.planSettings.freePlanModel || ''); renderPlanPriority(); } catch (err) { if (el.planProviderForm) setPlanProviderStatus(err.message, true); - if (el.autoModelForm) setAutoModelStatus(err.message, true); if (el.planPriorityList) setPlanChainStatus(err.message, true); } } - function populateAutoModelOptions(selectedValue) { - if (!el.autoModelSelect) return; - - const normalizeTier = (tier) => { - const normalized = String(tier || 'free').trim().toLowerCase(); - return ['free', 'plus', 'pro'].includes(normalized) ? normalized : 'free'; - }; - - // Use publicModels if available, fallback to configured for legacy support - const configured = state.publicModels.length > 0 ? state.publicModels : (Array.isArray(state.configured) ? state.configured : []); - const configuredByName = new Map(); - configured.forEach((m) => { - const name = (m && (m.name || m.id)) ? String(m.name || m.id).trim() : ''; - if (name) configuredByName.set(name, m); - }); - - const current = typeof selectedValue === 'string' ? selectedValue : el.autoModelSelect.value; - - el.autoModelSelect.innerHTML = ''; - const auto = document.createElement('option'); - auto.value = ''; - auto.textContent = 'Auto (first free model)'; - el.autoModelSelect.appendChild(auto); - - const freeModels = configured - .filter((m) => normalizeTier(m.tier) === 'free') - .map((m) => ({ - name: (m && (m.name || m.id)) ? String(m.name || m.id).trim() : '', - label: (m && (m.label || m.name || m.id)) ? String(m.label || m.name || m.id).trim() : '', - })) - .filter((m) => m.name); - - const freeGroup = document.createElement('optgroup'); - freeGroup.label = 'Free-tier models'; - - const freeNames = new Set(); - freeModels - .sort((a, b) => a.label.localeCompare(b.label)) - .forEach((m) => { - freeNames.add(m.name); - const opt = document.createElement('option'); - opt.value = m.name; - opt.textContent = m.label || m.name; - freeGroup.appendChild(opt); - }); - - const discoveredNames = getAvailableModelNames() - .map((name) => String(name || '').trim()) - .filter(Boolean) - .filter((name, idx, arr) => arr.indexOf(name) === idx) - .filter((name) => !freeNames.has(name)); - - const discoveredGroup = document.createElement('optgroup'); - discoveredGroup.label = 'Other discovered models'; - - discoveredNames - .sort((a, b) => a.localeCompare(b)) - .forEach((name) => { - const configuredModel = configuredByName.get(name); - const tier = configuredModel ? normalizeTier(configuredModel.tier) : null; - const opt = document.createElement('option'); - opt.value = name; - if (!configuredModel) { - opt.textContent = `${name} (unpublished)`; - } else { - opt.textContent = `${name} (${tier.toUpperCase()})`; - if (tier !== 'free') opt.disabled = true; - } - discoveredGroup.appendChild(opt); - }); - - const hasFree = freeGroup.children.length > 0; - const hasDiscovered = discoveredGroup.children.length > 0; - - if (hasFree) { - el.autoModelSelect.appendChild(freeGroup); - } - - if (hasDiscovered) { - el.autoModelSelect.appendChild(discoveredGroup); - } - - if (!hasFree && !hasDiscovered) { - const note = document.createElement('option'); - note.value = '__none__'; - note.textContent = '(No models discovered yet)'; - note.disabled = true; - el.autoModelSelect.appendChild(note); - } - - const currentName = (current || '').trim(); - if (currentName && !Array.from(el.autoModelSelect.options).some((opt) => opt.value === currentName)) { - const orphan = document.createElement('option'); - orphan.value = currentName; - orphan.textContent = `${currentName} (current selection)`; - el.autoModelSelect.appendChild(orphan); - } - - el.autoModelSelect.value = currentName; - } - - function populateFreePlanModelOptions(selectedValue) { - if (!el.freePlanModel) return; - const current = selectedValue || el.freePlanModel.value; - el.freePlanModel.innerHTML = ''; - const auto = document.createElement('option'); - auto.value = ''; - auto.textContent = 'Auto (use default)'; - el.freePlanModel.appendChild(auto); - (state.configured || []).forEach((m) => { - const opt = document.createElement('option'); - opt.value = m.name || m.id || ''; - opt.textContent = m.label || m.name || m.id || ''; - el.freePlanModel.appendChild(opt); - }); - if (current !== undefined && current !== null) { - el.freePlanModel.value = current; - } - } - - function populateOpencodeBackupOptions(selectedValue) { - console.log('populateOpencodeBackupOptions called with:', selectedValue); - if (!el.opencodeBackup) { - console.log('el.opencodeBackup is null, returning early'); - return; - } - console.log('el.opencodeBackup found, populating...'); - const current = selectedValue || el.opencodeBackup.value; - el.opencodeBackup.innerHTML = ''; - - const allModels = new Set(); - (state.available || []).forEach((m) => { - const name = m.name || m.id || m; - if (name) allModels.add(name); - }); - (state.configured || []).forEach((m) => { - if (m.name) allModels.add(m.name); - }); - Object.values(state.providerModels || {}).forEach((arr) => { - (arr || []).forEach((name) => { if (name) allModels.add(name); }); - }); - - console.log('Found models:', Array.from(allModels)); - const sorted = Array.from(allModels).filter(Boolean).sort((a, b) => a.localeCompare(b)); - - const none = document.createElement('option'); - none.value = ''; - none.textContent = 'None (no backup)'; - el.opencodeBackup.appendChild(none); - - sorted.forEach((name) => { - const opt = document.createElement('option'); - opt.value = name; - opt.textContent = name; - el.opencodeBackup.appendChild(opt); - }); - - if (current) el.opencodeBackup.value = current; - console.log('Dropdown populated with', sorted.length + 1, 'options'); - } - function formatDisplayDate(value) { if (!value) return '—'; const date = new Date(value); @@ -2750,47 +2548,6 @@ }); } - if (el.opencodeBackupForm) { - el.opencodeBackupForm.addEventListener('submit', async (e) => { - e.preventDefault(); - const opencodeBackupModel = el.opencodeBackup ? el.opencodeBackup.value.trim() : ''; - - setOpencodeBackupStatus('Saving...'); - try { - const res = await api('/api/admin/provider-limits', { - method: 'POST', - body: JSON.stringify({ provider: 'opencode', scope: 'provider', model: '', tokensPerMinute: '', tokensPerDay: '', requestsPerMinute: '', requestsPerDay: '', opencodeBackupModel }), - }); - // update local state and refresh dropdowns - state.opencodeBackupModel = res.opencodeBackupModel || opencodeBackupModel || ''; - populateOpencodeBackupOptions(state.opencodeBackupModel); - setOpencodeBackupStatus('Saved'); - setTimeout(() => setOpencodeBackupStatus(''), 3000); - } catch (err) { - setOpencodeBackupStatus(err.message, true); - } - }); - } - - if (el.autoModelForm) { - el.autoModelForm.addEventListener('submit', async (e) => { - e.preventDefault(); - const freePlanModel = el.autoModelSelect ? el.autoModelSelect.value.trim() : ''; - - setAutoModelStatus('Saving...'); - try { - await api('/api/admin/plan-settings', { - method: 'POST', - body: JSON.stringify({ freePlanModel }), - }); - setAutoModelStatus('Saved! Free plan users will use this model.'); - setTimeout(() => setAutoModelStatus(''), 3000); - } catch (err) { - setAutoModelStatus(err.message, true); - } - }); - } - if (el.planProviderForm) { el.planProviderForm.addEventListener('submit', async (e) => { e.preventDefault(); @@ -2838,7 +2595,6 @@ requestsPerMinute: Number(el.limitRpm.value || 0), requestsPerHour: Number(el.limitRph.value || 0), requestsPerDay: Number(el.limitRpd.value || 0), - opencodeBackupModel: el.limitBackup ? el.limitBackup.value.trim() : '', }; setProviderLimitStatus('Saving...'); try { diff --git a/chat/public/builder.js b/chat/public/builder.js index 8c9400c..b7c9081 100644 --- a/chat/public/builder.js +++ b/chat/public/builder.js @@ -3650,14 +3650,6 @@ function getBackupModel(currentModel) { const configuredModels = (state.models || []).map(normalize).filter(Boolean); const current = (currentModel || '').trim(); - const preferredBackup = normalize(window.providerLimits?.opencodeBackupModel || ''); - - // If we have a preferred backup model, use it if it's different from current - // Don't require it to be in configured models since OpenCode CLI can access models directly - if (preferredBackup && preferredBackup !== current) { - return preferredBackup; - } - if (!configuredModels.length) return null; // If current model is auto/default or not in list, pick the first configured model @@ -4829,15 +4821,8 @@ window.addEventListener('focus', () => { async function loadProviderLimits() { try { - const data = await api('/api/provider-limits'); - if (data?.opencodeBackupModel) { - window.providerLimits = { - opencodeBackupModel: data.opencodeBackupModel - }; - console.log('[PROVIDER-LIMITS] Loaded backup model:', window.providerLimits.opencodeBackupModel); - } + await api('/api/provider-limits'); } catch (err) { console.warn('[PROVIDER-LIMITS] Failed to load provider limits:', err); - window.providerLimits = window.providerLimits || {}; } } diff --git a/chat/server.js b/chat/server.js index 76d11d8..93539dd 100644 --- a/chat/server.js +++ b/chat/server.js @@ -1542,13 +1542,11 @@ let mistralSettings = { const PLANNING_PROVIDERS = ['openrouter', 'mistral', 'google', 'groq', 'nvidia', 'ollama', 'cohere', 'kilo']; let planSettings = { provider: 'openrouter', // legacy field, retained for backwards compatibility - freePlanModel: '', planningChain: [], // [{ provider, model }] }; let providerLimits = { limits: {}, modelProviders: {}, - opencodeBackupModel: '', }; let pendingProviderPersistTimer = null; let providerUsage = {}; @@ -4927,10 +4925,8 @@ function resolvePlanModel(plan, requestedModel) { const candidateList = getConfiguredModels(); - // For hobby/free plan users, use the admin-configured auto model + // For hobby/free plan users, use the first model from the fallback chain if (normalized === 'hobby') { - const adminDefault = (planSettings.freePlanModel || '').trim(); - if (adminDefault) return adminDefault; const firstModel = candidateList[0]; if (firstModel) return firstModel.name; return resolveFallbackModel(); @@ -5890,9 +5886,6 @@ async function loadPlanSettings() { if (typeof parsed.provider === 'string' && PLANNING_PROVIDERS.includes(normalizeProviderName(parsed.provider))) { planSettings.provider = normalizeProviderName(parsed.provider); } - if (typeof parsed.freePlanModel === 'string') { - planSettings.freePlanModel = parsed.freePlanModel.trim(); - } if (Array.isArray(parsed.planningChain)) { planSettings.planningChain = normalizePlanningChain(parsed.planningChain); } @@ -6178,7 +6171,6 @@ async function loadProviderLimits() { } collectProviderSeeds().forEach((p) => ensureProviderLimitDefaults(p)); - if (!providerLimits.opencodeBackupModel) providerLimits.opencodeBackupModel = ''; } async function persistProviderLimits() { @@ -8764,16 +8756,6 @@ function buildPlanModelChain() { const chain = normalizePlanningChain(planSettings.planningChain); if (chain.length) return chain; - // Check if freePlanModel has a provider prefix (e.g., "groq/compound-mini") - const freePlanModel = (planSettings.freePlanModel || '').trim(); - if (freePlanModel) { - const parsed = parseModelString(freePlanModel); - if (parsed.provider) { - // User specified a provider prefix, use it directly - return [{ provider: parsed.provider, model: parsed.model }]; - } - } - return defaultPlanningChainFromSettings(planSettings.provider); } @@ -10893,16 +10875,6 @@ async function sendToOpencodeWithFallback({ session, model, content, message, cl if (result) return result; } - const backupModel = (providerLimits.opencodeBackupModel || '').trim(); - if (backupModel) { - const backupChain = buildOpencodeAttemptChain(cliName, backupModel); - for (const option of backupChain) { - const result = await tryOption(option, true); - if (result instanceof Error) break; - if (result) return result; - } - } - const MAX_EARLY_TERMINATIONS = 2; const recentEarlyTerminations = attempts.filter( a => a.earlyTermination && @@ -16054,9 +16026,6 @@ async function handleAdminPlanSettingsPost(req, res) { if (Array.isArray(body.planningChain)) { planSettings.planningChain = normalizePlanningChain(body.planningChain); } - if (typeof body.freePlanModel === 'string') { - planSettings.freePlanModel = body.freePlanModel.trim(); - } if (!planSettings.planningChain.length) { planSettings.planningChain = defaultPlanningChainFromSettings(planSettings.provider); } @@ -16142,7 +16111,6 @@ async function handleAdminProviderLimitsGet(req, res) { sendJson(res, 200, { limits: providerLimits.limits, usage: snapshot, - opencodeBackupModel: providerLimits.opencodeBackupModel || '', providers: discovery.providers, providerModels: discovery.providerModels, }); @@ -16168,17 +16136,12 @@ async function handleAdminProviderLimitsPost(req, res) { if (body[field] !== undefined) target[field] = sanitizeLimitNumber(body[field]); }); - if (typeof body.opencodeBackupModel === 'string') { - providerLimits.opencodeBackupModel = body.opencodeBackupModel.trim(); - } - await persistProviderLimits(); const discovery = await discoverProviderModels(); sendJson(res, 200, { ok: true, limits: providerLimits.limits, usage: getProviderUsageSnapshot(discovery.providers), - opencodeBackupModel: providerLimits.opencodeBackupModel || '', providers: discovery.providers, providerModels: discovery.providerModels, }); @@ -19362,7 +19325,6 @@ async function bootstrap() { }); console.log('[CONFIG] Planning Settings:', { provider: planSettings.provider, - freePlanModel: planSettings.freePlanModel || 'not set', planningChainLength: planSettings.planningChain?.length || 0, planningChain: planSettings.planningChain });