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 |
+
};
|