Download source/src/App.jsx from mindchain/hub-flow: direct link, hf CLI and curl.
- Browser
- Download file 16 kB
-
https://huggingface.co/spaces/mindchain/hub-flow/resolve/main/source/src/App.jsx
- Command line
-
hf download hf://spaces/mindchain/hub-flow/source/src/App.jsx
-
curl -L -o App.jsx https://huggingface.co/spaces/mindchain/hub-flow/resolve/main/source/src/App.jsx
16 kB
| 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 <p className="py-6 text-sm text-[var(--dim)]">Empty board this week.</p> | |
| const maxdl = Math.max(1, ...rows.map((r) => r.dl30 || 0)) | |
| return ( | |
| <table className="w-full"> | |
| <thead> | |
| <tr className="text-[10.5px] uppercase tracking-[0.06em] text-[var(--faint)]"> | |
| <th className="w-8 py-2 pl-1 text-left font-semibold">#</th> | |
| <th className="py-2 text-left font-semibold">{isAuthors ? 'Author' : 'Model'}</th> | |
| <th className="hidden px-3 py-2 text-right font-semibold md:table-cell">30d</th> | |
| <th className="px-3 py-2 text-right font-semibold">7d</th> | |
| {!isAuthors && <th className="hidden px-3 py-2 text-right font-semibold sm:table-cell">Growth</th>} | |
| <th className="px-3 py-2 pr-1 text-right font-semibold">♥</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {rows.map((r, i) => { | |
| const name = isAuthors ? r.author : r.id | |
| const href = isAuthors ? '#/a/' + r.author : '#/m/' + r.id | |
| return ( | |
| <tr key={name} onClick={() => go(href)} className="cursor-pointer border-b border-[var(--line2)] transition-colors hover:bg-[var(--accent-soft)]/40"> | |
| <td className="tnum py-2.5 pl-1 text-[var(--faint)]">{i + 1}</td> | |
| <td className="break-all py-2.5 pr-3 font-mono text-[12.5px] font-semibold"> | |
| {name}{!isAuthors && r.pipeline_tag && <span className="ml-2 hidden font-sans text-[11px] font-normal text-[var(--faint)] lg:inline">{r.pipeline_tag}</span>} | |
| {isAuthors && <span className="ml-2 font-sans text-[11px] font-normal text-[var(--faint)]">{fmt(r.models)} models</span>} | |
| </td> | |
| <td className="tnum hidden px-3 py-2.5 text-right md:table-cell">{fmt(r.dl30)}</td> | |
| <td className="tnum px-3 py-2.5 text-right font-semibold">{fmt(r.dl_7d)}</td> | |
| {!isAuthors && <td className={'tnum hidden px-3 py-2.5 text-right font-mono text-[12px] sm:table-cell ' + ((r.growth_7d || 0) >= 0 ? 'text-[var(--up)]' : 'text-[var(--danger)]')}>{r.growth_7d != null ? pct(r.growth_7d) : '—'}</td>} | |
| <td className="tnum px-3 py-2.5 pr-1 text-right text-[var(--dim)]">♥ {fmt(r.likes)}</td> | |
| </tr> | |
| ) | |
| })} | |
| </tbody> | |
| </table> | |
| ) | |
| } | |
| 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 ( | |
| <div className="rise"> | |
| <h1 className="max-w-2xl pt-10 text-3xl font-extrabold leading-[1.08] tracking-tight sm:pt-14 sm:text-[42px]"> | |
| The pulse of the <span className="text-[var(--accent)]">whole Hub</span>, day by day. | |
| </h1> | |
| <p className="mt-3 max-w-xl text-sm leading-relaxed text-[var(--dim)] sm:text-[15px]"> | |
| Download history for every model on Hugging Face — search any repo, follow its curve, | |
| climb through the leaderboards. ~1M+ models, updated daily. | |
| </p> | |
| {hubErr && <ErrBox e={hubErr} retry={() => location.reload()} />} | |
| {!pulse && !hubErr && ( | |
| <div className="mt-10 rounded-2xl border border-[var(--line)] bg-[var(--panel)] p-6"> | |
| <div className="h-40 animate-pulse rounded-lg bg-[var(--line2)]" /> | |
| </div> | |
| )} | |
| {pulse && ( | |
| <section className="mt-8 rounded-2xl border border-[var(--line)] bg-[var(--panel)] p-4 sm:p-6"> | |
| <div className="flex flex-wrap items-baseline justify-between gap-2"> | |
| <h2 className="text-[13px] font-bold uppercase tracking-[0.08em]">Hub downloads per day</h2> | |
| <span className="tnum font-mono text-[11.5px] text-[var(--faint)]">{fmt(yestTotal)} yesterday · {fmt(week)} last 7d · vs {fmt(prevTotal)} week before</span> | |
| </div> | |
| <div className="mt-2"><AreaChart days={pulse.days} vals={pulse.vals} /></div> | |
| <p className="mt-2 text-[11px] text-[var(--faint)]">Aggregate across the 8 largest task families · gaps in the source data are smoothed out</p> | |
| </section> | |
| )} | |
| {trend && trend.length > 0 && ( | |
| <section className="mt-6"> | |
| <h2 className="text-[12px] font-semibold uppercase tracking-[0.06em] text-[var(--dim)]">Trending right now <span className="font-normal normal-case text-[var(--faint)]">· live from the Hub API</span></h2> | |
| <div className="mt-2 flex flex-wrap gap-2"> | |
| {trend.map((m) => ( | |
| <a key={m.id} href={'#/m/' + m.id} className="rounded-full border border-[var(--line)] px-3 py-1.5 font-mono text-[11.5px] transition-colors hover:border-[var(--accent)]"> | |
| {m.id} | |
| </a> | |
| ))} | |
| </div> | |
| </section> | |
| )} | |
| <section className="mt-10"> | |
| <div className="flex flex-wrap items-baseline justify-between gap-3"> | |
| <h2 className="text-[13px] font-bold uppercase tracking-[0.08em]">Leaderboards <span className="font-medium normal-case tracking-normal text-[var(--faint)]">· week of {boards ? boards.updated : '…'}</span></h2> | |
| </div> | |
| <div className="mt-3 flex flex-wrap gap-2"> | |
| {BOARDS.map((b) => ( | |
| <button key={b.key} onClick={() => setBoard(b.key)} | |
| className={'rounded-full border px-3.5 py-1.5 text-xs font-semibold transition-colors ' + (board === b.key ? 'border-[var(--accent)] bg-[var(--accent-soft)] text-[var(--ink)]' : 'border-[var(--line)] text-[var(--dim)] hover:border-[var(--accent)] hover:text-[var(--ink)]')}> | |
| {b.label} | |
| </button> | |
| ))} | |
| </div> | |
| <p className="mt-2 text-[11.5px] text-[var(--faint)]">{(BOARDS.find((b) => b.key === board) || {}).sub}</p> | |
| <div className="mt-2"> | |
| {boards ? <BoardTable rows={boards[board]} go={go} isAuthors={board.startsWith('authors')} /> : <Loading label="Loading leaderboards…" />} | |
| </div> | |
| </section> | |
| {top && ( | |
| <section className="mt-10"> | |
| <h2 className="border-b border-[var(--ink)] pb-2 text-[13px] font-bold uppercase tracking-[0.08em]">Most downloaded right now <span className="font-medium normal-case tracking-normal text-[var(--faint)]">· live from the Hub API</span></h2> | |
| <table className="mt-1 w-full"> | |
| <tbody> | |
| {top.map((m, i) => ( | |
| <tr key={m.id} onClick={() => go('#/m/' + m.id)} className="cursor-pointer border-b border-[var(--line2)] transition-colors hover:bg-[var(--accent-soft)]/40"> | |
| <td className="tnum w-8 py-2.5 pl-1 text-[var(--faint)]">{i + 1}</td> | |
| <td className="break-all py-2.5 pr-3 font-mono text-[12.5px] font-semibold">{m.id}<span className="ml-2 hidden font-sans text-[11px] font-normal text-[var(--faint)] lg:inline">{m.pipeline_tag || ''}</span></td> | |
| <td className="tnum px-3 py-2.5 text-right font-semibold">{fmt(m.downloads)}</td> | |
| <td className="tnum hidden px-3 py-2.5 text-right text-[var(--dim)] sm:table-cell">{fmt(m.downloadsAllTime)} all time</td> | |
| <td className="tnum hidden px-3 py-2.5 pr-1 text-right text-[var(--dim)] md:table-cell">♥ {fmt(m.likes)}</td> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| </section> | |
| )} | |
| </div> | |
| ) | |
| } | |
| 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 ( | |
| <div className="min-h-screen"> | |
| <header className="sticky top-0 z-40 border-b border-[var(--line)] bg-[var(--bg)]/95"> | |
| <div className="mx-auto flex max-w-6xl flex-wrap items-center gap-x-4 gap-y-2 px-4 py-3 sm:px-6"> | |
| <a href="#/" className="flex items-baseline gap-1 text-xl font-extrabold tracking-tight"> | |
| hub<span className="text-[var(--accent)]">·</span><span className="text-[var(--dim)]">flow</span> | |
| </a> | |
| <span className="hidden rounded-full border border-[var(--line)] px-2.5 py-0.5 font-mono text-[11px] text-[var(--dim)] sm:inline"> | |
| {route.v === 'home' ? 'every model on the hub' : route.v === 'model' ? 'model' : 'author'} | |
| </span> | |
| <div className="relative ml-auto" ref={boxRef}> | |
| <input ref={searchRef} value={query} onChange={(e) => 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 && ( | |
| <div className="absolute right-0 top-10 z-50 w-[320px] overflow-hidden rounded-xl border border-[var(--line)] bg-[var(--panel)] shadow-2xl"> | |
| {results.map((r) => ( | |
| <button key={r.id} onClick={() => open('#/m/' + r.id)} className="flex w-full items-baseline justify-between gap-3 px-3.5 py-2 text-left transition-colors hover:bg-[var(--accent-soft)]"> | |
| <span className="truncate font-mono text-[12px] font-semibold text-[var(--ink)]">{r.id}</span> | |
| <span className="tnum shrink-0 font-mono text-[11px] text-[var(--faint)]">{fmt(r.dl30)}/30d</span> | |
| </button> | |
| ))} | |
| </div> | |
| )} | |
| {results && !results.length && !searching && ( | |
| <div className="absolute right-0 top-10 z-50 w-[320px] rounded-xl border border-[var(--line)] bg-[var(--panel)] px-3.5 py-3 text-xs text-[var(--dim)] shadow-2xl">Nothing found for “{query}”</div> | |
| )} | |
| </div> | |
| <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} aria-label="toggle theme" | |
| className="rounded-lg border border-[var(--line)] px-2.5 py-1.5 text-xs text-[var(--dim)] transition-colors hover:text-[var(--ink)]"> | |
| {theme === 'dark' ? '☾' : '☀'} | |
| </button> | |
| </div> | |
| </header> | |
| <div className="grid-tex pointer-events-none absolute inset-x-0 top-0 h-56 opacity-40" aria-hidden="true" /> | |
| <main className="relative mx-auto max-w-6xl px-4 pb-16 sm:px-6"> | |
| {route.v === 'home' && <Home go={open} />} | |
| {route.v === 'model' && <ModelPage key={route.id} id={route.id} go={open} />} | |
| {route.v === 'author' && <AuthorPage key={route.author} author={route.author} go={open} />} | |
| <footer className="mt-16 border-t border-[var(--line)] pt-6 text-xs leading-relaxed text-[var(--faint)]"> | |
| <p> | |
| <b className="text-[var(--dim)]">Data & history API:</b>{' '} | |
| <a className="underline decoration-[var(--line)] underline-offset-2 hover:decoration-[var(--dim)]" href="https://modelpulse.ifsp.dev" target="_blank" rel="noopener">model-pulse</a>{' '} | |
| over the open dataset{' '} | |
| <a className="underline decoration-[var(--line)] underline-offset-2 hover:decoration-[var(--dim)]" href="https://huggingface.co/datasets/modelpulse/model-pulse-data" target="_blank" rel="noopener">modelpulse/model-pulse-data</a>{' '} | |
| (built on <a className="underline decoration-[var(--line)] underline-offset-2 hover:decoration-[var(--dim)]" href="https://huggingface.co/datasets/cfahlgren1/hub-stats" target="_blank" rel="noopener">cfahlgren1/hub-stats</a>). | |
| “Right now” lists come live from the public Hugging Face Hub API. Big thanks to the model-pulse project for open-sourcing both data and API. | |
| </p> | |
| <p className="mt-1.5"> | |
| <b className="text-[var(--dim)]">hub-flow</b> is an independent project, not affiliated with Hugging Face or model-pulse.{' '} | |
| Sibling space: <a className="underline decoration-[var(--line)] underline-offset-2 hover:decoration-[var(--dim)]" href="https://mindchain-model-flow.static.hf.space/" target="_blank" rel="noopener">mindchain/model-flow</a>.{' '} | |
| Press <kbd className="rounded border border-[var(--line)] px-1 font-mono">/</kbd> to search any of the ~1M models. | |
| </p> | |
| </footer> | |
| </main> | |
| </div> | |
| ) | |
| } | |