/** * The progress bar the three Spaces share. * * Every one of them reads safetensors headers over ranged requests — two * per shard, and a large model has a dozen shards. That is seconds of * silence behind a line of static text, which reads as a page that does * not work. The bar exists so waiting looks like waiting. * * One file, copied into each Space at deploy time rather than written * three times: three copies of a progress bar drift into three different * behaviours, and the one that matters here is subtle — a bar that goes * backwards, or sits at 100% while work continues, is worse than no bar, * because it is believed. * * Two modes, and which one is honest depends on what is known: * * start(label) indeterminate — something is happening, and * nobody knows how much of it there is yet * total(n) switches to determinate once the shard count * is known * step(label) one unit done * done() / fail() release it */ const BAR_CSS = ` .pg { margin: 1.25rem 0 .5rem; } .pg-label { font-size: .88rem; color: var(--muted); margin-bottom: .45rem; display: flex; justify-content: space-between; gap: 1rem; } .pg-count { font-family: var(--mono); font-size: .82rem; white-space: nowrap; } .pg-track { height: .4rem; border-radius: .2rem; background: var(--line); overflow: hidden; position: relative; } .pg-fill { height: 100%; background: var(--accent); border-radius: .2rem; width: 0; transition: width .18s ease-out; } .pg-track.indeterminate .pg-fill { width: 35%; animation: pg-slide 1.1s ease-in-out infinite; } @keyframes pg-slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(300%); } } @media (prefers-reduced-motion: reduce) { .pg-track.indeterminate .pg-fill { animation: none; width: 100%; opacity: .45; } .pg-fill { transition: none; } }`; let styled = false; function ensureStyle() { if (styled) return; const el = document.createElement('style'); el.textContent = BAR_CSS; document.head.appendChild(el); styled = true; } /** * A progress bar mounted in `host`. * * It replaces the host's contents, so the caller renders the result into * the same element when it finishes and the bar disappears with it. */ export function progress(host) { ensureStyle(); let done = 0; let total = 0; host.innerHTML = `