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

update provider summary

Browse files
public/hf-icon.svg ADDED
src/components/CTASection.tsx DELETED
@@ -1,24 +0,0 @@
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/Navbar.tsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from "react";
2
+ import UserSearchDialog from "./UserSearchDialog";
3
+
4
+ const Navbar: React.FC = () => {
5
+ return (
6
+ <nav className="w-full mt-4">
7
+ <div className="max-w-6xl mx-auto px-4 py-3">
8
+ <div className="flex items-center justify-end">
9
+ <UserSearchDialog />
10
+ </div>
11
+ </div>
12
+ </nav>
13
+ );
14
+ };
15
+
16
+ export default Navbar;
src/components/ProviderSummary.tsx CHANGED
@@ -1,5 +1,6 @@
1
  import React from "react";
2
  import { ProviderInfo, CalendarData } from "../types/heatmap";
 
3
 
4
  interface ProviderSummaryProps {
5
  provider: ProviderInfo;
@@ -38,45 +39,49 @@ const ProviderSummary: React.FC<ProviderSummaryProps> = ({
38
  };
39
 
40
  return (
41
- <div
42
- className="flex flex-col items-center min-w-0 flex-shrink-0 cursor-pointer group px-1"
43
- onClick={handleClick}
44
- >
45
- {/* Logo Circle */}
46
- <div className="relative">
47
- {/* Rank Badge */}
48
- <div className={`absolute -top-2 -left-2 ${getRankBadgeClasses()} text-xs font-bold rounded-full min-w-[24px] h-6 flex items-center justify-center px-1.5 shadow-lg border-2 border-background z-10`}>
49
- #{rank}
50
- </div>
 
 
 
51
 
52
- {provider.avatarUrl ? (
53
- <img
54
- src={provider.avatarUrl}
55
- alt={`${providerName} logo`}
56
- className="w-16 h-16 rounded-full shadow-lg border-2 border-border/50 hover:border-blue-500/50 transition-all duration-200"
57
- />
58
- ) : (
59
- <div className="w-16 h-16 rounded-full bg-muted flex items-center justify-center text-xl font-bold text-muted-foreground hover:bg-muted/80 transition-all duration-200">
60
- {providerName.charAt(0).toUpperCase()}
61
- </div>
62
- )}
63
- </div>
64
 
65
- {/* Activity Info */}
66
- <div className="mt-1.5 text-center space-y-0.5">
67
- {/* Provider Name */}
68
- <div className="text-xs font-medium text-foreground truncate max-w-20 text-center">
69
- {providerName}
70
- </div>
71
-
72
- {/* Release Count */}
73
- <div className="text-[10px] text-muted-foreground">
74
- {totalCount} in the last year
75
- </div>
76
-
77
-
78
- </div>
79
- </div>
 
80
  );
81
  };
82
 
 
1
  import React from "react";
2
  import { ProviderInfo, CalendarData } from "../types/heatmap";
3
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
4
 
5
  interface ProviderSummaryProps {
6
  provider: ProviderInfo;
 
39
  };
40
 
41
  return (
42
+ <TooltipProvider>
43
+ <Tooltip>
44
+ <TooltipTrigger asChild>
45
+ <div
46
+ className="flex flex-col items-center min-w-0 flex-shrink-0 cursor-pointer group px-1"
47
+ onClick={handleClick}
48
+ >
49
+ {/* Logo Circle */}
50
+ <div className="relative">
51
+ {/* Rank Badge */}
52
+ <div className={`absolute -top-2 -left-2 ${getRankBadgeClasses()} text-xs font-bold rounded-full min-w-[24px] h-6 flex items-center justify-center px-1.5 shadow-lg border-2 border-background z-10`}>
53
+ #{rank}
54
+ </div>
55
 
56
+ {provider.avatarUrl ? (
57
+ <img
58
+ src={provider.avatarUrl}
59
+ alt={`${providerName} logo`}
60
+ className="w-16 h-16 rounded-lg shadow-lg transition-all duration-200 bg-white dark:bg-gray-900"
61
+ />
62
+ ) : (
63
+ <div className="w-16 h-16 rounded-lg bg-muted flex items-center justify-center text-xl font-bold text-muted-foreground hover:bg-muted/80 transition-all duration-200">
64
+ {providerName.charAt(0).toUpperCase()}
65
+ </div>
66
+ )}
67
+ </div>
68
 
69
+ {/* Activity Info */}
70
+ <div className="mt-1.5 text-center">
71
+ {/* Provider Name */}
72
+ <div className="text-xs font-medium text-foreground truncate max-w-20 text-center">
73
+ {providerName}
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </TooltipTrigger>
78
+ <TooltipContent side="bottom" className="bg-background border border-border shadow-lg">
79
+ <p className="text-sm font-medium">
80
+ {totalCount} new repos in the last year
81
+ </p>
82
+ </TooltipContent>
83
+ </Tooltip>
84
+ </TooltipProvider>
85
  );
86
  };
87
 
src/components/UserSearchDialog.tsx CHANGED
@@ -99,7 +99,9 @@ const UserSearchDialog = () => {
99
  return (
100
  <Dialog open={isOpen} onOpenChange={handleDialogOpenChange}>
101
  <DialogTrigger asChild>
102
- <Button variant="outline">Search</Button>
 
 
103
  </DialogTrigger>
104
  <DialogContent className="w-full max-w-[95vw] sm:max-w-4xl p-4 sm:p-6 max-h-[90vh] flex flex-col">
105
  <DialogHeader>
 
99
  return (
100
  <Dialog open={isOpen} onOpenChange={handleDialogOpenChange}>
101
  <DialogTrigger asChild>
102
+ <button className="text-sm text-foreground hover:text-blue-500 transition-colors duration-200">
103
+ Search
104
+ </button>
105
  </DialogTrigger>
106
  <DialogContent className="w-full max-w-[95vw] sm:max-w-4xl p-4 sm:p-6 max-h-[90vh] flex flex-col">
107
  <DialogHeader>
src/constants/organizations.ts CHANGED
@@ -6,7 +6,7 @@ export const ORGANIZATIONS: ProviderInfo[] = [
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"] },
 
6
  { color: "#10A37F", authors: ["openai"] },
7
  { color: "#cc785c", authors: ["Anthropic"] },
8
  { color: "#DB4437", authors: ["google"] },
9
+ { color: "#F45098", authors: ["allenai"] },
10
  { color: "#0088cc", authors: ["apple"] },
11
  { color: "#FEB800", authors: ["microsoft"] },
12
  { color: "#76B900", authors: ["nvidia"] },
src/pages/index.tsx CHANGED
@@ -2,7 +2,7 @@ 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
 
@@ -25,38 +25,47 @@ function Page({
25
 
26
 
27
  return (
28
- <div className="w-full p-4 py-16 relative">
29
- <h1 className="text-3xl lg:text-5xl mt-16 font-bold text-center mb-2 text-foreground">
30
- Hugging Face Heatmap 🤗
31
- </h1>
32
- <div className="text-center text-sm my-8 space-y-4">
33
- <p className="text-muted-foreground">
34
- Open models, datasets, and apps from the top AI labs in the last year.
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}
45
- calendarData={calendarData}
46
- rank={index + 1}
47
  />
48
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  </div>
50
  </div>
 
 
 
 
 
 
51
  </div>
52
-
53
- <HeatmapGrid
54
- sortedProviders={providers}
55
- calendarData={calendarData}
56
- isLoading={isLoading}
57
- />
58
-
59
- <CTASection />
60
  </div>
61
  );
62
  }
 
2
  import { ProviderInfo, ModelData, CalendarData } from "../types/heatmap";
3
  import ProviderSummary from "../components/ProviderSummary";
4
  import HeatmapGrid from "../components/HeatmapGrid";
5
+ import Navbar from "../components/Navbar";
6
  import { getProviders } from "../utils/ranking";
7
  import { ORGANIZATIONS } from "../constants/organizations";
8
 
 
25
 
26
 
27
  return (
28
+ <div className="w-full">
29
+ <Navbar />
30
+
31
+ <div className="w-full p-4 py-16">
32
+ <div className="text-center mb-16 max-w-4xl mx-auto">
33
+ <h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-6xl font-bold text-foreground mb-6 bg-gradient-to-r from-foreground to-foreground/80 bg-clip-text">
34
+ <span className="inline-flex items-center gap-1 sm:gap-2">
35
+ Hugging Face Heatmap
36
+ <img
37
+ src="/hf-icon.svg"
38
+ alt="Hugging Face icon"
39
+ className="size-6 sm:size-8 md:size-10"
 
 
 
 
 
 
 
40
  />
41
+ </span>
42
+ </h1>
43
+ <p className="text-base sm:text-lg lg:text-xl text-muted-foreground max-w-2xl mx-auto leading-relaxed px-4">
44
+ Open models, datasets, and apps from popular AI labs in the last year.
45
+ </p>
46
+ </div>
47
+
48
+ <div className="mb-16 mx-auto">
49
+ <div className="overflow-x-auto scrollbar-hide">
50
+ <div className="flex gap-6 px-4 py-2 min-w-max justify-center">
51
+ {providers.map((provider, index) => (
52
+ <ProviderSummary
53
+ key={provider.fullName || provider.authors[0]}
54
+ provider={provider}
55
+ calendarData={calendarData}
56
+ rank={index + 1}
57
+ />
58
+ ))}
59
+ </div>
60
  </div>
61
  </div>
62
+
63
+ <HeatmapGrid
64
+ sortedProviders={providers}
65
+ calendarData={calendarData}
66
+ isLoading={isLoading}
67
+ />
68
  </div>
 
 
 
 
 
 
 
 
69
  </div>
70
  );
71
  }
src/utils/authors.ts CHANGED
@@ -1,77 +1,82 @@
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
  );
@@ -87,23 +92,14 @@ export async function fetchOrganizationData(authors: string[]) {
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,
@@ -114,34 +110,40 @@ export async function fetchOrganizationData(authors: string[]) {
114
  }
115
 
116
  export async function fetchAllProvidersData(providers: ProviderInfo[]): Promise<ProviderInfo[]> {
117
- return Promise.all(providers.map(async (providerInfo) => {
118
- const orgData = await fetchOrganizationData(providerInfo.authors);
119
- return {
120
- ...providerInfo,
121
- ...orgData
122
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  }));
124
  }
125
 
126
  export async function fetchAuthorData(author: string): Promise<ModelData[]> {
127
  const entityTypes = ["models", "datasets", "spaces"] as const;
 
128
  try {
129
  const allData = await Promise.all(
130
- entityTypes.map(async (type) => {
131
- const response = await fetch(
132
- `https://huggingface.co/api/${type}?author=${author}&sort=createdAt&direction=-1`
133
- );
134
- if (!response.ok) {
135
- throw new Error(`HTTP error! status: ${response.status}`);
136
- }
137
- const data = await response.json();
138
- return data.map((item: any): ModelData => ({
139
- createdAt: item.createdAt,
140
- id: item.id,
141
- }));
142
- })
143
  );
144
-
145
  return allData.flat();
146
  } catch (error) {
147
  console.error(`Error fetching data for author ${author}:`, error);
@@ -152,7 +154,7 @@ export async function fetchAuthorData(author: string): Promise<ModelData[]> {
152
  export async function fetchAllAuthorsData(authors: string[]): Promise<ModelData[]> {
153
  try {
154
  const allData = await Promise.all(
155
- authors.map(async (author) => await fetchAuthorData(author))
156
  );
157
  return allData.flat();
158
  } catch (error) {
 
1
  import { ProviderInfo, ModelData } from "../types/heatmap";
2
 
3
+ interface AuthorData {
4
+ author: string;
5
+ fullName: string;
6
+ avatarUrl: string | null;
7
+ isVerified: boolean;
8
+ isEnterprise: boolean;
9
+ numModels: number;
10
+ numSpaces: number;
11
+ numDatasets: number;
12
+ numFollowers: number;
13
+ numUsers: number;
14
+ }
15
+
16
+ const createDefaultAuthorData = (author: string): AuthorData => ({
17
+ author,
18
+ fullName: author,
19
+ avatarUrl: null,
20
+ isVerified: false,
21
+ isEnterprise: false,
22
+ numModels: 0,
23
+ numSpaces: 0,
24
+ numDatasets: 0,
25
+ numFollowers: 0,
26
+ numUsers: 0,
27
+ });
28
+
29
+ const transformApiData = (author: string, data: any, isOrganization: boolean): AuthorData => ({
30
+ author,
31
+ fullName: data.fullname || author,
32
+ avatarUrl: data.avatarUrl || null,
33
+ isVerified: data.isVerified || false,
34
+ isEnterprise: isOrganization ? (data.isEnterprise || false) : false,
35
+ numModels: data.numModels || 0,
36
+ numSpaces: data.numSpaces || 0,
37
+ numDatasets: data.numDatasets || 0,
38
+ numFollowers: data.numFollowers || 0,
39
+ numUsers: isOrganization ? (data.numUsers || 0) : 0,
40
+ });
41
+
42
+ async function fetchSingleAuthorData(author: string): Promise<AuthorData> {
43
+ try {
44
+ // Try organizations API first
45
+ const orgResponse = await fetch(`https://huggingface.co/api/organizations/${author}/overview`);
46
+ if (orgResponse.ok) {
47
+ const data = await orgResponse.json();
48
+ return transformApiData(author, data, true);
49
+ }
50
+
51
+ // Fallback to users API
52
+ const userResponse = await fetch(`https://huggingface.co/api/users/${author}/overview`);
53
+ if (userResponse.ok) {
54
+ const data = await userResponse.json();
55
+ return transformApiData(author, data, false);
56
+ }
57
+
58
+ throw new Error('Neither organization nor user API returned valid data');
59
+ } catch (error) {
60
+ console.error(`Error fetching data for ${author}:`, error);
61
+ return createDefaultAuthorData(author);
62
+ }
63
+ }
64
+
65
  export async function fetchOrganizationData(authors: string[]) {
66
  try {
 
67
  const authorsData = await Promise.all(
68
+ authors.map(author => fetchSingleAuthorData(author))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  );
70
 
 
71
  const primaryAuthor = authorsData[0];
72
 
 
73
  const aggregatedStats = authorsData.reduce(
74
  (acc, authorData) => ({
75
+ numModels: acc.numModels + authorData.numModels,
76
+ numSpaces: acc.numSpaces + authorData.numSpaces,
77
+ numDatasets: acc.numDatasets + authorData.numDatasets,
78
+ numFollowers: acc.numFollowers + authorData.numFollowers,
79
+ numUsers: acc.numUsers + authorData.numUsers,
80
  }),
81
  { numModels: 0, numSpaces: 0, numDatasets: 0, numFollowers: 0, numUsers: 0 }
82
  );
 
92
  } catch (error) {
93
  console.error(`Error fetching organization data for authors:`, error);
94
  const primaryAuthor = authors[0];
95
+ const defaultAuthorData = createDefaultAuthorData(primaryAuthor);
96
+
97
  return {
98
  fullName: primaryAuthor,
99
  avatarUrl: null,
100
  isVerified: false,
101
  isEnterprise: false,
102
+ authorsData: [defaultAuthorData],
 
 
 
 
 
 
 
 
 
 
 
103
  numModels: 0,
104
  numSpaces: 0,
105
  numDatasets: 0,
 
110
  }
111
 
112
  export async function fetchAllProvidersData(providers: ProviderInfo[]): Promise<ProviderInfo[]> {
113
+ return Promise.all(
114
+ providers.map(async (providerInfo) => {
115
+ const orgData = await fetchOrganizationData(providerInfo.authors);
116
+ return {
117
+ ...providerInfo,
118
+ ...orgData
119
+ };
120
+ })
121
+ );
122
+ }
123
+
124
+ async function fetchAuthorEntityData(author: string, entityType: string): Promise<ModelData[]> {
125
+ const response = await fetch(
126
+ `https://huggingface.co/api/${entityType}?author=${author}&sort=createdAt&direction=-1`
127
+ );
128
+
129
+ if (!response.ok) {
130
+ throw new Error(`HTTP error! status: ${response.status}`);
131
+ }
132
+
133
+ const data = await response.json();
134
+ return data.map((item: any): ModelData => ({
135
+ createdAt: item.createdAt,
136
+ id: item.id,
137
  }));
138
  }
139
 
140
  export async function fetchAuthorData(author: string): Promise<ModelData[]> {
141
  const entityTypes = ["models", "datasets", "spaces"] as const;
142
+
143
  try {
144
  const allData = await Promise.all(
145
+ entityTypes.map(type => fetchAuthorEntityData(author, type))
 
 
 
 
 
 
 
 
 
 
 
 
146
  );
 
147
  return allData.flat();
148
  } catch (error) {
149
  console.error(`Error fetching data for author ${author}:`, error);
 
154
  export async function fetchAllAuthorsData(authors: string[]): Promise<ModelData[]> {
155
  try {
156
  const allData = await Promise.all(
157
+ authors.map(author => fetchAuthorData(author))
158
  );
159
  return allData.flat();
160
  } catch (error) {