File size: 3,275 Bytes
5c85958
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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;
}