tfrere commited on
Commit
81702ec
·
1 Parent(s): 7154898

udpate filters | url handling | searchbar and languages

Browse files
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 total arena count when toggle changes
40
  React.useEffect(() => {
41
- if (arenaOnly) {
42
- const arenaCount = leaderboards.filter((board) =>
43
- board.tags?.includes("judge:humans")
44
- ).length;
45
- setTotalArenaCount(arenaCount);
46
- }
47
- }, [arenaOnly, leaderboards]);
 
 
 
 
 
 
 
 
 
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 = filterLeaderboards(data).length;
 
 
 
 
 
75
  return (
76
- <Button
77
- key={id}
78
- onClick={() => {
79
- if (filteredCount > 0) {
80
- document.getElementById(id)?.scrollIntoView({
81
- behavior: "smooth",
82
- block: "start",
83
- });
84
- }
85
- }}
86
- variant="outlined"
87
- size="small"
88
- disabled={filteredCount === 0}
89
- sx={{
90
- textTransform: "none",
91
- cursor: filteredCount === 0 ? "default" : "pointer",
92
- mb: 1,
93
- backgroundColor: (theme) =>
94
- theme.palette.mode === "dark"
95
- ? "background.paper"
96
- : "white",
97
- "&:hover": {
98
  backgroundColor: (theme) =>
99
- theme.palette.mode === "dark"
 
 
100
  ? "background.paper"
101
  : "white",
102
- },
103
- "& .MuiTouchRipple-root": {
 
 
 
 
 
 
 
 
 
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={(theme) => ({
123
- width: "4px",
124
- height: "4px",
125
- borderRadius: "100%",
126
- backgroundColor: alpha(
127
- theme.palette.text.primary,
128
- theme.palette.mode === "dark" ? 0.2 : 0.15
129
- ),
130
- })}
131
- />
132
- {filteredCount}
133
- </Box>
134
- </Button>
 
 
 
 
 
 
 
 
 
 
 
135
  );
136
  })}
137
  </Stack>
@@ -180,7 +257,7 @@ const LeaderboardFilters = ({ allSections }) => {
180
  fontWeight: 500,
181
  }}
182
  >
183
- {filteredCount}
184
  </Typography>
185
  <Box
186
  sx={{
@@ -204,7 +281,7 @@ const LeaderboardFilters = ({ allSections }) => {
204
  fontWeight: 500,
205
  }}
206
  >
207
- {arenaOnly ? totalArenaCount : totalLeaderboards}
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 ExpandLessIcon from "@mui/icons-material/ExpandLess";
 
7
 
8
  const ITEMS_PER_PAGE = 3;
9
 
10
- const LeaderboardSection = ({ title, leaderboards }) => {
11
- const [showAll, setShowAll] = useState(false);
12
- const [expanded, setExpanded] = useState(false);
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- if (!leaderboards || leaderboards.length === 0) return null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  // On affiche toujours les 3 premiers
17
- const displayedLeaderboards = leaderboards.slice(0, ITEMS_PER_PAGE);
18
  // Le reste sera dans le Collapse
19
- const remainingLeaderboards = leaderboards.slice(ITEMS_PER_PAGE);
 
 
 
 
 
 
 
 
 
20
 
21
  const toggleExpanded = () => {
22
- setShowAll(!showAll);
23
- setExpanded(!expanded);
 
 
 
 
 
 
 
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
- {leaderboards.length}
70
  </Typography>
71
  </Box>
72
- {leaderboards.length > ITEMS_PER_PAGE && (
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
- alpha(
83
- theme.palette.text.primary,
84
- theme.palette.mode === "dark" ? 0.1 : 0.06
85
- ),
 
 
86
  },
87
  }}
88
  endIcon={
89
  <ExpandMoreIcon
90
  sx={{
91
- transform: expanded ? "rotate(180deg)" : "rotate(0deg)",
92
  transition: "transform 300ms",
93
  }}
94
  />
95
  }
96
  >
97
- {expanded ? "Show less" : "Show more"}
98
  </Button>
99
  )}
100
  </Box>
101
- <Grid container spacing={3}>
102
- {displayedLeaderboards.map((leaderboard, index) => (
103
- <Grid item xs={12} sm={6} md={4} key={index}>
104
- <LeaderboardCard leaderboard={leaderboard} />
105
- </Grid>
106
- ))}
107
- {/* Add skeletons if needed */}
108
- {Array.from({ length: skeletonsNeeded }).map((_, index) => (
109
- <Grid item xs={12} sm={6} md={4} key={`skeleton-${index}`}>
110
- <Box
111
- sx={{
112
- height: "180px",
113
- borderRadius: 2,
114
- bgcolor: (theme) => alpha(theme.palette.primary.main, 0.15),
115
- opacity: 1,
116
- transition: "opacity 0.3s ease-in-out",
117
- "&:hover": {
118
- opacity: 0.8,
119
- },
120
- }}
121
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  </Grid>
123
- ))}
124
- </Grid>
125
- <Collapse in={showAll} timeout={300} unmountOnExit>
126
- <Grid container spacing={3} sx={{ mt: 0 }}>
127
- {remainingLeaderboards.map((leaderboard, index) => (
128
- <Grid item xs={12} sm={6} md={4} key={index + ITEMS_PER_PAGE}>
129
- <LeaderboardCard leaderboard={leaderboard} />
130
  </Grid>
131
- ))}
132
- </Grid>
133
- </Collapse>
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 to="/" style={{ textDecoration: "none", position: "relative" }}>
 
 
 
 
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(false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- // Filter leaderboards based on search query and arena toggle
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", filteredBoards).map((board) => {
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", filteredBoards).map((board) => {
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", filteredBoards).map((board) => {
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(filteredBoards).map((board) => {
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(filteredBoards).map((board) => {
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", filteredBoards).map((board) => {
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", filteredBoards).map((board) => {
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", filteredBoards).map((board) => {
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", filteredBoards).map((board) => {
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", filteredBoards).map((board) => {
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: filteredBoards.filter((board) => !categorizedIds.has(board.id)),
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 filterLeaderboards(leaderboards).length;
263
- }, [filterLeaderboards, leaderboards]);
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
- vibe_check: "✨",
209
  },
210
  modality: {
211
  text: "📝",
@@ -228,7 +228,7 @@ const getTagEmoji = (tag) => {
228
  language: {
229
  english: "🇬🇧",
230
  french: "🇫🇷",
231
- whatever: "🌍",
232
  },
233
  domain: {
234
  financial: "💰",
@@ -645,7 +645,7 @@ const HowToSubmitPage = () => {
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,7 +685,7 @@ const HowToSubmitPage = () => {
685
  "eval:code",
686
  "eval:performance",
687
  "eval:safety",
688
- "task:rag",
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={["language:english", "language:french", "language:whatever"]}
 
 
 
 
704
  explanations={[
705
  "",
706
  "",
@@ -737,7 +741,7 @@ const HowToSubmitPage = () => {
737
  },
738
  }}
739
  >
740
- Clementine Fourrier
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
- {!hasLeaderboards && isFiltering && (
 
100
  <Box
101
  sx={{
102
  display: "flex",
@@ -140,19 +142,38 @@ const LeaderboardPageContent = () => {
140
  </Box>
141
  )}
142
 
143
- {(hasLeaderboards || !isFiltering) &&
144
- sections.map(({ id, title, data }) => {
145
- const filteredLeaderboards = filterLeaderboards(data);
146
- if (filteredLeaderboards.length === 0) return null;
147
- return (
148
- <Box key={id} id={id}>
149
- <LeaderboardSection
150
- title={title}
151
- leaderboards={filteredLeaderboards}
152
- />
153
- </Box>
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"