Spaces:
				
			
			
	
			
			
					
		Running
		
			on 
			
			CPU Upgrade
	
	
	
			
			
	
	
	
	
		
		
					
		Running
		
			on 
			
			CPU Upgrade
	| import React, { useState, useEffect, useRef } from 'react' | |
| import { ArrowDownTrayIcon } from '@heroicons/react/24/outline' | |
| import MetricInfoIcon from './MetricInfoIcon' | |
| import Descriptions from '../Descriptions' | |
| 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 | |
| ) | |
| const [descriptionsLoaded, setDescriptionsLoaded] = useState(false) | |
| const descriptions = useRef(Descriptions.getInstance()) | |
| // Load descriptions | |
| useEffect(() => { | |
| descriptions.current.load().then(() => setDescriptionsLoaded(true)) | |
| }, []) | |
| // Handle row sort (sort columns by this metric) | |
| const handleRowSort = (metric: string) => { | |
| 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) => { | |
| 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 with model full names when available | |
| const header = [ | |
| 'Quality Metric', | |
| ...sortedModels.map(model => | |
| descriptionsLoaded && descriptions.current.getModelAlias(model) || model | |
| ) | |
| ] | |
| // 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="max-h-[80vh]"> | |
| <div className="flex justify-end"> | |
| <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"> | |
| Quality 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' | |
| } | |
| > | |
| {descriptionsLoaded && descriptions.current.getModelAlias(model) || 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 hover:z-30 relative"> | |
| <td | |
| className="sticky left-0 bg-base-100 z-10 hover:z-40 border-gray-700 border cursor-pointer select-none pr-4" | |
| onClick={() => handleRowSort(metric)} | |
| title={ | |
| isSorted | |
| ? direction === 'asc' | |
| ? 'Sort descending' | |
| : 'Clear sort' | |
| : 'Sort by this row (sorts columns)' | |
| } | |
| > | |
| <div className="flex items-center"> | |
| <span className="inline-block">{metric}</span> | |
| <MetricInfoIcon metricName={metric} /> | |
| <span className="ml-auto"> | |
| {isSorted ? (direction === 'asc' ? '←' : '→') : '⇆'} | |
| </span> | |
| </div> | |
| </td> | |
| {sortedModels.map((col) => { | |
| const cell = row[col] | |
| return ( | |
| <td | |
| key={`quality-${metric}-${col}`} | |
| className="text-center border-gray-700 border hover:z-40 relative" | |
| > | |
| {!isNaN(Number(cell)) ? Number(Number(cell).toFixed(3)) : cell} | |
| </td> | |
| ) | |
| })} | |
| </tr> | |
| ) | |
| })} | |
| </tbody> | |
| </table> | |
| </div> | |
| ) | |
| } | |
| export default QualityMetricsTable | |