Spaces:
Running
Running
simplify filters
Browse files- client/src/components/LeaderboardFilters/LeaderboardFilters.jsx +30 -351
- client/src/components/LeaderboardFilters/SearchBar.jsx +45 -25
- client/src/components/LeaderboardSection/components/SectionHeader.jsx +88 -9
- client/src/components/LeaderboardSection/index.jsx +5 -3
- client/src/context/LeaderboardContext.jsx +30 -441
- client/src/hooks/useDebounce.js +7 -0
- client/src/pages/LeaderboardPage/LeaderboardPage.jsx +103 -63
- client/src/utils/filterUtils.js +219 -0
client/src/components/LeaderboardFilters/LeaderboardFilters.jsx
CHANGED
@@ -2,35 +2,13 @@ import React, { useState, useMemo } from "react";
|
|
2 |
import { Box, Stack, useMediaQuery } from "@mui/material";
|
3 |
import { useLeaderboard } from "../../context/LeaderboardContext";
|
4 |
import { useDebounce } from "../../hooks/useDebounce";
|
5 |
-
import { alpha, lighten, darken } from "@mui/material/styles";
|
6 |
import SearchBar from "./SearchBar";
|
7 |
import FilterTag from "../common/FilterTag";
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
"modality:text",
|
14 |
-
"eval:code",
|
15 |
-
"eval:math",
|
16 |
-
"eval:reasoning",
|
17 |
-
"eval:hallucination",
|
18 |
-
"modality:video",
|
19 |
-
"modality:image",
|
20 |
-
"modality:3d",
|
21 |
-
"modality:audio",
|
22 |
-
"domain:financial",
|
23 |
-
"domain:medical",
|
24 |
-
"domain:legal",
|
25 |
-
"domain:biology",
|
26 |
-
"domain:translation",
|
27 |
-
"domain:chemistry",
|
28 |
-
"domain:physics",
|
29 |
-
"domain:commercial",
|
30 |
-
"eval:safety",
|
31 |
-
"eval:performance",
|
32 |
-
"eval:rag",
|
33 |
-
];
|
34 |
|
35 |
// Helper function to get the category group of a section
|
36 |
const getSectionGroup = (id) => {
|
@@ -116,175 +94,20 @@ const LeaderboardFilters = ({ allSections = [] }) => {
|
|
116 |
);
|
117 |
|
118 |
// Apply current filters except the category being counted
|
119 |
-
let baseFiltered = allLeaderboards;
|
120 |
-
|
121 |
-
// Apply arena filter if active
|
122 |
-
if (arenaOnly) {
|
123 |
-
baseFiltered = baseFiltered.filter((board) =>
|
124 |
-
board.tags?.includes("judge:humans")
|
125 |
-
);
|
126 |
-
}
|
127 |
-
|
128 |
-
// Apply search filter if active
|
129 |
-
if (searchQuery) {
|
130 |
-
const query = searchQuery.toLowerCase();
|
131 |
-
const tagMatch = query.match(/^(\w+):(\w+)$/);
|
132 |
-
|
133 |
-
if (tagMatch) {
|
134 |
-
const [_, category, value] = tagMatch;
|
135 |
-
const searchTag = `${category}:${value}`.toLowerCase();
|
136 |
-
baseFiltered = baseFiltered.filter((board) => {
|
137 |
-
const allTags = [...(board.tags || []), ...(board.editor_tags || [])];
|
138 |
-
return allTags.some((tag) => tag.toLowerCase() === searchTag);
|
139 |
-
});
|
140 |
-
} else {
|
141 |
-
baseFiltered = baseFiltered.filter((board) =>
|
142 |
-
board.card_data?.title?.toLowerCase().includes(query)
|
143 |
-
);
|
144 |
-
}
|
145 |
-
}
|
146 |
-
|
147 |
-
// Apply language filters if active
|
148 |
-
if (selectedLanguage.size > 0) {
|
149 |
-
baseFiltered = baseFiltered.filter((board) =>
|
150 |
-
Array.from(selectedLanguage).some((lang) =>
|
151 |
-
board.tags?.some(
|
152 |
-
(tag) => tag.toLowerCase() === `language:${lang.toLowerCase()}`
|
153 |
-
)
|
154 |
-
)
|
155 |
-
);
|
156 |
-
}
|
157 |
-
|
158 |
-
// Apply category filters if active, but exclude the category being counted
|
159 |
-
const applyCategoryFilters = (board, excludedCategory) => {
|
160 |
-
if (selectedCategories.size === 0) return true;
|
161 |
-
|
162 |
-
const tags = board.tags || [];
|
163 |
-
return Array.from(selectedCategories)
|
164 |
-
.filter((category) => category !== excludedCategory)
|
165 |
-
.every((category) => {
|
166 |
-
switch (category) {
|
167 |
-
case "agentic":
|
168 |
-
return tags.includes("modality:agent");
|
169 |
-
case "text":
|
170 |
-
return tags.includes("modality:text");
|
171 |
-
case "image":
|
172 |
-
return tags.includes("modality:image");
|
173 |
-
case "video":
|
174 |
-
return tags.includes("modality:video");
|
175 |
-
case "code":
|
176 |
-
return tags.includes("eval:code");
|
177 |
-
case "math":
|
178 |
-
return tags.includes("eval:math");
|
179 |
-
case "reasoning":
|
180 |
-
return tags.includes("eval:reasoning");
|
181 |
-
case "hallucination":
|
182 |
-
return tags.includes("eval:hallucination");
|
183 |
-
case "rag":
|
184 |
-
return tags.includes("eval:rag");
|
185 |
-
case "language":
|
186 |
-
return tags.some((tag) => tag.startsWith("language:"));
|
187 |
-
case "vision":
|
188 |
-
return tags.some(
|
189 |
-
(tag) => tag === "modality:video" || tag === "modality:image"
|
190 |
-
);
|
191 |
-
case "threeD":
|
192 |
-
return tags.includes("modality:3d");
|
193 |
-
case "audio":
|
194 |
-
return tags.includes("modality:audio");
|
195 |
-
case "financial":
|
196 |
-
return tags.includes("domain:financial");
|
197 |
-
case "medical":
|
198 |
-
return tags.includes("domain:medical");
|
199 |
-
case "legal":
|
200 |
-
return tags.includes("domain:legal");
|
201 |
-
case "biology":
|
202 |
-
return tags.includes("domain:biology");
|
203 |
-
case "commercial":
|
204 |
-
return tags.includes("domain:commercial");
|
205 |
-
case "translation":
|
206 |
-
return tags.includes("domain:translation");
|
207 |
-
case "chemistry":
|
208 |
-
return tags.includes("domain:chemistry");
|
209 |
-
case "safety":
|
210 |
-
return tags.includes("eval:safety");
|
211 |
-
case "performance":
|
212 |
-
return tags.includes("eval:performance");
|
213 |
-
case "uncategorized":
|
214 |
-
return !tags.some(
|
215 |
-
(tag) =>
|
216 |
-
CATEGORIZATION_TAGS.includes(tag) ||
|
217 |
-
tag.startsWith("language:")
|
218 |
-
);
|
219 |
-
default:
|
220 |
-
return false;
|
221 |
-
}
|
222 |
-
});
|
223 |
-
};
|
224 |
-
|
225 |
-
// Now calculate counts for each section based on the filtered boards
|
226 |
allSections.forEach(({ id, title }) => {
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
case "code":
|
241 |
-
return tags.includes("eval:code");
|
242 |
-
case "math":
|
243 |
-
return tags.includes("eval:math");
|
244 |
-
case "reasoning":
|
245 |
-
return tags.includes("eval:reasoning");
|
246 |
-
case "hallucination":
|
247 |
-
return tags.includes("eval:hallucination");
|
248 |
-
case "rag":
|
249 |
-
return tags.includes("eval:rag");
|
250 |
-
case "language":
|
251 |
-
return tags.some((tag) => tag.startsWith("language:"));
|
252 |
-
case "vision":
|
253 |
-
return tags.some(
|
254 |
-
(tag) => tag === "modality:video" || tag === "modality:image"
|
255 |
-
);
|
256 |
-
case "threeD":
|
257 |
-
return tags.includes("modality:3d");
|
258 |
-
case "audio":
|
259 |
-
return tags.includes("modality:audio");
|
260 |
-
case "financial":
|
261 |
-
return tags.includes("domain:financial");
|
262 |
-
case "medical":
|
263 |
-
return tags.includes("domain:medical");
|
264 |
-
case "legal":
|
265 |
-
return tags.includes("domain:legal");
|
266 |
-
case "biology":
|
267 |
-
return tags.includes("domain:biology");
|
268 |
-
case "commercial":
|
269 |
-
return tags.includes("domain:commercial");
|
270 |
-
case "translation":
|
271 |
-
return tags.includes("domain:translation");
|
272 |
-
case "chemistry":
|
273 |
-
return tags.includes("domain:chemistry");
|
274 |
-
case "safety":
|
275 |
-
return tags.includes("eval:safety");
|
276 |
-
case "performance":
|
277 |
-
return tags.includes("eval:performance");
|
278 |
-
case "uncategorized":
|
279 |
-
return !tags.some(
|
280 |
-
(tag) =>
|
281 |
-
CATEGORIZATION_TAGS.includes(tag) ||
|
282 |
-
tag.startsWith("language:")
|
283 |
-
);
|
284 |
-
default:
|
285 |
-
return false;
|
286 |
-
}
|
287 |
-
});
|
288 |
|
289 |
// Only count approved leaderboards
|
290 |
sectionBoards = sectionBoards.filter(
|
@@ -327,65 +150,7 @@ const LeaderboardFilters = ({ allSections = [] }) => {
|
|
327 |
if (selectedCategories.size > 0) {
|
328 |
boardsToCount = boardsToCount.filter((board) => {
|
329 |
if (board.approval_status !== "approved") return false;
|
330 |
-
|
331 |
-
return Array.from(selectedCategories).every((category) => {
|
332 |
-
switch (category) {
|
333 |
-
case "agentic":
|
334 |
-
return tags.includes("modality:agent");
|
335 |
-
case "text":
|
336 |
-
return tags.includes("modality:text");
|
337 |
-
case "image":
|
338 |
-
return tags.includes("modality:image");
|
339 |
-
case "video":
|
340 |
-
return tags.includes("modality:video");
|
341 |
-
case "code":
|
342 |
-
return tags.includes("eval:code");
|
343 |
-
case "math":
|
344 |
-
return tags.includes("eval:math");
|
345 |
-
case "reasoning":
|
346 |
-
return tags.includes("eval:reasoning");
|
347 |
-
case "hallucination":
|
348 |
-
return tags.includes("eval:hallucination");
|
349 |
-
case "rag":
|
350 |
-
return tags.includes("eval:rag");
|
351 |
-
case "language":
|
352 |
-
return tags.some((tag) => tag.startsWith("language:"));
|
353 |
-
case "vision":
|
354 |
-
return tags.some(
|
355 |
-
(tag) => tag === "modality:video" || tag === "modality:image"
|
356 |
-
);
|
357 |
-
case "threeD":
|
358 |
-
return tags.includes("modality:3d");
|
359 |
-
case "audio":
|
360 |
-
return tags.includes("modality:audio");
|
361 |
-
case "financial":
|
362 |
-
return tags.includes("domain:financial");
|
363 |
-
case "medical":
|
364 |
-
return tags.includes("domain:medical");
|
365 |
-
case "legal":
|
366 |
-
return tags.includes("domain:legal");
|
367 |
-
case "biology":
|
368 |
-
return tags.includes("domain:biology");
|
369 |
-
case "commercial":
|
370 |
-
return tags.includes("domain:commercial");
|
371 |
-
case "translation":
|
372 |
-
return tags.includes("domain:translation");
|
373 |
-
case "chemistry":
|
374 |
-
return tags.includes("domain:chemistry");
|
375 |
-
case "safety":
|
376 |
-
return tags.includes("eval:safety");
|
377 |
-
case "performance":
|
378 |
-
return tags.includes("eval:performance");
|
379 |
-
case "uncategorized":
|
380 |
-
return !tags.some(
|
381 |
-
(tag) =>
|
382 |
-
CATEGORIZATION_TAGS.includes(tag) ||
|
383 |
-
tag.startsWith("language:")
|
384 |
-
);
|
385 |
-
default:
|
386 |
-
return false;
|
387 |
-
}
|
388 |
-
});
|
389 |
});
|
390 |
}
|
391 |
|
@@ -396,15 +161,11 @@ const LeaderboardFilters = ({ allSections = [] }) => {
|
|
396 |
setTotalArenaCount(arenaCount);
|
397 |
}, [selectedCategories, allSections]);
|
398 |
|
399 |
-
// Calculer le nombre total
|
400 |
const totalCount = useMemo(() => {
|
401 |
if (!allSections) return 0;
|
402 |
|
403 |
-
|
404 |
-
return totalArenaCount;
|
405 |
-
}
|
406 |
-
|
407 |
-
// Récupérer tous les leaderboards uniques de toutes les sections
|
408 |
const allLeaderboards = Array.from(
|
409 |
new Set(
|
410 |
allSections.reduce((acc, section) => {
|
@@ -413,94 +174,17 @@ const LeaderboardFilters = ({ allSections = [] }) => {
|
|
413 |
)
|
414 |
);
|
415 |
|
416 |
-
//
|
417 |
-
|
418 |
-
if (selectedCategories.size > 0) {
|
419 |
-
filteredBoards = filteredBoards.filter((board) => {
|
420 |
-
if (board.approval_status !== "approved") return false;
|
421 |
-
const tags = board.tags || [];
|
422 |
-
return Array.from(selectedCategories).every((category) => {
|
423 |
-
switch (category) {
|
424 |
-
case "agentic":
|
425 |
-
return tags.includes("modality:agent");
|
426 |
-
case "text":
|
427 |
-
return tags.includes("modality:text");
|
428 |
-
case "image":
|
429 |
-
return tags.includes("modality:image");
|
430 |
-
case "video":
|
431 |
-
return tags.includes("modality:video");
|
432 |
-
case "code":
|
433 |
-
return tags.includes("eval:code");
|
434 |
-
case "math":
|
435 |
-
return tags.includes("eval:math");
|
436 |
-
case "reasoning":
|
437 |
-
return tags.includes("eval:reasoning");
|
438 |
-
case "hallucination":
|
439 |
-
return tags.includes("eval:hallucination");
|
440 |
-
case "rag":
|
441 |
-
return tags.includes("eval:rag");
|
442 |
-
case "language":
|
443 |
-
return tags.some((tag) => tag.startsWith("language:"));
|
444 |
-
case "vision":
|
445 |
-
return tags.some(
|
446 |
-
(tag) => tag === "modality:video" || tag === "modality:image"
|
447 |
-
);
|
448 |
-
case "threeD":
|
449 |
-
return tags.includes("modality:3d");
|
450 |
-
case "audio":
|
451 |
-
return tags.includes("modality:audio");
|
452 |
-
case "financial":
|
453 |
-
return tags.includes("domain:financial");
|
454 |
-
case "medical":
|
455 |
-
return tags.includes("domain:medical");
|
456 |
-
case "legal":
|
457 |
-
return tags.includes("domain:legal");
|
458 |
-
case "biology":
|
459 |
-
return tags.includes("domain:biology");
|
460 |
-
case "commercial":
|
461 |
-
return tags.includes("domain:commercial");
|
462 |
-
case "translation":
|
463 |
-
return tags.includes("domain:translation");
|
464 |
-
case "chemistry":
|
465 |
-
return tags.includes("domain:chemistry");
|
466 |
-
case "safety":
|
467 |
-
return tags.includes("eval:safety");
|
468 |
-
case "performance":
|
469 |
-
return tags.includes("eval:performance");
|
470 |
-
case "uncategorized":
|
471 |
-
return !tags.some(
|
472 |
-
(tag) =>
|
473 |
-
CATEGORIZATION_TAGS.includes(tag) ||
|
474 |
-
tag.startsWith("language:")
|
475 |
-
);
|
476 |
-
default:
|
477 |
-
return false;
|
478 |
-
}
|
479 |
-
});
|
480 |
-
});
|
481 |
-
}
|
482 |
-
|
483 |
-
// Appliquer le filtre Arena Only si nécessaire
|
484 |
-
if (arenaOnly) {
|
485 |
-
filteredBoards = filteredBoards.filter((board) =>
|
486 |
-
board.tags?.includes("judge:humans")
|
487 |
-
);
|
488 |
-
}
|
489 |
-
|
490 |
-
return filteredBoards.filter(
|
491 |
(board) => board.approval_status === "approved"
|
492 |
).length;
|
493 |
-
}, [
|
494 |
|
495 |
-
// Calculer le nombre
|
496 |
const currentFilteredCount = useMemo(() => {
|
497 |
if (!allSections) return 0;
|
498 |
|
499 |
-
|
500 |
-
return totalArenaCount;
|
501 |
-
}
|
502 |
-
|
503 |
-
// Récupérer tous les leaderboards uniques de toutes les sections
|
504 |
const allLeaderboards = Array.from(
|
505 |
new Set(
|
506 |
allSections.reduce((acc, section) => {
|
@@ -509,19 +193,12 @@ const LeaderboardFilters = ({ allSections = [] }) => {
|
|
509 |
)
|
510 |
);
|
511 |
|
512 |
-
// Appliquer les filtres
|
513 |
const filteredBoards = filterLeaderboards(allLeaderboards);
|
514 |
return filteredBoards.filter(
|
515 |
(board) => board.approval_status === "approved"
|
516 |
).length;
|
517 |
-
}, [
|
518 |
-
selectedCategories,
|
519 |
-
allSections,
|
520 |
-
filterLeaderboards,
|
521 |
-
arenaOnly,
|
522 |
-
searchQuery,
|
523 |
-
totalArenaCount,
|
524 |
-
]);
|
525 |
|
526 |
const isMobile = useMediaQuery((theme) => theme.breakpoints.down("md"));
|
527 |
|
@@ -584,6 +261,8 @@ const LeaderboardFilters = ({ allSections = [] }) => {
|
|
584 |
totalCount={totalCount}
|
585 |
totalArenaCount={totalArenaCount}
|
586 |
isMobile={isMobile}
|
|
|
|
|
587 |
/>
|
588 |
</Stack>
|
589 |
);
|
|
|
2 |
import { Box, Stack, useMediaQuery } from "@mui/material";
|
3 |
import { useLeaderboard } from "../../context/LeaderboardContext";
|
4 |
import { useDebounce } from "../../hooks/useDebounce";
|
|
|
5 |
import SearchBar from "./SearchBar";
|
6 |
import FilterTag from "../common/FilterTag";
|
7 |
+
import {
|
8 |
+
CATEGORIZATION_TAGS,
|
9 |
+
filterLeaderboards as filterLeaderboardsUtil,
|
10 |
+
applyCategoryFilters,
|
11 |
+
} from "../../utils/filterUtils";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
|
13 |
// Helper function to get the category group of a section
|
14 |
const getSectionGroup = (id) => {
|
|
|
94 |
);
|
95 |
|
96 |
// Apply current filters except the category being counted
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
allSections.forEach(({ id, title }) => {
|
98 |
+
const baseFiltered = filterLeaderboardsUtil({
|
99 |
+
boards: allLeaderboards,
|
100 |
+
searchQuery,
|
101 |
+
arenaOnly,
|
102 |
+
selectedLanguage,
|
103 |
+
selectedCategories,
|
104 |
+
excludedCategory: id,
|
105 |
+
});
|
106 |
+
|
107 |
+
let sectionBoards = baseFiltered.filter((board) => {
|
108 |
+
const tags = board.tags || [];
|
109 |
+
return applyCategoryFilters(board, new Set([id]));
|
110 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
|
112 |
// Only count approved leaderboards
|
113 |
sectionBoards = sectionBoards.filter(
|
|
|
150 |
if (selectedCategories.size > 0) {
|
151 |
boardsToCount = boardsToCount.filter((board) => {
|
152 |
if (board.approval_status !== "approved") return false;
|
153 |
+
return applyCategoryFilters(board, selectedCategories);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
});
|
155 |
}
|
156 |
|
|
|
161 |
setTotalArenaCount(arenaCount);
|
162 |
}, [selectedCategories, allSections]);
|
163 |
|
164 |
+
// Calculer le nombre total de leaderboards approuvés (sans aucun filtre)
|
165 |
const totalCount = useMemo(() => {
|
166 |
if (!allSections) return 0;
|
167 |
|
168 |
+
// Récupérer tous les leaderboards uniques
|
|
|
|
|
|
|
|
|
169 |
const allLeaderboards = Array.from(
|
170 |
new Set(
|
171 |
allSections.reduce((acc, section) => {
|
|
|
174 |
)
|
175 |
);
|
176 |
|
177 |
+
// Ne compter que les leaderboards approuvés
|
178 |
+
return allLeaderboards.filter(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
179 |
(board) => board.approval_status === "approved"
|
180 |
).length;
|
181 |
+
}, [allSections]);
|
182 |
|
183 |
+
// Calculer le nombre de leaderboards après application de tous les filtres
|
184 |
const currentFilteredCount = useMemo(() => {
|
185 |
if (!allSections) return 0;
|
186 |
|
187 |
+
// Récupérer tous les leaderboards uniques
|
|
|
|
|
|
|
|
|
188 |
const allLeaderboards = Array.from(
|
189 |
new Set(
|
190 |
allSections.reduce((acc, section) => {
|
|
|
193 |
)
|
194 |
);
|
195 |
|
196 |
+
// Appliquer tous les filtres
|
197 |
const filteredBoards = filterLeaderboards(allLeaderboards);
|
198 |
return filteredBoards.filter(
|
199 |
(board) => board.approval_status === "approved"
|
200 |
).length;
|
201 |
+
}, [allSections, filterLeaderboards]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
|
203 |
const isMobile = useMediaQuery((theme) => theme.breakpoints.down("md"));
|
204 |
|
|
|
261 |
totalCount={totalCount}
|
262 |
totalArenaCount={totalArenaCount}
|
263 |
isMobile={isMobile}
|
264 |
+
selectedCategories={selectedCategories}
|
265 |
+
selectedLanguage={selectedLanguage}
|
266 |
/>
|
267 |
</Stack>
|
268 |
);
|
client/src/components/LeaderboardFilters/SearchBar.jsx
CHANGED
@@ -22,14 +22,29 @@ const SearchBar = ({
|
|
22 |
totalCount,
|
23 |
totalArenaCount,
|
24 |
isMobile,
|
|
|
|
|
25 |
}) => {
|
26 |
const [inputValue, setInputValue] = useState(searchQuery || "");
|
27 |
const debouncedSearch = useDebounce(inputValue, 200);
|
28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
// Update the search query after debounce
|
30 |
React.useEffect(() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
setSearchQuery(debouncedSearch);
|
32 |
-
}, [debouncedSearch, setSearchQuery]);
|
33 |
|
34 |
// Update input value when searchQuery changes externally
|
35 |
React.useEffect(() => {
|
@@ -73,39 +88,42 @@ const SearchBar = ({
|
|
73 |
<Typography
|
74 |
variant="body2"
|
75 |
sx={{
|
76 |
-
color:
|
77 |
? "primary.main"
|
78 |
: "text.secondary",
|
79 |
fontWeight: 500,
|
80 |
}}
|
81 |
>
|
82 |
-
{currentFilteredCount}
|
83 |
</Typography>
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
alignItems: "center",
|
88 |
-
color: arenaOnly ? "secondary.main" : "text.secondary",
|
89 |
-
}}
|
90 |
-
>
|
91 |
-
<Typography
|
92 |
-
variant="body2"
|
93 |
-
sx={{
|
94 |
-
fontWeight: 500,
|
95 |
-
mx: 0.5,
|
96 |
-
}}
|
97 |
-
>
|
98 |
-
/
|
99 |
-
</Typography>
|
100 |
-
<Typography
|
101 |
-
variant="body2"
|
102 |
sx={{
|
103 |
-
|
|
|
|
|
104 |
}}
|
105 |
>
|
106 |
-
|
107 |
-
|
108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
<Typography
|
110 |
variant="body2"
|
111 |
sx={{
|
@@ -164,6 +182,8 @@ SearchBar.propTypes = {
|
|
164 |
totalCount: PropTypes.number.isRequired,
|
165 |
totalArenaCount: PropTypes.number.isRequired,
|
166 |
isMobile: PropTypes.bool.isRequired,
|
|
|
|
|
167 |
};
|
168 |
|
169 |
export default SearchBar;
|
|
|
22 |
totalCount,
|
23 |
totalArenaCount,
|
24 |
isMobile,
|
25 |
+
selectedCategories,
|
26 |
+
selectedLanguage,
|
27 |
}) => {
|
28 |
const [inputValue, setInputValue] = useState(searchQuery || "");
|
29 |
const debouncedSearch = useDebounce(inputValue, 200);
|
30 |
|
31 |
+
// Détecter si des filtres sont actifs
|
32 |
+
const hasActiveFilters =
|
33 |
+
debouncedSearch ||
|
34 |
+
arenaOnly ||
|
35 |
+
selectedCategories?.size > 0 ||
|
36 |
+
selectedLanguage?.size > 0;
|
37 |
+
|
38 |
// Update the search query after debounce
|
39 |
React.useEffect(() => {
|
40 |
+
// Si l'input est vide, on met à jour immédiatement
|
41 |
+
if (!inputValue.trim()) {
|
42 |
+
setSearchQuery("");
|
43 |
+
return;
|
44 |
+
}
|
45 |
+
// Sinon on attend le debounce
|
46 |
setSearchQuery(debouncedSearch);
|
47 |
+
}, [debouncedSearch, setSearchQuery, inputValue]);
|
48 |
|
49 |
// Update input value when searchQuery changes externally
|
50 |
React.useEffect(() => {
|
|
|
88 |
<Typography
|
89 |
variant="body2"
|
90 |
sx={{
|
91 |
+
color: hasActiveFilters
|
92 |
? "primary.main"
|
93 |
: "text.secondary",
|
94 |
fontWeight: 500,
|
95 |
}}
|
96 |
>
|
97 |
+
{hasActiveFilters ? currentFilteredCount : totalCount}
|
98 |
</Typography>
|
99 |
+
{hasActiveFilters && (
|
100 |
+
<Box
|
101 |
+
component="span"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
sx={{
|
103 |
+
display: "flex",
|
104 |
+
alignItems: "center",
|
105 |
+
color: "text.secondary",
|
106 |
}}
|
107 |
>
|
108 |
+
<Typography
|
109 |
+
variant="body2"
|
110 |
+
sx={{
|
111 |
+
fontWeight: 500,
|
112 |
+
mx: 0.5,
|
113 |
+
}}
|
114 |
+
>
|
115 |
+
/
|
116 |
+
</Typography>
|
117 |
+
<Typography
|
118 |
+
variant="body2"
|
119 |
+
sx={{
|
120 |
+
fontWeight: 500,
|
121 |
+
}}
|
122 |
+
>
|
123 |
+
{totalCount}
|
124 |
+
</Typography>
|
125 |
+
</Box>
|
126 |
+
)}
|
127 |
<Typography
|
128 |
variant="body2"
|
129 |
sx={{
|
|
|
182 |
totalCount: PropTypes.number.isRequired,
|
183 |
totalArenaCount: PropTypes.number.isRequired,
|
184 |
isMobile: PropTypes.bool.isRequired,
|
185 |
+
selectedCategories: PropTypes.instanceOf(Set),
|
186 |
+
selectedLanguage: PropTypes.instanceOf(Set),
|
187 |
};
|
188 |
|
189 |
export default SearchBar;
|
client/src/components/LeaderboardSection/components/SectionHeader.jsx
CHANGED
@@ -1,8 +1,40 @@
|
|
1 |
import React from "react";
|
2 |
-
import { Typography, Box, Button } from "@mui/material";
|
3 |
import { alpha } from "@mui/material/styles";
|
4 |
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
const SectionHeader = ({
|
7 |
title,
|
8 |
count,
|
@@ -11,26 +43,70 @@ const SectionHeader = ({
|
|
11 |
showExpandButton,
|
12 |
isExpandButtonEnabled,
|
13 |
}) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
return (
|
15 |
<Box
|
16 |
sx={{
|
17 |
display: "flex",
|
18 |
-
alignItems: "
|
19 |
justifyContent: "space-between",
|
20 |
mb: 4,
|
|
|
21 |
}}
|
22 |
>
|
23 |
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
24 |
-
<
|
25 |
-
variant="h4"
|
26 |
sx={{
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
}}
|
31 |
>
|
32 |
-
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
<Box
|
35 |
sx={(theme) => ({
|
36 |
width: "4px",
|
@@ -40,6 +116,7 @@ const SectionHeader = ({
|
|
40 |
theme.palette.text.primary,
|
41 |
theme.palette.mode === "dark" ? 0.2 : 0.15
|
42 |
),
|
|
|
43 |
})}
|
44 |
/>
|
45 |
<Typography
|
@@ -49,6 +126,7 @@ const SectionHeader = ({
|
|
49 |
fontWeight: 400,
|
50 |
fontSize: { xs: "1.25rem", md: "1.5rem" },
|
51 |
opacity: 0.6,
|
|
|
52 |
}}
|
53 |
>
|
54 |
{count}
|
@@ -64,6 +142,7 @@ const SectionHeader = ({
|
|
64 |
fontSize: "0.875rem",
|
65 |
textTransform: "none",
|
66 |
opacity: isExpandButtonEnabled ? 1 : 0.5,
|
|
|
67 |
"&:hover": {
|
68 |
backgroundColor: (theme) =>
|
69 |
isExpandButtonEnabled
|
|
|
1 |
import React from "react";
|
2 |
+
import { Typography, Box, Button, Chip } from "@mui/material";
|
3 |
import { alpha } from "@mui/material/styles";
|
4 |
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
5 |
|
6 |
+
// Composant pour les chips
|
7 |
+
const StyledChip = ({ label, sx = {} }) => (
|
8 |
+
<Chip
|
9 |
+
label={label}
|
10 |
+
size="small"
|
11 |
+
sx={{
|
12 |
+
height: "24px",
|
13 |
+
backgroundColor: (theme) =>
|
14 |
+
alpha(
|
15 |
+
theme.palette.text.primary,
|
16 |
+
theme.palette.mode === "dark" ? 0.1 : 0.05
|
17 |
+
),
|
18 |
+
color: "text.secondary",
|
19 |
+
fontSize: "0.75rem",
|
20 |
+
fontWeight: 500,
|
21 |
+
mx: 1,
|
22 |
+
"& .MuiChip-label": {
|
23 |
+
px: 1,
|
24 |
+
lineHeight: 1,
|
25 |
+
paddingTop: "1px", // Ajustement pour le centrage vertical
|
26 |
+
},
|
27 |
+
...sx,
|
28 |
+
}}
|
29 |
+
/>
|
30 |
+
);
|
31 |
+
|
32 |
+
// Composant pour le chip AND
|
33 |
+
const AndChip = () => <StyledChip label="AND" />;
|
34 |
+
|
35 |
+
// Composant pour le chip matching
|
36 |
+
const MatchingChip = () => <StyledChip label="MATCHING" />;
|
37 |
+
|
38 |
const SectionHeader = ({
|
39 |
title,
|
40 |
count,
|
|
|
43 |
showExpandButton,
|
44 |
isExpandButtonEnabled,
|
45 |
}) => {
|
46 |
+
// Séparer le titre en parties si c'est un titre combiné
|
47 |
+
const titleParts = title.split(" matching ");
|
48 |
+
const categories = titleParts[0].split(" + ");
|
49 |
+
const hasSearchQuery = titleParts.length > 1;
|
50 |
+
const searchQuery = hasSearchQuery
|
51 |
+
? titleParts[1].replace(/['"]/g, "")
|
52 |
+
: null;
|
53 |
+
|
54 |
return (
|
55 |
<Box
|
56 |
sx={{
|
57 |
display: "flex",
|
58 |
+
alignItems: "flex-start",
|
59 |
justifyContent: "space-between",
|
60 |
mb: 4,
|
61 |
+
minHeight: { xs: "2.5rem", md: "3rem" },
|
62 |
}}
|
63 |
>
|
64 |
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
65 |
+
<Box
|
|
|
66 |
sx={{
|
67 |
+
display: "flex",
|
68 |
+
alignItems: "center",
|
69 |
+
flexWrap: "wrap",
|
70 |
}}
|
71 |
>
|
72 |
+
<Typography
|
73 |
+
variant="h4"
|
74 |
+
component="div"
|
75 |
+
sx={{
|
76 |
+
color: "text.primary",
|
77 |
+
fontWeight: 600,
|
78 |
+
fontSize: { xs: "1.5rem", md: "2rem" },
|
79 |
+
display: "flex",
|
80 |
+
alignItems: "center",
|
81 |
+
flexWrap: "wrap",
|
82 |
+
lineHeight: 1.2,
|
83 |
+
minHeight: { xs: "2rem", md: "2.5rem" },
|
84 |
+
}}
|
85 |
+
>
|
86 |
+
{categories.map((category, index) => (
|
87 |
+
<React.Fragment key={index}>
|
88 |
+
{index > 0 && <AndChip />}
|
89 |
+
{category}
|
90 |
+
</React.Fragment>
|
91 |
+
))}
|
92 |
+
{hasSearchQuery && (
|
93 |
+
<>
|
94 |
+
<MatchingChip />
|
95 |
+
<Typography
|
96 |
+
component="span"
|
97 |
+
sx={{
|
98 |
+
color: "text.primary",
|
99 |
+
fontWeight: 600,
|
100 |
+
fontSize: "inherit",
|
101 |
+
lineHeight: "inherit",
|
102 |
+
}}
|
103 |
+
>
|
104 |
+
"{searchQuery}"
|
105 |
+
</Typography>
|
106 |
+
</>
|
107 |
+
)}
|
108 |
+
</Typography>
|
109 |
+
</Box>
|
110 |
<Box
|
111 |
sx={(theme) => ({
|
112 |
width: "4px",
|
|
|
116 |
theme.palette.text.primary,
|
117 |
theme.palette.mode === "dark" ? 0.2 : 0.15
|
118 |
),
|
119 |
+
mx: 1,
|
120 |
})}
|
121 |
/>
|
122 |
<Typography
|
|
|
126 |
fontWeight: 400,
|
127 |
fontSize: { xs: "1.25rem", md: "1.5rem" },
|
128 |
opacity: 0.6,
|
129 |
+
lineHeight: 1.2,
|
130 |
}}
|
131 |
>
|
132 |
{count}
|
|
|
142 |
fontSize: "0.875rem",
|
143 |
textTransform: "none",
|
144 |
opacity: isExpandButtonEnabled ? 1 : 0.5,
|
145 |
+
mt: { xs: "0.5rem", md: "0.75rem" },
|
146 |
"&:hover": {
|
147 |
backgroundColor: (theme) =>
|
148 |
isExpandButtonEnabled
|
client/src/components/LeaderboardSection/index.jsx
CHANGED
@@ -41,7 +41,8 @@ const LeaderboardSection = ({
|
|
41 |
const shouldShowAll =
|
42 |
(selectedCategories.size === 1 && selectedCategories.has(id)) || // Une seule catégorie sélectionnée ET c'est celle-ci
|
43 |
(selectedCategories.size > 1 && id === "combined") || // Plusieurs catégories ET c'est la section combinée
|
44 |
-
isExpanded
|
|
|
45 |
|
46 |
// Si on doit tout montrer, on ne divise pas la liste
|
47 |
const displayedLeaderboards = shouldShowAll
|
@@ -57,8 +58,9 @@ const LeaderboardSection = ({
|
|
57 |
? 0
|
58 |
: Math.max(0, 4 - approvedLeaderboards.length);
|
59 |
|
60 |
-
// On affiche le bouton expand seulement quand on n'a pas de sélection
|
61 |
-
const showExpandButton =
|
|
|
62 |
|
63 |
// Le bouton est actif seulement s'il y a plus de 4 leaderboards
|
64 |
const isExpandButtonEnabled = approvedLeaderboards.length > ITEMS_PER_PAGE;
|
|
|
41 |
const shouldShowAll =
|
42 |
(selectedCategories.size === 1 && selectedCategories.has(id)) || // Une seule catégorie sélectionnée ET c'est celle-ci
|
43 |
(selectedCategories.size > 1 && id === "combined") || // Plusieurs catégories ET c'est la section combinée
|
44 |
+
isExpanded || // Section dépliée (quelle que soit la sélection)
|
45 |
+
id === "search-results"; // Toujours afficher tous les résultats pour la recherche textuelle
|
46 |
|
47 |
// Si on doit tout montrer, on ne divise pas la liste
|
48 |
const displayedLeaderboards = shouldShowAll
|
|
|
58 |
? 0
|
59 |
: Math.max(0, 4 - approvedLeaderboards.length);
|
60 |
|
61 |
+
// On affiche le bouton expand seulement quand on n'a pas de sélection et que ce n'est pas une recherche textuelle
|
62 |
+
const showExpandButton =
|
63 |
+
selectedCategories.size === 0 && id !== "search-results";
|
64 |
|
65 |
// Le bouton est actif seulement s'il y a plus de 4 leaderboards
|
66 |
const isExpandButtonEnabled = approvedLeaderboards.length > ITEMS_PER_PAGE;
|
client/src/context/LeaderboardContext.jsx
CHANGED
@@ -8,50 +8,17 @@ import React, {
|
|
8 |
} from "react";
|
9 |
import { normalizeTags } from "../utils/tagFilters";
|
10 |
import { useUrlState } from "../hooks/useUrlState";
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
const LeaderboardContext = createContext();
|
13 |
|
14 |
-
// Constantes pour les tags de catégorisation
|
15 |
-
const CATEGORIZATION_TAGS = [
|
16 |
-
"modality:agent",
|
17 |
-
"modality:artefacts",
|
18 |
-
"modality:text",
|
19 |
-
"eval:code",
|
20 |
-
"eval:math",
|
21 |
-
"eval:reasoning",
|
22 |
-
"eval:hallucination",
|
23 |
-
"modality:video",
|
24 |
-
"modality:image",
|
25 |
-
"modality:3d",
|
26 |
-
"modality:audio",
|
27 |
-
"domain:financial",
|
28 |
-
"domain:medical",
|
29 |
-
"domain:legal",
|
30 |
-
"domain:biology",
|
31 |
-
"domain:translation",
|
32 |
-
"domain:chemistry",
|
33 |
-
"domain:physics",
|
34 |
-
"domain:commercial",
|
35 |
-
"eval:safety",
|
36 |
-
"eval:performance",
|
37 |
-
"eval:rag",
|
38 |
-
];
|
39 |
-
|
40 |
// Helper pour déterminer si un leaderboard est non catégorisé
|
41 |
-
const
|
42 |
-
const tags = board.tags || [];
|
43 |
-
console.log("Checking uncategorized for board:", {
|
44 |
-
id: board.id,
|
45 |
-
tags,
|
46 |
-
approval_status: board.approval_status,
|
47 |
-
isUncategorized: !tags.some(
|
48 |
-
(tag) => CATEGORIZATION_TAGS.includes(tag) || tag.startsWith("language:")
|
49 |
-
),
|
50 |
-
});
|
51 |
-
return !tags.some(
|
52 |
-
(tag) => CATEGORIZATION_TAGS.includes(tag) || tag.startsWith("language:")
|
53 |
-
);
|
54 |
-
};
|
55 |
|
56 |
export const LeaderboardProvider = ({ children }) => {
|
57 |
const { params, updateParams } = useUrlState();
|
@@ -139,42 +106,11 @@ export const LeaderboardProvider = ({ children }) => {
|
|
139 |
// Filter leaderboards based on search query and arena toggle, ignoring category selection
|
140 |
const filterLeaderboardsForCount = useCallback(
|
141 |
(boards) => {
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
if (searchQuery) {
|
148 |
-
const query = searchQuery.toLowerCase();
|
149 |
-
const tagMatch = query.match(/^(\w+):(\w+)$/);
|
150 |
-
|
151 |
-
if (tagMatch) {
|
152 |
-
// Search by tag (ex: language:french)
|
153 |
-
const [_, category, value] = tagMatch;
|
154 |
-
const searchTag = `${category}:${value}`.toLowerCase();
|
155 |
-
filtered = filtered.filter((board) => {
|
156 |
-
const allTags = [
|
157 |
-
...(board.tags || []),
|
158 |
-
...(board.editor_tags || []),
|
159 |
-
];
|
160 |
-
return allTags.some((tag) => tag.toLowerCase() === searchTag);
|
161 |
-
});
|
162 |
-
} else {
|
163 |
-
// Regular search in title
|
164 |
-
filtered = filtered.filter((board) =>
|
165 |
-
board.card_data?.title?.toLowerCase().includes(query)
|
166 |
-
);
|
167 |
-
}
|
168 |
-
}
|
169 |
-
|
170 |
-
// Filter arena only
|
171 |
-
if (arenaOnly) {
|
172 |
-
filtered = filtered.filter((board) =>
|
173 |
-
board.tags?.includes("judge:humans")
|
174 |
-
);
|
175 |
-
}
|
176 |
-
|
177 |
-
return filtered;
|
178 |
},
|
179 |
[searchQuery, arenaOnly]
|
180 |
);
|
@@ -182,84 +118,13 @@ export const LeaderboardProvider = ({ children }) => {
|
|
182 |
// Filter leaderboards based on all criteria including categories and language selection
|
183 |
const filterLeaderboards = useCallback(
|
184 |
(boards) => {
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
Array.from(selectedLanguage).some((lang) =>
|
193 |
-
board.tags?.some(
|
194 |
-
(tag) => tag.toLowerCase() === `language:${lang.toLowerCase()}`
|
195 |
-
)
|
196 |
-
)
|
197 |
-
);
|
198 |
-
}
|
199 |
-
|
200 |
-
// Filter by selected categories if any
|
201 |
-
if (selectedCategories.size > 0) {
|
202 |
-
filtered = filtered.filter((board) => {
|
203 |
-
const { tags = [] } = board;
|
204 |
-
// Un leaderboard est inclus s'il correspond à TOUTES les catégories sélectionnées
|
205 |
-
return Array.from(selectedCategories).every((category) => {
|
206 |
-
switch (category) {
|
207 |
-
case "agentic":
|
208 |
-
return tags.includes("modality:agent");
|
209 |
-
case "text":
|
210 |
-
return tags.includes("modality:text");
|
211 |
-
case "image":
|
212 |
-
return tags.includes("modality:image");
|
213 |
-
case "video":
|
214 |
-
return tags.includes("modality:video");
|
215 |
-
case "code":
|
216 |
-
return tags.includes("eval:code");
|
217 |
-
case "math":
|
218 |
-
return tags.includes("eval:math");
|
219 |
-
case "reasoning":
|
220 |
-
return tags.includes("eval:reasoning");
|
221 |
-
case "hallucination":
|
222 |
-
return tags.includes("eval:hallucination");
|
223 |
-
case "rag":
|
224 |
-
return tags.includes("eval:rag");
|
225 |
-
case "language":
|
226 |
-
return tags.some((tag) => tag.startsWith("language:"));
|
227 |
-
case "vision":
|
228 |
-
return tags.some(
|
229 |
-
(tag) => tag === "modality:video" || tag === "modality:image"
|
230 |
-
);
|
231 |
-
case "threeD":
|
232 |
-
return tags.includes("modality:3d");
|
233 |
-
case "audio":
|
234 |
-
return tags.includes("modality:audio");
|
235 |
-
case "financial":
|
236 |
-
return tags.includes("domain:financial");
|
237 |
-
case "medical":
|
238 |
-
return tags.includes("domain:medical");
|
239 |
-
case "legal":
|
240 |
-
return tags.includes("domain:legal");
|
241 |
-
case "biology":
|
242 |
-
return tags.includes("domain:biology");
|
243 |
-
case "commercial":
|
244 |
-
return tags.includes("domain:commercial");
|
245 |
-
case "translation":
|
246 |
-
return tags.includes("domain:translation");
|
247 |
-
case "chemistry":
|
248 |
-
return tags.includes("domain:chemistry");
|
249 |
-
case "safety":
|
250 |
-
return tags.includes("eval:safety");
|
251 |
-
case "performance":
|
252 |
-
return tags.includes("eval:performance");
|
253 |
-
case "uncategorized":
|
254 |
-
return isUncategorized(board);
|
255 |
-
default:
|
256 |
-
return false;
|
257 |
-
}
|
258 |
-
});
|
259 |
-
});
|
260 |
-
}
|
261 |
-
|
262 |
-
return filtered;
|
263 |
},
|
264 |
[searchQuery, arenaOnly, selectedCategories, selectedLanguage]
|
265 |
);
|
@@ -270,49 +135,9 @@ export const LeaderboardProvider = ({ children }) => {
|
|
270 |
return [...boards];
|
271 |
}, []);
|
272 |
|
273 |
-
// Fonction pour compter les leaderboards par langue
|
274 |
-
const getLanguageStats = useCallback((boards) => {
|
275 |
-
const stats = new Map();
|
276 |
-
|
277 |
-
boards.forEach((board) => {
|
278 |
-
board.tags?.forEach((tag) => {
|
279 |
-
if (tag.startsWith("language:")) {
|
280 |
-
const language = tag.split(":")[1];
|
281 |
-
const capitalizedLang =
|
282 |
-
language.charAt(0).toUpperCase() + language.slice(1);
|
283 |
-
const count = stats.get(capitalizedLang) || 0;
|
284 |
-
stats.set(capitalizedLang, count + 1);
|
285 |
-
}
|
286 |
-
});
|
287 |
-
});
|
288 |
-
|
289 |
-
return stats;
|
290 |
-
}, []);
|
291 |
-
|
292 |
-
// Calculate total number of unique leaderboards (excluding duplicates)
|
293 |
-
const totalLeaderboards = useMemo(() => {
|
294 |
-
// On ne compte que les leaderboards approuvés
|
295 |
-
const uniqueIds = new Set(
|
296 |
-
leaderboards
|
297 |
-
.filter((board) => board.approval_status === "approved")
|
298 |
-
.map((board) => board.id)
|
299 |
-
);
|
300 |
-
return uniqueIds.size;
|
301 |
-
}, [leaderboards]);
|
302 |
-
|
303 |
// Filter functions for categories
|
304 |
const filterByTag = useCallback((tag, boards) => {
|
305 |
const searchTag = tag.toLowerCase();
|
306 |
-
console.log("Filtering by tag:", {
|
307 |
-
searchTag,
|
308 |
-
boards: boards?.map((board) => ({
|
309 |
-
id: board.id,
|
310 |
-
tags: board.tags,
|
311 |
-
matches: {
|
312 |
-
tags: board.tags?.some((t) => t.toLowerCase() === searchTag),
|
313 |
-
},
|
314 |
-
})),
|
315 |
-
});
|
316 |
return (
|
317 |
boards?.filter((board) =>
|
318 |
board.tags?.some((t) => t.toLowerCase() === searchTag)
|
@@ -331,257 +156,23 @@ export const LeaderboardProvider = ({ children }) => {
|
|
331 |
// Define sections with raw data
|
332 |
const allSections = useMemo(() => {
|
333 |
if (!leaderboards) return [];
|
334 |
-
|
335 |
-
// Garder une trace des leaderboards déjà catégorisés
|
336 |
-
const categorizedIds = new Set();
|
337 |
-
|
338 |
-
const sections = [
|
339 |
-
// Science
|
340 |
-
{
|
341 |
-
id: "code",
|
342 |
-
title: "Code",
|
343 |
-
data: filterByTag("eval:code", leaderboards).map((board) => {
|
344 |
-
categorizedIds.add(board.id);
|
345 |
-
return board;
|
346 |
-
}),
|
347 |
-
},
|
348 |
-
{
|
349 |
-
id: "math",
|
350 |
-
title: "Math",
|
351 |
-
data: filterByTag("eval:math", leaderboards).map((board) => {
|
352 |
-
categorizedIds.add(board.id);
|
353 |
-
return board;
|
354 |
-
}),
|
355 |
-
},
|
356 |
-
{
|
357 |
-
id: "biology",
|
358 |
-
title: "Biology",
|
359 |
-
data: filterByTag("domain:biology", leaderboards).map((board) => {
|
360 |
-
categorizedIds.add(board.id);
|
361 |
-
return board;
|
362 |
-
}),
|
363 |
-
},
|
364 |
-
{
|
365 |
-
id: "chemistry",
|
366 |
-
title: "Chemistry",
|
367 |
-
data: filterByTag("domain:chemistry", leaderboards).map((board) => {
|
368 |
-
categorizedIds.add(board.id);
|
369 |
-
return board;
|
370 |
-
}),
|
371 |
-
},
|
372 |
-
{
|
373 |
-
id: "physics",
|
374 |
-
title: "Physics",
|
375 |
-
data: filterByTag("domain:physics", leaderboards).map((board) => {
|
376 |
-
categorizedIds.add(board.id);
|
377 |
-
return board;
|
378 |
-
}),
|
379 |
-
},
|
380 |
-
// Modalities
|
381 |
-
{
|
382 |
-
id: "image",
|
383 |
-
title: "Image",
|
384 |
-
data: filterByTag("modality:image", leaderboards).map((board) => {
|
385 |
-
categorizedIds.add(board.id);
|
386 |
-
return board;
|
387 |
-
}),
|
388 |
-
},
|
389 |
-
{
|
390 |
-
id: "video",
|
391 |
-
title: "Video",
|
392 |
-
data: filterByTag("modality:video", leaderboards).map((board) => {
|
393 |
-
categorizedIds.add(board.id);
|
394 |
-
return board;
|
395 |
-
}),
|
396 |
-
},
|
397 |
-
{
|
398 |
-
id: "audio",
|
399 |
-
title: "Audio",
|
400 |
-
data: filterByTag("modality:audio", leaderboards).map((board) => {
|
401 |
-
categorizedIds.add(board.id);
|
402 |
-
return board;
|
403 |
-
}),
|
404 |
-
},
|
405 |
-
{
|
406 |
-
id: "text",
|
407 |
-
title: "Text",
|
408 |
-
data: filterByTag("modality:text", leaderboards).map((board) => {
|
409 |
-
categorizedIds.add(board.id);
|
410 |
-
return board;
|
411 |
-
}),
|
412 |
-
},
|
413 |
-
{
|
414 |
-
id: "threeD",
|
415 |
-
title: "3D",
|
416 |
-
data: filterByTag("modality:3d", leaderboards).map((board) => {
|
417 |
-
categorizedIds.add(board.id);
|
418 |
-
return board;
|
419 |
-
}),
|
420 |
-
},
|
421 |
-
{
|
422 |
-
id: "embeddings",
|
423 |
-
title: "Embeddings",
|
424 |
-
data: filterByTag("modality:artefacts", leaderboards).map((board) => {
|
425 |
-
categorizedIds.add(board.id);
|
426 |
-
return board;
|
427 |
-
}),
|
428 |
-
},
|
429 |
-
// LLM Capabilities
|
430 |
-
{
|
431 |
-
id: "rag",
|
432 |
-
title: "RAG",
|
433 |
-
data: filterByTag("eval:rag", leaderboards).map((board) => {
|
434 |
-
categorizedIds.add(board.id);
|
435 |
-
return board;
|
436 |
-
}),
|
437 |
-
},
|
438 |
-
{
|
439 |
-
id: "reasoning",
|
440 |
-
title: "Reasoning",
|
441 |
-
data: filterByTag("eval:reasoning", leaderboards).map((board) => {
|
442 |
-
categorizedIds.add(board.id);
|
443 |
-
return board;
|
444 |
-
}),
|
445 |
-
},
|
446 |
-
{
|
447 |
-
id: "agentic",
|
448 |
-
title: "Agentic",
|
449 |
-
data: filterByTag("modality:agent", leaderboards).map((board) => {
|
450 |
-
categorizedIds.add(board.id);
|
451 |
-
return board;
|
452 |
-
}),
|
453 |
-
},
|
454 |
-
{
|
455 |
-
id: "safety",
|
456 |
-
title: "Safety",
|
457 |
-
data: filterByTag("eval:safety", leaderboards).map((board) => {
|
458 |
-
categorizedIds.add(board.id);
|
459 |
-
return board;
|
460 |
-
}),
|
461 |
-
},
|
462 |
-
{
|
463 |
-
id: "performance",
|
464 |
-
title: "Performance",
|
465 |
-
data: filterByTag("eval:performance", leaderboards).map((board) => {
|
466 |
-
categorizedIds.add(board.id);
|
467 |
-
return board;
|
468 |
-
}),
|
469 |
-
},
|
470 |
-
{
|
471 |
-
id: "hallucination",
|
472 |
-
title: "Hallucination",
|
473 |
-
data: filterByTag("eval:hallucination", leaderboards).map((board) => {
|
474 |
-
categorizedIds.add(board.id);
|
475 |
-
return board;
|
476 |
-
}),
|
477 |
-
},
|
478 |
-
// Domain Specific
|
479 |
-
{
|
480 |
-
id: "medical",
|
481 |
-
title: "Medical",
|
482 |
-
data: filterByTag("domain:medical", leaderboards).map((board) => {
|
483 |
-
categorizedIds.add(board.id);
|
484 |
-
return board;
|
485 |
-
}),
|
486 |
-
},
|
487 |
-
{
|
488 |
-
id: "financial",
|
489 |
-
title: "Financial",
|
490 |
-
data: filterByTag("domain:financial", leaderboards).map((board) => {
|
491 |
-
categorizedIds.add(board.id);
|
492 |
-
return board;
|
493 |
-
}),
|
494 |
-
},
|
495 |
-
{
|
496 |
-
id: "legal",
|
497 |
-
title: "Legal",
|
498 |
-
data: filterByTag("domain:legal", leaderboards).map((board) => {
|
499 |
-
categorizedIds.add(board.id);
|
500 |
-
return board;
|
501 |
-
}),
|
502 |
-
},
|
503 |
-
{
|
504 |
-
id: "commercial",
|
505 |
-
title: "Commercial",
|
506 |
-
data: filterByTag("domain:commercial", leaderboards).map((board) => {
|
507 |
-
categorizedIds.add(board.id);
|
508 |
-
return board;
|
509 |
-
}),
|
510 |
-
},
|
511 |
-
// Language Related
|
512 |
-
{
|
513 |
-
id: "language",
|
514 |
-
title: "Language Specific",
|
515 |
-
data: filterByLanguage(leaderboards).map((board) => {
|
516 |
-
categorizedIds.add(board.id);
|
517 |
-
return board;
|
518 |
-
}),
|
519 |
-
},
|
520 |
-
{
|
521 |
-
id: "translation",
|
522 |
-
title: "Translation",
|
523 |
-
data: filterByTag("domain:translation", leaderboards).map((board) => {
|
524 |
-
categorizedIds.add(board.id);
|
525 |
-
return board;
|
526 |
-
}),
|
527 |
-
},
|
528 |
-
// Misc
|
529 |
-
{
|
530 |
-
id: "uncategorized",
|
531 |
-
title: "Uncategorized",
|
532 |
-
data: leaderboards
|
533 |
-
.filter(isUncategorized)
|
534 |
-
.filter((board) => board.approval_status === "approved"),
|
535 |
-
},
|
536 |
-
];
|
537 |
-
|
538 |
-
return sections;
|
539 |
}, [leaderboards, filterByTag, filterByLanguage]);
|
540 |
|
541 |
// Get sections with data
|
542 |
const sections = useMemo(() => {
|
543 |
-
|
544 |
-
|
545 |
-
"All sections before filtering:",
|
546 |
-
allSections.map((s) => ({
|
547 |
-
title: s.title,
|
548 |
-
count: s.data.length,
|
549 |
-
ids: s.data.map((b) => b.id),
|
550 |
-
}))
|
551 |
-
);
|
552 |
-
|
553 |
-
// On garde une trace des titres déjà vus
|
554 |
-
const seenTitles = new Set();
|
555 |
-
const filteredSections = allSections.filter((section) => {
|
556 |
-
console.log(`\nAnalyzing section ${section.title}:`, {
|
557 |
-
data: section.data,
|
558 |
-
count: section.data.length,
|
559 |
-
uniqueIds: new Set(section.data.map((b) => b.id)).size,
|
560 |
-
boards: section.data.map((board) => ({
|
561 |
-
id: board.id,
|
562 |
-
tags: board.tags,
|
563 |
-
})),
|
564 |
-
});
|
565 |
-
|
566 |
-
// On garde la section si elle a des données et qu'on ne l'a pas déjà vue
|
567 |
-
if (section.data.length > 0 && !seenTitles.has(section.title)) {
|
568 |
-
seenTitles.add(section.title);
|
569 |
-
return true;
|
570 |
-
}
|
571 |
-
return false;
|
572 |
-
});
|
573 |
|
574 |
-
|
575 |
-
|
576 |
-
|
577 |
-
|
578 |
-
|
579 |
-
|
580 |
-
}))
|
581 |
);
|
582 |
-
|
583 |
-
|
584 |
-
}, [allSections]);
|
585 |
|
586 |
// Get filtered count
|
587 |
const filteredCount = useMemo(() => {
|
@@ -658,8 +249,6 @@ export const LeaderboardProvider = ({ children }) => {
|
|
658 |
setSelectedLanguage: handleLanguageSelection,
|
659 |
expandedSections,
|
660 |
setExpandedSections,
|
661 |
-
getLanguageStats,
|
662 |
-
getSectionLeaderboards,
|
663 |
resetState,
|
664 |
isLanguageExpanded,
|
665 |
setIsLanguageExpanded,
|
|
|
8 |
} from "react";
|
9 |
import { normalizeTags } from "../utils/tagFilters";
|
10 |
import { useUrlState } from "../hooks/useUrlState";
|
11 |
+
import {
|
12 |
+
CATEGORIZATION_TAGS,
|
13 |
+
isUncategorized,
|
14 |
+
filterLeaderboards as filterLeaderboardsUtil,
|
15 |
+
generateSections,
|
16 |
+
} from "../utils/filterUtils";
|
17 |
|
18 |
const LeaderboardContext = createContext();
|
19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
// Helper pour déterminer si un leaderboard est non catégorisé
|
21 |
+
const isUncategorizedBoard = isUncategorized;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
|
23 |
export const LeaderboardProvider = ({ children }) => {
|
24 |
const { params, updateParams } = useUrlState();
|
|
|
106 |
// Filter leaderboards based on search query and arena toggle, ignoring category selection
|
107 |
const filterLeaderboardsForCount = useCallback(
|
108 |
(boards) => {
|
109 |
+
return filterLeaderboardsUtil({
|
110 |
+
boards,
|
111 |
+
searchQuery,
|
112 |
+
arenaOnly,
|
113 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
},
|
115 |
[searchQuery, arenaOnly]
|
116 |
);
|
|
|
118 |
// Filter leaderboards based on all criteria including categories and language selection
|
119 |
const filterLeaderboards = useCallback(
|
120 |
(boards) => {
|
121 |
+
return filterLeaderboardsUtil({
|
122 |
+
boards,
|
123 |
+
searchQuery,
|
124 |
+
arenaOnly,
|
125 |
+
selectedCategories,
|
126 |
+
selectedLanguage,
|
127 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
},
|
129 |
[searchQuery, arenaOnly, selectedCategories, selectedLanguage]
|
130 |
);
|
|
|
135 |
return [...boards];
|
136 |
}, []);
|
137 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
138 |
// Filter functions for categories
|
139 |
const filterByTag = useCallback((tag, boards) => {
|
140 |
const searchTag = tag.toLowerCase();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
141 |
return (
|
142 |
boards?.filter((board) =>
|
143 |
board.tags?.some((t) => t.toLowerCase() === searchTag)
|
|
|
156 |
// Define sections with raw data
|
157 |
const allSections = useMemo(() => {
|
158 |
if (!leaderboards) return [];
|
159 |
+
return generateSections(leaderboards, filterByTag, filterByLanguage);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
}, [leaderboards, filterByTag, filterByLanguage]);
|
161 |
|
162 |
// Get sections with data
|
163 |
const sections = useMemo(() => {
|
164 |
+
return allSections.filter((section) => section.data.length > 0);
|
165 |
+
}, [allSections]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
166 |
|
167 |
+
// Calculate total number of unique leaderboards (excluding duplicates)
|
168 |
+
const totalLeaderboards = useMemo(() => {
|
169 |
+
const uniqueIds = new Set(
|
170 |
+
leaderboards
|
171 |
+
.filter((board) => board.approval_status === "approved")
|
172 |
+
.map((board) => board.id)
|
|
|
173 |
);
|
174 |
+
return uniqueIds.size;
|
175 |
+
}, [leaderboards]);
|
|
|
176 |
|
177 |
// Get filtered count
|
178 |
const filteredCount = useMemo(() => {
|
|
|
249 |
setSelectedLanguage: handleLanguageSelection,
|
250 |
expandedSections,
|
251 |
setExpandedSections,
|
|
|
|
|
252 |
resetState,
|
253 |
isLanguageExpanded,
|
254 |
setIsLanguageExpanded,
|
client/src/hooks/useDebounce.js
CHANGED
@@ -4,6 +4,13 @@ export const useDebounce = (value, delay = 200) => {
|
|
4 |
const [debouncedValue, setDebouncedValue] = useState(value);
|
5 |
|
6 |
useEffect(() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
const timer = setTimeout(() => {
|
8 |
setDebouncedValue(value);
|
9 |
}, delay);
|
|
|
4 |
const [debouncedValue, setDebouncedValue] = useState(value);
|
5 |
|
6 |
useEffect(() => {
|
7 |
+
// Si la valeur est vide, on met à jour immédiatement
|
8 |
+
if (!value || !value.trim()) {
|
9 |
+
setDebouncedValue("");
|
10 |
+
return;
|
11 |
+
}
|
12 |
+
|
13 |
+
// Sinon on attend le délai
|
14 |
const timer = setTimeout(() => {
|
15 |
setDebouncedValue(value);
|
16 |
}, delay);
|
client/src/pages/LeaderboardPage/LeaderboardPage.jsx
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import React, { useState, useEffect } from "react";
|
2 |
import { Box, CircularProgress, Typography } from "@mui/material";
|
3 |
import SearchOffIcon from "@mui/icons-material/SearchOff";
|
4 |
import Logo from "../../components/Logo/Logo";
|
@@ -23,6 +23,28 @@ const LeaderboardPageContent = () => {
|
|
23 |
selectedCategories,
|
24 |
} = useLeaderboard();
|
25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
useEffect(() => {
|
27 |
fetch(API_URLS.leaderboards)
|
28 |
.then((res) => {
|
@@ -145,80 +167,98 @@ const LeaderboardPageContent = () => {
|
|
145 |
</Box>
|
146 |
)}
|
147 |
|
148 |
-
{
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
// Agréger les données de toutes les sections sélectionnées
|
171 |
-
const selectedSections = sections.filter(({ id }) =>
|
172 |
-
selectedCategories.has(id)
|
173 |
-
);
|
174 |
-
|
175 |
-
// Créer un titre combiné
|
176 |
-
const combinedTitle = selectedSections
|
177 |
-
.map(({ title }) => title)
|
178 |
-
.join(" + ");
|
179 |
-
|
180 |
-
// Agréger les leaderboards
|
181 |
-
const combinedData = selectedSections.reduce(
|
182 |
-
(acc, { data }) => [...acc, ...data],
|
183 |
-
[]
|
184 |
-
);
|
185 |
-
|
186 |
-
// Filtrer les doublons par ID
|
187 |
-
const uniqueData = Array.from(
|
188 |
-
new Map(
|
189 |
-
combinedData.map((item) => [item.id, item])
|
190 |
-
).values()
|
191 |
-
);
|
192 |
-
|
193 |
-
const filteredLeaderboards = filterLeaderboards(uniqueData);
|
194 |
-
|
195 |
return (
|
196 |
-
<Box key=
|
197 |
<LeaderboardSection
|
198 |
-
id=
|
199 |
-
title={
|
200 |
-
leaderboards={
|
201 |
filteredLeaderboards={filteredLeaderboards}
|
202 |
/>
|
203 |
</Box>
|
204 |
);
|
205 |
-
})
|
206 |
-
:
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
211 |
return (
|
212 |
-
<Box key=
|
213 |
<LeaderboardSection
|
214 |
-
id=
|
215 |
-
title={
|
216 |
-
leaderboards={
|
217 |
filteredLeaderboards={filteredLeaderboards}
|
218 |
/>
|
219 |
</Box>
|
220 |
);
|
221 |
-
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
222 |
</Box>
|
223 |
)}
|
224 |
</Box>
|
|
|
1 |
+
import React, { useState, useEffect, useMemo } from "react";
|
2 |
import { Box, CircularProgress, Typography } from "@mui/material";
|
3 |
import SearchOffIcon from "@mui/icons-material/SearchOff";
|
4 |
import Logo from "../../components/Logo/Logo";
|
|
|
23 |
selectedCategories,
|
24 |
} = useLeaderboard();
|
25 |
|
26 |
+
// Vérifier si on a uniquement une recherche textuelle active
|
27 |
+
const isOnlyTextSearch =
|
28 |
+
searchQuery && !arenaOnly && selectedCategories.size === 0;
|
29 |
+
|
30 |
+
// Obtenir tous les leaderboards uniques de toutes les sections
|
31 |
+
const allUniqueLeaderboards = useMemo(() => {
|
32 |
+
if (!allSections) return [];
|
33 |
+
return Array.from(
|
34 |
+
new Set(
|
35 |
+
allSections.reduce((acc, section) => {
|
36 |
+
return [...acc, ...(section.data || [])];
|
37 |
+
}, [])
|
38 |
+
)
|
39 |
+
);
|
40 |
+
}, [allSections]);
|
41 |
+
|
42 |
+
// Filtrer tous les leaderboards pour la recherche textuelle
|
43 |
+
const searchResults = useMemo(() => {
|
44 |
+
if (!isOnlyTextSearch) return [];
|
45 |
+
return filterLeaderboards(allUniqueLeaderboards);
|
46 |
+
}, [isOnlyTextSearch, filterLeaderboards, allUniqueLeaderboards]);
|
47 |
+
|
48 |
useEffect(() => {
|
49 |
fetch(API_URLS.leaderboards)
|
50 |
.then((res) => {
|
|
|
167 |
</Box>
|
168 |
)}
|
169 |
|
170 |
+
{isOnlyTextSearch ? (
|
171 |
+
// Vue spéciale pour la recherche textuelle
|
172 |
+
<Box key="search-results">
|
173 |
+
<LeaderboardSection
|
174 |
+
id="search-results"
|
175 |
+
title={`All leaderboards matching "${searchQuery}"`}
|
176 |
+
leaderboards={allUniqueLeaderboards}
|
177 |
+
filteredLeaderboards={searchResults}
|
178 |
+
/>
|
179 |
+
</Box>
|
180 |
+
) : selectedCategories.size > 0 ? (
|
181 |
+
// Si des catégories sont sélectionnées
|
182 |
+
selectedCategories.size === 1 ? (
|
183 |
+
// Si une seule catégorie est sélectionnée, on affiche sa section
|
184 |
+
sections
|
185 |
+
.filter(({ id }) => selectedCategories.has(id))
|
186 |
+
.map(({ id, title, data }) => {
|
187 |
+
const filteredLeaderboards = filterLeaderboards(data);
|
188 |
+
// Ajouter le terme de recherche au titre si présent
|
189 |
+
const sectionTitle = searchQuery
|
190 |
+
? `${title} matching "${searchQuery}"`
|
191 |
+
: title;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
return (
|
193 |
+
<Box key={id} id={id}>
|
194 |
<LeaderboardSection
|
195 |
+
id={id}
|
196 |
+
title={sectionTitle}
|
197 |
+
leaderboards={data}
|
198 |
filteredLeaderboards={filteredLeaderboards}
|
199 |
/>
|
200 |
</Box>
|
201 |
);
|
202 |
+
})
|
203 |
+
) : (
|
204 |
+
// Si plusieurs catégories sont sélectionnées, on les agrège
|
205 |
+
(() => {
|
206 |
+
// Agréger les données de toutes les sections sélectionnées
|
207 |
+
const selectedSections = sections.filter(({ id }) =>
|
208 |
+
selectedCategories.has(id)
|
209 |
+
);
|
210 |
+
|
211 |
+
// Créer un titre combiné avec le terme de recherche si présent
|
212 |
+
const combinedTitle = selectedSections
|
213 |
+
.map(({ title }) => title)
|
214 |
+
.join(" + ");
|
215 |
+
const finalTitle = searchQuery
|
216 |
+
? `${combinedTitle} matching "${searchQuery}"`
|
217 |
+
: combinedTitle;
|
218 |
+
|
219 |
+
// Agréger les leaderboards
|
220 |
+
const combinedData = selectedSections.reduce(
|
221 |
+
(acc, { data }) => [...acc, ...data],
|
222 |
+
[]
|
223 |
+
);
|
224 |
+
|
225 |
+
// Filtrer les doublons par ID
|
226 |
+
const uniqueData = Array.from(
|
227 |
+
new Map(combinedData.map((item) => [item.id, item])).values()
|
228 |
+
);
|
229 |
+
|
230 |
+
const filteredLeaderboards = filterLeaderboards(uniqueData);
|
231 |
+
|
232 |
return (
|
233 |
+
<Box key="combined">
|
234 |
<LeaderboardSection
|
235 |
+
id="combined"
|
236 |
+
title={finalTitle}
|
237 |
+
leaderboards={uniqueData}
|
238 |
filteredLeaderboards={filteredLeaderboards}
|
239 |
/>
|
240 |
</Box>
|
241 |
);
|
242 |
+
})()
|
243 |
+
)
|
244 |
+
) : (
|
245 |
+
// Si aucune catégorie n'est sélectionnée, on affiche toutes les sections avec des résultats
|
246 |
+
(hasLeaderboards || !isFiltering) &&
|
247 |
+
sections.map(({ id, title, data }) => {
|
248 |
+
const filteredLeaderboards = filterLeaderboards(data);
|
249 |
+
if (filteredLeaderboards.length === 0) return null;
|
250 |
+
return (
|
251 |
+
<Box key={id} id={id}>
|
252 |
+
<LeaderboardSection
|
253 |
+
id={id}
|
254 |
+
title={title}
|
255 |
+
leaderboards={data}
|
256 |
+
filteredLeaderboards={filteredLeaderboards}
|
257 |
+
/>
|
258 |
+
</Box>
|
259 |
+
);
|
260 |
+
})
|
261 |
+
)}
|
262 |
</Box>
|
263 |
)}
|
264 |
</Box>
|
client/src/utils/filterUtils.js
ADDED
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Map of category IDs to their corresponding tag checks
|
2 |
+
export const CATEGORY_TAG_MAPPING = {
|
3 |
+
agentic: (tags) => tags.includes("modality:agent"),
|
4 |
+
text: (tags) => tags.includes("modality:text"),
|
5 |
+
image: (tags) => tags.includes("modality:image"),
|
6 |
+
video: (tags) => tags.includes("modality:video"),
|
7 |
+
code: (tags) => tags.includes("eval:code"),
|
8 |
+
math: (tags) => tags.includes("eval:math"),
|
9 |
+
reasoning: (tags) => tags.includes("eval:reasoning"),
|
10 |
+
hallucination: (tags) => tags.includes("eval:hallucination"),
|
11 |
+
rag: (tags) => tags.includes("eval:rag"),
|
12 |
+
embeddings: (tags) => tags.includes("modality:artefacts"),
|
13 |
+
language: (tags) => tags.some((tag) => tag.startsWith("language:")),
|
14 |
+
vision: (tags) =>
|
15 |
+
tags.some((tag) => tag === "modality:video" || tag === "modality:image"),
|
16 |
+
threeD: (tags) => tags.includes("modality:3d"),
|
17 |
+
audio: (tags) => tags.includes("modality:audio"),
|
18 |
+
financial: (tags) => tags.includes("domain:financial"),
|
19 |
+
medical: (tags) => tags.includes("domain:medical"),
|
20 |
+
legal: (tags) => tags.includes("domain:legal"),
|
21 |
+
biology: (tags) => tags.includes("domain:biology"),
|
22 |
+
commercial: (tags) => tags.includes("domain:commercial"),
|
23 |
+
translation: (tags) => tags.includes("domain:translation"),
|
24 |
+
chemistry: (tags) => tags.includes("domain:chemistry"),
|
25 |
+
physics: (tags) => tags.includes("domain:physics"),
|
26 |
+
safety: (tags) => tags.includes("eval:safety"),
|
27 |
+
performance: (tags) => tags.includes("eval:performance"),
|
28 |
+
};
|
29 |
+
|
30 |
+
// Constantes pour les tags de catégorisation
|
31 |
+
export const CATEGORIZATION_TAGS = [
|
32 |
+
"modality:agent",
|
33 |
+
"modality:artefacts",
|
34 |
+
"modality:text",
|
35 |
+
"eval:code",
|
36 |
+
"eval:math",
|
37 |
+
"eval:reasoning",
|
38 |
+
"eval:hallucination",
|
39 |
+
"modality:video",
|
40 |
+
"modality:image",
|
41 |
+
"modality:3d",
|
42 |
+
"modality:audio",
|
43 |
+
"domain:financial",
|
44 |
+
"domain:medical",
|
45 |
+
"domain:legal",
|
46 |
+
"domain:biology",
|
47 |
+
"domain:translation",
|
48 |
+
"domain:chemistry",
|
49 |
+
"domain:physics",
|
50 |
+
"domain:commercial",
|
51 |
+
"eval:safety",
|
52 |
+
"eval:performance",
|
53 |
+
"eval:rag",
|
54 |
+
];
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Vérifie si un leaderboard est non catégorisé
|
58 |
+
*/
|
59 |
+
export const isUncategorized = (board) => {
|
60 |
+
const tags = board.tags || [];
|
61 |
+
return !tags.some(
|
62 |
+
(tag) => CATEGORIZATION_TAGS.includes(tag) || tag.startsWith("language:")
|
63 |
+
);
|
64 |
+
};
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Applique les filtres de catégorie à un leaderboard
|
68 |
+
*/
|
69 |
+
export const applyCategoryFilters = (
|
70 |
+
board,
|
71 |
+
selectedCategories,
|
72 |
+
excludedCategory = null
|
73 |
+
) => {
|
74 |
+
if (selectedCategories.size === 0) return true;
|
75 |
+
|
76 |
+
const tags = board.tags || [];
|
77 |
+
return Array.from(selectedCategories)
|
78 |
+
.filter((category) => category !== excludedCategory)
|
79 |
+
.every((category) => {
|
80 |
+
if (category === "uncategorized") {
|
81 |
+
return isUncategorized(board);
|
82 |
+
}
|
83 |
+
const tagCheck = CATEGORY_TAG_MAPPING[category];
|
84 |
+
return tagCheck ? tagCheck(tags) : false;
|
85 |
+
});
|
86 |
+
};
|
87 |
+
|
88 |
+
/**
|
89 |
+
* Filtre une liste de leaderboards selon les critères donnés
|
90 |
+
*/
|
91 |
+
export const filterLeaderboards = ({
|
92 |
+
boards,
|
93 |
+
searchQuery = "",
|
94 |
+
arenaOnly = false,
|
95 |
+
selectedCategories = new Set(),
|
96 |
+
selectedLanguage = new Set(),
|
97 |
+
excludedCategory = null,
|
98 |
+
}) => {
|
99 |
+
if (!boards) return [];
|
100 |
+
|
101 |
+
let filtered = [...boards];
|
102 |
+
|
103 |
+
// Filter by search query
|
104 |
+
if (searchQuery) {
|
105 |
+
const query = searchQuery.toLowerCase();
|
106 |
+
const tagMatch = query.match(/^(\w+):(\w+)$/);
|
107 |
+
|
108 |
+
if (tagMatch) {
|
109 |
+
const [_, category, value] = tagMatch;
|
110 |
+
const searchTag = `${category}:${value}`.toLowerCase();
|
111 |
+
filtered = filtered.filter((board) => {
|
112 |
+
const allTags = [...(board.tags || []), ...(board.editor_tags || [])];
|
113 |
+
return allTags.some((tag) => tag.toLowerCase() === searchTag);
|
114 |
+
});
|
115 |
+
} else {
|
116 |
+
filtered = filtered.filter((board) =>
|
117 |
+
board.card_data?.title?.toLowerCase().includes(query)
|
118 |
+
);
|
119 |
+
}
|
120 |
+
}
|
121 |
+
|
122 |
+
// Filter arena only
|
123 |
+
if (arenaOnly) {
|
124 |
+
filtered = filtered.filter((board) => board.tags?.includes("judge:humans"));
|
125 |
+
}
|
126 |
+
|
127 |
+
// Filter by selected languages
|
128 |
+
if (selectedLanguage.size > 0) {
|
129 |
+
filtered = filtered.filter((board) =>
|
130 |
+
Array.from(selectedLanguage).every((lang) =>
|
131 |
+
board.tags?.some(
|
132 |
+
(tag) => tag.toLowerCase() === `language:${lang.toLowerCase()}`
|
133 |
+
)
|
134 |
+
)
|
135 |
+
);
|
136 |
+
}
|
137 |
+
|
138 |
+
// Filter by categories
|
139 |
+
if (selectedCategories.size > 0) {
|
140 |
+
filtered = filtered.filter((board) =>
|
141 |
+
applyCategoryFilters(board, selectedCategories, excludedCategory)
|
142 |
+
);
|
143 |
+
}
|
144 |
+
|
145 |
+
return filtered;
|
146 |
+
};
|
147 |
+
|
148 |
+
// Structure des sections avec leurs groupes
|
149 |
+
export const SECTIONS_STRUCTURE = {
|
150 |
+
science: [
|
151 |
+
{ id: "code", title: "Code", tag: "eval:code" },
|
152 |
+
{ id: "math", title: "Math", tag: "eval:math" },
|
153 |
+
{ id: "biology", title: "Biology", tag: "domain:biology" },
|
154 |
+
{ id: "chemistry", title: "Chemistry", tag: "domain:chemistry" },
|
155 |
+
{ id: "physics", title: "Physics", tag: "domain:physics" },
|
156 |
+
],
|
157 |
+
modalities: [
|
158 |
+
{ id: "image", title: "Image", tag: "modality:image" },
|
159 |
+
{ id: "video", title: "Video", tag: "modality:video" },
|
160 |
+
{ id: "audio", title: "Audio", tag: "modality:audio" },
|
161 |
+
{ id: "text", title: "Text", tag: "modality:text" },
|
162 |
+
{ id: "threeD", title: "3D", tag: "modality:3d" },
|
163 |
+
{ id: "embeddings", title: "Embeddings", tag: "modality:artefacts" },
|
164 |
+
],
|
165 |
+
llm_capabilities: [
|
166 |
+
{ id: "rag", title: "RAG", tag: "eval:rag" },
|
167 |
+
{ id: "reasoning", title: "Reasoning", tag: "eval:reasoning" },
|
168 |
+
{ id: "agentic", title: "Agentic", tag: "modality:agent" },
|
169 |
+
{ id: "safety", title: "Safety", tag: "eval:safety" },
|
170 |
+
{ id: "performance", title: "Performance", tag: "eval:performance" },
|
171 |
+
{ id: "hallucination", title: "Hallucination", tag: "eval:hallucination" },
|
172 |
+
],
|
173 |
+
domain_specific: [
|
174 |
+
{ id: "medical", title: "Medical", tag: "domain:medical" },
|
175 |
+
{ id: "financial", title: "Financial", tag: "domain:financial" },
|
176 |
+
{ id: "legal", title: "Legal", tag: "domain:legal" },
|
177 |
+
{ id: "commercial", title: "Commercial", tag: "domain:commercial" },
|
178 |
+
],
|
179 |
+
language_related: [
|
180 |
+
{ id: "language", title: "Language Specific", isLanguageFilter: true },
|
181 |
+
{ id: "translation", title: "Translation", tag: "domain:translation" },
|
182 |
+
],
|
183 |
+
};
|
184 |
+
|
185 |
+
/**
|
186 |
+
* Génère les sections avec leurs données
|
187 |
+
*/
|
188 |
+
export const generateSections = (
|
189 |
+
leaderboards,
|
190 |
+
filterByTag,
|
191 |
+
filterByLanguage
|
192 |
+
) => {
|
193 |
+
if (!leaderboards) return [];
|
194 |
+
|
195 |
+
const sections = [];
|
196 |
+
|
197 |
+
// Parcourir la structure des sections
|
198 |
+
Object.entries(SECTIONS_STRUCTURE).forEach(([group, groupSections]) => {
|
199 |
+
groupSections.forEach(({ id, title, tag, isLanguageFilter }) => {
|
200 |
+
let sectionData;
|
201 |
+
if (isLanguageFilter) {
|
202 |
+
sectionData = filterByLanguage(leaderboards);
|
203 |
+
} else {
|
204 |
+
sectionData = filterByTag(tag, leaderboards);
|
205 |
+
}
|
206 |
+
|
207 |
+
if (sectionData.length > 0) {
|
208 |
+
sections.push({
|
209 |
+
id,
|
210 |
+
title,
|
211 |
+
data: sectionData,
|
212 |
+
group,
|
213 |
+
});
|
214 |
+
}
|
215 |
+
});
|
216 |
+
});
|
217 |
+
|
218 |
+
return sections;
|
219 |
+
};
|