find-a-leaderboard / client /src /context /LeaderboardContext.jsx
tfrere's picture
update modality:agent
00c453b
raw
history blame
15.5 kB
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;
};