File size: 5,467 Bytes
cd201c7 7a85021 85181c4 7a85021 22932a5 cd201c7 7a85021 cd201c7 6c9b36e 7a85021 7fc47b1 7a85021 fdb7c58 7a85021 7fc47b1 cd201c7 cca9c34 c608763 7fc47b1 c608763 cd201c7 ab1abdd c608763 22932a5 198dbcf 22932a5 929d75e 22932a5 cd201c7 22932a5 cd201c7 cca9c34 22932a5 cca9c34 929d75e cca9c34 ab1abdd 22932a5 cca9c34 22932a5 ab1abdd 03b5e31 c07926b 7a85021 7fc47b1 7a85021 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
import React, { useState, useEffect, useMemo } from "react";
import { ProviderInfo, ModelData, CalendarData } from "../types/heatmap";
import OrganizationButton from "../components/OrganizationButton";
import HeatmapGrid from "../components/HeatmapGrid";
import Navbar from "../components/Navbar";
import TagSelector from "../components/TagSelector";
import { getProviders } from "../utils/ranking";
import { ORGANIZATIONS, SCIENTIFIC_TAGS } from "../constants/organizations";
interface PageProps {
calendarData: CalendarData;
providers: ProviderInfo[];
}
function Page({
calendarData,
providers,
}: PageProps) {
const [isLoading, setIsLoading] = useState(true);
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const [showAllOrgs, setShowAllOrgs] = useState(false);
useEffect(() => {
if (calendarData && Object.keys(calendarData).length > 0) {
setIsLoading(false);
}
}, [calendarData]);
// Filter providers based on selected tags
const filteredProviders = useMemo(() => {
if (selectedTags.length === 0) {
return providers;
}
return providers.filter(provider => {
if (!provider.tags) return false;
return selectedTags.some(tag => provider.tags!.includes(tag));
});
}, [providers, selectedTags]);
const handleTagToggle = (tagId: string) => {
setSelectedTags(prev => {
if (prev.includes(tagId)) {
return prev.filter(t => t !== tagId);
} else {
return [...prev, tagId];
}
});
};
return (
<div className="w-full">
<Navbar />
<div className="w-full p-4 py-16">
<div className="text-center mb-16 max-w-4xl mx-auto">
<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">
<span className="inline-flex items-center gap-1 sm:gap-2">
Hugging Science Heatmap
<img
src="/hf-icon.svg"
alt="Hugging Face icon"
className="size-6 sm:size-8 md:size-10"
/>
</span>
</h1>
<p className="text-base sm:text-lg lg:text-xl text-muted-foreground max-w-2xl mx-auto leading-relaxed px-4">
Open models, datasets, and apps from orgs contributing to AI4Science in the last year.
</p>
</div>
{/* Tag Selector */}
<div className="mb-16">
<TagSelector
tags={SCIENTIFIC_TAGS}
selectedTags={selectedTags}
onTagToggle={handleTagToggle}
/>
</div>
<div className="mb-16 mx-auto max-w-full">
{/* Organization Buttons with Horizontal Scroll */}
<div className="relative px-4">
{/* Scrollable container with visible scrollbar */}
<div
className="overflow-x-auto overflow-y-hidden pb-4 scrollbar-thin scrollbar-thumb-muted-foreground/30 scrollbar-track-transparent hover:scrollbar-thumb-muted-foreground/50"
style={{
scrollbarWidth: 'thin',
scrollbarColor: 'rgba(115, 115, 115, 0.3) transparent'
}}
>
<div className="flex gap-6 py-2 w-max mx-auto">
{(showAllOrgs ? filteredProviders : filteredProviders.slice(0, 10)).map((provider, index) => (
<OrganizationButton
key={provider.fullName || provider.authors[0]}
provider={provider}
calendarData={calendarData}
rank={index + 1}
/>
))}
</div>
</div>
{/* Visual scroll indicators */}
<div className="absolute left-0 top-0 bottom-4 w-8 bg-gradient-to-r from-background via-background/80 to-transparent pointer-events-none" />
<div className="absolute right-0 top-0 bottom-4 w-8 bg-gradient-to-l from-background via-background/80 to-transparent pointer-events-none" />
</div>
{/* Show More/Less Button */}
{filteredProviders.length > 10 && (
<div className="flex justify-center mt-4">
<button
onClick={() => setShowAllOrgs(!showAllOrgs)}
className="px-6 py-2 text-sm font-medium text-foreground bg-muted hover:bg-muted/80 rounded-full transition-colors duration-200 border border-border"
>
{showAllOrgs ? `Show Less (Top 10)` : `Show All ${filteredProviders.length} Organizations`}
</button>
</div>
)}
</div>
<HeatmapGrid
sortedProviders={showAllOrgs ? filteredProviders : filteredProviders.slice(0, 10)}
calendarData={calendarData}
isLoading={isLoading}
/>
</div>
</div>
);
}
export async function getStaticProps() {
try {
const { calendarData, providers } = await getProviders(ORGANIZATIONS);
return {
props: {
calendarData,
providers,
},
revalidate: 3600, // regenerate every hour
};
} catch (error) {
console.error("Error fetching data:", error);
return {
props: {
calendarData: {},
providers: ORGANIZATIONS,
},
revalidate: 60, // retry after 1 minute if there was an error
};
}
}
export default Page;
|