tfrere commited on
Commit
b85be5e
·
1 Parent(s): 6d3b050
.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 tous les leaderboards de toutes les sections
93
- return allSections.reduce(
94
- (total, section) => total + section.data.length,
95
- 0
96
- );
 
 
 
 
 
97
  }, [selectedCategory, allSections]);
98
 
99
  // Calculer le nombre filtré en prenant en compte tous les filtres
100
  const currentFilteredCount = useMemo(() => {
 
 
 
 
 
101
  if (selectedCategory) {
102
  const categorySection = allSections.find(
103
  (section) => section.id === selectedCategory
@@ -105,13 +115,26 @@ const LeaderboardFilters = ({ allSections }) => {
105
  if (!categorySection) return 0;
106
  return filterLeaderboards(categorySection.data).length;
107
  }
108
- // Quand aucune catégorie n'est sélectionnée, on compte dans toutes les sections
109
- const allBoards = allSections.reduce(
110
- (acc, section) => [...acc, ...section.data],
111
- []
112
- );
113
- return filterLeaderboards(allBoards).length;
114
- }, [selectedCategory, allSections, filterLeaderboards]);
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
  const isMobile = useMediaQuery((theme) => theme.breakpoints.down("md"));
117
 
@@ -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 bruts pour avoir toutes les langues
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 = leaderboards.filter((board) =>
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, leaderboards]);
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
- // Ne pas retourner si pas de langues, SAUF pour Other Languages qui doit toujours être affiché
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(new Set());
 
 
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
- const uniqueIds = new Set(leaderboards.map((board) => board.id));
 
 
 
 
 
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={["domain:financial", "domain:medical", "domain:legal"]}
 
 
 
 
 
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 = "Backend server for Leaderboard Explorer"
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, HTTPException, Request
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