rebranded en updated prompt controler

This commit is contained in:
rubz
2026-03-22 23:06:30 +01:00
parent eeb365bc0a
commit 6a47c67c25
6 changed files with 189 additions and 91 deletions
+27 -4
View File
@@ -1,6 +1,6 @@
# ComfyUI-SmartResize
# ComfyUI-Craftopia
Smart image resize nodes for ComfyUI — batch processing, LoRA prep & SeedVR support.
Custom nodes for ComfyUI — image resizing, dataset prep & prompt automation for LoRA training workflows.
All nodes appear under the **Craftopia** category in the node menu.
@@ -8,6 +8,27 @@ All nodes appear under the **Craftopia** category in the node menu.
## Nodes
### 🎛️ Smart Prompt Controller
Cycle through up to 4 prompt lists using a single incrementing counter. Counts lines automatically, selects the right prompt, and outputs which list is active — ideal for driving Switch nodes that control aspect ratio, latent size, or other per-category settings.
**Typical usecases:**
- LoRA dataset generation with multiple pose categories (headshots, halfbody, fullbody, tall portrait), each with their own prompt list and aspect ratio
- Any batch workflow where you need to rotate through different prompt sets and switch settings per set
| Input | Type | Description |
|---|---|---|
| counter | INT | Auto-incrementing counter (1-indexed) |
| prompt_list_1 | STRING | First prompt list — one prompt per line (forceInput) |
| prompt_list_2 | STRING | Second prompt list (optional, forceInput) |
| prompt_list_3 | STRING | Third prompt list (optional, forceInput) |
| prompt_list_4 | STRING | Fourth prompt list (optional, forceInput) |
**Outputs:** `prompt` (selected line as STRING), `list_index` (active list number as INT → feed to Switch nodes)
The node displays a status label showing the current position: `List 1 — 4/5 | total: 8`
---
### 📐 Smart Resize
Resize any image (or batch) so the **longest side** equals `max_pixels`, with aspect ratio preserved.
@@ -72,20 +93,22 @@ Also useful for LoRA dataset prep — using mixed resolutions in your training s
- `JWImageResizeByLongerSide` (comfyui-various) has no Lanczos in the official release
- No existing node combines batch folder loading + longest-side resize + original filename preservation
- No existing node outputs a ready-to-use `resolution` INT for SeedVR
- No existing node cycles through multiple prompt lists with automatic list switching and counter
---
## Installation
**ComfyUI Manager:** search for `ComfyUI-SmartResize` and install.
**ComfyUI Manager:** search for `ComfyUI-Craftopia` and install.
**Manual:**
```bash
cd ComfyUI/custom_nodes
git clone https://github.com/CraftopiaStudio/ComfyUI-SmartResize
git clone https://github.com/CraftopiaStudio/ComfyUI-Craftopia
```
Restart ComfyUI. All nodes appear under **Craftopia** in the node menu:
- `Craftopia` → **Smart Prompt Controller 🎛️**
- `Craftopia` → **Smart Resize 📐**
- `Craftopia` → **Smart Batch Resize 📁**
- `Craftopia` → **Smart Size Multiplier 📏**
+3 -1
View File
@@ -1,7 +1,9 @@
WEB_DIRECTORY = "./js"
from .smart_resize import NODE_CLASS_MAPPINGS as A, NODE_DISPLAY_NAME_MAPPINGS as A_NAMES
from .smart_batch_resize import NODE_CLASS_MAPPINGS as B, NODE_DISPLAY_NAME_MAPPINGS as B_NAMES
from .smart_size_multiplier import NODE_CLASS_MAPPINGS as C, NODE_DISPLAY_NAME_MAPPINGS as C_NAMES
from .smart_batch_controller import NODE_CLASS_MAPPINGS as D, NODE_DISPLAY_NAME_MAPPINGS as D_NAMES
from .smart_prompt_controller import NODE_CLASS_MAPPINGS as D, NODE_DISPLAY_NAME_MAPPINGS as D_NAMES
NODE_CLASS_MAPPINGS = {**A, **B, **C, **D}
NODE_DISPLAY_NAME_MAPPINGS = {**A_NAMES, **B_NAMES, **C_NAMES, **D_NAMES}
+32
View File
@@ -0,0 +1,32 @@
import { app } from "../../scripts/app.js";
app.registerExtension({
name: "Craftopia.SmartBatchController",
async nodeCreated(node) {
if (node.comfyClass !== "SmartBatchController") return;
// Create status widget — empty name so no label shows
const widget = node.addWidget("text", "", "", () => {});
widget.serializeValue = () => undefined;
// Listen for execution results
const onExecuted = node.onExecuted;
node.onExecuted = function (output) {
// Store current size before update
const w = node.size[0];
const h = node.size[1];
onExecuted?.apply(this, arguments);
if (output?.text?.[0]) {
widget.value = output.text[0];
}
// Restore size so ComfyUI doesn't shrink the node
node.size[0] = Math.max(w, node.size[0]);
node.size[1] = Math.max(h, node.size[1]);
node.setDirtyCanvas(true);
};
},
});
+30
View File
@@ -0,0 +1,30 @@
import { app } from "../../scripts/app.js";
app.registerExtension({
name: "Craftopia.SmartPromptController",
async nodeCreated(node) {
if (node.comfyClass !== "SmartPromptController") return;
// Status widget — no visible label name
const widget = node.addWidget("text", "", "", () => {});
widget.serializeValue = () => undefined;
const onExecuted = node.onExecuted;
node.onExecuted = function (output) {
const w = node.size[0];
const h = node.size[1];
onExecuted?.apply(this, arguments);
if (output?.text?.[0]) {
widget.value = output.text[0];
}
// Preserve node size
node.size[0] = Math.max(w, node.size[0]);
node.size[1] = Math.max(h, node.size[1]);
node.setDirtyCanvas(true);
};
},
});
-86
View File
@@ -1,86 +0,0 @@
class SmartBatchController:
"""
Craftopia — Smart Batch Controller 🎛️
Drives batch switching and prompt index from a single incrementing counter.
Accepts up to 4 prompt lists as STRING inputs and counts lines automatically.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"counter": ("INT", {
"default": 1,
"min": 1,
"max": 99999,
"step": 1,
"display": "number",
"control_after_generate": "increment",
}),
},
"optional": {
"batch_1": ("STRING", {"forceInput": True}),
"batch_2": ("STRING", {"forceInput": True}),
"batch_3": ("STRING", {"forceInput": True}),
"batch_4": ("STRING", {"forceInput": True}),
}
}
RETURN_TYPES = ("INT", "INT", "INT")
RETURN_NAMES = ("select", "prompt_index", "total_all")
FUNCTION = "control"
CATEGORY = "Craftopia"
OUTPUT_NODE = True
def _count_lines(self, text):
"""Count non-empty lines in a prompt list string."""
if not text:
return 0
return len([l for l in text.splitlines() if l.strip()])
def control(self, counter, batch_1=None, batch_2=None, batch_3=None, batch_4=None):
raw = [batch_1, batch_2, batch_3, batch_4]
# Build active batch list: (original_index, line_count)
active = []
for i, text in enumerate(raw):
count = self._count_lines(text)
if count > 0:
active.append((i, count))
total_all = sum(t for _, t in active)
if total_all == 0:
return {"ui": {"text": ["no prompts"]}, "result": (0, 0, 0)}
# Wrap counter within total_all (1-based counter → 0-based position)
position = (counter - 1) % total_all
# Find which batch and which prompt within that batch
cumulative = 0
batch_index = 0
prompt_index = 0
for i, t in active:
if position < cumulative + t:
batch_index = i
prompt_index = position - cumulative # 0-based within batch
break
cumulative += t
# UI label on node: "B2 — 3 / 7"
batch_number = batch_index + 1
batch_total = self._count_lines(raw[batch_index])
prompt_display = prompt_index + 1 # 1-based for display
label = f"B{batch_number} — {prompt_display} / {batch_total} | total: {total_all}"
return {"ui": {"text": [label]}, "result": (batch_index, prompt_index, total_all)}
NODE_CLASS_MAPPINGS = {
"SmartBatchController": SmartBatchController,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SmartBatchController": "Smart Batch Controller 🎛️",
}
+97
View File
@@ -0,0 +1,97 @@
class SmartPromptController:
"""
Craftopia — Smart Prompt Controller 🎛️
Cycles through up to 4 prompt lists using a single incrementing counter.
Counts lines automatically and outputs the selected prompt + list number.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"counter": ("INT", {
"default": 1,
"min": 1,
"max": 99999,
"step": 1,
"display": "number",
"control_after_generate": "increment",
}),
},
"optional": {
"prompt_list_1": ("STRING", {"forceInput": True}),
"prompt_list_2": ("STRING", {"forceInput": True}),
"prompt_list_3": ("STRING", {"forceInput": True}),
"prompt_list_4": ("STRING", {"forceInput": True}),
}
}
RETURN_TYPES = ("STRING", "INT")
RETURN_NAMES = ("prompt", "list_index")
FUNCTION = "control"
CATEGORY = "Craftopia"
OUTPUT_NODE = True
def _get_lines(self, text):
"""Return list of non-empty lines from a text block."""
if not text:
return []
if isinstance(text, list):
text = text[0] if text else ""
return [l for l in str(text).splitlines() if l.strip()]
def control(self, counter, prompt_list_1="", prompt_list_2="",
prompt_list_3="", prompt_list_4=""):
raw = [prompt_list_1, prompt_list_2, prompt_list_3, prompt_list_4]
# Collect active lists: (list_index, [lines])
active = []
for i, text in enumerate(raw):
lines = self._get_lines(text)
if lines:
active.append((i, lines))
total_all = sum(len(lines) for _, lines in active)
if total_all == 0:
return {"ui": {"text": ["no prompts connected"]},
"result": ("", 0)}
# Map counter position to list + line
position = (counter - 1) % total_all
cumulative = 0
selected_list_index = 0
selected_line_index = 0
selected_prompt = ""
for list_idx, lines in active:
if position < cumulative + len(lines):
selected_list_index = list_idx
selected_line_index = position - cumulative
selected_prompt = lines[selected_line_index]
break
cumulative += len(lines)
# Label: "List 1 — 4/5 | total: 8"
list_number = selected_list_index + 1
list_total = len(self._get_lines(raw[selected_list_index]))
prompt_display = selected_line_index + 1
label = f"List {list_number} \u2014 {prompt_display}/{list_total} | total: {total_all}"
return {
"ui": {"text": [label]},
"result": (
selected_prompt,
list_number,
)
}
NODE_CLASS_MAPPINGS = {
"SmartPromptController": SmartPromptController,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SmartPromptController": "Smart Prompt Controller \ud83c\udf9b\ufe0f",
}