cfahlgren1 HF Staff commited on
Commit
1d143c4
·
verified ·
1 Parent(s): 6ddc188

Update src/pages/index.tsx

Browse files
Files changed (1) hide show
  1. src/pages/index.tsx +217 -57
src/pages/index.tsx CHANGED
@@ -12,19 +12,19 @@ import UserSearchDialog from "../components/UserSearchDialog";
12
 
13
  const PROVIDERS: ProviderInfo[] = [
14
  { color: "#ff7000", authors: ["mistralai"] },
15
- { color: "#1877F2", authors: ["meta-llama", "facebook", ] },
16
  { color: "#10A37F", authors: ["openai"] },
17
  { color: "#cc785c", authors: ["Anthropic"] },
18
  { color: "#DB4437", authors: ["google"] },
19
  { color: "#5E35B1", authors: ["allenai"] },
20
- { color: "#0088cc", authors: ["apple"] },
21
  { color: "#FEB800", authors: ["microsoft"] },
22
  { color: "#76B900", authors: ["nvidia"] },
23
- { color: "#0088cc", authors: ["deepseek-ai"] },
24
- { color: "#0088cc", authors: ["Qwen"] },
25
- { color: "#4C6EE6", authors: ["CohereLabs"] },
26
- { color: "#4C6EE6", authors: ["ibm-granite"] },
27
- { color: "#A020F0", authors: ["stabilityai"] },
28
  ];
29
 
30
  export async function getStaticProps() {
@@ -42,7 +42,7 @@ export async function getStaticProps() {
42
  calendarData,
43
  providers: updatedProviders,
44
  },
45
- revalidate: 3600, // regenerate every hour
46
  };
47
  } catch (error) {
48
  console.error("Error fetching data:", error);
@@ -51,22 +51,74 @@ export async function getStaticProps() {
51
  calendarData: {},
52
  providers: PROVIDERS,
53
  },
54
- revalidate: 60, // retry after 1 minute if there was an error
55
  };
56
  }
57
  }
58
 
59
- const ProviderHeatmap = React.memo(({ provider, calendarData }: { provider: ProviderInfo, calendarData: CalendarData }) => {
 
 
 
 
 
 
 
 
 
 
60
  const providerName = provider.fullName || provider.authors[0];
 
 
 
61
  return (
62
- <div key={providerName} className="flex flex-col items-center">
63
- <Heatmap
64
- data={calendarData[providerName]}
65
- color={provider.color}
66
- providerName={providerName}
67
- fullName={provider.fullName ?? providerName}
68
- avatarUrl={provider.avatarUrl ?? ''}
69
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  </div>
71
  );
72
  });
@@ -76,6 +128,9 @@ const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
76
  providers,
77
  }) => {
78
  const [isLoading, setIsLoading] = useState(true);
 
 
 
79
 
80
  useEffect(() => {
81
  if (calendarData && Object.keys(calendarData).length > 0) {
@@ -83,49 +138,154 @@ const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
83
  }
84
  }, [calendarData]);
85
 
86
- const sortedProviders = useMemo(() =>
87
- providers.sort((a, b) =>
88
- calendarData[b.fullName || b.authors[0]].reduce(
89
- (sum, day) => sum + day.count,
90
- 0
91
- ) -
92
- calendarData[a.fullName || a.authors[0]].reduce(
93
- (sum, day) => sum + day.count,
94
- 0
95
- )
96
- ),
97
- [providers, calendarData]
98
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  return (
101
- <div className="w-full max-w-screen-lg mx-auto p-4 py-16">
102
- <h1 className="text-3xl lg:text-5xl mt-16 font-bold text-center mb-2">
103
- Hugging Face Heatmap 🤗
104
- </h1>
105
- <div className="text-center text-sm my-8 space-y-4">
106
- <p>
107
- Models, Datasets, and Spaces from the top AI labs.
108
- </p>
109
- <div className="flex justify-center space-x-4">
110
- <UserSearchDialog />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  </div>
113
-
114
- <div className="h-px max-w-lg mx-auto bg-gray-200 my-16" />
115
-
116
- {isLoading ? (
117
- <p className="text-center">Loading...</p>
118
- ) : (
119
- <div className="space-y-16">
120
- {sortedProviders.map((provider) => (
121
- <ProviderHeatmap
122
- key={provider.fullName || provider.authors[0]}
123
- provider={provider}
124
- calendarData={calendarData}
125
- />
126
- ))}
127
- </div>
128
- )}
129
  </div>
130
  );
131
  };
 
12
 
13
  const PROVIDERS: ProviderInfo[] = [
14
  { color: "#ff7000", authors: ["mistralai"] },
15
+ { color: "#1877F2", authors: ["meta-llama", "facebook"] },
16
  { color: "#10A37F", authors: ["openai"] },
17
  { color: "#cc785c", authors: ["Anthropic"] },
18
  { color: "#DB4437", authors: ["google"] },
19
  { color: "#5E35B1", authors: ["allenai"] },
20
+ { color: "#0066CC", authors: ["apple"] },
21
  { color: "#FEB800", authors: ["microsoft"] },
22
  { color: "#76B900", authors: ["nvidia"] },
23
+ { color: "#00A8E0", authors: ["deepseek-ai"] },
24
+ { color: "#6366F1", authors: ["Qwen"] },
25
+ { color: "#FF6B6B", authors: ["CohereLabs"] },
26
+ { color: "#4ECDC4", authors: ["ibm-granite"] },
27
+ { color: "#A855F7", authors: ["stabilityai"] },
28
  ];
29
 
30
  export async function getStaticProps() {
 
42
  calendarData,
43
  providers: updatedProviders,
44
  },
45
+ revalidate: 3600,
46
  };
47
  } catch (error) {
48
  console.error("Error fetching data:", error);
 
51
  calendarData: {},
52
  providers: PROVIDERS,
53
  },
54
+ revalidate: 60,
55
  };
56
  }
57
  }
58
 
59
+ const ProviderCard = React.memo(({
60
+ provider,
61
+ calendarData,
62
+ isExpanded,
63
+ onToggle
64
+ }: {
65
+ provider: ProviderInfo,
66
+ calendarData: CalendarData,
67
+ isExpanded: boolean,
68
+ onToggle: () => void
69
+ }) => {
70
  const providerName = provider.fullName || provider.authors[0];
71
+ const totalActivity = calendarData[providerName]?.reduce((sum, day) => sum + day.count, 0) || 0;
72
+ const recentActivity = calendarData[providerName]?.slice(-30).reduce((sum, day) => sum + day.count, 0) || 0;
73
+
74
  return (
75
+ <div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden transition-all duration-200 hover:shadow-md">
76
+ <button
77
+ onClick={onToggle}
78
+ className="w-full px-6 py-4 flex items-center justify-between hover:bg-gray-50 transition-colors"
79
+ >
80
+ <div className="flex items-center gap-4">
81
+ {provider.avatarUrl && (
82
+ <img
83
+ src={provider.avatarUrl}
84
+ alt={providerName}
85
+ className="w-12 h-12 rounded-lg object-cover"
86
+ />
87
+ )}
88
+ <div className="text-left">
89
+ <h3 className="font-semibold text-lg text-gray-900">{providerName}</h3>
90
+ <div className="flex gap-4 text-sm text-gray-600">
91
+ <span>Total: <span className="font-medium text-gray-900">{totalActivity.toLocaleString()}</span></span>
92
+ <span>Last 30 days: <span className="font-medium text-gray-900">{recentActivity.toLocaleString()}</span></span>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ <div className="flex items-center gap-3">
97
+ <div className="w-3 h-3 rounded-full" style={{ backgroundColor: provider.color }}></div>
98
+ <svg
99
+ className={`w-5 h-5 text-gray-400 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
100
+ fill="none"
101
+ stroke="currentColor"
102
+ viewBox="0 0 24 24"
103
+ >
104
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
105
+ </svg>
106
+ </div>
107
+ </button>
108
+
109
+ {isExpanded && (
110
+ <div className="px-6 pb-6 border-t border-gray-100">
111
+ <div className="pt-4">
112
+ <Heatmap
113
+ data={calendarData[providerName]}
114
+ color={provider.color}
115
+ providerName={providerName}
116
+ fullName={provider.fullName ?? providerName}
117
+ avatarUrl={provider.avatarUrl ?? ''}
118
+ />
119
+ </div>
120
+ </div>
121
+ )}
122
  </div>
123
  );
124
  });
 
128
  providers,
129
  }) => {
130
  const [isLoading, setIsLoading] = useState(true);
131
+ const [expandedProviders, setExpandedProviders] = useState<Set<string>>(new Set());
132
+ const [filterQuery, setFilterQuery] = useState("");
133
+ const [sortBy, setSortBy] = useState<"total" | "recent">("total");
134
 
135
  useEffect(() => {
136
  if (calendarData && Object.keys(calendarData).length > 0) {
 
138
  }
139
  }, [calendarData]);
140
 
141
+ const sortedAndFilteredProviders = useMemo(() => {
142
+ let filtered = providers;
143
+
144
+ if (filterQuery) {
145
+ filtered = providers.filter(p =>
146
+ (p.fullName || p.authors[0]).toLowerCase().includes(filterQuery.toLowerCase())
147
+ );
148
+ }
149
+
150
+ return filtered.sort((a, b) => {
151
+ const aName = a.fullName || a.authors[0];
152
+ const bName = b.fullName || b.authors[0];
153
+
154
+ if (sortBy === "total") {
155
+ return calendarData[bName]?.reduce((sum, day) => sum + day.count, 0) || 0 -
156
+ calendarData[aName]?.reduce((sum, day) => sum + day.count, 0) || 0;
157
+ } else {
158
+ return calendarData[bName]?.slice(-30).reduce((sum, day) => sum + day.count, 0) || 0 -
159
+ calendarData[aName]?.slice(-30).reduce((sum, day) => sum + day.count, 0) || 0;
160
+ }
161
+ });
162
+ }, [providers, calendarData, filterQuery, sortBy]);
163
+
164
+ const toggleProvider = (providerName: string) => {
165
+ setExpandedProviders(prev => {
166
+ const newSet = new Set(prev);
167
+ if (newSet.has(providerName)) {
168
+ newSet.delete(providerName);
169
+ } else {
170
+ newSet.add(providerName);
171
+ }
172
+ return newSet;
173
+ });
174
+ };
175
+
176
+ const toggleAll = () => {
177
+ if (expandedProviders.size === sortedAndFilteredProviders.length) {
178
+ setExpandedProviders(new Set());
179
+ } else {
180
+ setExpandedProviders(new Set(sortedAndFilteredProviders.map(p => p.fullName || p.authors[0])));
181
+ }
182
+ };
183
 
184
  return (
185
+ <div className="min-h-screen bg-gray-50">
186
+ <div className="w-full max-w-7xl mx-auto px-4 py-12">
187
+ {/* Header */}
188
+ <div className="text-center mb-12">
189
+ <h1 className="text-5xl font-bold text-gray-900 mb-4">
190
+ AI Labs Activity Dashboard
191
+ </h1>
192
+ <p className="text-lg text-gray-600 mb-8">
193
+ Track models, datasets, and spaces from leading AI organizations on Hugging Face
194
+ </p>
195
+
196
+ {/* Controls */}
197
+ <div className="flex flex-col sm:flex-row gap-4 justify-center items-center max-w-2xl mx-auto">
198
+ <div className="relative flex-1 w-full">
199
+ <input
200
+ type="text"
201
+ placeholder="Search organizations..."
202
+ value={filterQuery}
203
+ onChange={(e) => setFilterQuery(e.target.value)}
204
+ className="w-full px-4 py-2 pl-10 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
205
+ />
206
+ <svg className="absolute left-3 top-2.5 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
207
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
208
+ </svg>
209
+ </div>
210
+
211
+ <div className="flex gap-2">
212
+ <select
213
+ value={sortBy}
214
+ onChange={(e) => setSortBy(e.target.value as "total" | "recent")}
215
+ className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
216
+ >
217
+ <option value="total">Sort by Total</option>
218
+ <option value="recent">Sort by Recent</option>
219
+ </select>
220
+
221
+ <button
222
+ onClick={toggleAll}
223
+ className="px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
224
+ >
225
+ {expandedProviders.size === sortedAndFilteredProviders.length ? 'Collapse All' : 'Expand All'}
226
+ </button>
227
+
228
+ <UserSearchDialog />
229
+ </div>
230
+ </div>
231
  </div>
232
+
233
+ {/* Stats Summary */}
234
+ {!isLoading && (
235
+ <div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-8">
236
+ <div className="bg-white rounded-lg p-6 text-center shadow-sm border border-gray-200">
237
+ <div className="text-3xl font-bold text-gray-900">
238
+ {providers.length}
239
+ </div>
240
+ <div className="text-sm text-gray-600">Organizations</div>
241
+ </div>
242
+ <div className="bg-white rounded-lg p-6 text-center shadow-sm border border-gray-200">
243
+ <div className="text-3xl font-bold text-gray-900">
244
+ {Object.values(calendarData).reduce((total, days) =>
245
+ total + days.reduce((sum, day) => sum + day.count, 0), 0
246
+ ).toLocaleString()}
247
+ </div>
248
+ <div className="text-sm text-gray-600">Total Activities</div>
249
+ </div>
250
+ <div className="bg-white rounded-lg p-6 text-center shadow-sm border border-gray-200">
251
+ <div className="text-3xl font-bold text-gray-900">
252
+ {Object.values(calendarData).reduce((total, days) =>
253
+ total + days.slice(-30).reduce((sum, day) => sum + day.count, 0), 0
254
+ ).toLocaleString()}
255
+ </div>
256
+ <div className="text-sm text-gray-600">Last 30 Days</div>
257
+ </div>
258
+ </div>
259
+ )}
260
+
261
+ {/* Provider List */}
262
+ {isLoading ? (
263
+ <div className="flex justify-center items-center h-64">
264
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900"></div>
265
+ </div>
266
+ ) : (
267
+ <div className="space-y-4">
268
+ {sortedAndFilteredProviders.length === 0 ? (
269
+ <div className="text-center py-12 text-gray-500">
270
+ No organizations found matching "{filterQuery}"
271
+ </div>
272
+ ) : (
273
+ sortedAndFilteredProviders.map((provider) => {
274
+ const providerName = provider.fullName || provider.authors[0];
275
+ return (
276
+ <ProviderCard
277
+ key={providerName}
278
+ provider={provider}
279
+ calendarData={calendarData}
280
+ isExpanded={expandedProviders.has(providerName)}
281
+ onToggle={() => toggleProvider(providerName)}
282
+ />
283
+ );
284
+ })
285
+ )}
286
+ </div>
287
+ )}
288
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  </div>
290
  );
291
  };