CI / test (push) Failing after 6m27s
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>
129 lines
4.6 KiB
Go
129 lines
4.6 KiB
Go
package cli
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
|
|
)
|
|
|
|
// presetsBlock is the models that come with llmgw, under the models table:
|
|
// whether each fits a card here and whether it is in the catalog, and
|
|
// whether the leader adds them by itself. Empty from a control plane that
|
|
// cannot say.
|
|
func presetsBlock(v *adminapi.PresetsView, width int) string {
|
|
if v == nil || len(v.Presets) == 0 {
|
|
return ""
|
|
}
|
|
var rows [][]string
|
|
var why []string
|
|
for _, p := range v.Presets {
|
|
rows = append(rows, []string{p.Name, p.Makes, orDash(strings.Join(p.FitsClasses, ",")), presetState(p)})
|
|
// Why the leader leaves it, under the table: a reason is longer than
|
|
// any column the panel has room for. Automatic adding being off is
|
|
// said once, below.
|
|
if p.Blocked != "" && !strings.HasPrefix(p.Blocked, "presets.auto") && !p.Declined {
|
|
why = append(why, " "+wrap(p.Name+": "+p.Blocked, width-4, " "))
|
|
}
|
|
}
|
|
auto := "Added by themselves where a card fits · P stops that"
|
|
if !v.Auto {
|
|
auto = "Not added by themselves · P starts that · A adds one"
|
|
}
|
|
out := "\n\n Pictures and 3D models that come with llmgw\n" +
|
|
table([]string{"PRESET", "MAKES", "FITS", "HERE"}, rows, -1, width, false)
|
|
if len(why) > 0 {
|
|
out += "\n" + sDim.Render(strings.Join(why, "\n"))
|
|
}
|
|
return out + "\n " + sDim.Render(auto)
|
|
}
|
|
|
|
// presetChoice is how a preset is offered in the add dialog: its name, and
|
|
// where it stands when that is worth knowing before choosing it.
|
|
func presetChoice(v *adminapi.PresetsView, name string) string {
|
|
if v == nil {
|
|
return name
|
|
}
|
|
for _, p := range v.Presets {
|
|
if p.Name != name {
|
|
continue
|
|
}
|
|
switch {
|
|
case p.InCatalog:
|
|
return name + " (already here)"
|
|
case presetAddsComfyUI(v, p):
|
|
return name + " (installs ComfyUI too)"
|
|
case !p.Fits:
|
|
return name + " (no card here fits it)"
|
|
case p.Declined:
|
|
return name + " (removed before)"
|
|
}
|
|
}
|
|
return name
|
|
}
|
|
|
|
// presetAddsComfyUI is whether adding a preset by hand also sets
|
|
// engines.comfyui, which has the GPU nodes install it and the control planes
|
|
// restart on the setting.
|
|
func presetAddsComfyUI(v *adminapi.PresetsView, p adminapi.PresetView) bool {
|
|
return v != nil && !p.InCatalog && v.ComfyUI == ""
|
|
}
|
|
|
|
// addPreset adds a preset through the control plane, or, from one from
|
|
// before it could, the way the release that brought presets did. One whose
|
|
// adding installs ComfyUI and restarts the control planes is asked about
|
|
// first.
|
|
func (m *consoleModel) addPreset(name string) tea.Cmd {
|
|
if m.data.presets != nil {
|
|
for _, p := range m.data.presets.Presets {
|
|
if p.Name == name && presetAddsComfyUI(m.data.presets, p) {
|
|
m.confirmD = &confirmDialog{title: "Add " + name + " and ComfyUI",
|
|
text: "The cluster installs no ComfyUI yet. Adding " + name + " sets engines.comfyui:\n" +
|
|
"every GPU node installs it, about 10 GB each, and the control planes restart\n" +
|
|
"on the setting, standbys first. Engines keep serving.",
|
|
yes: func(m *consoleModel) tea.Cmd { return m.postPreset(name) }}
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
return m.postPreset(name)
|
|
}
|
|
|
|
func (m *consoleModel) postPreset(name string) tea.Cmd {
|
|
if m.data.presets == nil {
|
|
return m.runCLI("Add "+name, "ctl", "--server="+m.client.base, "model", "add", name)
|
|
}
|
|
return m.call("Add "+name, http.MethodPost, "/api/v1/presets/"+url.PathEscape(name), map[string]string{},
|
|
func(_ json.RawMessage) string {
|
|
return name + " is added, and ComfyUI with it where the cluster had none.\n" +
|
|
"It is placed on the next pass, and loads when first asked for."
|
|
})
|
|
}
|
|
|
|
// autoPresetsConfirm turns the adding of presets by themselves on or off,
|
|
// after saying what that does.
|
|
func (m *consoleModel) autoPresetsConfirm() {
|
|
on := !m.data.presets.Auto
|
|
title, text := "Stop adding pictures and 3D models by themselves",
|
|
"Presets already added stay. A preset is added only when somebody adds it."
|
|
if on {
|
|
title, text = "Add pictures and 3D models by themselves",
|
|
"Where a card fits one and the catalog has nothing that makes what it makes, the leader adds it.\n"+
|
|
"That also has every GPU node install ComfyUI, about 10 GB each, and the mirror fetch its weights.\n"+
|
|
"A preset somebody removed is not added again."
|
|
}
|
|
m.confirmD = &confirmDialog{title: title, text: text, yes: func(m *consoleModel) tea.Cmd {
|
|
return m.call(title, http.MethodPut, "/api/v1/presets/auto", adminapi.PresetsAutoRequest{On: on},
|
|
func(_ json.RawMessage) string {
|
|
if on {
|
|
return "Presets are added by themselves where a card fits."
|
|
}
|
|
return "Presets are no longer added by themselves."
|
|
})
|
|
}}
|
|
}
|