Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
open_llm_leaderboard
/
frontend
/src
/pages
/AddModelPage
/components
/EvaluationQueues
/EvaluationQueues.js
import React, { useState, useEffect, useRef } from "react"; | |
import { | |
Box, | |
Typography, | |
Table, | |
TableBody, | |
TableCell, | |
TableContainer, | |
TableHead, | |
TableRow, | |
Chip, | |
Link, | |
CircularProgress, | |
Alert, | |
Accordion, | |
AccordionSummary, | |
AccordionDetails, | |
Stack, | |
Tooltip, | |
} from "@mui/material"; | |
import AccessTimeIcon from "@mui/icons-material/AccessTime"; | |
import CheckCircleIcon from "@mui/icons-material/CheckCircle"; | |
import PendingIcon from "@mui/icons-material/Pending"; | |
import AutorenewIcon from "@mui/icons-material/Autorenew"; | |
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; | |
import OpenInNewIcon from "@mui/icons-material/OpenInNew"; | |
import { useVirtualizer } from "@tanstack/react-virtual"; | |
// Function to format wait time | |
const formatWaitTime = (waitTimeStr) => { | |
const seconds = parseFloat(waitTimeStr.replace("s", "")); | |
if (seconds < 60) { | |
return "just now"; | |
} | |
const minutes = Math.floor(seconds / 60); | |
if (minutes < 60) { | |
return `${minutes}m ago`; | |
} | |
const hours = Math.floor(minutes / 60); | |
if (hours < 24) { | |
return `${hours}h ago`; | |
} | |
const days = Math.floor(hours / 24); | |
return `${days}d ago`; | |
}; | |
// Column definitions with their properties | |
const columns = [ | |
{ | |
id: "model", | |
label: "Model", | |
width: "35%", | |
align: "left", | |
}, | |
{ | |
id: "submitter", | |
label: "Submitted by", | |
width: "15%", | |
align: "left", | |
}, | |
{ | |
id: "wait_time", | |
label: "Submitted", | |
width: "12%", | |
align: "center", | |
}, | |
{ | |
id: "precision", | |
label: "Precision", | |
width: "13%", | |
align: "center", | |
}, | |
{ | |
id: "revision", | |
label: "Revision", | |
width: "12%", | |
align: "center", | |
}, | |
{ | |
id: "status", | |
label: "Status", | |
width: "13%", | |
align: "center", | |
}, | |
]; | |
const StatusChip = ({ status }) => { | |
const statusConfig = { | |
finished: { | |
icon: <CheckCircleIcon />, | |
label: "Completed", | |
color: "success", | |
}, | |
evaluating: { | |
icon: <AutorenewIcon />, | |
label: "Evaluating", | |
color: "warning", | |
}, | |
pending: { icon: <PendingIcon />, label: "Pending", color: "info" }, | |
}; | |
const config = statusConfig[status] || statusConfig.pending; | |
return ( | |
<Chip | |
icon={config.icon} | |
label={config.label} | |
color={config.color} | |
size="small" | |
variant="outlined" | |
/> | |
); | |
}; | |
const ModelTable = ({ models, emptyMessage, status }) => { | |
const parentRef = useRef(null); | |
const rowVirtualizer = useVirtualizer({ | |
count: models.length, | |
getScrollElement: () => parentRef.current, | |
estimateSize: () => 53, | |
overscan: 5, | |
}); | |
if (models.length === 0) { | |
return ( | |
<Typography variant="body2" color="text.secondary" sx={{ p: 2 }}> | |
{emptyMessage} | |
</Typography> | |
); | |
} | |
return ( | |
<TableContainer | |
ref={parentRef} | |
sx={{ | |
maxHeight: 400, | |
"&::-webkit-scrollbar": { | |
width: 8, | |
height: 8, | |
}, | |
"&::-webkit-scrollbar-track": { | |
backgroundColor: "action.hover", | |
borderRadius: 4, | |
}, | |
"&::-webkit-scrollbar-thumb": { | |
backgroundColor: "action.selected", | |
borderRadius: 4, | |
"&:hover": { | |
backgroundColor: "action.focus", | |
}, | |
}, | |
}} | |
> | |
<Table size="small" stickyHeader sx={{ tableLayout: "fixed" }}> | |
<colgroup> | |
{columns.map((column) => ( | |
<col key={column.id} style={{ width: column.width }} /> | |
))} | |
</colgroup> | |
<TableHead> | |
<TableRow> | |
{columns.map((column, index) => ( | |
<TableCell | |
key={column.id} | |
align={column.align} | |
sx={{ | |
backgroundColor: "background.paper", | |
fontWeight: 600, | |
borderBottom: "2px solid", | |
borderColor: "divider", | |
borderRight: | |
index < columns.length - 1 ? "1px solid" : "none", | |
borderRightColor: "divider", | |
whiteSpace: "nowrap", | |
overflow: "hidden", | |
textOverflow: "ellipsis", | |
padding: "12px 16px", | |
}} | |
> | |
{column.label} | |
</TableCell> | |
))} | |
</TableRow> | |
</TableHead> | |
<TableBody> | |
<TableRow> | |
<TableCell | |
style={{ | |
height: `${rowVirtualizer.getTotalSize()}px`, | |
padding: 0, | |
}} | |
colSpan={columns.length} | |
> | |
<div | |
style={{ | |
position: "relative", | |
width: "100%", | |
height: `${rowVirtualizer.getTotalSize()}px`, | |
}} | |
> | |
{rowVirtualizer.getVirtualItems().map((virtualRow) => { | |
const model = models[virtualRow.index]; | |
const waitTime = formatWaitTime(model.wait_time); | |
return ( | |
<TableRow | |
key={virtualRow.index} | |
style={{ | |
position: "absolute", | |
top: 0, | |
left: 0, | |
width: "100%", | |
height: `${virtualRow.size}px`, | |
transform: `translateY(${virtualRow.start}px)`, | |
backgroundColor: "background.paper", | |
display: "flex", | |
}} | |
hover | |
> | |
<TableCell | |
component="div" | |
sx={{ | |
flex: `0 0 ${columns[0].width}`, | |
padding: "12px 16px", | |
overflow: "hidden", | |
textOverflow: "ellipsis", | |
whiteSpace: "nowrap", | |
borderRight: "1px solid", | |
borderRightColor: "divider", | |
display: "flex", | |
alignItems: "center", | |
}} | |
> | |
<Link | |
href={`https://huggingface.co/${model.name}`} | |
target="_blank" | |
rel="noopener noreferrer" | |
sx={{ | |
textDecoration: "none", | |
overflow: "hidden", | |
textOverflow: "ellipsis", | |
whiteSpace: "nowrap", | |
display: "flex", | |
alignItems: "center", | |
gap: 0.5, | |
"& .MuiSvgIcon-root": { | |
fontSize: "1rem", | |
opacity: 0.6, | |
}, | |
}} | |
> | |
{model.name} | |
<OpenInNewIcon /> | |
</Link> | |
</TableCell> | |
<TableCell | |
component="div" | |
sx={{ | |
flex: `0 0 ${columns[1].width}`, | |
padding: "12px 16px", | |
overflow: "hidden", | |
textOverflow: "ellipsis", | |
whiteSpace: "nowrap", | |
borderRight: "1px solid", | |
borderRightColor: "divider", | |
display: "flex", | |
alignItems: "center", | |
}} | |
> | |
{model.submitter} | |
</TableCell> | |
<TableCell | |
component="div" | |
align={columns[2].align} | |
sx={{ | |
flex: `0 0 ${columns[2].width}`, | |
padding: "12px 16px", | |
borderRight: "1px solid", | |
borderRightColor: "divider", | |
display: "flex", | |
alignItems: "center", | |
justifyContent: "center", | |
}} | |
> | |
<Tooltip title={model.wait_time} arrow placement="top"> | |
<Typography | |
variant="body2" | |
color="text.secondary" | |
sx={{ | |
display: "flex", | |
alignItems: "center", | |
justifyContent: "center", | |
gap: 0.5, | |
}} | |
> | |
<AccessTimeIcon sx={{ fontSize: "0.9rem" }} /> | |
{waitTime} | |
</Typography> | |
</Tooltip> | |
</TableCell> | |
<TableCell | |
component="div" | |
align={columns[3].align} | |
sx={{ | |
flex: `0 0 ${columns[3].width}`, | |
padding: "12px 16px", | |
borderRight: "1px solid", | |
borderRightColor: "divider", | |
display: "flex", | |
alignItems: "center", | |
justifyContent: "center", | |
}} | |
> | |
<Typography variant="body2" color="text.secondary"> | |
{model.precision} | |
</Typography> | |
</TableCell> | |
<TableCell | |
component="div" | |
align={columns[4].align} | |
sx={{ | |
flex: `0 0 ${columns[4].width}`, | |
padding: "12px 16px", | |
fontFamily: "monospace", | |
borderRight: "1px solid", | |
borderRightColor: "divider", | |
display: "flex", | |
alignItems: "center", | |
justifyContent: "center", | |
}} | |
> | |
{model.revision.substring(0, 7)} | |
</TableCell> | |
<TableCell | |
component="div" | |
align={columns[5].align} | |
sx={{ | |
flex: `0 0 ${columns[5].width}`, | |
padding: "12px 16px", | |
display: "flex", | |
alignItems: "center", | |
justifyContent: "center", | |
}} | |
> | |
<StatusChip status={status} /> | |
</TableCell> | |
</TableRow> | |
); | |
})} | |
</div> | |
</TableCell> | |
</TableRow> | |
</TableBody> | |
</Table> | |
</TableContainer> | |
); | |
}; | |
const QueueAccordion = ({ | |
title, | |
models, | |
status, | |
emptyMessage, | |
expanded, | |
onChange, | |
loading, | |
}) => ( | |
<Accordion | |
expanded={expanded} | |
onChange={onChange} | |
disabled={loading} | |
sx={{ | |
"&:before": { display: "none" }, | |
boxShadow: "none", | |
border: "none", | |
}} | |
> | |
<AccordionSummary expandIcon={<ExpandMoreIcon />}> | |
<Stack direction="row" spacing={2} alignItems="center"> | |
<Typography>{title}</Typography> | |
<Stack direction="row" spacing={1} alignItems="center"> | |
<Chip | |
label={models.length} | |
size="small" | |
color={ | |
status === "finished" | |
? "success" | |
: status === "evaluating" | |
? "warning" | |
: "info" | |
} | |
variant="outlined" | |
sx={(theme) => ({ | |
borderWidth: 2, | |
fontWeight: 600, | |
bgcolor: | |
status === "finished" | |
? theme.palette.success[100] | |
: status === "evaluating" | |
? theme.palette.warning[100] | |
: theme.palette.info[100], | |
borderColor: | |
status === "finished" | |
? theme.palette.success[400] | |
: status === "evaluating" | |
? theme.palette.warning[400] | |
: theme.palette.info[400], | |
color: | |
status === "finished" | |
? theme.palette.success[700] | |
: status === "evaluating" | |
? theme.palette.warning[700] | |
: theme.palette.info[700], | |
"& .MuiChip-label": { | |
px: 1.2, | |
}, | |
"&:hover": { | |
bgcolor: | |
status === "finished" | |
? theme.palette.success[200] | |
: status === "evaluating" | |
? theme.palette.warning[200] | |
: theme.palette.info[200], | |
}, | |
})} | |
/> | |
{loading && ( | |
<CircularProgress size={16} color="inherit" sx={{ opacity: 0.5 }} /> | |
)} | |
</Stack> | |
</Stack> | |
</AccordionSummary> | |
<AccordionDetails sx={{ p: 2 }}> | |
<Box | |
sx={{ | |
border: "1px solid", | |
borderColor: "grey.200", | |
borderRadius: 1, | |
overflow: "hidden", | |
}} | |
> | |
<ModelTable | |
models={models} | |
emptyMessage={emptyMessage} | |
status={status} | |
/> | |
</Box> | |
</AccordionDetails> | |
</Accordion> | |
); | |
const EvaluationQueues = ({ defaultExpanded = true }) => { | |
const [expanded, setExpanded] = useState(defaultExpanded); | |
const [expandedQueues, setExpandedQueues] = useState(new Set()); | |
const [models, setModels] = useState({ | |
pending: [], | |
evaluating: [], | |
finished: [], | |
}); | |
const [loading, setLoading] = useState(true); | |
const [error, setError] = useState(null); | |
useEffect(() => { | |
const fetchModels = async () => { | |
try { | |
const response = await fetch("/api/models/status"); | |
if (!response.ok) { | |
throw new Error("Failed to fetch models"); | |
} | |
const data = await response.json(); | |
// Sort models by submission date (most recent first) | |
const sortByDate = (models) => { | |
return [...models].sort((a, b) => { | |
const dateA = new Date(a.submission_time); | |
const dateB = new Date(b.submission_time); | |
return dateB - dateA; | |
}); | |
}; | |
setModels({ | |
finished: sortByDate(data.finished), | |
evaluating: sortByDate(data.evaluating), | |
pending: sortByDate(data.pending), | |
}); | |
} catch (err) { | |
setError(err.message); | |
} finally { | |
setLoading(false); | |
} | |
}; | |
fetchModels(); | |
const interval = setInterval(fetchModels, 30000); | |
return () => clearInterval(interval); | |
}, []); | |
const handleMainAccordionChange = (panel) => (event, isExpanded) => { | |
setExpanded(isExpanded ? panel : false); | |
}; | |
const handleQueueAccordionChange = (queueName) => (event, isExpanded) => { | |
setExpandedQueues((prev) => { | |
const newSet = new Set(prev); | |
if (isExpanded) { | |
newSet.add(queueName); | |
} else { | |
newSet.delete(queueName); | |
} | |
return newSet; | |
}); | |
}; | |
if (error) { | |
return ( | |
<Alert severity="error" sx={{ mb: 2 }}> | |
{error} | |
</Alert> | |
); | |
} | |
return ( | |
<Accordion | |
expanded={expanded === "main"} | |
onChange={handleMainAccordionChange("main")} | |
disabled={loading} | |
elevation={0} | |
sx={{ | |
mb: 3, | |
boxShadow: "none", | |
border: "1px solid", | |
borderColor: "divider", | |
borderRadius: "8px !important", | |
"&:before": { | |
display: "none", | |
}, | |
"&.Mui-disabled": { | |
backgroundColor: "rgba(0, 0, 0, 0.03)", | |
opacity: 0.9, | |
}, | |
"& .MuiAccordionSummary-root": { | |
minHeight: 64, | |
bgcolor: "background.paper", | |
borderRadius: "8px", | |
"&.Mui-expanded": { | |
minHeight: 64, | |
borderRadius: "8px 8px 0 0", | |
}, | |
}, | |
"& .MuiAccordionSummary-content": { | |
m: 0, | |
"&.Mui-expanded": { | |
m: 0, | |
}, | |
}, | |
}} | |
> | |
<AccordionSummary | |
expandIcon={<ExpandMoreIcon />} | |
sx={{ | |
px: 3, | |
"& .MuiAccordionSummary-expandIconWrapper": { | |
color: "text.secondary", | |
transform: "rotate(0deg)", | |
transition: "transform 150ms", | |
"&.Mui-expanded": { | |
transform: "rotate(180deg)", | |
}, | |
}, | |
}} | |
> | |
<Stack direction="row" spacing={2} alignItems="center"> | |
<Typography | |
variant="h6" | |
sx={{ | |
fontWeight: 600, | |
color: "text.primary", | |
letterSpacing: "-0.01em", | |
}} | |
> | |
Evaluation Status | |
</Typography> | |
{!loading && ( | |
<Stack | |
direction="row" | |
spacing={1} | |
sx={{ | |
transition: "opacity 0.2s", | |
".Mui-expanded &": { | |
opacity: 0, | |
}, | |
}} | |
> | |
<Chip | |
label={`${models.pending.length} In Queue`} | |
size="small" | |
color="info" | |
variant="outlined" | |
sx={{ | |
borderWidth: 2, | |
fontWeight: 600, | |
bgcolor: "info.100", | |
borderColor: "info.400", | |
color: "info.700", | |
"& .MuiChip-label": { | |
px: 1.2, | |
}, | |
"&:hover": { | |
bgcolor: "info.200", | |
}, | |
}} | |
/> | |
<Chip | |
label={`${models.evaluating.length} Evaluating`} | |
size="small" | |
color="warning" | |
variant="outlined" | |
sx={{ | |
borderWidth: 2, | |
fontWeight: 600, | |
bgcolor: "warning.100", | |
borderColor: "warning.400", | |
color: "warning.700", | |
"& .MuiChip-label": { | |
px: 1.2, | |
}, | |
"&:hover": { | |
bgcolor: "warning.200", | |
}, | |
}} | |
/> | |
<Chip | |
label={`${models.finished.length} Evaluated`} | |
size="small" | |
color="success" | |
variant="outlined" | |
sx={{ | |
borderWidth: 2, | |
fontWeight: 600, | |
bgcolor: "success.100", | |
borderColor: "success.400", | |
color: "success.700", | |
"& .MuiChip-label": { | |
px: 1.2, | |
}, | |
"&:hover": { | |
bgcolor: "success.200", | |
}, | |
}} | |
/> | |
</Stack> | |
)} | |
{loading && ( | |
<CircularProgress | |
size={20} | |
sx={{ | |
color: "primary.main", | |
}} | |
/> | |
)} | |
</Stack> | |
</AccordionSummary> | |
<AccordionDetails sx={{ p: 0 }}> | |
{loading ? ( | |
<Box | |
sx={{ | |
display: "flex", | |
justifyContent: "center", | |
alignItems: "center", | |
minHeight: 200, | |
width: "100%", | |
}} | |
> | |
<CircularProgress /> | |
</Box> | |
) : ( | |
<> | |
<QueueAccordion | |
title="Models in queue" | |
models={models.pending} | |
status="pending" | |
emptyMessage="No models in queue" | |
expanded={expandedQueues.has("pending")} | |
onChange={handleQueueAccordionChange("pending")} | |
loading={loading} | |
/> | |
<QueueAccordion | |
title="Models being evaluated" | |
models={models.evaluating} | |
status="evaluating" | |
emptyMessage="No models currently being evaluated" | |
expanded={expandedQueues.has("evaluating")} | |
onChange={handleQueueAccordionChange("evaluating")} | |
loading={loading} | |
/> | |
<QueueAccordion | |
title="Recently evaluated models" | |
models={models.finished} | |
status="finished" | |
emptyMessage="No models have been evaluated recently" | |
expanded={expandedQueues.has("finished")} | |
onChange={handleQueueAccordionChange("finished")} | |
loading={loading} | |
/> | |
</> | |
)} | |
</AccordionDetails> | |
</Accordion> | |
); | |
}; | |
export default EvaluationQueues; | |