Spaces:
Running
Running
update
Browse files- .gitignore +5 -0
- client/src/components/LeaderboardFilters/LeaderboardFilters.jsx +38 -15
- client/src/components/LeaderboardSection.jsx +17 -9
- client/src/context/LeaderboardContext.jsx +47 -5
- client/src/pages/HowToSubmitPage/HowToSubmitPage.jsx +8 -1
- server/.env.example +4 -0
- server/pyproject.toml +1 -5
- server/server.py +1 -131
.gitignore
CHANGED
|
@@ -30,3 +30,8 @@ client/*.local
|
|
| 30 |
|
| 31 |
client/.env
|
| 32 |
server/.env
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
client/.env
|
| 32 |
server/.env
|
| 33 |
+
server/data/leaderboards_discussions.json
|
| 34 |
+
server/data/leaderboards_list.json
|
| 35 |
+
server/data/leaderboards_results.json
|
| 36 |
+
server/data/leaderboards_runtime.json
|
| 37 |
+
|
client/src/components/LeaderboardFilters/LeaderboardFilters.jsx
CHANGED
|
@@ -20,9 +20,9 @@ import { useMediaQuery } from "@mui/material";
|
|
| 20 |
const getSectionGroup = (id) => {
|
| 21 |
const groups = {
|
| 22 |
modalities: ["agentic", "vision", "audio"],
|
| 23 |
-
capabilities: ["code", "math"],
|
| 24 |
languages: ["language"],
|
| 25 |
-
domains: ["financial", "medical", "legal"],
|
| 26 |
evaluation: ["safety"],
|
| 27 |
misc: ["uncategorized"],
|
| 28 |
};
|
|
@@ -89,15 +89,25 @@ const LeaderboardFilters = ({ allSections }) => {
|
|
| 89 |
);
|
| 90 |
return categorySection ? categorySection.data.length : 0;
|
| 91 |
}
|
| 92 |
-
// Quand aucune catégorie n'est sélectionnée, on compte
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
}, [selectedCategory, allSections]);
|
| 98 |
|
| 99 |
// Calculer le nombre filtré en prenant en compte tous les filtres
|
| 100 |
const currentFilteredCount = useMemo(() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
if (selectedCategory) {
|
| 102 |
const categorySection = allSections.find(
|
| 103 |
(section) => section.id === selectedCategory
|
|
@@ -105,13 +115,26 @@ const LeaderboardFilters = ({ allSections }) => {
|
|
| 105 |
if (!categorySection) return 0;
|
| 106 |
return filterLeaderboards(categorySection.data).length;
|
| 107 |
}
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
const isMobile = useMediaQuery((theme) => theme.breakpoints.down("md"));
|
| 117 |
|
|
@@ -221,7 +244,7 @@ const LeaderboardFilters = ({ allSections }) => {
|
|
| 221 |
placeholder={
|
| 222 |
isMobile
|
| 223 |
? "Search by name or tags..."
|
| 224 |
-
: "Search by name or use domain:, language:, judge:, test:, modality:, submission:"
|
| 225 |
}
|
| 226 |
value={inputValue}
|
| 227 |
onChange={(e) => setInputValue(e.target.value)}
|
|
|
|
| 20 |
const getSectionGroup = (id) => {
|
| 21 |
const groups = {
|
| 22 |
modalities: ["agentic", "vision", "audio"],
|
| 23 |
+
capabilities: ["code", "math", "rag"],
|
| 24 |
languages: ["language"],
|
| 25 |
+
domains: ["financial", "medical", "legal", "biology"],
|
| 26 |
evaluation: ["safety"],
|
| 27 |
misc: ["uncategorized"],
|
| 28 |
};
|
|
|
|
| 89 |
);
|
| 90 |
return categorySection ? categorySection.data.length : 0;
|
| 91 |
}
|
| 92 |
+
// Quand aucune catégorie n'est sélectionnée, on compte les leaderboards uniques
|
| 93 |
+
const uniqueIds = new Set();
|
| 94 |
+
allSections.forEach((section) => {
|
| 95 |
+
section.data.forEach((board) => {
|
| 96 |
+
if (board.approval_status === "approved") {
|
| 97 |
+
uniqueIds.add(board.id);
|
| 98 |
+
}
|
| 99 |
+
});
|
| 100 |
+
});
|
| 101 |
+
return uniqueIds.size;
|
| 102 |
}, [selectedCategory, allSections]);
|
| 103 |
|
| 104 |
// Calculer le nombre filtré en prenant en compte tous les filtres
|
| 105 |
const currentFilteredCount = useMemo(() => {
|
| 106 |
+
// Si on a uniquement le filtre arena actif (pas de recherche ni de catégorie)
|
| 107 |
+
if (arenaOnly && !debouncedSearch && !selectedCategory) {
|
| 108 |
+
return totalArenaCount;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
if (selectedCategory) {
|
| 112 |
const categorySection = allSections.find(
|
| 113 |
(section) => section.id === selectedCategory
|
|
|
|
| 115 |
if (!categorySection) return 0;
|
| 116 |
return filterLeaderboards(categorySection.data).length;
|
| 117 |
}
|
| 118 |
+
|
| 119 |
+
// Quand aucune catégorie n'est sélectionnée, on utilise un Set pour éviter les doublons
|
| 120 |
+
const uniqueFilteredIds = new Set();
|
| 121 |
+
allSections.forEach((section) => {
|
| 122 |
+
const filteredBoards = filterLeaderboards(section.data);
|
| 123 |
+
filteredBoards.forEach((board) => {
|
| 124 |
+
if (board.approval_status === "approved") {
|
| 125 |
+
uniqueFilteredIds.add(board.id);
|
| 126 |
+
}
|
| 127 |
+
});
|
| 128 |
+
});
|
| 129 |
+
return uniqueFilteredIds.size;
|
| 130 |
+
}, [
|
| 131 |
+
selectedCategory,
|
| 132 |
+
allSections,
|
| 133 |
+
filterLeaderboards,
|
| 134 |
+
arenaOnly,
|
| 135 |
+
debouncedSearch,
|
| 136 |
+
totalArenaCount,
|
| 137 |
+
]);
|
| 138 |
|
| 139 |
const isMobile = useMediaQuery((theme) => theme.breakpoints.down("md"));
|
| 140 |
|
|
|
|
| 244 |
placeholder={
|
| 245 |
isMobile
|
| 246 |
? "Search by name or tags..."
|
| 247 |
+
: "Search by name or use domain:, language:, eval:, judge:, test:, modality:, submission:"
|
| 248 |
}
|
| 249 |
value={inputValue}
|
| 250 |
onChange={(e) => setInputValue(e.target.value)}
|
client/src/components/LeaderboardSection.jsx
CHANGED
|
@@ -181,14 +181,14 @@ const LeaderboardSection = ({
|
|
| 181 |
}, [id, leaderboards]);
|
| 182 |
|
| 183 |
// Calculer le nombre de leaderboards par langue
|
| 184 |
-
// On utilise les leaderboards
|
| 185 |
const languageStats = useMemo(() => {
|
| 186 |
if (!languages) return null;
|
| 187 |
const stats = new Map();
|
| 188 |
|
| 189 |
-
// Compter les leaderboards pour chaque langue
|
| 190 |
languages.forEach((lang) => {
|
| 191 |
-
const count =
|
| 192 |
board.tags?.some(
|
| 193 |
(tag) => tag.toLowerCase() === `language:${lang.toLowerCase()}`
|
| 194 |
)
|
|
@@ -197,7 +197,7 @@ const LeaderboardSection = ({
|
|
| 197 |
});
|
| 198 |
|
| 199 |
return stats;
|
| 200 |
-
}, [languages,
|
| 201 |
|
| 202 |
// Filtrer pour n'avoir que les leaderboards approuvés
|
| 203 |
const approvedLeaderboards = filteredLeaderboards.filter(
|
|
@@ -361,10 +361,16 @@ const LeaderboardSection = ({
|
|
| 361 |
return langFamily === family;
|
| 362 |
});
|
| 363 |
|
| 364 |
-
//
|
| 365 |
if (familyLanguages.length === 0 && family !== "Other Languages")
|
| 366 |
return null;
|
| 367 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
return (
|
| 369 |
<Box key={family} sx={{ mb: 3 }}>
|
| 370 |
<Typography
|
|
@@ -373,13 +379,11 @@ const LeaderboardSection = ({
|
|
| 373 |
color: "text.secondary",
|
| 374 |
mb: 1,
|
| 375 |
fontWeight: 500,
|
| 376 |
-
opacity: 0.8,
|
| 377 |
}}
|
| 378 |
>
|
| 379 |
{family}{" "}
|
| 380 |
-
{familyLanguages.length > 0
|
| 381 |
-
? `(${familyLanguages.length})`
|
| 382 |
-
: ""}
|
| 383 |
</Typography>
|
| 384 |
<Box
|
| 385 |
sx={{
|
|
@@ -392,6 +396,7 @@ const LeaderboardSection = ({
|
|
| 392 |
{familyLanguages.map((lang) => {
|
| 393 |
const isActive = selectedLanguage === lang;
|
| 394 |
const count = languageStats?.get(lang) || 0;
|
|
|
|
| 395 |
|
| 396 |
return (
|
| 397 |
<Button
|
|
@@ -401,9 +406,11 @@ const LeaderboardSection = ({
|
|
| 401 |
}
|
| 402 |
variant={isActive ? "contained" : "outlined"}
|
| 403 |
size="small"
|
|
|
|
| 404 |
sx={{
|
| 405 |
textTransform: "none",
|
| 406 |
m: 0.125,
|
|
|
|
| 407 |
backgroundColor: (theme) =>
|
| 408 |
isActive
|
| 409 |
? undefined
|
|
@@ -417,6 +424,7 @@ const LeaderboardSection = ({
|
|
| 417 |
: theme.palette.mode === "dark"
|
| 418 |
? "background.paper"
|
| 419 |
: "white",
|
|
|
|
| 420 |
},
|
| 421 |
"& .MuiTouchRipple-root": {
|
| 422 |
transition: "none",
|
|
|
|
| 181 |
}, [id, leaderboards]);
|
| 182 |
|
| 183 |
// Calculer le nombre de leaderboards par langue
|
| 184 |
+
// On utilise les leaderboards filtrés pour avoir les bonnes statistiques
|
| 185 |
const languageStats = useMemo(() => {
|
| 186 |
if (!languages) return null;
|
| 187 |
const stats = new Map();
|
| 188 |
|
| 189 |
+
// Compter les leaderboards pour chaque langue en tenant compte des filtres
|
| 190 |
languages.forEach((lang) => {
|
| 191 |
+
const count = filteredLeaderboards.filter((board) =>
|
| 192 |
board.tags?.some(
|
| 193 |
(tag) => tag.toLowerCase() === `language:${lang.toLowerCase()}`
|
| 194 |
)
|
|
|
|
| 197 |
});
|
| 198 |
|
| 199 |
return stats;
|
| 200 |
+
}, [languages, filteredLeaderboards]);
|
| 201 |
|
| 202 |
// Filtrer pour n'avoir que les leaderboards approuvés
|
| 203 |
const approvedLeaderboards = filteredLeaderboards.filter(
|
|
|
|
| 361 |
return langFamily === family;
|
| 362 |
});
|
| 363 |
|
| 364 |
+
// Toujours afficher toutes les familles qui ont des langues dans la liste complète
|
| 365 |
if (familyLanguages.length === 0 && family !== "Other Languages")
|
| 366 |
return null;
|
| 367 |
|
| 368 |
+
// Calculer le nombre total de leaderboards dans cette famille
|
| 369 |
+
const familyTotal = familyLanguages.reduce(
|
| 370 |
+
(sum, lang) => sum + (languageStats?.get(lang) || 0),
|
| 371 |
+
0
|
| 372 |
+
);
|
| 373 |
+
|
| 374 |
return (
|
| 375 |
<Box key={family} sx={{ mb: 3 }}>
|
| 376 |
<Typography
|
|
|
|
| 379 |
color: "text.secondary",
|
| 380 |
mb: 1,
|
| 381 |
fontWeight: 500,
|
| 382 |
+
opacity: familyTotal === 0 ? 0.5 : 0.8,
|
| 383 |
}}
|
| 384 |
>
|
| 385 |
{family}{" "}
|
| 386 |
+
{familyLanguages.length > 0 ? `(${familyTotal})` : ""}
|
|
|
|
|
|
|
| 387 |
</Typography>
|
| 388 |
<Box
|
| 389 |
sx={{
|
|
|
|
| 396 |
{familyLanguages.map((lang) => {
|
| 397 |
const isActive = selectedLanguage === lang;
|
| 398 |
const count = languageStats?.get(lang) || 0;
|
| 399 |
+
const isDisabled = count === 0 && !isActive;
|
| 400 |
|
| 401 |
return (
|
| 402 |
<Button
|
|
|
|
| 406 |
}
|
| 407 |
variant={isActive ? "contained" : "outlined"}
|
| 408 |
size="small"
|
| 409 |
+
disabled={isDisabled}
|
| 410 |
sx={{
|
| 411 |
textTransform: "none",
|
| 412 |
m: 0.125,
|
| 413 |
+
opacity: isDisabled ? 0.5 : 1,
|
| 414 |
backgroundColor: (theme) =>
|
| 415 |
isActive
|
| 416 |
? undefined
|
|
|
|
| 424 |
: theme.palette.mode === "dark"
|
| 425 |
? "background.paper"
|
| 426 |
: "white",
|
| 427 |
+
opacity: isDisabled ? 0.5 : 0.8,
|
| 428 |
},
|
| 429 |
"& .MuiTouchRipple-root": {
|
| 430 |
transition: "none",
|
client/src/context/LeaderboardContext.jsx
CHANGED
|
@@ -45,7 +45,10 @@ const CATEGORIZATION_TAGS = [
|
|
| 45 |
"domain:financial",
|
| 46 |
"domain:medical",
|
| 47 |
"domain:legal",
|
|
|
|
| 48 |
"eval:safety",
|
|
|
|
|
|
|
| 49 |
];
|
| 50 |
|
| 51 |
// Helper pour déterminer si un leaderboard est non catégorisé
|
|
@@ -77,7 +80,9 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 77 |
const [selectedLanguage, setSelectedLanguage] = useState(
|
| 78 |
initialParams.language
|
| 79 |
);
|
| 80 |
-
const [expandedSections, setExpandedSections] = useState(
|
|
|
|
|
|
|
| 81 |
|
| 82 |
// Mettre à jour l'URL quand les filtres changent
|
| 83 |
useEffect(() => {
|
|
@@ -97,6 +102,7 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 97 |
setArenaOnly(params.arena);
|
| 98 |
setSelectedCategory(params.category);
|
| 99 |
setSelectedLanguage(params.language);
|
|
|
|
| 100 |
};
|
| 101 |
|
| 102 |
window.addEventListener("popstate", handleURLChange);
|
|
@@ -196,6 +202,8 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 196 |
return tags.includes("eval:code");
|
| 197 |
case "math":
|
| 198 |
return tags.includes("eval:math");
|
|
|
|
|
|
|
| 199 |
case "language":
|
| 200 |
return tags.some((tag) => tag.startsWith("language:"));
|
| 201 |
case "vision":
|
|
@@ -212,8 +220,12 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 212 |
return tags.includes("domain:medical");
|
| 213 |
case "legal":
|
| 214 |
return tags.includes("domain:legal");
|
|
|
|
|
|
|
| 215 |
case "safety":
|
| 216 |
return tags.includes("eval:safety");
|
|
|
|
|
|
|
| 217 |
case "uncategorized":
|
| 218 |
return isUncategorized(board);
|
| 219 |
default:
|
|
@@ -254,7 +266,12 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 254 |
|
| 255 |
// Calculate total number of unique leaderboards (excluding duplicates)
|
| 256 |
const totalLeaderboards = useMemo(() => {
|
| 257 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
return uniqueIds.size;
|
| 259 |
}, [leaderboards]);
|
| 260 |
|
|
@@ -300,12 +317,12 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 300 |
const getSectionGroup = (id) => {
|
| 301 |
const groups = {
|
| 302 |
agentic: ["agentic"],
|
| 303 |
-
capabilities: ["code", "math"],
|
| 304 |
languages: ["language"],
|
| 305 |
modalities: ["vision", "audio"],
|
| 306 |
threeD: ["threeD"],
|
| 307 |
-
domains: ["financial", "medical", "legal"],
|
| 308 |
-
evaluation: ["safety"],
|
| 309 |
misc: ["uncategorized"],
|
| 310 |
};
|
| 311 |
|
|
@@ -349,6 +366,14 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 349 |
return board;
|
| 350 |
}),
|
| 351 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
{
|
| 353 |
id: "language",
|
| 354 |
title: "Language Specific",
|
|
@@ -397,6 +422,14 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 397 |
return board;
|
| 398 |
}),
|
| 399 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
{
|
| 401 |
id: "legal",
|
| 402 |
title: "Legal",
|
|
@@ -413,6 +446,14 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 413 |
return board;
|
| 414 |
}),
|
| 415 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
{
|
| 417 |
id: "uncategorized",
|
| 418 |
title: "Uncategorized",
|
|
@@ -487,6 +528,7 @@ export const LeaderboardProvider = ({ children }) => {
|
|
| 487 |
"test:",
|
| 488 |
"modality:",
|
| 489 |
"submission:",
|
|
|
|
| 490 |
];
|
| 491 |
|
| 492 |
// Si c'est une recherche par tag, on ne highlight rien
|
|
|
|
| 45 |
"domain:financial",
|
| 46 |
"domain:medical",
|
| 47 |
"domain:legal",
|
| 48 |
+
"domain:biology",
|
| 49 |
"eval:safety",
|
| 50 |
+
"eval:performance",
|
| 51 |
+
"eval:rag",
|
| 52 |
];
|
| 53 |
|
| 54 |
// Helper pour déterminer si un leaderboard est non catégorisé
|
|
|
|
| 80 |
const [selectedLanguage, setSelectedLanguage] = useState(
|
| 81 |
initialParams.language
|
| 82 |
);
|
| 83 |
+
const [expandedSections, setExpandedSections] = useState(
|
| 84 |
+
new Set(initialParams.category ? [initialParams.category] : [])
|
| 85 |
+
);
|
| 86 |
|
| 87 |
// Mettre à jour l'URL quand les filtres changent
|
| 88 |
useEffect(() => {
|
|
|
|
| 102 |
setArenaOnly(params.arena);
|
| 103 |
setSelectedCategory(params.category);
|
| 104 |
setSelectedLanguage(params.language);
|
| 105 |
+
setExpandedSections(new Set(params.category ? [params.category] : []));
|
| 106 |
};
|
| 107 |
|
| 108 |
window.addEventListener("popstate", handleURLChange);
|
|
|
|
| 202 |
return tags.includes("eval:code");
|
| 203 |
case "math":
|
| 204 |
return tags.includes("eval:math");
|
| 205 |
+
case "rag":
|
| 206 |
+
return tags.includes("eval:rag");
|
| 207 |
case "language":
|
| 208 |
return tags.some((tag) => tag.startsWith("language:"));
|
| 209 |
case "vision":
|
|
|
|
| 220 |
return tags.includes("domain:medical");
|
| 221 |
case "legal":
|
| 222 |
return tags.includes("domain:legal");
|
| 223 |
+
case "biology":
|
| 224 |
+
return tags.includes("domain:biology");
|
| 225 |
case "safety":
|
| 226 |
return tags.includes("eval:safety");
|
| 227 |
+
case "performance":
|
| 228 |
+
return tags.includes("eval:performance");
|
| 229 |
case "uncategorized":
|
| 230 |
return isUncategorized(board);
|
| 231 |
default:
|
|
|
|
| 266 |
|
| 267 |
// Calculate total number of unique leaderboards (excluding duplicates)
|
| 268 |
const totalLeaderboards = useMemo(() => {
|
| 269 |
+
// On ne compte que les leaderboards approuvés
|
| 270 |
+
const uniqueIds = new Set(
|
| 271 |
+
leaderboards
|
| 272 |
+
.filter((board) => board.approval_status === "approved")
|
| 273 |
+
.map((board) => board.id)
|
| 274 |
+
);
|
| 275 |
return uniqueIds.size;
|
| 276 |
}, [leaderboards]);
|
| 277 |
|
|
|
|
| 317 |
const getSectionGroup = (id) => {
|
| 318 |
const groups = {
|
| 319 |
agentic: ["agentic"],
|
| 320 |
+
capabilities: ["code", "math", "rag"],
|
| 321 |
languages: ["language"],
|
| 322 |
modalities: ["vision", "audio"],
|
| 323 |
threeD: ["threeD"],
|
| 324 |
+
domains: ["financial", "medical", "legal", "biology"],
|
| 325 |
+
evaluation: ["safety", "performance"],
|
| 326 |
misc: ["uncategorized"],
|
| 327 |
};
|
| 328 |
|
|
|
|
| 366 |
return board;
|
| 367 |
}),
|
| 368 |
},
|
| 369 |
+
{
|
| 370 |
+
id: "rag",
|
| 371 |
+
title: "RAG",
|
| 372 |
+
data: filterByTag("eval:rag", leaderboards).map((board) => {
|
| 373 |
+
categorizedIds.add(board.id);
|
| 374 |
+
return board;
|
| 375 |
+
}),
|
| 376 |
+
},
|
| 377 |
{
|
| 378 |
id: "language",
|
| 379 |
title: "Language Specific",
|
|
|
|
| 422 |
return board;
|
| 423 |
}),
|
| 424 |
},
|
| 425 |
+
{
|
| 426 |
+
id: "biology",
|
| 427 |
+
title: "Biology",
|
| 428 |
+
data: filterByTag("domain:biology", leaderboards).map((board) => {
|
| 429 |
+
categorizedIds.add(board.id);
|
| 430 |
+
return board;
|
| 431 |
+
}),
|
| 432 |
+
},
|
| 433 |
{
|
| 434 |
id: "legal",
|
| 435 |
title: "Legal",
|
|
|
|
| 446 |
return board;
|
| 447 |
}),
|
| 448 |
},
|
| 449 |
+
{
|
| 450 |
+
id: "performance",
|
| 451 |
+
title: "Performance",
|
| 452 |
+
data: filterByTag("eval:performance", leaderboards).map((board) => {
|
| 453 |
+
categorizedIds.add(board.id);
|
| 454 |
+
return board;
|
| 455 |
+
}),
|
| 456 |
+
},
|
| 457 |
{
|
| 458 |
id: "uncategorized",
|
| 459 |
title: "Uncategorized",
|
|
|
|
| 528 |
"test:",
|
| 529 |
"modality:",
|
| 530 |
"submission:",
|
| 531 |
+
"eval:",
|
| 532 |
];
|
| 533 |
|
| 534 |
// Si c'est une recherche par tag, on ne highlight rien
|
client/src/pages/HowToSubmitPage/HowToSubmitPage.jsx
CHANGED
|
@@ -235,6 +235,7 @@ const getTagEmoji = (tag) => {
|
|
| 235 |
financial: "💰",
|
| 236 |
medical: "⚕️",
|
| 237 |
legal: "⚖️",
|
|
|
|
| 238 |
},
|
| 239 |
};
|
| 240 |
|
|
@@ -318,6 +319,7 @@ const TagSection = ({ title, description, tags, explanations }) => {
|
|
| 318 |
"Submission type",
|
| 319 |
"Test set status",
|
| 320 |
"Judges",
|
|
|
|
| 321 |
].includes(title);
|
| 322 |
|
| 323 |
return (
|
|
@@ -717,7 +719,12 @@ const HowToSubmitPage = () => {
|
|
| 717 |
<TagSection
|
| 718 |
title="Domain"
|
| 719 |
description="Indicates the specific domain of the leaderboard:"
|
| 720 |
-
tags={[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
/>
|
| 722 |
|
| 723 |
<Typography
|
|
|
|
| 235 |
financial: "💰",
|
| 236 |
medical: "⚕️",
|
| 237 |
legal: "⚖️",
|
| 238 |
+
biology: "🧬",
|
| 239 |
},
|
| 240 |
};
|
| 241 |
|
|
|
|
| 319 |
"Submission type",
|
| 320 |
"Test set status",
|
| 321 |
"Judges",
|
| 322 |
+
"Domain",
|
| 323 |
].includes(title);
|
| 324 |
|
| 325 |
return (
|
|
|
|
| 719 |
<TagSection
|
| 720 |
title="Domain"
|
| 721 |
description="Indicates the specific domain of the leaderboard:"
|
| 722 |
+
tags={[
|
| 723 |
+
"domain:financial",
|
| 724 |
+
"domain:medical",
|
| 725 |
+
"domain:legal",
|
| 726 |
+
"domain:biology",
|
| 727 |
+
]}
|
| 728 |
/>
|
| 729 |
|
| 730 |
<Typography
|
server/.env.example
CHANGED
|
@@ -11,3 +11,7 @@ HUGGING_FACE_STORAGE_FILE_PATH=final_leaderboards.json
|
|
| 11 |
|
| 12 |
# Update interval in minutes (optional, default: 15)
|
| 13 |
UPDATE_INTERVAL_MINUTES=15
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
# Update interval in minutes (optional, default: 15)
|
| 13 |
UPDATE_INTERVAL_MINUTES=15
|
| 14 |
+
|
| 15 |
+
# Server configuration
|
| 16 |
+
API_HOST=0.0.0.0
|
| 17 |
+
API_PORT=3002
|
server/pyproject.toml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
[tool.poetry]
|
| 2 |
name = "leaderboard-explorer-server"
|
| 3 |
version = "0.1.0"
|
| 4 |
-
description = "
|
| 5 |
authors = ["Your Name <[email protected]>"]
|
| 6 |
packages = [
|
| 7 |
{ include = "server.py" }
|
|
@@ -12,10 +12,6 @@ python = "^3.9"
|
|
| 12 |
fastapi = "^0.109.0"
|
| 13 |
uvicorn = "^0.27.0"
|
| 14 |
python-dotenv = "^1.0.0"
|
| 15 |
-
requests = "^2.31.0"
|
| 16 |
-
python-jose = {extras = ["cryptography"], version = "^3.3.0"}
|
| 17 |
-
apscheduler = "^3.10.4"
|
| 18 |
-
huggingface-hub = "^0.21.3"
|
| 19 |
|
| 20 |
[tool.poetry.scripts]
|
| 21 |
dev = "uvicorn server:app --reload --host 0.0.0.0 --port 3002"
|
|
|
|
| 1 |
[tool.poetry]
|
| 2 |
name = "leaderboard-explorer-server"
|
| 3 |
version = "0.1.0"
|
| 4 |
+
description = "Static file server for Leaderboard Explorer"
|
| 5 |
authors = ["Your Name <[email protected]>"]
|
| 6 |
packages = [
|
| 7 |
{ include = "server.py" }
|
|
|
|
| 12 |
fastapi = "^0.109.0"
|
| 13 |
uvicorn = "^0.27.0"
|
| 14 |
python-dotenv = "^1.0.0"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
[tool.poetry.scripts]
|
| 17 |
dev = "uvicorn server:app --reload --host 0.0.0.0 --port 3002"
|
server/server.py
CHANGED
|
@@ -1,20 +1,7 @@
|
|
| 1 |
-
from fastapi import FastAPI
|
| 2 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
from fastapi.staticfiles import StaticFiles
|
| 4 |
-
from fastapi.responses import FileResponse
|
| 5 |
-
from apscheduler.schedulers.background import BackgroundScheduler
|
| 6 |
-
from datetime import datetime
|
| 7 |
import os
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
-
from huggingface_hub import HfApi
|
| 10 |
-
import json
|
| 11 |
-
import logging
|
| 12 |
-
|
| 13 |
-
# Configure logging
|
| 14 |
-
logging.basicConfig(
|
| 15 |
-
level=logging.INFO,
|
| 16 |
-
format='%(asctime)s - %(levelname)s - %(message)s'
|
| 17 |
-
)
|
| 18 |
|
| 19 |
# Load environment variables
|
| 20 |
load_dotenv()
|
|
@@ -25,123 +12,6 @@ API_PORT = int(os.getenv("API_PORT", "3002"))
|
|
| 25 |
|
| 26 |
app = FastAPI()
|
| 27 |
|
| 28 |
-
# Add CORS middleware
|
| 29 |
-
app.add_middleware(
|
| 30 |
-
CORSMiddleware,
|
| 31 |
-
allow_origins=[
|
| 32 |
-
"http://localhost:5173", # Vite dev server
|
| 33 |
-
f"http://localhost:{API_PORT}", # API port
|
| 34 |
-
"https://huggingface.co", # HF main domain
|
| 35 |
-
"https://*.hf.space", # HF Spaces domains
|
| 36 |
-
],
|
| 37 |
-
allow_credentials=True,
|
| 38 |
-
allow_methods=["*"],
|
| 39 |
-
allow_headers=["*"],
|
| 40 |
-
)
|
| 41 |
-
|
| 42 |
-
# Cache storage
|
| 43 |
-
cache = {
|
| 44 |
-
"data": None,
|
| 45 |
-
"last_updated": None
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
# HF API configuration
|
| 49 |
-
HF_TOKEN = os.getenv("HUGGING_FACE_HUB_TOKEN")
|
| 50 |
-
REPO_ID = os.getenv("HUGGING_FACE_STORAGE_REPO")
|
| 51 |
-
FILE_PATH = os.getenv("HUGGING_FACE_STORAGE_FILE_PATH")
|
| 52 |
-
CACHE_DURATION_MINUTES = int(os.getenv("UPDATE_INTERVAL_MINUTES", "15"))
|
| 53 |
-
|
| 54 |
-
# Initialize HF API client
|
| 55 |
-
hf_api = HfApi(token=HF_TOKEN)
|
| 56 |
-
|
| 57 |
-
def fetch_leaderboards():
|
| 58 |
-
"""Fetch leaderboards data from Hugging Face"""
|
| 59 |
-
try:
|
| 60 |
-
logging.info(f"Fetching leaderboards from {REPO_ID}/{FILE_PATH}")
|
| 61 |
-
# Download the JSON file directly with force_download to ensure we get the latest version
|
| 62 |
-
json_path = hf_api.hf_hub_download(
|
| 63 |
-
repo_id=REPO_ID,
|
| 64 |
-
filename=FILE_PATH,
|
| 65 |
-
repo_type="dataset",
|
| 66 |
-
force_download=True, # Force download to ensure we get the latest version
|
| 67 |
-
force_filename="leaderboards_latest.json" # Force a specific filename to avoid caching issues
|
| 68 |
-
)
|
| 69 |
-
|
| 70 |
-
logging.info(f"File downloaded to: {json_path}")
|
| 71 |
-
|
| 72 |
-
with open(json_path, 'r') as f:
|
| 73 |
-
new_data = json.load(f)
|
| 74 |
-
old_data = cache["data"]
|
| 75 |
-
cache["data"] = new_data
|
| 76 |
-
cache["last_updated"] = datetime.now()
|
| 77 |
-
|
| 78 |
-
# Log the differences
|
| 79 |
-
old_len = len(old_data) if old_data and isinstance(old_data, list) else 0
|
| 80 |
-
new_len = len(new_data) if isinstance(new_data, list) else 0
|
| 81 |
-
logging.info(f"Cache updated: Old entries: {old_len}, New entries: {new_len}")
|
| 82 |
-
logging.info(f"Cache update timestamp: {cache['last_updated']}")
|
| 83 |
-
|
| 84 |
-
except Exception as e:
|
| 85 |
-
logging.error(f"Error fetching data: {str(e)}", exc_info=True)
|
| 86 |
-
if not cache["data"]: # Only raise if we don't have any cached data
|
| 87 |
-
raise HTTPException(status_code=500, detail="Failed to fetch leaderboards data")
|
| 88 |
-
|
| 89 |
-
# Initial fetch
|
| 90 |
-
fetch_leaderboards()
|
| 91 |
-
|
| 92 |
-
@app.get("/api/leaderboards")
|
| 93 |
-
async def get_leaderboards():
|
| 94 |
-
"""Get leaderboards data from cache"""
|
| 95 |
-
if not cache["data"]:
|
| 96 |
-
fetch_leaderboards()
|
| 97 |
-
|
| 98 |
-
return {
|
| 99 |
-
"data": cache["data"],
|
| 100 |
-
"last_updated": cache["last_updated"].isoformat() if cache["last_updated"] else None
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
-
@app.get("/api/health")
|
| 104 |
-
async def health_check():
|
| 105 |
-
"""Health check endpoint"""
|
| 106 |
-
return {
|
| 107 |
-
"status": "healthy",
|
| 108 |
-
"cache_status": "initialized" if cache["data"] else "empty",
|
| 109 |
-
"last_updated": cache["last_updated"].isoformat() if cache["last_updated"] else None
|
| 110 |
-
}
|
| 111 |
-
|
| 112 |
-
@app.post("/api/webhook")
|
| 113 |
-
async def handle_webhook(request: Request):
|
| 114 |
-
"""Handle webhook notifications from Hugging Face Hub"""
|
| 115 |
-
try:
|
| 116 |
-
body = await request.json()
|
| 117 |
-
logging.info(f"Received webhook with payload: {body}")
|
| 118 |
-
|
| 119 |
-
# Get the event details
|
| 120 |
-
event = body.get("event", {})
|
| 121 |
-
|
| 122 |
-
# Verify if it's a relevant update (repo content update)
|
| 123 |
-
if event.get("action") == "update" and event.get("scope") == "repo.content":
|
| 124 |
-
try:
|
| 125 |
-
logging.info(f"Dataset update detected for repo {REPO_ID}, file {FILE_PATH}")
|
| 126 |
-
# Force a clean fetch
|
| 127 |
-
fetch_leaderboards()
|
| 128 |
-
if cache["last_updated"]:
|
| 129 |
-
logging.info(f"Cache successfully updated at {cache['last_updated']}")
|
| 130 |
-
return {"status": "success", "message": "Cache updated"}
|
| 131 |
-
else:
|
| 132 |
-
logging.error("Cache update failed: last_updated is None")
|
| 133 |
-
return {"status": "error", "message": "Cache update failed"}
|
| 134 |
-
except Exception as fetch_error:
|
| 135 |
-
logging.error(f"Error during fetch_leaderboards: {str(fetch_error)}", exc_info=True)
|
| 136 |
-
return {"status": "error", "message": f"Failed to update cache: {str(fetch_error)}"}
|
| 137 |
-
|
| 138 |
-
logging.info(f"Ignoring webhook event: action={event.get('action')}, scope={event.get('scope')}")
|
| 139 |
-
return {"status": "ignored", "message": "Event type not relevant"}
|
| 140 |
-
|
| 141 |
-
except Exception as e:
|
| 142 |
-
logging.error(f"Error processing webhook: {str(e)}", exc_info=True)
|
| 143 |
-
raise HTTPException(status_code=500, detail=f"Failed to process webhook: {str(e)}")
|
| 144 |
-
|
| 145 |
# Mount static files for the React client
|
| 146 |
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
| 147 |
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
|
|
|
| 2 |
from fastapi.staticfiles import StaticFiles
|
|
|
|
|
|
|
|
|
|
| 3 |
import os
|
| 4 |
from dotenv import load_dotenv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
# Load environment variables
|
| 7 |
load_dotenv()
|
|
|
|
| 12 |
|
| 13 |
app = FastAPI()
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
# Mount static files for the React client
|
| 16 |
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
| 17 |
|