From 62a857abd81ccf51f86d877ec8e4268a88d482cf Mon Sep 17 00:00:00 2001 From: Satadal Dhara Date: Thu, 27 Nov 2025 20:32:00 +0530 Subject: [PATCH] clean_up --- README.md | 16 +++++++- js/prompt_autocomplete.js | 76 ++++++++++++++++++------------------- js/prompt_machine.js | 2 +- js/resolution_machine.js | 4 +- nodes/prompt_machine.py | 4 +- nodes/resolution_machine.py | 2 +- pyproject.toml | 2 +- requirements.txt | 4 +- 8 files changed, 60 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 112ccb0..5a8b79a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/js/prompt_autocomplete.js b/js/prompt_autocomplete.js index 90554a3..97b990d 100644 --- a/js/prompt_autocomplete.js +++ b/js/prompt_autocomplete.js @@ -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); diff --git a/js/prompt_machine.js b/js/prompt_machine.js index cefd990..88a8fc8 100644 --- a/js/prompt_machine.js +++ b/js/prompt_machine.js @@ -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); diff --git a/js/resolution_machine.js b/js/resolution_machine.js index d99dcb8..315ba02 100644 --- a/js/resolution_machine.js +++ b/js/resolution_machine.js @@ -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 } diff --git a/nodes/prompt_machine.py b/nodes/prompt_machine.py index d4c2046..ea936cf 100644 --- a/nodes/prompt_machine.py +++ b/nodes/prompt_machine.py @@ -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) diff --git a/nodes/resolution_machine.py b/nodes/resolution_machine.py index f07c37a..b0ec80b 100644 --- a/nodes/resolution_machine.py +++ b/nodes/resolution_machine.py @@ -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) diff --git a/pyproject.toml b/pyproject.toml index 5cb3262..ee44672 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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] diff --git a/requirements.txt b/requirements.txt index 1a2332e..2be6356 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,3 @@ opencv-python>=4.5.0 spandrel -nvidia-tensorrt>=8.0 -onnx>=1.12.0 -onnxruntime-gpu>=1.12.0 \ No newline at end of file +piexif \ No newline at end of file