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)
}
}
}