Spaces:
Running
Running
import React, { | |
createContext, | |
useContext, | |
useState, | |
useCallback, | |
useMemo, | |
useEffect, | |
} from "react"; | |
import { normalizeTags } from "../utils/tagFilters"; | |
import { useUrlState } from "../hooks/useUrlState"; | |
const LeaderboardContext = createContext(); | |
// Constantes pour les tags de catégorisation | |
const CATEGORIZATION_TAGS = [ | |
"modality:agent", | |
"modality:artefacts", | |
"modality:text", | |
"eval:code", | |
"eval:math", | |
"eval:reasoning", | |
"eval:hallucination", | |
"modality:video", | |
"modality:image", | |
"modality:3d", | |
"modality:audio", | |
"domain:financial", | |
"domain:medical", | |
"domain:legal", | |
"domain:biology", | |
"domain:translation", | |
"domain:chemistry", | |
"domain:physics", | |
"domain:commercial", | |
"eval:safety", | |
"eval:performance", | |
"eval:rag", | |
]; | |
// 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 }) => { | |
const { params, updateParams } = useUrlState(); | |
const [leaderboards, setLeaderboards] = useState([]); | |
const [searchQuery, setSearchQuery] = useState(params.search || ""); | |
const [arenaOnly, setArenaOnly] = useState(params.arena === "true"); | |
const [selectedCategories, setSelectedCategories] = useState( | |
new Set(params.categories || []) | |
); | |
const [selectedLanguage, setSelectedLanguage] = useState( | |
params.language || null | |
); | |
const [expandedSections, setExpandedSections] = useState( | |
new Set(params.categories || []) | |
); | |
const [isLanguageExpanded, setIsLanguageExpanded] = useState( | |
params.languageExpanded === "true" | |
); | |
// Mettre à jour l'URL quand les filtres changent | |
useEffect(() => { | |
updateParams({ | |
categories: Array.from(selectedCategories), | |
search: searchQuery, | |
arena: arenaOnly ? "true" : null, | |
language: selectedLanguage, | |
languageExpanded: isLanguageExpanded ? "true" : null, | |
}); | |
}, [ | |
selectedCategories, | |
searchQuery, | |
arenaOnly, | |
selectedLanguage, | |
isLanguageExpanded, | |
updateParams, | |
]); | |
// Wrapper pour setSelectedCategories qui gère aussi l'expansion des sections | |
const handleCategorySelection = useCallback((categoryId) => { | |
setSelectedCategories((prev) => { | |
const newCategories = new Set(prev); | |
if (newCategories.has(categoryId)) { | |
newCategories.delete(categoryId); | |
// Si on désélectionne, on replie la section | |
setExpandedSections((prev) => { | |
const newExpanded = new Set(prev); | |
newExpanded.delete(categoryId); | |
return newExpanded; | |
}); | |
} else { | |
newCategories.add(categoryId); | |
// Si on sélectionne, on déploie la section | |
setExpandedSections((prev) => { | |
const newExpanded = new Set(prev); | |
newExpanded.add(categoryId); | |
return newExpanded; | |
}); | |
} | |
return newCategories; | |
}); | |
// On réinitialise la langue sélectionnée seulement si on désélectionne "language" | |
if (categoryId === "language") { | |
setSelectedLanguage(null); | |
} | |
}, []); | |
// 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 categories 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 categories if any | |
if (selectedCategories.size > 0) { | |
filtered = filtered.filter((board) => { | |
const { tags = [] } = board; | |
// Un leaderboard est inclus s'il correspond à TOUTES les catégories sélectionnées | |
return Array.from(selectedCategories).every((category) => { | |
switch (category) { | |
case "agentic": | |
return tags.includes("modality:agent"); | |
case "text": | |
return tags.includes("modality:text"); | |
case "image": | |
return tags.includes("modality:image"); | |
case "video": | |
return tags.includes("modality:video"); | |
case "code": | |
return tags.includes("eval:code"); | |
case "math": | |
return tags.includes("eval:math"); | |
case "reasoning": | |
return tags.includes("eval:reasoning"); | |
case "hallucination": | |
return tags.includes("eval:hallucination"); | |
case "rag": | |
return tags.includes("eval:rag"); | |
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 "biology": | |
return tags.includes("domain:biology"); | |
case "commercial": | |
return tags.includes("domain:commercial"); | |
case "translation": | |
return tags.includes("domain:translation"); | |
case "chemistry": | |
return tags.includes("domain:chemistry"); | |
case "safety": | |
return tags.includes("eval:safety"); | |
case "performance": | |
return tags.includes("eval:performance"); | |
case "uncategorized": | |
return isUncategorized(board); | |
default: | |
return false; | |
} | |
}); | |
}); | |
} | |
return filtered; | |
}, | |
[searchQuery, arenaOnly, selectedCategories, 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(() => { | |
// On ne compte que les leaderboards approuvés | |
const uniqueIds = new Set( | |
leaderboards | |
.filter((board) => board.approval_status === "approved") | |
.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:")) | |
) || [] | |
); | |
}, []); | |
// 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 = [ | |
// Science | |
{ | |
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: "biology", | |
title: "Biology", | |
data: filterByTag("domain:biology", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "chemistry", | |
title: "Chemistry", | |
data: filterByTag("domain:chemistry", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "physics", | |
title: "Physics", | |
data: filterByTag("domain:physics", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
// Modalities | |
{ | |
id: "image", | |
title: "Image", | |
data: filterByTag("modality:image", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "video", | |
title: "Video", | |
data: filterByTag("modality:video", 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: "text", | |
title: "Text", | |
data: filterByTag("modality:text", 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: "embeddings", | |
title: "Embeddings", | |
data: filterByTag("modality:artefacts", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
// LLM Capabilities | |
{ | |
id: "rag", | |
title: "RAG", | |
data: filterByTag("eval:rag", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "reasoning", | |
title: "Reasoning", | |
data: filterByTag("eval:reasoning", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "agentic", | |
title: "Agentic", | |
data: filterByTag("modality:agent", 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: "performance", | |
title: "Performance", | |
data: filterByTag("eval:performance", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "hallucination", | |
title: "Hallucination", | |
data: filterByTag("eval:hallucination", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
// Domain Specific | |
{ | |
id: "medical", | |
title: "Medical", | |
data: filterByTag("domain:medical", 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: "legal", | |
title: "Legal", | |
data: filterByTag("domain:legal", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "commercial", | |
title: "Commercial", | |
data: filterByTag("domain:commercial", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
// Language Related | |
{ | |
id: "language", | |
title: "Language Specific", | |
data: filterByLanguage(leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
{ | |
id: "translation", | |
title: "Translation", | |
data: filterByTag("domain:translation", leaderboards).map((board) => { | |
categorizedIds.add(board.id); | |
return board; | |
}), | |
}, | |
// Misc | |
{ | |
id: "uncategorized", | |
title: "Uncategorized", | |
data: leaderboards | |
.filter(isUncategorized) | |
.filter((board) => board.approval_status === "approved"), | |
}, | |
]; | |
return sections; | |
}, [leaderboards, filterByTag, filterByLanguage]); | |
// 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 filterLeaderboards(leaderboards).length; | |
}, [filterLeaderboards, 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:", | |
"eval:", | |
]; | |
// 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, | |
}; | |
}, []); | |
// Wrapper pour setLeaderboards qui normalise les tags | |
const setNormalizedLeaderboards = useCallback((boards) => { | |
const normalizedBoards = boards.map((board) => ({ | |
...board, | |
tags: normalizeTags(board.tags), | |
})); | |
setLeaderboards(normalizedBoards); | |
}, []); | |
const resetState = useCallback(() => { | |
setSearchQuery(""); | |
setArenaOnly(false); | |
setSelectedCategories(new Set()); | |
setSelectedLanguage(null); | |
setExpandedSections(new Set()); | |
setIsLanguageExpanded(false); | |
}, []); | |
const value = { | |
leaderboards, | |
setLeaderboards: setNormalizedLeaderboards, | |
searchQuery, | |
setSearchQuery, | |
arenaOnly, | |
setArenaOnly, | |
totalLeaderboards, | |
filteredCount, | |
filterLeaderboards, | |
filterLeaderboardsForCount, | |
sections, | |
allSections, | |
getHighlightedText, | |
selectedCategories, | |
setSelectedCategories: handleCategorySelection, | |
selectedLanguage, | |
setSelectedLanguage: handleLanguageSelection, | |
expandedSections, | |
setExpandedSections, | |
getLanguageStats, | |
getSectionLeaderboards, | |
resetState, | |
isLanguageExpanded, | |
setIsLanguageExpanded, | |
}; | |
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; | |
}; | |