Files
Faych 5fa366d4d6 feat: Migrating the project into ComfyUI native style (#42)
* docs: design ComfyUI-native BAGEL migration

* docs: plan ComfyUI-native BAGEL migration

* docs: align BAGEL migration with custom node layout

* docs: define BAGEL test environment constraints

* docs: split BAGEL nodes by responsibility

* docs: retain flat custom node entrypoints

* feat: add converted BAGEL runtime loader

* feat: register native BAGEL ComfyUI nodes

* docs: document native BAGEL workflows

* fix: add custom node root to import path

* fix: log skipped BAGEL model discovery entries

* fix: prefer BAGEL model folder for native checkpoints

* feat: add BAGEL sidecar metadata helper

* fix: list BAGEL safetensors without metadata precheck

* fix: preserve BAGEL extra model paths

* fix: set Qwen token ids for BAGEL config fallback

* fix: decouple default Qwen RoPE from transformers registry

* feat: add streaming BAGEL metadata embedder

* feat: add free Colab metadata release workflow

* feat: describe BAGEL variant release metadata

* feat: prepare remaining BAGEL metadata releases

* fix: declare Echo-4o release as fp16

* fix: validate BAGEL latent image geometry

* fix: align BAGEL edit workflow preprocessing

* docs: add native BAGEL workflow figures

* chore: prepare 1.1.0 release contents

* docs: finalize release README and metadata validation

* fix: restore native BAGEL parity and lifecycle

* fix: align native controls with BAGEL app

* fix: migrate native thinking workflow controls

* docs: update native workflow examples

* fix: mark legacy nodes as deprecated
2026-07-31 01:56:08 +01:00

127 lines
6.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Native BAGEL text-to-image node. Returns a ComfyUI LATENT (VAE-decoupled)."""
from __future__ import annotations
import copy
import comfy.model_management as model_management
import torch
from .modeling.bagel.runtime import (
generate_latent,
generate_text,
init_gen_context,
update_context_text,
validate_bagel_image_shape,
)
from .nodes_common import (
GEN_THINK_SYSTEM_PROMPT,
apply_seed,
build_handle,
require_bagel_capability,
)
class BAGELTextToImage:
"""Generate an image (standard FLUX ``LATENT``) from a text prompt.
The node never loads or accepts a VAE. The returned latent is decoded
downstream by the official ``VAEDecode`` node using the FLUX AE.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("BAGEL_MODEL", {"tooltip": "Native BAGEL model from BAGEL Model Loader."}),
"prompt": ("STRING", {"multiline": True, "default": "A female cosplayer portraying an ethereal fairy or elf, wearing a flowing dress made of delicate fabrics in soft, mystical colors like emerald green and silver. She has pointed ears, a gentle, enchanting expression, and her outfit is adorned with sparkling jewels and intricate patterns. The background is a magical forest with glowing plants, mystical creatures, and a serene atmosphere.", "tooltip": "Text prompt for image generation."}),
"width": ("INT", {"default": 1024, "min": 256, "max": 1024, "step": 16, "tooltip": "Output width. This is the ComfyUI equivalent of the official app's image-ratio preset; use 16-pixel aligned dimensions."}),
"height": ("INT", {"default": 1024, "min": 256, "max": 1024, "step": 16, "tooltip": "Output height. This is the ComfyUI equivalent of the official app's image-ratio preset; use 16-pixel aligned dimensions."}),
"cfg_text_scale": ("FLOAT", {"default": 4.0, "min": 1.0, "max": 8.0, "step": 0.1, "tooltip": "Controls how strongly BAGEL follows the text prompt; the official app recommends 4.0–8.0."}),
"cfg_interval": ("FLOAT", {"default": 0.4, "min": 0.0, "max": 1.0, "step": 0.1, "tooltip": "Start of the CFG interval. The end is fixed at 1.0, matching the official app."}),
"timestep_shift": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 5.0, "step": 0.5, "tooltip": "Shifts denoising-step allocation: higher favours layout, lower favours detail."}),
"num_timesteps": ("INT", {"default": 50, "min": 10, "max": 100, "step": 5, "tooltip": "Total denoising steps."}),
"cfg_renorm_min": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.1, "tooltip": "CFG-Renorm minimum. 1.0 disables CFG-Renorm."}),
"cfg_renorm_type": (["global", "local", "text_channel"], {"default": "global", "tooltip": "CFG-Renorm method. global is the official text-to-image default."}),
"show_thinking": ("BOOLEAN", {"default": False, "tooltip": "Generate and return the model planning text before image sampling."}),
"max_think_tokens": ("INT", {"default": 1024, "min": 64, "max": 4096, "step": 64, "tooltip": "Maximum planning tokens when Thinking is enabled."}),
"do_sample": ("BOOLEAN", {"default": False, "tooltip": "Enable sampling for planning-text generation when Thinking is enabled."}),
"text_temperature": ("FLOAT", {"default": 0.3, "min": 0.1, "max": 1.0, "step": 0.1, "tooltip": "Planning-text randomness when Thinking is enabled."}),
"seed": ("INT", {"default": 0, "min": 0, "max": 1000000, "step": 1, "tooltip": "0 leaves the seed unset, matching the official app; positive values are reproducible."}),
}
}
RETURN_TYPES = ("LATENT", "STRING")
RETURN_NAMES = ("latent", "reasoning")
FUNCTION = "generate"
CATEGORY = "BAGEL/Generation"
@classmethod
def VALIDATE_INPUTS(cls, width, height, **_kwargs):
"""Reject externally supplied dimensions that cannot form BAGEL patches."""
for name, value in (("width", width), ("height", height)):
if not 256 <= value <= 1024:
return f"{name} must be between 256 and 1024"
if value % 16:
return f"{name} must be divisible by 16"
return True
def generate(self, model, prompt, width, height, cfg_text_scale, cfg_interval,
timestep_shift, num_timesteps, cfg_renorm_min, cfg_renorm_type,
show_thinking, max_think_tokens, do_sample, text_temperature, seed):
require_bagel_capability(model, "text_to_image")
model_management.load_models_gpu([model])
handle = build_handle(model)
m = handle["model"]
device = next(m.parameters()).device
validate_bagel_image_shape(m, (height, width))
apply_seed(seed)
with torch.autocast(device_type=device.type, dtype=torch.bfloat16):
gen = init_gen_context(m)
cfg_text = init_gen_context(m)
cfg_img = init_gen_context(m)
if show_thinking:
gen = update_context_text(handle, GEN_THINK_SYSTEM_PROMPT, gen)
cfg_img = update_context_text(handle, GEN_THINK_SYSTEM_PROMPT, cfg_img)
# Preserve InterleaveInferencer's text ordering. cfg_text snapshots
# the context before the user prompt; cfg_img carries text only.
cfg_text = copy.deepcopy(gen)
gen = update_context_text(handle, prompt, gen)
cfg_img = update_context_text(handle, prompt, cfg_img)
reasoning = ""
if show_thinking:
reasoning = generate_text(
handle, gen, max_length=max_think_tokens, do_sample=do_sample,
temperature=text_temperature,
)
gen = update_context_text(handle, reasoning, gen)
latent = generate_latent(
handle, gen, cfg_text, cfg_img, (height, width),
cfg_text_scale=cfg_text_scale,
# T2I has no image-conditioning widget in the official app;
# retain inferencer.py's fixed default for this branch.
cfg_img_scale=1.5,
cfg_interval=(cfg_interval, 1.0),
cfg_renorm_min=cfg_renorm_min,
cfg_renorm_type=cfg_renorm_type,
num_timesteps=num_timesteps,
timestep_shift=timestep_shift,
)
return (latent, reasoning)
NODE_CLASS_MAPPINGS = {
"BAGELTextToImage": BAGELTextToImage,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"BAGELTextToImage": "BAGEL Text to Image",
}
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]