Spaces:
Running
Running
File size: 6,857 Bytes
7f6ef8f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
import React, { useState } from 'react'
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'
interface Row {
metric: string
[key: string]: string | number
}
interface QualityMetricsTableProps {
qualityMetrics: string[]
tableHeader: string[]
selectedModels: Set<string>
tableRows: Row[]
}
const QualityMetricsTable: React.FC<QualityMetricsTableProps> = ({
qualityMetrics,
tableHeader,
selectedModels,
tableRows,
}) => {
// Sorting state
const [rowSort, setRowSort] = useState<{ metric: string; direction: 'asc' | 'desc' } | null>(null)
const [columnSort, setColumnSort] = useState<{ model: string; direction: 'asc' | 'desc' } | null>(
null
)
// Handle row sort (sort columns by this metric)
const handleRowSort = (metric: string) => {
setColumnSort(null) // Only one sort active at a time
setRowSort((prev) => {
if (!prev || prev.metric !== metric) return { metric, direction: 'asc' }
if (prev.direction === 'asc') return { metric, direction: 'desc' }
return null // Remove sort
})
}
// Handle column sort (sort rows by this model)
const handleColumnSort = (model: string) => {
setRowSort(null) // Only one sort active at a time
setColumnSort((prev) => {
if (!prev || prev.model !== model) return { model, direction: 'asc' }
if (prev.direction === 'asc') return { model, direction: 'desc' }
return null // Remove sort
})
}
// Sort models (columns)
let sortedModels = tableHeader.filter((model) => selectedModels.has(model))
if (rowSort) {
// Sort columns by the value in the selected metric row
sortedModels = [...sortedModels].sort((a, b) => {
const row = tableRows.find((r) => r.metric === rowSort.metric)
if (!row) return 0
const valA = Number(row[a])
const valB = Number(row[b])
if (isNaN(valA) && isNaN(valB)) return 0
if (isNaN(valA)) return 1
if (isNaN(valB)) return -1
return rowSort.direction === 'asc' ? valA - valB : valB - valA
})
}
// Sort metrics (rows)
let sortedMetrics = [...qualityMetrics]
if (columnSort) {
// Sort rows by the value in the selected model column
sortedMetrics = [...sortedMetrics].sort((a, b) => {
const rowA = tableRows.find((r) => r.metric === a)
const rowB = tableRows.find((r) => r.metric === b)
if (!rowA || !rowB) return 0
const valA = Number(rowA[columnSort.model])
const valB = Number(rowB[columnSort.model])
if (isNaN(valA) && isNaN(valB)) return 0
if (isNaN(valA)) return 1
if (isNaN(valB)) return -1
return columnSort.direction === 'asc' ? valA - valB : valB - valA
})
}
// CSV export logic
function exportToCSV() {
// Build header row
const header = ['Metric', ...sortedModels]
// Build data rows
const rows = sortedMetrics
.map((metric) => {
const row = tableRows.find((r) => r.metric === metric)
if (!row) return null
return [
metric,
...sortedModels.map((col) => {
const cell = row[col]
// Format as displayed
return !isNaN(Number(cell)) ? Number(Number(cell).toFixed(3)) : cell
}),
]
})
.filter((row): row is (string | number)[] => !!row)
// Combine
const csv = [header, ...rows]
.map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(','))
.join('\n')
// Download
const blob = new Blob([csv], { type: 'text/csv' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'quality_metrics.csv'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
}
if (qualityMetrics.length === 0) return null
return (
<div className="overflow-x-auto max-h-[80vh] overflow-y-auto">
<div className="flex justify-end mb-2">
<button className="btn btn-ghost btn-circle" title="Export CSV" onClick={exportToCSV}>
<ArrowDownTrayIcon className="h-6 w-6" />
</button>
</div>
<table className="table w-full min-w-max border-gray-700 border">
<thead>
<tr>
<th className="sticky left-0 top-0 bg-base-100 z-20 border-gray-700 border">Metric</th>
{sortedModels.map((model) => {
const isSorted = columnSort && columnSort.model === model
const direction = isSorted ? columnSort.direction : undefined
return (
<th
key={`quality-${model}`}
className="sticky top-0 bg-base-100 z-10 text-center text-xs border-gray-700 border cursor-pointer select-none"
onClick={() => handleColumnSort(model)}
title={
isSorted
? direction === 'asc'
? 'Sort descending'
: 'Clear sort'
: 'Sort by this column'
}
>
{model}
<span className="ml-1">{isSorted ? (direction === 'asc' ? 'β' : 'β') : 'β
'}</span>
</th>
)
})}
</tr>
</thead>
<tbody>
{sortedMetrics.map((metric) => {
const row = tableRows.find((r) => r.metric === metric)
if (!row) return null
const isSorted = rowSort && rowSort.metric === metric
const direction = isSorted ? rowSort.direction : undefined
return (
<tr key={`quality-${metric}`} className="hover:bg-base-100">
<td
className="sticky left-0 bg-base-100 z-10 border-gray-700 border cursor-pointer select-none"
onClick={() => handleRowSort(metric)}
title={
isSorted
? direction === 'asc'
? 'Sort descending'
: 'Clear sort'
: 'Sort by this row (sorts columns)'
}
>
{metric}
<span className="ml-1">{isSorted ? (direction === 'asc' ? 'β' : 'β') : 'β
'}</span>
</td>
{sortedModels.map((col) => {
const cell = row[col]
return (
<td
key={`quality-${metric}-${col}`}
className="text-center border-gray-700 border"
>
{!isNaN(Number(cell)) ? Number(Number(cell).toFixed(3)) : cell}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
</div>
)
}
export default QualityMetricsTable
|