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; | |