Spaces:
Running
Running
udpate filters | url handling | searchbar and languages
Browse files- client/src/components/LeaderboardFilters/LeaderboardFilters.jsx +142 -65
- client/src/components/LeaderboardSection.jsx +256 -54
- client/src/components/Logo/Logo.jsx +24 -1
- client/src/context/LeaderboardContext.jsx +230 -73
- client/src/pages/HowToSubmitPage/HowToSubmitPage.jsx +10 -6
- client/src/pages/LeaderboardPage/LeaderboardPage.jsx +35 -14
- server/pyproject.toml +6 -0
client/src/components/LeaderboardFilters/LeaderboardFilters.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import React, { useState } from "react";
|
| 2 |
import {
|
| 3 |
Box,
|
| 4 |
Stack,
|
|
@@ -16,6 +16,25 @@ import { useDebounce } from "../../hooks/useDebounce";
|
|
| 16 |
import { alpha } from "@mui/material/styles";
|
| 17 |
import { useMediaQuery } from "@mui/material";
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
const LeaderboardFilters = ({ allSections }) => {
|
| 20 |
const {
|
| 21 |
setSearchQuery,
|
|
@@ -24,10 +43,14 @@ const LeaderboardFilters = ({ allSections }) => {
|
|
| 24 |
totalLeaderboards,
|
| 25 |
filteredCount,
|
| 26 |
filterLeaderboards,
|
|
|
|
| 27 |
leaderboards,
|
|
|
|
|
|
|
|
|
|
| 28 |
} = useLeaderboard();
|
| 29 |
|
| 30 |
-
const [inputValue, setInputValue] = useState("");
|
| 31 |
const [totalArenaCount, setTotalArenaCount] = useState(0);
|
| 32 |
const debouncedSearch = useDebounce(inputValue, 200);
|
| 33 |
|
|
@@ -36,19 +59,60 @@ const LeaderboardFilters = ({ allSections }) => {
|
|
| 36 |
setSearchQuery(debouncedSearch);
|
| 37 |
}, [debouncedSearch, setSearchQuery]);
|
| 38 |
|
| 39 |
-
// Update
|
| 40 |
React.useEffect(() => {
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
// Check if any filter is active
|
| 50 |
const isFilterActive = debouncedSearch || arenaOnly;
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
const isMobile = useMediaQuery((theme) => theme.breakpoints.down("md"));
|
| 53 |
|
| 54 |
return (
|
|
@@ -70,68 +134,81 @@ const LeaderboardFilters = ({ allSections }) => {
|
|
| 70 |
justifyContent="center"
|
| 71 |
sx={{ pb: 2 }}
|
| 72 |
>
|
| 73 |
-
{allSections.map(({ id, title, data }) => {
|
| 74 |
-
const filteredCount =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
return (
|
| 76 |
-
<
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
: "white",
|
| 97 |
-
"&:hover": {
|
| 98 |
backgroundColor: (theme) =>
|
| 99 |
-
|
|
|
|
|
|
|
| 100 |
? "background.paper"
|
| 101 |
: "white",
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
transition: "none",
|
| 105 |
-
},
|
| 106 |
-
transition: "none",
|
| 107 |
-
}}
|
| 108 |
-
>
|
| 109 |
-
{title}
|
| 110 |
-
<Box
|
| 111 |
-
component="span"
|
| 112 |
-
sx={{
|
| 113 |
-
display: "inline-flex",
|
| 114 |
-
alignItems: "center",
|
| 115 |
-
gap: 0.75,
|
| 116 |
-
color: "text.secondary",
|
| 117 |
-
ml: 0.75,
|
| 118 |
}}
|
| 119 |
>
|
|
|
|
| 120 |
<Box
|
| 121 |
component="span"
|
| 122 |
-
sx={
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
);
|
| 136 |
})}
|
| 137 |
</Stack>
|
|
@@ -180,7 +257,7 @@ const LeaderboardFilters = ({ allSections }) => {
|
|
| 180 |
fontWeight: 500,
|
| 181 |
}}
|
| 182 |
>
|
| 183 |
-
{
|
| 184 |
</Typography>
|
| 185 |
<Box
|
| 186 |
sx={{
|
|
@@ -204,7 +281,7 @@ const LeaderboardFilters = ({ allSections }) => {
|
|
| 204 |
fontWeight: 500,
|
| 205 |
}}
|
| 206 |
>
|
| 207 |
-
{arenaOnly ? totalArenaCount :
|
| 208 |
</Typography>
|
| 209 |
</Box>
|
| 210 |
<Typography
|
|
|
|
| 1 |
+
import React, { useState, useMemo } from "react";
|
| 2 |
import {
|
| 3 |
Box,
|
| 4 |
Stack,
|
|
|
|
| 16 |
import { alpha } from "@mui/material/styles";
|
| 17 |
import { useMediaQuery } from "@mui/material";
|
| 18 |
|
| 19 |
+
// Helper function to get the category group of a section
|
| 20 |
+
const getSectionGroup = (id) => {
|
| 21 |
+
const groups = {
|
| 22 |
+
modalities: ["agentic", "vision", "audio"],
|
| 23 |
+
capabilities: ["code", "math"],
|
| 24 |
+
languages: ["language"],
|
| 25 |
+
domains: ["financial", "medical", "legal"],
|
| 26 |
+
evaluation: ["safety"],
|
| 27 |
+
misc: ["uncategorized"],
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
for (const [group, ids] of Object.entries(groups)) {
|
| 31 |
+
if (ids.includes(id)) {
|
| 32 |
+
return group;
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
return "misc";
|
| 36 |
+
};
|
| 37 |
+
|
| 38 |
const LeaderboardFilters = ({ allSections }) => {
|
| 39 |
const {
|
| 40 |
setSearchQuery,
|
|
|
|
| 43 |
totalLeaderboards,
|
| 44 |
filteredCount,
|
| 45 |
filterLeaderboards,
|
| 46 |
+
filterLeaderboardsForCount,
|
| 47 |
leaderboards,
|
| 48 |
+
selectedCategory,
|
| 49 |
+
setSelectedCategory,
|
| 50 |
+
searchQuery,
|
| 51 |
} = useLeaderboard();
|
| 52 |
|
| 53 |
+
const [inputValue, setInputValue] = useState(searchQuery || "");
|
| 54 |
const [totalArenaCount, setTotalArenaCount] = useState(0);
|
| 55 |
const debouncedSearch = useDebounce(inputValue, 200);
|
| 56 |
|
|
|
|
| 59 |
setSearchQuery(debouncedSearch);
|
| 60 |
}, [debouncedSearch, setSearchQuery]);
|
| 61 |
|
| 62 |
+
// Update input value when searchQuery changes externally
|
| 63 |
React.useEffect(() => {
|
| 64 |
+
setInputValue(searchQuery || "");
|
| 65 |
+
}, [searchQuery]);
|
| 66 |
+
|
| 67 |
+
// Update total arena count
|
| 68 |
+
React.useEffect(() => {
|
| 69 |
+
// Si une catégorie est sélectionnée, on compte les arena dans cette catégorie
|
| 70 |
+
const boardsToCount = selectedCategory
|
| 71 |
+
? allSections.find((section) => section.id === selectedCategory)?.data ||
|
| 72 |
+
[]
|
| 73 |
+
: allSections.reduce((acc, section) => [...acc, ...section.data], []);
|
| 74 |
+
|
| 75 |
+
const arenaCount = boardsToCount.filter((board) =>
|
| 76 |
+
board.tags?.includes("judge:humans")
|
| 77 |
+
).length;
|
| 78 |
+
setTotalArenaCount(arenaCount);
|
| 79 |
+
}, [leaderboards, selectedCategory, allSections]);
|
| 80 |
|
| 81 |
// Check if any filter is active
|
| 82 |
const isFilterActive = debouncedSearch || arenaOnly;
|
| 83 |
|
| 84 |
+
// Calculer le nombre total en fonction de la catégorie sélectionnée
|
| 85 |
+
const totalCount = useMemo(() => {
|
| 86 |
+
if (selectedCategory) {
|
| 87 |
+
const categorySection = allSections.find(
|
| 88 |
+
(section) => section.id === selectedCategory
|
| 89 |
+
);
|
| 90 |
+
return categorySection ? categorySection.data.length : 0;
|
| 91 |
+
}
|
| 92 |
+
// Quand aucune catégorie n'est sélectionnée, on compte tous les leaderboards de toutes les sections
|
| 93 |
+
return allSections.reduce(
|
| 94 |
+
(total, section) => total + section.data.length,
|
| 95 |
+
0
|
| 96 |
+
);
|
| 97 |
+
}, [selectedCategory, allSections]);
|
| 98 |
+
|
| 99 |
+
// Calculer le nombre filtré en prenant en compte tous les filtres
|
| 100 |
+
const currentFilteredCount = useMemo(() => {
|
| 101 |
+
if (selectedCategory) {
|
| 102 |
+
const categorySection = allSections.find(
|
| 103 |
+
(section) => section.id === selectedCategory
|
| 104 |
+
);
|
| 105 |
+
if (!categorySection) return 0;
|
| 106 |
+
return filterLeaderboards(categorySection.data).length;
|
| 107 |
+
}
|
| 108 |
+
// Quand aucune catégorie n'est sélectionnée, on compte dans toutes les sections
|
| 109 |
+
const allBoards = allSections.reduce(
|
| 110 |
+
(acc, section) => [...acc, ...section.data],
|
| 111 |
+
[]
|
| 112 |
+
);
|
| 113 |
+
return filterLeaderboards(allBoards).length;
|
| 114 |
+
}, [selectedCategory, allSections, filterLeaderboards]);
|
| 115 |
+
|
| 116 |
const isMobile = useMediaQuery((theme) => theme.breakpoints.down("md"));
|
| 117 |
|
| 118 |
return (
|
|
|
|
| 134 |
justifyContent="center"
|
| 135 |
sx={{ pb: 2 }}
|
| 136 |
>
|
| 137 |
+
{allSections.map(({ id, title, data }, index) => {
|
| 138 |
+
const filteredCount = filterLeaderboardsForCount(data).length;
|
| 139 |
+
const currentGroup = getSectionGroup(id);
|
| 140 |
+
const prevGroup =
|
| 141 |
+
index > 0 ? getSectionGroup(allSections[index - 1].id) : null;
|
| 142 |
+
const needsSpacing = currentGroup !== prevGroup;
|
| 143 |
+
|
| 144 |
return (
|
| 145 |
+
<React.Fragment key={id}>
|
| 146 |
+
{needsSpacing && index > 0 && (
|
| 147 |
+
<Box sx={{ width: "0.5rem", display: "inline-block" }} />
|
| 148 |
+
)}
|
| 149 |
+
<Button
|
| 150 |
+
onClick={() => {
|
| 151 |
+
if (selectedCategory === id || filteredCount > 0) {
|
| 152 |
+
setSelectedCategory(selectedCategory === id ? null : id);
|
| 153 |
+
}
|
| 154 |
+
}}
|
| 155 |
+
variant={selectedCategory === id ? "contained" : "outlined"}
|
| 156 |
+
size="small"
|
| 157 |
+
disabled={filteredCount === 0 && selectedCategory !== id}
|
| 158 |
+
sx={{
|
| 159 |
+
textTransform: "none",
|
| 160 |
+
cursor:
|
| 161 |
+
filteredCount === 0 && selectedCategory !== id
|
| 162 |
+
? "default"
|
| 163 |
+
: "pointer",
|
| 164 |
+
mb: 1,
|
|
|
|
|
|
|
| 165 |
backgroundColor: (theme) =>
|
| 166 |
+
selectedCategory === id
|
| 167 |
+
? undefined
|
| 168 |
+
: theme.palette.mode === "dark"
|
| 169 |
? "background.paper"
|
| 170 |
: "white",
|
| 171 |
+
"&:hover": {
|
| 172 |
+
backgroundColor: (theme) =>
|
| 173 |
+
selectedCategory === id
|
| 174 |
+
? undefined
|
| 175 |
+
: theme.palette.mode === "dark"
|
| 176 |
+
? "background.paper"
|
| 177 |
+
: "white",
|
| 178 |
+
},
|
| 179 |
+
"& .MuiTouchRipple-root": {
|
| 180 |
+
transition: "none",
|
| 181 |
+
},
|
| 182 |
transition: "none",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
}}
|
| 184 |
>
|
| 185 |
+
{title}
|
| 186 |
<Box
|
| 187 |
component="span"
|
| 188 |
+
sx={{
|
| 189 |
+
display: "inline-flex",
|
| 190 |
+
alignItems: "center",
|
| 191 |
+
gap: 0.75,
|
| 192 |
+
color: "text.secondary",
|
| 193 |
+
ml: 0.75,
|
| 194 |
+
}}
|
| 195 |
+
>
|
| 196 |
+
<Box
|
| 197 |
+
component="span"
|
| 198 |
+
sx={(theme) => ({
|
| 199 |
+
width: "4px",
|
| 200 |
+
height: "4px",
|
| 201 |
+
borderRadius: "100%",
|
| 202 |
+
backgroundColor: alpha(
|
| 203 |
+
theme.palette.text.primary,
|
| 204 |
+
theme.palette.mode === "dark" ? 0.2 : 0.15
|
| 205 |
+
),
|
| 206 |
+
})}
|
| 207 |
+
/>
|
| 208 |
+
{filteredCount}
|
| 209 |
+
</Box>
|
| 210 |
+
</Button>
|
| 211 |
+
</React.Fragment>
|
| 212 |
);
|
| 213 |
})}
|
| 214 |
</Stack>
|
|
|
|
| 257 |
fontWeight: 500,
|
| 258 |
}}
|
| 259 |
>
|
| 260 |
+
{currentFilteredCount}
|
| 261 |
</Typography>
|
| 262 |
<Box
|
| 263 |
sx={{
|
|
|
|
| 281 |
fontWeight: 500,
|
| 282 |
}}
|
| 283 |
>
|
| 284 |
+
{arenaOnly ? totalArenaCount : totalCount}
|
| 285 |
</Typography>
|
| 286 |
</Box>
|
| 287 |
<Typography
|
client/src/components/LeaderboardSection.jsx
CHANGED
|
@@ -1,31 +1,95 @@
|
|
| 1 |
-
import React, { useState } from "react";
|
| 2 |
-
import { Typography, Grid, Box, Button, Collapse } from "@mui/material";
|
| 3 |
import { alpha } from "@mui/material/styles";
|
| 4 |
import LeaderboardCard from "./LeaderboardCard";
|
| 5 |
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
| 6 |
-
import
|
|
|
|
| 7 |
|
| 8 |
const ITEMS_PER_PAGE = 3;
|
| 9 |
|
| 10 |
-
const LeaderboardSection = ({
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
// On affiche toujours les 3 premiers
|
| 17 |
-
const displayedLeaderboards =
|
| 18 |
// Le reste sera dans le Collapse
|
| 19 |
-
const remainingLeaderboards =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
const toggleExpanded = () => {
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
};
|
| 25 |
|
| 26 |
-
// Calculate how many skeletons we need
|
| 27 |
-
const skeletonsNeeded = Math.max(0, 3 - leaderboards.length);
|
| 28 |
-
|
| 29 |
return (
|
| 30 |
<Box sx={{ mb: 6 }}>
|
| 31 |
<Box
|
|
@@ -33,7 +97,7 @@ const LeaderboardSection = ({ title, leaderboards }) => {
|
|
| 33 |
display: "flex",
|
| 34 |
alignItems: "center",
|
| 35 |
justifyContent: "space-between",
|
| 36 |
-
mb: 4,
|
| 37 |
}}
|
| 38 |
>
|
| 39 |
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
|
@@ -66,71 +130,209 @@ const LeaderboardSection = ({ title, leaderboards }) => {
|
|
| 66 |
fontSize: { xs: "1.25rem", md: "1.5rem" },
|
| 67 |
}}
|
| 68 |
>
|
| 69 |
-
{
|
| 70 |
</Typography>
|
| 71 |
</Box>
|
| 72 |
-
{
|
| 73 |
<Button
|
| 74 |
onClick={toggleExpanded}
|
| 75 |
size="small"
|
|
|
|
| 76 |
sx={{
|
| 77 |
color: "text.secondary",
|
| 78 |
fontSize: "0.875rem",
|
| 79 |
textTransform: "none",
|
|
|
|
| 80 |
"&:hover": {
|
| 81 |
backgroundColor: (theme) =>
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
| 86 |
},
|
| 87 |
}}
|
| 88 |
endIcon={
|
| 89 |
<ExpandMoreIcon
|
| 90 |
sx={{
|
| 91 |
-
transform:
|
| 92 |
transition: "transform 300ms",
|
| 93 |
}}
|
| 94 |
/>
|
| 95 |
}
|
| 96 |
>
|
| 97 |
-
{
|
| 98 |
</Button>
|
| 99 |
)}
|
| 100 |
</Box>
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
</Grid>
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
</Grid>
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
</Box>
|
| 135 |
);
|
| 136 |
};
|
|
|
|
| 1 |
+
import React, { useState, useMemo } from "react";
|
| 2 |
+
import { Typography, Grid, Box, Button, Collapse, Stack } from "@mui/material";
|
| 3 |
import { alpha } from "@mui/material/styles";
|
| 4 |
import LeaderboardCard from "./LeaderboardCard";
|
| 5 |
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
| 6 |
+
import { useLeaderboard } from "../context/LeaderboardContext";
|
| 7 |
+
import SearchOffIcon from "@mui/icons-material/SearchOff";
|
| 8 |
|
| 9 |
const ITEMS_PER_PAGE = 3;
|
| 10 |
|
| 11 |
+
const LeaderboardSection = ({
|
| 12 |
+
title,
|
| 13 |
+
leaderboards,
|
| 14 |
+
filteredLeaderboards,
|
| 15 |
+
id,
|
| 16 |
+
}) => {
|
| 17 |
+
const {
|
| 18 |
+
expandedSections,
|
| 19 |
+
setExpandedSections,
|
| 20 |
+
selectedLanguage,
|
| 21 |
+
setSelectedLanguage,
|
| 22 |
+
searchQuery,
|
| 23 |
+
selectedCategory,
|
| 24 |
+
} = useLeaderboard();
|
| 25 |
+
const isExpanded = expandedSections.has(id);
|
| 26 |
|
| 27 |
+
// Extraire la liste des langues si c'est la section Language Specific
|
| 28 |
+
// Cette liste ne doit JAMAIS changer, peu importe les filtres
|
| 29 |
+
const languages = useMemo(() => {
|
| 30 |
+
if (id !== "language") return null;
|
| 31 |
+
const langSet = new Set();
|
| 32 |
+
leaderboards.forEach((board) => {
|
| 33 |
+
board.tags?.forEach((tag) => {
|
| 34 |
+
if (tag.startsWith("language:")) {
|
| 35 |
+
const language = tag.split(":")[1];
|
| 36 |
+
const capitalizedLang =
|
| 37 |
+
language.charAt(0).toUpperCase() + language.slice(1);
|
| 38 |
+
langSet.add(capitalizedLang);
|
| 39 |
+
}
|
| 40 |
+
});
|
| 41 |
+
});
|
| 42 |
+
return Array.from(langSet).sort();
|
| 43 |
+
}, [id, leaderboards]);
|
| 44 |
+
|
| 45 |
+
// Calculer le nombre de leaderboards par langue
|
| 46 |
+
// On utilise les leaderboards bruts pour avoir toutes les langues
|
| 47 |
+
const languageStats = useMemo(() => {
|
| 48 |
+
if (!languages) return null;
|
| 49 |
+
const stats = new Map();
|
| 50 |
+
|
| 51 |
+
// Compter les leaderboards pour chaque langue
|
| 52 |
+
languages.forEach((lang) => {
|
| 53 |
+
const count = leaderboards.filter((board) =>
|
| 54 |
+
board.tags?.some(
|
| 55 |
+
(tag) => tag.toLowerCase() === `language:${lang.toLowerCase()}`
|
| 56 |
+
)
|
| 57 |
+
).length;
|
| 58 |
+
stats.set(lang, count);
|
| 59 |
+
});
|
| 60 |
+
|
| 61 |
+
return stats;
|
| 62 |
+
}, [languages, leaderboards]);
|
| 63 |
+
|
| 64 |
+
// On ne retourne null que si on n'a pas de leaderboards bruts
|
| 65 |
+
if (!leaderboards) return null;
|
| 66 |
|
| 67 |
// On affiche toujours les 3 premiers
|
| 68 |
+
const displayedLeaderboards = filteredLeaderboards.slice(0, ITEMS_PER_PAGE);
|
| 69 |
// Le reste sera dans le Collapse
|
| 70 |
+
const remainingLeaderboards = filteredLeaderboards.slice(ITEMS_PER_PAGE);
|
| 71 |
+
|
| 72 |
+
// Calculate how many skeletons we need
|
| 73 |
+
const skeletonsNeeded = Math.max(0, 3 - filteredLeaderboards.length);
|
| 74 |
+
|
| 75 |
+
// On affiche le bouton seulement si aucune catégorie n'est sélectionnée
|
| 76 |
+
const showExpandButton = !selectedCategory;
|
| 77 |
+
|
| 78 |
+
// Le bouton est actif seulement s'il y a plus de 3 leaderboards
|
| 79 |
+
const isExpandButtonEnabled = filteredLeaderboards.length > ITEMS_PER_PAGE;
|
| 80 |
|
| 81 |
const toggleExpanded = () => {
|
| 82 |
+
setExpandedSections((prev) => {
|
| 83 |
+
const newSet = new Set(prev);
|
| 84 |
+
if (isExpanded) {
|
| 85 |
+
newSet.delete(id);
|
| 86 |
+
} else {
|
| 87 |
+
newSet.add(id);
|
| 88 |
+
}
|
| 89 |
+
return newSet;
|
| 90 |
+
});
|
| 91 |
};
|
| 92 |
|
|
|
|
|
|
|
|
|
|
| 93 |
return (
|
| 94 |
<Box sx={{ mb: 6 }}>
|
| 95 |
<Box
|
|
|
|
| 97 |
display: "flex",
|
| 98 |
alignItems: "center",
|
| 99 |
justifyContent: "space-between",
|
| 100 |
+
mb: languageStats ? 2 : 4,
|
| 101 |
}}
|
| 102 |
>
|
| 103 |
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
|
|
|
| 130 |
fontSize: { xs: "1.25rem", md: "1.5rem" },
|
| 131 |
}}
|
| 132 |
>
|
| 133 |
+
{filteredLeaderboards.length}
|
| 134 |
</Typography>
|
| 135 |
</Box>
|
| 136 |
+
{showExpandButton && (
|
| 137 |
<Button
|
| 138 |
onClick={toggleExpanded}
|
| 139 |
size="small"
|
| 140 |
+
disabled={!isExpandButtonEnabled}
|
| 141 |
sx={{
|
| 142 |
color: "text.secondary",
|
| 143 |
fontSize: "0.875rem",
|
| 144 |
textTransform: "none",
|
| 145 |
+
opacity: isExpandButtonEnabled ? 1 : 0.5,
|
| 146 |
"&:hover": {
|
| 147 |
backgroundColor: (theme) =>
|
| 148 |
+
isExpandButtonEnabled
|
| 149 |
+
? alpha(
|
| 150 |
+
theme.palette.text.primary,
|
| 151 |
+
theme.palette.mode === "dark" ? 0.1 : 0.06
|
| 152 |
+
)
|
| 153 |
+
: "transparent",
|
| 154 |
},
|
| 155 |
}}
|
| 156 |
endIcon={
|
| 157 |
<ExpandMoreIcon
|
| 158 |
sx={{
|
| 159 |
+
transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
|
| 160 |
transition: "transform 300ms",
|
| 161 |
}}
|
| 162 |
/>
|
| 163 |
}
|
| 164 |
>
|
| 165 |
+
{isExpanded ? "Show less" : "Show more"}
|
| 166 |
</Button>
|
| 167 |
)}
|
| 168 |
</Box>
|
| 169 |
+
|
| 170 |
+
{languages && selectedCategory === "language" && (
|
| 171 |
+
<>
|
| 172 |
+
<Typography
|
| 173 |
+
variant="body2"
|
| 174 |
+
color="text.secondary"
|
| 175 |
+
sx={{ mb: 2, fontWeight: 500 }}
|
| 176 |
+
>
|
| 177 |
+
Languages represented in this category
|
| 178 |
+
</Typography>
|
| 179 |
+
<Box
|
| 180 |
+
sx={{
|
| 181 |
+
display: "flex",
|
| 182 |
+
flexWrap: "wrap",
|
| 183 |
+
gap: 1,
|
| 184 |
+
mb: filteredLeaderboards.length > 0 ? 4 : 2,
|
| 185 |
+
mx: -0.5,
|
| 186 |
+
}}
|
| 187 |
+
>
|
| 188 |
+
{languages.map((lang) => {
|
| 189 |
+
const isActive = selectedLanguage === lang;
|
| 190 |
+
const count = languageStats?.get(lang) || 0;
|
| 191 |
+
|
| 192 |
+
return (
|
| 193 |
+
<Button
|
| 194 |
+
key={lang}
|
| 195 |
+
onClick={() => setSelectedLanguage(isActive ? null : lang)}
|
| 196 |
+
variant={isActive ? "contained" : "outlined"}
|
| 197 |
+
size="small"
|
| 198 |
+
sx={{
|
| 199 |
+
textTransform: "none",
|
| 200 |
+
m: 0.125,
|
| 201 |
+
backgroundColor: (theme) =>
|
| 202 |
+
isActive
|
| 203 |
+
? undefined
|
| 204 |
+
: theme.palette.mode === "dark"
|
| 205 |
+
? "background.paper"
|
| 206 |
+
: "white",
|
| 207 |
+
"&:hover": {
|
| 208 |
+
backgroundColor: (theme) =>
|
| 209 |
+
isActive
|
| 210 |
+
? undefined
|
| 211 |
+
: theme.palette.mode === "dark"
|
| 212 |
+
? "background.paper"
|
| 213 |
+
: "white",
|
| 214 |
+
},
|
| 215 |
+
"& .MuiTouchRipple-root": {
|
| 216 |
+
transition: "none",
|
| 217 |
+
},
|
| 218 |
+
transition: "none",
|
| 219 |
+
}}
|
| 220 |
+
>
|
| 221 |
+
{lang}
|
| 222 |
+
<Box
|
| 223 |
+
component="span"
|
| 224 |
+
sx={{
|
| 225 |
+
display: "inline-flex",
|
| 226 |
+
alignItems: "center",
|
| 227 |
+
gap: 0.75,
|
| 228 |
+
color: isActive ? "inherit" : "text.secondary",
|
| 229 |
+
ml: 0.75,
|
| 230 |
+
}}
|
| 231 |
+
>
|
| 232 |
+
<Box
|
| 233 |
+
component="span"
|
| 234 |
+
sx={(theme) => ({
|
| 235 |
+
width: "4px",
|
| 236 |
+
height: "4px",
|
| 237 |
+
borderRadius: "100%",
|
| 238 |
+
backgroundColor: alpha(
|
| 239 |
+
theme.palette.text.primary,
|
| 240 |
+
theme.palette.mode === "dark" ? 0.2 : 0.15
|
| 241 |
+
),
|
| 242 |
+
})}
|
| 243 |
+
/>
|
| 244 |
+
{count}
|
| 245 |
+
</Box>
|
| 246 |
+
</Button>
|
| 247 |
+
);
|
| 248 |
+
})}
|
| 249 |
+
</Box>
|
| 250 |
+
</>
|
| 251 |
+
)}
|
| 252 |
+
|
| 253 |
+
{filteredLeaderboards.length === 0 ? (
|
| 254 |
+
<Box
|
| 255 |
+
sx={{
|
| 256 |
+
display: "flex",
|
| 257 |
+
flexDirection: "column",
|
| 258 |
+
alignItems: "center",
|
| 259 |
+
gap: 2,
|
| 260 |
+
py: 7,
|
| 261 |
+
bgcolor: (theme) =>
|
| 262 |
+
theme.palette.mode === "dark"
|
| 263 |
+
? "background.paper"
|
| 264 |
+
: "background.default",
|
| 265 |
+
borderRadius: 2,
|
| 266 |
+
}}
|
| 267 |
+
>
|
| 268 |
+
<SearchOffIcon
|
| 269 |
+
sx={{
|
| 270 |
+
fontSize: 64,
|
| 271 |
+
color: "text.secondary",
|
| 272 |
+
opacity: 0.5,
|
| 273 |
+
}}
|
| 274 |
+
/>
|
| 275 |
+
<Typography variant="h5" color="text.secondary" align="center">
|
| 276 |
+
{searchQuery ? (
|
| 277 |
+
<>
|
| 278 |
+
No {title.toLowerCase()} leaderboard matches{" "}
|
| 279 |
+
<Box
|
| 280 |
+
component="span"
|
| 281 |
+
sx={{
|
| 282 |
+
bgcolor: "primary.main",
|
| 283 |
+
color: "primary.contrastText",
|
| 284 |
+
px: 1,
|
| 285 |
+
borderRadius: 1,
|
| 286 |
+
}}
|
| 287 |
+
>
|
| 288 |
+
{searchQuery}
|
| 289 |
+
</Box>
|
| 290 |
+
</>
|
| 291 |
+
) : (
|
| 292 |
+
`No ${title.toLowerCase()} leaderboard matches your criteria`
|
| 293 |
+
)}
|
| 294 |
+
</Typography>
|
| 295 |
+
<Typography variant="body1" color="text.secondary" align="center">
|
| 296 |
+
Try adjusting your search filters
|
| 297 |
+
</Typography>
|
| 298 |
+
</Box>
|
| 299 |
+
) : (
|
| 300 |
+
<>
|
| 301 |
+
<Grid container spacing={3}>
|
| 302 |
+
{displayedLeaderboards.map((leaderboard, index) => (
|
| 303 |
+
<Grid item xs={12} sm={6} md={4} key={index}>
|
| 304 |
+
<LeaderboardCard leaderboard={leaderboard} />
|
| 305 |
+
</Grid>
|
| 306 |
+
))}
|
| 307 |
+
{/* Add skeletons if needed */}
|
| 308 |
+
{Array.from({ length: skeletonsNeeded }).map((_, index) => (
|
| 309 |
+
<Grid item xs={12} sm={6} md={4} key={`skeleton-${index}`}>
|
| 310 |
+
<Box
|
| 311 |
+
sx={{
|
| 312 |
+
height: "180px",
|
| 313 |
+
borderRadius: 2,
|
| 314 |
+
bgcolor: (theme) => alpha(theme.palette.primary.main, 0.15),
|
| 315 |
+
opacity: 1,
|
| 316 |
+
transition: "opacity 0.3s ease-in-out",
|
| 317 |
+
"&:hover": {
|
| 318 |
+
opacity: 0.8,
|
| 319 |
+
},
|
| 320 |
+
}}
|
| 321 |
+
/>
|
| 322 |
+
</Grid>
|
| 323 |
+
))}
|
| 324 |
</Grid>
|
| 325 |
+
<Collapse in={isExpanded} timeout={300} unmountOnExit>
|
| 326 |
+
<Grid container spacing={3} sx={{ mt: 0 }}>
|
| 327 |
+
{remainingLeaderboards.map((leaderboard, index) => (
|
| 328 |
+
<Grid item xs={12} sm={6} md={4} key={index + ITEMS_PER_PAGE}>
|
| 329 |
+
<LeaderboardCard leaderboard={leaderboard} />
|
| 330 |
+
</Grid>
|
| 331 |
+
))}
|
| 332 |
</Grid>
|
| 333 |
+
</Collapse>
|
| 334 |
+
</>
|
| 335 |
+
)}
|
| 336 |
</Box>
|
| 337 |
);
|
| 338 |
};
|
client/src/components/Logo/Logo.jsx
CHANGED
|
@@ -3,10 +3,33 @@ import { Link } from "react-router-dom";
|
|
| 3 |
import { Box } from "@mui/material";
|
| 4 |
import logoText from "../../assets/logo-text.svg";
|
| 5 |
import gradient from "../../assets/gradient.svg";
|
|
|
|
| 6 |
|
| 7 |
const Logo = () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
return (
|
| 9 |
-
<Link
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
<Box
|
| 11 |
component="img"
|
| 12 |
src={gradient}
|
|
|
|
| 3 |
import { Box } from "@mui/material";
|
| 4 |
import logoText from "../../assets/logo-text.svg";
|
| 5 |
import gradient from "../../assets/gradient.svg";
|
| 6 |
+
import { useLeaderboard } from "../../context/LeaderboardContext";
|
| 7 |
|
| 8 |
const Logo = () => {
|
| 9 |
+
const {
|
| 10 |
+
setSearchQuery,
|
| 11 |
+
setArenaOnly,
|
| 12 |
+
setSelectedCategory,
|
| 13 |
+
setSelectedLanguage,
|
| 14 |
+
setExpandedSections,
|
| 15 |
+
} = useLeaderboard();
|
| 16 |
+
|
| 17 |
+
const handleReset = (e) => {
|
| 18 |
+
e.preventDefault();
|
| 19 |
+
setSearchQuery("");
|
| 20 |
+
setArenaOnly(false);
|
| 21 |
+
setSelectedCategory(null);
|
| 22 |
+
setSelectedLanguage(null);
|
| 23 |
+
setExpandedSections(new Set());
|
| 24 |
+
window.history.pushState({}, "", window.location.pathname);
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
return (
|
| 28 |
+
<Link
|
| 29 |
+
to="/"
|
| 30 |
+
onClick={handleReset}
|
| 31 |
+
style={{ textDecoration: "none", position: "relative" }}
|
| 32 |
+
>
|
| 33 |
<Box
|
| 34 |
component="img"
|
| 35 |
src={gradient}
|
client/src/context/LeaderboardContext.jsx
CHANGED
|
@@ -4,14 +4,218 @@ import React, {
|
|
| 4 |
useState,
|
| 5 |
useCallback,
|
| 6 |
useMemo,
|
|
|
|
| 7 |
} from "react";
|
| 8 |
|
| 9 |
const LeaderboardContext = createContext();
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
export const LeaderboardProvider = ({ children }) => {
|
|
|
|
|
|
|
|
|
|
| 12 |
const [leaderboards, setLeaderboards] = useState([]);
|
| 13 |
-
const [searchQuery, setSearchQuery] = useState("");
|
| 14 |
-
const [arenaOnly, setArenaOnly] = useState(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
// Calculate total number of unique leaderboards (excluding duplicates)
|
| 17 |
const totalLeaderboards = useMemo(() => {
|
|
@@ -57,60 +261,10 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 57 |
);
|
| 58 |
}, []);
|
| 59 |
|
| 60 |
-
//
|
| 61 |
-
const filterLeaderboards = useCallback(
|
| 62 |
-
(boards) => {
|
| 63 |
-
if (!boards) return [];
|
| 64 |
-
|
| 65 |
-
// On évite de créer une copie inutile
|
| 66 |
-
let filtered = boards;
|
| 67 |
-
|
| 68 |
-
// Filter by search query
|
| 69 |
-
if (searchQuery) {
|
| 70 |
-
const query = searchQuery.toLowerCase();
|
| 71 |
-
const searchableTagPrefixes = [
|
| 72 |
-
"domain:",
|
| 73 |
-
"language:",
|
| 74 |
-
"judge:",
|
| 75 |
-
"test:",
|
| 76 |
-
"modality:",
|
| 77 |
-
"submission:",
|
| 78 |
-
"domain:",
|
| 79 |
-
"eval:",
|
| 80 |
-
];
|
| 81 |
-
|
| 82 |
-
filtered = filtered.filter((board) => {
|
| 83 |
-
const isTagSearch = searchableTagPrefixes.some((prefix) =>
|
| 84 |
-
query.startsWith(prefix)
|
| 85 |
-
);
|
| 86 |
-
|
| 87 |
-
if (isTagSearch) {
|
| 88 |
-
return board.tags?.some((tag) => tag.toLowerCase().includes(query));
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
return board.card_data?.title?.toLowerCase().includes(query);
|
| 92 |
-
});
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
// Filter arena only
|
| 96 |
-
if (arenaOnly) {
|
| 97 |
-
filtered = filtered.filter((board) =>
|
| 98 |
-
board.tags?.includes("judge:humans")
|
| 99 |
-
);
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
return filtered;
|
| 103 |
-
},
|
| 104 |
-
[searchQuery, arenaOnly]
|
| 105 |
-
);
|
| 106 |
-
|
| 107 |
-
// Define sections
|
| 108 |
const allSections = useMemo(() => {
|
| 109 |
if (!leaderboards) return [];
|
| 110 |
|
| 111 |
-
// D'abord filtrer les leaderboards selon la recherche
|
| 112 |
-
const filteredBoards = filterLeaderboards(leaderboards);
|
| 113 |
-
|
| 114 |
// Garder une trace des leaderboards déjà catégorisés
|
| 115 |
const categorizedIds = new Set();
|
| 116 |
|
|
@@ -118,7 +272,7 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 118 |
{
|
| 119 |
id: "agentic",
|
| 120 |
title: "Agentic",
|
| 121 |
-
data: filterByTag("modality:agent",
|
| 122 |
categorizedIds.add(board.id);
|
| 123 |
return board;
|
| 124 |
}),
|
|
@@ -126,7 +280,7 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 126 |
{
|
| 127 |
id: "code",
|
| 128 |
title: "Code",
|
| 129 |
-
data: filterByTag("eval:code",
|
| 130 |
categorizedIds.add(board.id);
|
| 131 |
return board;
|
| 132 |
}),
|
|
@@ -134,7 +288,7 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 134 |
{
|
| 135 |
id: "math",
|
| 136 |
title: "Math",
|
| 137 |
-
data: filterByTag("eval:math",
|
| 138 |
categorizedIds.add(board.id);
|
| 139 |
return board;
|
| 140 |
}),
|
|
@@ -142,7 +296,7 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 142 |
{
|
| 143 |
id: "language",
|
| 144 |
title: "Language Specific",
|
| 145 |
-
data: filterByLanguage(
|
| 146 |
categorizedIds.add(board.id);
|
| 147 |
return board;
|
| 148 |
}),
|
|
@@ -150,7 +304,7 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 150 |
{
|
| 151 |
id: "vision",
|
| 152 |
title: "Vision",
|
| 153 |
-
data: filterByVision(
|
| 154 |
categorizedIds.add(board.id);
|
| 155 |
return board;
|
| 156 |
}),
|
|
@@ -158,7 +312,7 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 158 |
{
|
| 159 |
id: "audio",
|
| 160 |
title: "Audio",
|
| 161 |
-
data: filterByTag("modality:audio",
|
| 162 |
categorizedIds.add(board.id);
|
| 163 |
return board;
|
| 164 |
}),
|
|
@@ -166,7 +320,7 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 166 |
{
|
| 167 |
id: "financial",
|
| 168 |
title: "Financial",
|
| 169 |
-
data: filterByTag("domain:financial",
|
| 170 |
categorizedIds.add(board.id);
|
| 171 |
return board;
|
| 172 |
}),
|
|
@@ -174,7 +328,7 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 174 |
{
|
| 175 |
id: "medical",
|
| 176 |
title: "Medical",
|
| 177 |
-
data: filterByTag("domain:medical",
|
| 178 |
categorizedIds.add(board.id);
|
| 179 |
return board;
|
| 180 |
}),
|
|
@@ -182,7 +336,7 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 182 |
{
|
| 183 |
id: "legal",
|
| 184 |
title: "Legal",
|
| 185 |
-
data: filterByTag("domain:legal",
|
| 186 |
categorizedIds.add(board.id);
|
| 187 |
return board;
|
| 188 |
}),
|
|
@@ -190,7 +344,7 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 190 |
{
|
| 191 |
id: "safety",
|
| 192 |
title: "Safety",
|
| 193 |
-
data: filterByTag("eval:safety",
|
| 194 |
categorizedIds.add(board.id);
|
| 195 |
return board;
|
| 196 |
}),
|
|
@@ -199,18 +353,12 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 199 |
id: "uncategorized",
|
| 200 |
title: "Uncategorized",
|
| 201 |
// Mettre dans uncategorized uniquement les leaderboards qui n'apparaissent nulle part ailleurs
|
| 202 |
-
data:
|
| 203 |
},
|
| 204 |
];
|
| 205 |
|
| 206 |
return sections;
|
| 207 |
-
}, [
|
| 208 |
-
leaderboards,
|
| 209 |
-
filterLeaderboards,
|
| 210 |
-
filterByTag,
|
| 211 |
-
filterByLanguage,
|
| 212 |
-
filterByVision,
|
| 213 |
-
]);
|
| 214 |
|
| 215 |
// Get sections with data
|
| 216 |
const sections = useMemo(() => {
|
|
@@ -259,8 +407,8 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 259 |
|
| 260 |
// Get filtered count
|
| 261 |
const filteredCount = useMemo(() => {
|
| 262 |
-
return
|
| 263 |
-
}, [
|
| 264 |
|
| 265 |
// Function to get highlighted parts of text
|
| 266 |
const getHighlightedText = useCallback((text, searchTerm) => {
|
|
@@ -303,9 +451,18 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 303 |
totalLeaderboards,
|
| 304 |
filteredCount,
|
| 305 |
filterLeaderboards,
|
|
|
|
| 306 |
sections,
|
| 307 |
allSections,
|
| 308 |
getHighlightedText,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
};
|
| 310 |
|
| 311 |
return (
|
|
|
|
| 4 |
useState,
|
| 5 |
useCallback,
|
| 6 |
useMemo,
|
| 7 |
+
useEffect,
|
| 8 |
} from "react";
|
| 9 |
|
| 10 |
const LeaderboardContext = createContext();
|
| 11 |
|
| 12 |
+
// Helper pour mettre à jour l'URL
|
| 13 |
+
const updateURL = (params) => {
|
| 14 |
+
const url = new URL(window.location);
|
| 15 |
+
Object.entries(params).forEach(([key, value]) => {
|
| 16 |
+
if (value) {
|
| 17 |
+
url.searchParams.set(key, value);
|
| 18 |
+
} else {
|
| 19 |
+
url.searchParams.delete(key);
|
| 20 |
+
}
|
| 21 |
+
});
|
| 22 |
+
window.history.pushState({}, "", url);
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
// Helper pour lire les paramètres de l'URL
|
| 26 |
+
const getURLParams = () => {
|
| 27 |
+
const params = new URLSearchParams(window.location.search);
|
| 28 |
+
return {
|
| 29 |
+
category: params.get("category"),
|
| 30 |
+
search: params.get("search"),
|
| 31 |
+
arena: params.get("arena") === "true",
|
| 32 |
+
language: params.get("language"),
|
| 33 |
+
};
|
| 34 |
+
};
|
| 35 |
+
|
| 36 |
export const LeaderboardProvider = ({ children }) => {
|
| 37 |
+
// Lire les paramètres initiaux depuis l'URL
|
| 38 |
+
const initialParams = getURLParams();
|
| 39 |
+
|
| 40 |
const [leaderboards, setLeaderboards] = useState([]);
|
| 41 |
+
const [searchQuery, setSearchQuery] = useState(initialParams.search || "");
|
| 42 |
+
const [arenaOnly, setArenaOnly] = useState(initialParams.arena);
|
| 43 |
+
const [selectedCategory, setSelectedCategory] = useState(
|
| 44 |
+
initialParams.category
|
| 45 |
+
);
|
| 46 |
+
const [selectedLanguage, setSelectedLanguage] = useState(
|
| 47 |
+
initialParams.language
|
| 48 |
+
);
|
| 49 |
+
const [expandedSections, setExpandedSections] = useState(new Set());
|
| 50 |
+
|
| 51 |
+
// Mettre à jour l'URL quand les filtres changent
|
| 52 |
+
useEffect(() => {
|
| 53 |
+
updateURL({
|
| 54 |
+
category: selectedCategory,
|
| 55 |
+
search: searchQuery,
|
| 56 |
+
arena: arenaOnly ? "true" : null,
|
| 57 |
+
language: selectedLanguage,
|
| 58 |
+
});
|
| 59 |
+
}, [selectedCategory, searchQuery, arenaOnly, selectedLanguage]);
|
| 60 |
+
|
| 61 |
+
// Écouter les changements d'URL (navigation back/forward)
|
| 62 |
+
useEffect(() => {
|
| 63 |
+
const handleURLChange = () => {
|
| 64 |
+
const params = getURLParams();
|
| 65 |
+
setSearchQuery(params.search || "");
|
| 66 |
+
setArenaOnly(params.arena);
|
| 67 |
+
setSelectedCategory(params.category);
|
| 68 |
+
setSelectedLanguage(params.language);
|
| 69 |
+
};
|
| 70 |
+
|
| 71 |
+
window.addEventListener("popstate", handleURLChange);
|
| 72 |
+
return () => window.removeEventListener("popstate", handleURLChange);
|
| 73 |
+
}, []);
|
| 74 |
+
|
| 75 |
+
// Wrapper pour setSelectedCategory qui gère aussi l'expansion des sections
|
| 76 |
+
const handleCategorySelection = useCallback((categoryId) => {
|
| 77 |
+
// On réinitialise toujours la langue sélectionnée
|
| 78 |
+
setSelectedLanguage(null);
|
| 79 |
+
|
| 80 |
+
setSelectedCategory((prev) => {
|
| 81 |
+
if (prev === categoryId) {
|
| 82 |
+
// Si on désélectionne, on replie toutes les sections
|
| 83 |
+
setExpandedSections(new Set());
|
| 84 |
+
return null;
|
| 85 |
+
}
|
| 86 |
+
// Si on sélectionne une nouvelle catégorie, on la déploie
|
| 87 |
+
setExpandedSections(new Set([categoryId]));
|
| 88 |
+
return categoryId;
|
| 89 |
+
});
|
| 90 |
+
}, []);
|
| 91 |
+
|
| 92 |
+
// Wrapper pour la sélection de langue
|
| 93 |
+
const handleLanguageSelection = useCallback((language) => {
|
| 94 |
+
setSelectedLanguage((prev) => (prev === language ? null : language));
|
| 95 |
+
}, []);
|
| 96 |
+
|
| 97 |
+
// Filter leaderboards based on search query and arena toggle, ignoring category selection
|
| 98 |
+
const filterLeaderboardsForCount = useCallback(
|
| 99 |
+
(boards) => {
|
| 100 |
+
if (!boards) return [];
|
| 101 |
+
|
| 102 |
+
let filtered = [...boards];
|
| 103 |
+
|
| 104 |
+
// Filter by search query (text only)
|
| 105 |
+
if (searchQuery) {
|
| 106 |
+
const query = searchQuery.toLowerCase();
|
| 107 |
+
filtered = filtered.filter((board) =>
|
| 108 |
+
board.card_data?.title?.toLowerCase().includes(query)
|
| 109 |
+
);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
// Filter arena only
|
| 113 |
+
if (arenaOnly) {
|
| 114 |
+
filtered = filtered.filter((board) =>
|
| 115 |
+
board.tags?.includes("judge:humans")
|
| 116 |
+
);
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
return filtered;
|
| 120 |
+
},
|
| 121 |
+
[searchQuery, arenaOnly]
|
| 122 |
+
);
|
| 123 |
+
|
| 124 |
+
// Filter leaderboards based on all criteria including category and language selection
|
| 125 |
+
const filterLeaderboards = useCallback(
|
| 126 |
+
(boards) => {
|
| 127 |
+
if (!boards) return [];
|
| 128 |
+
|
| 129 |
+
let filtered = filterLeaderboardsForCount(boards);
|
| 130 |
+
|
| 131 |
+
// Filter by selected language if any
|
| 132 |
+
if (selectedLanguage) {
|
| 133 |
+
filtered = filtered.filter((board) =>
|
| 134 |
+
board.tags?.some(
|
| 135 |
+
(tag) =>
|
| 136 |
+
tag.toLowerCase() === `language:${selectedLanguage.toLowerCase()}`
|
| 137 |
+
)
|
| 138 |
+
);
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
// Filter by selected category if any
|
| 142 |
+
if (selectedCategory) {
|
| 143 |
+
filtered = filtered.filter((board) => {
|
| 144 |
+
const { tags = [] } = board;
|
| 145 |
+
switch (selectedCategory) {
|
| 146 |
+
case "agentic":
|
| 147 |
+
return tags.includes("modality:agent");
|
| 148 |
+
case "code":
|
| 149 |
+
return tags.includes("eval:code");
|
| 150 |
+
case "math":
|
| 151 |
+
return tags.includes("eval:math");
|
| 152 |
+
case "language":
|
| 153 |
+
return tags.some((tag) => tag.startsWith("language:"));
|
| 154 |
+
case "vision":
|
| 155 |
+
return tags.some(
|
| 156 |
+
(tag) => tag === "modality:video" || tag === "modality:image"
|
| 157 |
+
);
|
| 158 |
+
case "audio":
|
| 159 |
+
return tags.includes("modality:audio");
|
| 160 |
+
case "financial":
|
| 161 |
+
return tags.includes("domain:financial");
|
| 162 |
+
case "medical":
|
| 163 |
+
return tags.includes("domain:medical");
|
| 164 |
+
case "legal":
|
| 165 |
+
return tags.includes("domain:legal");
|
| 166 |
+
case "safety":
|
| 167 |
+
return tags.includes("eval:safety");
|
| 168 |
+
case "uncategorized":
|
| 169 |
+
return !tags.some(
|
| 170 |
+
(tag) =>
|
| 171 |
+
[
|
| 172 |
+
"modality:agent",
|
| 173 |
+
"eval:code",
|
| 174 |
+
"eval:math",
|
| 175 |
+
"modality:video",
|
| 176 |
+
"modality:image",
|
| 177 |
+
"modality:audio",
|
| 178 |
+
"domain:financial",
|
| 179 |
+
"domain:medical",
|
| 180 |
+
"domain:legal",
|
| 181 |
+
"eval:safety",
|
| 182 |
+
].includes(tag) || tag.startsWith("language:")
|
| 183 |
+
);
|
| 184 |
+
default:
|
| 185 |
+
return true;
|
| 186 |
+
}
|
| 187 |
+
});
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
return filtered;
|
| 191 |
+
},
|
| 192 |
+
[filterLeaderboardsForCount, selectedCategory, selectedLanguage]
|
| 193 |
+
);
|
| 194 |
+
|
| 195 |
+
// Fonction pour obtenir les leaderboards bruts d'une section
|
| 196 |
+
const getSectionLeaderboards = useCallback((boards) => {
|
| 197 |
+
if (!boards) return [];
|
| 198 |
+
return [...boards];
|
| 199 |
+
}, []);
|
| 200 |
+
|
| 201 |
+
// Fonction pour compter les leaderboards par langue
|
| 202 |
+
const getLanguageStats = useCallback((boards) => {
|
| 203 |
+
const stats = new Map();
|
| 204 |
+
|
| 205 |
+
boards.forEach((board) => {
|
| 206 |
+
board.tags?.forEach((tag) => {
|
| 207 |
+
if (tag.startsWith("language:")) {
|
| 208 |
+
const language = tag.split(":")[1];
|
| 209 |
+
const capitalizedLang =
|
| 210 |
+
language.charAt(0).toUpperCase() + language.slice(1);
|
| 211 |
+
const count = stats.get(capitalizedLang) || 0;
|
| 212 |
+
stats.set(capitalizedLang, count + 1);
|
| 213 |
+
}
|
| 214 |
+
});
|
| 215 |
+
});
|
| 216 |
+
|
| 217 |
+
return stats;
|
| 218 |
+
}, []);
|
| 219 |
|
| 220 |
// Calculate total number of unique leaderboards (excluding duplicates)
|
| 221 |
const totalLeaderboards = useMemo(() => {
|
|
|
|
| 261 |
);
|
| 262 |
}, []);
|
| 263 |
|
| 264 |
+
// Define sections with raw data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
const allSections = useMemo(() => {
|
| 266 |
if (!leaderboards) return [];
|
| 267 |
|
|
|
|
|
|
|
|
|
|
| 268 |
// Garder une trace des leaderboards déjà catégorisés
|
| 269 |
const categorizedIds = new Set();
|
| 270 |
|
|
|
|
| 272 |
{
|
| 273 |
id: "agentic",
|
| 274 |
title: "Agentic",
|
| 275 |
+
data: filterByTag("modality:agent", leaderboards).map((board) => {
|
| 276 |
categorizedIds.add(board.id);
|
| 277 |
return board;
|
| 278 |
}),
|
|
|
|
| 280 |
{
|
| 281 |
id: "code",
|
| 282 |
title: "Code",
|
| 283 |
+
data: filterByTag("eval:code", leaderboards).map((board) => {
|
| 284 |
categorizedIds.add(board.id);
|
| 285 |
return board;
|
| 286 |
}),
|
|
|
|
| 288 |
{
|
| 289 |
id: "math",
|
| 290 |
title: "Math",
|
| 291 |
+
data: filterByTag("eval:math", leaderboards).map((board) => {
|
| 292 |
categorizedIds.add(board.id);
|
| 293 |
return board;
|
| 294 |
}),
|
|
|
|
| 296 |
{
|
| 297 |
id: "language",
|
| 298 |
title: "Language Specific",
|
| 299 |
+
data: filterByLanguage(leaderboards).map((board) => {
|
| 300 |
categorizedIds.add(board.id);
|
| 301 |
return board;
|
| 302 |
}),
|
|
|
|
| 304 |
{
|
| 305 |
id: "vision",
|
| 306 |
title: "Vision",
|
| 307 |
+
data: filterByVision(leaderboards).map((board) => {
|
| 308 |
categorizedIds.add(board.id);
|
| 309 |
return board;
|
| 310 |
}),
|
|
|
|
| 312 |
{
|
| 313 |
id: "audio",
|
| 314 |
title: "Audio",
|
| 315 |
+
data: filterByTag("modality:audio", leaderboards).map((board) => {
|
| 316 |
categorizedIds.add(board.id);
|
| 317 |
return board;
|
| 318 |
}),
|
|
|
|
| 320 |
{
|
| 321 |
id: "financial",
|
| 322 |
title: "Financial",
|
| 323 |
+
data: filterByTag("domain:financial", leaderboards).map((board) => {
|
| 324 |
categorizedIds.add(board.id);
|
| 325 |
return board;
|
| 326 |
}),
|
|
|
|
| 328 |
{
|
| 329 |
id: "medical",
|
| 330 |
title: "Medical",
|
| 331 |
+
data: filterByTag("domain:medical", leaderboards).map((board) => {
|
| 332 |
categorizedIds.add(board.id);
|
| 333 |
return board;
|
| 334 |
}),
|
|
|
|
| 336 |
{
|
| 337 |
id: "legal",
|
| 338 |
title: "Legal",
|
| 339 |
+
data: filterByTag("domain:legal", leaderboards).map((board) => {
|
| 340 |
categorizedIds.add(board.id);
|
| 341 |
return board;
|
| 342 |
}),
|
|
|
|
| 344 |
{
|
| 345 |
id: "safety",
|
| 346 |
title: "Safety",
|
| 347 |
+
data: filterByTag("eval:safety", leaderboards).map((board) => {
|
| 348 |
categorizedIds.add(board.id);
|
| 349 |
return board;
|
| 350 |
}),
|
|
|
|
| 353 |
id: "uncategorized",
|
| 354 |
title: "Uncategorized",
|
| 355 |
// Mettre dans uncategorized uniquement les leaderboards qui n'apparaissent nulle part ailleurs
|
| 356 |
+
data: leaderboards.filter((board) => !categorizedIds.has(board.id)),
|
| 357 |
},
|
| 358 |
];
|
| 359 |
|
| 360 |
return sections;
|
| 361 |
+
}, [leaderboards, filterByTag, filterByLanguage, filterByVision]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
|
| 363 |
// Get sections with data
|
| 364 |
const sections = useMemo(() => {
|
|
|
|
| 407 |
|
| 408 |
// Get filtered count
|
| 409 |
const filteredCount = useMemo(() => {
|
| 410 |
+
return filterLeaderboardsForCount(leaderboards).length;
|
| 411 |
+
}, [filterLeaderboardsForCount, leaderboards]);
|
| 412 |
|
| 413 |
// Function to get highlighted parts of text
|
| 414 |
const getHighlightedText = useCallback((text, searchTerm) => {
|
|
|
|
| 451 |
totalLeaderboards,
|
| 452 |
filteredCount,
|
| 453 |
filterLeaderboards,
|
| 454 |
+
filterLeaderboardsForCount,
|
| 455 |
sections,
|
| 456 |
allSections,
|
| 457 |
getHighlightedText,
|
| 458 |
+
selectedCategory,
|
| 459 |
+
setSelectedCategory: handleCategorySelection,
|
| 460 |
+
selectedLanguage,
|
| 461 |
+
setSelectedLanguage: handleLanguageSelection,
|
| 462 |
+
expandedSections,
|
| 463 |
+
setExpandedSections,
|
| 464 |
+
getLanguageStats,
|
| 465 |
+
getSectionLeaderboards,
|
| 466 |
};
|
| 467 |
|
| 468 |
return (
|
client/src/pages/HowToSubmitPage/HowToSubmitPage.jsx
CHANGED
|
@@ -205,7 +205,7 @@ const getTagEmoji = (tag) => {
|
|
| 205 |
function: "⚙️",
|
| 206 |
model: "🧠",
|
| 207 |
humans: "👥",
|
| 208 |
-
|
| 209 |
},
|
| 210 |
modality: {
|
| 211 |
text: "📝",
|
|
@@ -228,7 +228,7 @@ const getTagEmoji = (tag) => {
|
|
| 228 |
language: {
|
| 229 |
english: "🇬🇧",
|
| 230 |
french: "🇫🇷",
|
| 231 |
-
|
| 232 |
},
|
| 233 |
domain: {
|
| 234 |
financial: "💰",
|
|
@@ -645,7 +645,7 @@ const HowToSubmitPage = () => {
|
|
| 645 |
"judge:function",
|
| 646 |
"judge:model",
|
| 647 |
"judge:humans",
|
| 648 |
-
"judge:
|
| 649 |
]}
|
| 650 |
explanations={[
|
| 651 |
"evaluations are run <strong>automatically</strong>, using an evaluation suite such as <strong>lm_eval</strong> or <strong>lighteval</strong>",
|
|
@@ -685,7 +685,7 @@ const HowToSubmitPage = () => {
|
|
| 685 |
"eval:code",
|
| 686 |
"eval:performance",
|
| 687 |
"eval:safety",
|
| 688 |
-
"
|
| 689 |
]}
|
| 690 |
explanations={[
|
| 691 |
"the evaluation looks at <strong>generation capabilities</strong> specifically (can be image generation, text generation, ...)",
|
|
@@ -700,7 +700,11 @@ const HowToSubmitPage = () => {
|
|
| 700 |
<TagSection
|
| 701 |
title="Language"
|
| 702 |
description="You can indicate the languages covered by your benchmark like so: language:mylanguage."
|
| 703 |
-
tags={[
|
|
|
|
|
|
|
|
|
|
|
|
|
| 704 |
explanations={[
|
| 705 |
"",
|
| 706 |
"",
|
|
@@ -737,7 +741,7 @@ const HowToSubmitPage = () => {
|
|
| 737 |
},
|
| 738 |
}}
|
| 739 |
>
|
| 740 |
-
|
| 741 |
</Link>{" "}
|
| 742 |
on Hugging Face.
|
| 743 |
</Typography>
|
|
|
|
| 205 |
function: "⚙️",
|
| 206 |
model: "🧠",
|
| 207 |
humans: "👥",
|
| 208 |
+
vibeCheck: "✨",
|
| 209 |
},
|
| 210 |
modality: {
|
| 211 |
text: "📝",
|
|
|
|
| 228 |
language: {
|
| 229 |
english: "🇬🇧",
|
| 230 |
french: "🇫🇷",
|
| 231 |
+
yourOwnLanguage: "🌍",
|
| 232 |
},
|
| 233 |
domain: {
|
| 234 |
financial: "💰",
|
|
|
|
| 645 |
"judge:function",
|
| 646 |
"judge:model",
|
| 647 |
"judge:humans",
|
| 648 |
+
"judge:vibe check",
|
| 649 |
]}
|
| 650 |
explanations={[
|
| 651 |
"evaluations are run <strong>automatically</strong>, using an evaluation suite such as <strong>lm_eval</strong> or <strong>lighteval</strong>",
|
|
|
|
| 685 |
"eval:code",
|
| 686 |
"eval:performance",
|
| 687 |
"eval:safety",
|
| 688 |
+
"eval:rag",
|
| 689 |
]}
|
| 690 |
explanations={[
|
| 691 |
"the evaluation looks at <strong>generation capabilities</strong> specifically (can be image generation, text generation, ...)",
|
|
|
|
| 700 |
<TagSection
|
| 701 |
title="Language"
|
| 702 |
description="You can indicate the languages covered by your benchmark like so: language:mylanguage."
|
| 703 |
+
tags={[
|
| 704 |
+
"language:english",
|
| 705 |
+
"language:french",
|
| 706 |
+
"language:your own language",
|
| 707 |
+
]}
|
| 708 |
explanations={[
|
| 709 |
"",
|
| 710 |
"",
|
|
|
|
| 741 |
},
|
| 742 |
}}
|
| 743 |
>
|
| 744 |
+
Clémentine Fourrier
|
| 745 |
</Link>{" "}
|
| 746 |
on Hugging Face.
|
| 747 |
</Typography>
|
client/src/pages/LeaderboardPage/LeaderboardPage.jsx
CHANGED
|
@@ -20,6 +20,7 @@ const LeaderboardPageContent = () => {
|
|
| 20 |
allSections,
|
| 21 |
searchQuery,
|
| 22 |
arenaOnly,
|
|
|
|
| 23 |
} = useLeaderboard();
|
| 24 |
|
| 25 |
useEffect(() => {
|
|
@@ -96,7 +97,8 @@ const LeaderboardPageContent = () => {
|
|
| 96 |
>
|
| 97 |
<LeaderboardFilters allSections={allSections} />
|
| 98 |
|
| 99 |
-
{
|
|
|
|
| 100 |
<Box
|
| 101 |
sx={{
|
| 102 |
display: "flex",
|
|
@@ -140,19 +142,38 @@ const LeaderboardPageContent = () => {
|
|
| 140 |
</Box>
|
| 141 |
)}
|
| 142 |
|
| 143 |
-
{
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
</Box>
|
| 157 |
)}
|
| 158 |
</Box>
|
|
|
|
| 20 |
allSections,
|
| 21 |
searchQuery,
|
| 22 |
arenaOnly,
|
| 23 |
+
selectedCategory,
|
| 24 |
} = useLeaderboard();
|
| 25 |
|
| 26 |
useEffect(() => {
|
|
|
|
| 97 |
>
|
| 98 |
<LeaderboardFilters allSections={allSections} />
|
| 99 |
|
| 100 |
+
{/* Message global "No results" seulement si on n'a pas de catégorie sélectionnée */}
|
| 101 |
+
{!hasLeaderboards && isFiltering && !selectedCategory && (
|
| 102 |
<Box
|
| 103 |
sx={{
|
| 104 |
display: "flex",
|
|
|
|
| 142 |
</Box>
|
| 143 |
)}
|
| 144 |
|
| 145 |
+
{/* On affiche toujours la section sélectionnée, sinon on affiche les sections avec des résultats */}
|
| 146 |
+
{selectedCategory
|
| 147 |
+
? sections
|
| 148 |
+
.filter(({ id }) => id === selectedCategory)
|
| 149 |
+
.map(({ id, title, data }) => {
|
| 150 |
+
const filteredLeaderboards = filterLeaderboards(data);
|
| 151 |
+
return (
|
| 152 |
+
<Box key={id} id={id}>
|
| 153 |
+
<LeaderboardSection
|
| 154 |
+
id={id}
|
| 155 |
+
title={title}
|
| 156 |
+
leaderboards={data}
|
| 157 |
+
filteredLeaderboards={filteredLeaderboards}
|
| 158 |
+
/>
|
| 159 |
+
</Box>
|
| 160 |
+
);
|
| 161 |
+
})
|
| 162 |
+
: (hasLeaderboards || !isFiltering) &&
|
| 163 |
+
sections.map(({ id, title, data }) => {
|
| 164 |
+
const filteredLeaderboards = filterLeaderboards(data);
|
| 165 |
+
if (filteredLeaderboards.length === 0) return null;
|
| 166 |
+
return (
|
| 167 |
+
<Box key={id} id={id}>
|
| 168 |
+
<LeaderboardSection
|
| 169 |
+
id={id}
|
| 170 |
+
title={title}
|
| 171 |
+
leaderboards={data}
|
| 172 |
+
filteredLeaderboards={filteredLeaderboards}
|
| 173 |
+
/>
|
| 174 |
+
</Box>
|
| 175 |
+
);
|
| 176 |
+
})}
|
| 177 |
</Box>
|
| 178 |
)}
|
| 179 |
</Box>
|
server/pyproject.toml
CHANGED
|
@@ -3,6 +3,9 @@ name = "leaderboard-explorer-server"
|
|
| 3 |
version = "0.1.0"
|
| 4 |
description = "Backend server for Leaderboard Explorer"
|
| 5 |
authors = ["Your Name <[email protected]>"]
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
[tool.poetry.dependencies]
|
| 8 |
python = "^3.9"
|
|
@@ -14,6 +17,9 @@ python-jose = {extras = ["cryptography"], version = "^3.3.0"}
|
|
| 14 |
apscheduler = "^3.10.4"
|
| 15 |
huggingface-hub = "^0.21.3"
|
| 16 |
|
|
|
|
|
|
|
|
|
|
| 17 |
[build-system]
|
| 18 |
requires = ["poetry-core>=1.0.0"]
|
| 19 |
build-backend = "poetry.core.masonry.api"
|
|
|
|
| 3 |
version = "0.1.0"
|
| 4 |
description = "Backend server for Leaderboard Explorer"
|
| 5 |
authors = ["Your Name <[email protected]>"]
|
| 6 |
+
packages = [
|
| 7 |
+
{ include = "server.py" }
|
| 8 |
+
]
|
| 9 |
|
| 10 |
[tool.poetry.dependencies]
|
| 11 |
python = "^3.9"
|
|
|
|
| 17 |
apscheduler = "^3.10.4"
|
| 18 |
huggingface-hub = "^0.21.3"
|
| 19 |
|
| 20 |
+
[tool.poetry.scripts]
|
| 21 |
+
dev = "uvicorn server:app --reload --host 0.0.0.0 --port 3002"
|
| 22 |
+
|
| 23 |
[build-system]
|
| 24 |
requires = ["poetry-core>=1.0.0"]
|
| 25 |
build-backend = "poetry.core.masonry.api"
|