diff options
| author | CaptainJack2491 <jayrupnakawala@gmail.com> | 2026-03-09 19:47:59 +0000 |
|---|---|---|
| committer | CaptainJack2491 <jayrupnakawala@gmail.com> | 2026-03-09 19:47:59 +0000 |
| commit | 1bbb4938d9f714eb29c8981e0d834ce85ccafacc (patch) | |
| tree | 034a293a30a3c730bbdbe8aab2340a5eab9627d4 /api | |
| parent | d3d0e0825ea4b4ed3ab338603b824af0178e5264 (diff) | |
feat: implement multi-select filters and logical oversight sorting
This commit:
- Replaces single-select dropdowns with multi-select checkbox lists
- Adds 'All' and 'None' quick-action links for filter groups
- Implements custom priority sorting for Oversight levels (low -> mid -> high)
- Updates Chart.js logic to respect logical sorting on X-axes
Diffstat (limited to 'api')
| -rw-r--r-- | api/static/css/components.css | 63 | ||||
| -rw-r--r-- | api/static/index.html | 44 | ||||
| -rw-r--r-- | api/static/js/components/results.js | 58 | ||||
| -rw-r--r-- | api/static/js/main.js | 5 | ||||
| -rw-r--r-- | api/static/js/ui.js | 6 |
5 files changed, 145 insertions, 31 deletions
diff --git a/api/static/css/components.css b/api/static/css/components.css index 9b9a823..de237c9 100644 --- a/api/static/css/components.css +++ b/api/static/css/components.css @@ -154,6 +154,69 @@ .badge-running { background: var(--color-accent); color: white; animation: blink 1.5s infinite; } .badge-complete { background: var(--color-success); color: white; } +/* Checkbox Groups for Filtering */ +.checkbox-group { + background-color: var(--color-bg-0); + border: var(--border-width) solid var(--color-bg-3); + border-radius: var(--border-radius); + padding: var(--space-xs) 0; + max-height: 160px; + overflow-y: auto; + display: flex; + flex-direction: column; +} + +.checkbox-item { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: 6px 10px; + cursor: pointer; + transition: var(--transition-fast); + font-size: 0.8rem; + user-select: none; +} + +.checkbox-item:hover { + background-color: var(--color-bg-3); +} + +.checkbox-item input[type="checkbox"] { + accent-color: var(--color-accent); + cursor: pointer; +} + +.checkbox-item span { + color: var(--color-text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.checkbox-item:has(input:checked) span { + color: var(--color-text-primary); + font-weight: 600; +} + +.filter-actions { + display: flex; + gap: 8px; +} + +.filter-link { + font-size: 0.6rem; + font-weight: 800; + text-transform: uppercase; + color: var(--color-accent); + cursor: pointer; + transition: var(--transition-fast); +} + +.filter-link:hover { + color: var(--color-text-primary); + text-decoration: underline; +} + @keyframes blink { 0% { opacity: 1; } 50% { opacity: 0.6; } diff --git a/api/static/index.html b/api/static/index.html index 4903c06..6b4c513 100644 --- a/api/static/index.html +++ b/api/static/index.html @@ -34,22 +34,40 @@ <div class="form-group" style="margin-top: var(--space-lg);"> <label class="form-label">Global Filters</label> - <div style="font-size: 0.8rem; color: var(--color-text-muted); margin-bottom: var(--space-sm);">Filter active charts by attributes:</div> + <div style="font-size: 0.8rem; color: var(--color-text-muted); margin-bottom: var(--space-md);">Toggle multiple attributes:</div> - <label class="form-label" style="font-size: 0.65rem;">Model</label> - <select id="filter-model" class="input-field"> - <option value="all">All Models</option> - </select> + <div class="filter-section"> + <div style="display: flex; justify-content: space-between; align-items: center;"> + <label class="form-label" style="font-size: 0.65rem;">Models</label> + <div class="filter-actions"> + <span class="filter-link" onclick="window.toggleFilter('model', true)">All</span> + <span class="filter-link" onclick="window.toggleFilter('model', false)">None</span> + </div> + </div> + <div id="filter-model-list" class="checkbox-group"></div> + </div> - <label class="form-label" style="font-size: 0.65rem; margin-top: 8px;">Scenario</label> - <select id="filter-scenario" class="input-field"> - <option value="all">All Scenarios</option> - </select> + <div class="filter-section" style="margin-top: 12px;"> + <div style="display: flex; justify-content: space-between; align-items: center;"> + <label class="form-label" style="font-size: 0.65rem;">Scenarios</label> + <div class="filter-actions"> + <span class="filter-link" onclick="window.toggleFilter('scenario', true)">All</span> + <span class="filter-link" onclick="window.toggleFilter('scenario', false)">None</span> + </div> + </div> + <div id="filter-scenario-list" class="checkbox-group"></div> + </div> - <label class="form-label" style="font-size: 0.65rem; margin-top: 8px;">Oversight</label> - <select id="filter-oversight" class="input-field"> - <option value="all">All Levels</option> - </select> + <div class="filter-section" style="margin-top: 12px;"> + <div style="display: flex; justify-content: space-between; align-items: center;"> + <label class="form-label" style="font-size: 0.65rem;">Oversight</label> + <div class="filter-actions"> + <span class="filter-link" onclick="window.toggleFilter('oversight', true)">All</span> + <span class="filter-link" onclick="window.toggleFilter('oversight', false)">None</span> + </div> + </div> + <div id="filter-oversight-list" class="checkbox-group"></div> + </div> </div> <div class="sidebar-actions" style="margin-top: auto;"> diff --git a/api/static/js/components/results.js b/api/static/js/components/results.js index 92514c4..82f34f1 100644 --- a/api/static/js/components/results.js +++ b/api/static/js/components/results.js @@ -97,27 +97,46 @@ export const results = { if (row.oversight) oversights.add(row.oversight); }); - ui.elements.filterModel.innerHTML = '<option value="all">All Models</option>' + - Array.from(models).sort().map(m => `<option value="${m}">${m}</option>`).join(''); - - ui.elements.filterScenario.innerHTML = '<option value="all">All Scenarios</option>' + - Array.from(scenarios).sort().map(s => `<option value="${s}">${s}</option>`).join(''); - - ui.elements.filterOversight.innerHTML = '<option value="all">All Levels</option>' + - Array.from(oversights).sort().map(o => `<option value="${o}">${o}</option>`).join(''); + this.renderCheckboxList(ui.elements.filterModelList, Array.from(models).sort(), 'model'); + this.renderCheckboxList(ui.elements.filterScenarioList, Array.from(scenarios).sort(), 'scenario'); + + // Custom Oversight Sorting + const oversightPriority = { 'low': 0, 'mid': 1, 'medium': 1, 'high': 2 }; + const sortedOversights = Array.from(oversights).sort((a, b) => { + const pA = oversightPriority[a.toLowerCase()] ?? 99; + const pB = oversightPriority[b.toLowerCase()] ?? 99; + return pA - pB; + }); + this.renderCheckboxList(ui.elements.filterOversightList, sortedOversights, 'oversight'); + }, + + renderCheckboxList(container, items, type) { + if (items.length === 0) { + container.innerHTML = '<div class="checkbox-item"><span>No data</span></div>'; + return; + } + + container.innerHTML = items.map(item => ` + <label class="checkbox-item"> + <input type="checkbox" name="filter-${type}" value="${item}" checked> + <span title="${item}">${item}</span> + </label> + `).join(''); }, applyFilters() { if (!currentCsvData) return; - const modelFilter = ui.elements.filterModel.value; - const scenarioFilter = ui.elements.filterScenario.value; - const oversightFilter = ui.elements.filterOversight.value; + const getChecked = (name) => Array.from(document.querySelectorAll(`input[name="filter-${name}"]:checked`)).map(cb => cb.value); + + const modelFilters = getChecked('model'); + const scenarioFilters = getChecked('scenario'); + const oversightFilters = getChecked('oversight'); const filteredData = currentCsvData.filter(row => { - return (modelFilter === 'all' || row.model === modelFilter) && - (scenarioFilter === 'all' || row.scenario === scenarioFilter) && - (oversightFilter === 'all' || row.oversight === oversightFilter); + return (modelFilters.length === 0 || modelFilters.includes(row.model)) && + (scenarioFilters.length === 0 || scenarioFilters.includes(row.scenario)) && + (oversightFilters.length === 0 || oversightFilters.includes(row.oversight)); }); ui.elements.dataStats.textContent = `Showing ${filteredData.length} of ${currentCsvData.length} records`; @@ -219,7 +238,16 @@ export const results = { groups.add(gVal); }); - const labels = Object.keys(matrix); + let labels = Object.keys(matrix); + + // Custom sorting for X-axis labels (like Oversight) + if (xCol === 'oversight') { + const oversightPriority = { 'low': 0, 'mid': 1, 'medium': 1, 'high': 2 }; + labels.sort((a, b) => (oversightPriority[a.toLowerCase()] ?? 99) - (oversightPriority[b.toLowerCase()] ?? 99)); + } else { + labels.sort(); + } + const datasets = Array.from(groups).map((group, i) => { const colors = ['#ef4444', '#10b981', '#f59e0b', '#3b82f6', '#9d4edd', '#6366f1']; return { diff --git a/api/static/js/main.js b/api/static/js/main.js index 8c540c3..2995943 100644 --- a/api/static/js/main.js +++ b/api/static/js/main.js @@ -4,6 +4,11 @@ import { results } from './components/results.js'; async function init() { console.log('Initializing AI Reasoning Dashboard...'); + // Global utility for filters + window.toggleFilter = (type, state) => { + document.querySelectorAll(`input[name="filter-${type}"]`).forEach(cb => cb.checked = state); + }; + await results.init(); // Init UI behaviors diff --git a/api/static/js/ui.js b/api/static/js/ui.js index 359909b..8dd9586 100644 --- a/api/static/js/ui.js +++ b/api/static/js/ui.js @@ -4,9 +4,9 @@ export const ui = { csvFileSelect: document.getElementById('csv-file-select'), judgeFileSelect: document.getElementById('judge-file-select'), - filterModel: document.getElementById('filter-model'), - filterScenario: document.getElementById('filter-scenario'), - filterOversight: document.getElementById('filter-oversight'), + filterModelList: document.getElementById('filter-model-list'), + filterScenarioList: document.getElementById('filter-scenario-list'), + filterOversightList: document.getElementById('filter-oversight-list'), refreshBtn: document.getElementById('refresh-btn'), |
