A site whose catalog had no flux-schnell or triposr showed no Images or 3D page, and nothing said they were one click away. The cluster page now has a Pictures and 3D models card under the models: each preset, what it makes, the classes of card here it fits, whether and by whom it was added, and why the leader has not added it by itself; an Add button, which says first when it would install ComfyUI on the GPU nodes and restart the control planes; and the switch for presets.auto. The console's models screen shows the same under its table: A adds one, asking first in the same case, and P turns automatic adding on or off. `ctl model add PRESET` and `ctl model presets` go through the presets API, so the control plane keeps who added one, and `ctl model presets auto on|off` flips the setting; against a control plane from before, add sends the entry as v0.14.4 did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
148 lines
5.1 KiB
Go
148 lines
5.1 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
|
|
)
|
|
|
|
// PresetAdmin is the models that come with llmgw, as the cluster page shows
|
|
// and adds them: flux-schnell for pictures, triposr for 3D models. A site
|
|
// whose catalog had neither showed no Images or 3D page, and nothing said
|
|
// they were one click away.
|
|
type PresetAdmin interface {
|
|
Presets(ctx context.Context) (adminapi.PresetsView, error)
|
|
AddPreset(ctx context.Context, name, by string) error
|
|
SetAutoPresets(ctx context.Context, on bool, by string) error
|
|
}
|
|
|
|
// presetsCard is the presets as the cluster page shows them.
|
|
type presetsCard struct {
|
|
Auto bool
|
|
Rows []presetRow
|
|
}
|
|
|
|
// presetRow is one preset, its words already in the reader's language.
|
|
type presetRow struct {
|
|
Name, Description, Makes, Card, Licence string
|
|
// Fits says which of the cluster's classes can run it; empty when none.
|
|
Fits string
|
|
// State is where it stands: added, and by whom; removed, so not added
|
|
// again by itself; or neither.
|
|
State string
|
|
InCatalog bool
|
|
Declined bool
|
|
// Blocked is why the leader does not add it by itself, in the control
|
|
// plane's words (English); not said when automatic adding is off, which
|
|
// the line under the table says once.
|
|
Blocked string
|
|
// AddsComfyUI is an Add that also has the GPU nodes install ComfyUI and
|
|
// the control planes restart on that setting.
|
|
AddsComfyUI bool
|
|
}
|
|
|
|
// presetAddsComfyUI is whether adding a preset by hand sets engines.comfyui:
|
|
// the cluster installs none yet.
|
|
func presetAddsComfyUI(v adminapi.PresetsView, p adminapi.PresetView) bool {
|
|
return !p.InCatalog && v.ComfyUI == ""
|
|
}
|
|
|
|
func init() {
|
|
registerKeys("dash.presets.makes.pictures", "dash.presets.makes.meshes")
|
|
}
|
|
|
|
// presetsCardOf reads the presets for the page; nil, and the card left off,
|
|
// where the control plane cannot say.
|
|
func presetsCardOf(ctx context.Context, l *Lang, a PresetAdmin) *presetsCard {
|
|
v, err := a.Presets(ctx)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
c := &presetsCard{Auto: v.Auto}
|
|
for _, p := range v.Presets {
|
|
row := presetRow{
|
|
Name: p.Name, Description: p.Description, Card: p.Card,
|
|
Makes: l.T("dash.presets.makes." + p.Makes),
|
|
Licence: p.Licence.Name,
|
|
Fits: strings.Join(p.FitsClasses, ", "),
|
|
InCatalog: p.InCatalog, Declined: p.Declined,
|
|
AddsComfyUI: presetAddsComfyUI(v, p),
|
|
}
|
|
if !strings.HasPrefix(p.Blocked, "presets.auto") && !p.Declined {
|
|
row.Blocked = p.Blocked
|
|
}
|
|
if c := p.Licence.Commercial; c != nil && !*c {
|
|
row.Licence = l.T("dash.presets.not_commercial", p.Licence.Name)
|
|
}
|
|
at := ""
|
|
if !p.AddedAt.IsZero() {
|
|
at = p.AddedAt.UTC().Format("2006-01-02 15:04")
|
|
}
|
|
switch {
|
|
case p.InCatalog && p.AddedBy == "auto":
|
|
row.State = l.T("dash.presets.added_auto", at)
|
|
case p.InCatalog && p.AddedBy != "":
|
|
row.State = l.T("dash.presets.added_by", p.AddedBy, at)
|
|
case p.InCatalog:
|
|
row.State = l.T("dash.presets.in_catalog")
|
|
case p.Declined:
|
|
row.State = l.T("dash.presets.declined")
|
|
}
|
|
c.Rows = append(c.Rows, row)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// RegisterPresets mounts adding a preset, and turning their automatic adding
|
|
// on and off, on the cluster page: for an administrator, with the session's
|
|
// CSRF token, answered by going back to the page with what happened.
|
|
func (p *AccountPages) RegisterPresets(mux *http.ServeMux, a PresetAdmin) {
|
|
back := func(w http.ResponseWriter, r *http.Request, q url.Values) {
|
|
http.Redirect(w, r, "/admin?"+q.Encode()+"#presets", http.StatusSeeOther)
|
|
}
|
|
checked := func(h func(w http.ResponseWriter, r *http.Request, admin store.User)) http.HandlerFunc {
|
|
return p.adminOnly(func(w http.ResponseWriter, r *http.Request, admin store.User) {
|
|
sess, _, _ := p.session(r)
|
|
if !p.accounts.CheckCSRF(sess, r.FormValue("csrf")) {
|
|
http.Error(w, "bad request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
h(w, r, admin)
|
|
})
|
|
}
|
|
// Before the one by name, which would otherwise take "auto" for a preset.
|
|
mux.HandleFunc("POST /admin/presets/auto", checked(func(w http.ResponseWriter, r *http.Request, admin store.User) {
|
|
on := r.FormValue("on") == "true"
|
|
q := url.Values{}
|
|
if err := a.SetAutoPresets(r.Context(), on, admin.Email); err != nil {
|
|
p.log.Warn("presets.auto was not changed", "admin", admin.Email, "err", err)
|
|
q.Set("problem", err.Error())
|
|
} else {
|
|
p.log.Info("presets.auto changed", "admin", admin.Email, "on", on)
|
|
key := "dash.presets.notice.auto_off"
|
|
if on {
|
|
key = "dash.presets.notice.auto_on"
|
|
}
|
|
q.Set("notice", langOf(r).T(key))
|
|
}
|
|
back(w, r, q)
|
|
}))
|
|
mux.HandleFunc("POST /admin/presets/{name}", checked(func(w http.ResponseWriter, r *http.Request, admin store.User) {
|
|
name := r.PathValue("name")
|
|
q := url.Values{}
|
|
if err := a.AddPreset(r.Context(), name, admin.Email); err != nil {
|
|
p.log.Warn("a preset was not added", "admin", admin.Email, "preset", name, "err", err)
|
|
// The control plane's reason, as it gave it: in English.
|
|
q.Set("problem", name+": "+err.Error())
|
|
} else {
|
|
p.log.Info("a preset was added", "admin", admin.Email, "preset", name)
|
|
q.Set("notice", langOf(r).T("dash.presets.notice.added", name))
|
|
}
|
|
back(w, r, q)
|
|
}))
|
|
}
|