Spaces:
Running
Running
import React, { | |
createContext, | |
useContext, | |
useState, | |
useCallback, | |
useMemo, | |
useEffect, | |
} from "react"; | |
const LeaderboardContext = createContext(); | |
// Helper pour mettre à jour l'URL | |
const updateURL = (params) => { | |
const url = new URL(window.location); | |
Object.entries(params).forEach(([key, value]) => { | |
if (value) { | |
url.searchParams.set(key, value); | |
} else { | |
url.searchParams.delete(key); | |
} | |
}); | |
window.history.pushState({}, "", url); | |
}; | |
// Helper pour lire les paramètres de l'URL | |
const getURLParams = () => { | |
const params = new URLSearchParams(window.location.search); | |
return { | |
category: params.get("category"), | |
search: params.get("search"), | |
arena: params.get("arena") === "true", | |
language: params.get("language"), | |
}; | |
}; | |
// Constantes pour les tags de catégorisation | |
const CATEGORIZATION_TAGS = [ | |
"modality:agent", | |
"eval:code", | |
"eval:math", | |
"modality:video", | |
"modality:image", | |
"modality:3d", | |
"modality:audio", | |
"domain:financial", | |
"domain:medical", | |
"domain:legal", | |
"eval:safety", | |
]; | |
// Helper pour déterminer si un leaderboard est non catégorisé | |
const isUncategorized = (board) => { | |
const tags = board.tags || []; | |
console.log("Checking uncategorized for board:", { | |
id: board.id, | |
tags, | |
approval_status: board.approval_status, | |
isUncategorized: !tags.some( | |
(tag) => CATEGORIZATION_TAGS.includes(tag) || tag.startsWith("language:") | |
), | |
}); | |
return !tags.some( | |
(tag) => CATEGORIZATION_TAGS.includes(tag) || tag.startsWith("language:") | |
); | |
}; | |
export const LeaderboardProvider = ({ children }) => { | |
// Lire les paramètres initiaux depuis l'URL | |
const initialParams = getURLParams(); | |
const [leaderboards, setLeaderboards] = useState([]); | |
const [searchQuery, setSearchQuery] = useState(initialParams.search || ""); | |
const [arenaOnly, setArenaOnly] = useState(initialParams.arena); | |
const [selectedCategory, setSelectedCategory] = useState( | |
initialParams.category | |
); | |
const [selectedLanguage, setSelectedLanguage] = useState( | |
initialParams.language | |
); | |
const [expandedSections, setExpandedSections] = useState(new Set()); | |
// Mettre à jour l'URL quand les filtres changent | |
useEffect(() => { | |
updateURL({ | |
category: selectedCategory, | |
search: searchQuery, | |
arena: arenaOnly ? "true" : null, | |
language: selectedLanguage, | |
}); | |
}, [selectedCategory, searchQuery, arenaOnly, selectedLanguage]); | |
// Écouter les changements d'URL (navigation back/forward) | |
useEffect(() => { | |
const handleURLChange = () => { | |
const params = getURLParams(); | |
setSearchQuery(params.search || ""); | |
setArenaOnly(params.arena); | |
setSelectedCategory(params.category); | |
setSelectedLanguage(params.language); | |
}; | |
window.addEventListener("popstate", handleURLChange); | |
return () => window.removeEventListener("popstate", handleURLChange); | |
}, []); | |
// Wrapper pour setSelectedCategory qui gère aussi l'expansion des sections | |
const handleCategorySelection = useCallback((categoryId) => { | |
// On réinitialise toujours la langue sélectionnée | |
setSelectedLanguage(null); | |
setSelectedCategory((prev) => { | |
if (prev === categoryId) { | |
// Si on désélectionne, on replie toutes les sections | |
setExpandedSections(new Set()); | |
return null; | |
} | |
// Si on sélectionne une nouvelle catégorie, on la déploie | |
setExpandedSections(new Set([categoryId])); | |
return categoryId; | |
}); | |
}, []); | |
// Wrapper pour la sélection de langue | |
const handleLanguageSelection = useCallback((language) => { | |
setSelectedLanguage((prev) => (prev === language ? null : language)); | |
}, []); | |
// Filter leaderboards based on search query and arena toggle, ignoring category selection | |
const filterLeaderboardsForCount = useCallback( | |
(boards) => { | |
if (!boards) return []; | |
let filtered = [...boards]; | |
// Filter by search query | |
if (searchQuery) { | |
const query = searchQuery.toLowerCase(); | |
const tagMatch = query.match(/^(\w+):(\w+)$/); | |
if (tagMatch) { | |
// Search by tag (ex: language:french) | |
const [_, category, value] = tagMatch; | |
const searchTag = `${category}:${value}`.toLowerCase(); | |
filtered = filtered.filter((board) => { | |
const allTags = [ | |
...(board.tags || []), | |
...(board.editor_tags || []), | |
]; | |
return allTags.some((tag) => tag.toLowerCase() === searchTag); | |
}); | |
} else { | |
// Regular search in title | |
filtered = filtered.filter((board) => | |
board.card_data?.title?.toLowerCase().includes(query) | |
); | |
} | |
} | |
// Filter arena only | |
if (arenaOnly) { | |
filtered = filtered.filter((board) => | |
board.tags?.includes("judge:humans") | |
); | |
} | |
return filtered; | |
}, | |
[searchQuery, arenaOnly] | |
); | |
// Filter leaderboards based on all criteria including category and language selection | |
const filterLeaderboards = useCallback( | |
(boards) => { | |
if (!boards) return []; | |
let filtered = filterLeaderboardsForCount(boards); | |
// Filter by selected language if any | |
if (selectedLanguage) { | |
filtered = filtered.filter((board) => | |
board.tags?.some( | |
(tag) => | |
tag.toLowerCase() === `language:${selectedLanguage.toLowerCase()}` | |
) | |
); | |
} | |
// Filter by selected category if any | |
if (selectedCategory) { | |
filtered = filtered.filter((board) => { | |
const { tags = [] } = board; | |
switch (selectedCategory) { | |
case "agentic": | |
return tags.includes("modality:agent"); | |
case "code": | |
return tags.includes("eval:code"); | |
case "math": | |
return tags.includes("eval:math"); | |
case "language": | |
return tags.some((tag) => tag.startsWith("language:")); | |
case "vision": | |
return tags.some( | |
(tag) => tag === "modality:video" || tag === "modality:image" | |
); | |
case "threeD": | |
return tags.includes("modality:3d"); | |
case "audio": | |
return tags.includes("modality:audio"); | |
case "financial": | |
return tags.includes("domain:financial"); | |
case "medical": | |
return tags.includes("domain:medical"); | |
case "legal": | |
return tags.includes("domain:legal"); | |
case "safety": | |
return tags.includes("eval:safety"); | |
case "uncategorized": | |
return isUncategorized(board); | |
default: | |
return true; | |
} | |
}); | |
} | |
return filtered; | |
}, | |
[filterLeaderboardsForCount, selectedCategory, selectedLanguage] | |
); | |
// Fonction pour obtenir les leaderboards bruts d'une section | |
const getSectionLeaderboards = useCallback((boards) => { | |
if (!boards) return []; | |
return [...boards]; | |
}, []); | |
// Fonction pour compter les leaderboards par langue | |
const getLanguageStats = useCallback((boards) => { | |
const stats = new Map(); | |
boards.forEach((board) => { | |
board.tags?.forEach((tag) => { | |
if (tag.startsWith("language:")) { | |
const language = tag.split(":")[1]; | |
const capitalizedLang = | |
language.charAt(0).toUpperCase() + language.slice(1); | |
const count = stats.get(capitalizedLang) || 0; | |
stats.set(capitalizedLang, count + 1); | |
} | |
}); | |
}); | |
return stats; | |
}, []); | |
// Calculate total number of unique leaderboards (excluding duplicates) | |
const totalLeaderboards = useMemo(() => { | |
const uniqueIds = new Set(leaderboards.map((board) => board.id)); | |
return uniqueIds.size; | |
}, [leaderboards]); | |
// Filter functions for categories | |
const filterByTag = useCallback((tag, boards) => { | |
const searchTag = tag.toLowerCase(); | |
console.log("Filtering by tag:", { | |
searchTag, | |
boards: boards?.map((board) => ({ | |
id: board.id, | |
tags: board.tags, | |
matches: { | |
tags: board.tags?.some((t) => t.toLowerCase() === searchTag), | |
}, | |
})), | |
}); | |
return ( | |
boards?.filter((board) => | |
board.tags?.some((t) => t.toLowerCase() === searchTag) | |
) || [] | |
); | |
}, []); | |
const filterByLanguage = useCallback((boards) => { | |
return ( | |
boards?.filter((board) => | |
board.tags?.some((tag) => tag.startsWith("language:")) | |
) || [] | |
); | |
}, []); | |
const filterByVision = useCallback((boards) => { | |
return ( | |
boards?.filter((board) => | |
board.tags?.some( | |
(tag) => tag === "modality:video" || tag === "modality:image" | |
) | |
) || [] | |
); | |
}, []); | |
// Helper function to get the category group of a section | |
const getSectionGroup = (id) => { | |
const groups = { | |
agentic: ["agentic"], | |
capabilities: ["code", "math"], | |
languages: ["language"], | |
modalities: ["vision", "audio"], | |
threeD: ["threeD"], | |
domains: ["financial", "medical", "legal"], | |
evaluation: ["safety"], | |
misc: ["uncategorized"], | |
}; | |
for (const [group, ids] of Object.entries(groups)) { | |
if (ids.includes(id)) { | |
return group; | |
} | |
} | |
return "misc"; | |
}; | |
// Define sections with raw data | |
const allSections = useMemo(() => { | |
if (!leaderboards) return []; | |
// Garder une trace des leaderboards déjà catégorisés | |
const categorizedIds = new Set(); | |
const sections = [ | |
{ | |
id: "agentic", | |
title: "Agentic", | |
data: filterByTag("modality:agent", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "code", | |
title: "Code", | |
data: filterByTag("eval:code", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "math", | |
title: "Math", | |
data: filterByTag("eval:math", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "language", | |
title: "Language Specific", | |
data: filterByLanguage(leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "vision", | |
title: "Vision", | |
data: filterByVision(leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "audio", | |
title: "Audio", | |
data: filterByTag("modality:audio", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "threeD", | |
title: "3D", | |
data: filterByTag("modality:3d", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "financial", | |
title: "Financial", | |
data: filterByTag("domain:financial", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "medical", | |
title: "Medical", | |
data: filterByTag("domain:medical", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "legal", | |
title: "Legal", | |
data: filterByTag("domain:legal", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "safety", | |
title: "Safety", | |
data: filterByTag("eval:safety", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "uncategorized", | |
title: "Uncategorized", | |
data: leaderboards | |
.filter(isUncategorized) | |
.filter((board) => board.approval_status === "approved"), | |
}, | |
]; | |
return sections; | |
}, [leaderboards, filterByTag, filterByLanguage, filterByVision]); | |
// Get sections with data | |
const sections = useMemo(() => { | |
console.log("Starting sections filtering..."); | |
console.log( | |
"All sections before filtering:", | |
allSections.map((s) => ({ | |
title: s.title, | |
count: s.data.length, | |
ids: s.data.map((b) => b.id), | |
})) | |
); | |
// On garde une trace des titres déjà vus | |
const seenTitles = new Set(); | |
const filteredSections = allSections.filter((section) => { | |
console.log(`\nAnalyzing section ${section.title}:`, { | |
data: section.data, | |
count: section.data.length, | |
uniqueIds: new Set(section.data.map((b) => b.id)).size, | |
boards: section.data.map((board) => ({ | |
id: board.id, | |
tags: board.tags, | |
})), | |
}); | |
// On garde la section si elle a des données et qu'on ne l'a pas déjà vue | |
if (section.data.length > 0 && !seenTitles.has(section.title)) { | |
seenTitles.add(section.title); | |
return true; | |
} | |
return false; | |
}); | |
console.log( | |
"\nFinal sections after filtering:", | |
filteredSections.map((s) => ({ | |
title: s.title, | |
count: s.data.length, | |
uniqueIds: new Set(s.data.map((b) => b.id)).size, | |
})) | |
); | |
return filteredSections; | |
}, [allSections]); | |
// Get filtered count | |
const filteredCount = useMemo(() => { | |
return filterLeaderboardsForCount(leaderboards).length; | |
}, [filterLeaderboardsForCount, leaderboards]); | |
// Function to get highlighted parts of text | |
const getHighlightedText = useCallback((text, searchTerm) => { | |
if (!searchTerm || !text) return { text, shouldHighlight: false }; | |
const query = searchTerm.toLowerCase(); | |
const searchableTagPrefixes = [ | |
"domain:", | |
"language:", | |
"judge:", | |
"test:", | |
"modality:", | |
"submission:", | |
]; | |
// Si c'est une recherche par tag, on ne highlight rien | |
if (searchableTagPrefixes.some((prefix) => query.startsWith(prefix))) { | |
return { text, shouldHighlight: false }; | |
} | |
// Sinon on highlight les parties qui matchent | |
const index = text.toLowerCase().indexOf(query); | |
if (index === -1) return { text, shouldHighlight: false }; | |
return { | |
text, | |
shouldHighlight: true, | |
highlightStart: index, | |
highlightEnd: index + query.length, | |
}; | |
}, []); | |
const value = { | |
leaderboards, | |
setLeaderboards, | |
searchQuery, | |
setSearchQuery, | |
arenaOnly, | |
setArenaOnly, | |
totalLeaderboards, | |
filteredCount, | |
filterLeaderboards, | |
filterLeaderboardsForCount, | |
sections, | |
allSections, | |
getHighlightedText, | |
selectedCategory, | |
setSelectedCategory: handleCategorySelection, | |
selectedLanguage, | |
setSelectedLanguage: handleLanguageSelection, | |
expandedSections, | |
setExpandedSections, | |
getLanguageStats, | |
getSectionLeaderboards, | |
}; | |
return ( | |
<LeaderboardContext.Provider value={value}> | |
{children} | |
</LeaderboardContext.Provider> | |
); | |
}; | |
export const useLeaderboard = () => { | |
const context = useContext(LeaderboardContext); | |
if (!context) { | |
throw new Error("useLeaderboard must be used within a LeaderboardProvider"); | |
} | |
return context; | |
}; | |