import { app } from "/scripts/app.js"; import { ComfyWidgets } from "/scripts/widgets.js"; /* ------------------------------------------------------------------ Extension: Comfy.GenerateTextNode Adds a read‑only multiline STRING widget to the "Generate Text (🔗LLMToolkit)" node that displays the LLM response directly on the node. The node still outputs the normal "context" so it can be connected to an external Display Text node as before. -------------------------------------------------------------------*/ app.registerExtension({ name: "Comfy.GenerateTextNode", async beforeRegisterNodeDef(nodeType, nodeData, app) { // Only act on our LLMToolkitTextGenerator node if (nodeData.name !== "LLMToolkitTextGenerator") return; /* ----------------------------- 1. Inject widget on creation ----------------------------- */ const origOnNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function () { // Invoke original handler first const ret = origOnNodeCreated ? origOnNodeCreated.apply(this, arguments) : undefined; // Build a unique widget name so each node instance has its own key const existingNodes = app.graph._nodes.filter(n => n.type === nodeData.name); const widgetName = `${nodeData.name}_response_${existingNodes.length}`; // Create read‑only STRING widget for displaying response const widgetInfo = ComfyWidgets.STRING( this, widgetName, ["STRING", { default: "", placeholder: "LLM response will appear here …", multiline: true, }], app ); // Make read‑only widgetInfo.widget.inputEl.readOnly = true; // Store reference for quick updates this.__llmResponseWidget = widgetInfo.widget || widgetInfo; // Ensure node size grows to fit another widget this.setSize(this.computeSize()); return ret; }; /* ----------------------------- 2. Helper to update widget ----------------------------- */ function updateResponseWidget(textContainer) { if (!this.__llmResponseWidget) return; // Extract final text string (textContainer may be array/string) let valueToSet = ""; if (Array.isArray(textContainer)) { valueToSet = textContainer.join("\n"); } else if (typeof textContainer === "string") { valueToSet = textContainer; } // Trim and write valueToSet = valueToSet.trim(); this.__llmResponseWidget.value = valueToSet; if (this.__llmResponseWidget.inputEl) this.__llmResponseWidget.inputEl.value = valueToSet; app.graph.setDirtyCanvas(true); } /* ----------------------------- 3. Hook into onExecuted to receive message ----------------------------- */ const origOnExecuted = nodeType.prototype.onExecuted; nodeType.prototype.onExecuted = function (message) { if (origOnExecuted) origOnExecuted.apply(this, arguments); // message is typically { ui: { string: [...] }, result: (…) } let textData = undefined; if (message?.ui?.string) { textData = message.ui.string; } else if (message?.string) { textData = message.string; } else if (Array.isArray(message)) { textData = message; } updateResponseWidget.call(this, textData); }; /* ----------------------------- 4. Restore saved value on load ----------------------------- */ const origOnConfigure = nodeType.prototype.onConfigure; nodeType.prototype.onConfigure = function (config) { if (origOnConfigure) origOnConfigure.apply(this, arguments); if (!this.__llmResponseWidget || !config?.widgets_values?.length) return; const idx = this.widgets.findIndex(w => w === this.__llmResponseWidget); if (idx !== -1 && config.widgets_values.length > idx) { const savedVal = config.widgets_values[idx] || ""; this.__llmResponseWidget.value = savedVal; if (this.__llmResponseWidget.inputEl) this.__llmResponseWidget.inputEl.value = savedVal; } }; }, });