Files
ImaGen/cmd/imagen/main.go
mAi 8435817ce1 mAi: #10 - multi-model backend expansion (workflow templates + compare harness)
Path 1 architecture: one comfyui adapter, workflows as data.

- workflow_template.go: embed.FS + token substitution with type-preserving
  whole-value placeholders. ${prompt} → string, ${seed} → int64,
  ${cfg} → float64 — no JSON round-tripping. Partial matches ignored.
- comfyui.go: refactored to load workflow from embedded FS or filesystem
  path. Back-compat preserved: workflow: defaults to flux1-schnell.
- workflows/{flux1-schnell,flux2-klein,sd35-medium}.json — bundled
  templates. flux1-schnell migrated from hardcoded with identical node IDs.
- compare.go: new `imagen compare` subcommand. Sequential N-backend run
  (one GPU on mRock — parallel would OOM), per-backend PNG, sidecar JSON
  with per-model metadata + errors, composite contact sheet via Go image
  package (no ImageMagick dep).
- Sample config gains flux2-klein-local + sd35-medium-local instances.
- docs/backends.md: architecture rationale + per-model HF download paths
  + how to add a new bundled workflow + compare-harness reference.

Live smoke verified: compare mock + flux-schnell-local at 768×768 →
both PNGs written, sidecar JSON has workflow="flux1-schnell" + full
metadata, contact sheet renders. Worker contract (Request → Generate)
unchanged, so flexsiebels /imagine UI API surface preserved.

Tests: 11 existing comfyui + 6 new workflow_template + 5 new compare
tests, all green.

Adding a new model is now yaml + JSON, never Go.
2026-05-11 17:29:57 +02:00

88 lines
2.4 KiB
Go

// Command imagen is the model-agnostic image-generation CLI. It dispatches
// `generate`, `backends`, and `config` subcommands against backends that
// register themselves at package init time.
package main
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"syscall"
_ "mgit.msbls.de/m/ImaGen/internal/backend"
)
const helpText = `imagen — model-agnostic image generation
Usage:
imagen generate <prompt> [flags] generate one image
imagen compare <prompt> --models a,b,c [flags]
run one prompt across N backends + contact sheet
imagen worker [flags] consume the imagen.jobs queue (daemon)
imagen backends list registered backend types
imagen config init print a sample imagen.yaml on stdout
imagen config validate validate the active config
imagen serve [--addr :8080] (stub) start the HTTP server
imagen usage [--since DATE] show cost-tracking rows
imagen version print version
imagen help show this help
Run "imagen <subcommand> --help" for subcommand-specific flags.
`
// Version is overridable at link time via -ldflags '-X main.Version=...'.
var Version = "dev"
func main() {
if len(os.Args) < 2 {
fmt.Fprint(os.Stderr, helpText)
os.Exit(2)
}
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
args := os.Args[2:]
var err error
switch os.Args[1] {
case "generate":
err = runGenerate(ctx, args)
case "compare":
err = runCompare(ctx, args)
case "worker":
err = runWorker(ctx, args)
case "backends":
err = runBackends(args)
case "config":
err = runConfig(args)
case "serve":
err = runServe(args)
case "usage":
err = runUsage(ctx, args)
case "version", "-v", "--version":
fmt.Println(Version)
case "help", "-h", "--help":
fmt.Print(helpText)
default:
fmt.Fprintf(os.Stderr, "imagen: unknown subcommand %q\n\n%s", os.Args[1], helpText)
os.Exit(2)
}
if err != nil {
fmt.Fprintln(os.Stderr, "imagen:", err)
var u *userError
if errors.As(err, &u) {
os.Exit(2)
}
os.Exit(1)
}
}
// userError signals "user did the wrong thing" so we exit 2 rather than 1.
type userError struct{ msg string }
func (u *userError) Error() string { return u.msg }
func userErr(format string, a ...any) error {
return &userError{msg: fmt.Sprintf(format, a...)}
}