import React, { useState, useEffect } from "react"; import { Box, Typography, Paper, Button, Alert, List, ListItem, CircularProgress, Chip, Divider, IconButton, Stack, Link, } from "@mui/material"; import AccessTimeIcon from "@mui/icons-material/AccessTime"; import PersonIcon from "@mui/icons-material/Person"; import OpenInNewIcon from "@mui/icons-material/OpenInNew"; import HowToVoteIcon from "@mui/icons-material/HowToVote"; import { useAuth } from "../../hooks/useAuth"; import PageHeader from "../../components/shared/PageHeader"; import AuthContainer from "../../components/shared/AuthContainer"; import { alpha } from "@mui/material/styles"; import CheckIcon from "@mui/icons-material/Check"; const NoModelsToVote = () => ( No Models to Vote There are currently no models waiting for votes.
Check back later!
); function VoteModelPage() { const { isAuthenticated, user, loading } = useAuth(); const [pendingModels, setPendingModels] = useState([]); const [loadingModels, setLoadingModels] = useState(true); const [error, setError] = useState(null); const [userVotes, setUserVotes] = useState(new Set()); const formatWaitTime = (submissionTime) => { if (!submissionTime) return "N/A"; const now = new Date(); const submitted = new Date(submissionTime); const diffInHours = Math.floor((now - submitted) / (1000 * 60 * 60)); // Less than 24 hours: show in hours if (diffInHours < 24) { return `${diffInHours}h`; } // Less than 7 days: show in days const diffInDays = Math.floor(diffInHours / 24); if (diffInDays < 7) { return `${diffInDays}d`; } // More than 7 days: show in weeks const diffInWeeks = Math.floor(diffInDays / 7); return `${diffInWeeks}w`; }; // Fetch user's votes useEffect(() => { const fetchUserVotes = async () => { if (!isAuthenticated || !user) return; try { // Récupérer les votes du localStorage const localVotes = JSON.parse( localStorage.getItem(`votes_${user.username}`) || "[]" ); const localVotesSet = new Set(localVotes); // Récupérer les votes du serveur const response = await fetch(`/api/votes/user/${user.username}`); if (!response.ok) { throw new Error("Failed to fetch user votes"); } const data = await response.json(); // Fusionner les votes du serveur avec les votes locaux const votedModels = new Set([ ...data.map((vote) => vote.model), ...localVotesSet, ]); setUserVotes(votedModels); } catch (err) { console.error("Error fetching user votes:", err); } }; fetchUserVotes(); }, [isAuthenticated, user]); useEffect(() => { const fetchModels = async () => { try { const response = await fetch("/api/models/pending"); if (!response.ok) { throw new Error("Failed to fetch pending models"); } const data = await response.json(); // Fetch votes for each model const modelsWithVotes = await Promise.all( data.map(async (model) => { const [provider, modelName] = model.name.split("/"); const votesResponse = await fetch( `/api/votes/model/${provider}/${modelName}` ); const votesData = await votesResponse.json(); // Calculate total vote score from votes_by_revision const totalScore = Object.values( votesData.votes_by_revision || {} ).reduce((a, b) => a + b, 0); // Calculate wait time based on submission_time from model data const waitTimeDisplay = formatWaitTime(model.submission_time); return { ...model, votes: totalScore, votes_by_revision: votesData.votes_by_revision, wait_time: waitTimeDisplay, hasVoted: userVotes.has(model.name), }; }) ); // Sort models by vote score in descending order const sortedModels = modelsWithVotes.sort((a, b) => b.votes - a.votes); setPendingModels(sortedModels); } catch (err) { setError(err.message); } finally { setLoadingModels(false); } }; fetchModels(); }, [userVotes]); const handleVote = async (modelName) => { if (!isAuthenticated) return; try { // Disable the button immediately by adding the model to userVotes setUserVotes((prev) => { const newSet = new Set([...prev, modelName]); // Sauvegarder dans le localStorage if (user) { const localVotes = JSON.parse( localStorage.getItem(`votes_${user.username}`) || "[]" ); if (!localVotes.includes(modelName)) { localVotes.push(modelName); localStorage.setItem( `votes_${user.username}`, JSON.stringify(localVotes) ); } } return newSet; }); // Split modelName into provider and model const [provider, model] = modelName.split("/"); const response = await fetch( `/api/votes/${modelName}?vote_type=up&user_id=${user.username}`, { method: "POST", headers: { "Content-Type": "application/json", }, } ); if (!response.ok) { // Si le vote échoue, on retire le vote du localStorage et du state setUserVotes((prev) => { const newSet = new Set(prev); newSet.delete(modelName); if (user) { const localVotes = JSON.parse( localStorage.getItem(`votes_${user.username}`) || "[]" ); const updatedVotes = localVotes.filter( (vote) => vote !== modelName ); localStorage.setItem( `votes_${user.username}`, JSON.stringify(updatedVotes) ); } return newSet; }); throw new Error("Failed to submit vote"); } // Refresh votes for this model const votesResponse = await fetch( `/api/votes/model/${provider}/${model}` ); const votesData = await votesResponse.json(); // Calculate total vote score from votes_by_revision const totalScore = Object.values( votesData.votes_by_revision || {} ).reduce((a, b) => a + b, 0); // Update model and resort the list setPendingModels((models) => { const updatedModels = models.map((model) => model.name === modelName ? { ...model, votes: totalScore, votes_by_revision: votesData.votes_by_revision, } : model ); return updatedModels.sort((a, b) => b.votes - a.votes); }); } catch (err) { setError(err.message); } }; if (loading) { return ( ); } return ( Help us prioritize which models to evaluate next } /> {error && ( {error} )} {/* Auth Status */} {/* {isAuthenticated ? ( Connected as {user?.username} ) : ( Login to Vote You need to be logged in with your Hugging Face account to vote for models )} */} {/* Models List */} {/* Header - Always visible */} theme.palette.mode === "dark" ? alpha(theme.palette.divider, 0.1) : "grey.200", bgcolor: (theme) => theme.palette.mode === "dark" ? alpha(theme.palette.background.paper, 0.5) : "grey.50", }} > Models Pending Evaluation {/* Table Header */} Model Votes Priority {/* Content */} {loadingModels ? ( ) : pendingModels.length === 0 && !loadingModels ? ( ) : ( {pendingModels.map((model, index) => { const isTopThree = index < 3; return ( {index > 0 && } {/* Left side - Model info */} {/* Model name and link */} {model.name} {/* Metadata row */} {model.wait_time} {model.submitter} {/* Vote Column */} + {model.votes > 999 ? "999" : model.votes} votes {/* Priority Column */} {isTopThree && ( HIGH )} #{index + 1} } size="medium" variant={isTopThree ? "filled" : "outlined"} sx={{ height: 36, minWidth: "100px", bgcolor: isTopThree ? (theme) => alpha(theme.palette.primary.main, 0.1) : "transparent", borderColor: isTopThree ? "primary.main" : "grey.300", borderWidth: 2, "& .MuiChip-label": { px: 2, fontSize: "0.95rem", }, }} /> ); })} )} ); } export default VoteModelPage;