clean_up
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# SATA Utility Node Suite for ComfyUI
|
||||
|
||||
**Author:** Satadal
|
||||
**Version:** 1.2.0
|
||||
**Version:** 1.5.0
|
||||
**License:** MIT
|
||||
**Repository:** https://github.com/SatadalAI/SATA_UtilityNode
|
||||
|
||||
@@ -18,6 +18,7 @@ It includes:
|
||||
- **Resolution Machine:** Quick selection of model-specific image resolutions for popular models.
|
||||
- **Save Image w/Metadata:** saving images with meta data and with different file formats.
|
||||
- **Prompt Autocomplete:** Text widget with autocompletion, random selection, and preview capabilities.
|
||||
- **Latent Machine:** Advanced noise generation for creating empty latents initialized with specific noise patterns instead of standard Gaussian noise.
|
||||
|
||||
---
|
||||
|
||||
@@ -30,7 +31,6 @@ It includes:
|
||||
- **CPU-based Rescaling:** Efficient resizing using OpenCV.
|
||||
- **Supersampling:** Optional detail preservation for downscaling/fractional resizing.
|
||||
- **Rounding Modulus:** Ensures output dimensions align with model/UI constraints.
|
||||
- **Dual Output:** Returns both the processed image and a help/documentation link.
|
||||
|
||||
### 💬 Prompt Machine
|
||||
|
||||
@@ -70,6 +70,18 @@ It includes:
|
||||
- **Global Mode:** Optional setting to enable autocompletion on ALL text widgets in ComfyUI.
|
||||
- **Custom Data:** Add your own `.csv` or `.json` files to the `prompt` folder to extend the library.
|
||||
|
||||
### 🌌 Latent Machine
|
||||
|
||||
- **Advanced Noise Generation:** Create empty latents initialized with specific noise patterns instead of standard Gaussian noise.
|
||||
- **Noise Types:**
|
||||
- **Gaussian (White):** Standard noise, good for sharp details.
|
||||
- **Pink (1/f):** Balanced noise, great for photorealism and nature.
|
||||
- **Brown (1/f²):** Smoother noise, ideal for backgrounds and anime.
|
||||
- **Perlin:** Cloud-like structure, perfect for landscapes and fluids.
|
||||
- **Plasma:** Very smooth, abstract blobs for sci-fi and alien terrains.
|
||||
- **Model Support:** Select between **4-channel** (SD1.5, SDXL, etc.) and **16-channel** (Flux, SD3) latents.
|
||||
- **Reproducibility:** Full seed support for consistent noise generation.
|
||||
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
|
||||
+38
-38
@@ -3,7 +3,7 @@ import { app } from "../../../scripts/app.js";
|
||||
// Register extension
|
||||
app.registerExtension({
|
||||
name: "SATA_UtilityNode.PromptAutocomplete",
|
||||
|
||||
|
||||
async setup() {
|
||||
// Add settings
|
||||
app.ui.settings.addSetting({
|
||||
@@ -59,7 +59,7 @@ app.registerExtension({
|
||||
const contentData = await contentResp.json();
|
||||
this.snippetsCache[file] = contentData.items || [];
|
||||
}
|
||||
console.log("[PromptAutocomplete] Loaded snippets:", Object.keys(this.snippetsCache));
|
||||
|
||||
} catch (err) {
|
||||
console.error("[PromptAutocomplete] Failed to load snippets:", err);
|
||||
}
|
||||
@@ -77,12 +77,12 @@ app.registerExtension({
|
||||
// Hook into the input element creation/usage
|
||||
// NOTE: ComfyUI's customtext widget often creates a textarea.
|
||||
// We can try to find the textarea in the DOM if it's active, or hook the input callback.
|
||||
|
||||
|
||||
// A more robust way for ComfyUI widgets:
|
||||
// The widget has an `inputEl` property if it's a DOM widget.
|
||||
|
||||
|
||||
// Let's try to hook the `inputEl` if it exists, or wait for it.
|
||||
|
||||
|
||||
// Helper to setup the listener
|
||||
function setupListener(input) {
|
||||
if (input.dataset.hasAutocomplete) return;
|
||||
@@ -99,7 +99,7 @@ app.registerExtension({
|
||||
// If not, it might be created later. We can hook `onInput` or similar,
|
||||
// but `inputEl` is usually created when the node is drawn or added.
|
||||
// Let's poll or hook draw? Hooking draw is safer to catch it.
|
||||
widget.draw = function(ctx, node, widgetWidth, y, widgetHeight) {
|
||||
widget.draw = function (ctx, node, widgetWidth, y, widgetHeight) {
|
||||
const result = originalDraw?.apply(this, arguments);
|
||||
if (this.inputEl) {
|
||||
setupListener(this.inputEl);
|
||||
@@ -110,7 +110,7 @@ app.registerExtension({
|
||||
},
|
||||
|
||||
// --- Autocomplete Logic ---
|
||||
|
||||
|
||||
popup: null,
|
||||
active: false,
|
||||
currentCategory: null, // null = showing categories, string = showing items
|
||||
@@ -127,7 +127,7 @@ app.registerExtension({
|
||||
|
||||
// Simple check: is the character before cursor the trigger?
|
||||
// Or are we in a "trigger session"?
|
||||
|
||||
|
||||
// If we just typed the trigger
|
||||
if (e.data === trigger) {
|
||||
this.showPopup(input, cursor);
|
||||
@@ -144,11 +144,11 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const query = val.substring(lastTriggerIndex + 1, cursor);
|
||||
|
||||
|
||||
// Check if we have a category selected (format: #category:query)
|
||||
// But wait, the plan said: Select Category -> Then Items.
|
||||
// So maybe we just filter categories first.
|
||||
|
||||
|
||||
if (this.currentCategory) {
|
||||
// We are inside a category
|
||||
// Check if the query contains the category prefix?
|
||||
@@ -161,17 +161,17 @@ app.registerExtension({
|
||||
// But how do we represent that in text?
|
||||
// Maybe we don't change text, just internal state?
|
||||
// Standard way: #category:item
|
||||
|
||||
|
||||
// Let's parse the text to see where we are.
|
||||
// If text is "#cat", we filter categories.
|
||||
// If text is "#cat:ite", we filter items in "cat".
|
||||
|
||||
|
||||
const parts = query.split(":");
|
||||
if (parts.length > 1) {
|
||||
// We have a category selected
|
||||
const catName = parts[0];
|
||||
const itemQuery = parts.slice(1).join(":"); // rest is item query
|
||||
|
||||
|
||||
// Check if catName is valid
|
||||
if (this.snippetsCache[catName + ".csv"] || this.snippetsCache[catName + ".json"] || this.snippetsCache[catName]) {
|
||||
// It's a valid category (roughly).
|
||||
@@ -184,7 +184,7 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If no colon, or invalid category, we are filtering categories
|
||||
this.currentCategory = null;
|
||||
this.updateCategories(query);
|
||||
@@ -192,17 +192,17 @@ app.registerExtension({
|
||||
} else {
|
||||
// No category selected yet
|
||||
// Check for colon
|
||||
const parts = query.split(":");
|
||||
if (parts.length > 1) {
|
||||
// Maybe user typed "style:" manually?
|
||||
const catName = parts[0];
|
||||
let key = Object.keys(this.snippetsCache).find(k => k.replace(/\.(csv|json)$/, "") === catName);
|
||||
if (key) {
|
||||
this.currentCategory = key;
|
||||
this.updateItems(parts.slice(1).join(":"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
const parts = query.split(":");
|
||||
if (parts.length > 1) {
|
||||
// Maybe user typed "style:" manually?
|
||||
const catName = parts[0];
|
||||
let key = Object.keys(this.snippetsCache).find(k => k.replace(/\.(csv|json)$/, "") === catName);
|
||||
if (key) {
|
||||
this.currentCategory = key;
|
||||
this.updateItems(parts.slice(1).join(":"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateCategories(query);
|
||||
}
|
||||
@@ -289,10 +289,10 @@ app.registerExtension({
|
||||
updateItems(query) {
|
||||
if (!this.currentCategory) return;
|
||||
const items = this.snippetsCache[this.currentCategory] || [];
|
||||
|
||||
|
||||
// Filter
|
||||
let matches = items.filter(i => i.toLowerCase().includes(query.toLowerCase()));
|
||||
|
||||
|
||||
// Add "Random" option at top
|
||||
this.filteredItems = [
|
||||
{ type: "random", value: "RANDOM", display: "🎲 Random" },
|
||||
@@ -328,7 +328,7 @@ app.registerExtension({
|
||||
if (idx === this.selectedIndex) {
|
||||
div.style.backgroundColor = "#444";
|
||||
div.style.color = "#fff";
|
||||
|
||||
|
||||
// Show preview if it's a long item
|
||||
if (item.type === "item" && item.value.length > 30) {
|
||||
this.showPreview(item.value);
|
||||
@@ -341,10 +341,10 @@ app.registerExtension({
|
||||
this.selectedIndex = idx;
|
||||
this.selectItem(this.activeInput); // We need reference to input
|
||||
};
|
||||
|
||||
|
||||
this.popup.appendChild(div);
|
||||
});
|
||||
|
||||
|
||||
// Keep active input ref
|
||||
const activeElement = document.activeElement;
|
||||
if (activeElement && activeElement.tagName === "TEXTAREA") {
|
||||
@@ -370,7 +370,7 @@ app.registerExtension({
|
||||
});
|
||||
document.body.appendChild(this.previewBox);
|
||||
}
|
||||
|
||||
|
||||
this.previewBox.textContent = text;
|
||||
const rect = this.popup.getBoundingClientRect();
|
||||
this.previewBox.style.left = `${rect.right + 5}px`;
|
||||
@@ -402,16 +402,16 @@ app.registerExtension({
|
||||
this.currentCategory = item.value;
|
||||
// Don't close popup, just update to items
|
||||
// We need to update the input value first
|
||||
|
||||
// Replace everything from trigger to cursor with trigger + textToInsert
|
||||
|
||||
// Replace everything from trigger to cursor with trigger + textToInsert
|
||||
const before = val.substring(0, lastTriggerIndex + 1); // includes trigger
|
||||
const after = val.substring(cursor);
|
||||
input.value = before + textToInsert + after;
|
||||
|
||||
|
||||
// Move cursor
|
||||
const newCursor = lastTriggerIndex + 1 + textToInsert.length;
|
||||
input.setSelectionRange(newCursor, newCursor);
|
||||
|
||||
|
||||
// Trigger input event manually to update state
|
||||
this.handleInput({ data: null }, input, null);
|
||||
return;
|
||||
@@ -430,13 +430,13 @@ app.registerExtension({
|
||||
// Replace
|
||||
const before = val.substring(0, lastTriggerIndex); // remove trigger
|
||||
const after = val.substring(cursor);
|
||||
|
||||
|
||||
// If we are replacing a category query like #style:something, we need to find the start of #
|
||||
// Actually, if we are in item mode, the text is "#style:query".
|
||||
// We want to replace the whole "#style:query" with "textToInsert".
|
||||
|
||||
|
||||
input.value = before + textToInsert + after;
|
||||
|
||||
|
||||
// Move cursor
|
||||
const newCursor = before.length + textToInsert.length;
|
||||
input.setSelectionRange(newCursor, newCursor);
|
||||
|
||||
@@ -34,7 +34,7 @@ app.registerExtension({
|
||||
const resp = await fetch(`/sata/prompt_machine/names?csv=${encodeURIComponent(csvValue)}`);
|
||||
const data = await resp.json();
|
||||
namesConfig = data.names || ["None"];
|
||||
console.log("[PromptMachineFrontend] Loaded names:", namesConfig);
|
||||
|
||||
refreshNames();
|
||||
} catch (err) {
|
||||
console.error("[PromptMachineFrontend] Failed to load names:", err);
|
||||
|
||||
@@ -25,7 +25,7 @@ app.registerExtension({
|
||||
try {
|
||||
const resp = await fetch("/SATA_UtilityNode/resolutions_config");
|
||||
resolutionsConfig = await resp.json();
|
||||
console.log("[Resolution_Machine] Loaded config:", resolutionsConfig);
|
||||
|
||||
} catch (err) {
|
||||
console.error("[Resolution_Machine] Failed to load config:", err);
|
||||
}
|
||||
@@ -46,7 +46,7 @@ app.registerExtension({
|
||||
resolutionWidget.value = available[0];
|
||||
}
|
||||
|
||||
console.log(`[Resolution_Machine] Resolutions for ${selectedModel}:`, available);
|
||||
|
||||
app.graph.change();
|
||||
applyResolution(); // auto-apply width/height on refresh
|
||||
}
|
||||
|
||||
@@ -121,9 +121,9 @@ class Prompt_Machine:
|
||||
CATEGORY = "SATA_UtilityNode"
|
||||
|
||||
def get_prompts(self, csv_file, name, positive_preview=None, negative_preview=None, note_preview=None):
|
||||
print(f"[PromptMachine] get_prompts - csv_file={csv_file}, name={name}")
|
||||
|
||||
pos, neg, note = read_prompt_row(csv_file, name)
|
||||
print(f"[PromptMachine] get_prompts -> pos_len={len(pos)}, neg_len={len(neg)}, note_len={len(note)}")
|
||||
|
||||
return (pos, neg, note)
|
||||
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ class Resolution_Machine:
|
||||
entry = resolutions[resolution]
|
||||
width, height = entry["width"], entry["height"]
|
||||
|
||||
print(f"[{NODE_NAME}] Model={model}, Resolution={resolution}, Output=({width}, {height})")
|
||||
|
||||
return (width, height)
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui_sata_utilitynode"
|
||||
description = "ComfyUI utility nodes collection providing features like prompt machine, resolution machine, and upscale machine to enhance workflow efficiency."
|
||||
version = "1.2.0"
|
||||
version = "1.5.0"
|
||||
license = "MIT"
|
||||
|
||||
[project.urls]
|
||||
|
||||
+1
-3
@@ -1,5 +1,3 @@
|
||||
opencv-python>=4.5.0
|
||||
spandrel
|
||||
nvidia-tensorrt>=8.0
|
||||
onnx>=1.12.0
|
||||
onnxruntime-gpu>=1.12.0
|
||||
piexif
|
||||
Reference in New Issue
Block a user