Viani commited on
Commit
7b8fd86
·
1 Parent(s): 2f80fef

feat: dynamic leaderboard ranked by HF activity + papers stats

Browse files

Replace the hardcoded 16-org list with a ~40-org candidate pool that is
dynamically ranked by actual HuggingFace activity (models + datasets +
spaces created in the last year). Top 16 are shown with auto-assigned
colors from a palette (brand color overrides supported).

Also adds papers count to the organization stats badges, sourced from
the HF org overview API.

- organizations.ts: OrgCandidate[] with ~40 known AI orgs (AMD, Intel,
Qualcomm, EleutherAI, Unsloth, etc.)
- colors.ts: 20-color palette with assignColor() helper
- ranking.ts: getProviders fetches all candidates in parallel, ranks,
slices top 16
- OrganizationHeader: new papers stat badge

src/components/OrganizationHeader.tsx CHANGED
@@ -172,6 +172,31 @@ const OrganizationHeader: React.FC<OrganizationHeaderProps> = ({
172
  </div>
173
  )}
174
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  {provider.authorsData && provider.authorsData.length > 1 ? (
176
  <Tooltip>
177
  <TooltipTrigger asChild>
 
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.numPapers || 0).toLocaleString()}</span>
180
+ <span className="text-muted-foreground ml-1">papers</span>
181
+ </div>
182
+ </TooltipTrigger>
183
+ <TooltipContent side="bottom">
184
+ <div className="text-xs">
185
+ {provider.authorsData.map(author => (
186
+ <div key={`papers-${author.author}`}>
187
+ {author.author}: {author.numPapers.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.numPapers || 0).toLocaleString()}</span>
196
+ <span className="text-muted-foreground ml-1">papers</span>
197
+ </div>
198
+ )}
199
+
200
  {provider.authorsData && provider.authorsData.length > 1 ? (
201
  <Tooltip>
202
  <TooltipTrigger asChild>
src/constants/colors.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const PALETTE = [
2
+ "#ff7000", "#1877F2", "#10A37F", "#DB4437",
3
+ "#F45098", "#0088cc", "#FEB800", "#76B900",
4
+ "#A020F0", "#e93b3b", "#4C6EE6", "#FEC912",
5
+ "#cc785c", "#00B4D8", "#2D6A4F", "#E07A5F",
6
+ "#6A4C93", "#1982C4", "#8AC926", "#FF595E",
7
+ ];
8
+
9
+ export const assignColor = (
10
+ index: number,
11
+ brandColor?: string,
12
+ ): string => {
13
+ if (brandColor) return brandColor;
14
+ return PALETTE[index % PALETTE.length];
15
+ };
src/constants/organizations.ts CHANGED
@@ -1,20 +1,60 @@
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: "#F45098", 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
- { color: "#e93b3b", authors: ["ByteDance", "ByteDance-Seed", "bytedance-research"] },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  ];
 
1
+ import { OrgCandidate } from "../types/heatmap";
2
 
3
+ export const LEADERBOARD_SIZE = 16;
4
+
5
+ export const CANDIDATES: OrgCandidate[] = [
6
+ // Original 16
7
+ { authors: ["mistralai"], color: "#ff7000" },
8
+ { authors: ["meta-llama", "facebook"], color: "#1877F2" },
9
+ { authors: ["openai"], color: "#10A37F" },
10
+ { authors: ["Anthropic"], color: "#cc785c" },
11
+ { authors: ["google"], color: "#DB4437" },
12
+ { authors: ["allenai"], color: "#F45098" },
13
+ { authors: ["apple"], color: "#0088cc" },
14
+ { authors: ["microsoft"], color: "#FEB800" },
15
+ { authors: ["nvidia"], color: "#76B900" },
16
+ { authors: ["deepseek-ai"] },
17
+ { authors: ["Qwen"] },
18
+ { authors: ["CohereLabs", "CohereForAI"] },
19
+ { authors: ["ibm-granite"] },
20
+ { authors: ["stabilityai"], color: "#A020F0" },
21
+ {
22
+ authors: [
23
+ "huggingface", "OpenEvals", "HuggingFaceTB", "HuggingFaceH4",
24
+ "HuggingFaceM4", "HuggingFaceFW", "HuggingFaceFV", "open-r1",
25
+ "parler-tts", "nanotron", "lerobot", "distilbert",
26
+ ],
27
+ color: "#FEC912",
28
+ },
29
+ { authors: ["ByteDance", "ByteDance-Seed", "bytedance-research"], color: "#e93b3b" },
30
+
31
+ // Hardware / chip companies
32
+ { authors: ["amd"], color: "#ED1C24" },
33
+ { authors: ["Intel"], color: "#0071C5" },
34
+ { authors: ["qualcomm"] },
35
+
36
+ // Research labs & open-source orgs
37
+ { authors: ["EleutherAI"] },
38
+ { authors: ["bigscience"] },
39
+ { authors: ["NousResearch"] },
40
+ { authors: ["tiiuae"] },
41
+ { authors: ["ai21labs"] },
42
+ { authors: ["Salesforce"] },
43
+ { authors: ["unsloth"] },
44
+ { authors: ["black-forest-labs"] },
45
+ { authors: ["sentence-transformers"] },
46
+
47
+ // Chinese AI labs
48
+ { authors: ["alibaba-nlp"] },
49
+ { authors: ["THUDM"] },
50
+ { authors: ["01-ai"] },
51
+ { authors: ["internlm"] },
52
+ { authors: ["baichuan-inc"] },
53
+
54
+ // Other notable orgs
55
+ { authors: ["xai-org"] },
56
+ { authors: ["amazon"] },
57
+ { authors: ["databricks"] },
58
+ { authors: ["Nexusflow"] },
59
+ { authors: ["Writer"] },
60
  ];
src/pages/index.tsx CHANGED
@@ -5,7 +5,7 @@ import HeatmapGrid from "../components/HeatmapGrid";
5
  import Navbar from "../components/Navbar";
6
  import { getProviders } from "../utils/ranking";
7
  import { compactCalendarData, expandCalendarData } from "../utils/calendar";
8
- import { ORGANIZATIONS } from "../constants/organizations";
9
 
10
  interface PageProps {
11
  compactData: CompactCalendarData;
@@ -75,7 +75,7 @@ function Page({ compactData, providers }: PageProps) {
75
 
76
  export async function getStaticProps() {
77
  try {
78
- const { calendarData, providers } = await getProviders(ORGANIZATIONS);
79
 
80
  return {
81
  props: {
@@ -89,7 +89,7 @@ export async function getStaticProps() {
89
  return {
90
  props: {
91
  compactData: {},
92
- providers: ORGANIZATIONS,
93
  },
94
  revalidate: 60,
95
  };
 
5
  import Navbar from "../components/Navbar";
6
  import { getProviders } from "../utils/ranking";
7
  import { compactCalendarData, expandCalendarData } from "../utils/calendar";
8
+ import { CANDIDATES } from "../constants/organizations";
9
 
10
  interface PageProps {
11
  compactData: CompactCalendarData;
 
75
 
76
  export async function getStaticProps() {
77
  try {
78
+ const { calendarData, providers } = await getProviders(CANDIDATES);
79
 
80
  return {
81
  props: {
 
89
  return {
90
  props: {
91
  compactData: {},
92
+ providers: [],
93
  },
94
  revalidate: 60,
95
  };
src/types/heatmap.ts CHANGED
@@ -9,6 +9,7 @@ export interface ProviderInfo {
9
  numSpaces?: number;
10
  numDatasets?: number;
11
  numFollowers?: number;
 
12
  numUsers?: number;
13
  authorsData?: {
14
  author: string;
@@ -20,6 +21,7 @@ export interface ProviderInfo {
20
  numSpaces: number;
21
  numDatasets: number;
22
  numFollowers: number;
 
23
  numUsers: number;
24
  }[];
25
  }
@@ -52,3 +54,8 @@ export interface AuthorPageProvider {
52
  fullName?: string;
53
  avatarUrl?: string | null;
54
  }
 
 
 
 
 
 
9
  numSpaces?: number;
10
  numDatasets?: number;
11
  numFollowers?: number;
12
+ numPapers?: number;
13
  numUsers?: number;
14
  authorsData?: {
15
  author: string;
 
21
  numSpaces: number;
22
  numDatasets: number;
23
  numFollowers: number;
24
+ numPapers: number;
25
  numUsers: number;
26
  }[];
27
  }
 
54
  fullName?: string;
55
  avatarUrl?: string | null;
56
  }
57
+
58
+ export interface OrgCandidate {
59
+ authors: string[];
60
+ color?: string;
61
+ }
src/utils/authors.ts CHANGED
@@ -10,6 +10,7 @@ interface AuthorData {
10
  numSpaces: number;
11
  numDatasets: number;
12
  numFollowers: number;
 
13
  numUsers: number;
14
  }
15
 
@@ -23,6 +24,7 @@ const createDefaultAuthorData = (author: string): AuthorData => ({
23
  numSpaces: 0,
24
  numDatasets: 0,
25
  numFollowers: 0,
 
26
  numUsers: 0,
27
  });
28
 
@@ -36,6 +38,7 @@ const transformApiData = (author: string, data: any, isOrganization: boolean): A
36
  numSpaces: data.numSpaces || 0,
37
  numDatasets: data.numDatasets || 0,
38
  numFollowers: data.numFollowers || 0,
 
39
  numUsers: isOrganization ? (data.numUsers || 0) : 0,
40
  });
41
 
@@ -76,9 +79,10 @@ export async function fetchOrganizationData(authors: string[]) {
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
  );
83
 
84
  return {
@@ -104,6 +108,7 @@ export async function fetchOrganizationData(authors: string[]) {
104
  numSpaces: 0,
105
  numDatasets: 0,
106
  numFollowers: 0,
 
107
  numUsers: 0,
108
  };
109
  }
 
10
  numSpaces: number;
11
  numDatasets: number;
12
  numFollowers: number;
13
+ numPapers: number;
14
  numUsers: number;
15
  }
16
 
 
24
  numSpaces: 0,
25
  numDatasets: 0,
26
  numFollowers: 0,
27
+ numPapers: 0,
28
  numUsers: 0,
29
  });
30
 
 
38
  numSpaces: data.numSpaces || 0,
39
  numDatasets: data.numDatasets || 0,
40
  numFollowers: data.numFollowers || 0,
41
+ numPapers: data.numPapers || 0,
42
  numUsers: isOrganization ? (data.numUsers || 0) : 0,
43
  });
44
 
 
79
  numSpaces: acc.numSpaces + authorData.numSpaces,
80
  numDatasets: acc.numDatasets + authorData.numDatasets,
81
  numFollowers: acc.numFollowers + authorData.numFollowers,
82
+ numPapers: acc.numPapers + authorData.numPapers,
83
  numUsers: acc.numUsers + authorData.numUsers,
84
  }),
85
+ { numModels: 0, numSpaces: 0, numDatasets: 0, numFollowers: 0, numPapers: 0, numUsers: 0 }
86
  );
87
 
88
  return {
 
108
  numSpaces: 0,
109
  numDatasets: 0,
110
  numFollowers: 0,
111
+ numPapers: 0,
112
  numUsers: 0,
113
  };
114
  }
src/utils/ranking.ts CHANGED
@@ -1,6 +1,13 @@
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;
@@ -10,23 +17,27 @@ export interface RankingBadge {
10
  export const getRankingBadge = (rank: number): RankingBadge => {
11
  if (rank === 1) {
12
  return {
13
- className: "absolute -top-4 -left-4 w-12 h-12 bg-gradient-to-br from-yellow-400 via-yellow-500 to-yellow-600 text-white rounded-full flex items-center justify-center text-lg font-bold shadow-2xl border-4 border-yellow-300",
14
- icon: "👑"
 
15
  };
16
  } else if (rank === 2) {
17
  return {
18
- className: "absolute -top-4 -left-4 w-10 h-10 bg-gradient-to-br from-gray-300 via-gray-400 to-gray-500 text-white rounded-full flex items-center justify-center text-base font-bold shadow-xl border-3 border-gray-200",
19
- icon: "🥈"
 
20
  };
21
  } else if (rank === 3) {
22
  return {
23
- className: "absolute -top-4 -left-4 w-10 h-10 bg-gradient-to-br from-amber-600 via-amber-700 to-amber-800 text-white rounded-full flex items-center justify-center text-base font-bold shadow-xl border-3 border-amber-400",
24
- icon: "🥉"
 
25
  };
26
  } else {
27
  return {
28
- className: "absolute -top-3 -left-3 w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold shadow-lg",
29
- icon: null
 
30
  };
31
  }
32
  };
@@ -37,37 +48,60 @@ export const extractUniqueAuthors = (providers: ProviderInfo[]): string[] => {
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
- };
 
1
+ import {
2
+ ProviderInfo,
3
+ CalendarData,
4
+ ModelData,
5
+ OrgCandidate,
6
+ } from "../types/heatmap";
7
  import { generateCalendarData } from "./calendar";
8
  import { fetchAllProvidersData, fetchAllAuthorsData } from "./authors";
9
+ import { assignColor } from "../constants/colors";
10
+ import { LEADERBOARD_SIZE } from "../constants/organizations";
11
 
12
  export interface RankingBadge {
13
  className: string;
 
17
  export const getRankingBadge = (rank: number): RankingBadge => {
18
  if (rank === 1) {
19
  return {
20
+ className:
21
+ "absolute -top-4 -left-4 w-12 h-12 bg-gradient-to-br from-yellow-400 via-yellow-500 to-yellow-600 text-white rounded-full flex items-center justify-center text-lg font-bold shadow-2xl border-4 border-yellow-300",
22
+ icon: "👑",
23
  };
24
  } else if (rank === 2) {
25
  return {
26
+ className:
27
+ "absolute -top-4 -left-4 w-10 h-10 bg-gradient-to-br from-gray-300 via-gray-400 to-gray-500 text-white rounded-full flex items-center justify-center text-base font-bold shadow-xl border-3 border-gray-200",
28
+ icon: "🥈",
29
  };
30
  } else if (rank === 3) {
31
  return {
32
+ className:
33
+ "absolute -top-4 -left-4 w-10 h-10 bg-gradient-to-br from-amber-600 via-amber-700 to-amber-800 text-white rounded-full flex items-center justify-center text-base font-bold shadow-xl border-3 border-amber-400",
34
+ icon: "🥉",
35
  };
36
  } else {
37
  return {
38
+ className:
39
+ "absolute -top-3 -left-3 w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold shadow-lg",
40
+ icon: null,
41
  };
42
  }
43
  };
 
48
  };
49
 
50
  export const sortProvidersByActivity = (
51
+ providers: ProviderInfo[],
52
  calendarData: CalendarData
53
  ): ProviderInfo[] => {
54
+ return [...providers].sort((a, b) => {
55
+ const aCount = (calendarData[a.authors[0]] || []).reduce(
56
+ (sum, day) => sum + day.count,
 
 
 
57
  0
58
  );
59
+ const bCount = (calendarData[b.authors[0]] || []).reduce(
60
+ (sum, day) => sum + day.count,
61
  0
62
  );
63
+ return bCount - aCount;
 
64
  });
65
  };
66
 
67
+ const candidatesToProviders = (candidates: OrgCandidate[]): ProviderInfo[] =>
68
+ candidates.map((c) => ({
69
+ color: c.color || "",
70
+ authors: c.authors,
71
+ }));
72
 
73
+ export const getProviders = async (candidates: OrgCandidate[]) => {
74
+ const allProviders = candidatesToProviders(candidates);
75
+
76
+ const uniqueAuthors = extractUniqueAuthors(allProviders);
77
+ const [authorModelsData, providersWithMetadata] = await Promise.all([
78
+ fetchAllAuthorsData(uniqueAuthors),
79
+ fetchAllProvidersData(allProviders),
80
+ ]);
81
+
82
+ const calendarData = generateCalendarData(
83
+ authorModelsData,
84
+ providersWithMetadata
85
+ );
86
+
87
+ const sorted = sortProvidersByActivity(providersWithMetadata, calendarData);
88
+ const topN = sorted.slice(0, LEADERBOARD_SIZE);
89
+
90
+ const coloredProviders = topN.map((provider, index) => ({
91
+ ...provider,
92
+ color: provider.color || assignColor(index),
93
+ }));
94
+
95
+ const topKeys = new Set(coloredProviders.map((p) => p.authors[0]));
96
+ const filteredCalendar: CalendarData = {};
97
+ for (const key of Object.keys(calendarData)) {
98
+ if (topKeys.has(key)) {
99
+ filteredCalendar[key] = calendarData[key];
100
+ }
101
+ }
102
 
103
  return {
104
+ calendarData: filteredCalendar,
105
+ providers: coloredProviders,
106
  };
107
+ };