Add FL Show Text node and bump to 2.8.0

Displays a connected STRING input on the node via a flex-fill DOM
widget with header, length badge, monospace body, scrollbar styling,
and wheel-to-scroll capture so wheel events scroll the body instead
of zooming the canvas. The same string is passed through to the
output socket.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Fillip
2026-06-12 11:50:40 -07:00
co-authored by Claude Opus 4.7
parent d164833f16
commit 42297ecf02
4 changed files with 218 additions and 1 deletions
+3
View File
@@ -196,6 +196,7 @@ from .nodes.utility.FL_RandomRange import FL_RandomNumber
from .nodes.utility.FL_RandomShapeGenerator import FL_RandomShapeGenerator
from .nodes.utility.FL_SD_Slices import FL_SDUltimate_Slices
from .nodes.utility.FL_SeparateMasks import FL_SeparateMaskComponents
from .nodes.utility.FL_ShowText import FL_ShowText
from .nodes.utility.FL_StringToLoraName import FL_StringToLoraName
from .nodes.utility.FL_Switch import FL_Switch
from .nodes.utility.FL_Switch_Big import FL_Switch_Big
@@ -449,6 +450,7 @@ NODE_CLASS_MAPPINGS = {
"FL_RandomShapeGenerator": FL_RandomShapeGenerator,
"FL_KartelJobInput": FL_KartelJobInput,
"FL_KartelJobOutput": FL_KartelJobOutput,
"FL_ShowText": FL_ShowText,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -652,6 +654,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"FL_RandomShapeGenerator": "FL Random Shape Generator",
"FL_KartelJobInput": "FL Kartel Job Input",
"FL_KartelJobOutput": "FL Kartel Job Output",
"FL_ShowText": "FL Show Text",
}
# Conditionally register google-genai nodes if the SDK loaded successfully
+22
View File
@@ -0,0 +1,22 @@
class FL_ShowText:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"forceInput": True}),
},
"hidden": {"unique_id": "UNIQUE_ID"},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "show"
OUTPUT_NODE = True
CATEGORY = "🏵️Fill Nodes/Utility"
def show(self, text, unique_id=None):
if text is None:
text = ""
if not isinstance(text, str):
text = str(text)
return {"ui": {"text": [text]}, "result": (text,)}
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui_fill-nodes"
description = "Fill-Nodes is a versatile collection of custom nodes for ComfyUI that extends functionality across multiple domains. Features include advanced image processing (pixelation, slicing, masking), visual effects generation (glitch, halftone, pixel art), comprehensive file handling (PDF creation/extraction, Google Drive integration), AI model interfaces (GPT, DALL-E, Hugging Face), utility nodes for workflow enhancement, and specialized tools for video processing, captioning, and batch operations. The pack provides both practical workflow solutions and creative tools within a unified node collection."
version = "2.7.9"
version = "2.8.0"
license = "LICENSE"
dependencies = ["librosa", "sounddevice", "glitch_this", "PyOpenGL", "glfw", "scipy>=1.13.1", "requests", "aiohttp", "moviepy", "matplotlib", "reportlab", "openai", "PyPDF2", "pdf2image", "PyMuPDF", "reportlab", "PyPDF2", "ollama", "kornia", "opencv-python", "gdown", "open_clip_torch", "google-genai"]
+192
View File
@@ -0,0 +1,192 @@
/**
* File: FL_ShowText.js
* Project: ComfyUI_Fill-Nodes
*
* Displays the incoming text on the node via a styled DOM widget and
* passes the same string through to the output socket.
*/
import { app } from "../../../../scripts/app.js";
const STYLES = `
.flshowtext-host {
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
.flshowtext-widget {
background: #17181c;
border: 1px solid #2a2d34;
border-radius: 8px;
color: #f4f4f5;
display: flex;
flex: 1 1 auto;
flex-direction: column;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: 12px;
line-height: 1.4;
min-height: 0;
overflow: hidden;
padding: 0;
box-sizing: border-box;
width: 100%;
}
.flshowtext-widget * { box-sizing: border-box; }
.flshowtext-header {
align-items: center;
background: #101114;
border-bottom: 1px solid #2a2d34;
color: #cbd5e1;
display: flex;
flex: 0 0 auto;
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
font-size: 10px;
font-weight: 700;
gap: 8px;
justify-content: space-between;
letter-spacing: 0.04em;
padding: 6px 10px;
text-transform: uppercase;
}
.flshowtext-badge {
background: #27272a;
border-radius: 999px;
color: #a1a1aa;
font-size: 9px;
font-variant-numeric: tabular-nums;
font-weight: 700;
letter-spacing: 0;
padding: 2px 7px;
text-transform: none;
}
.flshowtext-body {
color: #e5e7eb;
flex: 1 1 auto;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
padding: 10px;
white-space: pre-wrap;
word-break: break-word;
scrollbar-width: thin;
scrollbar-color: #3f3f46 #17181c;
}
.flshowtext-body::-webkit-scrollbar { width: 8px; }
.flshowtext-body::-webkit-scrollbar-track { background: #17181c; }
.flshowtext-body::-webkit-scrollbar-thumb {
background: #3f3f46;
border-radius: 999px;
}
.flshowtext-body::-webkit-scrollbar-thumb:hover { background: #52525b; }
.flshowtext-body.empty {
color: #71717a;
font-style: italic;
}
`;
const PLACEHOLDER = "Waiting for input. Run the workflow to display text.";
function injectStyles() {
const id = "flshowtext-styles";
if (document.getElementById(id)) return;
const style = document.createElement("style");
style.id = id;
style.textContent = STYLES;
document.head.appendChild(style);
}
function formatLength(text) {
const chars = text.length;
const lines = text ? text.split("\n").length : 0;
return `${chars} chars · ${lines} line${lines === 1 ? "" : "s"}`;
}
function buildPanel() {
const root = document.createElement("div");
root.className = "flshowtext-widget";
root.innerHTML = `
<div class="flshowtext-header">
<span>Show Text</span>
<span class="flshowtext-badge" data-role="length">empty</span>
</div>
<div class="flshowtext-body empty" data-role="body"></div>
`;
const body = root.querySelector('[data-role="body"]');
const badge = root.querySelector('[data-role="length"]');
body.textContent = PLACEHOLDER;
return { root, body, badge };
}
function attachWheelCapture(body) {
body.addEventListener(
"wheel",
(event) => {
const canScrollDown =
event.deltaY > 0 && body.scrollTop + body.clientHeight < body.scrollHeight - 1;
const canScrollUp = event.deltaY < 0 && body.scrollTop > 0;
if (canScrollDown || canScrollUp) {
body.scrollTop += event.deltaY;
event.preventDefault();
event.stopPropagation();
}
},
{ passive: false }
);
}
app.registerExtension({
name: "fl.node.FL_ShowText",
nodeCreated(node) {
const comfyClass = (node.constructor && node.constructor.comfyClass) || "";
if (comfyClass !== "FL_ShowText") return;
injectStyles();
const host = document.createElement("div");
host.className = "flshowtext-host";
const panel = buildPanel();
host.appendChild(panel.root);
attachWheelCapture(panel.body);
node.addDOMWidget("fl_showtext_display", "fl-showtext", host, {
getMinHeight: () => 80,
hideOnZoom: false,
serialize: false,
});
const setText = (text) => {
const value = text == null ? "" : String(text);
if (value === "") {
panel.body.classList.add("empty");
panel.body.textContent = PLACEHOLDER;
panel.badge.textContent = "empty";
} else {
panel.body.classList.remove("empty");
panel.body.textContent = value;
panel.badge.textContent = formatLength(value);
}
};
const [oldW, oldH] = node.size;
node.setSize([Math.max(oldW, 320), Math.max(oldH, 220)]);
const originalOnExecuted = node.onExecuted;
node.onExecuted = function (message) {
originalOnExecuted?.apply(this, arguments);
const lines = message?.text;
if (Array.isArray(lines) && lines.length > 0) {
setText(lines.join("\n"));
}
};
const originalOnConfigure = node.onConfigure;
node.onConfigure = function () {
originalOnConfigure?.apply(this, arguments);
setText("");
};
},
});