Add up/down reorder buttons and order numbers to public models
- Add order number badge (#1, #2, #3) to each public model - Add up/down arrow buttons to reorder models in the list - Add persistPublicModelsOrder function to save reordered list - Add server-side /api/admin/models/reorder endpoint - Remove automatic alphabetical sorting to preserve custom order - First model (#1) gets green background highlighting
This commit is contained in:
@@ -605,7 +605,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
modelsToRender.forEach((m) => {
|
modelsToRender.forEach((m, idx) => {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'provider-row slim';
|
row.className = 'provider-row slim';
|
||||||
|
|
||||||
@@ -615,6 +615,14 @@
|
|||||||
const info = document.createElement('div');
|
const info = document.createElement('div');
|
||||||
info.className = 'model-chip';
|
info.className = 'model-chip';
|
||||||
|
|
||||||
|
// Order number badge
|
||||||
|
const orderBadge = document.createElement('span');
|
||||||
|
orderBadge.className = 'pill';
|
||||||
|
orderBadge.textContent = `#${idx + 1}`;
|
||||||
|
orderBadge.style.background = idx === 0 ? 'var(--shopify-green)' : 'var(--primary)';
|
||||||
|
orderBadge.style.fontWeight = '700';
|
||||||
|
info.appendChild(orderBadge);
|
||||||
|
|
||||||
if (m.icon) {
|
if (m.icon) {
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = m.icon;
|
img.src = m.icon;
|
||||||
@@ -658,6 +666,36 @@
|
|||||||
const headerActions = document.createElement('div');
|
const headerActions = document.createElement('div');
|
||||||
headerActions.className = 'provider-row-actions';
|
headerActions.className = 'provider-row-actions';
|
||||||
|
|
||||||
|
// Up button
|
||||||
|
const upBtn = document.createElement('button');
|
||||||
|
upBtn.className = 'ghost';
|
||||||
|
upBtn.textContent = '↑';
|
||||||
|
upBtn.title = 'Move up';
|
||||||
|
upBtn.disabled = idx === 0;
|
||||||
|
upBtn.addEventListener('click', async () => {
|
||||||
|
if (idx === 0) return;
|
||||||
|
const next = [...modelsToRender];
|
||||||
|
const [item] = next.splice(idx, 1);
|
||||||
|
next.splice(idx - 1, 0, item);
|
||||||
|
await persistPublicModelsOrder(next);
|
||||||
|
});
|
||||||
|
headerActions.appendChild(upBtn);
|
||||||
|
|
||||||
|
// Down button
|
||||||
|
const downBtn = document.createElement('button');
|
||||||
|
downBtn.className = 'ghost';
|
||||||
|
downBtn.textContent = '↓';
|
||||||
|
downBtn.title = 'Move down';
|
||||||
|
downBtn.disabled = idx === modelsToRender.length - 1;
|
||||||
|
downBtn.addEventListener('click', async () => {
|
||||||
|
if (idx === modelsToRender.length - 1) return;
|
||||||
|
const next = [...modelsToRender];
|
||||||
|
const [item] = next.splice(idx, 1);
|
||||||
|
next.splice(idx + 1, 0, item);
|
||||||
|
await persistPublicModelsOrder(next);
|
||||||
|
});
|
||||||
|
headerActions.appendChild(downBtn);
|
||||||
|
|
||||||
const delBtn = document.createElement('button');
|
const delBtn = document.createElement('button');
|
||||||
delBtn.className = 'ghost';
|
delBtn.className = 'ghost';
|
||||||
delBtn.textContent = 'Delete';
|
delBtn.textContent = 'Delete';
|
||||||
@@ -826,6 +864,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function persistPublicModelsOrder(orderedModels) {
|
||||||
|
setStatus('Saving order...');
|
||||||
|
try {
|
||||||
|
// Use the reorder endpoint to save the new order
|
||||||
|
const res = await api('/api/admin/models/reorder', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ models: orderedModels }),
|
||||||
|
});
|
||||||
|
// Update local state with new order from server
|
||||||
|
state.publicModels = res.publicModels || orderedModels;
|
||||||
|
renderConfigured();
|
||||||
|
setStatus('Order saved');
|
||||||
|
setTimeout(() => setStatus(''), 1500);
|
||||||
|
} catch (err) {
|
||||||
|
setStatus(err.message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Render the unified provider chain with up/down controls
|
// Render the unified provider chain with up/down controls
|
||||||
function renderProviderChain() {
|
function renderProviderChain() {
|
||||||
if (!el.providerChainList) return;
|
if (!el.providerChainList) return;
|
||||||
|
|||||||
@@ -15857,7 +15857,7 @@ async function handleAdminModelsList(req, res) {
|
|||||||
if (!session) return;
|
if (!session) return;
|
||||||
// Return new unified structure
|
// Return new unified structure
|
||||||
sendJson(res, 200, {
|
sendJson(res, 200, {
|
||||||
publicModels: publicModels.sort((a, b) => (a.label || '').localeCompare(b.label || '')),
|
publicModels: publicModels,
|
||||||
providerChain,
|
providerChain,
|
||||||
// Legacy support
|
// Legacy support
|
||||||
models: getConfiguredModels('opencode'),
|
models: getConfiguredModels('opencode'),
|
||||||
@@ -15954,7 +15954,7 @@ async function handleAdminModelUpsert(req, res) {
|
|||||||
await persistAdminModels();
|
await persistAdminModels();
|
||||||
sendJson(res, 200, {
|
sendJson(res, 200, {
|
||||||
model: payload,
|
model: payload,
|
||||||
publicModels: publicModels.sort((a, b) => (a.label || '').localeCompare(b.label || '')),
|
publicModels: publicModels,
|
||||||
providerChain,
|
providerChain,
|
||||||
models: getConfiguredModels('opencode'),
|
models: getConfiguredModels('opencode'),
|
||||||
});
|
});
|
||||||
@@ -15964,6 +15964,45 @@ async function handleAdminModelUpsert(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleAdminModelsReorder(req, res) {
|
||||||
|
const session = requireAdminAuth(req, res);
|
||||||
|
if (!session) return;
|
||||||
|
try {
|
||||||
|
const body = await parseJsonBody(req);
|
||||||
|
if (!Array.isArray(body.models)) {
|
||||||
|
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));
|
||||||
|
|
||||||
|
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;
|
||||||
|
await persistAdminModels();
|
||||||
|
|
||||||
|
sendJson(res, 200, {
|
||||||
|
ok: true,
|
||||||
|
publicModels: publicModels,
|
||||||
|
providerChain,
|
||||||
|
models: getConfiguredModels('opencode'),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
sendJson(res, 400, { error: error.message || 'Unable to reorder models' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleAdminModelDelete(req, res, id) {
|
async function handleAdminModelDelete(req, res, id) {
|
||||||
const session = requireAdminAuth(req, res);
|
const session = requireAdminAuth(req, res);
|
||||||
if (!session) return;
|
if (!session) return;
|
||||||
@@ -18878,6 +18917,7 @@ async function routeInternal(req, res, url, pathname) {
|
|||||||
if (req.method === 'GET' && pathname === '/api/admin/icons') return handleAdminListIcons(req, res);
|
if (req.method === 'GET' && pathname === '/api/admin/icons') return handleAdminListIcons(req, res);
|
||||||
if (req.method === 'GET' && pathname === '/api/admin/models') return handleAdminModelsList(req, res);
|
if (req.method === 'GET' && pathname === '/api/admin/models') return handleAdminModelsList(req, res);
|
||||||
if (req.method === 'POST' && pathname === '/api/admin/models') return handleAdminModelUpsert(req, res);
|
if (req.method === 'POST' && pathname === '/api/admin/models') return handleAdminModelUpsert(req, res);
|
||||||
|
if (req.method === 'POST' && pathname === '/api/admin/models/reorder') return handleAdminModelsReorder(req, res);
|
||||||
if (req.method === 'GET' && pathname === '/api/admin/openrouter-settings') return handleAdminOpenRouterSettingsGet(req, res);
|
if (req.method === 'GET' && pathname === '/api/admin/openrouter-settings') return handleAdminOpenRouterSettingsGet(req, res);
|
||||||
if (req.method === 'POST' && pathname === '/api/admin/openrouter-settings') return handleAdminOpenRouterSettingsPost(req, res);
|
if (req.method === 'POST' && pathname === '/api/admin/openrouter-settings') return handleAdminOpenRouterSettingsPost(req, res);
|
||||||
if (req.method === 'GET' && pathname === '/api/admin/mistral-settings') return handleAdminMistralSettingsGet(req, res);
|
if (req.method === 'GET' && pathname === '/api/admin/mistral-settings') return handleAdminMistralSettingsGet(req, res);
|
||||||
|
|||||||
Reference in New Issue
Block a user