feat(dashboard): add provider, modality, and capability filters to Model Registry
This commit enhances the Model Registry UI by adding dropdown filters for Provider, Modality (Text/Image/Audio), and Capabilities (Tool Calling/Reasoning) alongside the existing text search. The filtering logic has been refactored to be non-destructive and apply instantly on the client side.
This commit is contained in:
@@ -284,8 +284,31 @@ class Dashboard {
|
|||||||
<h3 class="card-title">Model Registry</h3>
|
<h3 class="card-title">Model Registry</h3>
|
||||||
<p class="card-subtitle">Manage model availability and custom pricing</p>
|
<p class="card-subtitle">Manage model availability and custom pricing</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions" style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
|
||||||
<input type="text" id="model-search" placeholder="Search models..." class="form-control" style="margin-bottom: 0; padding: 4px 8px; width: 250px;">
|
<select id="model-provider-filter" class="form-control" style="margin-bottom: 0; padding: 4px 8px; width: auto;">
|
||||||
|
<option value="">All Providers</option>
|
||||||
|
<option value="openai">OpenAI</option>
|
||||||
|
<option value="anthropic">Anthropic / Gemini</option>
|
||||||
|
<option value="google">Google</option>
|
||||||
|
<option value="deepseek">DeepSeek</option>
|
||||||
|
<option value="xai">xAI</option>
|
||||||
|
<option value="meta">Meta</option>
|
||||||
|
<option value="cohere">Cohere</option>
|
||||||
|
<option value="mistral">Mistral</option>
|
||||||
|
<option value="other">Other</option>
|
||||||
|
</select>
|
||||||
|
<select id="model-modality-filter" class="form-control" style="margin-bottom: 0; padding: 4px 8px; width: auto;">
|
||||||
|
<option value="">All Modalities</option>
|
||||||
|
<option value="text">Text</option>
|
||||||
|
<option value="image">Vision/Image</option>
|
||||||
|
<option value="audio">Audio</option>
|
||||||
|
</select>
|
||||||
|
<select id="model-capability-filter" class="form-control" style="margin-bottom: 0; padding: 4px 8px; width: auto;">
|
||||||
|
<option value="">All Capabilities</option>
|
||||||
|
<option value="tool_call">Tool Calling</option>
|
||||||
|
<option value="reasoning">Reasoning</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" id="model-search" placeholder="Search models..." class="form-control" style="margin-bottom: 0; padding: 4px 8px; width: 200px;">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
|
|||||||
@@ -31,13 +31,58 @@ class ModelsPage {
|
|||||||
return;
|
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 = '<tr><td colspan="7" class="text-center">No models match the selected filters</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Sort by provider then name
|
// 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);
|
if (a.provider !== b.provider) return a.provider.localeCompare(b.provider);
|
||||||
return a.name.localeCompare(b.name);
|
return a.name.localeCompare(b.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
tableBody.innerHTML = this.models.map(model => {
|
tableBody.innerHTML = filteredModels.map(model => {
|
||||||
const statusClass = model.enabled ? 'success' : 'secondary';
|
const statusClass = model.enabled ? 'success' : 'secondary';
|
||||||
const statusIcon = model.enabled ? 'check-circle' : 'ban';
|
const statusIcon = model.enabled ? 'check-circle' : 'ban';
|
||||||
|
|
||||||
@@ -150,27 +195,18 @@ class ModelsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
const searchInput = document.getElementById('model-search');
|
const attachFilter = (id) => {
|
||||||
if (searchInput) {
|
const el = document.getElementById(id);
|
||||||
searchInput.oninput = (e) => this.filterModels(e.target.value);
|
if (el) {
|
||||||
}
|
el.addEventListener('input', () => this.renderModelsTable());
|
||||||
}
|
el.addEventListener('change', () => this.renderModelsTable());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
filterModels(query) {
|
attachFilter('model-search');
|
||||||
if (!query) {
|
attachFilter('model-provider-filter');
|
||||||
this.renderModelsTable();
|
attachFilter('model-modality-filter');
|
||||||
return;
|
attachFilter('model-capability-filter');
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user