hub-flow / source /src /pages.jsx
mindchain's picture
Upload source/src/pages.jsx with huggingface_hub
38d87ed verified
Raw History Blame Contribute Delete
10.5 kB
import React, { useEffect, useMemo, useState } from 'react'
import { api, hubTop } from './api.js'
import { BigChart, Stat, fmt, pct } from './components.jsx'
export function Loading({ label }) {
return (
<div className="state py-16 text-center text-sm text-[var(--dim)]">
<span className="mb-3 inline-block h-5 w-5 animate-spin rounded-full border-2 border-[var(--line)] border-t-[var(--dim)]" style={{ display: 'inline-block' }} />
<br />{label || 'Loading…'}
</div>
)
}
export function ErrBox({ e, retry }) {
return (
<div className="mt-10 rounded-xl border border-[var(--danger)]/40 bg-[var(--danger)]/5 p-8 text-center">
<p className="text-sm text-[var(--ink)]">Could not load data{e ? ' (' + e.message + ')' : ''}. The model-pulse API may be rate-limiting or down.</p>
{retry && <button onClick={retry} className="mt-3 rounded-lg border border-[var(--line)] px-4 py-2 text-sm font-semibold hover:border-[var(--accent)]">Retry</button>}
</div>
)
}
const REL_ORDER = ['quantized', 'finetune', 'adapter', 'merge']
const REL_LABEL = { quantized: 'Quantizations', finetune: 'Fine-tunes', adapter: 'Adapters', merge: 'Merges' }
export function ModelPage({ id, go }) {
const [data, setData] = useState(null)
const [err, setErr] = useState(null)
const load = () => { setErr(null); setData(null); api.model(id).then(setData).catch(setErr) }
useEffect(load, [id])
if (err) return <ErrBox e={err} retry={load} />
if (!data) return <Loading label={'Loading ' + id + '…'} />
const m = data.model || {}
const rel = data.series && data.series.day && data.series.day.length > 1
const groups = {}
;(data.children || []).forEach((c) => { (groups[c.relation] = groups[c.relation] || []).push(c) })
return (
<div className="rise">
<div className="flex flex-wrap items-center gap-2 pt-8">
<h1 className="break-all font-mono text-xl font-bold tracking-tight sm:text-2xl">{id}</h1>
{m.pipeline_tag && <span className="rounded-full border border-[var(--line)] px-2.5 py-0.5 text-[11px] text-[var(--dim)]">{m.pipeline_tag}</span>}
{m.is_gguf && <span className="rounded-full bg-[var(--accent)] px-2.5 py-0.5 text-[11px] font-bold text-black">GGUF</span>}
{m.license && <span className="hidden rounded-full border border-[var(--line)] px-2.5 py-0.5 text-[11px] text-[var(--faint)] sm:inline">{String(m.license).slice(0, 40)}</span>}
</div>
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-1 font-mono text-[11.5px] text-[var(--faint)]">
{m.params > 0 && <span>{(m.params / 1e9).toFixed(1)}B params</span>}
{m.created_at && <span>created {String(m.created_at).slice(0, 10)}</span>}
{m.first_seen && <span>tracked since {m.first_seen}</span>}
{m.rank_dl30 != null && <span className="text-[var(--dim)]">rank dl30 <b className="text-[var(--ink)]">#{fmt(m.rank_dl30)}</b></span>}
{m.rank_7d != null && <span className="text-[var(--dim)]">rank 7d <b className="text-[var(--ink)]">#{fmt(m.rank_7d)}</b></span>}
<a className="underline decoration-[var(--line)] underline-offset-2 hover:decoration-[var(--dim)]" href={'https://huggingface.co/' + id} target="_blank" rel="noopener">open on the Hub ↗</a>
</div>
<div className="mt-6 grid grid-cols-2 divide-[var(--line)] overflow-hidden rounded-2xl border border-[var(--line)] bg-[var(--panel)] sm:grid-cols-3 lg:grid-cols-6 lg:divide-x">
<Stat k="Downloads 30d" v={m.dl30} sub={m.dl_7d != null ? fmt(m.dl_7d) + ' in 7d' : ''} />
<Stat k="All time" v={m.dl_all} />
<Stat k="7d vs prev" v={m.dl_7d} d={m.growth_7d != null ? pct(m.growth_7d) + ' vs week before' : ''} dCls={m.growth_7d >= 0 ? 'text-[var(--up)]' : 'text-[var(--danger)]'} />
<Stat k="Likes" v={m.likes} sub={m.likes_7d ? '+' + fmt(m.likes_7d) + ' in 7d' : ''} />
{m.fam_members != null && <Stat k="Family" v={m.fam_members} sub={fmt(m.fam_dl30) + ' dl30'} />}
{m.trending != null && <Stat k="Trending score" v={m.trending} />}
</div>
<section className="mt-8 rounded-2xl border border-[var(--line)] bg-[var(--panel)] p-4 sm:p-6">
<div className="flex gap-5 text-[11.5px] text-[var(--dim)]">
<span><i className="mr-1.5 inline-block h-2 w-2 rounded-[2px] bg-[var(--bar)]" />downloads / day</span>
<span><i className="mr-1.5 inline-block h-2 w-2 rounded-[2px] bg-[var(--accent)]" />cumulative</span>
</div>
{rel ? <BigChart series={data.series} /> : <p className="py-8 text-center text-sm text-[var(--dim)]">No history yet — too new.</p>}
</section>
{(data.children || []).length > 0 && (
<section className="mt-8">
<h2 className="border-b border-[var(--ink)] pb-2 text-[13px] font-bold uppercase tracking-[0.08em]">
Family <span className="font-medium normal-case tracking-normal text-[var(--faint)]">· {(data.children || []).length} derivatives (quantizations, fine-tunes, …)</span>
</h2>
{REL_ORDER.filter((r) => groups[r]).concat(Object.keys(groups).filter((r) => !REL_ORDER.includes(r))).map((rel2) => {
const list = groups[rel2].slice().sort((a, b) => b.dl30 - a.dl30).slice(0, 8)
const maxdl = Math.max(1, ...groups[rel2].map((c) => c.dl30))
return (
<div key={rel2} className="mt-5">
<h3 className="text-[12px] font-semibold uppercase tracking-[0.06em] text-[var(--dim)]">{REL_LABEL[rel2] || rel2} <span className="text-[var(--faint)]">· {groups[rel2].length}</span></h3>
<table className="mt-1 w-full">
<tbody>
{list.map((c) => (
<tr key={c.id} onClick={() => go('#/m/' + c.id)} className="cursor-pointer border-b border-[var(--line2)] transition-colors hover:bg-[var(--accent-soft)]/40">
<td className="break-all py-2.5 pl-1 pr-3 font-mono text-[12.5px]">{c.id}</td>
<td className="w-[130px] px-3 py-2.5"><div className="h-1.5 rounded-full bg-[var(--line2)]"><div className="h-1.5 rounded-full bg-[var(--accent)]" style={{ width: Math.max(3, (c.dl30 / maxdl) * 100) + '%' }} /></div></td>
<td className="tnum px-3 py-2.5 text-right text-[12.5px]">{fmt(c.dl30)}</td>
<td className="tnum hidden px-3 py-2.5 text-right text-[var(--dim)] sm:table-cell">{fmt(c.dl_all)}</td>
</tr>
))}
</tbody>
</table>
{groups[rel2].length > 8 && <p className="mt-1 text-[11px] text-[var(--faint)]">+ {groups[rel2].length - 8} more</p>}
</div>
)
})}
</section>
)}
</div>
)
}
export function AuthorPage({ author, go }) {
const [data, setData] = useState(null)
const [live, setLive] = useState(null)
const [err, setErr] = useState(null)
const load = () => { setErr(null); setData(null); api.author(author).then(setData).catch(setErr); hubTop('downloads', 1000).then((a) => setLive(a.filter((m) => m.id.startsWith(author + '/')))).catch(() => {}) }
useEffect(load, [author])
if (err) return <ErrBox e={err} retry={load} />
if (!data) return <Loading label={'Loading ' + author + '…'} />
const models = (data.models || []).slice().sort((a, b) => (b.dl30 || 0) - (a.dl30 || 0))
const totals = data.totals || {}
return (
<div className="rise">
<h1 className="break-all pt-8 text-2xl font-extrabold tracking-tight sm:text-3xl">{author}</h1>
<div className="mt-6 grid grid-cols-2 divide-[var(--line)] overflow-hidden rounded-2xl border border-[var(--line)] bg-[var(--panel)] sm:grid-cols-4 lg:divide-x">
<Stat k="Models" v={(data.models || []).length} />
<Stat k="Downloads 30d" v={totals.dl30 != null ? totals.dl30 : models.reduce((a, m) => a + (m.dl30 || 0), 0)} />
<Stat k="All time" v={totals.dl_all != null ? totals.dl_all : models.reduce((a, m) => a + (m.dl_all || 0), 0)} />
<Stat k="Likes" v={totals.likes != null ? totals.likes : models.reduce((a, m) => a + (m.likes || 0), 0)} />
</div>
<section className="mt-8 rounded-2xl border border-[var(--line)] bg-[var(--panel)] p-4 sm:p-6">
<h2 className="text-[12px] font-semibold uppercase tracking-[0.06em] text-[var(--dim)]">Author downloads, all models combined</h2>
{data.series && data.series.day && data.series.day.length > 1 ? <BigChart series={data.series} /> : <p className="py-8 text-center text-sm text-[var(--dim)]">No history yet.</p>}
</section>
<section className="mt-8">
<h2 className="border-b border-[var(--ink)] pb-2 text-[13px] font-bold uppercase tracking-[0.08em]">Models <span className="font-medium normal-case tracking-normal text-[var(--faint)]">· {models.length}</span></h2>
<table className="mt-1 w-full">
<thead>
<tr className="text-[10.5px] uppercase tracking-[0.06em] text-[var(--faint)]">
<th className="py-2 pl-1 text-left font-semibold">Model</th>
<th className="px-3 py-2 text-right font-semibold">30d</th>
<th className="hidden px-3 py-2 text-right font-semibold sm:table-cell">All time</th>
<th className="px-3 py-2 pr-1 text-right font-semibold">♥</th>
</tr>
</thead>
<tbody>
{models.map((m) => (
<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="break-all py-2.5 pl-1 pr-3 font-mono text-[12.5px] font-semibold">{m.id.replace(author + '/', '')}{m.pipeline_tag && <span className="ml-2 hidden font-sans text-[11px] font-normal text-[var(--faint)] md:inline">{m.pipeline_tag}</span>}</td>
<td className="tnum px-3 py-2.5 text-right font-semibold">{fmt(m.dl30)}</td>
<td className="tnum hidden px-3 py-2.5 text-right sm:table-cell">{fmt(m.dl_all)}</td>
<td className="tnum px-3 py-2.5 pr-1 text-right text-[var(--dim)]">♥ {fmt(m.likes)}</td>
</tr>
))}
</tbody>
</table>
{live && <p className="mt-2 text-[11px] text-[var(--faint)]">Cross-checked live with the Hub API ({live.length} public repos).</p>}
</section>
</div>
)
}