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>
167 lines
5.3 KiB
Go
167 lines
5.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/agent/provision"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/catalog"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/settings"
|
|
)
|
|
|
|
func newModelPresetsCmd(client func() *ctlClient, asJSON *bool) *cobra.Command {
|
|
c := &cobra.Command{
|
|
Use: "presets",
|
|
Short: "The models that come with llmgw: whether each fits and is here",
|
|
Long: "Each is a catalog entry whose workflow, custom nodes, packages and licence\n" +
|
|
"were found and tried, and whose profile says the card it needs rather than\n" +
|
|
"a class, so it places on any site's cards. Add one with\n" +
|
|
"`llmgw ctl model add NAME`.\n\n" +
|
|
"While presets.auto is on, the leader adds one by itself when a card fits it\n" +
|
|
"and the catalog has nothing that makes what it makes; one somebody removed\n" +
|
|
"is not added again by itself.",
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
var v adminapi.PresetsView
|
|
err := client().get(cmd.Context(), "/api/v1/presets", &v)
|
|
if err != nil && !olderControlPlane(err) {
|
|
return err
|
|
}
|
|
if *asJSON && err == nil {
|
|
return emitJSON(v)
|
|
}
|
|
w := tabTo(cmd.OutOrStdout())
|
|
if err != nil {
|
|
// A control plane from before it kept them: what this
|
|
// release has, with nothing about the cluster.
|
|
fmt.Fprintln(w, "NAME\tMAKES\tCARD\tLICENCE\tWHAT IT IS FOR")
|
|
for _, p := range catalog.Presets() {
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", p.Model.Name, p.Makes, presetCard(p.Model),
|
|
presetLicence(p.Model), p.Model.Description)
|
|
}
|
|
return w.Flush()
|
|
}
|
|
fmt.Fprintln(w, "NAME\tMAKES\tCARD\tLICENCE\tFITS\tHERE\tWHAT IT IS FOR")
|
|
for _, p := range v.Presets {
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", p.Name, p.Makes, p.Card, licenceText(p.Licence),
|
|
orDash(strings.Join(p.FitsClasses, ",")), presetState(p), p.Description)
|
|
}
|
|
if err := w.Flush(); err != nil {
|
|
return err
|
|
}
|
|
if v.Auto {
|
|
fmt.Fprintln(cmd.OutOrStdout(), "\nAdded by themselves where a card fits (presets.auto); "+
|
|
"llmgw ctl model presets auto off stops it.")
|
|
} else {
|
|
fmt.Fprintln(cmd.OutOrStdout(), "\nNot added by themselves; llmgw ctl model presets auto on starts it.")
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
c.AddCommand(&cobra.Command{
|
|
Use: "auto on|off",
|
|
Short: "Whether the leader adds a preset by itself where a card fits it",
|
|
Args: cobra.ExactArgs(1),
|
|
ValidArgs: []string{"on", "off"},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if args[0] != "on" && args[0] != "off" {
|
|
return fmt.Errorf("on or off, not %q", args[0])
|
|
}
|
|
body, _ := json.Marshal(adminapi.PresetsAutoRequest{On: args[0] == "on"})
|
|
var v adminapi.PresetsView
|
|
if err := client().put(cmd.Context(), "/api/v1/presets/auto", body, &v); err != nil {
|
|
return err
|
|
}
|
|
if v.Auto {
|
|
fmt.Fprintln(cmd.OutOrStdout(), "Presets are added by themselves where a card fits.")
|
|
} else {
|
|
fmt.Fprintln(cmd.OutOrStdout(), "Presets are no longer added by themselves.")
|
|
}
|
|
return nil
|
|
},
|
|
})
|
|
return c
|
|
}
|
|
|
|
// olderControlPlane is an error from a control plane that has no such path:
|
|
// one from before it was added.
|
|
func olderControlPlane(err error) bool {
|
|
s := err.Error()
|
|
return strings.Contains(s, "answered with a page") || strings.HasPrefix(s, "404 Not Found: 404 page not found")
|
|
}
|
|
|
|
func presetState(p adminapi.PresetView) string {
|
|
switch {
|
|
case p.InCatalog && p.AddedBy == "auto":
|
|
return "added by itself"
|
|
case p.InCatalog && p.AddedBy != "":
|
|
return "added by " + p.AddedBy
|
|
case p.InCatalog:
|
|
return "here"
|
|
case p.Declined:
|
|
return "removed; stays out"
|
|
}
|
|
return "—"
|
|
}
|
|
|
|
func licenceText(l catalog.License) string {
|
|
if l.Commercial != nil && !*l.Commercial {
|
|
return l.Name + ", not commercial"
|
|
}
|
|
return l.Name
|
|
}
|
|
|
|
func presetCard(m catalog.Model) string {
|
|
if len(m.Profiles) == 0 || m.Profiles[0].GPU == nil {
|
|
return "—"
|
|
}
|
|
return m.Profiles[0].GPU.String()
|
|
}
|
|
|
|
func presetLicence(m catalog.Model) string {
|
|
if m.License == nil {
|
|
return "—"
|
|
}
|
|
l := m.License.Name
|
|
if c := m.License.Commercial; c != nil && !*c {
|
|
l += ", not commercial"
|
|
}
|
|
return l
|
|
}
|
|
|
|
// ensureComfyUI has the nodes install ComfyUI for a model that needs it,
|
|
// unless the cluster names a version already, or none on purpose.
|
|
func ensureComfyUI(cmd *cobra.Command, client func() *ctlClient, model string) error {
|
|
out := cmd.OutOrStdout()
|
|
path := []string{"engines", catalog.EngineComfyUI}
|
|
var cur adminapi.SettingsView
|
|
if err := client().get(cmd.Context(), "/api/v1/settings", &cur); err != nil {
|
|
return err
|
|
}
|
|
doc, err := settings.FromBytes("settings", []byte(cur.Body))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
version := provision.ComfyUIVersion()
|
|
if n := doc.Node(path); n != nil {
|
|
if n.Value == "" {
|
|
fmt.Fprintf(out, "engines.comfyui is set to none, so the nodes install no ComfyUI for it: set a version "+
|
|
"(llmgw ctl config set engines.comfyui %s) or name one installed by hand in agent.yaml\n", version)
|
|
}
|
|
return nil
|
|
}
|
|
fmt.Fprintf(out, "Nodes with an NVIDIA GPU install ComfyUI %s for it, about 10 GB each:\n", version)
|
|
if err := doc.SetYAML(path, version); err != nil {
|
|
return err
|
|
}
|
|
b, err := doc.Bytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return putSettings(cmd, client, cur, b, "ComfyUI for "+model)
|
|
}
|