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:
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 || {};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user