Spaces:
Running
Running
| 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&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 ( | |
| <AppsContext.Provider value={{ apps, loading, error }}> | |
| {children} | |
| </AppsContext.Provider> | |
| ); | |
| } | |
| // 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; | |
| } | |