diff --git a/py/nodes/debug_metadata.py b/py/nodes/debug_metadata.py index 839d1431..7489c7dc 100644 --- a/py/nodes/debug_metadata.py +++ b/py/nodes/debug_metadata.py @@ -1,4 +1,5 @@ import logging +from server import PromptServer # type: ignore from ..metadata_collector.metadata_processor import MetadataProcessor logger = logging.getLogger(__name__) @@ -7,6 +8,7 @@ class DebugMetadata: NAME = "Debug Metadata (LoraManager)" CATEGORY = "Lora Manager/utils" DESCRIPTION = "Debug node to verify metadata_processor functionality" + OUTPUT_NODE = True @classmethod def INPUT_TYPES(cls): @@ -19,8 +21,7 @@ class DebugMetadata: }, } - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("metadata_json",) + RETURN_TYPES = () FUNCTION = "process_metadata" def process_metadata(self, images, id): @@ -32,7 +33,13 @@ class DebugMetadata: # Use the MetadataProcessor to convert it to JSON string metadata_json = MetadataProcessor.to_json(metadata, id) - return (metadata_json,) + # Send metadata to frontend for display + PromptServer.instance.send_sync("metadata_update", { + "id": id, + "metadata": metadata_json + }) + except Exception as e: logger.error(f"Error processing metadata: {e}") - return ("{}",) # Return empty JSON object in case of error + + return () diff --git a/web/comfyui/debug_metadata.js b/web/comfyui/debug_metadata.js new file mode 100644 index 00000000..1c86a177 --- /dev/null +++ b/web/comfyui/debug_metadata.js @@ -0,0 +1,52 @@ +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js"; +import { addJsonDisplayWidget } from "./json_display_widget.js"; + +app.registerExtension({ + name: "LoraManager.DebugMetadata", + + setup() { + // Add message handler to listen for metadata updates from Python + api.addEventListener("metadata_update", (event) => { + const { id, metadata } = event.detail; + this.handleMetadataUpdate(id, metadata); + }); + }, + + async nodeCreated(node) { + if (node.comfyClass === "Debug Metadata (LoraManager)") { + // Enable widget serialization + node.serialize_widgets = true; + + // Add a widget to display metadata + const jsonWidget = addJsonDisplayWidget(node, "metadata", { + defaultVal: "", + }).widget; + + // Store reference to the widget + node.jsonWidget = jsonWidget; + + // Restore saved value if exists + if (node.widgets_values && node.widgets_values.length > 0) { + const savedValue = node.widgets_values[0]; + if (savedValue) { + jsonWidget.value = savedValue; + } + } + } + }, + + // Handle metadata updates from Python + handleMetadataUpdate(id, metadata) { + const node = app.graph.getNodeById(+id); + if (!node || node.comfyClass !== "Debug Metadata (LoraManager)") { + console.warn("Node not found or not a DebugMetadata node:", id); + return; + } + + if (node.jsonWidget) { + // Update the widget with the received metadata + node.jsonWidget.value = metadata; + } + } +}); \ No newline at end of file diff --git a/web/comfyui/json_display_widget.js b/web/comfyui/json_display_widget.js new file mode 100644 index 00000000..f1bbf816 --- /dev/null +++ b/web/comfyui/json_display_widget.js @@ -0,0 +1,151 @@ +export function addJsonDisplayWidget(node, name, opts) { + // Create container for JSON display + const container = document.createElement("div"); + container.className = "comfy-json-display-container"; + + // Set initial height + const defaultHeight = 200; + container.style.setProperty('--comfy-widget-min-height', `${defaultHeight}px`); + container.style.setProperty('--comfy-widget-max-height', `${defaultHeight * 2}px`); + container.style.setProperty('--comfy-widget-height', `${defaultHeight}px`); + + Object.assign(container.style, { + display: "block", + padding: "8px", + backgroundColor: "rgba(40, 44, 52, 0.6)", + borderRadius: "6px", + width: "100%", + boxSizing: "border-box", + overflow: "auto", + overflowY: "scroll", + maxHeight: `${defaultHeight}px`, + fontFamily: "monospace", + fontSize: "12px", + lineHeight: "1.5", + whiteSpace: "pre-wrap", + color: "rgba(226, 232, 240, 0.9)" + }); + + // Initialize default value + const initialValue = opts?.defaultVal || ""; + + // Function to format and display JSON content with syntax highlighting + const displayJson = (jsonString, widget) => { + try { + // If string is empty, show placeholder + if (!jsonString || jsonString.trim() === '') { + container.textContent = "No metadata available"; + container.style.fontStyle = "italic"; + container.style.color = "rgba(226, 232, 240, 0.6)"; + container.style.textAlign = "center"; + container.style.padding = "20px 0"; + return; + } + + // Try to parse and pretty-print if it's valid JSON + try { + const jsonObj = JSON.parse(jsonString); + container.innerHTML = syntaxHighlight(JSON.stringify(jsonObj, null, 2)); + } catch (e) { + // If not valid JSON, display as-is + container.textContent = jsonString; + } + + container.style.fontStyle = "normal"; + container.style.textAlign = "left"; + container.style.padding = "8px"; + } catch (error) { + console.error("Error displaying JSON:", error); + container.textContent = "Error displaying content"; + } + }; + + // Function to add syntax highlighting to JSON + function syntaxHighlight(json) { + // Color scheme + const colors = { + key: "#6ad6f5", // Light blue for keys + string: "#98c379", // Soft green for strings + number: "#e5c07b", // Amber for numbers + boolean: "#c678dd", // Purple for booleans + null: "#7f848e" // Gray for null + }; + + // Replace JSON syntax with highlighted HTML + json = json.replace(/&/g, '&').replace(//g, '>'); + + return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { + let cls = 'number'; + let color = colors.number; + + if (/^"/.test(match)) { + if (/:$/.test(match)) { + cls = 'key'; + color = colors.key; + // Remove the colon from the key and add it back without color + match = match.replace(/:$/, ''); + return '' + match + ':'; + } else { + cls = 'string'; + color = colors.string; + } + } else if (/true|false/.test(match)) { + cls = 'boolean'; + color = colors.boolean; + } else if (/null/.test(match)) { + cls = 'null'; + color = colors.null; + } + + return '' + match + ''; + }); + } + + // Store the value + let widgetValue = initialValue; + + // Create widget with DOM Widget API + const widget = node.addDOMWidget(name, "custom", container, { + getValue: function() { + return widgetValue; + }, + setValue: function(v) { + widgetValue = v; + displayJson(widgetValue, widget); + }, + getMinHeight: function() { + return parseInt(container.style.getPropertyValue('--comfy-widget-min-height')) || defaultHeight; + }, + getMaxHeight: function() { + return parseInt(container.style.getPropertyValue('--comfy-widget-max-height')) || defaultHeight * 2; + }, + getHeight: function() { + return defaultHeight; // Fixed height to maintain node boundaries + }, + hideOnZoom: true + }); + + // Set initial value + widget.value = initialValue; + + widget.serializeValue = () => { + return widgetValue; + }; + + // Update widget when node is resized + const onNodeResize = node.onResize; + node.onResize = function(size) { + if(onNodeResize) { + onNodeResize.call(this, size); + } + + // Adjust container height to node height + if(size && size[1]) { + const widgetHeight = Math.min(size[1] - 80, defaultHeight * 2); // Account for node header + container.style.maxHeight = `${widgetHeight}px`; + container.style.setProperty('--comfy-widget-height', `${widgetHeight}px`); + } + }; + + return { minWidth: 300, minHeight: defaultHeight, widget }; +}