cfahlgren1 HF Staff commited on
Commit
b86067d
·
1 Parent(s): 37b153e

clear up confusion, show combined authors

Browse files
src/components/OrganizationCard.tsx CHANGED
@@ -1,5 +1,6 @@
1
  import React from "react";
2
  import { ProviderInfo } from "../types/heatmap";
 
3
 
4
  interface OrganizationCardProps {
5
  provider: ProviderInfo;
@@ -15,66 +16,199 @@ const OrganizationCard: React.FC<OrganizationCardProps> = ({
15
  totalCount,
16
  }) => {
17
  return (
18
- <div className="mb-4">
19
- {/* Organization Name & Stats Badge */}
20
- <div className="text-center bg-muted/20 rounded-lg p-3 border border-border/30">
21
- {/* Avatar and Name Row */}
22
- <div className="flex items-center justify-center gap-2 mb-2">
23
- {provider.avatarUrl && (
24
- <div className="relative">
25
- <img
26
- src={provider.avatarUrl}
27
- alt={`${providerName} logo`}
28
- className="w-8 h-8 rounded-md shadow-sm border border-border/50"
29
- />
30
- {provider.isVerified && (
31
- <div className="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-blue-500 rounded-full flex items-center justify-center">
32
- <svg className="w-2 h-2 text-white" fill="currentColor" viewBox="0 0 20 20">
33
- <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" />
34
- </svg>
35
- </div>
36
- )}
37
- </div>
38
- )}
39
- <h3 className="text-base font-bold text-foreground">
40
- <a
41
- href={`https://huggingface.co/${calendarKey}`}
42
- target="_blank"
43
- rel="noopener noreferrer"
44
- className="hover:text-blue-500 hover:underline transition-colors duration-200"
45
- >
46
- {providerName}
47
- </a>
48
- </h3>
49
- </div>
50
-
51
- {/* Compact Organization Stats */}
52
- <div className="flex flex-wrap justify-center gap-2 text-xs mb-2">
53
- <div className="bg-background/60 rounded px-2 py-1 border border-border/40">
54
- <span className="font-semibold text-foreground">{(provider.numModels || 0).toLocaleString()}</span>
55
- <span className="text-muted-foreground ml-1">models</span>
56
- </div>
57
- <div className="bg-background/60 rounded px-2 py-1 border border-border/40">
58
- <span className="font-semibold text-foreground">{(provider.numDatasets || 0).toLocaleString()}</span>
59
- <span className="text-muted-foreground ml-1">datasets</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  </div>
61
- <div className="bg-background/60 rounded px-2 py-1 border border-border/40">
62
- <span className="font-semibold text-foreground">{(provider.numSpaces || 0).toLocaleString()}</span>
63
- <span className="text-muted-foreground ml-1">spaces</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  </div>
65
- <div className="bg-background/60 rounded px-2 py-1 border border-border/40">
66
- <span className="font-semibold text-foreground">{(provider.numFollowers || 0).toLocaleString()}</span>
67
- <span className="text-muted-foreground ml-1">followers</span>
 
 
 
 
68
  </div>
69
  </div>
70
-
71
- {/* Releases Past Year */}
72
- <div className="text-xs text-muted-foreground italic">
73
- <span className="font-bold text-foreground">{totalCount.toLocaleString()}</span> releases this past year
74
- </div>
75
  </div>
76
- </div>
77
  );
78
  };
79
 
80
- export default OrganizationCard;
 
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;
 
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">
28
+ {provider.authorsData.slice(0, 2).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
+ {/* Divider */}
202
+ <div className="border-t border-border/20 mb-3"></div>
203
+
204
+ {/* Releases Past Year */}
205
+ <div className="text-xs text-muted-foreground italic">
206
+ <span className="font-bold text-foreground">{totalCount.toLocaleString()}</span> in the last year
207
  </div>
208
  </div>
 
 
 
 
 
209
  </div>
210
+ </TooltipProvider>
211
  );
212
  };
213
 
214
+ export default OrganizationCard;
src/components/ProviderSummary.tsx CHANGED
@@ -71,7 +71,7 @@ const ProviderSummary: React.FC<ProviderSummaryProps> = ({
71
 
72
  {/* Release Count */}
73
  <div className="text-[10px] text-muted-foreground">
74
- {totalCount} releases
75
  </div>
76
 
77
 
 
71
 
72
  {/* Release Count */}
73
  <div className="text-[10px] text-muted-foreground">
74
+ {totalCount} in the last year
75
  </div>
76
 
77
 
src/pages/index.tsx CHANGED
@@ -15,7 +15,7 @@ import { getRankingBadge } from "../utils/ranking";
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"] },
@@ -120,7 +120,7 @@ const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
120
  </h1>
121
  <div className="text-center text-sm my-8 space-y-4">
122
  <p className="text-muted-foreground">
123
- Open models, datasets, and apps from the top AI labs the past year.
124
  </p>
125
  </div>
126
 
 
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"] },
 
120
  </h1>
121
  <div className="text-center text-sm my-8 space-y-4">
122
  <p className="text-muted-foreground">
123
+ Open models, datasets, and apps from the top AI labs in the last year.
124
  </p>
125
  </div>
126
 
src/types/heatmap.ts CHANGED
@@ -10,6 +10,18 @@ export interface ProviderInfo {
10
  numDatasets?: number;
11
  numFollowers?: number;
12
  numUsers?: number;
 
 
 
 
 
 
 
 
 
 
 
 
13
  }
14
 
15
  export interface ModelData {
 
10
  numDatasets?: number;
11
  numFollowers?: number;
12
  numUsers?: number;
13
+ authorsData?: {
14
+ author: string;
15
+ fullName: string;
16
+ avatarUrl: string | null;
17
+ isVerified: boolean;
18
+ isEnterprise: boolean;
19
+ numModels: number;
20
+ numSpaces: number;
21
+ numDatasets: number;
22
+ numFollowers: number;
23
+ numUsers: number;
24
+ }[];
25
  }
26
 
27
  export interface ModelData {
src/utils/authors.ts CHANGED
@@ -1,50 +1,109 @@
1
  import { ProviderInfo, ModelData } from "../types/heatmap";
2
 
3
  export async function fetchOrganizationData(authors: string[]) {
4
- const primaryAuthor = authors[0];
5
  try {
6
- // Try organizations API first
7
- const orgResponse = await fetch(`https://huggingface.co/api/organizations/${primaryAuthor}/overview`);
8
- if (orgResponse.ok) {
9
- const data = await orgResponse.json();
10
- return {
11
- fullName: data.fullname || primaryAuthor,
12
- avatarUrl: data.avatarUrl || null,
13
- isVerified: data.isVerified || false,
14
- isEnterprise: data.isEnterprise || false,
15
- numModels: data.numModels || 0,
16
- numSpaces: data.numSpaces || 0,
17
- numDatasets: data.numDatasets || 0,
18
- numFollowers: data.numFollowers || 0,
19
- numUsers: data.numUsers || 0,
20
- };
21
- }
22
-
23
- // Fallback to users API if organization doesn't exist
24
- const userResponse = await fetch(`https://huggingface.co/api/users/${primaryAuthor}/overview`);
25
- if (userResponse.ok) {
26
- const data = await userResponse.json();
27
- return {
28
- fullName: data.fullname || primaryAuthor,
29
- avatarUrl: data.avatarUrl || null,
30
- isVerified: false,
31
- isEnterprise: false,
32
- numModels: data.numModels || 0,
33
- numSpaces: data.numSpaces || 0,
34
- numDatasets: data.numDatasets || 0,
35
- numFollowers: data.numFollowers || 0,
36
- numUsers: 0,
37
- };
38
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
- throw new Error('Neither organization nor user API returned valid data');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  } catch (error) {
42
- console.error(`Error fetching organization data for ${primaryAuthor}:`, error);
 
43
  return {
44
  fullName: primaryAuthor,
45
  avatarUrl: null,
46
  isVerified: false,
47
  isEnterprise: false,
 
 
 
 
 
 
 
 
 
 
 
 
48
  numModels: 0,
49
  numSpaces: 0,
50
  numDatasets: 0,
 
1
  import { ProviderInfo, ModelData } from "../types/heatmap";
2
 
3
  export async function fetchOrganizationData(authors: string[]) {
 
4
  try {
5
+ // Fetch data for all authors
6
+ const authorsData = await Promise.all(
7
+ authors.map(async (author) => {
8
+ try {
9
+ // Try organizations API first
10
+ const orgResponse = await fetch(`https://huggingface.co/api/organizations/${author}/overview`);
11
+ if (orgResponse.ok) {
12
+ const data = await orgResponse.json();
13
+ return {
14
+ author,
15
+ fullName: data.fullname || author,
16
+ avatarUrl: data.avatarUrl || null,
17
+ isVerified: data.isVerified || false,
18
+ isEnterprise: data.isEnterprise || false,
19
+ numModels: data.numModels || 0,
20
+ numSpaces: data.numSpaces || 0,
21
+ numDatasets: data.numDatasets || 0,
22
+ numFollowers: data.numFollowers || 0,
23
+ numUsers: data.numUsers || 0,
24
+ };
25
+ }
26
+
27
+ // Fallback to users API if organization doesn't exist
28
+ const userResponse = await fetch(`https://huggingface.co/api/users/${author}/overview`);
29
+ if (userResponse.ok) {
30
+ const data = await userResponse.json();
31
+ return {
32
+ author,
33
+ fullName: data.fullname || author,
34
+ avatarUrl: data.avatarUrl || null,
35
+ isVerified: false,
36
+ isEnterprise: false,
37
+ numModels: data.numModels || 0,
38
+ numSpaces: data.numSpaces || 0,
39
+ numDatasets: data.numDatasets || 0,
40
+ numFollowers: data.numFollowers || 0,
41
+ numUsers: 0,
42
+ };
43
+ }
44
+
45
+ throw new Error('Neither organization nor user API returned valid data');
46
+ } catch (error) {
47
+ console.error(`Error fetching data for ${author}:`, error);
48
+ return {
49
+ author,
50
+ fullName: author,
51
+ avatarUrl: null,
52
+ isVerified: false,
53
+ isEnterprise: false,
54
+ numModels: 0,
55
+ numSpaces: 0,
56
+ numDatasets: 0,
57
+ numFollowers: 0,
58
+ numUsers: 0,
59
+ };
60
+ }
61
+ })
62
+ );
63
+
64
+ // Use the primary author for main display name and avatar
65
+ const primaryAuthor = authorsData[0];
66
 
67
+ // Aggregate stats from all authors
68
+ const aggregatedStats = authorsData.reduce(
69
+ (acc, authorData) => ({
70
+ numModels: acc.numModels + (authorData.numModels || 0),
71
+ numSpaces: acc.numSpaces + (authorData.numSpaces || 0),
72
+ numDatasets: acc.numDatasets + (authorData.numDatasets || 0),
73
+ numFollowers: acc.numFollowers + (authorData.numFollowers || 0),
74
+ numUsers: acc.numUsers + (authorData.numUsers || 0),
75
+ }),
76
+ { numModels: 0, numSpaces: 0, numDatasets: 0, numFollowers: 0, numUsers: 0 }
77
+ );
78
+
79
+ return {
80
+ fullName: primaryAuthor.fullName,
81
+ avatarUrl: primaryAuthor.avatarUrl,
82
+ isVerified: primaryAuthor.isVerified,
83
+ isEnterprise: primaryAuthor.isEnterprise,
84
+ authorsData, // Include all authors data for multi-logo display
85
+ ...aggregatedStats,
86
+ };
87
  } catch (error) {
88
+ console.error(`Error fetching organization data for authors:`, error);
89
+ const primaryAuthor = authors[0];
90
  return {
91
  fullName: primaryAuthor,
92
  avatarUrl: null,
93
  isVerified: false,
94
  isEnterprise: false,
95
+ authorsData: [{
96
+ author: primaryAuthor,
97
+ fullName: primaryAuthor,
98
+ avatarUrl: null,
99
+ isVerified: false,
100
+ isEnterprise: false,
101
+ numModels: 0,
102
+ numSpaces: 0,
103
+ numDatasets: 0,
104
+ numFollowers: 0,
105
+ numUsers: 0,
106
+ }],
107
  numModels: 0,
108
  numSpaces: 0,
109
  numDatasets: 0,