Spaces:
Running
Running
feat: add sortable indicators to model detail table headers
Browse files- index.html +83 -8
index.html
CHANGED
@@ -472,8 +472,10 @@
|
|
472 |
let allRows = []; // Store all fetched rows globally
|
473 |
let currentFilteredRows = []; // Store currently filtered rows
|
474 |
let uniqueModels = [];
|
475 |
-
let currentSortKey = null; // Track current sort column
|
476 |
-
let currentSortDirection = 'asc'; // Track current sort direction
|
|
|
|
|
477 |
const rowsPerFetch = 100; // How many rows to fetch per API call
|
478 |
|
479 |
// Plotly layout defaults (same as before)
|
@@ -604,6 +606,26 @@
|
|
604 |
sortTable(currentSortKey, currentSortDirection);
|
605 |
});
|
606 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
607 |
|
608 |
// --- Fetching Logic ---
|
609 |
function fetchAllRows(offset = 0) {
|
@@ -892,7 +914,8 @@
|
|
892 |
document.getElementById('table-title').textContent = `Detailed Comparison for ${selectedModelId.split('/').pop()}`;
|
893 |
const tableHead = document.querySelector('#modelDetailTable thead');
|
894 |
const tableBody = document.querySelector('#modelDetailTable tbody');
|
895 |
-
|
|
|
896 |
|
897 |
const providerStats = {};
|
898 |
|
@@ -905,9 +928,27 @@
|
|
905 |
if (row.duration_ms !== null && row.duration_ms >= 0) providerStats[provider].latencies.push(row.duration_ms);
|
906 |
});
|
907 |
|
908 |
-
|
909 |
-
|
910 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
911 |
|
912 |
const providerDataArray = [];
|
913 |
for (const provider in providerStats) {
|
@@ -919,8 +960,33 @@
|
|
919 |
errors: stats.errors, total: stats.total
|
920 |
});
|
921 |
}
|
922 |
-
providerDataArray.sort((a, b) => (a.medianLatency ?? Infinity) - (b.medianLatency ?? Infinity)); // Sort by latency
|
923 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
924 |
providerDataArray.forEach(data => {
|
925 |
const row = tableBody.insertRow();
|
926 |
row.insertCell().textContent = data.provider;
|
@@ -937,7 +1003,16 @@
|
|
937 |
});
|
938 |
}
|
939 |
|
940 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
941 |
function sortTable(key, direction) {
|
942 |
currentFilteredRows.sort((a, b) => {
|
943 |
let valA = a[key];
|
|
|
472 |
let allRows = []; // Store all fetched rows globally
|
473 |
let currentFilteredRows = []; // Store currently filtered rows
|
474 |
let uniqueModels = [];
|
475 |
+
let currentSortKey = null; // Track current sort column for requestTable
|
476 |
+
let currentSortDirection = 'asc'; // Track current sort direction for requestTable
|
477 |
+
let modelDetailSortKey = 'medianLatency'; // Default sort for detail table
|
478 |
+
let modelDetailSortDirection = 'asc'; // Default direction for detail table
|
479 |
const rowsPerFetch = 100; // How many rows to fetch per API call
|
480 |
|
481 |
// Plotly layout defaults (same as before)
|
|
|
606 |
sortTable(currentSortKey, currentSortDirection);
|
607 |
});
|
608 |
|
609 |
+
// Sorting listener for Model Detail Table Header
|
610 |
+
document.querySelector('#modelDetailTable thead').addEventListener('click', (event) => {
|
611 |
+
const header = event.target.closest('th.sortable-header');
|
612 |
+
if (!header) return;
|
613 |
+
|
614 |
+
const sortKey = header.dataset.sortKey;
|
615 |
+
|
616 |
+
if (sortKey === modelDetailSortKey) {
|
617 |
+
modelDetailSortDirection = modelDetailSortDirection === 'asc' ? 'desc' : 'asc';
|
618 |
+
} else {
|
619 |
+
modelDetailSortKey = sortKey;
|
620 |
+
modelDetailSortDirection = 'asc';
|
621 |
+
}
|
622 |
+
|
623 |
+
// Re-render the table which includes sorting
|
624 |
+
const selectedModel = modelSelector.value;
|
625 |
+
const rowsForTable = selectedModel === 'all' ? allRows : allRows.filter(row => row.model_id === selectedModel);
|
626 |
+
createModelDetailTable(rowsForTable, selectedModel);
|
627 |
+
});
|
628 |
+
|
629 |
|
630 |
// --- Fetching Logic ---
|
631 |
function fetchAllRows(offset = 0) {
|
|
|
914 |
document.getElementById('table-title').textContent = `Detailed Comparison for ${selectedModelId.split('/').pop()}`;
|
915 |
const tableHead = document.querySelector('#modelDetailTable thead');
|
916 |
const tableBody = document.querySelector('#modelDetailTable tbody');
|
917 |
+
// Clear only the body, keep the header for event listeners
|
918 |
+
tableBody.innerHTML = '';
|
919 |
|
920 |
const providerStats = {};
|
921 |
|
|
|
928 |
if (row.duration_ms !== null && row.duration_ms >= 0) providerStats[provider].latencies.push(row.duration_ms);
|
929 |
});
|
930 |
|
931 |
+
// Create Header (only if it doesn't exist)
|
932 |
+
if (tableHead.rows.length === 0) {
|
933 |
+
const headerRow = tableHead.insertRow();
|
934 |
+
const headers = [
|
935 |
+
{ text: 'Provider', key: 'provider', sortable: true },
|
936 |
+
{ text: 'Median Latency (ms)', key: 'medianLatency', sortable: true },
|
937 |
+
{ text: 'Success Rate (%)', key: 'successRate', sortable: true },
|
938 |
+
{ text: 'Error Count', key: 'errors', sortable: true },
|
939 |
+
{ text: 'Total Requests', key: 'total', sortable: true }
|
940 |
+
];
|
941 |
+
headers.forEach(header => {
|
942 |
+
const th = document.createElement('th');
|
943 |
+
th.textContent = header.text;
|
944 |
+
if (header.sortable) {
|
945 |
+
th.classList.add('sortable-header');
|
946 |
+
th.dataset.sortKey = header.key;
|
947 |
+
}
|
948 |
+
headerRow.appendChild(th);
|
949 |
+
});
|
950 |
+
}
|
951 |
+
|
952 |
|
953 |
const providerDataArray = [];
|
954 |
for (const provider in providerStats) {
|
|
|
960 |
errors: stats.errors, total: stats.total
|
961 |
});
|
962 |
}
|
|
|
963 |
|
964 |
+
// Sort the data based on current state
|
965 |
+
providerDataArray.sort((a, b) => {
|
966 |
+
let valA = a[modelDetailSortKey];
|
967 |
+
let valB = b[modelDetailSortKey];
|
968 |
+
|
969 |
+
// Handle null/undefined for numeric sorts (e.g., medianLatency)
|
970 |
+
const nullVal = modelDetailSortDirection === 'asc' ? Infinity : -Infinity;
|
971 |
+
if (typeof valA === 'number' || valA === null || valA === undefined) {
|
972 |
+
valA = (valA === null || valA === undefined) ? nullVal : valA;
|
973 |
+
}
|
974 |
+
if (typeof valB === 'number' || valB === null || valB === undefined) {
|
975 |
+
valB = (valB === null || valB === undefined) ? nullVal : valB;
|
976 |
+
}
|
977 |
+
|
978 |
+
// Comparison logic
|
979 |
+
if (typeof valA === 'string' && typeof valB === 'string') {
|
980 |
+
return modelDetailSortDirection === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA);
|
981 |
+
} else {
|
982 |
+
return modelDetailSortDirection === 'asc' ? valA - valB : valB - valA;
|
983 |
+
}
|
984 |
+
});
|
985 |
+
|
986 |
+
// Update visual indicators *after* sorting
|
987 |
+
updateModelDetailSortIndicators();
|
988 |
+
|
989 |
+
// Render rows
|
990 |
providerDataArray.forEach(data => {
|
991 |
const row = tableBody.insertRow();
|
992 |
row.insertCell().textContent = data.provider;
|
|
|
1003 |
});
|
1004 |
}
|
1005 |
|
1006 |
+
function updateModelDetailSortIndicators() {
|
1007 |
+
document.querySelectorAll('#modelDetailTable th.sortable-header').forEach(th => {
|
1008 |
+
th.classList.remove('sort-asc', 'sort-desc');
|
1009 |
+
if (th.dataset.sortKey === modelDetailSortKey) {
|
1010 |
+
th.classList.add(modelDetailSortDirection === 'asc' ? 'sort-asc' : 'sort-desc');
|
1011 |
+
}
|
1012 |
+
});
|
1013 |
+
}
|
1014 |
+
|
1015 |
+
// --- Sorting Function for Request Inspector ---
|
1016 |
function sortTable(key, direction) {
|
1017 |
currentFilteredRows.sort((a, b) => {
|
1018 |
let valA = a[key];
|