diff --git a/py/nodes/debug_metadata.py b/py/nodes/debug_metadata.py index 7489c7dc..e9175508 100644 --- a/py/nodes/debug_metadata.py +++ b/py/nodes/debug_metadata.py @@ -1,15 +1,15 @@ import logging -from server import PromptServer # type: ignore from ..metadata_collector.metadata_processor import MetadataProcessor logger = logging.getLogger(__name__) + 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): return { @@ -25,21 +25,37 @@ class DebugMetadata: FUNCTION = "process_metadata" def process_metadata(self, images, id): + """ + Process metadata from the execution context and return it for UI display. + + The metadata is returned via the 'ui' key in the return dict, which triggers + node.onExecuted on the frontend to update the JsonDisplayWidget. + + Args: + images: Input images (required for execution flow) + id: Node's unique ID (hidden) + + Returns: + Dict with 'result' (empty tuple) and 'ui' (metadata dict for widget display) + """ try: # Get the current execution context's metadata from ..metadata_collector import get_metadata + metadata = get_metadata() - - # Use the MetadataProcessor to convert it to JSON string - metadata_json = MetadataProcessor.to_json(metadata, id) - - # Send metadata to frontend for display - PromptServer.instance.send_sync("metadata_update", { - "id": id, - "metadata": metadata_json - }) - + + # Use the MetadataProcessor to convert it to dict + metadata_dict = MetadataProcessor.to_dict(metadata, id) + + return { + "result": (), + # ComfyUI expects ui values to be lists, wrap the dict in a list + "ui": {"metadata": [metadata_dict]}, + } + except Exception as e: logger.error(f"Error processing metadata: {e}") - - return () + return { + "result": (), + "ui": {"metadata": [{"error": str(e)}]}, + } diff --git a/vue-widgets/src/components/JsonDisplayWidget.vue b/vue-widgets/src/components/JsonDisplayWidget.vue new file mode 100644 index 00000000..80818a72 --- /dev/null +++ b/vue-widgets/src/components/JsonDisplayWidget.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/vue-widgets/src/main.ts b/vue-widgets/src/main.ts index d22fb8c7..66455640 100644 --- a/vue-widgets/src/main.ts +++ b/vue-widgets/src/main.ts @@ -2,6 +2,7 @@ import { createApp, type App as VueApp } from 'vue' import PrimeVue from 'primevue/config' import LoraPoolWidget from '@/components/LoraPoolWidget.vue' import LoraRandomizerWidget from '@/components/LoraRandomizerWidget.vue' +import JsonDisplayWidget from '@/components/JsonDisplayWidget.vue' import type { LoraPoolConfig, LegacyLoraPoolConfig, RandomizerConfig } from './composables/types' const LORA_POOL_WIDGET_MIN_WIDTH = 500 @@ -9,6 +10,8 @@ const LORA_POOL_WIDGET_MIN_HEIGHT = 400 const LORA_RANDOMIZER_WIDGET_MIN_WIDTH = 500 const LORA_RANDOMIZER_WIDGET_MIN_HEIGHT = 510 const LORA_RANDOMIZER_WIDGET_MAX_HEIGHT = LORA_RANDOMIZER_WIDGET_MIN_HEIGHT +const JSON_DISPLAY_WIDGET_MIN_WIDTH = 300 +const JSON_DISPLAY_WIDGET_MIN_HEIGHT = 200 // @ts-ignore - ComfyUI external module import { app } from '../../../scripts/app.js' @@ -207,6 +210,72 @@ function createLoraRandomizerWidget(node) { return { widget } } +// @ts-ignore +function createJsonDisplayWidget(node) { + const container = document.createElement('div') + container.id = `json-display-widget-${node.id}` + container.style.width = '100%' + container.style.height = '100%' + container.style.display = 'flex' + container.style.flexDirection = 'column' + container.style.overflow = 'hidden' + + forwardMiddleMouseToCanvas(container) + + let internalValue: Record | undefined + + const widget = node.addDOMWidget( + 'metadata', + 'JSON_DISPLAY', + container, + { + getValue() { + return internalValue + }, + setValue(v: Record) { + internalValue = v + if (typeof widget.onSetValue === 'function') { + widget.onSetValue(v) + } + }, + serialize: false, // Display-only widget - don't save metadata in workflows + getMinHeight() { + return JSON_DISPLAY_WIDGET_MIN_HEIGHT + } + } + ) + + const vueApp = createApp(JsonDisplayWidget, { + widget, + node + }) + + vueApp.use(PrimeVue, { + unstyled: true, + ripple: false + }) + + vueApp.mount(container) + vueApps.set(node.id + 20000, vueApp) // Offset to avoid collision with other widgets + + widget.computeLayoutSize = () => { + const minWidth = JSON_DISPLAY_WIDGET_MIN_WIDTH + const minHeight = JSON_DISPLAY_WIDGET_MIN_HEIGHT + + return { minHeight, minWidth } + } + + widget.onRemove = () => { + const vueApp = vueApps.get(node.id + 20000) + if (vueApp) { + vueApp.unmount() + vueApps.delete(node.id + 20000) + } + } + + return { widget } +} + app.registerExtension({ name: 'LoraManager.VueWidgets', @@ -238,5 +307,20 @@ app.registerExtension({ return addLorasWidgetCache(node, 'loras', { isRandomizerNode }, callback) } } + }, + + // Add display-only widget to Debug Metadata node + // @ts-ignore + async beforeRegisterNodeDef(nodeType, nodeData) { + if (nodeData.name === 'Debug Metadata (LoraManager)') { + const onNodeCreated = nodeType.prototype.onNodeCreated + + nodeType.prototype.onNodeCreated = function () { + onNodeCreated?.apply(this, []) + + // Add the JSON display widget + createJsonDisplayWidget(this) + } + } } }) diff --git a/web/comfyui/debug_metadata.js b/web/comfyui/debug_metadata.js deleted file mode 100644 index bcbb893f..00000000 --- a/web/comfyui/debug_metadata.js +++ /dev/null @@ -1,53 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js"; -import { addJsonDisplayWidget } from "./json_display_widget.js"; -import { getNodeFromGraph } from "./utils.js"; - -app.registerExtension({ - name: "LoraManager.DebugMetadata", - - setup() { - // Add message handler to listen for metadata updates from Python - api.addEventListener("metadata_update", (event) => { - const { id, graph_id: graphId, metadata } = event.detail; - this.handleMetadataUpdate(id, graphId, 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, graphId, metadata) { - const node = getNodeFromGraph(graphId, 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 deleted file mode 100644 index c0b3f7c4..00000000 --- a/web/comfyui/json_display_widget.js +++ /dev/null @@ -1,144 +0,0 @@ -import { forwardMiddleMouseToCanvas } from "./utils.js"; - -export function addJsonDisplayWidget(node, name, opts) { - // Create container for JSON display - const container = document.createElement("div"); - container.className = "comfy-json-display-container"; - - forwardMiddleMouseToCanvas(container); - - // Set initial height - const defaultHeight = 200; - - 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); - }, - 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]) { - // Reduce the offset to minimize the gap at the bottom - const widgetHeight = Math.min(size[1] - 30, defaultHeight * 2); // Reduced from 80 to 30 - container.style.maxHeight = `${widgetHeight}px`; - container.style.setProperty('--comfy-widget-height', `${widgetHeight}px`); - } - }; - - return { minWidth: 300, minHeight: defaultHeight, widget }; -}