diff --git a/routstr/core/admin.py b/routstr/core/admin.py index 7961797..0b1245e 100644 --- a/routstr/core/admin.py +++ b/routstr/core/admin.py @@ -988,6 +988,10 @@ DASHBOARD_MODELS_JS: str = """ if (event.target == createModal) { closeCreateModel(); } + const batchModal = document.getElementById('model-batch-modal'); + if (event.target == batchModal) { + closeBatchModal(); + } } function openCreateModel() { @@ -1089,6 +1093,71 @@ DASHBOARD_MODELS_JS: str = """ btn.textContent = '➕ Create'; } } + + function openBatchModal() { + const modal = document.getElementById('model-batch-modal'); + if (!modal) { alert('Batch modal not found'); return; } + const err = document.getElementById('batch-error'); + if (err) { err.style.display = 'none'; err.textContent = ''; } + const textarea = document.getElementById('batch-json'); + if (textarea && !textarea.value.trim()) { + const sample = { + models: [ + { + id: 'provider/model-id', + name: 'Model Name', + description: 'Description', + created: Math.floor(Date.now()/1000), + context_length: 0, + architecture: { modality: 'text', input_modalities: ['text'], output_modalities: ['text'], tokenizer: '', instruct_type: null }, + pricing: { prompt: 0.0, completion: 0.0, request: 0.0, image: 0.0, web_search: 0.0, internal_reasoning: 0.0 }, + per_request_limits: null, + top_provider: null + } + ] + }; + textarea.value = JSON.stringify(sample, null, 2); + } + modal.style.display = 'block'; + } + + function closeBatchModal() { + const modal = document.getElementById('model-batch-modal'); + if (modal) modal.style.display = 'none'; + } + + async function performBatchAdd() { + const textarea = document.getElementById('batch-json'); + const err = document.getElementById('batch-error'); + const btn = document.getElementById('batch-submit-btn'); + if (err) { err.style.display = 'none'; err.textContent = ''; } + if (btn) { btn.disabled = true; btn.textContent = 'Adding…'; } + try { + if (!textarea) throw new Error('Input not found'); + const data = JSON.parse(textarea.value); + if (!data || !Array.isArray(data.models) || data.models.length === 0) { + throw new Error('Payload must include a non-empty "models" array'); + } + const resp = await fetch('/admin/api/models/batch', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'same-origin', + body: JSON.stringify(data) + }); + if (!resp.ok) { + let errText = 'Failed to add models'; + try { const e = await resp.json(); if (e && e.detail) errText = typeof e.detail === 'string' ? e.detail : JSON.stringify(e.detail); } catch(_) {} + throw new Error(errText); + } + closeBatchModal(); + await fetchModels(); + } catch (e) { + if (err) { err.style.display = 'block'; err.textContent = e.message || String(e); } + else { alert(e.message || String(e)); } + } finally { + if (btn) { btn.disabled = false; btn.textContent = '➕ Add Models'; } + } + } """ @@ -1126,6 +1195,7 @@ def models_page() -> str: