feat(dashboard): add provider, modality, and capability filters to Model Registry
Some checks failed
CI / Check (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Formatting (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Release Build (push) Has been cancelled

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:
2026-03-07 01:28:39 +00:00
parent 0d32d953d2
commit e1bc3b35eb
2 changed files with 83 additions and 24 deletions

View File

@@ -284,8 +284,31 @@ class Dashboard {
<h3 class="card-title">Model Registry</h3>
<p class="card-subtitle">Manage model availability and custom pricing</p>
</div>
<div class="card-actions">
<input type="text" id="model-search" placeholder="Search models..." class="form-control" style="margin-bottom: 0; padding: 4px 8px; width: 250px;">
<div class="card-actions" style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
<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 class="table-container">

View File

@@ -31,13 +31,58 @@ class ModelsPage {
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
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');
}
}