Files
llmgw/internal/web/mirror_explore.go
T
Ilker ManapandClaude Opus 5.5 02fa639657 Open the mirror's registry on what is current, not an empty search box
The mirror page lists Hugging Face's trending models without anything
typed, with views for what is new this month and what is most
downloaded, filters for task and size, and each repository's parameter
count, architecture, context length and age from one registry call.
"Only what these nodes can run" is on by default and leaves out
architectures the nodes' llama.cpp cannot load, and adapters, saying how
many it hid. Listings are remembered for ten minutes.

The stylesheet is now linked by a hash of its content. It is cached for
a day, so under a fixed name a deploy's new styles reached people a day
after its markup.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 21:56:58 +02:00

242 lines
6.5 KiB
Go

package web
import (
"fmt"
"html/template"
"net/http"
"net/url"
"strings"
"time"
"gitea.mynodes.xyz/ilker/llmgw/internal/gguf"
"gitea.mynodes.xyz/ilker/llmgw/internal/hf"
)
// The explorer's views: what the registry is excited about this week, what
// arrived this month, and what everybody settled on.
const (
viewTrending = "trending"
viewNew = "new"
viewDownloads = "downloads"
)
// newWithin is how recent a repository has to be to count as new.
const newWithin = 30 * 24 * time.Hour
// minParams is the smallest thing worth calling a model.
const minParams = 50e6
// exploreShown is how many rows the explorer lists.
const exploreShown = 25
var exploreViews = []struct{ key, label string }{
{viewTrending, "Trending"}, {viewNew, "New this month"}, {viewDownloads, "Most downloaded"},
}
var exploreTasks = []struct{ key, label, tag string }{
{"chat", "Chat", "text-generation"}, {"embed", "Embeddings", "feature-extraction"}, {"any", "Any task", ""},
}
var exploreSizes = []struct {
key, label string
max int64
}{
{"3", "≤ 3B", 3e9}, {"9", "≤ 9B", 9e9}, {"35", "≤ 35B", 35e9}, {"any", "Any size", 0},
}
// explorer is the registry half of the mirror page when no repository is
// open: a listing, its filters, and links that keep them.
type explorer struct {
View, Task, Size string
// Here keeps to what the nodes' llama.cpp can load. On by default: a
// trending list is mostly things this cluster cannot run, and finding
// that out one repository at a time is what the explorer is for avoiding.
Here bool
Rows []exploreRow
// Hidden is how many rows Here took out, so an empty page says why.
Hidden int
Views, Tasks, Sizes []exploreLink
}
type exploreLink struct {
Label string
Href template.URL
Active bool
}
type exploreRow struct {
hf.Model
Verdict gguf.Verdict
Href template.URL
New bool
}
// ParamsLabel is the parameter count as people say it: 7.6B, 494M.
func (r exploreRow) ParamsLabel() string {
n := r.Params()
switch {
case n <= 0:
return "—"
case n >= 1e9:
return trimZero(fmt.Sprintf("%.1f", float64(n)/1e9)) + "B"
default:
return fmt.Sprintf("%dM", n/1e6)
}
}
// ContextLabel is the context length in thousands of tokens.
func (r exploreRow) ContextLabel() string {
if r.GGUF == nil || r.GGUF.ContextLength <= 0 {
return "—"
}
return fmt.Sprintf("%dk", r.GGUF.ContextLength/1024)
}
// Age is how long ago the repository last changed, coarsely.
func (r exploreRow) Age() string {
t := r.LastModified
if t.IsZero() {
t = r.CreatedAt
}
if t.IsZero() {
return "—"
}
d := time.Since(t)
switch {
case d < time.Hour:
return "just now"
case d < 48*time.Hour:
return fmt.Sprintf("%dh", int(d.Hours()))
case d < 14*24*time.Hour:
return fmt.Sprintf("%dd", int(d.Hours()/24))
case d < 60*24*time.Hour:
return fmt.Sprintf("%dw", int(d.Hours()/24/7))
default:
return fmt.Sprintf("%dmo", int(d.Hours()/24/30))
}
}
func trimZero(s string) string { return strings.TrimSuffix(s, ".0") }
// exploreState reads the explorer's settings from the address. With none set
// — the page opened from the menu — it is trending chat models this cluster
// can run.
func exploreState(q url.Values) explorer {
e := explorer{View: q.Get("view"), Task: q.Get("task"), Size: q.Get("size")}
// "x" marks a submitted form, so an unticked box reads as off rather than
// as not yet chosen.
e.Here = q.Get("x") != "1" || q.Get("here") == "1"
if !oneOf(e.View, viewTrending, viewNew, viewDownloads) {
e.View = viewTrending
// A search with no view asks for what a name usually means, which is
// the one everybody downloads.
if strings.TrimSpace(q.Get("q")) != "" {
e.View = viewDownloads
}
}
if !oneOf(e.Task, "chat", "embed", "any") {
e.Task = "chat"
}
if !oneOf(e.Size, "3", "9", "35", "any") {
e.Size = "any"
}
return e
}
func oneOf(s string, options ...string) bool {
for _, o := range options {
if s == o {
return true
}
}
return false
}
// 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"}}
if e.Here {
v.Set("here", "1")
}
if query != "" {
v.Set("q", query)
}
if key != "" {
v.Set(key, value)
}
return template.URL("/admin/mirror?" + v.Encode())
}
// hfQuery is what to ask the registry for.
func (e explorer) hfQuery(search string) hf.Query {
q := hf.Query{Search: search, Sort: hf.SortTrending, Limit: 60,
// What runs here is GGUF; the architecture check needs the registry's
// reading of one.
GGUF: e.Here}
switch e.View {
case viewDownloads:
q.Sort = hf.SortDownloads
case viewNew:
// Trending, then kept to the recent: sorted by creation date alone,
// the newest repositories are mostly empty uploads nobody has used.
q.Limit = 100
}
for _, t := range exploreTasks {
if t.key == e.Task {
q.Task = t.tag
}
}
for _, s := range exploreSizes {
if s.key == e.Size {
q.MaxParams = s.max
}
}
return q
}
// exploreList fills in the listing.
func (p *AccountPages) exploreList(r *http.Request, d *adminMirrorData) {
e := exploreState(r.URL.Query())
for _, v := range exploreViews {
e.Views = append(e.Views, exploreLink{v.label, e.href(d.Query, "view", v.key), v.key == e.View})
}
for _, t := range exploreTasks {
e.Tasks = append(e.Tasks, exploreLink{t.label, e.href(d.Query, "task", t.key), t.key == e.Task})
}
for _, s := range exploreSizes {
e.Sizes = append(e.Sizes, exploreLink{s.label, e.href(d.Query, "size", s.key), s.key == e.Size})
}
d.Explore = &e
got, err := p.registry.Explore(r.Context(), e.hfQuery(d.Query))
if err != nil {
d.RegistryErr = err.Error()
return
}
back := e.href(d.Query, "", "")
for _, m := range got {
if e.View == viewNew && time.Since(m.CreatedAt) > newWithin {
continue
}
row := exploreRow{Model: m, Verdict: gguf.Verdict{Servable: true},
Href: back + template.URL("&repo="+url.QueryEscape(m.ID)),
New: !m.CreatedAt.IsZero() && time.Since(m.CreatedAt) < newWithin}
if a := m.Architecture(); a != "" && p.llamaVersion != "" {
row.Verdict = gguf.LlamaCpp(p.llamaVersion, a)
}
// An adapter or a fragment, not a model: tens of megabytes of LoRA
// weights listed among the models they need a model to run on.
if e.Here && m.Params() > 0 && m.Params() < minParams {
continue
}
if e.Here && row.Verdict.Checked && !row.Verdict.Servable {
e.Hidden++
continue
}
if len(e.Rows) < exploreShown {
e.Rows = append(e.Rows, row)
}
}
}