From d04dbd7d3373ce0862fcb4c23e5afb92854dc6a4 Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Fri, 3 Oct 2025 13:22:08 -0500 Subject: [PATCH 1/4] Fix automatic promotion of previews on conversion --- src/composables/node/useNodeImage.ts | 6 ++++-- src/core/graph/subgraph/proxyWidgetUtils.ts | 13 ++++++++++--- .../widgets/composables/useImageUploadWidget.ts | 10 ++++++++-- src/services/litegraphService.ts | 10 +++++----- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/composables/node/useNodeImage.ts b/src/composables/node/useNodeImage.ts index ad0a041787..aad473ae7c 100644 --- a/src/composables/node/useNodeImage.ts +++ b/src/composables/node/useNodeImage.ts @@ -98,7 +98,7 @@ const useNodePreview = ( /** * Attaches a preview image to a node. */ -export const useNodeImage = (node: LGraphNode) => { +export const useNodeImage = (node: LGraphNode, callback?: () => void) => { node.previewMediaType = 'image' const loadElement = (url: string): Promise => @@ -112,6 +112,7 @@ export const useNodeImage = (node: LGraphNode) => { const onLoaded = (elements: HTMLImageElement[]) => { node.imageIndex = null node.imgs = elements + callback?.() } return useNodePreview(node, { @@ -126,7 +127,7 @@ export const useNodeImage = (node: LGraphNode) => { /** * Attaches a preview video to a node. */ -export const useNodeVideo = (node: LGraphNode) => { +export const useNodeVideo = (node: LGraphNode, callback?: () => void) => { node.previewMediaType = 'video' let minHeight = DEFAULT_VIDEO_SIZE let minWidth = DEFAULT_VIDEO_SIZE @@ -187,6 +188,7 @@ export const useNodeVideo = (node: LGraphNode) => { } node.videoContainer.replaceChildren(videoElement) + callback?.() } return useNodePreview(node, { diff --git a/src/core/graph/subgraph/proxyWidgetUtils.ts b/src/core/graph/subgraph/proxyWidgetUtils.ts index e13cc99429..d2e9d23edc 100644 --- a/src/core/graph/subgraph/proxyWidgetUtils.ts +++ b/src/core/graph/subgraph/proxyWidgetUtils.ts @@ -119,9 +119,16 @@ export function promoteRecommendedWidgets(subgraphNode: SubgraphNode) { const interiorNodes = subgraphNode.subgraph.nodes for (const node of interiorNodes) { node.updateComputedDisabled() - //NOTE: Since this operation is async, previews still don't exist after the single frame - //Add an onLoad callback to updatePreviews? - updatePreviews(node) + node.images = [] + function checkWidgets() { + updatePreviews(node) + const widget = node.widgets?.find((w) => w.name.startsWith('$$')) + if (!widget) return + const pw = getProxyWidgets(subgraphNode) + if (pw.some(matchesPropertyItem([node, widget]))) return + promoteWidget(node, widget, [subgraphNode]) + } + updatePreviews(node, checkWidgets) } const filteredWidgets: WidgetItem[] = interiorNodes .flatMap(nodeWidgets) diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts b/src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts index c8c4f575f1..faad0348a9 100644 --- a/src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts +++ b/src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts @@ -108,12 +108,18 @@ export const useImageUploadWidget = () => { // On load if we have a value then render the image // The value isn't set immediately so we need to wait a moment // No change callbacks seem to be fired on initial setting of the value - requestAnimationFrame(() => { + nodeOutputStore.setNodeOutputs(node, fileComboWidget.value, { + isAnimated + }) + showPreview({ block: false }) + const originalOnConfigure = node.onConfigure + node.onConfigure = function (...args) { nodeOutputStore.setNodeOutputs(node, fileComboWidget.value, { isAnimated }) showPreview({ block: false }) - }) + return originalOnConfigure?.apply(this, args) + } return { widget: uploadWidget } } diff --git a/src/services/litegraphService.ts b/src/services/litegraphService.ts index d345acb673..bf9d74db00 100644 --- a/src/services/litegraphService.ts +++ b/src/services/litegraphService.ts @@ -853,14 +853,14 @@ export const useLitegraphService = () => { return [] } } - function updatePreviews(node: LGraphNode) { + function updatePreviews(node: LGraphNode, callback?: () => void) { try { - unsafeUpdatePreviews.call(node) + unsafeUpdatePreviews.call(node, callback) } catch (error) { console.error('Error drawing node background', error) } } - function unsafeUpdatePreviews(this: LGraphNode) { + function unsafeUpdatePreviews(this: LGraphNode, callback?: () => void) { if (this.flags.collapsed) return const nodeOutputStore = useNodeOutputStore() @@ -891,9 +891,9 @@ export const useLitegraphService = () => { (this.animatedImages && !isAnimatedWebp && !isAnimatedPng) || isVideoNode(this) if (isVideo) { - useNodeVideo(this).showPreview() + useNodeVideo(this, callback).showPreview() } else { - useNodeImage(this).showPreview() + useNodeImage(this, callback).showPreview() } } From 41258d3e0d83693c38abcbd6e0a1f139439d9c17 Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Fri, 3 Oct 2025 23:03:35 -0500 Subject: [PATCH 2/4] In proxyWidget schema, parse first if string Property storage was change from strings back to array, but this change was breaking intermediary workflows. An extra check has been added to parse the input if it is a string --- src/core/schemas/proxyWidget.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/schemas/proxyWidget.ts b/src/core/schemas/proxyWidget.ts index 307c4987ac..d2df586fcb 100644 --- a/src/core/schemas/proxyWidget.ts +++ b/src/core/schemas/proxyWidget.ts @@ -9,7 +9,10 @@ export type ProxyWidgetsProperty = z.infer export function parseProxyWidgets( property: NodeProperty | undefined ): ProxyWidgetsProperty { - const result = proxyWidgetsPropertySchema.safeParse(property) + if (typeof property === 'string') property = JSON.parse(property) + const result = proxyWidgetsPropertySchema.safeParse( + typeof property === 'string' ? JSON.parse(property) : property + ) if (result.success) return result.data const error = fromZodError(result.error) From bce0aca1bed7e311ad23a8745dd74ecbabe44bb3 Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Sun, 5 Oct 2025 10:37:21 -0500 Subject: [PATCH 3/4] Remove label from overlay --- src/core/graph/subgraph/proxyWidget.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/graph/subgraph/proxyWidget.ts b/src/core/graph/subgraph/proxyWidget.ts index d7d76a9d10..3879fed8af 100644 --- a/src/core/graph/subgraph/proxyWidget.ts +++ b/src/core/graph/subgraph/proxyWidget.ts @@ -121,7 +121,6 @@ function addProxyWidget( afterQueued: undefined, computedHeight: undefined, isProxyWidget: true, - label: name, last_y: undefined, name, node: subgraphNode, From 76cc043c63baa5bd44ee02fc893f0639816d8ffb Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Sun, 5 Oct 2025 11:28:07 -0500 Subject: [PATCH 4/4] Fix test regressions on image previews --- src/core/graph/subgraph/proxyWidgetUtils.ts | 3 +-- .../widgets/composables/useImageUploadWidget.ts | 10 ++-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/core/graph/subgraph/proxyWidgetUtils.ts b/src/core/graph/subgraph/proxyWidgetUtils.ts index d2e9d23edc..2d4ed5eb04 100644 --- a/src/core/graph/subgraph/proxyWidgetUtils.ts +++ b/src/core/graph/subgraph/proxyWidgetUtils.ts @@ -119,7 +119,6 @@ export function promoteRecommendedWidgets(subgraphNode: SubgraphNode) { const interiorNodes = subgraphNode.subgraph.nodes for (const node of interiorNodes) { node.updateComputedDisabled() - node.images = [] function checkWidgets() { updatePreviews(node) const widget = node.widgets?.find((w) => w.name.startsWith('$$')) @@ -128,7 +127,7 @@ export function promoteRecommendedWidgets(subgraphNode: SubgraphNode) { if (pw.some(matchesPropertyItem([node, widget]))) return promoteWidget(node, widget, [subgraphNode]) } - updatePreviews(node, checkWidgets) + requestAnimationFrame(() => updatePreviews(node, checkWidgets)) } const filteredWidgets: WidgetItem[] = interiorNodes .flatMap(nodeWidgets) diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts b/src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts index faad0348a9..c8c4f575f1 100644 --- a/src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts +++ b/src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts @@ -108,18 +108,12 @@ export const useImageUploadWidget = () => { // On load if we have a value then render the image // The value isn't set immediately so we need to wait a moment // No change callbacks seem to be fired on initial setting of the value - nodeOutputStore.setNodeOutputs(node, fileComboWidget.value, { - isAnimated - }) - showPreview({ block: false }) - const originalOnConfigure = node.onConfigure - node.onConfigure = function (...args) { + requestAnimationFrame(() => { nodeOutputStore.setNodeOutputs(node, fileComboWidget.value, { isAnimated }) showPreview({ block: false }) - return originalOnConfigure?.apply(this, args) - } + }) return { widget: uploadWidget } }