summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/static/css/components.css63
-rw-r--r--api/static/index.html44
-rw-r--r--api/static/js/components/results.js58
-rw-r--r--api/static/js/main.js5
-rw-r--r--api/static/js/ui.js6
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'),