fix: refresh image previews on media upload nodes when refreshing node definitions (#9141)

## Summary
- When pressing `R` to refresh node definitions, image previews on
LoadImage/LoadVideo nodes now update to reflect external file changes
- Re-triggers the combo widget callback to regenerate preview URLs with
a fresh cache-busting `&rand=` parameter
- Extracts `isMediaUploadComboInput` from `uploadImage.ts` to
`nodeDefSchema.ts` as a shared utility

- Fixes #2082



https://github.com/user-attachments/assets/d18d69ae-6ecd-448d-8d7c-76b2c49fdea5



## Test plan
- [ ] Open a workflow with a LoadImage node and select an image
- [ ] Edit and save the image externally (e.g. in an image editor)
- [ ] Press `R` to refresh node definitions
- [ ] Verify the preview updates to show the edited image
This commit is contained in:
Johnpaul Chiwetelu
2026-02-24 05:57:24 +01:00
committed by GitHub
parent 3b5649232d
commit 1f0ca18737
4 changed files with 35 additions and 16 deletions

View File

@@ -2,27 +2,13 @@ import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import {
type ComfyNodeDef,
type InputSpec,
isComboInputSpecV1
isMediaUploadComboInput
} from '@/schemas/nodeDefSchema'
import { app } from '../../scripts/app'
// Adds an upload button to the nodes
const isMediaUploadComboInput = (inputSpec: InputSpec) => {
const [inputName, inputOptions] = inputSpec
if (!inputOptions) return false
const isUploadInput =
inputOptions['image_upload'] === true ||
inputOptions['video_upload'] === true ||
inputOptions['animated_image_upload'] === true
return (
isUploadInput && (isComboInputSpecV1(inputSpec) || inputName === 'COMBO')
)
}
const createUploadInput = (
imageInputName: string,
imageInputOptions: InputSpec

View File

@@ -158,6 +158,20 @@ export function isComboInputSpec(
return isComboInputSpecV1(inputSpec) || isComboInputSpecV2(inputSpec)
}
export function isMediaUploadComboInput(inputSpec: InputSpec): boolean {
const [inputName, inputOptions] = inputSpec
if (!inputOptions) return false
const isUploadInput =
inputOptions['image_upload'] === true ||
inputOptions['video_upload'] === true ||
inputOptions['animated_image_upload'] === true
return (
isUploadInput && (isComboInputSpecV1(inputSpec) || inputName === 'COMBO')
)
}
/**
* Get the type of an input spec.
*

View File

@@ -89,7 +89,8 @@ import {
executeWidgetsCallback,
createNode,
fixLinkInputSlots,
isImageNode
isImageNode,
isVideoNode
} from '@/utils/litegraphUtil'
import {
createSharedObjectUrl,
@@ -1822,6 +1823,7 @@ export class ComfyApp {
this.registerNodeDef(nodeId, defs[nodeId])
}
// Refresh combo widgets in all nodes including those in subgraphs
const nodeOutputStore = useNodeOutputStore()
forEachNode(this.rootGraph, (node) => {
const def = defs[node.type]
// Allow primitive nodes to handle refresh
@@ -1854,6 +1856,12 @@ export class ComfyApp {
}
}
}
// Re-trigger previews on media nodes (e.g. LoadImage)
// to bust browser cache when files are edited externally
if (isImageNode(node) || isVideoNode(node)) {
nodeOutputStore.refreshNodeOutputs(node)
}
})
await useExtensionService().invokeExtensionsAsync(

View File

@@ -388,6 +388,16 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
}
}
function refreshNodeOutputs(node: LGraphNode) {
const locatorId = nodeToNodeLocatorId(node)
if (!locatorId) return
const outputs = app.nodeOutputs[locatorId]
if (!outputs) return
nodeOutputs.value[locatorId] = { ...outputs }
}
function resetAllOutputsAndPreviews() {
app.nodeOutputs = {}
nodeOutputs.value = {}
@@ -433,6 +443,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
setNodePreviewsByExecutionId,
setNodePreviewsByNodeId,
updateNodeImages,
refreshNodeOutputs,
syncLegacyNodeImgs,
// Cleanup