First step of the model-agnostic image-generation framework. Lands the
plumbing other components (skill, ComfyUI/Replicate adapters, agents)
will plug into:
- internal/backend: Backend interface (Request/Result), thread-safe
Registry with init-time Register, plus a Mock reference adapter that
emits a deterministic gradient PNG for smoke tests.
- internal/config: YAML loader for ~/.config/imagen.yaml. Framework owns
default_backend + output settings + a per-backend block; each adapter
owns the schema below its own block via BackendSpec.Raw.
- internal/output: filename templating ({date}/{time}/{slug}/{seed}/
{backend}/{ext}), JSON metadata sidecar, --output override path.
- internal/prompt: embedded styles.yaml, style-preset suffix application.
- internal/server: 501 stub — HTTP surface lands in a follow-up issue.
- cmd/imagen: generate / backends / config (init|validate|path) / serve
/ version subcommands. Stdlib-only flag parsing with a small helper to
honour positional prompt args ahead of flags (matches the issue spec).
- Tests for output (slug, naming template, sidecar), backend (mock PNG
validity + determinism, registry build + duplicate panic), config
(round-trip + validation), prompt (style apply + unknown-style error).
- CLAUDE.md, README.md, docs/architecture.md, docs/usage.md, Makefile.
Acceptance criteria from #211:
1. go build ./... — clean
2. imagen backends — lists registered backends, exits 0
3. imagen generate "test prompt" --backend mock --output /tmp/x.png —
writes a 1024x1024 PNG plus an x.png.json sidecar
4. imagen config init | imagen config validate — round-trips cleanly
5. CLAUDE.md "Adding a new adapter" — six-step recipe
64 lines
1.5 KiB
Go
64 lines
1.5 KiB
Go
// Package prompt enriches a raw user prompt before it reaches a backend. The
|
|
// only enrichment today is style-preset suffixes; future passes can add
|
|
// negative-prompt resolution, safety filters, etc.
|
|
package prompt
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
//go:embed styles.yaml
|
|
var stylesYAML []byte
|
|
|
|
type stylesFile struct {
|
|
Styles map[string]string `yaml:"styles"`
|
|
}
|
|
|
|
var defaultStyles map[string]string
|
|
|
|
func init() {
|
|
var f stylesFile
|
|
if err := yaml.Unmarshal(stylesYAML, &f); err != nil {
|
|
panic(fmt.Sprintf("prompt: parse embedded styles.yaml: %v", err))
|
|
}
|
|
defaultStyles = f.Styles
|
|
}
|
|
|
|
// Styles returns the names of registered style presets, sorted.
|
|
func Styles() []string {
|
|
out := make([]string, 0, len(defaultStyles))
|
|
for k := range defaultStyles {
|
|
out = append(out, k)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// HasStyle reports whether name is a known preset.
|
|
func HasStyle(name string) bool {
|
|
_, ok := defaultStyles[name]
|
|
return ok
|
|
}
|
|
|
|
// Apply returns the prompt with the named style preset appended. Unknown
|
|
// styles return an error rather than silently passing the prompt through —
|
|
// surprising the caller with missing style is worse than a hard error.
|
|
func Apply(prompt, style string) (string, error) {
|
|
if style == "" {
|
|
return prompt, nil
|
|
}
|
|
suffix, ok := defaultStyles[style]
|
|
if !ok {
|
|
return "", fmt.Errorf("unknown style %q, available: %v", style, Styles())
|
|
}
|
|
if strings.TrimSpace(prompt) == "" {
|
|
return suffix, nil
|
|
}
|
|
return prompt + ", " + suffix, nil
|
|
}
|