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 |
|