diff --git a/static/js/dashboard.js b/static/js/dashboard.js
index 312d9177..aa88c90f 100644
--- a/static/js/dashboard.js
+++ b/static/js/dashboard.js
@@ -284,8 +284,31 @@ class Dashboard {
Model Registry
Manage model availability and custom pricing
-
diff --git a/static/js/pages/models.js b/static/js/pages/models.js
index 726d963f..2790d6ae 100644
--- a/static/js/pages/models.js
+++ b/static/js/pages/models.js
@@ -30,14 +30,59 @@ class ModelsPage {
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
- this.models.sort((a, b) => {
+ filteredModels.sort((a, b) => {
if (a.provider !== b.provider) return a.provider.localeCompare(b.provider);
return a.name.localeCompare(b.name);
});
- tableBody.innerHTML = this.models.map(model => {
+ tableBody.innerHTML = filteredModels.map(model => {
const statusClass = model.enabled ? 'success' : 'secondary';
const statusIcon = model.enabled ? 'check-circle' : 'ban';
@@ -150,27 +195,18 @@ class ModelsPage {
}
setupEventListeners() {
- const searchInput = document.getElementById('model-search');
- if (searchInput) {
- searchInput.oninput = (e) => this.filterModels(e.target.value);
- }
- }
+ const attachFilter = (id) => {
+ const el = document.getElementById(id);
+ if (el) {
+ el.addEventListener('input', () => this.renderModelsTable());
+ el.addEventListener('change', () => this.renderModelsTable());
+ }
+ };
- filterModels(query) {
- if (!query) {
- this.renderModelsTable();
- return;
- }
-
- const q = query.toLowerCase();
- const originalModels = this.models;
- this.models = this.models.filter(m =>
- m.id.toLowerCase().includes(q) ||
- m.name.toLowerCase().includes(q) ||
- m.provider.toLowerCase().includes(q)
- );
- this.renderModelsTable();
- this.models = originalModels;
+ attachFilter('model-search');
+ attachFilter('model-provider-filter');
+ attachFilter('model-modality-filter');
+ attachFilter('model-capability-filter');
}
}