diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index ac33aa280..5da16d0f0 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -307,13 +307,23 @@ watch( removeSlotError(node) const nodeErrors = lastNodeErrors?.[node.id] if (!nodeErrors) continue + + let slotErrorsChanged = false for (const error of nodeErrors.errors) { - if (error.extra_info && error.extra_info.input_name) { - const inputIndex = node.findInputSlot(error.extra_info.input_name) - if (inputIndex !== -1) { - node.inputs[inputIndex].hasErrors = true - } - } + if (!error.extra_info?.input_name) continue + + const inputIndex = node.findInputSlot(error.extra_info.input_name) + if (inputIndex === -1) continue + + node.inputs[inputIndex].hasErrors = true + slotErrorsChanged = true + } + + // Trigger Vue node data update if slot errors changed + if (slotErrorsChanged && comfyApp.graph.onTrigger) { + comfyApp.graph.onTrigger('node:slot-errors:changed', { + nodeId: node.id + }) } } diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index 31cc63a81..dbce2c84e 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -435,6 +435,28 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { }) } } + } else if ( + action === 'node:slot-errors:changed' && + param && + typeof param === 'object' + ) { + const event = param as { nodeId: string | number } + const nodeId = String(event.nodeId) + const litegraphNode = nodeRefs.get(nodeId) + const currentData = vueNodeData.get(nodeId) + + if (litegraphNode && currentData) { + // Re-extract slot data with updated hasErrors properties + vueNodeData.set(nodeId, { + ...currentData, + inputs: litegraphNode.inputs + ? [...litegraphNode.inputs] + : undefined, + outputs: litegraphNode.outputs + ? [...litegraphNode.outputs] + : undefined + }) + } } // Call original trigger handler if it exists diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index 08ff30d3b..2b3ec26ae 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -136,7 +136,11 @@ import { computed, inject, onErrorCaptured, onMounted, provide, ref } from 'vue' import type { VueNodeData } from '@/composables/graph/useGraphNodeManager' import { useErrorHandling } from '@/composables/useErrorHandling' -import { LiteGraph } from '@/lib/litegraph/src/litegraph' +import { + type INodeInputSlot, + type INodeOutputSlot, + LiteGraph +} from '@/lib/litegraph/src/litegraph' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions' import { TransformStateKey } from '@/renderer/core/layout/injectionKeys' @@ -200,9 +204,21 @@ const hasExecutionError = computed( ) // Computed error states for styling -const hasAnyError = computed( - (): boolean => !!(hasExecutionError.value || nodeData.hasErrors || error) -) +const hasAnyError = computed((): boolean => { + return ( + !!hasExecutionError.value || + !!nodeData.hasErrors || + !!error || + // Type assertions needed because VueNodeData.inputs/outputs are typed as unknown[] + // but at runtime they contain INodeInputSlot/INodeOutputSlot objects + !!nodeData.inputs?.some( + (slot) => (slot as INodeInputSlot)?.hasErrors ?? false + ) || + !!nodeData.outputs?.some( + (slot) => (slot as INodeOutputSlot)?.hasErrors ?? false + ) + ) +}) const bypassed = computed((): boolean => nodeData.mode === 4) @@ -263,7 +279,7 @@ const { latestPreviewUrl, shouldShowPreviewImg } = useNodePreviewState( const borderClass = computed(() => { if (hasAnyError.value) { - return 'border-error' + return 'border-red-500 dark-theme:border-red-500' } if (executing.value) { return 'border-blue-500' @@ -276,7 +292,7 @@ const outlineClass = computed(() => { return undefined } if (hasAnyError.value) { - return 'outline-error' + return 'outline-red-500' } if (executing.value) { return 'outline-blue-500'