import React, { useEffect, useMemo, useRef, useState } from 'react'
import { api, hubTop } from './api.js'
import { Sparkline, Stat, AreaChart, fmt, pct } from './components.jsx'
import { ModelPage, AuthorPage, Loading, ErrBox } from './pages.jsx'
function parseHash() {
const h = location.hash
if (h.startsWith('#/m/')) return { v: 'model', id: decodeURIComponent(h.slice(4)) }
if (h.startsWith('#/a/')) return { v: 'author', author: decodeURIComponent(h.slice(4)) }
return { v: 'home' }
}
const BOARDS = [
{ key: 'gainers_7d', label: 'Gainers 7d', sub: 'biggest absolute 7-day gains' },
{ key: 'growth_7d', label: 'Fastest growing', sub: 'relative growth vs week before' },
{ key: 'breakouts', label: 'Breakouts', sub: 'new entries with traction' },
{ key: 'likes_7d', label: 'Most liked 7d', sub: 'likes gained this week' },
{ key: 'families', label: 'Top families', sub: 'ecosystems by family downloads' },
{ key: 'authors_dl30', label: 'Authors 30d', sub: 'most downloaded authors' },
{ key: 'authors_7d', label: 'Authors 7d', sub: 'this week\'s top authors' },
]
function BoardTable({ rows, go, isAuthors }) {
if (!rows || !rows.length) return
Empty board this week.
const maxdl = Math.max(1, ...rows.map((r) => r.dl30 || 0))
return (
| # |
{isAuthors ? 'Author' : 'Model'} |
30d |
7d |
{!isAuthors && Growth | }
♥ |
{rows.map((r, i) => {
const name = isAuthors ? r.author : r.id
const href = isAuthors ? '#/a/' + r.author : '#/m/' + r.id
return (
go(href)} className="cursor-pointer border-b border-[var(--line2)] transition-colors hover:bg-[var(--accent-soft)]/40">
| {i + 1} |
{name}{!isAuthors && r.pipeline_tag && {r.pipeline_tag}}
{isAuthors && {fmt(r.models)} models}
|
{fmt(r.dl30)} |
{fmt(r.dl_7d)} |
{!isAuthors && = 0 ? 'text-[var(--up)]' : 'text-[var(--danger)]')}>{r.growth_7d != null ? pct(r.growth_7d) : '—'} | }
♥ {fmt(r.likes)} |
)
})}
)
}
function Home({ go }) {
const [hub, setHub] = useState(null)
const [hubErr, setHubErr] = useState(null)
const [boards, setBoards] = useState(null)
const [board, setBoard] = useState('gainers_7d')
const [top, setTop] = useState(null)
const [trend, setTrend] = useState(null)
useEffect(() => {
api.hub().then(setHub).catch(setHubErr)
api.leaderboards().then(setBoards).catch(() => {})
hubTop('downloads', 12).then(setTop).catch(() => {})
hubTop('trendingScore', 10).then(setTrend).catch(() => {})
}, [])
const pulse = useMemo(() => {
if (!hub || !hub.day) return null
const byDay = new Map()
const low = new Set(hub.low_days || [])
hub.day.forEach((day, i) => { if (low.has(day)) return; byDay.set(day, (byDay.get(day) || 0) + (hub.dl[i] || 0)) })
const days = Array.from(byDay.keys()).sort()
return { days, vals: days.map((d) => byDay.get(d)) }
}, [hub])
const yestTotal = pulse ? pulse.vals[pulse.vals.length - 1] : null
const prevTotal = pulse ? pulse.vals[pulse.vals.length - 8] : null
const week = pulse ? pulse.vals.slice(-7).reduce((a, b) => a + b, 0) : null
return (
The pulse of the whole Hub, day by day.
Download history for every model on Hugging Face — search any repo, follow its curve,
climb through the leaderboards. ~1M+ models, updated daily.
{hubErr &&
location.reload()} />}
{!pulse && !hubErr && (
)}
{pulse && (
Hub downloads per day
{fmt(yestTotal)} yesterday · {fmt(week)} last 7d · vs {fmt(prevTotal)} week before
Aggregate across the 8 largest task families · gaps in the source data are smoothed out
)}
{trend && trend.length > 0 && (
Trending right now · live from the Hub API
{trend.map((m) => (
{m.id}
))}
)}
Leaderboards · week of {boards ? boards.updated : '…'}
{BOARDS.map((b) => (
))}
{(BOARDS.find((b) => b.key === board) || {}).sub}
{boards ? : }
{top && (
Most downloaded right now · live from the Hub API
{top.map((m, i) => (
go('#/m/' + m.id)} className="cursor-pointer border-b border-[var(--line2)] transition-colors hover:bg-[var(--accent-soft)]/40">
| {i + 1} |
{m.id}{m.pipeline_tag || ''} |
{fmt(m.downloads)} |
{fmt(m.downloadsAllTime)} all time |
♥ {fmt(m.likes)} |
))}
)}
)
}
export default function App() {
const [route, setRoute] = useState(parseHash())
const [theme, setTheme] = useState(() => (localStorage.getItem('hf-theme') || 'dark'))
const [query, setQuery] = useState('')
const [results, setResults] = useState(null)
const [searching, setSearching] = useState(false)
const searchRef = useRef(null)
const boxRef = useRef(null)
useEffect(() => {
document.documentElement.classList.toggle('light', theme === 'light')
localStorage.setItem('hf-theme', theme)
}, [theme])
useEffect(() => {
const f = () => { setRoute(parseHash()); setResults(null); setQuery('') }
window.addEventListener('hashchange', f)
return () => window.removeEventListener('hashchange', f)
}, [])
useEffect(() => {
const onKey = (e) => {
if (e.key === '/' && document.activeElement !== searchRef.current) { e.preventDefault(); searchRef.current?.focus() }
if (e.key === 'Escape') { setResults(null); searchRef.current?.blur() }
}
const onClick = (e) => { if (boxRef.current && !boxRef.current.contains(e.target)) setResults(null) }
window.addEventListener('keydown', onKey)
window.addEventListener('mousedown', onClick)
return () => { window.removeEventListener('keydown', onKey); window.removeEventListener('mousedown', onClick) }
}, [])
useEffect(() => {
const q = query.trim()
if (q.length < 2) { setResults(null); setSearching(false); return }
setSearching(true)
const t = setTimeout(() => {
api.search(q).then((r) => { setResults(r.slice(0, 8)); setSearching(false) }).catch(() => { setResults([]); setSearching(false) })
}, 250)
return () => clearTimeout(t)
}, [query])
const go = (href) => { location.hash = href }
const open = (href) => { setResults(null); setQuery(''); go(href) }
return (
hub·flow
{route.v === 'home' ? 'every model on the hub' : route.v === 'model' ? 'model' : 'author'}
setQuery(e.target.value)}
placeholder={'Search models /'}
className="w-44 rounded-lg border border-[var(--line)] bg-[var(--panel)] px-3 py-1.5 font-mono text-xs text-[var(--ink)] placeholder-[var(--faint)] outline-none transition-colors focus:border-[var(--accent)] sm:w-64" />
{results && results.length > 0 && (
{results.map((r) => (
))}
)}
{results && !results.length && !searching && (
Nothing found for “{query}”
)}
{route.v === 'home' && }
{route.v === 'model' && }
{route.v === 'author' && }
)
}