import { createContext, useContext, useState, useEffect } from 'react'; // URLs for fetching apps const OFFICIAL_APP_LIST_URL = 'https://huggingface.co/datasets/pollen-robotics/reachy-mini-official-app-store/raw/main/app-list.json'; const HF_SPACES_API = 'https://huggingface.co/api/spaces'; // Context const AppsContext = createContext(null); // Fetch all spaces with reachy_mini tag from HuggingFace API async function fetchAllReachyMiniSpaces() { try { const response = await fetch(`${HF_SPACES_API}?filter=reachy_mini_python_app&full=true&limit=100`); if (!response.ok) { console.warn('Failed to fetch spaces with tag:', response.status); return []; } return await response.json(); } catch (err) { console.error('Error fetching reachy_mini spaces:', err); return []; } } // Provider component export function AppsProvider({ children }) { const [apps, setApps] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [hasFetched, setHasFetched] = useState(false); useEffect(() => { // Only fetch once if (hasFetched) return; async function fetchApps() { setLoading(true); setError(null); try { // 1. Fetch official app IDs from the curated list const officialResponse = await fetch(OFFICIAL_APP_LIST_URL); let officialIdList = []; if (officialResponse.ok) { officialIdList = await officialResponse.json(); } const officialSet = new Set(officialIdList.map(id => id.toLowerCase())); // 2. Fetch ALL spaces with reachy_mini tag from HuggingFace API const allSpaces = await fetchAllReachyMiniSpaces(); console.log(`[AppsContext] Fetched ${allSpaces.length} spaces with reachy_mini tag`); // 3. Build apps list with isOfficial flag const allApps = allSpaces.map(space => { const spaceId = space.id || ''; const isOfficial = officialSet.has(spaceId.toLowerCase()); return { id: spaceId, name: spaceId.split('/').pop(), description: space.cardData?.short_description || '', cardData: space.cardData || {}, likes: space.likes || 0, lastModified: space.lastModified, author: spaceId.split('/')[0], isOfficial, }; }); // Sort: official first, then by likes allApps.sort((a, b) => { if (a.isOfficial !== b.isOfficial) { return a.isOfficial ? -1 : 1; } return (b.likes || 0) - (a.likes || 0); }); setApps(allApps); setHasFetched(true); } catch (err) { console.error('Failed to fetch apps:', err); setError('Failed to load apps. Please try again later.'); } finally { setLoading(false); } } fetchApps(); }, [hasFetched]); return ( {children} ); } // Hook to use the apps context export function useApps() { const context = useContext(AppsContext); if (!context) { throw new Error('useApps must be used within an AppsProvider'); } return context; }