// Models Page Module class ModelsPage { constructor() { this.models = []; this.init(); } async init() { await this.loadModels(); this.setupEventListeners(); } async loadModels() { try { const data = await window.api.get('/models'); this.models = data; this.renderModelsTable(); } catch (error) { console.error('Error loading models:', error); window.authManager.showToast('Failed to load models', 'error'); } } renderModelsTable() { const tableBody = document.querySelector('#models-table tbody'); if (!tableBody) return; if (this.models.length === 0) { tableBody.innerHTML = 'No models found in registry'; return; } const searchInput = document.getElementById('model-search'); const providerFilter = document.getElementById('model-provider-filter'); const modalityFilter = document.getElementById('model-modality-filter'); const capabilityFilter = document.getElementById('model-capability-filter'); const q = searchInput ? searchInput.value.toLowerCase() : ''; const providerVal = providerFilter ? providerFilter.value : ''; const modalityVal = modalityFilter ? modalityFilter.value : ''; const capabilityVal = capabilityFilter ? capabilityFilter.value : ''; // Apply filters non-destructively let filteredModels = this.models.filter(m => { // Text search if (q && !(m.id.toLowerCase().includes(q) || m.name.toLowerCase().includes(q) || m.provider.toLowerCase().includes(q))) { return false; } // Provider filter if (providerVal) { if (providerVal === 'other') { const known = ['openai', 'anthropic', 'google', 'deepseek', 'xai', 'meta', 'cohere', 'mistral']; if (known.includes(m.provider.toLowerCase())) return false; } else if (m.provider.toLowerCase() !== providerVal) { return false; } } // Modality filter if (modalityVal) { const mods = m.modalities && m.modalities.input ? m.modalities.input.map(x => x.toLowerCase()) : []; if (!mods.includes(modalityVal)) return false; } // Capability filter if (capabilityVal === 'tool_call' && !m.tool_call) return false; if (capabilityVal === 'reasoning' && !m.reasoning) return false; return true; }); if (filteredModels.length === 0) { tableBody.innerHTML = 'No models match the selected filters'; return; } // Sort by provider then name filteredModels.sort((a, b) => { if (a.provider !== b.provider) return a.provider.localeCompare(b.provider); return a.name.localeCompare(b.name); }); tableBody.innerHTML = filteredModels.map(model => { const statusClass = model.enabled ? 'success' : 'secondary'; const statusIcon = model.enabled ? 'check-circle' : 'ban'; return ` ${model.id} ${model.name} ${model.provider.toUpperCase()} ${window.api.formatCurrency(model.prompt_cost)} / ${window.api.formatCurrency(model.completion_cost)} ${model.context_limit ? (model.context_limit / 1000) + 'k' : 'Unknown'} ${model.enabled ? 'Active' : 'Disabled'} ${window._userRole === 'admin' ? `
` : ''} `; }).join(''); } configureModel(id) { const model = this.models.find(m => m.id === id); if (!model) return; const modal = document.createElement('div'); modal.className = 'modal active'; modal.innerHTML = ` `; document.body.appendChild(modal); modal.querySelector('#save-model-config').onclick = async () => { const enabled = modal.querySelector('#model-enabled').checked; const promptCost = parseFloat(modal.querySelector('#model-prompt-cost').value); const completionCost = parseFloat(modal.querySelector('#model-completion-cost').value); const cacheReadCost = parseFloat(modal.querySelector('#model-cache-read-cost').value); const cacheWriteCost = parseFloat(modal.querySelector('#model-cache-write-cost').value); const mapping = modal.querySelector('#model-mapping').value; try { await window.api.put(`/models/${id}`, { enabled, prompt_cost: promptCost, completion_cost: completionCost, cache_read_cost: isNaN(cacheReadCost) ? null : cacheReadCost, cache_write_cost: isNaN(cacheWriteCost) ? null : cacheWriteCost, mapping: mapping || null }); window.authManager.showToast(`Model ${model.id} updated`, 'success'); modal.remove(); this.loadModels(); } catch (error) { window.authManager.showToast(error.message, 'error'); } }; } setupEventListeners() { const attachFilter = (id) => { const el = document.getElementById(id); if (el) { el.addEventListener('input', () => this.renderModelsTable()); el.addEventListener('change', () => this.renderModelsTable()); } }; attachFilter('model-search'); attachFilter('model-provider-filter'); attachFilter('model-modality-filter'); attachFilter('model-capability-filter'); } } window.initModels = async () => { window.modelsPage = new ModelsPage(); };