main b9d6a614198b cached
25 files
6.9 MB
1.8M tokens
154 symbols
1 requests
Download .txt
Showing preview only (7,211K chars total). Download the full file or copy to clipboard to get everything.
Repository: robertvoy/ComfyUI-Flux-Continuum
Branch: main
Commit: b9d6a614198b
Files: 25
Total size: 6.9 MB

Directory structure:
gitextract_si5rgbko/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── publish_action.yml
├── LICENSE
├── README.md
├── __init__.py
├── misc.py
├── pyproject.toml
├── web/
│   ├── getsetorder.js
│   ├── guidanceversions.js
│   ├── help.js
│   ├── hint.js
│   ├── imagedisplay.js
│   ├── imagetransfershortcut.js
│   ├── impactpackfix.js
│   ├── outputgetnode.js
│   ├── samplerversions.js
│   ├── tabs.js
│   └── textversions.js
└── workflow/
    ├── Flux+ 1.3_release.json
    ├── Flux+ 1.4.4_release.json
    ├── Flux+ 1.4.5_release.json
    ├── Flux+ 1.6.4_release.json
    ├── Flux+ 1.7.0_release.json
    ├── Flux+ 1.7.1_beta.json
    └── Flux+ Light 1.0.0_release.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: robertvoy
buy_me_a_coffee: robertvoy


================================================
FILE: .github/workflows/publish_action.yml
================================================
name: Publish to Comfy registry
on:
  workflow_dispatch:
  push:
    branches:
      - main
    paths:
      - "pyproject.toml"

jobs:
  publish-node:
    name: Publish Custom Node to registry
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v4
      - name: Publish Custom Node
        uses: Comfy-Org/publish-node-action@main
        with:
          personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2024 Robert

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# ComfyUI Flux Continuum - Modular Interface

![banner_2](https://github.com/user-attachments/assets/5681868a-002d-46a4-9fc2-7455af728821)

> A modular workflow that brings order to the chaos of image generation pipelines.

📺 [Watch the Tutorial](https://www.youtube.com/watch?v=cjWuPcRZ1j0)

## Updates
- **1.7.0:** Enhanced workflow and usability update 📺 [Watch Video Update](https://www.youtube.com/watch?v=e_7cYbBwjFc)
  - **Image Transfer Shortcut**: Use `Ctrl+Shift+C` to copy images from Img Preview to Img Load (customizable in Settings > Keybinding > Image Transfer)
  - **Configurable Model Router**: Dynamic model selection with customizable JSON mapping for flexible workflows
  - **Hint System**: Interactive hint nodes provide contextual help throughout the workflow
  - **Crop & Stitch**: Enhanced inpainting/outpainting with automatic crop and stitch functionality
  - **Smart Guidance**: Automatic guidance value of 30 for inpainting, outpainting, canny, and depth operations
  - **TeaCache Integration**: Optional speed boost for all outputs (trades some quality for performance)
  - **Improved Preprocessor Preview Logic**: CN Input is used for previewing when ControlNet strength > 0, otherwise uses Img Load
  - **Workflow Reorganization**: Modules reordered for more logical flow
  - **Redux Naming**: IP Adapter renamed to Redux for consistency with BFL terminology

<details>
<summary><b>📋 Older Changelog</b> (Click to expand)</summary>

- **1.6.4:** ControlNet Union Pro v2 Update 📺 [Watch Video Update](https://www.youtube.com/watch?v=oh1P_4d9_HI)
  - ControlNet Union Pro v2: Integrated the new Depth, Canny, OpenPose ControlNets
  - New canny preprocessor control
  - Removed the input preview tab
  - Better upscaling controls
  - New Redux (IPAdapter) implementation

- **Flux Continuum Light 1.0.0:**
  - Light version of the workflow with all the basic functions that requires only the FLUX.1-dev model. [Download](https://github.com/robertvoy/ComfyUI-Flux-Continuum/blob/main/workflow/Flux%2B%20Light%201.0.0_release.json) 

- **1.4.2:** Black Forest Labs tools update
  - Black Forest Labs tools: Integrated the new Redux, Depth, Canny, Fill models
  - Preview Panel: Preview all your image inputs and masks at a glance
  - Mask Feather Control: Feather the mask using one control
  - Text Versions: Add more tabs via properties
  - New Nodes: *FluxContinuumModelRouter*, *OutputGet*, *OutputGetString*, *OutputTextDisplay*, *DrawTextConfig* and *ConfigurableDrawText*

</details>

## Overview

ComfyUI Flux Continuum revolutionises workflow management through a thoughtful dual-interface design:

- **Front-end**: A consistent control interface shared across all modules
- **Back-end**: Powerful, modular architecture for customisation and experimentation

## ✨ Core Features

> Perfect for creators who want a consistent, streamlined experience across all image generation tasks, while maintaining the power to customize when needed.

- **Unified Control Interface**
  - Single set of controls affects all relevant modules
  - Smart guidance adjustment based on operation type
  - Consistent experience across all generation tasks

- **Smart Workflow Management**
  - Only activates nodes and models required for current task
  - Toggle between different output types seamlessly
  - Efficiently handles resource allocation
  - Optional TeaCache for speed optimization

- **Universal Model Integration**
  - LoRAs, ControlNets and Redux work across all output modules
  - Seamless Black Forest Labs model support
  - Configurable model routing for custom workflows

- **Enhanced Usability**
  - Interactive hint system for contextual help
  - Quick image transfer with keyboard shortcut
  - Intelligent preprocessing based on control values
  - Crop & stitch for seamless inpainting/outpainting

---

## 🚀 Quick Start  
📺 **New to Flux Continuum?** [Watch the tutorial first](https://www.youtube.com/watch?v=cjWuPcRZ1j0)  
1. Clone repo to the custom nodes folder
```shell
git clone https://github.com/robertvoy/ComfyUI-Flux-Continuum
```
2. [Download](https://github.com/robertvoy/ComfyUI-Flux-Continuum/blob/main/workflow/Flux%2B%201.7.0_release.json) and import the workflow into ComfyUI
3. Install missing custom nodes using the ComfyUI Manager
4. Configure your models in the config panel (press `2` to access)
5. Download any missing models (see Model Downloads section below)
6. Return to the main interface (press `1`)
7. Select `txt2img` from the output selector (top left corner)
8. Run the workflow to generate your first image

---

## 🎯 Usage Guide

### Output Selection
The workflow is controlled by the **Output selector** in the top-left corner. Select your desired output and all relevant controls will automatically apply.

### Key Controls

**🎨 Main Generation**
- **Prompt**: Your text description for generation
- **Denoise**: Controls strength for img2img operations (0 = no change, 1 = completely new)
- **Steps**: Number of sampling steps (higher = more detail, slower)
- **Guidance**: How closely to follow the prompt (automatically set to 30 for inpainting/outpainting/canny/depth)
- **TeaCache**: Toggle for speed boost (some quality trade-off)

**🖼️ Input Images**
- **Img Load**: Primary image for all img2img operations (inpainting, outpainting, detailer, upscaling)
- **CN Input**: Source for ControlNet preprocessing
- **Redux 1-3**: Up to 3 reference images for style transfer (use very low strength values)
- **Tip**: Use `Ctrl+Shift+C` to quickly copy from Img Preview to Img Load

**🎛️ ControlNet & Redux**
- ControlNets activate when strength > 0
- When CN strength > 0, preprocessor uses CN Input; otherwise uses Img Load
- Preview preprocessor results by selecting corresponding output (e.g., "preprocessor canny")
- Redux sliders control each Redux input individually (1 = Redux 1, etc.)

**Recommended ControlNet Values:**
- **Canny**: Strength=0.7, End=0.8
- **Depth**: Strength=0.8, End=0.8
- **Pose**: Strength=0.9, End=0.65

**🔧 Image Processing**
- Resize, crop, sharpen, color correct, or pad images
- Preview results with "imgload prep" output
- Bypass nodes after processing to avoid reprocessing (`Ctrl+B`)

**⬆️ Upscaling**
- **Resolution Multiply**: Multiplies image resolution after any preprocessing
- **Upscale Model**: Choose your upscaling model (recommended: 4xNomos8kDAT)
- 📺 [Watch Upscaling Tutorial](https://www.youtube.com/watch?v=TmF3JK_1AAs)

---

## 🎯 Workflow Modules

### Main Modules 
> All modules use the same unified control interface

| Module | Description |
|--------|-------------|
| **txt2img** | Standard text-to-image generation from prompts |
| **txt2img noise injection** | Enhanced detail generation ([Learn more](https://youtu.be/tned5bYOC08?si=qfP2Sv2VOTzDK-uL&t=1335)) |
| **img2img** | Transform existing images with text prompts |
| **inpainting** | Edit specific areas with automatic crop & stitch using BFL Fill model |
| **outpainting** | Expand images beyond boundaries with smart padding and BFL Fill model |
| **canny** | Edge-guided generation using BFL Canny model |
| **depth** | Depth-guided generation using BFL Depth model |
| **detailer** | Focused refinement using mask selection |
| **ultimate upscaler** | Advanced tiled upscaling with full control |
| **upscaler** | Simple model-based upscaling |

### Utility Modules

| Module | Description |
|--------|-------------|
| **imgload prep** | Preview processed images after crop/sharpen/resize/padding |
| **preprocessor canny** | Preview canny edge detection results |
| **preprocessor depth** | Preview depth map generation |
| **preprocessor openpose** | Preview pose detection results |

---

## 🔧 Custom Nodes

> *These custom nodes were made specifically for this workflow and are required for it to work*

### Interface Enhancement Nodes

- **Hint Node**: 
  - Interactive help system throughout the workflow
  - Hover for contextual information
  - Right-click to edit hint content
  - Supports markdown formatting

- **Tabs**:
  - Space-saving node organization
  - Add tabs via properties panel
  - Compatible with most nodes
  - Special handling for unsupported nodes

- **Sliders**: 
  - Suite of pre-configured sliders
  - Optimized ranges and defaults for common operations
  - Includes: Denoise, Step, Guidance, Batch, GPU, ControlNet, Redux, and more

- **OutputGet System**:
  - Filters set nodes with prefix `Output -`
  - **OutputTextDisplay**: Visual display of selected output
  - **OutputGetString**: String output for conditional routing

- **Text Versions**:
  - Multi-tab text management (default 5 tabs)
  - Add more tabs via properties panel
  - Save different prompt versions
  - Perfect for A/B testing

- **ImageDisplay**: 
  - Base64 image display on canvas
  - Configurable via properties
  - Useful for reference images

### Workflow Control Nodes

- **Image Transfer Shortcut**: 
  - `Ctrl+Shift+C` copies from Img Preview to Img Load
  - Customizable in ComfyUI keybindings

- **Configurable Model Router**: 
  - Dynamic model selection with JSON mapping
  - Flexible routing based on conditions
  - Supports lazy loading for efficiency

- **Sampler Parameter Packer/Unpacker**: 
  - Consolidate sampler settings
  - Tabbed interface for version control

- **Image Batch Boolean**: 
  - Conditional batch processing
  - Smart second image loading

- **Configurable Draw Text**: 
  - Advanced text rendering on images
  - Configurable fonts, colors, shadows, alignment

- **Pass Nodes**: 
  - Extended pass-through for Latent, Pipe, SEGS, and Int data types
  - Maintains data flow integrity

---

## 📥 Model Downloads

### Required Models

**unet folder:**
- [flux1-dev.safetensors](https://huggingface.co/black-forest-labs/FLUX.1-dev/resolve/main/flux1-dev.safetensors)
- [flux1-depth-dev.safetensors](https://huggingface.co/black-forest-labs/FLUX.1-Depth-dev/resolve/main/flux1-depth-dev.safetensors)
- [flux1-canny-dev.safetensors](https://huggingface.co/black-forest-labs/FLUX.1-Canny-dev/resolve/main/flux1-canny-dev.safetensors)
- [flux1-fill-dev.safetensors](https://huggingface.co/black-forest-labs/FLUX.1-Fill-dev/resolve/main/flux1-fill-dev.safetensors)

> **Note**: If you don't use Canny or Depth models, you can bypass their load nodes and skip downloading them.

**vae folder:**
- [ae.safetensors](https://huggingface.co/black-forest-labs/FLUX.1-dev/resolve/main/ae.safetensors)

**clip folder:**
- [t5xxl_fp8_e4m3fn.safetensors](https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp8_e4m3fn.safetensors)
- [clip_l.safetensors](https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/clip_l.safetensors)

**style_models folder:**
- [flux1-redux-dev.safetensors](https://huggingface.co/black-forest-labs/FLUX.1-Redux-dev/resolve/main/flux1-redux-dev.safetensors)

**clip_vision folder:**
- [sigclip_vision_patch14_384.safetensors](https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors)

**controlnet/FLUX folder:**
- [FLUX.1-dev-ControlNet-Union-Pro-2.0.safetensors](https://huggingface.co/Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro-2.0/resolve/main/diffusion_pytorch_model.safetensors) *(rename file)*

---

## 🔜 Coming Soon

- **Multi-GPU Support**: Distributed processing across multiple GPUs

---

## 🙏 Acknowledgments

Special thanks to the creators of these essential custom node packs:
- [rgthree ComfyUI Extensions](https://github.com/rgthree/rgthree-comfy)
- [ComfyUI Essentials](https://github.com/cubiq/ComfyUI_essentials)
- [ComfyUI Impact Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack)
- [ComfyUI KJNodes](https://github.com/kijai/ComfyUI-KJNodes)


================================================
FILE: __init__.py
================================================
from .misc import MISC_CLASS_MAPPINGS

# Merge both mappings into a single dictionary for the custom nodes
NODE_CLASS_MAPPINGS = {**MISC_CLASS_MAPPINGS}
WEB_DIRECTORY = "./web"

# Optional: You can also define NODE_DISPLAY_NAME_MAPPINGS if you want custom display names in the UI
NODE_DISPLAY_NAME_MAPPINGS = {
    "StepSlider": "Step Slider",
    "DenoiseSlider": "Denoise Slider",
    "GuidanceSlider": "Guidance Slider",
    "GPUSlider": "GPU Slider",
    "BatchSlider": "Batch Slider",
    "IPAdapterSlider": "IPAdapter Slider",
    "MaxShiftSlider": "Max Shift Slider",
    "ControlNetSlider": "ControlNet Slider",
    "CannySlider": "CannySlider",
    "SelectFromBatch": "Select From Batch",
    "LatentPass": "LatentPass",
    "SEGSPass": "SEGSPass",
    "PipePass": "PipePass",
    "IntPass": "IntPass",
    "TextVersions": "Text Versions",
    "ResolutionPicker": "Resolution Picker",
    "ResolutionMultiplySlider": "ResolutionMultiplySlider",
    "SamplerParameterPacker": "Sampler Parameter Packer",
    "SamplerParameterUnpacker": "Sampler Parameter Unpacker",
    "ImpactControlBridgeFix": "ImpactControlBridgeFix",
    "BooleanToEnabled": "Boolean To Enabled",
    "OutputGetString": "OutputGetString",
    "SplitVec2": "SplitVec2",
    "SplitVec3": "SplitVec3",
    "SimpleTextTruncate": "Simple Text Truncate",
    "FluxContinuumModelRouter": "Flux Continuum Model Router",
    "ConfigurableModelRouter": "Configurable Model Router",
    "ImageBatchBoolean": "Image Batch Boolean",
    "DrawTextConfig": "DrawTextConfig",
    "ConfigurableDrawText": "ConfigurableDrawText"
}

__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']

================================================
FILE: misc.py
================================================
import nodes
from server import PromptServer
import torch
import comfy.samplers
import os
import time
from PIL import Image, ImageDraw, ImageFont, ImageColor, ImageFilter
import torchvision.transforms.v2 as T
import numpy as np
import folder_paths
import numpy as np
import json
from typing import Any, Mapping, Tuple

class AnyType(str):
    def __ne__(self, __value: object) -> bool:
        return False

any_typ = AnyType("*")

class DenoiseSlider:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "value": ("FLOAT", { "display": "slider", "default": 0.5, "min": 0.0, "max": 1.0, "step": 0.001 }),
            },
        }

    RETURN_TYPES = ("FLOAT", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Sliders"
    DESCRIPTION = """Control the **denoising strength** for img2img operations, including: inpainting, ultimate upscaler and detailer.
    - A value of **1.0** means a completely new image.
    - A value of **0.0** means no change to the latent image."""

    def execute(self, value):
        return (value, )

class StepSlider:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "value": ("FLOAT", { "display": "slider", "default": 25.0, "min": 0.0, "max": 50.0, "step": 1.0 }),
            },
        }
    RETURN_TYPES = ("INT", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Sliders"
    DESCRIPTION = "Set the number of **sampling steps**. Higher values can increase detail but take longer to process."
    def execute(self, value):
        # Use round() instead of int() to ensure proper integer conversion
        return (int(round(value)), )

class BatchSlider:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "value": ("FLOAT", { "display": "slider", "default": 1.0, "min": 1.0, "max": 10.0, "step": 1.0 }),
            },
        }
    RETURN_TYPES = ("INT", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Sliders"
    DESCRIPTION = "Provides a slider for controlling batch size with range 1-10"
    def execute(self, value):
        # Use round() instead of int() to ensure proper integer conversion
        return (int(round(value)), )

class ResolutionMultiplySlider:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "value": ("FLOAT", { "display": "slider", "default": 1.0, "min": 1.0, "max": 10.0, "step": 0.1 }),
            },
        }
    RETURN_TYPES = ("FLOAT", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Sliders"
    DESCRIPTION = "Provides a slider for controlling resolution multiplication for upscaling, with range 1-10"
    def execute(self, value):
        return (value, )

class GPUSlider:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "value": ("FLOAT", { "display": "slider", "default": 1.0, "min": 1.0, "max": 4.0, "step": 1.0 }),
            },
        }
    RETURN_TYPES = ("INT", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Sliders"
    DESCRIPTION = "Provides a slider for selecting number of GPUs with range 1-4"
    def execute(self, value):
        # Use round() instead of int() to ensure proper integer conversion
        return (int(round(value)), )

class SelectFromBatch:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "value": ("FLOAT", { "display": "slider", "default": 0.0, "min": 0.0, "max": 24.0, "step": 1.0 }),
            },
        }
    RETURN_TYPES = ("INT", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Sliders"
    DESCRIPTION = "Provides a slider for selecting specific images from a batch with range 0-24"
    def execute(self, value):
        # Use round() instead of int() to ensure proper integer conversion
        return (int(round(value)), )

class GuidanceSlider:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "value": ("FLOAT", { "display": "slider", "default": 2.5, "min": -1.0, "max": 30.0, "step": 0.1 }),
            },
        }

    RETURN_TYPES = ("FLOAT", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Sliders"
    DESCRIPTION = "Higher values make the output adhere more strictly to the prompt. Select between different presets for convenience. NOTE: FLUX Continuum workflow automatically sets your guidance to 30 when you're doing inpainting, outpainting, canny, or depth operations."

    def execute(self, value):
        # Return the float value directly
        return (value, )

class MaxShiftSlider:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "value": ("FLOAT", { "display": "slider", "default": 1.15, "min": 0.0, "max": 4.0, "step": 0.05 }),
            },
        }

    RETURN_TYPES = ("FLOAT", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Sliders"
    DESCRIPTION = "Control the **maximum pixel shift**, often used to introduce variation."

    def execute(self, value):
        # Return the float value directly
        return (value, )

class ControlNetSlider:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "Strength": ("FLOAT", { "display": "slider", "default": 1, "min": 0.0, "max": 1.0, "step": 0.05 }),
                "Start": ("FLOAT", { "display": "slider", "default": 0, "min": 0.0, "max": 1.0, "step": 0.05 }),
                "End": ("FLOAT", { "display": "slider", "default": 1, "min": 0.0, "max": 1.0, "step": 0.05 }),
            },
        }

    RETURN_TYPES = ("VEC3", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Sliders"
    DESCRIPTION = """- **Strength**: The overall influence of the ControlNet.
- **Start**: The step at which the ControlNet begins to apply (as a percentage).
- **End**: The step at which the ControlNet stops applying (as a percentage)."""

    def execute(self, Strength, Start, End):
        # Return the three values as a VEC3
        return ((Strength, Start, End), )

class CannySlider:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "Low_Threshold": ("FLOAT", { "display": "slider", "default": 0.40, "min": 0.1, "max": 0.99, "step": 0.01 }),
                "High_Threshold": ("FLOAT", { "display": "slider", "default": 0.80, "min": 0.1, "max": 0.99, "step": 0.01 })
            },
        }

    RETURN_TYPES = ("VEC2", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Sliders"
    DESCRIPTION = "Provides two sliders for canny preprocessor parameters"

    def execute(self, Low_Threshold, High_Threshold):
        # Return the two values as a VEC2
        return ((Low_Threshold, High_Threshold), )

class IPAdapterSlider:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "IP1": ("FLOAT", { "display": "slider", "default": 0, "min": 0.0, "max": 1.0, "step": 0.05 }),
                "IP2": ("FLOAT", { "display": "slider", "default": 0, "min": 0.0, "max": 1.0, "step": 0.05 }),
                "IP3": ("FLOAT", { "display": "slider", "default": 0, "min": 0.0, "max": 1.0, "step": 0.05 }),
            },
        }
    
    RETURN_TYPES = ("VEC3",)
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Sliders"
    DESCRIPTION = "Control the strength of up to three different Redux inputs simultaneously."

    def execute(self, IP1, IP2, IP3):
        # Return the three values as a VEC3
        return ((IP1, IP2, IP3),)

class SEGSPass:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "SEGS": ("SEGS",),
            },
        }

    RETURN_TYPES = ("SEGS", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Utilities"

    def execute(self, SEGS):
        # Return the integer value directly
        return (SEGS, )

class PipePass:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "PIPE_LINE": ("PIPE_LINE",),
            },
        }

    RETURN_TYPES = ("PIPE_LINE", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Utilities"

    def execute(self, PIPE_LINE):
        return (PIPE_LINE, )

class LatentPass:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "latent": ("LATENT",),
            },
        }
    RETURN_TYPES = ("LATENT", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Utilities"
    
    def execute(self, latent):
        # Simply pass through the latent data
        return (latent, )

class IntPass:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "INT": ("INT",),
            },
        }
    RETURN_TYPES = ("INT", )
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Utilities"
    
    def execute(self, INT):
        # Simply pass through an integer
        return (INT, )
        
class ResolutionPicker:
    @classmethod
    def INPUT_TYPES(s):
        return {"required": {
            "resolution": (["704x1408 (0.5)","704x1344 (0.52)","768x1344 (0.57)","768x1280 (0.6)","832x1216 (0.68)","832x1152 (0.72)","896x1152 (0.78)","896x1088 (0.82)","960x1088 (0.88)","960x1024 (0.94)","1024x1024 (1.0)","1024x960 (1.07)","1088x960 (1.13)","1088x896 (1.21)","1152x896 (1.29)","1152x832 (1.38)","1216x832 (1.46)","1280x768 (1.67)","1344x768 (1.75)","1344x704 (1.91)","1408x704 (2.0)","1472x704 (2.09)","1536x640 (2.4)","1600x640 (2.5)","1664x576 (2.89)","1728x576 (3.0)",], {"default": "1024x1024 (1.0)"}),
            }}
    RETURN_TYPES = (["704x1408 (0.5)","704x1344 (0.52)","768x1344 (0.57)","768x1280 (0.6)","832x1216 (0.68)","832x1152 (0.72)","896x1152 (0.78)","896x1088 (0.82)","960x1088 (0.88)","960x1024 (0.94)","1024x1024 (1.0)","1024x960 (1.07)","1088x960 (1.13)","1088x896 (1.21)","1152x896 (1.29)","1152x832 (1.38)","1216x832 (1.46)","1280x768 (1.67)","1344x768 (1.75)","1344x704 (1.91)","1408x704 (2.0)","1472x704 (2.09)","1536x640 (2.4)","1600x640 (2.5)","1664x576 (2.89)","1728x576 (3.0)",],)
    RETURN_NAMES = ("resolution",)
    FUNCTION = "execute"
    CATEGORY = "Flux-Continuum/Utilities"
    DESCRIPTION = "Provides a convenient dropdown menu to select from a list of common, pre-calculated image **resolutions** and their aspect ratios. Perfect for FLUX."

    def execute(self, resolution):
        return (resolution,)

class SamplerParameterPacker:
    CATEGORY = 'Flux-Continuum/Utilities'
    RETURN_TYPES = ("SAMPLER_PARAMS",)
    RETURN_NAMES = ("sampler_params",)
    FUNCTION = "pack_parameters"
    DESCRIPTION = "Packs sampler and scheduler selections into a single parameter object for efficient passing"
    
    @classmethod
    def INPUT_TYPES(cls):
        return {"required": {
            "sampler": (comfy.samplers.KSampler.SAMPLERS,),
            "scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
        }}
    
    def pack_parameters(self, sampler, scheduler):
        return ((sampler, str(sampler), scheduler, str(scheduler)),)

class SamplerParameterUnpacker:
    CATEGORY = 'Flux-Continuum/Utilities'
    RETURN_TYPES = (comfy.samplers.KSampler.SAMPLERS, "STRING", any_typ, "STRING",)
    RETURN_NAMES = ("sampler", "sampler_name", "scheduler", "scheduler_name",)
    FUNCTION = "unpack_parameters"
    DESCRIPTION = "Unpacks previously packed sampler parameters back into individual components"
    
    @classmethod
    def INPUT_TYPES(cls):
        return {"required": {
            "sampler_params": ("SAMPLER_PARAMS",),
        }}
    
    def unpack_parameters(self, sampler_params):
        sampler, sampler_name, scheduler, scheduler_name = sampler_params
        return (sampler, sampler_name, scheduler, scheduler_name,)

class TextVersions:
    @classmethod
    def INPUT_TYPES(s):
        return {"required": {
                    "text": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": True}),
                },
        }
    
    RETURN_TYPES = ("STRING",)
    RETURN_NAMES = ("text",)
    FUNCTION = "process_text"
    CATEGORY = "Flux-Continuum/Utilities"
    DESCRIPTION = "Provides a multi-tab interface for managing different versions of text input"

    def __init__(self):
        self.order = 0

    def process_text(self, text):
        return (text,)

def workflow_to_map(workflow):
    nodes_map = {}
    links = {}

    # Create a lookup table for links and nodes
    for links_data in workflow['links']:
        links[links_data[0]] = links_data[1:]

    for node_data in workflow['nodes']:
        nodes_map[str(node_data['id'])] = node_data

    return nodes_map, links

def is_execution_model_version_supported():
    try:
        import comfy_execution
        return True
    except:
        return False


class ImpactControlBridgeFix:
    @classmethod
    def INPUT_TYPES(cls):
        return {"required": {
                      "value": (any_typ,),
                      "mode": ("BOOLEAN", {"default": True, "label_on": "Active", "label_off": "Stop/Mute/Bypass"}),
                      "behavior": (["Stop", "Mute", "Bypass"], ),
                    },
                "hidden": {"unique_id": "UNIQUE_ID", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}
                }

    FUNCTION = "doit"
    CATEGORY = "Flux-Continuum/Utilities"
    RETURN_TYPES = (any_typ,)
    RETURN_NAMES = ("value",)
    OUTPUT_NODE = True

    DESCRIPTION = ("When behavior is Stop and mode is active, the input value is passed directly to the output.\n"
                   "When behavior is Mute/Bypass and mode is active, the node connected to the output is changed to active state.\n"
                   "When behavior is Stop and mode is Stop/Mute/Bypass, the workflow execution of the current node is halted.\n"
                   "When behavior is Mute/Bypass and mode is Stop/Mute/Bypass, the node connected to the output is changed to Mute/Bypass state.")

    @classmethod
    def IS_CHANGED(self, value, mode, behavior="Stop", unique_id=None, prompt=None, extra_pnginfo=None):
        if behavior == "Stop":
            return value, mode, behavior
        
        try:
            if prompt and 'extra_data' in prompt and 'extra_pnginfo' in prompt['extra_data']:
                workflow = prompt['extra_data']['extra_pnginfo'].get('workflow')
                if workflow:
                    nodes_map, links = workflow_to_map(workflow)
                    next_nodes = []
                    for link in nodes_map[unique_id]['outputs'][0]['links']:
                        node_id = str(links[link][2])
                        if node_id in nodes_map:
                            next_nodes.append(node_id)
                    return next_nodes
        except:
            pass
            
        return 0

    def doit(self, value, mode, behavior="Stop", unique_id=None, prompt=None, extra_pnginfo=None):
        # Check for execution model support
        if is_execution_model_version_supported():
            from comfy_execution.graph import ExecutionBlocker
        else:
            print("[Impact Pack] ImpactControlBridge: ComfyUI is outdated. The 'Stop' behavior cannot function properly.")

        # Handle Stop behavior
        if behavior == "Stop":
            if mode:
                return (value, )
            else:
                return (ExecutionBlocker(None), )

        # Handle other behaviors
        try:
            # Validate extra_pnginfo
            if not extra_pnginfo or not isinstance(extra_pnginfo, dict) or 'workflow' not in extra_pnginfo:
                return (value, )

            workflow_nodes, links = workflow_to_map(extra_pnginfo['workflow'])
            
            # Initialize node lists
            active_nodes = []
            mute_nodes = []
            bypass_nodes = []

            node_outputs = workflow_nodes.get(unique_id, {}).get('outputs', [])
            if not node_outputs:
                return (value, )

            output_links = node_outputs[0].get('links', [])
            
            for link in output_links:
                try:
                    node_id = str(links[link][2])
                    next_nodes = []
                    if node_id in workflow_nodes:
                        next_nodes.append(node_id)

                    for next_node_id in next_nodes:
                        node_mode = workflow_nodes[next_node_id].get('mode', 0)
                        
                        if node_mode == 0:
                            active_nodes.append(next_node_id)
                        elif node_mode == 2:
                            mute_nodes.append(next_node_id)
                        elif node_mode == 4:
                            bypass_nodes.append(next_node_id)
                except:
                    continue

            # Handle mode-specific behavior
            if mode:
                # active
                should_be_active_nodes = mute_nodes + bypass_nodes
                if should_be_active_nodes:
                    PromptServer.instance.send_sync("impact-bridge-continue", 
                                                  {"node_id": unique_id, 
                                                   'actives': list(should_be_active_nodes)})
                    nodes.interrupt_processing()

            elif behavior == "Mute" or behavior == True:
                # mute
                should_be_mute_nodes = active_nodes + bypass_nodes
                if should_be_mute_nodes:
                    PromptServer.instance.send_sync("impact-bridge-continue", 
                                                  {"node_id": unique_id, 
                                                   'mutes': list(should_be_mute_nodes)})
                    nodes.interrupt_processing()

            else:
                # bypass
                should_be_bypass_nodes = active_nodes + mute_nodes
                if should_be_bypass_nodes:
                    PromptServer.instance.send_sync("impact-bridge-continue", 
                                                  {"node_id": unique_id, 
                                                   'bypasses': list(should_be_bypass_nodes)})
                    nodes.interrupt_processing()

        except Exception as e:
            print(f"[Impact Pack] Error in ImpactControlBridge: {str(e)}")
            
        return (value, )

class BooleanToEnabled:
    """Convert boolean value to enabled string format"""
    def __init__(self):
        pass

    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "BOOLEAN": ("BOOLEAN",),
            },
        }

    RETURN_TYPES = (["true", "false", "remote"],)  # Match the exact format from RemoteQueueWorker
    RETURN_NAMES = ("enabled",)
    FUNCTION = "convert"
    CATEGORY = "Flux-Continuum/Utilities"
    TITLE = "Boolean to Enabled"
    DESCRIPTION = "Converts boolean values to 'true'/'false'/'remote' strings for ComfyUI_NetDist"

    def convert(self, BOOLEAN):
        # Convert boolean to appropriate string value
        return ("true" if BOOLEAN else "false",)

class OutputGetString:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {  
              
            },
            "hidden": {
                "unique_id": "UNIQUE_ID",
                "prompt": "PROMPT",
                 "title": ("STRING", {"default": ""})
            }
        }
    
    RETURN_TYPES = ("STRING",)
    RETURN_NAMES = ("STRING",)
    FUNCTION = "process"
    CATEGORY = "Flux-Continuum/Utilities"
    OUTPUT_NODE = True
    
    def process(self, title, unique_id, prompt):
        title = title[len("Output - "):]
        return (title,)

 

# Type definition for Vec3
Vec3 = Tuple[float, float, float]
Vec2 = Tuple[float, float]

# Zero vector constant
VEC3_ZERO = (0.0, 0.0, 0.0)
VEC2_ZERO = (0.0, 0.0)

class SplitVec3:
    @classmethod
    def INPUT_TYPES(cls) -> Mapping[str, Any]:
        return {"required": {"a": ("VEC3", {"default": VEC3_ZERO})}}

    RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT")
    FUNCTION = "op"
    CATEGORY = "Flux-Continuum/Utilities"
    DESCRIPTION = "Splits a vector3 input into its three individual float components"

    def op(self, a: Vec3) -> tuple[float, float, float]:
        return (a[0], a[1], a[2])

class SplitVec2:
    @classmethod
    def INPUT_TYPES(cls) -> Mapping[str, Any]:
        return {"required": {"a": ("VEC2", {"default": (0.0, 0.0)})}}
    RETURN_TYPES = ("FLOAT", "FLOAT")
    FUNCTION = "op"
    CATEGORY = "Flux-Continuum/Utilities"
    DESCRIPTION = "Splits a vector2 input into its two individual float components"
    def op(self, a) -> tuple[float, float]:
        return (a[0], a[1])

class SimpleTextTruncate:
    def __init__(self):
        pass

    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "text": ("STRING", {"forceInput": True}),
                "word_count": ("INT", {"default": 10, "min": 0, "max": 99999999, "step": 1}),
            }
        }

    RETURN_TYPES = ("STRING",)
    RETURN_NAMES = ("TEXT",)
    FUNCTION = "truncate_words"
    CATEGORY = "Text Operations"
    DESCRIPTION = "Truncates input text to a specified number of words"

    def truncate_words(self, text, word_count):
        if text is None:
            return ("",)  # Return as a tuple
            
        words = str(text).split()
        result = ' '.join(words[:word_count])
        
        # Return as a tuple since RETURN_TYPES is defined as a tuple
        return (result,)

class FluxContinuumModelRouter:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "condition": ("STRING", {"default": ""})
            },
            "optional": {
                "flux_fill": ("MODEL", {"lazy": True}),  # Lazy load for inpainting/outpainting
                "flux_depth": ("MODEL", {"lazy": True}), # Lazy load for depth
                "flux_canny": ("MODEL", {"lazy": True}), # Lazy load for canny
                "flux_dev": ("MODEL", {"lazy": True}),   # Lazy load for default case
            }
        }
    
    RETURN_TYPES = ("MODEL",)
    FUNCTION = "route_model"
    CATEGORY = "Flux-Continuum/Utilities"
    DESCRIPTION = "For Flux Continuum workflow only. Routes model selection based on conditional input for different tasks (fill, depth, canny, dev)"

    def check_lazy_status(self, condition, flux_fill=None, flux_depth=None, flux_canny=None, flux_dev=None):
        condition = condition.lower().strip()
        needed = []
        
        # Only request the model we actually need based on the condition
        if condition in ["inpainting", "outpainting"]:
            if flux_fill is None:
                needed.append("flux_fill")
        elif condition == "depth":
            if flux_depth is None:
                needed.append("flux_depth")
        elif condition == "canny":
            if flux_canny is None:
                needed.append("flux_canny")
        else:
            if flux_dev is None:
                needed.append("flux_dev")
                
        return needed

    def route_model(self, condition, flux_fill=None, flux_depth=None, flux_canny=None, flux_dev=None):
        condition = condition.lower().strip()
        
        if condition in ["inpainting", "outpainting"]:
            print(f"ModelRouter: Condition '{condition}' matched - Selected flux_fill model")
            return (flux_fill,)
        elif condition == "depth":
            print(f"ModelRouter: Condition '{condition}' matched - Selected flux_depth model")
            return (flux_depth,)
        elif condition == "canny":
            print(f"ModelRouter: Condition '{condition}' matched - Selected flux_canny model")
            return (flux_canny,)
        else:
            return (flux_dev,)

class ConfigurableModelRouter:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                # This will be a text box widget on the node for manual input
                "condition": ("STRING", {"multiline": False, "default": "default"}),
                
                # The JSON config is also a widget on the node
                "routing_config": ("STRING", {
                    "multiline": True,
                    "default": '{\n  "default": 1,\n  "inpainting": 2,\n  "depth": 3,\n  "canny": 4\n}'
                }),
            },
            "optional": {
                "model_1": ("MODEL", {"lazy": True}),
                "model_2": ("MODEL", {"lazy": True}),
                "model_3": ("MODEL", {"lazy": True}),
                "model_4": ("MODEL", {"lazy": True}),
                "model_5": ("MODEL", {"lazy": True}),
            }
        }

    RETURN_TYPES = ("MODEL",)
    FUNCTION = "route_model"
    CATEGORY = "Flux-Continuum/Utilities"
    DESCRIPTION = """
A dynamic model router that selects one of its inputs based on a configurable JSON mapping.
How to Use:
1. **Configure Logic:** Edit the `routing_config` JSON to map condition strings (e.g., `"inpainting"`) to an input index (e.g., `2`).
2. The `"default"` key is used if no other condition matches.
"""

    # It's an instance method, so it can correctly read the widget values.
    def check_lazy_status(self, condition, routing_config, **kwargs):
        needed = []
        try:
            config = json.loads(routing_config)
            
            # Use the values from the widgets to find the target index
            target_index = config.get(condition.strip().lower(), config.get("default", 1))
            
            # Construct the name of the model input we need to load
            model_key = f"model_{target_index}"

            # If the required model hasn't been loaded yet, request it by name
            if kwargs.get(model_key) is None:
                needed.append(model_key)
        except:
            # If the JSON is invalid, do nothing.
            pass

        print(f"[Model Router Check] Condition: '{condition}', Needing to load: {needed}")
        return needed

    def route_model(self, condition, routing_config, **kwargs):
        # This logic runs after the needed model has been loaded.
        config = json.loads(routing_config)
        target_index = config.get(condition.strip().lower(), config.get("default", 1))
        model_key = f"model_{target_index}"

        # Check that the model exists and is connected
        if model_key not in kwargs or kwargs.get(model_key) is None:
            raise ValueError(f"Input '{model_key}' is required for condition '{condition}' but is not connected or loaded.")
        
        print(f"Model Router: Successfully routed to '{model_key}'")
        return (kwargs[model_key],)
        
class ImageBatchBoolean:
    @classmethod
    def INPUT_TYPES(s):
        return {
            "required": {
                "image1": ("IMAGE",),
                "image2": ("IMAGE", {"lazy": True}),  # Make image2 lazy
                "batch_enabled": ("BOOLEAN", {"default": True}),
            }
        }
    
    RETURN_TYPES = ("IMAGE",)
    FUNCTION = "batch"
    CATEGORY = "Flux-Continuum/Utilities"
    
    def check_lazy_status(self, image1, image2, batch_enabled):
        needed = []
        # Only need image2 if batching is enabled
        if image2 is None and batch_enabled:
            needed.append("image2")
        return needed
    
    def batch(self, image1, image2, batch_enabled):
        # If batching is disabled, just return the first image
        if not batch_enabled:
            return (image1,)
            
        # If batching is enabled, perform the normal batch operation
        if image1.shape[1:] != image2.shape[1:]:
            image2 = comfy.utils.common_upscale(
                image2.movedim(-1,1), 
                image1.shape[2], 
                image1.shape[1], 
                "bilinear", 
                "center"
            ).movedim(1,-1)
        
        s = torch.cat((image1, image2), dim=0)
        return (s,)

# based on ComfyUI Essentials: github.com/cubiq/ComfyUI_essentials

MAX_RESOLUTION = 2048
FONTS_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fonts")

def hex_to_rgba(hex_color):
    hex_color = hex_color.lstrip('#')
    if len(hex_color) == 6:
        r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
        return (r, g, b, 255)
    elif len(hex_color) == 8:
        r, g, b, a = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4, 6))
        return (r, g, b, a)
    else:
        raise ValueError("Invalid hex color format")

class DrawTextConfig:
    @classmethod
    def INPUT_TYPES(s):
        return {"required": {
            "font": (sorted([f for f in os.listdir(FONTS_DIR) if f.endswith('.ttf') or f.endswith('.otf')]), ),
            "size": ("INT", { "default": 56, "min": 1, "max": 9999, "step": 1 }),
            "color": ("STRING", { "multiline": False, "default": "#FFFFFF" }),
            "background_color": ("STRING", { "multiline": False, "default": "#00000000" }),
            "padding": ("INT", { "default": 20, "min": 0, "max": 500, "step": 1 }),
            "shadow_distance": ("INT", { "default": 0, "min": 0, "max": 100, "step": 1 }),
            "shadow_blur": ("INT", { "default": 0, "min": 0, "max": 100, "step": 1 }),
            "shadow_color": ("STRING", { "multiline": False, "default": "#000000" }),
            "horizontal_align": (["left", "center", "right"],),
            "vertical_align": (["top", "center", "bottom"],),
            "offset_x": ("INT", { "default": 0, "min": -MAX_RESOLUTION, "max": MAX_RESOLUTION, "step": 1 }),
            "offset_y": ("INT", { "default": 0, "min": -MAX_RESOLUTION, "max": MAX_RESOLUTION, "step": 1 }),
            "direction": (["ltr", "rtl"],),
        }}
    
    RETURN_TYPES = ("TEXT_STYLE",)
    FUNCTION = "configure"
    CATEGORY = "text"
    DESCRIPTION = "Configures text rendering parameters including font, size, color, alignment, and effects"

    def configure(self, font, size, color, background_color, padding, shadow_distance, shadow_blur, 
                 shadow_color, horizontal_align, vertical_align, offset_x, offset_y, direction):
        return ({
            "font": font,
            "size": size,
            "color": color,
            "background_color": background_color,
            "padding": padding,
            "shadow_distance": shadow_distance,
            "shadow_blur": shadow_blur,
            "shadow_color": shadow_color,
            "horizontal_align": horizontal_align,
            "vertical_align": vertical_align,
            "offset_x": offset_x,
            "offset_y": offset_y,
            "direction": direction
        },)

class ConfigurableDrawText:
    @classmethod
    def INPUT_TYPES(s):
        return {"required": {
            "TEXT": ("STRING", {"multiline": True}),
            "TEXT_STYLE": ("TEXT_STYLE",),
            "IMAGE": ("IMAGE",),
        }}
    
    RETURN_TYPES = ("IMAGE",)
    FUNCTION = "draw"
    CATEGORY = "text"
    DESCRIPTION = "Renders text onto images using previously configured text style parameters"

    def draw(self, TEXT, TEXT_STYLE, IMAGE):
        font = ImageFont.truetype(os.path.join(FONTS_DIR, TEXT_STYLE["font"]), TEXT_STYLE["size"])

        lines = TEXT.split("\n")
        if TEXT_STYLE["direction"] == "rtl":
            lines = [line[::-1] for line in lines]

        ascent, descent = font.getmetrics()
        line_spacing = ascent + descent
        text_width = max(font.getbbox(line)[2] - font.getbbox(line)[0] for line in lines)
        text_height = line_spacing * (len(lines) - 1) + ascent + descent

        IMAGE = T.ToPILImage()(IMAGE.permute([0,3,1,2])[0]).convert('RGBA')
        width = IMAGE.width
        height = IMAGE.height
        image = Image.new('RGBA', (width, height), (0,0,0,0))

        box_width = text_width + (TEXT_STYLE["padding"] * 2)
        box_height = text_height + (TEXT_STYLE["padding"] * 2)

        if TEXT_STYLE["horizontal_align"] == "left":
            box_x = TEXT_STYLE["offset_x"]
        elif TEXT_STYLE["horizontal_align"] == "center":
            box_x = (width - box_width) // 2 + TEXT_STYLE["offset_x"]
        else:  # right
            box_x = width - box_width + TEXT_STYLE["offset_x"]

        if TEXT_STYLE["vertical_align"] == "top":
            box_y = TEXT_STYLE["offset_y"]
        elif TEXT_STYLE["vertical_align"] == "center":
            box_y = (height - box_height) // 2 + TEXT_STYLE["offset_y"]
        else:  # bottom
            box_y = height - box_height + TEXT_STYLE["offset_y"]

        x = box_x + TEXT_STYLE["padding"]
        y = box_y + TEXT_STYLE["padding"]
        
        draw = ImageDraw.Draw(image)
        draw.rectangle([box_x, box_y, box_x + box_width, box_y + box_height], 
                      fill=hex_to_rgba(TEXT_STYLE["background_color"]))

        image_shadow = None
        if TEXT_STYLE["shadow_distance"] > 0:
            image_shadow = image.copy()

        for i, line in enumerate(lines):
            current_y = y + (i * line_spacing)
            
            draw = ImageDraw.Draw(image)
            draw.text((x, current_y), line, font=font, fill=hex_to_rgba(TEXT_STYLE["color"]))

            if image_shadow is not None:
                draw = ImageDraw.Draw(image_shadow)
                draw.text((x + TEXT_STYLE["shadow_distance"], current_y + TEXT_STYLE["shadow_distance"]), 
                         line, font=font, fill=hex_to_rgba(TEXT_STYLE["shadow_color"]))

        if image_shadow is not None:
            image_shadow = image_shadow.filter(ImageFilter.GaussianBlur(TEXT_STYLE["shadow_blur"]))
            image = Image.alpha_composite(image_shadow, image)

        image = Image.alpha_composite(IMAGE, image)
        image = T.ToTensor()(image).unsqueeze(0).permute([0,2,3,1])

        return (image[:, :, :, :3],)

MISC_CLASS_MAPPINGS = {
    "DenoiseSlider": DenoiseSlider,
    "StepSlider": StepSlider,
    "GuidanceSlider": GuidanceSlider,
    "BatchSlider": BatchSlider,
    "MaxShiftSlider": MaxShiftSlider,
    "ControlNetSlider": ControlNetSlider,
    "IPAdapterSlider": IPAdapterSlider,
    "CannySlider": CannySlider,
    "SelectFromBatch": SelectFromBatch,
    "GPUSlider": GPUSlider,
    "SEGSPass": SEGSPass,
    "IntPass": IntPass,
    "PipePass": PipePass,
    "LatentPass": LatentPass,
    "ResolutionPicker": ResolutionPicker,
    "ResolutionMultiplySlider": ResolutionMultiplySlider,
    "SamplerParameterPacker": SamplerParameterPacker,
    "SamplerParameterUnpacker": SamplerParameterUnpacker,
    "TextVersions": TextVersions,
    "ImpactControlBridgeFix": ImpactControlBridgeFix,
    "BooleanToEnabled": BooleanToEnabled,
    "OutputGetString": OutputGetString,
    "SplitVec2": SplitVec2,
    "SplitVec3": SplitVec3,
    "SimpleTextTruncate": SimpleTextTruncate,
    "FluxContinuumModelRouter": FluxContinuumModelRouter,
    "ConfigurableModelRouter": ConfigurableModelRouter,
    "ImageBatchBoolean": ImageBatchBoolean,
    "DrawTextConfig": DrawTextConfig,
    "ConfigurableDrawText": ConfigurableDrawText
}


================================================
FILE: pyproject.toml
================================================
[project]
name = "comfyui-flux-continuum"
description = "Set of custom nodes to use with the ComfyUI Flux Continuum: Modular Interface. NODES: Text Versions, Image64 Display, Tabs, Step Slider, Denoise Slider, Guidance Slider, Batch Slider, Max Shift Slider, ControlNet Slider and more"
version = "1.7.1"
license = {file = "LICENSE"}
dependencies  = []

[project.urls]
Repository = "https://github.com/robertvoy/ComfyUI-Flux-Continuum"
#  Used by Comfy Registry https://comfyregistry.org

[tool.comfy]
PublisherId = "robertvoy"
DisplayName = "ComfyUI-Flux-Continuum"
Icon = ""


================================================
FILE: web/getsetorder.js
================================================
import { app } from "../../../scripts/app.js";

const originalAlert = window.alert;
window.alert = (message) => {
    if (message === "Error: Set node input undefined. Most likely you're missing custom nodes") {
        return;
    }
    originalAlert(message);
};

// Configuration for nodes that should be forced to back
const BACKGROUND_NODE_CONFIG = {
    nodes: {
        "ImagePass": -200,
        "GetNode": -100,
        "SetNode": -150
    }
};

app.registerExtension({
    name: "FluxContinuum.GetSetNodeOrdering",
    async setup() {
        const originalLoadGraphData = app.loadGraphData;
        app.loadGraphData = function(graph_data) {
            const result = originalLoadGraphData.apply(this, arguments);
            
            // Using requestAnimationFrame instead of setTimeout for better performance
            requestAnimationFrame(() => {
                if (!app.graph?._nodes?.length) return;
                
                const nodes = app.graph._nodes;
                
                // Batch z-index updates
                for (let i = 0; i < nodes.length; i++) {
                    const zIndex = BACKGROUND_NODE_CONFIG.nodes[nodes[i].type];
                    if (zIndex !== undefined) {
                        nodes[i].z_index = zIndex;
                    }
                }
                
                // In-place sort
                nodes.sort((a, b) => (a.z_index || 0) - (b.z_index || 0));
                
                // Mark canvas as dirty
                app.canvas.setDirty(true, true);
            });
            
            return result;
        };
    },

    async beforeRegisterNodeDef(nodeType, nodeData, app) {
        const zIndex = BACKGROUND_NODE_CONFIG.nodes[nodeType.type];
        if (zIndex === undefined) return;
        
        const onNodeCreated = nodeType.prototype.onNodeCreated;
        nodeType.prototype.onNodeCreated = function() {
            if (onNodeCreated) {
                onNodeCreated.apply(this, arguments);
            }
            this.z_index = zIndex;
        };
    }
});

================================================
FILE: web/guidanceversions.js
================================================
import { app } from "../../../scripts/app.js";

// Configuration Constants
const TAB_CONFIG = {
    width: 40,
    height: 15,
    fontSize: 10,
    normalColor: "#0d0d0d",
    selectedColor: "#666666",
    textColor: "white",
    borderRadius: 4,
    spacing: 10,
    offset: 14,
    labels: ["1", "2", "3"], // 3 preset tabs
    yPosition: 6
};

app.registerExtension({
    name: "FluxContinuum.TabbedGuidanceSlider",
    
    // Modern API uses nodeCreated instead of beforeRegisterNodeDef
    nodeCreated(node) {
        // Only apply to GuidanceSlider nodes
        if (node.type !== "GuidanceSlider" && node.comfyClass !== "GuidanceSlider") return;
        
        console.log("TabbedGuidanceSlider: Found GuidanceSlider node", node);
        
        // Store original methods
        const originalOnNodeCreated = node.onNodeCreated;
        const originalOnDrawForeground = node.onDrawForeground;
        const originalGetBounding = node.getBounding;
        const originalOnSerialize = node.onSerialize;
        const originalOnConfigure = node.onConfigure;
        const originalOnMouseDown = node.onMouseDown; // IMPORTANT: Store the original onMouseDown

        // Override methods
        node.onNodeCreated = function() {
            if (originalOnNodeCreated) {
                originalOnNodeCreated.apply(this, arguments);
            }
            
            // Initialize tab state and content
            this.activeTab = 0;
            this.tabContents = TAB_CONFIG.labels.map(() => ({
                value: this.widgets[0].value
            }));
            
            // Store widget reference
            this.valueWidget = this.widgets[0];
            
            // Add change listener to widget
            this.valueWidget.callback = () => {
                this.tabContents[this.activeTab].value = this.valueWidget.value;
            };
        };
        
        node.onDrawForeground = function(ctx) {
            if (originalOnDrawForeground) {
                originalOnDrawForeground.apply(this, arguments);
            }
            
            if (this.flags.collapsed) return;
            
            ctx.save();
            
            // Draw tabs
            TAB_CONFIG.labels.forEach((label, i) => {
                const x = TAB_CONFIG.offset + (TAB_CONFIG.width + TAB_CONFIG.spacing) * i;
                const y = TAB_CONFIG.yPosition;
                
                // Draw tab background
                ctx.fillStyle = i === this.activeTab ? TAB_CONFIG.selectedColor : TAB_CONFIG.normalColor;
                ctx.beginPath();
                ctx.roundRect(x, y, TAB_CONFIG.width, TAB_CONFIG.height, TAB_CONFIG.borderRadius);
                ctx.fill();
                
                // Draw tab text
                ctx.fillStyle = TAB_CONFIG.textColor;
                ctx.font = `${TAB_CONFIG.fontSize}px Arial`;
                ctx.textAlign = "center";
                ctx.textBaseline = "middle";
                ctx.fillText(label, x + TAB_CONFIG.width / 2, y + TAB_CONFIG.height / 2);
            });
            
            ctx.restore();
        };
        
        node.onMouseDown = function(event, local_pos, graphCanvas) {
            const [x, y] = local_pos;
            const { yPosition, height, width, spacing, offset, labels } = TAB_CONFIG;
            
            // Check if click is in tab area
            if (y >= yPosition && y <= yPosition + height) {
                for (let i = 0; i < labels.length; i++) {
                    const tabX = offset + (width + spacing) * i;
                    if (x >= tabX && x <= tabX + width) {
                        if (i === this.activeTab) return false;
                        
                        // Save current widget value to current tab
                        this.tabContents[this.activeTab] = {
                            value: this.valueWidget.value
                        };
                        
                        // Switch tab
                        this.activeTab = i;
                        
                        // Load content from new tab
                        this.valueWidget.value = this.tabContents[i].value;
                        
                        this.setDirtyCanvas(true);
                        return true;
                    }
                }
            }
            
            // IMPORTANT: Call the original onMouseDown if we didn't handle the click
            if (originalOnMouseDown) {
                return originalOnMouseDown.apply(this, arguments);
            }
            
            return false;
        };
        
        node.getBounding = function() {
            const bounds = originalGetBounding ? originalGetBounding.apply(this, arguments) : [0, 0, 200, 100];
            const tabsHeight = Math.abs(TAB_CONFIG.yPosition) + TAB_CONFIG.height;
            bounds[1] -= tabsHeight; // Extend top boundary to include tabs
            bounds[3] += tabsHeight; // Add tab height to total height
            return bounds;
        };

        node.onSerialize = function(o) {
            if (originalOnSerialize) {
                originalOnSerialize.apply(this, arguments);
            }
            o.tabContents = this.tabContents;
            o.activeTab = this.activeTab;
        };

        node.onConfigure = function(o) {
            if (originalOnConfigure) {
                originalOnConfigure.apply(this, arguments);
            }
            if (o.tabContents && Array.isArray(o.tabContents)) {
                // Ensure tabContents length matches number of tabs
                this.tabContents = TAB_CONFIG.labels.map((_, i) => 
                    o.tabContents[i] || {
                        value: this.valueWidget.value
                    }
                );
                this.activeTab = o.activeTab >= 0 && o.activeTab < TAB_CONFIG.labels.length ? o.activeTab : 0;
                
                // Load the active tab's content
                if (this.valueWidget) {
                    this.valueWidget.value = this.tabContents[this.activeTab].value;
                }
            }
        };
    }
});

================================================
FILE: web/help.js
================================================
import { app } from "../../../scripts/app.js";

// Replace this with the category/categories used by your nodes.
const categories = ["Flux-Continuum/Utilities", "Flux-Continuum/Sliders"];

// --- MODIFICATION 3: Change the extension name to be unique ---
app.registerExtension({
    name: "FluxContinuum.Help", // A unique name for your extension
    async beforeRegisterNodeDef(nodeType, nodeData) {
        
        if (app.ui.settings.getSettingValue("KJNodes.helpPopup") === false) {
            return;
        }

        try {
            // --- MODIFICATION 4: Corrected Logic ---
            // Check if the node's category matches any in our list.
            const hasMatchingCategory = categories.some(cat => nodeData?.category?.startsWith(cat));

            if (hasMatchingCategory) {
                addDocumentation(nodeData, nodeType);
            }

        } catch (error) {
            console.error("Error in registering MyNodePack.HelpPopup", error);
        }
    },
});

const create_documentation_stylesheet = () => {
    const tag = 'my-documentation-stylesheet' // Use a unique tag

    let styleTag = document.head.querySelector(tag)

    if (!styleTag) {
        styleTag = document.createElement('style')
        styleTag.type = 'text/css'
        styleTag.id = tag
        styleTag.innerHTML = `
        .kj-documentation-popup {
          background: var(--comfy-menu-bg);
          position: absolute;
          color: var(--fg-color);
          font: 12px monospace;
          line-height: 1.5em;
          padding: 10px;
          border-radius: 10px;
          border-style: solid;
          border-width: medium;
          border-color: var(--border-color);
          z-index: 50; /* Increased z-index */
          overflow: hidden;
          max-width: 450px; /* Set a maximum width */
          min-width: 200px; /* Set a minimum width */
          word-wrap: break-word; /* Ensure long words wrap */
          }
          .content-wrapper {
          overflow: auto;
          max-height: 100%;
          /* Scrollbar styling for Chrome */
          &::-webkit-scrollbar {
             width: 6px;
          }
          &::-webkit-scrollbar-track {
             background: var(--bg-color);
          }
          &::-webkit-scrollbar-thumb {
             background-color: var(--fg-color);
             border-radius: 6px;
             border: 3px solid var(--bg-color);
          }
        
          /* Scrollbar styling for Firefox */
          scrollbar-width: thin;
          scrollbar-color: var(--fg-color) var(--bg-color);
          a {
            color: yellow;
          }
          a:visited {
            color: orange;
          }
          a:hover {
            color: red;
          }
          }
          `
        document.head.appendChild(styleTag)
    }
}

/** Add documentation widget to the selected node */
export const addDocumentation = (
    nodeData,
    nodeType,
    opts = { icon_size: 14, icon_margin: 4 },) => {

    opts = opts || {}
    const iconSize = opts.icon_size ? opts.icon_size : 14
    const iconMargin = opts.icon_margin ? opts.icon_margin : 4
    let docElement = null
    let contentWrapper = null
    //if no description in the node python code, don't do anything
    if (!nodeData.description) {
        return
    }

    const drawFg = nodeType.prototype.onDrawForeground
    nodeType.prototype.onDrawForeground = function (ctx) {
        const r = drawFg ? drawFg.apply(this, arguments) : undefined
        if (this.flags.collapsed) return r

        // icon position
        const x = this.size[0] - iconSize - iconMargin
        
        // create the popup
        if (this.show_doc && docElement === null) {
            docElement = document.createElement('div')
            contentWrapper = document.createElement('div');
            docElement.appendChild(contentWrapper);

            create_documentation_stylesheet()
            contentWrapper.classList.add('content-wrapper');
            docElement.classList.add('kj-documentation-popup')
            
            contentWrapper.innerHTML = DOMPurify.sanitize(marked.parse(nodeData.description,))

            // resize handle
            const resizeHandle = document.createElement('div');
            resizeHandle.style.width = '0';
            resizeHandle.style.height = '0';
            resizeHandle.style.position = 'absolute';
            resizeHandle.style.bottom = '0';
            resizeHandle.style.right = '0';
            resizeHandle.style.cursor = 'se-resize';
            
            // Add pseudo-elements to create a triangle shape
            const borderColor = getComputedStyle(document.documentElement).getPropertyValue('--border-color').trim();
            resizeHandle.style.borderTop = '10px solid transparent';
            resizeHandle.style.borderLeft = '10px solid transparent';
            resizeHandle.style.borderBottom = `10px solid ${borderColor}`;
            resizeHandle.style.borderRight = `10px solid ${borderColor}`;

            docElement.appendChild(resizeHandle)
            let isResizing = false
            let startX, startY, startWidth, startHeight

            resizeHandle.addEventListener('mousedown', function (e) {
                e.preventDefault();
                e.stopPropagation();
                isResizing = true;
                startX = e.clientX;
                startY = e.clientY;
                startWidth = parseInt(document.defaultView.getComputedStyle(docElement).width, 10);
                startHeight = parseInt(document.defaultView.getComputedStyle(docElement).height, 10);
            },
                { signal: this.docCtrl.signal },
            );

            // close button
            const closeButton = document.createElement('div');
            closeButton.textContent = '❌';
            closeButton.style.position = 'absolute';
            closeButton.style.top = '0';
            closeButton.style.right = '0';
            closeButton.style.cursor = 'pointer';
            closeButton.style.padding = '5px';
            closeButton.style.color = 'red';
            closeButton.style.fontSize = '12px';

            docElement.appendChild(closeButton)

            closeButton.addEventListener('mousedown', (e) => {
                e.stopPropagation();
                this.show_doc = !this.show_doc
                docElement.parentNode.removeChild(docElement)
                docElement = null
                if (contentWrapper) {
                    contentWrapper.remove()
                    contentWrapper = null
                }
            },
                { signal: this.docCtrl.signal },
            );
            
            document.addEventListener('mousemove', function (e) {
                if (!isResizing) return;
                const scale = app.canvas.ds.scale;
                const newWidth = startWidth + (e.clientX - startX) / scale;
                const newHeight = startHeight + (e.clientY - startY) / scale;;
                docElement.style.width = `${newWidth}px`;
                docElement.style.height = `${newHeight}px`;
            },
                { signal: this.docCtrl.signal },
            );

            document.addEventListener('mouseup', function () {
                isResizing = false
            },
                { signal: this.docCtrl.signal },
            )

            document.body.appendChild(docElement)
        }
        // close the popup
        else if (!this.show_doc && docElement !== null) {
            docElement.parentNode.removeChild(docElement)
            docElement = null
        }
        // update position of the popup
        if (this.show_doc && docElement !== null) {
            const rect = ctx.canvas.getBoundingClientRect()
            const scaleX = rect.width / ctx.canvas.width
            const scaleY = rect.height / ctx.canvas.height

            const transform = new DOMMatrix()
                .scaleSelf(scaleX, scaleY)
                .multiplySelf(ctx.getTransform())
                .translateSelf(this.size[0], 0) // Adjusted position slightly
                .translateSelf(10, 0)
            
            const scale = new DOMMatrix()
                .scaleSelf(transform.a, transform.d);
            const bcr = app.canvas.canvas.getBoundingClientRect()

            const styleObject = {
                transformOrigin: '0 0',
                transform: scale,
                left: `${bcr.x + transform.e}px`,
                top: `${bcr.y + transform.f}px`,
            };
            Object.assign(docElement.style, styleObject);
        }

        ctx.save()
        ctx.translate(x, iconSize - 38)
        ctx.scale(iconSize / 24, iconSize / 24)
        ctx.font = 'bold 24px monospace'
        ctx.fillStyle = this.show_doc ? 'orange' : 'rgba(255, 255, 255, 0.4)';
        ctx.fillText('?', 0, 24)
        ctx.restore()
        return r
    }
    // handle clicking of the icon
    const mouseDown = nodeType.prototype.onMouseDown
    nodeType.prototype.onMouseDown = function (e, localPos, canvas) {
        const r = mouseDown ? mouseDown.apply(this, arguments) : undefined
        const iconX = this.size[0] - iconSize - iconMargin
        const iconY = iconSize - 34
        if (
            localPos[0] > iconX &&
            localPos[0] < iconX + iconSize &&
            localPos[1] > iconY &&
            localPos[1] < iconY + iconSize
        ) {
            if (this.show_doc === undefined) {
                this.show_doc = true
            } else {
                this.show_doc = !this.show_doc
            }
            if (this.show_doc) {
                this.docCtrl = new AbortController()
            } else {
                if (this.docCtrl) {
                    this.docCtrl.abort()
                }
            }
            return true;
        }
        return r;
    }
    const onRem = nodeType.prototype.onRemoved

    nodeType.prototype.onRemoved = function () {
        const r = onRem ? onRem.apply(this, []) : undefined
    
        if (docElement) {
            docElement.remove()
            docElement = null
        }
    
        if (contentWrapper) {
            contentWrapper.remove()
            contentWrapper = null
        }
        return r
    }
}

================================================
FILE: web/hint.js
================================================
import { app } from "../../../scripts/app.js";

app.registerExtension({
    name: "FluxContinuum.HintNode",

    registerCustomNodes() {

        const LiteGraph = window.LiteGraph;

        // --- Stylesheet for the popup ---
        const create_hint_stylesheet = () => {
            const tagId = 'flux-hint-stylesheet-final';
            if (document.head.querySelector(`#${tagId}`)) return;
            
            const styleTag = document.createElement('style');
            styleTag.id = tagId;
            styleTag.innerHTML = `
            .flux-hint-popup-final {
                background: var(--comfy-menu-bg);
                position: absolute;
                color: var(--fg-color);
                font: 12px monospace;
                line-height: 1.5em;
                padding: 25px;
                border-radius: 10px;
                border: 1px solid var(--border-color);
                z-index: 101;
                overflow: hidden;
                min-width: 200px;
                max-width: 500px;
                min-height: 50px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            }
            .flux-hint-popup-final .content-wrapper {
                overflow: auto;
                max-height: 400px;
                white-space: pre-wrap;
            }
            .flux-hint-popup-final .content-wrapper p,
            .flux-hint-popup-final .content-wrapper h1,
            .flux-hint-popup-final .content-wrapper h2,
            .flux-hint-popup-final .content-wrapper h3,
            .flux-hint-popup-final .content-wrapper h4,
            .flux-hint-popup-final .content-wrapper h5,
            .flux-hint-popup-final .content-wrapper h6 {
                margin-top: 0.1em !important;
                margin-bottom: 0.1em !important;
            }
            .flux-hint-edit-dialog textarea {
                width: 500px;
                height: 200px; 
                font-family: monospace;
            }
            .flux-hint-edit-dialog .buttons {
                text-align: right;
                margin-top: 10px;
            }
            `;
            document.head.appendChild(styleTag);
        };

        // --- Define the custom Hint Node class ---
        class FCHintNode extends LiteGraph.LGraphNode {
            constructor() {
                super("Hint");
                
                this.isVirtualNode = true;
                this.shape = LiteGraph.ROUND_SHAPE;
                this.resizable = false; 
                this.size = [20, 20];

                this.properties = {
                    hint_content: "Right-click me and choose 'Edit Hint' to add multi-line text!\n\n**Markdown** is supported.",
                    saved_size: null 
                };

                this.popupElement = null;
                this.contentWrapper = null;
                this.docCtrl = null;
                this.closeTimer = null;
                
                create_hint_stylesheet();
            }
            
            // --- Core LiteGraph Methods ---
            
            getExtraMenuOptions(canvas, options) {
                options.unshift({
                    content: "Edit Hint...",
                    callback: () => {
                        const initialValue = this.properties.hint_content;
                        
                        const dialogContent = `
                            <div class="flux-hint-edit-dialog">
                                <textarea autofocus>${initialValue}</textarea>
                                <div class="buttons">
                                    <button id="hint-cancel-btn">Cancel</button>
                                    <button id="hint-save-btn" style="margin-left: 5px;">Save</button>
                                </div>
                            </div>`;

                        const dialog = canvas.createDialog(dialogContent, {
                            title: "Edit Hint Content",
                            close_on_click_outside: true,
                        });

                        const textarea = dialog.querySelector("textarea");
                        const saveBtn = dialog.querySelector("#hint-save-btn");
                        const cancelBtn = dialog.querySelector("#hint-cancel-btn");

                        saveBtn.addEventListener('click', () => {
                            this.properties.hint_content = textarea.value;
                            if (this.popupElement) {
                                this.updatePopupContent();
                            }
                            dialog.close();
                        });

                        cancelBtn.addEventListener('click', () => {
                            dialog.close();
                        });
                    }
                });
            }

            computeSize(out) {
                if (this.properties.saved_size) return this.properties.saved_size;
                return [20, 20];
            }
            
            onAdded(graph) {
                if (this.properties.saved_size) this.size = this.properties.saved_size;
            }
            
            onResize(size) { }

            onMouseEnter(e) {
                if (this.closeTimer) clearTimeout(this.closeTimer);
                this.createPopup();
            }

            onMouseLeave(e) {
                this.closeTimer = setTimeout(() => this.removePopup(), 300);
            }

            onDrawBackground(ctx) {}

            onDrawForeground(ctx) {
                if (this.flags.collapsed) return;
                
                ctx.save();
                ctx.font = 'bold ' + (this.size[1] * 0.6) + 'px monospace';
                ctx.fillStyle = this.popupElement ? 'orange' : 'rgba(255, 255, 255, 0.7)';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText('?', this.size[0] / 2, this.size[1] / 2 + 2);
                ctx.restore();
                
                if (this.popupElement) {
                    this.updatePopupPosition(ctx);
                }
            }
            
            onRemoved() {
                this.removePopup();
            }

            onSerialize(o) {
                o.size = this.size;
                o.hint_content = this.properties.hint_content;
            }

            onConfigure(o) {
                if (o.size) {
                    this.properties.saved_size = o.size;
                    this.size = o.size;
                }
                if (o.hint_content) this.properties.hint_content = o.hint_content;
            }

            // --- Popup Management ---
            updatePopupPosition(ctx) {
                if (!this.popupElement || !ctx) return;
            
                const canvas = app.canvas.canvas;
                const rect = canvas.getBoundingClientRect();
                const scaleX = rect.width / canvas.width;
                const scaleY = rect.height / canvas.height;

                const transform = new DOMMatrix()
                    .scaleSelf(scaleX, scaleY)
                    .multiplySelf(ctx.getTransform())
                    .translateSelf(this.size[0], 0)
                    .translateSelf(10 / scaleX, 0);
                
                const scale = new DOMMatrix().scaleSelf(transform.a, transform.d);

                Object.assign(this.popupElement.style, {
                    transformOrigin: '0 0',
                    transform: scale,
                    left: `${rect.x + transform.e}px`,
                    top: `${rect.y + transform.f}px`,
                });
            }

            createPopup() {
                if (this.popupElement) return;
                
                this.popupElement = document.createElement('div');
                this.contentWrapper = document.createElement('div');
                this.popupElement.appendChild(this.contentWrapper);

                this.popupElement.className = 'flux-hint-popup-final';
                this.contentWrapper.className = 'content-wrapper';

                this.popupElement.addEventListener('mouseenter', () => {
                    if (this.closeTimer) clearTimeout(this.closeTimer);
                });
                this.popupElement.addEventListener('mouseleave', () => {
                    this.closeTimer = setTimeout(() => this.removePopup(), 300);
                });
                
                this.updatePopupContent();
                this.addPopupControls();
                
                document.body.appendChild(this.popupElement);
            }

            removePopup() {
                if (this.closeTimer) clearTimeout(this.closeTimer);
                if (this.popupElement) this.popupElement.remove();
                if (this.docCtrl) this.docCtrl.abort();
                this.popupElement = null;
                this.docCtrl = null;
            }
            
            updatePopupContent() {
                if (!this.contentWrapper) return;
                const content = this.properties.hint_content || "";

                if (window.marked && window.DOMPurify) {
                    const html = marked.parse(content, { breaks: true });
                    this.contentWrapper.innerHTML = DOMPurify.sanitize(html);
                } else {
                    this.contentWrapper.textContent = content;
                }
            }

            addPopupControls() {
                const resizeHandle = document.createElement('div');
                Object.assign(resizeHandle.style, {
                    width: '0', height: '0', position: 'absolute', bottom: '0', right: '0', cursor: 'se-resize',
                    borderTop: '10px solid transparent', borderLeft: '10px solid transparent',
                    borderBottom: '10px solid var(--border-color)', borderRight: '10px solid var(--border-color)'
                });
                this.popupElement.appendChild(resizeHandle);
                
                // ** The close button creation logic has been removed. **
                
                this.docCtrl = new AbortController();
                const signal = this.docCtrl.signal;
                
                let isResizing = false, startX, startY, startWidth, startHeight;
                
                resizeHandle.addEventListener('mousedown', (e) => {
                    e.preventDefault(); e.stopPropagation();
                    isResizing = true;
                    startX = e.clientX; startY = e.clientY;
                    startWidth = this.popupElement.offsetWidth;
                    startHeight = this.popupElement.offsetHeight;
                }, { signal });
                
                document.addEventListener('mousemove', (e) => {
                    if (!isResizing) return;
                    const newWidth = startWidth + (e.clientX - startX);
                    const newHeight = startHeight + (e.clientY - startY);
                    this.popupElement.style.width = `${newWidth}px`;
                    this.popupElement.style.height = `${newHeight}px`;
                }, { signal });

                document.addEventListener('mouseup', () => { isResizing = false; }, { signal });
            }
        }

        // --- Register the Node with LiteGraph ---
        const nodeClassName = "Hint Node";
        const nodeCategory = "Flux-Continuum/Utilities";
        
        LiteGraph.registerNodeType(nodeClassName, Object.assign(FCHintNode, {
            title: "Hint",
            title_mode: LiteGraph.NO_TITLE,
            category: nodeCategory
        }));
        
        FCHintNode.prototype.flags = { no_title: true };
    }
});

================================================
FILE: web/imagedisplay.js
================================================
import { app } from "../../../scripts/app.js";

// Default white square image (200x200 pixels, white)
const DEFAULT_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6CAIAAAAHjs1qAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKUklEQVR4nO3dS0hU7R/AcR3NLHOs1IoulEyBhRlZ0Y3uIGFBQRRSi8guVKuoLApatLA23RZRC7tRGxMEW1mmyFuRJiJUVmJlNyvTzDQnrVHnvzhQ/bvonJkzz/PMeb6f7Yvn95vp23lnzhynsDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7Cpe9wO9cLldGRkZycnJCQsLAgQNlrwPTPB7Px48f6+rqioqKamtrZa/zf1TJfdSoUXv37jVCDw9XZSsEwuv11tfXFxUVnTp16vnz57LXUUN0dPTZs2e/fPnihU253e5Lly7Fx8fLbk22jIyMV69eyf7jgAjv37/PzMyU21uExNl79uzJzc0dPny4xB0gzJAhQ1avXh0VFVVWViZrB2m5Hzp06MiRI5GRkbIWgHgOh2PhwoWxsbHFxcVSFpCT++bNm48dO+ZwOKRMh1xz587t6OgoLy8XP1rCNZCUlJTbt28PHTpU/Ggowu12p6en3717V/BcCbmXlJQsW7ZM/FwopaqqatasWYKHin4xs3bt2v379wseCgWNHj26qampqqpK5FDRZ/c7d+7Mnz9f8FCo6eHDh6mpqSInCn2zmJycPHv2bJETobKpU6cuXrxY5EShuW/cuJErj/iV4A+ehOYu/q0JFDdz5kyR44Tm7nK5RI6D+iZMmCBynNC3qm63e/DgwSInQn1xcXHt7e1iZgk9u9M6/jR27Fhhs1R/41hRUXHu3Lm2tjbZi6AfsbGxGzZs8OMDxMTExGDsI5/ZW0ZbW1ujo6Nlbw1fORyOhoYGs3/KixYtErehsEl+ePfuXVdXl+wt4Kve3t5nz57J3qIvSueOkNPT0yN7hb6QOzRC7tAIuUMj5A6NkDs0Qu7QCLlDI+QOjZA7NELu0Ijqd0QGLjk5OSsra86cOePHj09ISIiKivr+/XtLS8urV68qKysvXrxYU1Mje0fYkdl75R49ehTIuAULFty8edPj8fQxoru7u7S0lO+9sUppaanKd0QKJSx3h8Nx9uzZb9+++TjI4/FcuHAhIkLmF8Tag+K52/C1u9PpvH379vbt26Oionz8kcjIyE2bNpWXl48aNSqou0Euu+UeHR1dVlY2b948P3521qxZN27ccDqdlm8FRdgt97y8vLS0NL9/PDU19erVqxbuA6XYKvcdO3asWrUqwIMsX7589+7dluwD1dgn94iIiIMHD1pyqH379vE7srZkn9z37t1r1Vc4jBw58sCBA5YcCkqxT+5r1qyx8GiBvyiCgmySu9PpnDZtmoUHTElJUfOi5IgRIy5fvvzixYvm5ubq6uqdO3fK3gj/YPYDCN8/Zlq3bp3Zg/crKysrqM+GH1wuV11d3W975ubmyt7rJz5mEmHy5MmWH3PSpEmWHzMQLpfr+vXrf261ZcuW8+fPS1kp5Ngk97i4uJA4pt+M1idOnPjX/5qVlUXxvrBJ7l6vV/YKQdR36waK94VNcv/8+bPlx2xtbbX8mH7wpXUDxffLJrk/efLE8mPW1dVZfkyzfG/dQPEKMfue3fcrM9HR0Z2dnWaP3wePxxMfHx/UZ6NfLpfr6dOnfiwvsXiuzIjQ1dVVXV1t4QHv37/f0tJi4QHNMnte/xXn+H+xSe5hYWF5eXkWHq2goMDCo5kVSOsGipfP7P/mzP42U319vdkRf/XmzRuJv9nk92uYP4kvnhcz4hw+fNhrxRXJI0eOyPqe8sDP67/iHC+T2b/3fvyu6pUrV8xO+Y3ElzEWntd/JbJ4xc/uQpl9IvzI3eFwlJWVmR30Q3l5+YABA4Lx2PsVpNYNwoon95/MPhH+fRPBgAEDioqKzM7yer2lpaUxMTGWP2pfBLV1g5jiyf0ns09EIN8zk5OT09HR4eOgr1+/Hj9+3MJHaoqA1g0Ciif3n8w+EQF+rdKUKVPy8/PdbncfIzo7OwsLC6dPn27VYzRLWOuGYBeveO52/tK8x48fr1u3Lj4+fuvWrfPnz09KSkpISIiIiOjp6Wlpaamvr6+oqMjNzW1qapK1obXXYXxh3MS/efNmYRP1ZfbvfYBnd8UJPq//KnjneMXP7ra67h5CxJ/Xf6Xt9Xhyl0Bu6wY9iyd30VRo3aBh8eQulDqtG3QrntzFUa11g1bFk7sgarZu0Kd4chdB5dYNmhRP7kGnfusGHYrXPfdhw4YF9cvxQqV1g+2L1zf3DRs2PH78uKWl5f37969fv96zZ4/lI0KrdYPtixfH7MfLwbuJIDs7+89/ke/kyZMWjpB4j0Dg/C5e8ZsIhDL7RAQp9+zs7O7u7r9OtKr4kG7d4F/x5P6T2SciGLn30boh8OJt0Lrh9OnTZh87uf9k9omwPPd+WzcEUrxtWvd6vffu3TP78BXPXaO3qtnZ2UePHvXlGzV27drlX/Gh+N5UK7rk7nvrBj+Kp3X1aZG72dYNpoqn9ZBg/9z9a93gY/G0HipsnnsgrRv6LZ7WQ4idcw+8dUMfxdN6aLFt7la1bvhr8bQecuyZu7WtG34rntZDkQ1zD0brhh/F03qIstvXKu3evTtIrRt27do1ePDgpUuX0nooslvuK1asCPa/RLBt27agHh/BY8MXM8C/kDs0Qu7QCLlDI+QOjZA7NELu0Ai5QyPkDo2QOzRC7tAIuUMj5A6NkDs0Qu7QiN3ud6+urpa9gn08efJE9goWs1vu2dnZsleAungxA42QOzRC7tAIuUMj5A6NkDs0Qu7QCLlDI+QOjZA7NELu0Ai5QyPkDo0onfugQYNkrwBzhg0bJnuFviid+4QJE7Zv3y57C/gqMzMzJSVF9hZ9CRc5zOv1+vEjL1++7OzsDMY+sFBUVFRSUpIf/5bE7NmzKysrg7HSn4T+ekd3d3dkpLmJ4eHhSUlJQdoHKmhubhY2S+iLmc+fP4scB/V1dXW9ePFC2Dihub9580bkOKjv7du3IscJzf3Bgwcix0F9NTU1IscJzb24uFjkOKivrKxM5DihV2bCwsIaGhrGjBkjeCjU1NbWlpiY6PF4hE0Ufd09Pz9f8EQoq6CgQGTrYeLP7k6n8+nTpyNGjBA8F6ppb29PS0t7/vy5yKGiz+7t7e05OTmCh0JBp06dEtx6mPizu6GwsHDVqlVSRkMF//333+LFi8XPlZN7TEzMrVu30tLSpEyHXLW1tUuWLGlsbBQ/Ws4tYm63e8WKFXx9qYZqa2tXrlwppfUwiXdENjY2Lly48Nq1a7IWgHglJSVLliwR/5L9B9P3r1nI4/Hk5eW1trbOmDEjJiZG4iYItk+fPuXk5GzdurWjo0P2LrI5nc4TJ040NDR4YTsfPnw4c+aMIpee5bxV/Zf169enp6enpqaOGzdu6NChZu8Whgp6enra2trevn374MGDkpKSy5cv9/b2yl4KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA1v4He6Qy6Tvwx/oAAAAASUVORK5CYII=";

class ImageDisplay extends LGraphNode {
   constructor() {
       super();
       this.isVirtualNode = true;
       this.shape = LiteGraph.BOX_SHAPE;
       
       this.size = [200, 200];
       this.bgcolor = "#00000000";
       this.color = "#00000000";
       this.resizable = true;
       
       // Add properties to store original dimensions and image data
       this.properties = {
           originalWidth: 200,
           originalHeight: 200,
           maintainAspectRatio: true,
           imageBase64: DEFAULT_IMAGE, // Initialize with default image
       };
       
       this.addProperty("imageBase64", DEFAULT_IMAGE, "string");  // Add widget for image input
       
       this.widgets_up = true;
       this.img = new Image();
       this.loaded = false;
       
       this.setupImage();
   }

   setupImage() {
       this.img.onload = () => {
           console.log("Image loaded successfully!");
           console.log("Image dimensions:", this.img.width, "x", this.img.height);
           this.loaded = true;
           
           // Store original dimensions
           this.properties.originalWidth = this.img.width;
           this.properties.originalHeight = this.img.height;
           
           // Set initial size if not restored from previous session
           if (!this.properties.lastWidth) {
               this.size[0] = this.img.width;
               this.size[1] = this.img.height;
           } else {
               // Restore previous size
               this.size[0] = this.properties.lastWidth;
               this.size[1] = this.properties.lastHeight;
           }
           
           this.setDirtyCanvas(true);
       };

       this.img.onerror = (err) => {
           console.error("Error loading image:", err);
           // If loading fails, revert to default image
           if (this.properties.imageBase64 !== DEFAULT_IMAGE) {
               console.log("Reverting to default image");
               this.properties.imageBase64 = DEFAULT_IMAGE;
               this.img.src = "data:image/png;base64," + DEFAULT_IMAGE;
           }
       };

       // Load initial image
       this.img.src = "data:image/png;base64," + this.properties.imageBase64;
   }

   // Method to update image when base64 property changes
   onPropertyChanged(name, value) {
       if (name === "imageBase64") {
           this.loaded = false;
           // If value is empty, use default image
           const imageData = value || DEFAULT_IMAGE;
           this.img.src = "data:image/png;base64," + imageData;
       }
   }

   // Handle resizing with aspect ratio
   onResize(size) {
       if (this.properties.maintainAspectRatio && this.loaded) {
           const aspectRatio = this.properties.originalWidth / this.properties.originalHeight;
           const currentRatio = size[0] / size[1];
           
           if (currentRatio > aspectRatio) {
               size[0] = size[1] * aspectRatio;
           } else {
               size[1] = size[0] / aspectRatio;
           }
       }
       
       this.properties.lastWidth = size[0];
       this.properties.lastHeight = size[1];
       
       return size;
   }

   // Serialize the node state
   onSerialize(o) {
       if (this.loaded) {
           o.lastWidth = this.size[0];
           o.lastHeight = this.size[1];
           o.imageBase64 = this.properties.imageBase64;  // Save image data
       }
   }

   // Deserialize the node state
   onConfigure(o) {
       if (o.lastWidth !== undefined) {
           this.properties.lastWidth = o.lastWidth;
           this.properties.lastHeight = o.lastHeight;
       }
       if (o.imageBase64 !== undefined) {
           this.properties.imageBase64 = o.imageBase64;
           this.onPropertyChanged("imageBase64", o.imageBase64);
       }
   }
}

// Override the default node drawing
const oldDrawNode = LGraphCanvas.prototype.drawNode;
LGraphCanvas.prototype.drawNode = function(node, ctx) {
   if (node.constructor === ImageDisplay) {
       const tmp_shape = node.shape;
       const tmp_color = node.color;
       const tmp_bgcolor = node.bgcolor;
       
       node.shape = null;
       node.color = "#00000000";
       node.bgcolor = "#00000000";
       
       const r = oldDrawNode.apply(this, arguments);
       
       if (node.img && node.loaded) {
           ctx.save();
           ctx.drawImage(node.img, 0, 0, node.size[0], node.size[1]);
           ctx.restore();
       }
       
       node.shape = tmp_shape;
       node.color = tmp_color;
       node.bgcolor = tmp_bgcolor;
       
       return r;
   }
   return oldDrawNode.apply(this, arguments);
};

app.registerExtension({
   name: "FluxContinuum.ImageDisplay",
   async beforeRegisterNodeDef(nodeType, nodeData, app) {
       if (nodeData.name === "ImageDisplay") {
           nodeType.prototype.execute = () => {}; 
       }
   },
   registerCustomNodes() {
       LiteGraph.registerNodeType(
           "ImageDisplay",
           Object.assign(ImageDisplay, {
               title: "ImageDisplay",
               title_mode: LiteGraph.NO_TITLE,
               category: "Flux-Continuum/Utilities",
               comfyClass: "ImageDisplay"
           })
       );

       ImageDisplay.prototype.flags = { no_title: true };
   }
});

================================================
FILE: web/imagetransfershortcut.js
================================================
import { app } from "/scripts/app.js";
import { api } from "/scripts/api.js";

app.registerExtension({
  name: "FluxContinuum.ImageTransfer",
  
  commands: [
    {
      id: "imageTransfer",
      label: "Image Transfer",
      function: async () => {
        const sourceNodeTitle = "Img Preview";
        const destNodeTitle = "Img Load";
        
        try {
          const sourceNode = app.graph.findNodesByTitle(sourceNodeTitle)?.[0];
          const destNode = app.graph.findNodesByTitle(destNodeTitle)?.[0];
          
          if (!sourceNode || !destNode) {
            console.error(`Custom Command: Could not find nodes "${sourceNodeTitle}" and/or "${destNodeTitle}".`);
            return;
          }
          
          if (!sourceNode.images || sourceNode.images.length === 0) {
            console.warn(`Custom Command: Source node "${sourceNodeTitle}" has no image to copy.`);
            return;
          }
          
          // Get the current image index
          let currentIndex = sourceNode.imageIndex ?? 0;
          
          // Ensure index is within bounds
          currentIndex = Math.max(0, Math.min(currentIndex, sourceNode.images.length - 1));
          
          // Use the selected image
          const imageInfo = sourceNode.images[currentIndex];
          
          const response = await api.fetchApi(`/view?filename=${encodeURIComponent(imageInfo.filename)}&type=${imageInfo.type}&subfolder=${encodeURIComponent(imageInfo.subfolder)}`);
          const imageBlob = await response.blob();
          
          const formData = new FormData();
          formData.append("image", imageBlob, imageInfo.filename);
          formData.append("overwrite", "true");
          
          const uploadResponse = await api.fetchApi('/upload/image', {
            method: 'POST',
            body: formData
          });
          
          const uploadResult = await uploadResponse.json();
          
          if (!uploadResult || !uploadResult.name) {
            throw new Error("Upload failed or did not return a valid filename from the server.");
          }
          
          const newFilename = uploadResult.name;
          const imageWidget = destNode.widgets.find((w) => w.name === "image");
          
          if (imageWidget) {
            imageWidget.value = newFilename;
            if (imageWidget.callback) {
              imageWidget.callback(imageWidget.value);
            }
            destNode.setDirtyCanvas(true, true);
          } else {
            console.error(`Custom Command: Could not find the 'image' widget on "${destNodeTitle}".`);
          }
        } catch (error) {
          console.error("Custom Command: An error occurred:", error);
        }
      },
    },
  ],
  keybindings: [
    {
      combo: { key: "c", ctrl: true, shift: true },
      commandId: "imageTransfer",
    },
  ],
});


================================================
FILE: web/impactpackfix.js
================================================
import { app } from "../../scripts/app.js";

app.registerExtension({
    name: "FluxContinuum.ImpactControlBridgeFix",
    async beforeRegisterNodeDef(nodeType, nodeData, app) {
        if(nodeData.name == "ImpactControlBridge") {
            const onConnectionsChange = nodeType.prototype.onConnectionsChange;
            nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) {
                if(index != 0 || !link_info || this.inputs[0].type != '*')
                    return;

                // assign type
                let slot_type = '*';

                if(type == 2) {
                    slot_type = link_info.type;
                }
                else {
                    const node = app.graph.getNodeById(link_info.origin_id);
                    slot_type = node.outputs[link_info.origin_slot].type;
                }

                this.inputs[0].type = slot_type;
                this.outputs[0].type = slot_type;
                this.outputs[0].label = slot_type;
            }
        }
    }
});

================================================
FILE: web/outputgetnode.js
================================================
import { app } from "../../../scripts/app.js";

//based on KJNodes SetGet: https://github.com/kijai/ComfyUI-KJNodes

// Configuration
const CONFIG = {
    NODE: {
        name: "OutputGet",
        category: "Flux-Continuum/Utilities",
        title: "OutputGet",
        defaultColor: "#FFF",
        prefix: "Output -"
    },
    CACHE: {
        timeout: 1000,
        debounceWait: 100
    },
    ERRORS: {
        singletonError: "Error: Only one OutputGet node is allowed.",
        noSetNodeError: "No Output SetNode found for",
        errorTimeout: 5000
    }
};

// Helper functions
function debounce(func, wait = CONFIG.CACHE.debounceWait) {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func.apply(this, args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

function createCachedFunction(fn) {
    let cache = null;
    let lastUpdate = 0;

    return function (...args) {
        const now = Date.now();
        if (!cache || (now - lastUpdate) > CONFIG.CACHE.timeout) {
            cache = fn.apply(this, args);
            lastUpdate = now;
        }
        return cache;
    };
}

app.registerExtension({
    name: "FluxContinuum.OutputGet",
    registerCustomNodes() {
        class OutputGetNode extends LGraphNode {
            static instance = null;

            constructor(title) {
                super(title);

                if (OutputGetNode.instance) {
                    alert(CONFIG.ERRORS.singletonError);
                    setTimeout(() => this.graph?.remove(this), 0);
                    return;
                }
                
                OutputGetNode.instance = this;

                if (!this.properties) {
                    this.properties = {};
                }
                this.properties.showOutputText = true;
                const node = this;

                const widget = this.addWidget(
                    "combo",
                    "Selected",
                    "",
                    function (value, canvas, node, pos, event) {
                        try {
                            this.value = value;
                            node.widgets[0].value = value;
                            node.onRename();
                            node.graph._version++;
                            node.setDirtyCanvas(true, true);
                            node.updateGetStringTitles();
                        } catch (error) {
                            console.error("Error in combo widget callback:", error);
                        }
                    },
                    {
                        values: () => node.getSetterNodeTitles(node.graph),
                    }
                );

                this.addOutput("*", '*');

                this.onConnectionsChange = function (slotType, slot, isChangeConnect, link_info, output) {
                    try {
                        this.validateLinks();
                        if (link_info && node.graph) {
                            const setter = node.findSetter(node.graph);
                            if (setter) {
                                let linkType = setter.inputs[0].type;
                                node.setType(linkType);
                                node.updateGetStringTitles();
                            }
                        }
                        node.setDirtyCanvas(true, true);
                    } catch (error) {
                        console.error("Error in onConnectionsChange:", error);
                    }
                };

                this.setName = function (name) {
                    try {
                        node.widgets[0].value = name;
                        node.onRename();
                        node.serialize();
                        node.updateGetStringTitles();
                    } catch (error) {
                        console.error("Error in setName:", error);
                    }
                };

                this.onRename = function () {
                    try {
                        const setter = node.findSetter(node.graph);
                        if (setter) {
                            let linkType = setter.inputs[0].type;
                            node.setType(linkType);
                            node.title = setter.widgets[0].value;

                            if (app.ui.settings.getSettingValue("KJNodes.nodeAutoColor")) {
                                setColorAndBgColor.call(node, linkType);
                            }

                            node.updateGetStringTitles();
                        } else {
                            node.setType('*');
                        }
                    } catch (error) {
                        console.error("Error in onRename:", error);
                    }
                };

                this.updateGetStringTitles = debounce(function () {
                    try {
                        const getStringNodes = this.graph._nodes.filter((n) => n.type === "OutputGetString");
                        getStringNodes.forEach((getStringNode) => {
                            getStringNode.title = "GetString_" + this.title;
                        });
                    } catch (error) {
                        console.error("Error updating GetString titles:", error);
                    }
                }, CONFIG.CACHE.debounceWait);

                this.getSetterNodeTitles = createCachedFunction(function (graph) {
                    return graph._nodes
                        .filter(node => node.type === 'SetNode' && node.widgets[0].value.startsWith(CONFIG.NODE.prefix))
                        .map(node => node.widgets[0].value)
                        .sort();
                });

                this.defaultVisibility = true;
                this.serialize_widgets = true;
                this.drawConnection = false;
                this.slotColor = CONFIG.NODE.defaultColor;
                this.currentSetter = null;
                this.canvas = app.canvas;
                this.isVirtualNode = true;
            }

            onRemoved() {
                if (OutputGetNode.instance === this) {
                    OutputGetNode.instance = null;
                }
                this.drawConnection = false;
                this.currentSetter = null;
            }

            validateLinks() {
                if (this.outputs[0].type !== '*' && this.outputs[0].links) {
                    this.outputs[0].links.filter((linkId) => {
                        const link = this.graph.links[linkId];
                        return link && (link.type !== this.outputs[0].type && link.type !== '*');
                    }).forEach((linkId) => {
                        this.graph.removeLink(linkId);
                    });
                }
            }

            setType(type) {
                this.outputs[0].name = type;
                this.outputs[0].type = type;
                this.validateLinks();
            }

            findSetter(graph) {
                const name = this.widgets[0].value;
                return graph._nodes.find(
                    (otherNode) =>
                        otherNode.type === 'SetNode' &&
                        otherNode.widgets[0].value === name &&
                        name !== '' &&
                        name.startsWith(CONFIG.NODE.prefix)
                );
            }

            goToSetter() {
                const setter = this.findSetter(this.graph);
                if (setter) {
                    this.canvas.centerOnNode(setter);
                    this.canvas.selectNode(setter);
                }
            }

            getInputLink(slot) {
                try {
                    const setter = this.findSetter(this.graph);
                    if (setter) {
                        const slotInfo = setter.inputs[slot];
                        return this.graph.links[slotInfo.link];
                    } else {
                        const message = `${CONFIG.ERRORS.noSetNodeError} ${this.widgets[0].value}`;
                        if (!window.isAlertShown) {
                            window.isAlertShown = true;
                            alert(message);
                            setTimeout(() => (window.isAlertShown = false), CONFIG.ERRORS.errorTimeout);
                        }
                    }
                } catch (error) {
                    console.error("Error in getInputLink:", error);
                    return null;
                }
            }

            getExtraMenuOptions(_, options) {
                let menuEntry = this.drawConnection ? "Hide connections" : "Show connections";

                options.unshift(
                    {
                        content: "Go to output setter",
                        callback: () => {
                            this.goToSetter();
                        },
                    },
                    {
                        content: menuEntry,
                        callback: () => {
                            try {
                                this.currentSetter = this.findSetter(this.graph);
                                if (!this.currentSetter) return;
                                let linkType = this.currentSetter.inputs[0].type;
                                this.drawConnection = !this.drawConnection;
                                this.slotColor = this.canvas.default_connection_color_byType[linkType];
                                this.canvas.setDirty(true, true);
                            } catch (error) {
                                console.error("Error in menu callback:", error);
                            }
                        },
                    }
                );
            }

            onDrawForeground(ctx, lGraphCanvas) {
                if (this.drawConnection) {
                    this._drawVirtualLink(lGraphCanvas, ctx);
                }
            }

            _drawVirtualLink(lGraphCanvas, ctx) {
                if (!this.currentSetter) return;

                try {
                    let start_node_slotpos = this.currentSetter.getConnectionPos(false, 0);
                    start_node_slotpos = [
                        start_node_slotpos[0] - this.pos[0],
                        start_node_slotpos[1] - this.pos[1],
                    ];
                    let end_node_slotpos = [0, -LiteGraph.NODE_TITLE_HEIGHT * 0.5];
                    lGraphCanvas.renderLink(
                        ctx,
                        start_node_slotpos,
                        end_node_slotpos,
                        null,
                        false,
                        null,
                        this.slotColor
                    );
                } catch (error) {
                    console.error("Error drawing virtual link:", error);
                }
            }
        }

        LiteGraph.registerNodeType(CONFIG.NODE.name, OutputGetNode);
        OutputGetNode.category = CONFIG.NODE.category;

        const originalCheckNodeTypes = LGraphCanvas.prototype.checkNodeTypes;
        LGraphCanvas.prototype.checkNodeTypes = function() {
            const r = originalCheckNodeTypes.apply(this, arguments);
            
            if (r && r.type === CONFIG.NODE.name && OutputGetNode.hasInstance()) {
                r.disabled = true;
                r.tooltip = CONFIG.ERRORS.singletonError;
            }
            
            return r;
        };
    },
});

app.registerExtension({
  name: "FluxContinuum.OutputGetString",
  async beforeRegisterNodeDef(nodeType, nodeData) {
    if (nodeData.name !== "OutputGetString") {
      return;
    }

    // Ensure the widget is hidden and updated
    const ensureHiddenWidget = (node) => {
      let widget = node.widgets?.find((w) => w.name === "title");
      if (!widget) {
        // If the widget does not exist, create it
        widget = { name: "title", value: "", hidden: true, serialize: true };
        node.widgets = node.widgets || [];
        node.widgets.push(widget);
      }
      // Keep it hidden and serialized
      widget.hidden = true;
      return widget;
    };

    // Update title and widget values
    const updateGetStringTitles = (graph) => {
      const getStringNodes = graph._nodes.filter((n) => n.type === "OutputGetString");
      const outputGetNode = graph._nodes.find((n) => n.type === CONFIG.NODE.name);

      getStringNodes.forEach((node) => {
        const titleWidget = ensureHiddenWidget(node);
        if (outputGetNode) {
          // Update the title and widget value
          const newTitle = "GetString_" + outputGetNode.title;
          node.title = newTitle;
          titleWidget.value = newTitle.replace("GetString_", "");
        } else {
          // Default title if no OutputGet node
          node.title = "GetString";
          titleWidget.value = "";
        }
      });
    };

    // Hijack app.queuePrompt to update titles on execution
    const originalQueuePrompt = app.queuePrompt;
    app.queuePrompt = async function () {
      const graph = app.graph;
      if (graph) {
        updateGetStringTitles(graph);
      }
      return await originalQueuePrompt.apply(this, arguments);
    };

    // Initialize hidden widget on node addition
    nodeType.prototype.onAdded = function (graph) {
      ensureHiddenWidget(this);
      this.updateTitle();
    };

    // Update title when called
    nodeType.prototype.updateTitle = function () {
      if (!this.graph) {
        return;
      }
      const outputGetNode = this.findOutputGetNode(this.graph);
      if (outputGetNode) {
        this.title = "GetString_" + outputGetNode.title;
      } else {
        this.title = "GetString";
      }
    };

    // Find the OutputGet node in the graph
    nodeType.prototype.findOutputGetNode = function (graph) {
      return graph._nodes.find((node) => node.type === CONFIG.NODE.name);
    };
  },
});

class TextDisplay extends LGraphNode {
    constructor() {
        super();
        this.isVirtualNode = true;
        this.shape = LiteGraph.BOX_SHAPE;
        this.size = [200, 50];
        this.resizable = true;
        this.properties = {
            fontSize: 24,
            fontFamily: "Arial",
            fontColor: "#ffffff",
            textAlign: "center",
            bgColor: "transparent",
            padding: 10
        };
        this.widgets_up = true;
        this.bgcolor = "#00000000";
    }

    onAdded(graph) {
        // When added to a graph, move to end of nodes array
        if (graph && graph._nodes) {
            const index = graph._nodes.indexOf(this);
            if (index !== -1) {
                graph._nodes.splice(index, 1);
                graph._nodes.push(this);
            }
        }
    }

    onDragFinished() {
        // After dragging, ensure node stays at end of array
        if (this.graph && this.graph._nodes) {
            const index = this.graph._nodes.indexOf(this);
            if (index !== -1) {
                this.graph._nodes.splice(index, 1);
                this.graph._nodes.push(this);
            }
        }
    }

    onDrawForeground(ctx) {
        if (!this.graph) return;
        
        // Always ensure this node is at the end of the array before drawing
        const index = this.graph._nodes.indexOf(this);
        if (index !== -1 && index !== this.graph._nodes.length - 1) {
            this.graph._nodes.splice(index, 1);
            this.graph._nodes.push(this);
        }
        
        const outputGetNode = this.findOutputGetNode(this.graph);
        if (!outputGetNode || !outputGetNode.widgets[0]) {
            this.displayText = "NO OUTPUTGET NODE FOUND";
        } else {
            this.displayText = outputGetNode.widgets[0].value || "NO SELECTION";
            this.displayText = this.displayText.toUpperCase();
        }

        ctx.save();
        ctx.textAlign = this.properties.textAlign;
        ctx.textBaseline = "middle";
        ctx.fillStyle = this.properties.fontColor || "#ffffff";
        ctx.font = `${this.properties.fontSize}px ${this.properties.fontFamily}`;

        const x = this.properties.textAlign === "center" ? this.size[0] / 2 : 
                 this.properties.textAlign === "right" ? this.size[0] - 10 : 10;
        
        const y = this.size[1] / 2;
        ctx.fillText(this.displayText, x, y);
        ctx.restore();
    }

    findOutputGetNode(graph) {
        return graph._nodes.find((node) => node.type === "OutputGet");
    }

    onResize(size) {
        size[0] = Math.max(50, size[0]);
        size[1] = Math.max(20, size[1]);
        return size;
    }

    onDblClick(event, pos, canvas) {
        LGraphCanvas.active_canvas.showShowNodePanel(this);
    }
}

// Node type registration with required static properties
TextDisplay.title = "OutputTextDisplay";
TextDisplay.title_mode = LiteGraph.NO_TITLE;
TextDisplay.collapsable = false;

const oldDrawNode = LGraphCanvas.prototype.drawNode;
LGraphCanvas.prototype.drawNode = function(node, ctx) {
    if (node.constructor === TextDisplay) {
        const tmp_shape = node.shape;
        node.shape = null;
        const r = oldDrawNode.apply(this, arguments);
        if (node.onDrawForeground) {
            node.onDrawForeground(ctx);
        }
        node.shape = tmp_shape;
        return r;
    }
    return oldDrawNode.apply(this, arguments);
};

app.registerExtension({
    name: "FluxContinuum.OutputTextDisplay",
    registerCustomNodes() {
        LiteGraph.registerNodeType(
            "OutputTextDisplay",
            Object.assign(TextDisplay, {
                title: "OutputTextDisplay",
                title_mode: LiteGraph.NO_TITLE,
                category: "Flux-Continuum/Utilities",
                comfyClass: "OutputTextDisplay",
                "@fontSize": { type: "number" },
                "@fontFamily": { type: "string" },
                "@fontColor": { type: "string", default: "#ffffff" },
                "@bgColor": { type: "string", default: "#00000000" },
                "@textAlign": { type: "combo", values: ["left", "center", "right"] },
                "@padding": { type: "number" }
            })
        );
        TextDisplay.prototype.flags = { no_title: true };
    }
});

================================================
FILE: web/samplerversions.js
================================================
import { app } from "../../../scripts/app.js";

// Configuration Constants
const TAB_CONFIG = {
    width: 40,
    height: 15,
    fontSize: 10,
    normalColor: "#0d0d0d",
    selectedColor: "#666666",
    textColor: "white",
    borderRadius: 4,
    spacing: 10,
    offset: 16,
    labels: ["1", "2", "3"], // 3 tabs
    yPosition: 6
};

app.registerExtension({
    name: "FluxContinuum.SamplerParameterPacker",
    nodeCreated(node) {
        // Only apply to SamplerParameterPacker nodes
        if (node.type !== "SamplerParameterPacker" && node.comfyClass !== "SamplerParameterPacker") return;
        
        // Preserve original methods
        const originalOnNodeCreated = node.onNodeCreated;
        const originalOnDrawForeground = node.onDrawForeground;
        const originalGetBounding = node.getBounding;
        const originalOnSerialize = node.onSerialize;
        const originalOnConfigure = node.onConfigure;
        const originalOnMouseDown = node.onMouseDown; // IMPORTANT: Store the original onMouseDown

        node.onNodeCreated = function() {
            if (originalOnNodeCreated) {
                originalOnNodeCreated.apply(this, arguments);
            }
            
            // Initialize tab state and content
            this.activeTab = 0;
            this.tabContents = TAB_CONFIG.labels.map(() => ({
                sampler: this.widgets[0].value,
                scheduler: this.widgets[1].value
            }));
            
            // Store widget references
            this.samplerWidget = this.widgets[0];
            this.schedulerWidget = this.widgets[1];
            
            // Add change listeners to widgets
            this.samplerWidget.callback = () => {
                this.tabContents[this.activeTab].sampler = this.samplerWidget.value;
            };

            this.schedulerWidget.callback = () => {
                this.tabContents[this.activeTab].scheduler = this.schedulerWidget.value;
            };
        };
        
        node.onDrawForeground = function(ctx) {
            if (originalOnDrawForeground) {
                originalOnDrawForeground.apply(this, arguments);
            }
            
            if (this.flags.collapsed) return;
            
            ctx.save();
            
            // Draw tabs
            TAB_CONFIG.labels.forEach((label, i) => {
                const x = TAB_CONFIG.offset + (TAB_CONFIG.width + TAB_CONFIG.spacing) * i;
                const y = TAB_CONFIG.yPosition;
                
                // Draw tab background
                ctx.fillStyle = i === this.activeTab ? TAB_CONFIG.selectedColor : TAB_CONFIG.normalColor;
                ctx.beginPath();
                ctx.roundRect(x, y, TAB_CONFIG.width, TAB_CONFIG.height, TAB_CONFIG.borderRadius);
                ctx.fill();
                
                // Draw tab text
                ctx.fillStyle = TAB_CONFIG.textColor;
                ctx.font = `${TAB_CONFIG.fontSize}px Arial`;
                ctx.textAlign = "center";
                ctx.textBaseline = "middle";
                ctx.fillText(label, x + TAB_CONFIG.width / 2, y + TAB_CONFIG.height / 2);
            });
            
            ctx.restore();
        };
        
        node.onMouseDown = function(event, local_pos, graphCanvas) {
            const [x, y] = local_pos;
            const { yPosition, height, width, spacing, offset, labels } = TAB_CONFIG;
            
            // Check if click is in tab area
            if (y >= yPosition && y <= yPosition + height) {
                for (let i = 0; i < labels.length; i++) {
                    const tabX = offset + (width + spacing) * i;
                    if (x >= tabX && x <= tabX + width) {
                        if (i === this.activeTab) return false;
                        
                        // Save current widget values to current tab
                        this.tabContents[this.activeTab] = {
                            sampler: this.samplerWidget.value,
                            scheduler: this.schedulerWidget.value
                        };
                        
                        // Switch tab
                        this.activeTab = i;
                        
                        // Load content from new tab
                        this.samplerWidget.value = this.tabContents[i].sampler;
                        this.schedulerWidget.value = this.tabContents[i].scheduler;
                        
                        this.setDirtyCanvas(true);
                        return true;
                    }
                }
            }
            
            // IMPORTANT: Call the original onMouseDown if we didn't handle the click
            if (originalOnMouseDown) {
                return originalOnMouseDown.apply(this, arguments);
            }
            
            return false;
        };
        
        node.getBounding = function() {
            const bounds = originalGetBounding ? originalGetBounding.apply(this, arguments) : [0, 0, 200, 100];
            const tabsHeight = Math.abs(TAB_CONFIG.yPosition) + TAB_CONFIG.height;
            bounds[1] -= tabsHeight; // Extend top boundary to include tabs
            bounds[3] += tabsHeight; // Add tab height to total height
            return bounds;
        };

        node.onSerialize = function(o) {
            if (originalOnSerialize) {
                originalOnSerialize.apply(this, arguments);
            }
            o.tabContents = this.tabContents;
            o.activeTab = this.activeTab;
        };

        node.onConfigure = function(o) {
            if (originalOnConfigure) {
                originalOnConfigure.apply(this, arguments);
            }
            if (o.tabContents && Array.isArray(o.tabContents)) {
                // Ensure tabContents length matches number of tabs
                this.tabContents = TAB_CONFIG.labels.map((_, i) => 
                    o.tabContents[i] || {
                        sampler: this.samplerWidget.value,
                        scheduler: this.schedulerWidget.value
                    }
                );
                this.activeTab = o.activeTab >= 0 && o.activeTab < TAB_CONFIG.labels.length ? o.activeTab : 0;
                
                // Load the active tab's content
                if (this.samplerWidget && this.schedulerWidget) {
                    this.samplerWidget.value = this.tabContents[this.activeTab].sampler;
                    this.schedulerWidget.value = this.tabContents[this.activeTab].scheduler;
                }
            }
        };
    }
});

================================================
FILE: web/tabs.js
================================================
import { app } from "../../../scripts/app.js";

/**
 * Constants for node and tab dimensions and appearance
 * @readonly
 */
const NODE_METRICS = {
    TITLE_HEIGHT: 30,
    TAB_HEIGHT: 48,
    DEFAULT_TAB_X_OFFSET: 10,
    DEFAULT_SECOND_TAB_OFFSET: 80,
    MIN_TAB_SPACING: 5  // Minimum spacing between tabs
};

/**
 * Style configuration for tabs
 * @readonly
 */
const TAB_STYLE = {
    defaultWidth: 65,
    minWidth: 30,         // Minimum allowed tab width
    maxWidth: 200,        // Maximum allowed tab width
    height: 18,
    fontSize: 9,
    normalColor: "#5a5a5a",
    textColor: "white",
    borderRadius: 4,
    fontFamily: "'Segoe UI', Arial, sans-serif"
};

/**
 * Utility class for bounding box calculations
 * Optimizes memory usage by reusing a single Float32Array
 */
class BoundingBoxCalculator {
    constructor() {
        this.boundingBoxBuffer = new Float32Array(4);
    }

    /**
     * Calculate the bounding box containing all provided rectangles
     * @param {Array<{x: number, y: number, width: number, height: number}>} rects - Array of rectangles
     * @returns {Float32Array} Bounding box as [x, y, width, height]
     */
    calculate(rects) {
        if (!rects || rects.length === 0) {
            this.boundingBoxBuffer.set([0, 0, 0, 0]);
            return this.boundingBoxBuffer;
        }

        let minX = rects[0].x;
        let minY = rects[0].y;
        let maxX = rects[0].x + rects[0].width;
        let maxY = rects[0].y + rects[0].height;

        for (let i = 1; i < rects.length; i++) {
            const rect = rects[i];
            minX = Math.min(minX, rect.x);
            minY = Math.min(minY, rect.y);
            maxX = Math.max(maxX, rect.x + rect.width);
            maxY = Math.max(maxY, rect.y + rect.height);
        }

        this.boundingBoxBuffer.set([minX, minY, maxX - minX, maxY - minY]);
        return this.boundingBoxBuffer;
    }
}

// Create a single instance of the calculator for reuse
const boundingBoxCalculator = new BoundingBoxCalculator();

/**
 * Class to handle tab interactions and rendering
 */
class TabManager {
    /**
     * Get the width of a tab, with validation
     * @param {Object} config - Tab configuration
     * @param {boolean} isSecondTab - Whether this is the second tab
     * @returns {number} - The tab width
     */
    static getTabWidth(config, isSecondTab = false) {
        try {
            if (!config) {
                return TAB_STYLE.defaultWidth;
            }
            
            let width;
            if (isSecondTab) {
                width = config.secondTabWidth || TAB_STYLE.defaultWidth;
            } else {
                width = config.width || TAB_STYLE.defaultWidth;
            }
            
            // Validate width is within acceptable range
            return Math.min(Math.max(width, TAB_STYLE.minWidth), TAB_STYLE.maxWidth);
        } catch (error) {
            console.warn("Error getting tab width:", error);
            return TAB_STYLE.defaultWidth;
        }
    }

    /**
     * Check if tabs would overlap with current configuration
     * @param {Object} properties - Node properties
     * @returns {boolean} - True if tabs would overlap
     */
    static wouldTabsOverlap(properties) {
        if (!properties.hasSecondTab) return false;
        
        const tab1End = properties.tabXOffset + properties.tabWidth;
        const tab2Start = properties.secondTabOffset;
        
        return tab2Start < tab1End + NODE_METRICS.MIN_TAB_SPACING;
    }

    /**
     * Draw a tab on the canvas
     * @param {CanvasRenderingContext2D} ctx - Canvas context
     * @param {number} x - X position
     * @param {number} y - Y position
     * @param {string} text - Tab text
     * @param {number} width - Tab width
     */
    static drawTab(ctx, x, y, text, width) {
        const { height, fontSize, normalColor, textColor, borderRadius, fontFamily } = TAB_STYLE;

        ctx.save();
        ctx.fillStyle = normalColor;

        // Draw tab background
        ctx.beginPath();
        ctx.moveTo(x + borderRadius, y);
        ctx.lineTo(x + width - borderRadius, y);
        ctx.quadraticCurveTo(x + width, y, x + width, y + borderRadius);
        ctx.lineTo(x + width, y + height);
        ctx.lineTo(x, y + height);
        ctx.lineTo(x, y + borderRadius);
        ctx.quadraticCurveTo(x, y, x + borderRadius, y);
        ctx.closePath();
        ctx.fill();

        // Text rendering with optimizations
        ctx.fillStyle = textColor;
        ctx.textRendering = 'optimizeLegibility';
        ctx.imageSmoothingEnabled = true;
        ctx.font = `${fontSize}px ${fontFamily}`;
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        
        // Truncate text if it's too long for the tab
        let displayText = text;
        const textWidth = ctx.measureText(text).width;
        if (textWidth > width - 10) {
            // Create ellipsis if text is too long
            while (ctx.measureText(displayText + "...").width > width - 10 && displayText.length > 0) {
                displayText = displayText.slice(0, -1);
            }
            displayText += "...";
        }
        
        ctx.fillText(displayText, x + width / 2, y + height / 2);
        ctx.restore();
    }

    /**
     * Handle tab click to reorder the node
     * @param {LiteGraph.LGraphNode} node - The node being clicked
     * @param {boolean} toFront - Whether to bring the node to front
     * @returns {Function} - Cleanup function
     */
    static handleNodeOrder(node, toFront = true) {
        const { graph } = node;
        if (!graph?._nodes) return null;

        const index = graph._nodes.indexOf(node);
        if (index === -1) return null;

        let frameId;

        const handleDeselection = () => {
            frameId = requestAnimationFrame(() => {
                app.canvas.deselectNode(node);
                node.setDirtyCanvas(true, true);
            });
        };

        // Reorder the node
        graph._nodes.splice(index, 1);
        if (toFront) {
            graph._nodes.push(node);
        } else {
            graph._nodes.unshift(node);
        }

        handleDeselection();

        // Return cleanup function
        return () => {
            if (frameId) {
                cancelAnimationFrame(frameId);
            }
        };
    }
}

/**
 * Helper function to check if a point is inside a rectangle
 * @param {number} x - Point X coordinate
 * @param {number} y - Point Y coordinate
 * @param {number} rx - Rectangle X coordinate
 * @param {number} ry - Rectangle Y coordinate
 * @param {number} rw - Rectangle width
 * @param {number} rh - Rectangle height
 * @returns {boolean} - True if point is inside rectangle
 */
const isInsideRect = (x, y, rx, ry, rw, rh) => (
    x >= rx && x <= rx + rw && y >= ry && y <= ry + rh
);

/**
 * Main class to handle node tab functionality
 */
class NodeWithTabs {
    /**
     * Initialize a node with tab properties
     * @param {LiteGraph.LGraphNode} node - The node being created
     */
    static onNodeCreated(node) {
        // Add properties to the node when it's created
        node.addProperty("enableTabs", false, "boolean", "Enable tabs above the node");
        node.addProperty("tabWidth", TAB_STYLE.defaultWidth, "number", "Width of the main tab");
        node.addProperty("tabXOffset", NODE_METRICS.DEFAULT_TAB_X_OFFSET, "number", "X offset for the main tab");
        node.addProperty("hasSecondTab", false, "boolean", "Enable a second tab");
        node.addProperty("secondTabText", "Send Back", "string", "Text for the second tab");
        node.addProperty("secondTabOffset", NODE_METRICS.DEFAULT_SECOND_TAB_OFFSET, "number", "X offset for second tab");
        node.addProperty("secondTabWidth", TAB_STYLE.defaultWidth, "number", "Width of the second tab");
        
        // Add a method to validate tab configuration
        node.validateTabConfiguration = function() {
            // Ensure tabs don't overlap
            if (TabManager.wouldTabsOverlap(this.properties)) {
                this.properties.secondTabOffset = this.properties.tabXOffset + 
                    this.properties.tabWidth + NODE_METRICS.MIN_TAB_SPACING;
            }
            
            // Validate tab widths
            this.properties.tabWidth = TabManager.getTabWidth({ width: this.properties.tabWidth });
            this.properties.secondTabWidth = TabManager.getTabWidth(
                { secondTabWidth: this.properties.secondTabWidth }, 
                true
            );
        };
    }
}

/**
 * ComfyUI NodeTabExtension
 * 
 * This extension adds customizable tabs to ComfyUI nodes.
 * Features:
 * - Add one or two tabs to a node
 * - Customize tab text, width, and position
 * - Click tabs to bring node to front or send to back
 * 
 * Usage:
 * 1. Enable tabs on a node by setting the "enableTabs" property to true
 * 2. Customize the main tab width and position with "tabWidth" and "tabXOffset"
 * 3. Enable a second tab with "hasSecondTab" and customize with "secondTabText",
 *    "secondTabOffset", and "secondTabWidth"
 */
app.registerExtension({
    name: "FluxContinuum.NodeTabExtension",
    
    // Using the modern nodeCreated hook that works with the latest ComfyUI version
    nodeCreated(node) {
        try {
            // Store original methods to call them when appropriate
            const originalMethods = {
                onNodeCreated: node.onNodeCreated,
                getBounding: node.getBounding,
                isPointInside: node.isPointInside,
                onMouseDown: node.onMouseDown,
                onMouseUp: node.onMouseUp,
                onDrawForeground: node.onDrawForeground,
                onPropertyChanged: node.onPropertyChanged
            };

            // Override onNodeCreated method
            node.onNodeCreated = function() {
                if (originalMethods.onNodeCreated) {
                    originalMethods.onNodeCreated.apply(this, arguments);
                }
                NodeWithTabs.onNodeCreated(this);
            };
            
            // Call onNodeCreated immediately if the node is already created
            if (node.flags && !node._tabsInitialized) {
                NodeWithTabs.onNodeCreated(node);
                node._tabsInitialized = true;
            }

            // Add support for property validation
            node.onPropertyChanged = function(property, value) {
                if (originalMethods.onPropertyChanged) {
                    originalMethods.onPropertyChanged.call(this, property, value);
                }
                
                // If a tab-related property changed, validate the configuration
                const tabProperties = [
                    "tabWidth", "tabXOffset", "hasSecondTab", 
                    "secondTabOffset", "secondTabWidth"
                ];
                
                if (tabProperties.includes(property)) {
                    this.validateTabConfiguration();
                }
            };

            /**
             * Get the bounding box for the node including tabs
             */
            node.getBounding = function(out) {
                if (!this.properties?.enableTabs || this.flags.collapsed) {
                    return originalMethods.getBounding.call(this, out);
                }

                // Validate tab configuration
                this.validateTabConfiguration();

                out = out || new Float32Array(4);
                const [width, height] = this.size;
                const [nodeX, nodeY] = this.pos;

                const rects = [
                    // Body and title
                    {
                        x: nodeX,
                        y: nodeY - NODE_METRICS.TITLE_HEIGHT,
                        width,
                        height: height + NODE_METRICS.TITLE_HEIGHT
                    },
                    // Main tab
                    {
                        x: nodeX + this.properties.tabXOffset,
                        y: nodeY - NODE_METRICS.TAB_HEIGHT,
                        width: this.properties.tabWidth,
                        height: TAB_STYLE.height
                    }
                ];

                if (this.properties.hasSecondTab) {
                    rects.push({
                        x: nodeX + this.properties.secondTabOffset,
                        y: nodeY - NODE_METRICS.TAB_HEIGHT,
                        width: this.properties.secondTabWidth,
                        height: TAB_STYLE.height
                    });
                }

                // Calculate the bounding box
                const result = boundingBoxCalculator.calculate(rects);
                
                // Copy values to the out parameter
                out.set(result);
                return out;
            };

            /**
             * Check if a point is inside the node or its tabs
             */
            node.isPointInside = function(x, y, margin = 0) {
                if (!this.properties?.enableTabs || this.flags.collapsed) {
                    return originalMethods.isPointInside.call(this, x, y, margin);
                }

                // Validate tab configuration
                this.validateTabConfiguration();

                const [nodeX, nodeY] = this.pos;
                const [width, height] = this.size;

                // During rectangle selection, use a simpler hit test that includes the margin
                if (margin > 0) {
                    const boundingBox = this.getBounding();
                    return isInsideRect(
                        x, y,
                        boundingBox[0] - margin,
                        boundingBox[1] - margin,
                        boundingBox[2] + 2 * margin,
                        boundingBox[3] + 2 * margin
                    );
                }

                // For regular clicking, keep the detailed hit testing
                const bodyAndTitle = isInsideRect(x, y, 
                    nodeX, nodeY - NODE_METRICS.TITLE_HEIGHT, 
                    width, height + NODE_METRICS.TITLE_HEIGHT
                );
                if (bodyAndTitle) return true;

                const mainTabHit = isInsideRect(x, y,
                    nodeX + this.properties.tabXOffset, 
                    nodeY - NODE_METRICS.TAB_HEIGHT,
                    this.properties.tabWidth, 
                    TAB_STYLE.height
                );
                if (mainTabHit) return true;

                if (this.properties.hasSecondTab) {
                    return isInsideRect(x, y,
                        nodeX + this.properties.secondTabOffset,
                        nodeY - NODE_METRICS.TAB_HEIGHT,
                        this.properties.secondTabWidth,
                        TAB_STYLE.height
                    );
                }

                return false;
            };

            /**
             * Handle mouse down event on the node or its tabs
             */
            node.onMouseDown = function(event, local_pos, graphCanvas) {
                if (!this.properties?.enableTabs || this.flags.collapsed) {
                    return originalMethods.onMouseDown?.call(this, event, local_pos, graphCanvas) ?? false;
                }

                // Validate tab configuration
                this.validateTabConfiguration();

                const [localX, localY] = local_pos;
                let tabHandled = false;
                let cleanup = null;

                // Check main tab click
                const mainTabHit = isInsideRect(localX, localY,
                    this.properties.tabXOffset, -NODE_METRICS.TAB_HEIGHT,
                    this.properties.tabWidth, TAB_STYLE.height
                );
                if (mainTabHit) {
                    cleanup = TabManager.handleNodeOrder(this, true);
                    tabHandled = true;
                }

                // Check second tab click
                if (!tabHandled && this.properties.hasSecondTab) {
                    const secondTabHit = isInsideRect(localX, localY,
                        this.properties.secondTabOffset, -NODE_METRICS.TAB_HEIGHT,
                        this.properties.secondTabWidth, TAB_STYLE.height
                    );
                    if (secondTabHit) {
                        cleanup = TabManager.handleNodeOrder(this, false);
                        tabHandled = true;
                    }
                }

                // If tab was handled, return early
                if (tabHandled) {
                    // Store cleanup function to be called later
                    this._tabCleanup = cleanup;
                    return true;
                }

                // If no tab was clicked, delegate to original handler
                const originalResult = originalMethods.onMouseDown?.call(this, event, local_pos, graphCanvas) ?? false;
                return originalResult;
            };

            /**
             * Clean up any pending tab operations when mouse is released
             */
            node.onMouseUp = function(event) {
                // Call any pending tab cleanup
                if (this._tabCleanup) {
                    this._tabCleanup();
                    this._tabCleanup = null;
                }
                
                // Call original handler
                if (originalMethods.onMouseUp) {
                    return originalMethods.onMouseUp.call(this, event);
                }
            };

            /**
             * Draw the tabs above the node
             */
            node.onDrawForeground = function(ctx) {
                // Call original method first
                if (originalMethods.onDrawForeground) {
                    originalMethods.onDrawForeground.call(this, ctx);
                }

                if (!this.properties?.enableTabs || this.flags.collapsed) {
                    return;
                }

                // Validate tab configuration
                this.validateTabConfiguration();

                ctx.save();
                const baseY = -NODE_METRICS.TAB_HEIGHT;

                // Draw main tab
                TabManager.drawTab(ctx, this.properties.tabXOffset, baseY, this.title, this.properties.tabWidth);

                // Draw second tab if enabled
                if (this.properties.hasSecondTab) {
                    TabManager.drawTab(ctx, this.properties.secondTabOffset, baseY, 
                        this.properties.secondTabText, this.properties.secondTabWidth);
                }

                ctx.restore();
            };
        } catch (error) {
            console.error("NodeTabExtension: Error initializing node", error);
        }
    }
});

================================================
FILE: web/textversions.js
================================================
import { app } from "../../../scripts/app.js";

/**
 * Default and limit configurations
 */
const CONFIG = {
    DEFAULT_VERSION_AMOUNT: 5,
    MAX_VERSION_AMOUNT: 100,
    MIN_VERSION_AMOUNT: 1
};

/**
 * Visual style configuration for version tabs
 */
const TAB_STYLE = {
    width: 40,
    height: 18,
    fontSize: 10,
    normalColor: "#333333",
    selectedColor: "#666666",
    textColor: "white",
    borderRadius: 4,
    spacing: 10,
    offset: 10,
    yPosition: 10
};

function createTabConfig(numVersions) {
    return {
        ...TAB_STYLE,
        labels: Array.from({length: numVersions}, (_, i) => (i + 1).toString())
    };
}

function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

app.registerExtension({
    name: "FluxContinuum.TextVersions",
    
    // Modern API uses nodeCreated instead of beforeRegisterNodeDef
    nodeCreated(node) {
        // Only apply to TextVersions nodes
        if (node.type !== "TextVersions" && node.comfyClass !== "TextVersions") return;
        
        // Store original methods
        const onNodeCreated = node.onNodeCreated;
        const onDrawBackground = node.onDrawBackground;
        const getBounding = node.getBounding;
        const onSerialize = node.onSerialize;
        const onConfigure = node.onConfigure;
        const onMouseDown = node.onMouseDown; // IMPORTANT: Store the original onMouseDown

        // Add onNodeCreated to the node
        node.onNodeCreated = function() {
            if (onNodeCreated) {
                onNodeCreated.apply(this, arguments);
            }

            // Add properties
            this.addProperty("versionAmount", CONFIG.DEFAULT_VERSION_AMOUNT, "number");
            
            // Initialize node
            if (!this.properties) {
                this.properties = {};
            }
            this.properties.versionAmount = this.properties.versionAmount || CONFIG.DEFAULT_VERSION_AMOUNT;
            
            // Create tab config based on number of versions
            this.tabConfig = createTabConfig(this.properties.versionAmount);
            
            // Initialize tab state and content
            this.activeTab = 0;
            this.tabContents = Array(this.properties.versionAmount).fill("");
            
            // Store reference to the text widget
            this.textWidget = this.widgets.find(w => w.name === "text");
            
            // Initial content setup
            if (this.textWidget) {
                this.textWidget.value = this.tabContents[this.activeTab];
                
                // Add debounced change event listener
                if (this.textWidget.inputEl) {
                    const saveContent = debounce(() => {
                        this.tabContents[this.activeTab] = this.textWidget.value;
                    }, 300);

                    this.textWidget.inputEl.addEventListener("input", saveContent);
                }
            }
        };

        node.onPropertyChanged = function(name, value) {
            if (name === "versionAmount") {
                const newValue = Math.max(CONFIG.MIN_VERSION_AMOUNT, 
                    Math.min(CONFIG.MAX_VERSION_AMOUNT, Math.floor(value)));
                const oldContents = [...this.tabContents];
                this.properties.versionAmount = newValue;
                this.tabConfig = createTabConfig(newValue);
                this.tabContents = Array(newValue).fill("").map((_, i) => oldContents[i] || "");
                this.activeTab = Math.min(this.activeTab, newValue - 1);
                if (this.textWidget) {
                    this.textWidget.value = this.tabContents[this.activeTab];
                }
                this.setDirtyCanvas(true);
            }
        };

        node.onDrawBackground = function(ctx) {
            if (onDrawBackground) {
                onDrawBackground.apply(this, arguments);
            }
            
            if (this.flags.collapsed) return;
            
            ctx.save();

            // Create clipping region for overflow
            const nodeWidth = this.size[0];
            const clipPadding = 10;
            ctx.beginPath();
            ctx.rect(clipPadding, this.tabConfig.yPosition - 5, 
                    nodeWidth - (2 * clipPadding), 
                    this.tabConfig.height + 10);
            ctx.clip();
            
            // Draw tabs
            this.tabConfig.labels.forEach((label, i) => {
                const x = this.tabConfig.offset + (this.tabConfig.width + this.tabConfig.spacing) * i;
                const y = this.tabConfig.yPosition;
                
                ctx.fillStyle = i === this.activeTab ? this.tabConfig.selectedColor : this.tabConfig.normalColor;
                ctx.beginPath();
                ctx.roundRect(x, y, this.tabConfig.width, this.tabConfig.height, this.tabConfig.borderRadius);
                ctx.fill();
                
                ctx.fillStyle = this.tabConfig.textColor;
                ctx.font = `${this.tabConfig.fontSize}px Arial`;
                ctx.textAlign = "center";
                ctx.textBaseline = "middle";
                ctx.fillText(label, x + this.tabConfig.width / 2, y + this.tabConfig.height / 2);
            });
            
            ctx.restore();
        };
        
        node.onMouseDown = function(event, local_pos, graphCanvas) {
            const [x, y] = local_pos;
            const { yPosition, height, width, spacing, offset, labels } = this.tabConfig;
            
            if (y >= yPosition && y <= yPosition + height) {
                for (let i = 0; i < labels.length; i++) {
                    const tabX = offset + (width + spacing) * i;
                    if (x >= tabX && x <= tabX + width) {
                        if (i === this.activeTab) return false;
                        
                        if (this.textWidget) {
                            this.tabContents[this.activeTab] = this.textWidget.value;
                        }
                        
                        this.activeTab = i;
                        
                        if (this.textWidget) {
                            this.textWidget.value = this.tabContents[i];
                        }
                        
                        this.setDirtyCanvas(true);
                        return true;
                    }
                }
            }
            
            // IMPORTANT: Call the original onMouseDown if we didn't handle the click
            if (onMouseDown) {
                return onMouseDown.apply(this, arguments);
            }
            
            return false;
        };
        
        node.getBounding = function() {
            const bounds = getBounding?.apply(this, arguments) || new Float32Array(4);
            const tabsHeight = Math.abs(this.tabConfig.yPosition) + this.tabConfig.height;
            bounds[1] -= tabsHeight;
            bounds[3] += tabsHeight;
            return bounds;
        };

        node.onSerialize = function(o) {
            if (onSerialize) {
                onSerialize.apply(this, arguments);
            }
            o.tabContents = this.tabContents;
            o.activeTab = this.activeTab;
        };

        node.onConfigure = function(o) {
            if (onConfigure) {
                onConfigure.apply(this, arguments);
            }
            
            this.tabConfig = createTabConfig(this.properties.versionAmount);
            
            if (o.tabContents && Array.isArray(o.tabContents)) {
                this.tabContents = Array(this.properties.versionAmount).fill("").map((_, i) => o.tabContents[i] || "");
                this.activeTab = o.activeTab >= 0 && o.activeTab < this.properties.versionAmount ? o.activeTab : 0;
                if (this.textWidget) {
                    this.textWidget.value = this.tabContents[this.activeTab];
                }
            }
        };
    }
});

================================================
FILE: workflow/Flux+ 1.3_release.json
================================================
{
  "last_node_id": 3156,
  "last_link_id": 4684,
  "nodes": [
    {
      "id": 1026,
      "type": "ImagePass",
      "pos": [
        660,
        220
      ],
      "size": [
        210,
        30
      ],
      "flags": {
        "collapsed": true
      },
      "order": 154,
      "mode": 0,
      "inputs": [
        {
          "name": "image",
          "type": "IMAGE",
          "link": 1744,
          "shape": 7
        }
      ],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            1745
          ],
          "slot_index": 0,
          "shape": 3
        }
      ],
      "properties": {
        "Node name for S&R": "ImagePass",
        "enableTabs": false,
        "tabWidth": 65,
        "tabXOffset": 10,
        "hasSecondTab": false,
        "secondTabText": "Send Back",
        "secondTabOffset": 80,
        "secondTabWidth": 65
      },
      "widgets_values": []
    },
    {
      "id": 1113,
      "type": "ImagePass",
      "pos": [
        5567.8857421875,
        6493.90966796875
      ],
      "size": [
        210,
        30
      ],
      "flags": {
        "collapsed": true
      },
      "order": 158,
      "mode": 0,
      "inputs": [
        {
          "name": "image",
          "type": "IMAGE",
          "link": 4511,
          "shape": 7
        }
      ],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            1859
          ],
          "slot_index": 0,
          "shape": 3
        }
      ],
      "properties": {
        "Node name for S&R": "ImagePass",
        "enableTabs": false,
        "tabWidth": 65,
        "tabXOffset": 10,
        "hasSecondTab": false,
        "secondTabText": "Send Back",
        "secondTabOffset": 80,
        "secondTabWidth": 65
      },
      "widgets_values": []
    },
    {
      "id": 1346,
      "type": "ImagePass",
      "pos": [
        1790,
        1000
      ],
      "size": [
        140,
        30
      ],
      "flags": {
        "collapsed": true
      },
      "order": 329,
      "mode": 0,
      "inputs": [
        {
          "name": "image",
          "type": "IMAGE",
          "link": 4607,
          "shape": 7
        }
      ],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            2073
          ],
          "slot_index": 0,
          "shape": 3
        }
      ],
      "properties": {
        "Node name for S&R": "ImagePass",
        "enableTabs": false,
        "tabWidth": 65,
        "tabXOffset": 10,
        "hasSecondTab": false,
        "secondTabText": "Send Back",
        "secondTabOffset": 80,
        "secondTabWidth": 65
      },
      "widgets_values": []
    },
    {
      "id": 644,
      "type": "SetNode",
      "pos": [
        4493.2578125,
        240.7759246826172
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 312,
      "mode": 0,
      "inputs": [
        {
          "name": "BASIC_PIPE",
          "type": "BASIC_PIPE",
          "link": 1187
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_pipe",
      "properties": {
        "previousName": "pipe"
      },
      "widgets_values": [
        "pipe"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 712,
      "type": "SetNode",
      "pos": [
        5569.60400390625,
        1606.9547119140625
      ],
      "size": [
        210,
        60
      ],
      "flags": {
        "collapsed": true
      },
      "order": 346,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 2030
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_Output - inpaint",
      "properties": {
        "previousName": "Output - inpaint"
      },
      "widgets_values": [
        "Output - inpaint"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 715,
      "type": "SetNode",
      "pos": [
        5973.2578125,
        220.7759552001953
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 337,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 1538
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_Output - txt2img",
      "properties": {
        "previousName": "Output - txt2img"
      },
      "widgets_values": [
        "Output - txt2img"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 716,
      "type": "SetNode",
      "pos": [
        5776.8857421875,
        6489.90966796875
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 232,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 1859
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_final",
      "properties": {
        "previousName": "final"
      },
      "widgets_values": [
        "final"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 723,
      "type": "SetNode",
      "pos": [
        1431.1396484375,
        1160.7176513671875
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 223,
      "mode": 0,
      "inputs": [
        {
          "name": "STRING",
          "type": "STRING",
          "link": 3492
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_prompt",
      "properties": {
        "previousName": "prompt"
      },
      "widgets_values": [
        "prompt"
      ]
    },
    {
      "id": 729,
      "type": "SetNode",
      "pos": [
        5428.25927734375,
        3585.4765625
      ],
      "size": [
        252,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 331,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 2177
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_Output - ultimate upscaler",
      "properties": {
        "previousName": "Output - ultimate upscaler"
      },
      "widgets_values": [
        "Output - ultimate upscaler"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 788,
      "type": "SetNode",
      "pos": [
        1590.7579345703125,
        1348.9764404296875
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 224,
      "mode": 0,
      "inputs": [
        {
          "name": "STRING",
          "type": "STRING",
          "link": 3493
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": [],
          "slot_index": 0
        }
      ],
      "title": "Set_tags",
      "properties": {
        "previousName": "tags"
      },
      "widgets_values": [
        "tags"
      ]
    },
    {
      "id": 832,
      "type": "SetNode",
      "pos": [
        3613.2578125,
        366.5325927734375
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 205,
      "mode": 0,
      "inputs": [
        {
          "name": "VAE",
          "type": "VAE",
          "link": 4425
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_vae",
      "properties": {
        "previousName": "vae"
      },
      "widgets_values": [
        "vae"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 873,
      "type": "SetNode",
      "pos": [
        5448.25927734375,
        4285.47607421875
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 332,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 2206
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_Output - detailer",
      "properties": {
        "previousName": "Output - detailer"
      },
      "widgets_values": [
        "Output - detailer"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 925,
      "type": "SetNode",
      "pos": [
        680,
        1180
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 207,
      "mode": 0,
      "inputs": [
        {
          "name": "INT",
          "type": "INT",
          "link": 1589
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_seed",
      "properties": {
        "previousName": "seed"
      },
      "widgets_values": [
        "seed"
      ]
    },
    {
      "id": 1023,
      "type": "SetNode",
      "pos": [
        670,
        220
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 230,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 1745
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_send",
      "properties": {
        "previousName": "send"
      },
      "widgets_values": [
        "send"
      ]
    },
    {
      "id": 1061,
      "type": "SetNode",
      "pos": [
        7663.2578125,
        254.91954040527344
      ],
      "size": [
        302.3999938964844,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 362,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 1783
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_Output - txt2img_noise injection",
      "properties": {
        "previousName": "Output - txt2img_noise injection"
      },
      "widgets_values": [
        "Output - txt2img_noise injection"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 1075,
      "type": "SetNode",
      "pos": [
        4695.26611328125,
        999.5366821289062
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 313,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 1802
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_Output - img2img",
      "properties": {
        "previousName": "Output - img2img"
      },
      "widgets_values": [
        "Output - img2img"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 1095,
      "type": "SetNode",
      "pos": [
        480,
        1070
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 211,
      "mode": 0,
      "inputs": [
        {
          "name": "INT",
          "type": "INT",
          "link": 2305
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_batch",
      "properties": {
        "previousName": "batch"
      },
      "widgets_values": [
        "batch"
      ]
    },
    {
      "id": 1105,
      "type": "SetNode",
      "pos": [
        3620,
        220
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 225,
      "mode": 0,
      "inputs": [
        {
          "name": "MODEL",
          "type": "MODEL",
          "link": 3494
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_model",
      "properties": {
        "previousName": "model"
      },
      "widgets_values": [
        "model"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 1114,
      "type": "SetNode",
      "pos": [
        3620,
        500
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 204,
      "mode": 0,
      "inputs": [
        {
          "name": "CLIP",
          "type": "CLIP",
          "link": 4424
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_clip",
      "properties": {
        "previousName": "clip"
      },
      "widgets_values": [
        "clip"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 1116,
      "type": "SetNode",
      "pos": [
        650,
        350
      ],
      "size": [
        285.375,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 233,
      "mode": 0,
      "inputs": [
        {
          "name": "MODEL",
          "type": "MODEL",
          "link": 1863
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_loras",
      "properties": {
        "previousName": "loras"
      },
      "widgets_values": [
        "loras"
      ]
    },
    {
      "id": 1217,
      "type": "SetNode",
      "pos": [
        1940,
        650
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 208,
      "mode": 0,
      "inputs": [
        {
          "name": "FLOAT",
          "type": "FLOAT",
          "link": 2000
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_denoise",
      "properties": {
        "previousName": "denoise"
      },
      "widgets_values": [
        "denoise"
      ]
    },
    {
      "id": 1271,
      "type": "SetNode",
      "pos": [
        4135.2470703125,
        6430.484375
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 254,
      "mode": 0,
      "inputs": [
        {
          "name": "COMBO",
          "type": "COMBO",
          "link": 3464
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_scheduler",
      "properties": {
        "previousName": "scheduler"
      },
      "widgets_values": [
        "scheduler"
      ]
    },
    {
      "id": 1272,
      "type": "SetNode",
      "pos": [
        4135.2470703125,
        6540.484375
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 255,
      "mode": 0,
      "inputs": [
        {
          "name": "STRING",
          "type": "STRING",
          "link": 3466
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_scheduler_name",
      "properties": {
        "previousName": "scheduler_name"
      },
      "widgets_values": [
        "scheduler_name"
      ]
    },
    {
      "id": 1280,
      "type": "SetNode",
      "pos": [
        670,
        750
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 209,
      "mode": 0,
      "inputs": [
        {
          "name": "INT",
          "type": "INT",
          "link": 2002
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_steps",
      "properties": {
        "previousName": "steps"
      },
      "widgets_values": [
        "steps"
      ]
    },
    {
      "id": 1281,
      "type": "SetNode",
      "pos": [
        650,
        860
      ],
      "size": [
        210,
        60
      ],
      "flags": {
        "collapsed": true
      },
      "order": 210,
      "mode": 0,
      "inputs": [
        {
          "name": "FLOAT",
          "type": "FLOAT",
          "link": 2001
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_guidance",
      "properties": {
        "previousName": "guidance"
      },
      "widgets_values": [
        "guidance"
      ]
    },
    {
      "id": 1320,
      "type": "SetNode",
      "pos": [
        1920,
        240
      ],
      "size": [
        210,
        60
      ],
      "flags": {
        "collapsed": true,
        "pinned": true
      },
      "order": 229,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 4446
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": [],
          "slot_index": 0
        }
      ],
      "title": "Set_loadImage",
      "properties": {
        "previousName": "loadImage"
      },
      "widgets_values": [
        "loadImage"
      ]
    },
    {
      "id": 1321,
      "type": "SetNode",
      "pos": [
        1940,
        300
      ],
      "size": [
        210,
        60
      ],
      "flags": {
        "collapsed": true,
        "pinned": true
      },
      "order": 227,
      "mode": 0,
      "inputs": [
        {
          "name": "MASK",
          "type": "MASK",
          "link": 4515
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_mask",
      "properties": {
        "previousName": "mask"
      },
      "widgets_values": [
        "mask"
      ]
    },
    {
      "id": 1330,
      "type": "SetNode",
      "pos": [
        1881.1695556640625,
        1443.02587890625
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 212,
      "mode": 0,
      "inputs": [
        {
          "name": "UPSCALE_MODEL",
          "type": "UPSCALE_MODEL",
          "link": 2361
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_upscale_model",
      "properties": {
        "previousName": ""
      },
      "widgets_values": [
        "upscale_model"
      ]
    },
    {
      "id": 1331,
      "type": "SetNode",
      "pos": [
        1920,
        1300
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true,
        "pinned": true
      },
      "order": 215,
      "mode": 0,
      "inputs": [
        {
          "name": "INT",
          "type": "INT",
          "link": 3215
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_res multiply",
      "properties": {
        "previousName": "res multiply"
      },
      "widgets_values": [
        "res multiply"
      ]
    },
    {
      "id": 1345,
      "type": "SetNode",
      "pos": [
        1800,
        1050
      ],
      "size": [
        268.79998779296875,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 342,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 2073
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_Output - imgload_prep",
      "properties": {
        "previousName": "Output - imgload_prep"
      },
      "widgets_values": [
        "Output - imgload_prep"
      ]
    },
    {
      "id": 1392,
      "type": "SetNode",
      "pos": [
        4448.25927734375,
        3765.4775390625
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 294,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 3433
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_Output - upscaler",
      "properties": {
        "previousName": "Output - upscaler"
      },
      "widgets_values": [
        "Output - upscaler"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 1508,
      "type": "SetNode",
      "pos": [
        4135.2470703125,
        6470.48388671875
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 252,
      "mode": 0,
      "inputs": [
        {
          "name": "COMBO",
          "type": "COMBO",
          "link": 3463
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_sampler",
      "properties": {
        "previousName": "sampler"
      },
      "widgets_values": [
        "sampler"
      ]
    },
    {
      "id": 1510,
      "type": "SetNode",
      "pos": [
        4135.2470703125,
        6500.48388671875
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 253,
      "mode": 0,
      "inputs": [
        {
          "name": "STRING",
          "type": "STRING",
          "link": 3465
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_sampler_name",
      "properties": {
        "previousName": "sampler_name"
      },
      "widgets_values": [
        "sampler_name"
      ]
    },
    {
      "id": 1518,
      "type": "SetNode",
      "pos": [
        5603.2578125,
        600.77587890625
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 240,
      "mode": 0,
      "inputs": [
        {
          "name": "SAMPLER",
          "type": "SAMPLER",
          "link": 2335
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_sampler_convert",
      "properties": {
        "previousName": "sampler_convert"
      },
      "widgets_values": [
        "sampler_convert"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 1617,
      "type": "SetNode",
      "pos": [
        650,
        940
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 213,
      "mode": 0,
      "inputs": [
        {
          "name": "FLOAT",
          "type": "FLOAT",
          "link": 2418
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_max shift",
      "properties": {
        "previousName": "max shift"
      },
      "widgets_values": [
        "max shift"
      ]
    },
    {
      "id": 2122,
      "type": "SetNode",
      "pos": [
        6098.259765625,
        6450.18798828125
      ],
      "size": [
        319.20001220703125,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 0,
      "mode": 0,
      "inputs": [
        {
          "name": "*",
          "type": "*",
          "link": null
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_Output END -----------------------",
      "properties": {
        "previousName": "Output END -----------------------"
      },
      "widgets_values": [
        "Output END -----------------------"
      ]
    },
    {
      "id": 2129,
      "type": "SetNode",
      "pos": [
        6098.259765625,
        6500.18798828125
      ],
      "size": [
        310.79998779296875,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 1,
      "mode": 0,
      "inputs": [
        {
          "name": "*",
          "type": "*",
          "link": null
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_--------- SELECT Output ---------",
      "properties": {
        "previousName": "--------- SELECT Output ---------"
      },
      "widgets_values": [
        "--------- SELECT Output ---------"
      ]
    },
    {
      "id": 2273,
      "type": "SetNode",
      "pos": [
        7498.462890625,
        5117.22998046875
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 352,
      "mode": 0,
      "inputs": [
        {
          "name": "CONDITIONING",
          "type": "CONDITIONING",
          "link": 3356
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_cond_cnApplied",
      "properties": {
        "previousName": "cond_cnApplied"
      },
      "widgets_values": [
        "cond_cnApplied"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 2285,
      "type": "SetNode",
      "pos": [
        5773.65234375,
        6065.1103515625
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 350,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 3295
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_Output - preprocessor",
      "properties": {
        "previousName": "Output - preprocessor"
      },
      "widgets_values": [
        "Output - preprocessor"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 2287,
      "type": "SetNode",
      "pos": [
        5293.65283203125,
        5635.1103515625
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 325,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 3296
        }
      ],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            4032
          ],
          "slot_index": 0
        }
      ],
      "title": "Set_pre_OpenPose",
      "properties": {
        "previousName": "pre_OpenPose"
      },
      "widgets_values": [
        "pre_OpenPose"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 2288,
      "type": "SetNode",
      "pos": [
        5273.65283203125,
        5915.1103515625
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 324,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 3297
        }
      ],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            4031
          ],
          "slot_index": 0
        }
      ],
      "title": "Set_pre_Depth",
      "properties": {
        "previousName": "pre_Depth"
      },
      "widgets_values": [
        "pre_Depth"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 2289,
      "type": "SetNode",
      "pos": [
        5274.7216796875,
        6064.35986328125
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 323,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 4026
        }
      ],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            4030
          ],
          "slot_index": 0
        }
      ],
      "title": "Set_pre_Lineart",
      "properties": {
        "previousName": "pre_Lineart"
      },
      "widgets_values": [
        "pre_Lineart"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 2290,
      "type": "SetNode",
      "pos": [
        1831.615234375,
        804.8087768554688
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 216,
      "mode": 0,
      "inputs": [
        {
          "name": "VEC3",
          "type": "VEC3",
          "link": 3299
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_cn_slider_depth",
      "properties": {
        "previousName": "cn_slider_depth"
      },
      "widgets_values": [
        "cn_slider_depth"
      ]
    },
    {
      "id": 2311,
      "type": "SetNode",
      "pos": [
        1791.615234375,
        804.8087768554688
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 218,
      "mode": 0,
      "inputs": [
        {
          "name": "VEC3",
          "type": "VEC3",
          "link": 3347
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_cn_slider_lineart",
      "properties": {
        "previousName": "cn_slider_lineart"
      },
      "widgets_values": [
        "cn_slider_lineart"
      ]
    },
    {
      "id": 2313,
      "type": "SetNode",
      "pos": [
        1811.615234375,
        804.8087768554688
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 219,
      "mode": 0,
      "inputs": [
        {
          "name": "VEC3",
          "type": "VEC3",
          "link": 3348
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_cn_slider_pose",
      "properties": {
        "previousName": "cn_slider_pose"
      },
      "widgets_values": [
        "cn_slider_pose"
      ]
    },
    {
      "id": 2315,
      "type": "SetNode",
      "pos": [
        1900,
        798.9000244140625
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 220,
      "mode": 0,
      "inputs": [
        {
          "name": "VEC3",
          "type": "VEC3",
          "link": 3349
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_cn_slider_tile",
      "properties": {
        "previousName": "cn_slider_tile"
      },
      "widgets_values": [
        "cn_slider_tile"
      ]
    },
    {
      "id": 2342,
      "type": "SetNode",
      "pos": [
        7490,
        5368
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 353,
      "mode": 0,
      "inputs": [
        {
          "name": "SEGS",
          "type": "SEGS",
          "link": 3403
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_segs_cnApplied",
      "properties": {
        "previousName": "segs_cnApplied"
      },
      "widgets_values": [
        "segs_cnApplied"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 2428,
      "type": "SetNode",
      "pos": [
        650,
        1350
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 221,
      "mode": 0,
      "inputs": [
        {
          "name": "COMBO",
          "type": "COMBO",
          "link": 3447
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_resolution",
      "properties": {
        "previousName": "resolution"
      },
      "widgets_values": [
        "resolution"
      ]
    },
    {
      "id": 2437,
      "type": "SetNode",
      "pos": [
        610,
        620
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 222,
      "mode": 0,
      "inputs": [
        {
          "name": "SAMPLER_PARAMS",
          "type": "SAMPLER_PARAMS",
          "link": 3460
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_sampler params",
      "properties": {
        "previousName": "sampler params"
      },
      "widgets_values": [
        "sampler params"
      ]
    },
    {
      "id": 2475,
      "type": "SetNode",
      "pos": [
        3610,
        590
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 217,
      "mode": 0,
      "inputs": [
        {
          "name": "CONTROL_NET",
          "type": "CONTROL_NET",
          "link": 3505
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_controlnet_model",
      "properties": {
        "previousName": "controlnet_model"
      },
      "widgets_values": [
        "controlnet_model"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 2507,
      "type": "SetNode",
      "pos": [
        4623.2578125,
        550.7760620117188
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 249,
      "mode": 0,
      "inputs": [
        {
          "name": "INT",
          "type": "INT",
          "link": 3543
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_width",
      "properties": {
        "previousName": "width"
      },
      "widgets_values": [
        "width"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 2508,
      "type": "SetNode",
      "pos": [
        4623.2578125,
        600.77587890625
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 251,
      "mode": 0,
      "inputs": [
        {
          "name": "INT",
          "type": "INT",
          "link": 3544
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_height",
      "properties": {
        "previousName": "height"
      },
      "widgets_values": [
        "height"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 2784,
      "type": "SetNode",
      "pos": [
        1900,
        350
      ],
      "size": [
        210,
        60
      ],
      "flags": {
        "collapsed": true
      },
      "order": 228,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 4469
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_loadImage_cn",
      "properties": {
        "previousName": "loadImage_cn"
      },
      "widgets_values": [
        "loadImage_cn"
      ]
    },
    {
      "id": 3032,
      "type": "SetNode",
      "pos": [
        4623.2578125,
        500.77618408203125
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 248,
      "mode": 0,
      "inputs": [
        {
          "name": "LATENT",
          "type": "LATENT",
          "link": 4514
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_latent",
      "properties": {
        "previousName": "latent"
      },
      "widgets_values": [
        "latent"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 3057,
      "type": "SetNode",
      "pos": [
        5767.1435546875,
        2728.443115234375
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 339,
      "mode": 0,
      "inputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "link": 4543
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_Output - outpaint",
      "properties": {
        "previousName": "Output - outpaint"
      },
      "widgets_values": [
        "Output - outpaint"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 3094,
      "type": "SetNode",
      "pos": [
        3613.2578125,
        696.5328979492188
      ],
      "size": [
        260.3999938964844,
        60
      ],
      "flags": {
        "collapsed": true
      },
      "order": 226,
      "mode": 0,
      "inputs": [
        {
          "name": "CONTROL_NET",
          "type": "CONTROL_NET",
          "link": 4592
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_controlnet_model_inpainting",
      "properties": {
        "previousName": "controlnet_model_inpainting"
      },
      "widgets_values": [
        "controlnet_model_inpainting"
      ],
      "color": "#322",
      "bgcolor": "#533"
    },
    {
      "id": 3103,
      "type": "SetNode",
      "pos": [
        1830,
        1120
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 330,
      "mode": 0,
      "inputs": [
        {
          "name": "MASK",
          "type": "MASK",
          "link": 4608
        }
      ],
      "outputs": [
        {
          "name": "*",
          "type": "*",
          "links": null
        }
      ],
      "title": "Set_mask_outpainting",
      "properties": {
        "previousName": "mask_outpainting"
      },
      "widgets_values": [
        "mask_outpainting"
      ]
    },
    {
      "id": 596,
      "type": "GetNode",
      "pos": [
        5313.2578125,
        370.7759704589844
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 2,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "INT",
          "type": "INT",
          "links": [
            1360
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_seed",
      "properties": {},
      "widgets_values": [
        "seed"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 645,
      "type": "GetNode",
      "pos": [
        4718.25927734375,
        4355.474609375
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 3,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "BASIC_PIPE",
          "type": "BASIC_PIPE",
          "links": [
            3152
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_pipe",
      "properties": {},
      "widgets_values": [
        "pipe"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 708,
      "type": "GetNode",
      "pos": [
        4928.25927734375,
        3715.477294921875
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 4,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "INT",
          "type": "INT",
          "links": [
            2173
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_seed",
      "properties": {},
      "widgets_values": [
        "seed"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 713,
      "type": "GetNode",
      "pos": [
        480,
        200
      ],
      "size": [
        310,
        60
      ],
      "flags": {
        "collapsed": false,
        "pinned": true
      },
      "order": 5,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            1744
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_Output - txt2img",
      "properties": {},
      "widgets_values": [
        "Output - txt2img"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 717,
      "type": "GetNode",
      "pos": [
        1000,
        150
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 6,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            1372,
            3426,
            4414
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_final",
      "properties": {},
      "widgets_values": [
        "final"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 724,
      "type": "GetNode",
      "pos": [
        3953.2578125,
        590.7759399414062
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 7,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "STRING",
          "type": "STRING",
          "links": [
            1442
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_prompt",
      "properties": {},
      "widgets_values": [
        "prompt"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 726,
      "type": "GetNode",
      "pos": [
        4839.33056640625,
        6439.1826171875
      ],
      "size": [
        313.42822265625,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 8,
      "mode": 4,
      "inputs": [],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            2380
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_final",
      "properties": {},
      "widgets_values": [
        "final"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 793,
      "type": "GetNode",
      "pos": [
        3963.2578125,
        550.7760620117188
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 9,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "STRING",
          "type": "STRING",
          "links": [
            1443
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_tags",
      "properties": {},
      "widgets_values": [
        "tags"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 933,
      "type": "GetNode",
      "pos": [
        4533.8857421875,
        3681.912841796875
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 10,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "BASIC_PIPE",
          "type": "BASIC_PIPE",
          "links": [
            1599
          ]
        }
      ],
      "title": "Get_pipe",
      "properties": {},
      "widgets_values": [
        "pipe"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 965,
      "type": "GetNode",
      "pos": [
        3725.26708984375,
        999.5366821289062
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 11,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "VAE",
          "type": "VAE",
          "links": [
            1680,
            1804
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_vae",
      "properties": {},
      "widgets_values": [
        "vae"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 1022,
      "type": "GetNode",
      "pos": [
        5396.8857421875,
        6493.90966796875
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 12,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "IMAGE",
          "type": "IMAGE",
          "links": [
            4511
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_send",
      "properties": {},
      "widgets_values": [
        "send"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 1057,
      "type": "GetNode",
      "pos": [
        6263.2578125,
        274.9195251464844
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 13,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "INT",
          "type": "INT",
          "links": [
            1775
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_seed",
      "properties": {},
      "widgets_values": [
        "seed"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 1064,
      "type": "GetNode",
      "pos": [
        7333.2578125,
        214.91949462890625
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 14,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "VAE",
          "type": "VAE",
          "links": [
            1786
          ]
        }
      ],
      "title": "Get_vae",
      "properties": {},
      "widgets_values": [
        "vae"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 1079,
      "type": "GetNode",
      "pos": [
        4105.26611328125,
        1049.53662109375
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 15,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "MODEL",
          "type": "MODEL",
          "links": [
            3363
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_loras",
      "properties": {},
      "widgets_values": [
        "loras"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 1086,
      "type": "GetNode",
      "pos": [
        3965.26611328125,
        1219.53662109375
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 16,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "INT",
          "type": "INT",
          "links": [
            1813
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_seed",
      "properties": {},
      "widgets_values": [
        "seed"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 1099,
      "type": "GetNode",
      "pos": [
        6333.2578125,
        404.919677734375
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 17,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "LATENT",
          "type": "LATENT",
          "links": [
            2105
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_latent",
      "properties": {},
      "widgets_values": [
        "latent"
      ],
      "color": "#232",
      "bgcolor": "#353"
    },
    {
      "id": 1106,
      "type": "GetNode",
      "pos": [
        650,
        350
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 18,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "MODEL",
          "type": "MODEL",
          "links": [
            3092
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_model",
      "properties": {},
      "widgets_values": [
        "model"
      ]
    },
    {
      "id": 1115,
      "type": "GetNode",
      "pos": [
        660,
        350
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 19,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "CLIP",
          "type": "CLIP",
          "links": [
            3432
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_clip",
      "properties": {},
      "widgets_values": [
        "clip"
      ]
    },
    {
      "id": 1117,
      "type": "GetNode",
      "pos": [
        4143.2578125,
        370.7759704589844
      ],
      "size": [
        210,
        58
      ],
      "flags": {
        "collapsed": true
      },
      "order": 20,
      "mode": 0,
      "inputs": [],
      "outputs": [
        {
          "name": "MODEL",
          "type": "MODEL",
          "links": [
            1864,
            3293
          ],
          "slot_index": 0
        }
      ],
      "title": "Get_loras",
      "properties": {},
      "widgets_values": [
        "loras"
      ],
     
Download .txt
gitextract_si5rgbko/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── publish_action.yml
├── LICENSE
├── README.md
├── __init__.py
├── misc.py
├── pyproject.toml
├── web/
│   ├── getsetorder.js
│   ├── guidanceversions.js
│   ├── help.js
│   ├── hint.js
│   ├── imagedisplay.js
│   ├── imagetransfershortcut.js
│   ├── impactpackfix.js
│   ├── outputgetnode.js
│   ├── samplerversions.js
│   ├── tabs.js
│   └── textversions.js
└── workflow/
    ├── Flux+ 1.3_release.json
    ├── Flux+ 1.4.4_release.json
    ├── Flux+ 1.4.5_release.json
    ├── Flux+ 1.6.4_release.json
    ├── Flux+ 1.7.0_release.json
    ├── Flux+ 1.7.1_beta.json
    └── Flux+ Light 1.0.0_release.json
Download .txt
SYMBOL INDEX (154 symbols across 11 files)

FILE: misc.py
  class AnyType (line 15) | class AnyType(str):
    method __ne__ (line 16) | def __ne__(self, __value: object) -> bool:
  class DenoiseSlider (line 21) | class DenoiseSlider:
    method INPUT_TYPES (line 23) | def INPUT_TYPES(s):
    method execute (line 37) | def execute(self, value):
  class StepSlider (line 40) | class StepSlider:
    method INPUT_TYPES (line 42) | def INPUT_TYPES(s):
    method execute (line 52) | def execute(self, value):
  class BatchSlider (line 56) | class BatchSlider:
    method INPUT_TYPES (line 58) | def INPUT_TYPES(s):
    method execute (line 68) | def execute(self, value):
  class ResolutionMultiplySlider (line 72) | class ResolutionMultiplySlider:
    method INPUT_TYPES (line 74) | def INPUT_TYPES(s):
    method execute (line 84) | def execute(self, value):
  class GPUSlider (line 87) | class GPUSlider:
    method INPUT_TYPES (line 89) | def INPUT_TYPES(s):
    method execute (line 99) | def execute(self, value):
  class SelectFromBatch (line 103) | class SelectFromBatch:
    method INPUT_TYPES (line 105) | def INPUT_TYPES(s):
    method execute (line 115) | def execute(self, value):
  class GuidanceSlider (line 119) | class GuidanceSlider:
    method INPUT_TYPES (line 121) | def INPUT_TYPES(s):
    method execute (line 133) | def execute(self, value):
  class MaxShiftSlider (line 137) | class MaxShiftSlider:
    method INPUT_TYPES (line 139) | def INPUT_TYPES(s):
    method execute (line 151) | def execute(self, value):
  class ControlNetSlider (line 155) | class ControlNetSlider:
    method INPUT_TYPES (line 157) | def INPUT_TYPES(s):
    method execute (line 173) | def execute(self, Strength, Start, End):
  class CannySlider (line 177) | class CannySlider:
    method INPUT_TYPES (line 179) | def INPUT_TYPES(s):
    method execute (line 192) | def execute(self, Low_Threshold, High_Threshold):
  class IPAdapterSlider (line 196) | class IPAdapterSlider:
    method INPUT_TYPES (line 198) | def INPUT_TYPES(s):
    method execute (line 212) | def execute(self, IP1, IP2, IP3):
  class SEGSPass (line 216) | class SEGSPass:
    method INPUT_TYPES (line 218) | def INPUT_TYPES(s):
    method execute (line 229) | def execute(self, SEGS):
  class PipePass (line 233) | class PipePass:
    method INPUT_TYPES (line 235) | def INPUT_TYPES(s):
    method execute (line 246) | def execute(self, PIPE_LINE):
  class LatentPass (line 249) | class LatentPass:
    method INPUT_TYPES (line 251) | def INPUT_TYPES(s):
    method execute (line 261) | def execute(self, latent):
  class IntPass (line 265) | class IntPass:
    method INPUT_TYPES (line 267) | def INPUT_TYPES(s):
    method execute (line 277) | def execute(self, INT):
  class ResolutionPicker (line 281) | class ResolutionPicker:
    method INPUT_TYPES (line 283) | def INPUT_TYPES(s):
    method execute (line 293) | def execute(self, resolution):
  class SamplerParameterPacker (line 296) | class SamplerParameterPacker:
    method INPUT_TYPES (line 304) | def INPUT_TYPES(cls):
    method pack_parameters (line 310) | def pack_parameters(self, sampler, scheduler):
  class SamplerParameterUnpacker (line 313) | class SamplerParameterUnpacker:
    method INPUT_TYPES (line 321) | def INPUT_TYPES(cls):
    method unpack_parameters (line 326) | def unpack_parameters(self, sampler_params):
  class TextVersions (line 330) | class TextVersions:
    method INPUT_TYPES (line 332) | def INPUT_TYPES(s):
    method __init__ (line 344) | def __init__(self):
    method process_text (line 347) | def process_text(self, text):
  function workflow_to_map (line 350) | def workflow_to_map(workflow):
  function is_execution_model_version_supported (line 363) | def is_execution_model_version_supported():
  class ImpactControlBridgeFix (line 371) | class ImpactControlBridgeFix:
    method INPUT_TYPES (line 373) | def INPUT_TYPES(cls):
    method IS_CHANGED (line 394) | def IS_CHANGED(self, value, mode, behavior="Stop", unique_id=None, pro...
    method doit (line 414) | def doit(self, value, mode, behavior="Stop", unique_id=None, prompt=No...
  class BooleanToEnabled (line 499) | class BooleanToEnabled:
    method __init__ (line 501) | def __init__(self):
    method INPUT_TYPES (line 505) | def INPUT_TYPES(cls):
    method convert (line 519) | def convert(self, BOOLEAN):
  class OutputGetString (line 523) | class OutputGetString:
    method INPUT_TYPES (line 525) | def INPUT_TYPES(s):
    method process (line 543) | def process(self, title, unique_id, prompt):
  class SplitVec3 (line 557) | class SplitVec3:
    method INPUT_TYPES (line 559) | def INPUT_TYPES(cls) -> Mapping[str, Any]:
    method op (line 567) | def op(self, a: Vec3) -> tuple[float, float, float]:
  class SplitVec2 (line 570) | class SplitVec2:
    method INPUT_TYPES (line 572) | def INPUT_TYPES(cls) -> Mapping[str, Any]:
    method op (line 578) | def op(self, a) -> tuple[float, float]:
  class SimpleTextTruncate (line 581) | class SimpleTextTruncate:
    method __init__ (line 582) | def __init__(self):
    method INPUT_TYPES (line 586) | def INPUT_TYPES(cls):
    method truncate_words (line 600) | def truncate_words(self, text, word_count):
  class FluxContinuumModelRouter (line 610) | class FluxContinuumModelRouter:
    method INPUT_TYPES (line 612) | def INPUT_TYPES(s):
    method check_lazy_status (line 630) | def check_lazy_status(self, condition, flux_fill=None, flux_depth=None...
    method route_model (line 650) | def route_model(self, condition, flux_fill=None, flux_depth=None, flux...
  class ConfigurableModelRouter (line 665) | class ConfigurableModelRouter:
    method INPUT_TYPES (line 667) | def INPUT_TYPES(cls):
    method check_lazy_status (line 699) | def check_lazy_status(self, condition, routing_config, **kwargs):
    method route_model (line 720) | def route_model(self, condition, routing_config, **kwargs):
  class ImageBatchBoolean (line 733) | class ImageBatchBoolean:
    method INPUT_TYPES (line 735) | def INPUT_TYPES(s):
    method check_lazy_status (line 748) | def check_lazy_status(self, image1, image2, batch_enabled):
    method batch (line 755) | def batch(self, image1, image2, batch_enabled):
  function hex_to_rgba (line 778) | def hex_to_rgba(hex_color):
  class DrawTextConfig (line 789) | class DrawTextConfig:
    method INPUT_TYPES (line 791) | def INPUT_TYPES(s):
    method configure (line 813) | def configure(self, font, size, color, background_color, padding, shad...
  class ConfigurableDrawText (line 831) | class ConfigurableDrawText:
    method INPUT_TYPES (line 833) | def INPUT_TYPES(s):
    method draw (line 845) | def draw(self, TEXT, TEXT_STYLE, IMAGE):

FILE: web/getsetorder.js
  constant BACKGROUND_NODE_CONFIG (line 12) | const BACKGROUND_NODE_CONFIG = {
  method setup (line 22) | async setup() {
  method beforeRegisterNodeDef (line 52) | async beforeRegisterNodeDef(nodeType, nodeData, app) {

FILE: web/guidanceversions.js
  constant TAB_CONFIG (line 4) | const TAB_CONFIG = {
  method nodeCreated (line 22) | nodeCreated(node) {

FILE: web/help.js
  method beforeRegisterNodeDef (line 9) | async beforeRegisterNodeDef(nodeType, nodeData) {

FILE: web/hint.js
  method registerCustomNodes (line 6) | registerCustomNodes() {

FILE: web/imagedisplay.js
  constant DEFAULT_IMAGE (line 4) | const DEFAULT_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6CAIAAAAHjs1qAAAAC...
  class ImageDisplay (line 6) | class ImageDisplay extends LGraphNode {
    method constructor (line 7) | constructor() {
    method setupImage (line 34) | setupImage() {
    method onPropertyChanged (line 72) | onPropertyChanged(name, value) {
    method onResize (line 82) | onResize(size) {
    method onSerialize (line 101) | onSerialize(o) {
    method onConfigure (line 110) | onConfigure(o) {
  method beforeRegisterNodeDef (line 153) | async beforeRegisterNodeDef(nodeType, nodeData, app) {
  method registerCustomNodes (line 158) | registerCustomNodes() {

FILE: web/impactpackfix.js
  method beforeRegisterNodeDef (line 5) | async beforeRegisterNodeDef(nodeType, nodeData, app) {

FILE: web/outputgetnode.js
  constant CONFIG (line 6) | const CONFIG = {
  function debounce (line 26) | function debounce(func, wait = CONFIG.CACHE.debounceWait) {
  function createCachedFunction (line 38) | function createCachedFunction(fn) {
  method registerCustomNodes (line 54) | registerCustomNodes() {
  method beforeRegisterNodeDef (line 316) | async beforeRegisterNodeDef(nodeType, nodeData) {
  class TextDisplay (line 391) | class TextDisplay extends LGraphNode {
    method constructor (line 392) | constructor() {
    method onAdded (line 410) | onAdded(graph) {
    method onDragFinished (line 421) | onDragFinished() {
    method onDrawForeground (line 432) | onDrawForeground(ctx) {
    method findOutputGetNode (line 464) | findOutputGetNode(graph) {
    method onResize (line 468) | onResize(size) {
    method onDblClick (line 474) | onDblClick(event, pos, canvas) {
  method registerCustomNodes (line 501) | registerCustomNodes() {

FILE: web/samplerversions.js
  constant TAB_CONFIG (line 4) | const TAB_CONFIG = {
  method nodeCreated (line 20) | nodeCreated(node) {

FILE: web/tabs.js
  constant NODE_METRICS (line 7) | const NODE_METRICS = {
  constant TAB_STYLE (line 19) | const TAB_STYLE = {
  class BoundingBoxCalculator (line 35) | class BoundingBoxCalculator {
    method constructor (line 36) | constructor() {
    method calculate (line 45) | calculate(rects) {
  class TabManager (line 75) | class TabManager {
    method getTabWidth (line 82) | static getTabWidth(config, isSecondTab = false) {
    method wouldTabsOverlap (line 108) | static wouldTabsOverlap(properties) {
    method drawTab (line 125) | static drawTab(ctx, x, y, text, width) {
    method handleNodeOrder (line 172) | static handleNodeOrder(node, toFront = true) {
  class NodeWithTabs (line 224) | class NodeWithTabs {
    method onNodeCreated (line 229) | static onNodeCreated(node) {
  method nodeCreated (line 276) | nodeCreated(node) {

FILE: web/textversions.js
  constant CONFIG (line 6) | const CONFIG = {
  constant TAB_STYLE (line 15) | const TAB_STYLE = {
  function createTabConfig (line 28) | function createTabConfig(numVersions) {
  function debounce (line 35) | function debounce(func, wait) {
  method nodeCreated (line 51) | nodeCreated(node) {
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (7,568K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 92,
    "preview": "# These are supported funding model platforms\n\ngithub: robertvoy\nbuy_me_a_coffee: robertvoy\n"
  },
  {
    "path": ".github/workflows/publish_action.yml",
    "chars": 461,
    "preview": "name: Publish to Comfy registry\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n    paths:\n      - \"pyprojec"
  },
  {
    "path": "LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2024 Robert\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "README.md",
    "chars": 11697,
    "preview": "# ComfyUI Flux Continuum - Modular Interface\n\n![banner_2](https://github.com/user-attachments/assets/5681868a-002d-46a4-"
  },
  {
    "path": "__init__.py",
    "chars": 1656,
    "preview": "from .misc import MISC_CLASS_MAPPINGS\n\n# Merge both mappings into a single dictionary for the custom nodes\nNODE_CLASS_MA"
  },
  {
    "path": "misc.py",
    "chars": 35156,
    "preview": "import nodes\nfrom server import PromptServer\nimport torch\nimport comfy.samplers\nimport os\nimport time\nfrom PIL import Im"
  },
  {
    "path": "pyproject.toml",
    "chars": 577,
    "preview": "[project]\nname = \"comfyui-flux-continuum\"\ndescription = \"Set of custom nodes to use with the ComfyUI Flux Continuum: Mod"
  },
  {
    "path": "web/getsetorder.js",
    "chars": 2084,
    "preview": "import { app } from \"../../../scripts/app.js\";\n\nconst originalAlert = window.alert;\nwindow.alert = (message) => {\n    if"
  },
  {
    "path": "web/guidanceversions.js",
    "chars": 6155,
    "preview": "import { app } from \"../../../scripts/app.js\";\n\n// Configuration Constants\nconst TAB_CONFIG = {\n    width: 40,\n    heigh"
  },
  {
    "path": "web/help.js",
    "chars": 10250,
    "preview": "import { app } from \"../../../scripts/app.js\";\n\n// Replace this with the category/categories used by your nodes.\nconst c"
  },
  {
    "path": "web/hint.js",
    "chars": 11722,
    "preview": "import { app } from \"../../../scripts/app.js\";\n\napp.registerExtension({\n    name: \"FluxContinuum.HintNode\",\n\n    registe"
  },
  {
    "path": "web/imagedisplay.js",
    "chars": 9011,
    "preview": "import { app } from \"../../../scripts/app.js\";\n\n// Default white square image (200x200 pixels, white)\nconst DEFAULT_IMAG"
  },
  {
    "path": "web/imagetransfershortcut.js",
    "chars": 2874,
    "preview": "import { app } from \"/scripts/app.js\";\nimport { api } from \"/scripts/api.js\";\n\napp.registerExtension({\n  name: \"FluxCont"
  },
  {
    "path": "web/impactpackfix.js",
    "chars": 1058,
    "preview": "import { app } from \"../../scripts/app.js\";\n\napp.registerExtension({\n    name: \"FluxContinuum.ImpactControlBridgeFix\",\n "
  },
  {
    "path": "web/outputgetnode.js",
    "chars": 18433,
    "preview": "import { app } from \"../../../scripts/app.js\";\n\n//based on KJNodes SetGet: https://github.com/kijai/ComfyUI-KJNodes\n\n// "
  },
  {
    "path": "web/samplerversions.js",
    "chars": 6611,
    "preview": "import { app } from \"../../../scripts/app.js\";\n\n// Configuration Constants\nconst TAB_CONFIG = {\n    width: 40,\n    heigh"
  },
  {
    "path": "web/tabs.js",
    "chars": 18837,
    "preview": "import { app } from \"../../../scripts/app.js\";\n\n/**\n * Constants for node and tab dimensions and appearance\n * @readonly"
  },
  {
    "path": "web/textversions.js",
    "chars": 8183,
    "preview": "import { app } from \"../../../scripts/app.js\";\n\n/**\n * Default and limit configurations\n */\nconst CONFIG = {\n    DEFAULT"
  },
  {
    "path": "workflow/Flux+ 1.3_release.json",
    "chars": 998122,
    "preview": "{\n  \"last_node_id\": 3156,\n  \"last_link_id\": 4684,\n  \"nodes\": [\n    {\n      \"id\": 1026,\n      \"type\": \"ImagePass\",\n      "
  },
  {
    "path": "workflow/Flux+ 1.4.4_release.json",
    "chars": 1026674,
    "preview": "{\n  \"last_node_id\": 3818,\n  \"last_link_id\": 5743,\n  \"nodes\": [\n    {\n      \"id\": 1026,\n      \"type\": \"ImagePass\",\n      "
  },
  {
    "path": "workflow/Flux+ 1.4.5_release.json",
    "chars": 1026225,
    "preview": "{\n  \"last_node_id\": 3829,\n  \"last_link_id\": 5773,\n  \"nodes\": [\n    {\n      \"id\": 1026,\n      \"type\": \"ImagePass\",\n      "
  },
  {
    "path": "workflow/Flux+ 1.6.4_release.json",
    "chars": 1041489,
    "preview": "{\n  \"id\": \"58bb46b3-6af0-4368-8562-5b29e4c07d28\",\n  \"revision\": 0,\n  \"last_node_id\": 4106,\n  \"last_link_id\": 6239,\n  \"no"
  },
  {
    "path": "workflow/Flux+ 1.7.0_release.json",
    "chars": 1062916,
    "preview": "{\n  \"id\": \"72381480-a0d5-4292-88ec-5961ca04ee42\",\n  \"revision\": 0,\n  \"last_node_id\": 4184,\n  \"last_link_id\": 6373,\n  \"no"
  },
  {
    "path": "workflow/Flux+ 1.7.1_beta.json",
    "chars": 1061722,
    "preview": "{\n  \"id\": \"72381480-a0d5-4292-88ec-5961ca04ee42\",\n  \"revision\": 0,\n  \"last_node_id\": 4185,\n  \"last_link_id\": 6375,\n  \"no"
  },
  {
    "path": "workflow/Flux+ Light 1.0.0_release.json",
    "chars": 843824,
    "preview": "{\n  \"last_node_id\": 3853,\n  \"last_link_id\": 5833,\n  \"nodes\": [\n    {\n      \"id\": 1026,\n      \"type\": \"ImagePass\",\n      "
  }
]

About this extraction

This page contains the full source code of the robertvoy/ComfyUI-Flux-Continuum GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (6.9 MB), approximately 1.8M tokens, and a symbol index with 154 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!