Fix model sections: separate Provider Models and Public-Facing Models

- Provider Models section: restored OpenCode integration with dropdown
- Public-Facing Models section: completely separate manual entry
- Provider Chain section: fallback chain with up/down buttons (unchanged)
- Added separate arrays: providerModels and publicModels
- Added reorder support for both provider and public models
- Updated server to handle providerModel type and reorder by type
This commit is contained in:
southseact-3d
2026-02-18 14:38:19 +00:00
parent 7e5dc8b62d
commit cc17079988
3 changed files with 596 additions and 81 deletions

View File

@@ -1525,7 +1525,8 @@ const adminSessions = new Map();
let adminModels = [];
let adminModelIndex = new Map();
// New unified model chain structure
let publicModels = []; // Models displayed in builder dropdown [{id, name, label, icon, tier, supportsMedia, multiplier}]
let providerModels = []; // Provider models from OpenCode [{id, name, label, icon, tier, supportsMedia, multiplier}]
let publicModels = []; // Public-facing models (completely separate) [{id, name, label, icon, tier, supportsMedia, multiplier}]
let providerChain = []; // Unified fallback chain [{provider, model, primary}]
let openrouterSettings = {
primaryModel: OPENROUTER_MODEL_PRIMARY,
@@ -5762,14 +5763,15 @@ async function loadAdminModelStore() {
// Check if using new unified structure or old structure
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
// New unified structure
providerModels = Array.isArray(parsed.providerModels) ? parsed.providerModels : [];
publicModels = Array.isArray(parsed.publicModels) ? parsed.publicModels : [];
providerChain = Array.isArray(parsed.providerChain) ? parsed.providerChain : [];
adminModels = Array.isArray(parsed.adminModels) ? parsed.adminModels : [];
} else if (Array.isArray(parsed)) {
// Old array structure - migrate to new
adminModels = parsed;
// Create public models from admin models
publicModels = parsed.map((m) => ({
// Create provider models from admin models (these are from OpenCode)
providerModels = parsed.map((m) => ({
id: m.id || randomUUID(),
name: m.name,
label: m.label || m.name,
@@ -5778,6 +5780,8 @@ async function loadAdminModelStore() {
supportsMedia: m.supportsMedia ?? false,
multiplier: getTierMultiplier(m.tier),
})).filter((m) => !!m.name);
// Public models start empty (user can add these manually later)
publicModels = [];
// Create unified provider chain from all unique providers
const allProviders = new Map();
parsed.forEach((m) => {
@@ -5797,11 +5801,23 @@ async function loadAdminModelStore() {
});
providerChain = Array.from(allProviders.values());
} else {
providerModels = [];
publicModels = [];
providerChain = [];
adminModels = [];
}
// Ensure all provider models have required fields
providerModels = providerModels.map((m) => ({
id: m.id || randomUUID(),
name: m.name,
label: m.label || m.name,
icon: m.icon || '',
tier: normalizeTier(m.tier),
supportsMedia: m.supportsMedia ?? false,
multiplier: m.multiplier || getTierMultiplier(m.tier),
})).filter((m) => !!m.name);
// Ensure all public models have required fields
publicModels = publicModels.map((m) => ({
id: m.id || randomUUID(),
@@ -5822,12 +5838,14 @@ async function loadAdminModelStore() {
refreshAdminModelIndex();
log('Loaded admin model store', {
providerModels: providerModels.length,
publicModels: publicModels.length,
providerChain: providerChain.length,
legacyAdminModels: adminModels.length
});
} catch (error) {
log('Failed to load admin models, starting empty', { error: String(error) });
providerModels = [];
publicModels = [];
providerChain = [];
adminModels = [];
@@ -5840,10 +5858,11 @@ async function persistAdminModels() {
await ensureAssetsDir();
// Save new unified structure
const payload = JSON.stringify({
providerModels,
publicModels,
providerChain,
adminModels, // Keep legacy for backwards compatibility
version: 2, // Schema version
version: 3, // Schema version - added providerModels
}, null, 2);
await safeWriteFile(ADMIN_MODELS_FILE, payload);
refreshAdminModelIndex();
@@ -15857,7 +15876,8 @@ async function handleAdminModelsList(req, res) {
if (!session) return;
// Return new unified structure
sendJson(res, 200, {
publicModels: publicModels,
providerModels,
publicModels,
providerChain,
// Legacy support
models: getConfiguredModels('opencode'),
@@ -15870,9 +15890,45 @@ async function handleAdminModelUpsert(req, res) {
try {
const body = await parseJsonBody(req);
// Check if this is a public model or provider chain update
if (body.type === 'publicModel') {
// Handle public model update
// Check if this is a provider model, public model, or provider chain update
if (body.type === 'providerModel') {
// Handle provider model update (from OpenCode)
const modelName = (body.name || body.model || '').trim();
const label = (body.label || body.displayName || modelName).trim();
const tier = normalizeTier(body.tier);
if (!modelName) return sendJson(res, 400, { error: 'Model name is required' });
const id = body.id || randomUUID();
const existingIndex = providerModels.findIndex((m) => m.id === id);
let icon = providerModels[existingIndex]?.icon || '';
if (typeof body.icon === 'string' && body.icon.trim()) {
icon = await normalizeIconPath(body.icon);
if (!icon) return sendJson(res, 400, { error: 'Icon must be stored in /assets' });
}
const supportsMedia = typeof body.supportsMedia === 'boolean' ? body.supportsMedia : false;
const payload = {
id,
name: modelName,
label: label || modelName,
icon,
tier,
supportsMedia,
multiplier: getTierMultiplier(tier),
};
if (existingIndex >= 0) providerModels[existingIndex] = { ...providerModels[existingIndex], ...payload };
else providerModels.push(payload);
await persistAdminModels();
sendJson(res, 200, {
providerModel: payload,
providerModels,
publicModels,
providerChain,
models: getConfiguredModels('opencode'),
});
} else if (body.type === 'publicModel') {
// Handle public model update (completely separate from OpenCode)
const modelName = (body.name || body.model || '').trim();
const label = (body.label || body.displayName || modelName).trim();
const tier = normalizeTier(body.tier);
@@ -15902,7 +15958,8 @@ async function handleAdminModelUpsert(req, res) {
await persistAdminModels();
sendJson(res, 200, {
publicModel: payload,
publicModels: publicModels.sort((a, b) => (a.label || '').localeCompare(b.label || '')),
providerModels,
publicModels,
providerChain,
models: getConfiguredModels('opencode'),
});
@@ -15973,28 +16030,52 @@ async function handleAdminModelsReorder(req, res) {
return sendJson(res, 400, { error: 'models array is required' });
}
// Validate that all provided IDs exist
const currentIds = new Set(publicModels.map(m => m.id));
const newIds = body.models.map(m => m.id);
const allExist = newIds.every(id => currentIds.has(id));
const reorderType = body.type || 'publicModels';
if (!allExist) {
return sendJson(res, 400, { error: 'Invalid model IDs in reorder request' });
if (reorderType === 'providerModels') {
// Validate that all provided IDs exist in providerModels
const currentIds = new Set(providerModels.map(m => m.id));
const newIds = body.models.map(m => m.id);
const allExist = newIds.every(id => currentIds.has(id));
if (!allExist) {
return sendJson(res, 400, { error: 'Invalid model IDs in reorder request' });
}
// Reorder providerModels based on the provided order
const reordered = [];
body.models.forEach(m => {
const model = providerModels.find(pm => pm.id === m.id);
if (model) reordered.push(model);
});
providerModels = reordered;
} else {
// Validate that all provided IDs exist in publicModels
const currentIds = new Set(publicModels.map(m => m.id));
const newIds = body.models.map(m => m.id);
const allExist = newIds.every(id => currentIds.has(id));
if (!allExist) {
return sendJson(res, 400, { error: 'Invalid model IDs in reorder request' });
}
// Reorder publicModels based on the provided order
const reordered = [];
body.models.forEach(m => {
const model = publicModels.find(pm => pm.id === m.id);
if (model) reordered.push(model);
});
publicModels = reordered;
}
// Reorder publicModels based on the provided order
const reordered = [];
body.models.forEach(m => {
const model = publicModels.find(pm => pm.id === m.id);
if (model) reordered.push(model);
});
publicModels = reordered;
await persistAdminModels();
sendJson(res, 200, {
ok: true,
publicModels: publicModels,
providerModels,
publicModels,
providerChain,
models: getConfiguredModels('opencode'),
});
@@ -16006,13 +16087,26 @@ async function handleAdminModelsReorder(req, res) {
async function handleAdminModelDelete(req, res, id) {
const session = requireAdminAuth(req, res);
if (!session) return;
const before = publicModels.length;
// Try to delete from providerModels first
const beforeProvider = providerModels.length;
providerModels = providerModels.filter((m) => m.id !== id);
const deletedFromProvider = providerModels.length < beforeProvider;
// Try to delete from publicModels
const beforePublic = publicModels.length;
publicModels = publicModels.filter((m) => m.id !== id);
if (publicModels.length === before) return sendJson(res, 404, { error: 'Model not found' });
const deletedFromPublic = publicModels.length < beforePublic;
if (!deletedFromProvider && !deletedFromPublic) {
return sendJson(res, 404, { error: 'Model not found' });
}
await persistAdminModels();
sendJson(res, 200, {
ok: true,
publicModels: publicModels.sort((a, b) => (a.label || '').localeCompare(b.label || '')),
ok: true,
providerModels,
publicModels,
providerChain,
models: getConfiguredModels('opencode'),
});