A model's catalog entry now says what its workflow takes and makes:
comfyui.inputs, each a placeholder of its name with a type (text, int,
float, bool, choice, image, mask, audio, video, file), bounds, choices
and a default; and comfyui.output, which gains video, audio and files
beside pictures and meshes. POST /v1/workflows/runs with {model, inputs}
checks a request against them before a job is written — a required input
left out, an unknown one, a number out of bounds, a song sent as a
picture, another owner's file id are each refused with what was wrong —
and the job worker replays it to a replica, where the files go into
ComfyUI's input directory, the workflow is filled and run, and the first
file of the model's output kind is the job's result. /v1/models lists
each ComfyUI model's workflow, so a client can build its form.
A PNG result carries the licence in an iTXt chunk, as a GLB does in
asset.extras. A file whose server named no type is typed by its name
before its bytes, so a WAV is audio/wav, not audio/wave.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
48 lines
2.2 KiB
Go
48 lines
2.2 KiB
Go
package catalog
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestAWorkflowsInputsAreCheckedWhenTheCatalogIsBuilt(t *testing.T) {
|
|
one := 1.0
|
|
zero := 0.0
|
|
run := func(out string, inputs ...Input) Model {
|
|
return comfyModel("/w/ace.safetensors", &ComfyUI{Workflow: "w.json", Output: out, Inputs: inputs})
|
|
}
|
|
cat, err := New([]Model{run(OutputAudio,
|
|
Input{Name: "tags", Type: InputText, Required: true},
|
|
Input{Name: "strength", Type: InputFloat, Min: &zero, Max: &one, Default: 0.5},
|
|
Input{Name: "style", Type: InputChoice, Choices: []string{"pop"}},
|
|
Input{Name: "clip", Type: InputVideo, MaxMB: 512})})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m, _ := cat.Resolve("flux")
|
|
if !m.RunsWorkflows() || m.MakesImages() || m.MakesMeshes() || m.ComfyOutput() != OutputAudio || len(m.ComfyInputs()) != 4 {
|
|
t.Errorf("an audio model: runs %v pictures %v meshes %v output %q", m.RunsWorkflows(), m.MakesImages(), m.MakesMeshes(), m.ComfyOutput())
|
|
}
|
|
if m.ComfyInputs()[3].MaxBytes() != 512<<20 || (Input{Type: InputImage}).MaxBytes() != 16<<20 {
|
|
t.Error("a file input's bound")
|
|
}
|
|
for name, c := range map[string]struct {
|
|
m Model
|
|
want string
|
|
}{
|
|
"an unknown kind": {run("hologram"), "not one of pictures"},
|
|
"video, no workflow": {comfyModel("/w/x", &ComfyUI{Output: OutputVideo}), "makes video names its workflow"},
|
|
"a capital name": {run(OutputAudio, Input{Name: "Tags", Type: InputText}), "lowercase"},
|
|
"an unknown type": {run(OutputAudio, Input{Name: "x", Type: "colour"}), "not one of text"},
|
|
"a choice of nothing": {run(OutputAudio, Input{Name: "x", Type: InputChoice}), "no choices"},
|
|
"min above max": {run(OutputAudio, Input{Name: "x", Type: InputInt, Min: &one, Max: &zero}), "min above max"},
|
|
"required and default": {run(OutputAudio, Input{Name: "x", Type: InputInt, Required: true, Default: 3}), "one or the other"},
|
|
"a file's default": {run(OutputAudio, Input{Name: "x", Type: InputImage, Default: "a.png"}), "cannot have a default"},
|
|
"twice": {run(OutputAudio, Input{Name: "x", Type: InputText}, Input{Name: "x", Type: InputInt}), "declared twice"},
|
|
} {
|
|
if _, err := New([]Model{c.m}); err == nil || !strings.Contains(err.Error(), c.want) {
|
|
t.Errorf("%s: %v", name, err)
|
|
}
|
|
}
|
|
}
|