cfahlgren1 HF Staff commited on
Commit
6ee3edd
·
1 Parent(s): fa9b9b5

refactor, rename things a bit

Browse files
src/components/CTASection.tsx ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from "react";
2
+ import UserSearchDialog from "./UserSearchDialog";
3
+
4
+ const CTASection: React.FC = () => {
5
+ return (
6
+ <div className="mt-24 mb-16 flex justify-center">
7
+ <div className="bg-gradient-to-br from-card to-card/95 rounded-2xl border border-border shadow-lg hover:shadow-xl transition-all duration-300 p-8 max-w-2xl w-full text-center space-y-6">
8
+ <div className="space-y-4">
9
+ <h2 className="text-2xl lg:text-3xl font-bold text-foreground">
10
+ Want Your Own Heatmap?
11
+ </h2>
12
+ <p className="text-muted-foreground text-lg">
13
+ Search for any Hugging Face organization or user to see their model release activity.
14
+ </p>
15
+ </div>
16
+ <div className="flex justify-center">
17
+ <UserSearchDialog />
18
+ </div>
19
+ </div>
20
+ </div>
21
+ );
22
+ };
23
+
24
+ export default CTASection;
src/components/HeatmapGrid.tsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from "react";
2
+ import { ProviderInfo, CalendarData } from "../types/heatmap";
3
+ import OrganizationCard from "./OrganizationCard";
4
+ import ProviderHeatmapSkeleton from "./ProviderHeatmapSkeleton";
5
+
6
+ interface HeatmapGridProps {
7
+ sortedProviders: ProviderInfo[];
8
+ calendarData: CalendarData;
9
+ isLoading: boolean;
10
+ }
11
+
12
+ const HeatmapGrid: React.FC<HeatmapGridProps> = ({ sortedProviders, calendarData, isLoading }) => {
13
+ if (isLoading) {
14
+ return (
15
+ <div className="flex flex-col gap-8 max-w-4xl mx-auto mb-16">
16
+ {Array.from({ length: 3 }).map((_, idx) => (
17
+ <ProviderHeatmapSkeleton key={idx} />
18
+ ))}
19
+ </div>
20
+ );
21
+ }
22
+
23
+ return (
24
+ <div className="flex flex-col gap-8 max-w-4xl mx-auto mb-16">
25
+ {sortedProviders.map((provider, index) => (
26
+ <OrganizationCard
27
+ key={provider.fullName || provider.authors[0]}
28
+ provider={provider}
29
+ calendarData={calendarData}
30
+ rank={index + 1}
31
+ />
32
+ ))}
33
+ </div>
34
+ );
35
+ };
36
+
37
+ export default HeatmapGrid;
src/components/OrganizationCard.tsx CHANGED
@@ -1,207 +1,45 @@
1
  import React from "react";
2
- import { ProviderInfo } from "../types/heatmap";
3
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
 
4
 
5
  interface OrganizationCardProps {
6
  provider: ProviderInfo;
7
- calendarKey: string;
8
- providerName: string;
9
- totalCount: number;
10
  }
11
 
12
- const OrganizationCard: React.FC<OrganizationCardProps> = ({
13
- provider,
14
- calendarKey,
15
- providerName,
16
- totalCount,
17
- }) => {
18
  return (
19
- <TooltipProvider>
20
- <div className="mb-4">
21
- {/* Organization Name & Stats Badge */}
22
- <div className="text-center bg-muted/20 rounded-lg p-3 border border-border/30">
23
- {/* Avatar and Name Row */}
24
- <div className="flex items-center justify-center gap-2 mb-2">
25
- {/* Multi-author avatars and names */}
26
- {provider.authorsData && provider.authorsData.length > 1 ? (
27
- <div className="flex items-center gap-2 flex-wrap justify-center">
28
- {provider.authorsData.map((authorData, index) => (
29
- <React.Fragment key={authorData.author}>
30
- {index > 0 && (
31
- <span className="text-muted-foreground text-sm font-medium mx-1">+</span>
32
- )}
33
- <div className="flex items-center gap-1">
34
- <div className="relative">
35
- <img
36
- src={authorData.avatarUrl || `https://huggingface.co/${authorData.author}/avatar.jpg`}
37
- alt={`${authorData.fullName} logo`}
38
- className="w-8 h-8 rounded-md shadow-sm border border-border/50"
39
- onError={(e) => {
40
- const target = e.target as HTMLImageElement;
41
- target.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(authorData.fullName)}&background=random`;
42
- }}
43
- />
44
- {authorData.isVerified && (
45
- <div className="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-blue-500 rounded-full flex items-center justify-center">
46
- <svg className="w-2 h-2 text-white" fill="currentColor" viewBox="0 0 20 20">
47
- <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
48
- </svg>
49
- </div>
50
- )}
51
- </div>
52
- <span className="text-base font-bold text-foreground">
53
- <a
54
- href={`https://huggingface.co/${authorData.author}`}
55
- target="_blank"
56
- rel="noopener noreferrer"
57
- className="hover:text-blue-500 hover:underline transition-colors duration-200"
58
- >
59
- {authorData.author}
60
- </a>
61
- </span>
62
- </div>
63
- </React.Fragment>
64
- ))}
65
- </div>
66
- ) : (
67
- <div className="flex items-center gap-2">
68
- {provider.avatarUrl && (
69
- <div className="relative">
70
- <img
71
- src={provider.avatarUrl}
72
- alt={`${providerName} logo`}
73
- className="w-8 h-8 rounded-md shadow-sm border border-border/50"
74
- />
75
- {provider.isVerified && (
76
- <div className="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-blue-500 rounded-full flex items-center justify-center">
77
- <svg className="w-2 h-2 text-white" fill="currentColor" viewBox="0 0 20 20">
78
- <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
79
- </svg>
80
- </div>
81
- )}
82
- </div>
83
- )}
84
- <h3 className="text-base font-bold text-foreground">
85
- <a
86
- href={`https://huggingface.co/${calendarKey}`}
87
- target="_blank"
88
- rel="noopener noreferrer"
89
- className="hover:text-blue-500 hover:underline transition-colors duration-200"
90
- >
91
- {providerName}
92
- </a>
93
- </h3>
94
- </div>
95
- )}
96
- </div>
97
-
98
- {/* Compact Organization Stats */}
99
- <div className="flex flex-wrap justify-center gap-2 text-xs mb-3">
100
- {provider.authorsData && provider.authorsData.length > 1 ? (
101
- <Tooltip>
102
- <TooltipTrigger asChild>
103
- <div className="bg-background/60 rounded px-2 py-1 border border-border/40 cursor-pointer">
104
- <span className="font-semibold text-foreground">{(provider.numModels || 0).toLocaleString()}</span>
105
- <span className="text-muted-foreground ml-1">models</span>
106
- </div>
107
- </TooltipTrigger>
108
- <TooltipContent side="bottom">
109
- <div className="text-xs">
110
- {provider.authorsData.map(author => (
111
- <div key={`models-${author.author}`}>
112
- {author.author}: {author.numModels.toLocaleString()}
113
- </div>
114
- ))}
115
- </div>
116
- </TooltipContent>
117
- </Tooltip>
118
- ) : (
119
- <div className="bg-background/60 rounded px-2 py-1 border border-border/40">
120
- <span className="font-semibold text-foreground">{(provider.numModels || 0).toLocaleString()}</span>
121
- <span className="text-muted-foreground ml-1">models</span>
122
- </div>
123
- )}
124
-
125
- {provider.authorsData && provider.authorsData.length > 1 ? (
126
- <Tooltip>
127
- <TooltipTrigger asChild>
128
- <div className="bg-background/60 rounded px-2 py-1 border border-border/40 cursor-pointer">
129
- <span className="font-semibold text-foreground">{(provider.numDatasets || 0).toLocaleString()}</span>
130
- <span className="text-muted-foreground ml-1">datasets</span>
131
- </div>
132
- </TooltipTrigger>
133
- <TooltipContent side="bottom">
134
- <div className="text-xs">
135
- {provider.authorsData.map(author => (
136
- <div key={`datasets-${author.author}`}>
137
- {author.author}: {author.numDatasets.toLocaleString()}
138
- </div>
139
- ))}
140
- </div>
141
- </TooltipContent>
142
- </Tooltip>
143
- ) : (
144
- <div className="bg-background/60 rounded px-2 py-1 border border-border/40">
145
- <span className="font-semibold text-foreground">{(provider.numDatasets || 0).toLocaleString()}</span>
146
- <span className="text-muted-foreground ml-1">datasets</span>
147
- </div>
148
- )}
149
-
150
- {provider.authorsData && provider.authorsData.length > 1 ? (
151
- <Tooltip>
152
- <TooltipTrigger asChild>
153
- <div className="bg-background/60 rounded px-2 py-1 border border-border/40 cursor-pointer">
154
- <span className="font-semibold text-foreground">{(provider.numSpaces || 0).toLocaleString()}</span>
155
- <span className="text-muted-foreground ml-1">spaces</span>
156
- </div>
157
- </TooltipTrigger>
158
- <TooltipContent side="bottom">
159
- <div className="text-xs">
160
- {provider.authorsData.map(author => (
161
- <div key={`spaces-${author.author}`}>
162
- {author.author}: {author.numSpaces.toLocaleString()}
163
- </div>
164
- ))}
165
- </div>
166
- </TooltipContent>
167
- </Tooltip>
168
- ) : (
169
- <div className="bg-background/60 rounded px-2 py-1 border border-border/40">
170
- <span className="font-semibold text-foreground">{(provider.numSpaces || 0).toLocaleString()}</span>
171
- <span className="text-muted-foreground ml-1">spaces</span>
172
- </div>
173
- )}
174
-
175
- {provider.authorsData && provider.authorsData.length > 1 ? (
176
- <Tooltip>
177
- <TooltipTrigger asChild>
178
- <div className="bg-background/60 rounded px-2 py-1 border border-border/40 cursor-pointer">
179
- <span className="font-semibold text-foreground">{(provider.numFollowers || 0).toLocaleString()}</span>
180
- <span className="text-muted-foreground ml-1">followers</span>
181
- </div>
182
- </TooltipTrigger>
183
- <TooltipContent side="bottom">
184
- <div className="text-xs">
185
- {provider.authorsData.map(author => (
186
- <div key={`followers-${author.author}`}>
187
- {author.author}: {author.numFollowers.toLocaleString()}
188
- </div>
189
- ))}
190
- </div>
191
- </TooltipContent>
192
- </Tooltip>
193
- ) : (
194
- <div className="bg-background/60 rounded px-2 py-1 border border-border/40">
195
- <span className="font-semibold text-foreground">{(provider.numFollowers || 0).toLocaleString()}</span>
196
- <span className="text-muted-foreground ml-1">followers</span>
197
- </div>
198
- )}
199
- </div>
200
-
201
- </div>
202
  </div>
203
- </TooltipProvider>
 
 
 
 
204
  );
205
- };
206
 
207
- export default OrganizationCard;
 
1
  import React from "react";
2
+ import { ProviderInfo, CalendarData } from "../types/heatmap";
3
+ import Heatmap from "./Heatmap";
4
+ import OrganizationHeader from "./OrganizationHeader";
5
 
6
  interface OrganizationCardProps {
7
  provider: ProviderInfo;
8
+ calendarData: CalendarData;
9
+ rank: number;
 
10
  }
11
 
12
+ const OrganizationCard = React.memo(({ provider, calendarData, rank }: OrganizationCardProps) => {
13
+ const providerName = provider.fullName || provider.authors[0];
14
+ const calendarKey = provider.authors[0];
15
+ const totalCount = calendarData[calendarKey]?.reduce((sum, day) => sum + day.count, 0) || 0;
16
+
 
17
  return (
18
+ <div id={`provider-${calendarKey}`} className="relative bg-gradient-to-br from-card to-card/95 rounded-2xl border border-border shadow-lg hover:shadow-xl transition-all duration-300 p-6 group">
19
+ <OrganizationHeader
20
+ provider={provider}
21
+ calendarKey={calendarKey}
22
+ providerName={providerName}
23
+ totalCount={totalCount}
24
+ />
25
+
26
+ <div className="flex flex-col items-center bg-muted/30 rounded-xl p-3 group-hover:bg-muted/40 transition-colors duration-300">
27
+ <Heatmap
28
+ data={calendarData[calendarKey]}
29
+ color={provider.color}
30
+ providerName={providerName}
31
+ fullName={provider.fullName ?? providerName}
32
+ avatarUrl={provider.avatarUrl ?? ''}
33
+ authorId={calendarKey}
34
+ showHeader={false}
35
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  </div>
37
+
38
+ <div className="mt-3 text-xs text-muted-foreground italic text-center">
39
+ <span className="font-bold text-foreground">{totalCount}</span> new repos in the last year
40
+ </div>
41
+ </div>
42
  );
43
+ });
44
 
45
+ export default OrganizationCard;
src/components/OrganizationHeader.tsx ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from "react";
2
+ import { ProviderInfo } from "../types/heatmap";
3
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
4
+
5
+ interface OrganizationHeaderProps {
6
+ provider: ProviderInfo;
7
+ calendarKey: string;
8
+ providerName: string;
9
+ totalCount: number;
10
+ }
11
+
12
+ const OrganizationHeader: React.FC<OrganizationHeaderProps> = ({
13
+ provider,
14
+ calendarKey,
15
+ providerName,
16
+ totalCount,
17
+ }) => {
18
+ return (
19
+ <TooltipProvider>
20
+ <div className="mb-4">
21
+ {/* Organization Name & Stats Badge */}
22
+ <div className="text-center bg-muted/20 rounded-lg p-3 border border-border/30">
23
+ {/* Avatar and Name Row */}
24
+ <div className="flex items-center justify-center gap-2 mb-2">
25
+ {/* Multi-author avatars and names */}
26
+ {provider.authorsData && provider.authorsData.length > 1 ? (
27
+ <div className="flex items-center gap-2 flex-wrap justify-center">
28
+ {provider.authorsData.map((authorData, index) => (
29
+ <React.Fragment key={authorData.author}>
30
+ {index > 0 && (
31
+ <span className="text-muted-foreground text-sm font-medium mx-1">+</span>
32
+ )}
33
+ <div className="flex items-center gap-1">
34
+ <div className="relative">
35
+ <img
36
+ src={authorData.avatarUrl || `https://huggingface.co/${authorData.author}/avatar.jpg`}
37
+ alt={`${authorData.fullName} logo`}
38
+ className="w-8 h-8 rounded-md shadow-sm border border-border/50"
39
+ onError={(e) => {
40
+ const target = e.target as HTMLImageElement;
41
+ target.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(authorData.fullName)}&background=random`;
42
+ }}
43
+ />
44
+ {authorData.isVerified && (
45
+ <div className="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-blue-500 rounded-full flex items-center justify-center">
46
+ <svg className="w-2 h-2 text-white" fill="currentColor" viewBox="0 0 20 20">
47
+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
48
+ </svg>
49
+ </div>
50
+ )}
51
+ </div>
52
+ <span className="text-base font-bold text-foreground">
53
+ <a
54
+ href={`https://huggingface.co/${authorData.author}`}
55
+ target="_blank"
56
+ rel="noopener noreferrer"
57
+ className="hover:text-blue-500 hover:underline transition-colors duration-200"
58
+ >
59
+ {authorData.author}
60
+ </a>
61
+ </span>
62
+ </div>
63
+ </React.Fragment>
64
+ ))}
65
+ </div>
66
+ ) : (
67
+ <div className="flex items-center gap-2">
68
+ {provider.avatarUrl && (
69
+ <div className="relative">
70
+ <img
71
+ src={provider.avatarUrl}
72
+ alt={`${providerName} logo`}
73
+ className="w-8 h-8 rounded-md shadow-sm border border-border/50"
74
+ />
75
+ {provider.isVerified && (
76
+ <div className="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-blue-500 rounded-full flex items-center justify-center">
77
+ <svg className="w-2 h-2 text-white" fill="currentColor" viewBox="0 0 20 20">
78
+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
79
+ </svg>
80
+ </div>
81
+ )}
82
+ </div>
83
+ )}
84
+ <h3 className="text-base font-bold text-foreground">
85
+ <a
86
+ href={`https://huggingface.co/${calendarKey}`}
87
+ target="_blank"
88
+ rel="noopener noreferrer"
89
+ className="hover:text-blue-500 hover:underline transition-colors duration-200"
90
+ >
91
+ {providerName}
92
+ </a>
93
+ </h3>
94
+ </div>
95
+ )}
96
+ </div>
97
+
98
+ {/* Compact Organization Stats */}
99
+ <div className="flex flex-wrap justify-center gap-2 text-xs mb-3">
100
+ {provider.authorsData && provider.authorsData.length > 1 ? (
101
+ <Tooltip>
102
+ <TooltipTrigger asChild>
103
+ <div className="bg-background/60 rounded px-2 py-1 border border-border/40 cursor-pointer">
104
+ <span className="font-semibold text-foreground">{(provider.numModels || 0).toLocaleString()}</span>
105
+ <span className="text-muted-foreground ml-1">models</span>
106
+ </div>
107
+ </TooltipTrigger>
108
+ <TooltipContent side="bottom">
109
+ <div className="text-xs">
110
+ {provider.authorsData.map(author => (
111
+ <div key={`models-${author.author}`}>
112
+ {author.author}: {author.numModels.toLocaleString()}
113
+ </div>
114
+ ))}
115
+ </div>
116
+ </TooltipContent>
117
+ </Tooltip>
118
+ ) : (
119
+ <div className="bg-background/60 rounded px-2 py-1 border border-border/40">
120
+ <span className="font-semibold text-foreground">{(provider.numModels || 0).toLocaleString()}</span>
121
+ <span className="text-muted-foreground ml-1">models</span>
122
+ </div>
123
+ )}
124
+
125
+ {provider.authorsData && provider.authorsData.length > 1 ? (
126
+ <Tooltip>
127
+ <TooltipTrigger asChild>
128
+ <div className="bg-background/60 rounded px-2 py-1 border border-border/40 cursor-pointer">
129
+ <span className="font-semibold text-foreground">{(provider.numDatasets || 0).toLocaleString()}</span>
130
+ <span className="text-muted-foreground ml-1">datasets</span>
131
+ </div>
132
+ </TooltipTrigger>
133
+ <TooltipContent side="bottom">
134
+ <div className="text-xs">
135
+ {provider.authorsData.map(author => (
136
+ <div key={`datasets-${author.author}`}>
137
+ {author.author}: {author.numDatasets.toLocaleString()}
138
+ </div>
139
+ ))}
140
+ </div>
141
+ </TooltipContent>
142
+ </Tooltip>
143
+ ) : (
144
+ <div className="bg-background/60 rounded px-2 py-1 border border-border/40">
145
+ <span className="font-semibold text-foreground">{(provider.numDatasets || 0).toLocaleString()}</span>
146
+ <span className="text-muted-foreground ml-1">datasets</span>
147
+ </div>
148
+ )}
149
+
150
+ {provider.authorsData && provider.authorsData.length > 1 ? (
151
+ <Tooltip>
152
+ <TooltipTrigger asChild>
153
+ <div className="bg-background/60 rounded px-2 py-1 border border-border/40 cursor-pointer">
154
+ <span className="font-semibold text-foreground">{(provider.numSpaces || 0).toLocaleString()}</span>
155
+ <span className="text-muted-foreground ml-1">spaces</span>
156
+ </div>
157
+ </TooltipTrigger>
158
+ <TooltipContent side="bottom">
159
+ <div className="text-xs">
160
+ {provider.authorsData.map(author => (
161
+ <div key={`spaces-${author.author}`}>
162
+ {author.author}: {author.numSpaces.toLocaleString()}
163
+ </div>
164
+ ))}
165
+ </div>
166
+ </TooltipContent>
167
+ </Tooltip>
168
+ ) : (
169
+ <div className="bg-background/60 rounded px-2 py-1 border border-border/40">
170
+ <span className="font-semibold text-foreground">{(provider.numSpaces || 0).toLocaleString()}</span>
171
+ <span className="text-muted-foreground ml-1">spaces</span>
172
+ </div>
173
+ )}
174
+
175
+ {provider.authorsData && provider.authorsData.length > 1 ? (
176
+ <Tooltip>
177
+ <TooltipTrigger asChild>
178
+ <div className="bg-background/60 rounded px-2 py-1 border border-border/40 cursor-pointer">
179
+ <span className="font-semibold text-foreground">{(provider.numFollowers || 0).toLocaleString()}</span>
180
+ <span className="text-muted-foreground ml-1">followers</span>
181
+ </div>
182
+ </TooltipTrigger>
183
+ <TooltipContent side="bottom">
184
+ <div className="text-xs">
185
+ {provider.authorsData.map(author => (
186
+ <div key={`followers-${author.author}`}>
187
+ {author.author}: {author.numFollowers.toLocaleString()}
188
+ </div>
189
+ ))}
190
+ </div>
191
+ </TooltipContent>
192
+ </Tooltip>
193
+ ) : (
194
+ <div className="bg-background/60 rounded px-2 py-1 border border-border/40">
195
+ <span className="font-semibold text-foreground">{(provider.numFollowers || 0).toLocaleString()}</span>
196
+ <span className="text-muted-foreground ml-1">followers</span>
197
+ </div>
198
+ )}
199
+ </div>
200
+
201
+ </div>
202
+ </div>
203
+ </TooltipProvider>
204
+ );
205
+ };
206
+
207
+ export default OrganizationHeader;
src/constants/organizations.ts ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ProviderInfo } from "../types/heatmap";
2
+
3
+ export const ORGANIZATIONS: ProviderInfo[] = [
4
+ { color: "#ff7000", authors: ["mistralai"] },
5
+ { color: "#1877F2", authors: ["meta-llama","facebook", ] },
6
+ { color: "#10A37F", authors: ["openai"] },
7
+ { color: "#cc785c", authors: ["Anthropic"] },
8
+ { color: "#DB4437", authors: ["google"] },
9
+ { color: "#5E35B1", authors: ["allenai"] },
10
+ { color: "#0088cc", authors: ["apple"] },
11
+ { color: "#FEB800", authors: ["microsoft"] },
12
+ { color: "#76B900", authors: ["nvidia"] },
13
+ { color: "#0088cc", authors: ["deepseek-ai"] },
14
+ { color: "#0088cc", authors: ["Qwen"] },
15
+ { color: "#4C6EE6", authors: ["CohereLabs"] },
16
+ { color: "#4C6EE6", authors: ["ibm-granite"] },
17
+ { color: "#A020F0", authors: ["stabilityai"] },
18
+ { color: "#FEC912", authors: ["huggingface", "OpenEvals", "HuggingFaceTB","HuggingFaceH4", "HuggingFaceM4", "HuggingFaceFW", "HuggingFaceFV","open-r1","parler-tts","nanotron","lerobot","distilbert"] },
19
+ ];
src/pages/index.tsx CHANGED
@@ -1,105 +1,20 @@
1
- import React, { useState, useEffect, useMemo } from "react";
2
- import { generateCalendarData } from "../utils/calendar";
3
- import {
4
- OpenSourceHeatmapProps,
5
- ProviderInfo,
6
- ModelData,
7
- CalendarData,
8
- } from "../types/heatmap";
9
- import Heatmap from "../components/Heatmap";
10
- import { fetchAllProvidersData, fetchAllAuthorsData } from "../utils/authors";
11
- import UserSearchDialog from "../components/UserSearchDialog";
12
  import ProviderSummary from "../components/ProviderSummary";
13
- import OrganizationCard from "../components/OrganizationCard";
14
- import ProviderHeatmapSkeleton from "../components/ProviderHeatmapSkeleton";
 
 
15
 
16
- const PROVIDERS: ProviderInfo[] = [
17
- { color: "#ff7000", authors: ["mistralai"] },
18
- { color: "#1877F2", authors: ["meta-llama","facebook", ] },
19
- { color: "#10A37F", authors: ["openai"] },
20
- { color: "#cc785c", authors: ["Anthropic"] },
21
- { color: "#DB4437", authors: ["google"] },
22
- { color: "#5E35B1", authors: ["allenai"] },
23
- { color: "#0088cc", authors: ["apple"] },
24
- { color: "#FEB800", authors: ["microsoft"] },
25
- { color: "#76B900", authors: ["nvidia"] },
26
- { color: "#0088cc", authors: ["deepseek-ai"] },
27
- { color: "#0088cc", authors: ["Qwen"] },
28
- { color: "#4C6EE6", authors: ["CohereLabs"] },
29
- { color: "#4C6EE6", authors: ["ibm-granite"] },
30
- { color: "#A020F0", authors: ["stabilityai"] },
31
- { color: "#FEC912", authors: ["huggingface", "OpenEvals", "HuggingFaceTB","HuggingFaceH4", "HuggingFaceM4", "HuggingFaceFW", "HuggingFaceFV","open-r1","parler-tts","nanotron","lerobot","distilbert"] },
32
- ];
33
-
34
- export async function getStaticProps() {
35
- try {
36
- const allAuthors = PROVIDERS.flatMap(({ authors }) => authors);
37
- const uniqueAuthors = Array.from(new Set(allAuthors));
38
-
39
- const flatData: ModelData[] = await fetchAllAuthorsData(uniqueAuthors);
40
- const updatedProviders = await fetchAllProvidersData(PROVIDERS);
41
-
42
- const calendarData = generateCalendarData(flatData, updatedProviders);
43
-
44
- return {
45
- props: {
46
- calendarData,
47
- providers: updatedProviders,
48
- },
49
- revalidate: 3600, // regenerate every hour
50
- };
51
- } catch (error) {
52
- console.error("Error fetching data:", error);
53
- return {
54
- props: {
55
- calendarData: {},
56
- providers: PROVIDERS,
57
- },
58
- revalidate: 60, // retry after 1 minute if there was an error
59
- };
60
- }
61
  }
62
 
63
- const ProviderHeatmap = React.memo(({ provider, calendarData }: { provider: ProviderInfo, calendarData: CalendarData }) => {
64
- const providerName = provider.fullName || provider.authors[0];
65
- const calendarKey = provider.authors[0]; // Use the same key as calendar generation
66
- const totalCount = calendarData[calendarKey]?.reduce((sum, day) => sum + day.count, 0) || 0;
67
-
68
- return (
69
- <div id={`provider-${calendarKey}`} className="relative bg-gradient-to-br from-card to-card/95 rounded-2xl border border-border shadow-lg hover:shadow-xl transition-all duration-300 p-6 group">
70
- {/* Organization Header */}
71
- <OrganizationCard
72
- provider={provider}
73
- calendarKey={calendarKey}
74
- providerName={providerName}
75
- totalCount={totalCount}
76
- />
77
-
78
- {/* Heatmap Section */}
79
- <div className="flex flex-col items-center bg-muted/30 rounded-xl p-3 group-hover:bg-muted/40 transition-colors duration-300">
80
- <Heatmap
81
- data={calendarData[calendarKey]}
82
- color={provider.color}
83
- providerName={providerName}
84
- fullName={provider.fullName ?? providerName}
85
- avatarUrl={provider.avatarUrl ?? ''}
86
- authorId={calendarKey}
87
- showHeader={false}
88
- />
89
- </div>
90
-
91
- {/* Releases Past Year placed under the heatmap */}
92
- <div className="mt-3 text-xs text-muted-foreground italic text-center">
93
- <span className="font-bold text-foreground">{totalCount}</span> new repos in the last year
94
- </div>
95
- </div>
96
- );
97
- });
98
-
99
- const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
100
  calendarData,
101
  providers,
102
- }) => {
103
  const [isLoading, setIsLoading] = useState(true);
104
 
105
  useEffect(() => {
@@ -108,16 +23,6 @@ const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
108
  }
109
  }, [calendarData]);
110
 
111
- const sortedProviders = useMemo(() =>
112
- providers.sort((a, b) => {
113
- const aData = calendarData[a.authors[0]] || [];
114
- const bData = calendarData[b.authors[0]] || [];
115
- const aCount = aData.reduce((sum, day) => sum + day.count, 0);
116
- const bCount = bData.reduce((sum, day) => sum + day.count, 0);
117
- return bCount - aCount;
118
- }),
119
- [providers, calendarData]
120
- );
121
 
122
  return (
123
  <div className="w-full p-4 py-16 relative">
@@ -130,11 +35,10 @@ const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
130
  </p>
131
  </div>
132
 
133
- {/* Provider Summary List */}
134
  <div className="mb-16 mx-auto">
135
  <div className="overflow-x-auto scrollbar-hide">
136
  <div className="flex gap-6 px-4 py-2 min-w-max justify-center">
137
- {sortedProviders.map((provider, index) => (
138
  <ProviderSummary
139
  key={provider.fullName || provider.authors[0]}
140
  provider={provider}
@@ -146,59 +50,39 @@ const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
146
  </div>
147
  </div>
148
 
149
- {isLoading ? (
150
- <div className="flex flex-col gap-8 max-w-4xl mx-auto mb-16">
151
- {Array.from({ length: 3 }).map((_, idx) => (
152
- <ProviderHeatmapSkeleton key={idx} />
153
- ))}
154
- </div>
155
- ) : (
156
- <>
157
- <div className="mb-16">
158
- <div className="flex flex-col gap-8 max-w-4xl mx-auto">
159
- {sortedProviders.slice(0, 3).map((provider, index) => (
160
- <ProviderHeatmap
161
- key={provider.fullName || provider.authors[0]}
162
- provider={provider}
163
- calendarData={calendarData}
164
- />
165
- ))}
166
- </div>
167
- </div>
168
-
169
- {/* Rest of the providers */}
170
- {sortedProviders.length > 3 && (
171
- <div className="flex flex-col gap-8 max-w-4xl mx-auto">
172
- {sortedProviders.slice(3).map((provider, index) => (
173
- <ProviderHeatmap
174
- key={provider.fullName || provider.authors[0]}
175
- provider={provider}
176
- calendarData={calendarData}
177
- />
178
- ))}
179
- </div>
180
- )}
181
- </>
182
- )}
183
 
184
- {/* CTA Section */}
185
- <div className="mt-24 mb-16 flex justify-center">
186
- <div className="bg-gradient-to-br from-card to-card/95 rounded-2xl border border-border shadow-lg hover:shadow-xl transition-all duration-300 p-8 max-w-2xl w-full text-center space-y-6">
187
- <div className="space-y-4">
188
- <h2 className="text-2xl lg:text-3xl font-bold text-foreground">
189
- Want Your Own Heatmap?
190
- </h2>
191
- <p className="text-muted-foreground text-lg">
192
- Search for any Hugging Face organization or user to see their model release activity.
193
- </p>
194
- </div>
195
- <div className="flex justify-center">
196
- <UserSearchDialog />
197
- </div>
198
- </div>
199
- </div>
200
  </div>
201
  );
202
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
- export default OpenSourceHeatmap;
 
1
+ import React, { useState, useEffect } from "react";
2
+ import { ProviderInfo, ModelData, CalendarData } from "../types/heatmap";
 
 
 
 
 
 
 
 
 
3
  import ProviderSummary from "../components/ProviderSummary";
4
+ import HeatmapGrid from "../components/HeatmapGrid";
5
+ import CTASection from "../components/CTASection";
6
+ import { getProviders } from "../utils/ranking";
7
+ import { ORGANIZATIONS } from "../constants/organizations";
8
 
9
+ interface PageProps {
10
+ calendarData: CalendarData;
11
+ providers: ProviderInfo[];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  }
13
 
14
+ function Page({
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  calendarData,
16
  providers,
17
+ }: PageProps) {
18
  const [isLoading, setIsLoading] = useState(true);
19
 
20
  useEffect(() => {
 
23
  }
24
  }, [calendarData]);
25
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  return (
28
  <div className="w-full p-4 py-16 relative">
 
35
  </p>
36
  </div>
37
 
 
38
  <div className="mb-16 mx-auto">
39
  <div className="overflow-x-auto scrollbar-hide">
40
  <div className="flex gap-6 px-4 py-2 min-w-max justify-center">
41
+ {providers.map((provider, index) => (
42
  <ProviderSummary
43
  key={provider.fullName || provider.authors[0]}
44
  provider={provider}
 
50
  </div>
51
  </div>
52
 
53
+ <HeatmapGrid
54
+ sortedProviders={providers}
55
+ calendarData={calendarData}
56
+ isLoading={isLoading}
57
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
+ <CTASection />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  </div>
61
  );
62
+ }
63
+
64
+
65
+ export async function getStaticProps() {
66
+ try {
67
+ const { calendarData, providers } = await getProviders(ORGANIZATIONS);
68
+
69
+ return {
70
+ props: {
71
+ calendarData,
72
+ providers,
73
+ },
74
+ revalidate: 3600, // regenerate every hour
75
+ };
76
+ } catch (error) {
77
+ console.error("Error fetching data:", error);
78
+ return {
79
+ props: {
80
+ calendarData: {},
81
+ providers: ORGANIZATIONS,
82
+ },
83
+ revalidate: 60, // retry after 1 minute if there was an error
84
+ };
85
+ }
86
+ }
87
 
88
+ export default Page;
src/utils/ranking.ts CHANGED
@@ -1,3 +1,7 @@
 
 
 
 
1
  export interface RankingBadge {
2
  className: string;
3
  icon: string | null;
@@ -25,4 +29,45 @@ export const getRankingBadge = (rank: number): RankingBadge => {
25
  icon: null
26
  };
27
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  };
 
1
+ import { ProviderInfo, CalendarData, ModelData } from "../types/heatmap";
2
+ import { generateCalendarData } from "./calendar";
3
+ import { fetchAllProvidersData, fetchAllAuthorsData } from "./authors";
4
+
5
  export interface RankingBadge {
6
  className: string;
7
  icon: string | null;
 
29
  icon: null
30
  };
31
  }
32
+ };
33
+
34
+ export const extractUniqueAuthors = (providers: ProviderInfo[]): string[] => {
35
+ const allAuthors = providers.flatMap(({ authors }) => authors);
36
+ return Array.from(new Set(allAuthors));
37
+ };
38
+
39
+ export const sortProvidersByActivity = (
40
+ providers: ProviderInfo[],
41
+ calendarData: CalendarData
42
+ ): ProviderInfo[] => {
43
+ return providers.sort((providerA, providerB) => {
44
+ const providerAData = calendarData[providerA.authors[0]] || [];
45
+ const providerBData = calendarData[providerB.authors[0]] || [];
46
+
47
+ const providerAActivityCount = providerAData.reduce(
48
+ (totalCount, day) => totalCount + day.count,
49
+ 0
50
+ );
51
+ const providerBActivityCount = providerBData.reduce(
52
+ (totalCount, day) => totalCount + day.count,
53
+ 0
54
+ );
55
+
56
+ return providerBActivityCount - providerAActivityCount;
57
+ });
58
+ };
59
+
60
+ export const getProviders = async (providers: ProviderInfo[]) => {
61
+ const uniqueAuthors = extractUniqueAuthors(providers);
62
+
63
+ const authorModelsData: ModelData[] = await fetchAllAuthorsData(uniqueAuthors);
64
+ const providersWithMetadata = await fetchAllProvidersData(providers);
65
+
66
+ const calendarData = generateCalendarData(authorModelsData, providersWithMetadata);
67
+ const sortedProvidersByActivity = sortProvidersByActivity(providersWithMetadata, calendarData);
68
+
69
+ return {
70
+ calendarData,
71
+ providers: sortedProvidersByActivity,
72
+ };
73
  };