import { app } from "../../../scripts/app.js"; import { ComfyWidgets } from "../../../scripts/widgets.js"; app.registerExtension({ name: "LogToWorkspace.Extension", async beforeRegisterNodeDef(nodeType, nodeData, app) { const targetNodes = ["LogToWorkspace", "LogToWorkspaceAdv"]; if (targetNodes.includes(nodeData.name)) { // 1. GENERATE THE GENUINE TEXTAREA WIDGET ON CREATION const onNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function () { onNodeCreated?.apply(this, arguments); // Use the official ComfyUI STRING widget constructor for multi-line boxes const widgetResult = ComfyWidgets["STRING"]( this, // The node instance object "displayed_text", // The name of your custom widget ["STRING", { multiline: true }], // Tells Comfy to spin up a textarea element app, // Pass the current app reference ); const widget = widgetResult.widget; // Configure parameters for read-only tracking logs widget.inputEl.readOnly = true; widget.inputEl.style.opacity = "0.85"; // Gives it a clean, greyed-out terminal appearance widget.serializeValue = () => undefined; // Prevents logging bloat inside saved workflow metadata // 2. DEFER REPOSITIONING: Let ComfyUI finish initialization loops first to stop the "true" bug setTimeout(() => { const currentIdx = this.widgets.indexOf(widget); if (currentIdx > -1) this.widgets.splice(currentIdx, 1); this.widgets.splice(0, 0, widget); }, 5); // Adjust standard node bounding boxes if (nodeData.name === "LogToWorkspaceAdv") this.size = [400, 340]; // Room for text area above the toggle checkboxes else this.size = [400, 240]; // Default dimension // Force layout dimensions to fit the new HTML element // this.size = [400, 200]; this.setDirtyCanvas(true, true); }; // 2. FORCE SYNC BACKEND PAYLOADS STRAIGHT INTO THE HTML DOM const onExecuted = nodeType.prototype.onExecuted; nodeType.prototype.onExecuted = function (message) { onExecuted?.apply(this, arguments); if (message?.text) { const textValue = message.text.join("\n"); const widget = this.widgets?.find((w) => w.name === "displayed_text"); if (widget) { // Sync internal LiteGraph state widget.value = textValue; // Sync native browser DOM textarea so the lines warp and update visually if (widget.inputEl) { widget.inputEl.value = textValue; // AUTO-SCROLL MECHANISM: Automatically tracks and pulls down to show the latest logs widget.inputEl.scrollTop = widget.inputEl.scrollHeight; } this.setDirtyCanvas(true, true); } } }; } }, });