Remove freePlanModel and opencodeBackupModel settings

Users now use OpenCode Models (Fallback Chain) for model selection.
- Removed Auto Model for Hobby/Free Plan section from admin panel
- Removed OpenCode Ultimate Backup Model section from admin panel
- Updated server to use opencodeModels for free plan users
- Removed backup model fallback logic (opencodeModels chain handles this)
This commit is contained in:
southseact-3d
2026-02-19 13:25:54 +00:00
parent a831f331cd
commit f5fee2ac4d
4 changed files with 4 additions and 339 deletions

View File

@@ -243,44 +243,6 @@
</div>
</div>
<div class="admin-card" style="margin-top: 16px;">
<header>
<h3>OpenCode Ultimate Backup Model</h3>
<div class="pill">Fallback</div>
</header>
<p style="margin-top:0; color: var(--muted);">Configure the ultimate fallback model that will be used when all providers fail. This is the last-resort backup for reliability.</p>
<form id="opencode-backup-form" class="admin-form">
<label>
OpenCode backup model
<select id="opencode-backup"></select>
</label>
<div class="admin-actions">
<button type="submit" class="primary">Save backup model</button>
</div>
<div class="status-line" id="opencode-backup-status"></div>
</form>
</div>
<div class="admin-card" style="margin-top: 16px;">
<header>
<h3>Auto Model for Hobby/Free Plan</h3>
<div class="pill">Free Plan</div>
</header>
<p style="margin-top:0; color: var(--muted);">Select which model Hobby and Free plan users will automatically use. Paid plan users can select their own models.</p>
<form id="auto-model-form" class="admin-form">
<label>
Model for hobby/free users
<select id="auto-model-select">
<option value="">Auto (use first configured model)</option>
</select>
</label>
<div class="admin-actions">
<button type="submit" class="primary">Save auto model</button>
</div>
<div class="status-line" id="auto-model-status"></div>
</form>
</div>
<div class="admin-grid" style="margin-top: 16px;">
<div class="admin-card">
<header>

View File

@@ -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 {

View File

@@ -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 || {};
}
}