diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png index 17770c31f..a30e4b2c0 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png index 3f02b8cf6..64898a216 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png index e3f693e8f..ac6a841ae 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png index 77d7c5fb0..c89b8b240 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png index c9ab0ed1a..cf8384a03 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png index 5dfa61c19..ee051f5df 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-active-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png index 55b9bce6f..cf8384a03 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-chromium-linux.png differ diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 4e19e7d89..6d86a0fec 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -104,7 +104,6 @@ import { useGlobalLitegraph } from '@/composables/useGlobalLitegraph' import { usePaste } from '@/composables/usePaste' import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags' import { i18n, t } from '@/i18n' -import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { useLitegraphSettings } from '@/platform/settings/composables/useLitegraphSettings' import { CORE_SETTINGS } from '@/platform/settings/constants/coreSettings' import { useSettingStore } from '@/platform/settings/settingStore' @@ -293,45 +292,36 @@ watch( { deep: true } ) -// Update node slot errors +// Update node slot errors for LiteGraph nodes +// (Vue nodes read from store directly) watch( () => executionStore.lastNodeErrors, (lastNodeErrors) => { - const removeSlotError = (node: LGraphNode) => { + if (!comfyApp.graph) return + + for (const node of comfyApp.graph.nodes) { + // Clear existing errors for (const slot of node.inputs) { delete slot.hasErrors } for (const slot of node.outputs) { delete slot.hasErrors } - } - for (const node of comfyApp.graph.nodes) { - removeSlotError(node) const nodeErrors = lastNodeErrors?.[node.id] if (!nodeErrors) continue const validErrors = nodeErrors.errors.filter( (error) => error.extra_info?.input_name !== undefined ) - const slotErrorsChanged = - validErrors.length > 0 && - validErrors.some((error) => { - const inputName = error.extra_info!.input_name! - const inputIndex = node.findInputSlot(inputName) - if (inputIndex !== -1) { - node.inputs[inputIndex].hasErrors = true - return true - } - return false - }) - // Trigger Vue node data update if slot errors changed - if (slotErrorsChanged && comfyApp.graph.onTrigger) { - comfyApp.graph.onTrigger('node:slot-errors:changed', { - nodeId: node.id - }) - } + validErrors.forEach((error) => { + const inputName = error.extra_info!.input_name! + const inputIndex = node.findInputSlot(inputName) + if (inputIndex !== -1) { + node.inputs[inputIndex].hasErrors = true + } + }) } comfyApp.canvas.draw(true, true) diff --git a/src/renderer/extensions/vueNodes/components/InputSlot.vue b/src/renderer/extensions/vueNodes/components/InputSlot.vue index 1e8387335..10de3a402 100644 --- a/src/renderer/extensions/vueNodes/components/InputSlot.vue +++ b/src/renderer/extensions/vueNodes/components/InputSlot.vue @@ -5,7 +5,7 @@ @@ -13,7 +13,9 @@
{{ slotData.localized_name || slotData.name || `Input ${index}` }} @@ -39,6 +41,7 @@ import type { INodeSlot } from '@/lib/litegraph/src/litegraph' import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips' import { useSlotElementTracking } from '@/renderer/extensions/vueNodes/composables/useSlotElementTracking' import { useSlotLinkInteraction } from '@/renderer/extensions/vueNodes/composables/useSlotLinkInteraction' +import { useExecutionStore } from '@/stores/executionStore' import { cn } from '@/utils/tailwindUtil' import LODFallback from './LODFallback.vue' @@ -57,7 +60,30 @@ interface InputSlotProps { const props = defineProps() -// Error boundary implementation +const executionStore = useExecutionStore() + +const hasSlotError = computed(() => { + const nodeErrors = executionStore.lastNodeErrors?.[props.nodeId ?? ''] + if (!nodeErrors) return false + + const slotName = props.slotData.name + return nodeErrors.errors.some( + (error) => error.extra_info?.input_name === slotName + ) +}) + +const errorClassesDot = computed(() => { + return hasSlotError.value + ? 'ring-2 ring-error dark-theme:ring-error ring-offset-0 rounded-full' + : '' +}) + +const labelClasses = computed(() => + hasSlotError.value + ? 'text-error dark-theme:text-error font-medium' + : 'dark-theme:text-slate-200 text-stone-200' +) + const renderError = ref(null) const { toastErrorHandler } = useErrorHandling() @@ -81,8 +107,12 @@ onErrorCaptured((error) => { return false }) -// Get slot color based on type -const slotColor = computed(() => getSlotColor(props.slotData.type)) +const slotColor = computed(() => { + if (hasSlotError.value) { + return 'var(--color-error)' + } + return getSlotColor(props.slotData.type) +}) const slotWrapperClass = computed(() => cn( @@ -103,8 +133,6 @@ const connectionDotRef = ref | null>(null) const slotElRef = ref(null) -// Watch for when the child component's ref becomes available -// Vue automatically unwraps the Ref when exposing it watchEffect(() => { const el = connectionDotRef.value?.slotElRef slotElRef.value = el || null diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index dc03dae00..a61111bcf 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -10,7 +10,7 @@ cn( 'bg-white dark-theme:bg-charcoal-800', 'lg-node absolute rounded-2xl', - 'border border-solid border-sand-100 dark-theme:border-charcoal-600', + 'border-2 border-solid border-sand-100 dark-theme:border-charcoal-600', // hover (only when node should handle events) shouldHandleNodePointerEvents && 'hover:ring-7 ring-gray-500/50 dark-theme:ring-gray-500/20', @@ -101,7 +101,11 @@ > @@ -215,16 +219,12 @@ const hasExecutionError = computed( () => executionStore.lastExecutionErrorNodeId === nodeData.id ) -// Computed error states for styling 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?.hasErrors) || - nodeData.outputs?.some((slot) => slot?.hasErrors) + (executionStore.lastNodeErrors?.[nodeData.id]?.errors.length ?? 0) > 0 ) }) @@ -316,26 +316,19 @@ const { latestPreviewUrl, shouldShowPreviewImg } = useNodePreviewState( ) const borderClass = computed(() => { - if (hasAnyError.value) { - return 'border-error dark-theme:border-error' - } - if (executing.value) { - return 'border-blue-500' - } - return undefined + return ( + (hasAnyError.value && 'border-error dark-theme:border-error') || + (executing.value && 'border-blue-500') + ) }) const outlineClass = computed(() => { - if (!isSelected.value) { - return undefined - } - if (hasAnyError.value) { - return 'outline-error dark-theme:outline-error' - } - if (executing.value) { - return 'outline-blue-500 dark-theme:outline-blue-500' - } - return 'outline-black dark-theme:outline-white' + return ( + isSelected.value && + ((hasAnyError.value && 'outline-error dark-theme:outline-error') || + (executing.value && 'outline-blue-500 dark-theme:outline-blue-500') || + 'outline-black dark-theme:outline-white') + ) }) // Event handlers