Compare commits

...
1 Commits
Author SHA1 Message Date
Ilker ManapandClaude Opus 5.5 2ae12ae1fe Split the mirror page into Held, Explore and Fetch tabs
Release / build (push) Successful in 58s
Release / notes (push) Successful in 3s
CI / test (push) Successful in 3m45s
One long page made every visit wait on Hugging Face for a list it might not
show. Each tab now has its own address and does only its own work; only
Held reloads while a download runs, and a refused fetch returns to the form
it came from.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 08:31:28 +02:00
7 changed files with 198 additions and 50 deletions

No files matched your search

+16
View File
@@ -20,6 +20,22 @@ is only released once its **exit criterion** is met, not when its code lands.
Until v1.0.0 the minor version is the breaking one: configuration formats and Go
APIs change between minors without a deprecation period.
## Unreleased
Changes since v0.10.4, published as release candidates (v0.10.5-rc.N) until
there are enough for v0.10.5.
### The mirror page in tabs
/admin/mirror was one long page: what the mirror holds, the Hugging Face
explorer and the fetch form, one under another, with every visit waiting on
Hugging Face for a list it might not show. It is now three tabs — Held (with
how many are held, downloading and failed), Explore Hugging Face, and Fetch by
URL — each with its own address. Only the tab shown does its work, and only
Held reloads while something downloads. A download started from the explorer
links to Held, where its progress is; a fetch the mirror refused comes back on
the form it was sent from.
## v0.10.4 — 2026-09-30
Published first as release candidates v0.10.4-rc.1 to rc.9.
+81 -3
View File
@@ -100,7 +100,11 @@ type mirrorRow struct {
}
type adminMirrorData struct {
Nav Nav
Nav Nav
// Tab is the part of the page shown: held, explore or fetch. Tabs are the
// links to each.
Tab string
Tabs []mirrorTab
Addr string
Base string
Rows []mirrorRow
@@ -361,7 +365,14 @@ func (p *AccountPages) renderMirror(w http.ResponseWriter, r *http.Request,
}
d.HasCatalog = p.catalog != nil
p.explore(r, &d)
d.HasRegistry = p.registry != nil
d.Tab = mirrorTabFor(r, errMsg)
// Only the tab shown does its work: the registry is a server across the
// internet, and asking it for sixty repositories to show somebody who came
// to look at a download's progress made every visit wait on it.
if d.Tab == tabExplore {
p.explore(r, &d)
}
got, err := m.List(r.Context())
if err != nil {
@@ -370,6 +381,7 @@ func (p *AccountPages) renderMirror(w http.ResponseWriter, r *http.Request,
if d.Error == "" {
d.Error = err.Error()
}
d.Tabs = mirrorTabs(d)
render(w, "admin_mirror", d)
return
}
@@ -462,12 +474,78 @@ func (p *AccountPages) renderMirror(w http.ResponseWriter, r *http.Request,
d.Groups = groupByModel(d.Rows)
if d.Fetching > 0 {
// Only on the list: a reload anywhere else takes with it a search, an
// open repository or a URL half typed.
if d.Fetching > 0 && d.Tab == tabHeld {
d.RefreshSec = 5
}
d.Tabs = mirrorTabs(d)
render(w, "admin_mirror", d)
}
const (
tabHeld = "held"
tabExplore = "explore"
tabFetch = "fetch"
)
// mirrorTab is one of the page's tabs.
type mirrorTab struct {
Label string
Count string // beside the label, empty for none
Href string
Active bool
}
// mirrorTabFor is the tab a request is for.
//
// Named in the address when it is chosen, and otherwise read from what the
// address carries, so links made before the page had tabs — a search, an
// open repository — still land where they meant. After a change the list is
// shown, where its effect is, unless it was refused: then the form it came
// from, to try again.
func mirrorTabFor(r *http.Request, errMsg string) string {
valid := func(t string) bool { return t == tabHeld || t == tabExplore || t == tabFetch }
if r.Method == http.MethodPost {
if t := r.PostFormValue("tab"); errMsg != "" && valid(t) {
return t
}
return tabHeld
}
q := r.URL.Query()
if t := q.Get("tab"); valid(t) {
return t
}
for _, k := range []string{"q", "repo", "view", "task", "size", "x"} {
if q.Has(k) {
return tabExplore
}
}
return tabHeld
}
func mirrorTabs(d adminMirrorData) []mirrorTab {
held := mirrorTab{Label: "Held", Href: "/admin/mirror?tab=held", Active: d.Tab == tabHeld}
switch {
case d.Unreachable:
held.Count = "unreachable"
case d.Fetching > 0:
held.Count = fmt.Sprintf("%d · %d downloading", d.Present, d.Fetching)
default:
held.Count = strconv.Itoa(d.Present)
}
if d.Failed > 0 {
held.Count += fmt.Sprintf(" · %d failed", d.Failed)
}
tabs := []mirrorTab{held}
if d.HasRegistry {
tabs = append(tabs, mirrorTab{Label: "Explore Hugging Face",
Href: "/admin/mirror?tab=explore", Active: d.Tab == tabExplore})
}
return append(tabs, mirrorTab{Label: "Fetch by URL",
Href: "/admin/mirror?tab=fetch", Active: d.Tab == tabFetch})
}
// mirrorGroup is a family of models the mirror holds — every qwen, every
// llama — and their versions.
type mirrorGroup struct {
+65 -10
View File
@@ -341,16 +341,18 @@ func TestAFailedRowOffersRetryRatherThanRemove(t *testing.T) {
}
type fakeRegistry struct {
results []hf.Model
repo hf.Repo
err error
asked string
query hf.Query
results []hf.Model
repo hf.Repo
err error
asked string
query hf.Query
explored int
}
func (f *fakeRegistry) Explore(_ context.Context, q hf.Query) ([]hf.Model, error) {
f.asked = q.Search
f.query = q
f.explored++
return f.results, f.err
}
@@ -441,7 +443,11 @@ func TestWithNoRegistryThereIsNoSearchBox(t *testing.T) {
if strings.Contains(body, "Search Hugging Face") {
t.Error("the explorer should be absent without a registry")
}
if !strings.Contains(body, "Fetch a model") {
if strings.Contains(body, "Explore Hugging Face") {
t.Error("a tab for an explorer that is not there")
}
fetch := mirrorGet(t, mirrorPage(t, &fakeMirror{}, true), "/admin/mirror?tab=fetch").Body.String()
if !strings.Contains(fetch, `name="source" required`) {
t.Error("fetching by hand must still be there")
}
}
@@ -464,8 +470,7 @@ func TestAShardedModelIsOneRowAndOneButton(t *testing.T) {
"/admin/mirror?repo=Qwen/Qwen2.5-7B-Instruct").Body.String()
// Fifteen shards is one decision, not fifteen. Four here, and one form.
if n := strings.Count(body, `action="/admin/mirror/fetch"`); n != 2 {
// One for the set, one for the manual form at the bottom.
if n := strings.Count(body, `action="/admin/mirror/fetch"`); n != 1 {
t.Errorf("expected the four shards as a single form, found %d forms", n)
}
if !strings.Contains(body, "4 shards, fetched together") {
@@ -534,8 +539,8 @@ func TestAlternativeQuantisationsStaySeparateRows(t *testing.T) {
// Two GGUFs are alternatives, not halves. Offering "fetch all" here would
// spend gigabytes on a copy nobody asked for.
if n := strings.Count(body, `action="/admin/mirror/fetch"`); n != 3 {
t.Errorf("expected a form each plus the manual one, found %d", n)
if n := strings.Count(body, `action="/admin/mirror/fetch"`); n != 2 {
t.Errorf("expected a form each, found %d", n)
}
}
@@ -655,3 +660,53 @@ func privateOK(m *mirror.Manager) *mirror.Manager {
m.AllowPrivateSources()
return m
}
func TestEachTabDoesOnlyItsOwnWork(t *testing.T) {
reg := &fakeRegistry{}
mux := mirrorPageWith(t, &fakeMirror{models: []mirror.Status{
{Model: "m", Version: "v", File: "m.gguf", State: mirror.Present, BytesDone: 1},
{Model: "n", Version: "v", File: "n.gguf", State: mirror.Fetching, BytesDone: 1},
}}, reg, true)
held := mirrorGet(t, mux, "/admin/mirror").Body.String()
if reg.explored != 0 {
t.Error("the list asked the registry for repositories it does not show")
}
if !strings.Contains(held, `class="on" aria-current="page">Held <span class="count">1 · 1 downloading</span>`) {
t.Errorf("the Held tab should be chosen and count what it holds: %s", held)
}
if strings.Contains(held, `name="source" required`) || strings.Contains(held, "Search Hugging Face") {
t.Error("other tabs' forms are on the list")
}
if !strings.Contains(held, `http-equiv="refresh"`) {
t.Error("the list stopped following a download")
}
explore := mirrorGet(t, mux, "/admin/mirror?tab=explore").Body.String()
if reg.explored != 1 || !strings.Contains(explore, "Search Hugging Face") {
t.Error("the explorer tab did not explore")
}
if strings.Contains(explore, `id="held"`) || strings.Contains(explore, `http-equiv="refresh"`) {
t.Error("the explorer shows the list, or reloads away a search")
}
// A link from before there were tabs still lands on the explorer.
if old := mirrorGet(t, mux, "/admin/mirror?q=qwen").Body.String(); !strings.Contains(old, "Search Hugging Face") {
t.Error("a search address no longer opens the explorer")
}
}
func TestARefusedFetchReturnsToItsFormAndAStartedOneToTheList(t *testing.T) {
m := &fakeMirror{fetchErr: errors.New("upstream said 404")}
mux := mirrorPage(t, m, true)
body := mirrorPost(t, mux, "/admin/mirror/fetch", url.Values{"csrf": {"tok"}, "source": {"https://h.example/m.gguf"}, "model": {"m"},
"version": {"v"}, "force": {"1"}, "tab": {"fetch"}}).Body.String()
if !strings.Contains(body, "upstream said 404") || !strings.Contains(body, `name="source" required`) {
t.Errorf("a refusal should come back on the form it came from: %s", body)
}
m.fetchErr = nil
body = mirrorPost(t, mux, "/admin/mirror/fetch", url.Values{"csrf": {"tok"}, "source": {"https://h.example/m.gguf"}, "model": {"m"},
"version": {"v"}, "force": {"1"}, "tab": {"fetch"}}).Body.String()
if !strings.Contains(body, `id="held"`) {
t.Errorf("a started download should be shown where it is followed: %s", body)
}
}
+1 -1
View File
@@ -155,7 +155,7 @@ func oneOf(s string, options ...string) bool {
// href is the page with these settings and one of them changed.
func (e explorer) href(query, key, value string) template.URL {
v := url.Values{"view": {e.View}, "task": {e.Task}, "size": {e.Size}, "x": {"1"}}
v := url.Values{"tab": {tabExplore}, "view": {e.View}, "task": {e.Task}, "size": {e.Size}, "x": {"1"}}
if e.Here {
v.Set("here", "1")
}
+2 -2
View File
@@ -27,7 +27,7 @@ func TestTheExplorerOpensOnTrendingModelsTheNodesCanRun(t *testing.T) {
ggufModel("odd/Kev-GGUF", "ggmlc", 5e8, now.Add(-2*24*time.Hour)),
}}
body := mirrorGet(t, mirrorPageLlama(t, MirrorDeps{Mirror: &fakeMirror{}, Registry: reg}),
"/admin/mirror").Body.String()
"/admin/mirror?tab=explore").Body.String()
q := reg.query
if q.Sort != hf.SortTrending || q.Task != "text-generation" || !q.GGUF || q.Search != "" {
@@ -96,7 +96,7 @@ func TestAnAdapterIsNotListedAsAModel(t *testing.T) {
ggufModel("a/Tiny-GGUF", "qwen2", 135e6, time.Now()),
}}
body := mirrorGet(t, mirrorPageLlama(t, MirrorDeps{Mirror: &fakeMirror{}, Registry: reg}),
"/admin/mirror").Body.String()
"/admin/mirror?tab=explore").Body.String()
if strings.Contains(body, "Some-LoRA") || !strings.Contains(body, "a/Tiny-GGUF") {
t.Errorf("adapters should be left out and small models kept: %s", body)
}
+9
View File
@@ -456,6 +456,15 @@ table.held select.mode { width: 100%; padding: .15rem .3rem; font-size: .8rem; }
/* Link pills: the same look as .tabs, for choices that are addresses rather
than panels on one page — a filtered listing should survive a reload and be
worth sending to somebody. */
/* A page's own sections, one address each. Underlined rather than pills so
* they do not read as one more filter beside the ones inside a section. */
.page-tabs { display: flex; flex-wrap: wrap; gap: 0 1.5rem; margin: -1rem 0 1.5rem;
border-bottom: 1px solid var(--line); }
.page-tabs a { padding: .55rem 0; margin-bottom: -1px; font-weight: 600; color: var(--muted);
text-decoration: none; border-bottom: 2px solid transparent; }
.page-tabs a:hover { color: var(--fg); }
.page-tabs a.on { color: var(--fg); border-bottom-color: var(--accent); }
.page-tabs .count { font-weight: 500; font-size: .8rem; color: var(--muted); margin-left: .25rem; }
.pills { display: flex; flex-wrap: wrap; gap: .35rem; margin: .5rem 0; }
.pills a { font-size: .85rem; font-weight: 600; color: var(--muted); padding: .35rem .85rem;
border: 1px solid transparent; border-radius: 999px; text-decoration: none; }
+24 -34
View File
@@ -8,16 +8,20 @@
</head><body><div class="wrap">
<header>
<h1>Model mirror</h1>
<p class="sub">
{{if .Unreachable}}{{.Addr}} did not answer{{else}}{{.Present}} held ·
{{.Fetching}} downloading · {{.Failed}} failed · {{.Addr}}{{end}}
</p>
<p class="sub">{{.Addr}}{{if .Unreachable}} did not answer{{end}}</p>
{{template "nav" .Nav}}
</header>
{{/* Links rather than a script's panels: each tab has an address, the back
button works, and only the one shown asks anything of the registry. */}}
<nav class="page-tabs" aria-label="Mirror">
{{range .Tabs}}<a href="{{.Href}}"{{if .Active}} class="on" aria-current="page"{{end}}>{{.Label}}{{with .Count}} <span class="count">{{.}}</span>{{end}}</a>
{{end}}</nav>
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
{{if .Message}}<p class="ok">{{.Message}}</p>{{end}}
{{if eq .Tab "held"}}
{{if .Unreachable}}
{{/* Said outright rather than shown as an empty table. A mirror holding
nothing and a mirror that is not there look the same in a list of zero
@@ -29,7 +33,6 @@ reachable from this machine.</p>
{{else}}
<div id="held">
<h2>What the mirror holds</h2>
{{if .Rows}}
<table class="compact held">
<tr><th>Model</th><th>Files</th><th class="num">Size</th><th>Runs</th><th></th></tr>
@@ -190,11 +193,14 @@ document.addEventListener('click', function (e) {
} else { window.prompt('Address', b.dataset.url); }
});
</script>
{{else}}<p class="empty">The mirror is reachable and holds nothing yet.</p>{{end}}
{{else}}<p class="empty">The mirror is reachable and holds nothing yet.
{{if .HasRegistry}}<a href="/admin/mirror?tab=explore">Explore Hugging Face</a> for a model, or
{{end}}<a href="/admin/mirror?tab=fetch">fetch one by URL</a>.</p>{{end}}
</div>
{{end}}
{{end}}
{{if .HasRegistry}}
<h2>Explore models</h2>
{{if and (eq .Tab "explore") .HasRegistry}}
{{if not .Repo}}{{with .Explore}}
<nav class="pills" aria-label="Which models">{{range .Views}}<a href="{{.Href}}"{{if .Active}} class="on" aria-current="page"{{end}}>{{.Label}}</a>{{end}}</nav>
<div class="explore-filters">
@@ -203,6 +209,7 @@ document.addEventListener('click', function (e) {
</div>
{{end}}
<form method="get" action="/admin/mirror" class="explore-search">
<input type="hidden" name="tab" value="explore">
{{with .Explore}}<input type="hidden" name="view" value="{{.View}}">
<input type="hidden" name="task" value="{{.Task}}">
<input type="hidden" name="size" value="{{.Size}}">
@@ -307,9 +314,12 @@ document.querySelector("table.explore").addEventListener("submit", async functio
return;
}
note.classList.remove("bad");
note.textContent = "✓ " + out.message + " — its progress is in the table above.";
note.textContent = "✓ " + out.message + " — ";
const link = document.createElement("a");
link.href = "/admin/mirror?tab=held";
link.textContent = "follow it under Held";
note.appendChild(link);
btn.textContent = "Started";
followHeld();
} catch (err) {
note.textContent = "Could not reach the gateway: " + err;
note.classList.add("bad");
@@ -317,28 +327,6 @@ document.querySelector("table.explore").addEventListener("submit", async functio
btn.textContent = was;
}
});
// Refreshes only the mirror's own table, every five seconds while anything
// in it is downloading.
let following = false;
async function followHeld() {
if (following) return;
following = true;
try {
for (;;) {
const r = await fetch(location.pathname + location.search, { headers: { Accept: "text/html" } });
if (!r.ok) break;
const doc = new DOMParser().parseFromString(await r.text(), "text/html");
const fresh = doc.getElementById("held"), held = document.getElementById("held");
if (!fresh || !held) break;
held.replaceWith(fresh);
if (!fresh.querySelector(".state-starting")) break;
await new Promise((ok) => setTimeout(ok, 5000));
}
} finally {
following = false;
}
}
</script>
{{if .Hidden}}<p class="note">{{.Hidden}} more {{if eq .Hidden 1}}is{{else}}are{{end}} hidden: their architecture is one the nodes' llama.cpp cannot load.
Untick “only what these nodes can run” to see them.</p>{{end}}
@@ -354,10 +342,11 @@ directory, or give the mirror a token.</p>
{{if .Repo}}{{template "mirror_repo" .}}{{end}}
{{end}}
<h2>Fetch a model</h2>
{{if eq .Tab "fetch"}}
<p class="note">By hand, for anything the search does not reach.</p>
<form method="post" action="/admin/mirror/fetch">
<input type="hidden" name="csrf" value="{{.CSRF}}">
<input type="hidden" name="tab" value="fetch">
<label>Source URL
<input type="text" name="source" required
placeholder="https://huggingface.co/…/resolve/main/model.gguf"></label>
@@ -373,7 +362,7 @@ directory, or give the mirror a token.</p>
</form>
<p class="note">The mirror answers as soon as the download is under way, not when
it finishes — a model is tens of gigabytes and nothing here holds a connection
open for that. The row above will show it arriving. A version is never
open for that. The Held tab shows it arriving. A version is never
overwritten: fetching into one that exists is refused, so a new build is a new
version and the old weights stay where they are.</p>
{{end}}
@@ -415,6 +404,7 @@ version and the old weights stay where they are.</p>
{{if .Verdict.Architecture}}<span class="opt" title="{{.Verdict.Reason}}">{{.Verdict.Architecture}}{{if .Verdict.Checked}} ✓{{end}}</span>{{end}}
<form method="post" action="/admin/mirror/fetch" class="inline">
<input type="hidden" name="csrf" value="{{$.CSRF}}">
<input type="hidden" name="tab" value="explore">
{{range .Sources}}<input type="hidden" name="source" value="{{.URL}}">
<input type="hidden" name="file" value="{{.Path}}">
{{end}}