find-a-leaderboard / client /src /context /LeaderboardContext.jsx
tfrere's picture
add multi selection and update language specific list
5621ad9
raw
history blame
19.4 kB
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;
};