| 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[]>([]); |
|
|
| useEffect(() => { |
| if (calendarData && Object.keys(calendarData).length > 0) { |
| setIsLoading(false); |
| } |
| }, [calendarData]); |
|
|
| |
| 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"> |
| <div className="overflow-x-auto scrollbar-hide"> |
| <div className="flex gap-6 px-4 py-2 min-w-max justify-center"> |
| {filteredProviders.map((provider, index) => ( |
| <OrganizationButton |
| key={provider.fullName || provider.authors[0]} |
| provider={provider} |
| calendarData={calendarData} |
| rank={index + 1} |
| /> |
| ))} |
| </div> |
| </div> |
| </div> |
| |
| <HeatmapGrid |
| sortedProviders={filteredProviders} |
| calendarData={calendarData} |
| isLoading={isLoading} |
| /> |
| </div> |
| </div> |
| ); |
| } |
|
|
|
|
| export async function getStaticProps() { |
| try { |
| const { calendarData, providers } = await getProviders(ORGANIZATIONS); |
|
|
| return { |
| props: { |
| calendarData, |
| providers, |
| }, |
| revalidate: 3600, |
| }; |
| } catch (error) { |
| console.error("Error fetching data:", error); |
| return { |
| props: { |
| calendarData: {}, |
| providers: ORGANIZATIONS, |
| }, |
| revalidate: 60, |
| }; |
| } |
| } |
|
|
| export default Page; |
|
|