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: , label: "Completed", color: "success", }, evaluating: { icon: , label: "Evaluating", color: "warning", }, pending: { icon: , label: "Pending", color: "info" }, }; const config = statusConfig[status] || statusConfig.pending; return ( ); }; 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 ( {emptyMessage} ); } return ( {columns.map((column) => ( ))} {columns.map((column, index) => ( {column.label} ))}
{rowVirtualizer.getVirtualItems().map((virtualRow) => { const model = models[virtualRow.index]; const waitTime = formatWaitTime(model.wait_time); return ( {model.name} {model.submitter} {waitTime} {model.precision} {model.revision.substring(0, 7)} ); })}
); }; const QueueAccordion = ({ title, models, status, emptyMessage, expanded, onChange, loading, }) => ( }> {title} ({ 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 && ( )} ); 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 ( {error} ); } return ( } sx={{ px: 3, "& .MuiAccordionSummary-expandIconWrapper": { color: "text.secondary", transform: "rotate(0deg)", transition: "transform 150ms", "&.Mui-expanded": { transform: "rotate(180deg)", }, }, }} > Evaluation Status {!loading && ( )} {loading && ( )} {loading ? ( ) : ( <> )} ); }; export default EvaluationQueues;