import { useEffect, useState } from "react"; import chartXkcd from "chart.xkcd"; function transformLikesData(likesData) { // Step 1: Sort by likedAt to ensure chronological order and easily find the first like date. likesData.sort((a, b) => new Date(a.likedAt) - new Date(b.likedAt)); if (likesData.length === 0) { return []; } // Determine the start date for this specific project (first like date) const startDate = new Date(likesData[0].likedAt); // Set time to 00:00:00.000 UTC for accurate day calculation startDate.setUTCHours(0, 0, 0, 0); const cumulativeLikesByDay = {}; let cumulativeCount = 0; // Step 2 & 3: Calculate cumulative likes and days since start likesData.forEach(like => { cumulativeCount++; const currentDate = new Date(like.likedAt); // Set time to 00:00:00.000 UTC for current date too currentDate.setUTCHours(0, 0, 0, 0); const timeDiff = currentDate.getTime() - startDate.getTime(); // Calculate days since startDate. Math.floor ensures we get full days. const daysSinceStart = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); // Store the latest cumulative count for this specific day cumulativeLikesByDay[daysSinceStart] = cumulativeCount; }); // Step 4: Convert the object back into an array of { x: days, y: likes } // Ensure the keys (days) are sorted numerically for the chart const transformedData = Object.keys(cumulativeLikesByDay) .sort((a, b) => parseInt(a) - parseInt(b)) .map(day => ({ x: parseInt(day), // x-axis will be numbers (days since first like) y: cumulativeLikesByDay[day].toString() })); return transformedData; } function getProjectsFromHash() { let hash = window.location.hash; console.log('hash', hash) const projects = hash.replace("#", "").split('&').filter(project => project !== ''); return projects; } const initProjects = getProjectsFromHash(); function App() { const [projectType, setProjectType] = useState("models"); const [projectName, setProjectName] = useState(""); const [hasGraph, setHasGraph] = useState(false); const [isLoading, setIsLoading] = useState(false); const [datasets, setDatasets] = useState([]); function setHash() { const hashes = datasets.map(dataset => dataset.label).join('&'); if (window.parent && window.parent.postMessage) { window.parent.postMessage({ hash: hashes, }, "*"); } window.location.hash = hashes } async function getLikeHistory(projectPath) { const res = await fetch(`https://huggingface.co/api/${projectPath}/likers?expand[]=likeAt`) /** * Format: * [{"user": "timqian", "likedAt": "2021-07-01T00:00:00.000Z"}, {"user": "yy", "likedAt": "2021-07-02T00:00:00.000Z"}] */ const likers = await res.json() let likeHistory = transformLikesData(likers) if (likeHistory.length > 40) { // sample 20 points const sampledLikeHistory = [] const step = Math.floor(likeHistory.length / 20) for (let i = 0; i < likeHistory.length; i += step) { sampledLikeHistory.push(likeHistory[i]) } // Add the last point if it's not included if (sampledLikeHistory[sampledLikeHistory.length - 1].x !== likeHistory[likeHistory.length - 1].x) { sampledLikeHistory.push(likeHistory[likeHistory.length - 1]) } likeHistory = sampledLikeHistory } return likeHistory; } const onSubmit = async () => { setIsLoading(true) const likeHistory = await getLikeHistory(`${projectType}/${projectName}`); // if likeHistory is empty, show error message if (likeHistory.length === 0) { setIsLoading(false) alert("No like history found") return } setDatasets([...datasets, { label: `${projectType !== 'models' ? `${projectType}/` : ''}${projectName}`, data: likeHistory, }]) setHasGraph(true) setIsLoading(false) setProjectName("") } useEffect(() => { const svg = document.querySelector('.line-chart') if (datasets.length === 0) { svg.innerHTML = '' setHash() return } // draw chart in next tick new chartXkcd.XY(svg, { title: 'Like History', xLabel: 'Days since first like', // Changed xLabel to reflect days yLabel: 'Likes', data: { datasets, }, options: { // unxkcdify: true, xTickCount: 3, yTickCount: 4, legendPosition: chartXkcd.config.positionType.upLeft, showLine: true, // Removed timeFormat as x-axis is now numerical days dotSize: 0.5, dataColors: [ "#FBBF24", // Warm Yellow "#60A5FA", // Light Blue "#14B8A6", // Teal "#A78BFA", // Soft Purple "#FF8C00", // Orange "#64748B", // Slate Gray "#FB7185", // Coral Pink "#6EE7B7", // Mint Green "#2563EB", // Deep Blue "#374151" // Charcoal ] }, }); setHash() }, [datasets]) useEffect(() => { function handleReceiveMessage(event) { // You might want to check event.origin here for security if needed // and ensure that event.data contains the properties you expect if (event.data && typeof event.data === 'object' && 'hash' in event.data) { // Update the hash of the parent window's URL window.location.hash = event.data.hash; console.log('hash') console.log(window.location.hash) } } // Add event listener for 'message' events window.addEventListener('message', handleReceiveMessage); // Clean up the event listener on component unmount return () => { window.removeEventListener('message', handleReceiveMessage); }; }, []); useEffect(() => { const projects = initProjects; if (projects.length <= 0) return; async function getLikeHistoryAndDisplay() { setIsLoading(true); const newDatasets = []; // Create a temporary array to store new datasets for (const project of projects) { let projectPath = project.startsWith('spaces/') || project.startsWith('datasets/') ? project : `models/${project}` const likeHistory = await getLikeHistory(projectPath); newDatasets.push({ // Add to the temporary array label: project, data: likeHistory, }) } setDatasets(newDatasets); // Set state once after all fetches are done setIsLoading(false); } getLikeHistoryAndDisplay() }, []) return (

View the like history of a project on huggingface 🤗

setProjectName(e.target.value.trim())} onFocus={(e) => e.target.select()} onKeyDown={async (e) => { if (e.key === "Enter") { try { await onSubmit(); } catch (err) { setIsLoading(false); alert(`No like history found for ${projectName}, please check the name and try again`); } } }} disabled={isLoading} /> { isLoading &&
}
{datasets.length > 0 &&
{datasets.map(dataset => ) }
} { hasGraph && 🤗 forked from like-history.ai }
); } export default App;