diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index e4031fd51..ffa64ca2c 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -223,7 +223,6 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => { selected: node.selected || false, executing: false, // Will be updated separately based on execution state subgraphId, - hasErrors: !!node.has_errors, widgets: safeWidgets, inputs: node.inputs ? [...node.inputs] : undefined, diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index b0802df93..78ac4effd 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -13,7 +13,7 @@ // border 'border border-solid border-sand-100 dark-theme:border-charcoal-300', !!executing && 'border-blue-100 dark-theme:border-blue-100', - !!(error || nodeData.hasErrors) && 'border-error', + hasAnyError && 'border-error', // hover 'hover:ring-7 ring-gray-500/50 dark-theme:ring-gray-500/20', // Selected @@ -21,7 +21,7 @@ !!isSelected && 'outline-black dark-theme:outline-white', !!(isSelected && executing) && 'outline-blue-100 dark-theme:outline-blue-100', - !!(isSelected && (error || nodeData.hasErrors)) && 'outline-error', + isSelected && hasAnyError && 'outline-error', { 'animate-pulse': executing, 'opacity-50': nodeData.mode === 4, @@ -146,6 +146,7 @@ import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayo import { LODLevel, useLOD } from '@/renderer/extensions/vueNodes/lod/useLOD' import { ExecutedWsMessage } from '@/schemas/apiSchema' import { app } from '@/scripts/app' +import { useExecutionStore } from '@/stores/executionStore' import { useNodeOutputStore } from '@/stores/imagePreviewStore' import { getNodeByLocatorId } from '@/utils/graphTraversalUtil' import { cn } from '@/utils/tailwindUtil' @@ -167,7 +168,12 @@ interface LGraphNodeProps { zoomLevel?: number } -const props = defineProps() +const { + nodeData, + error = null, + readonly = false, + zoomLevel = 1 +} = defineProps() const emit = defineEmits<{ 'node-click': [ @@ -186,7 +192,7 @@ const emit = defineEmits<{ 'update:title': [nodeId: string, newTitle: string] }>() -useVueElementTracking(props.nodeData.id, 'node') +useVueElementTracking(nodeData.id, 'node') // Inject selection state from parent const selectedNodeIds = inject(SelectedNodeIdsKey) @@ -198,14 +204,25 @@ if (!selectedNodeIds) { // Computed selection state - only this node re-evaluates when its selection changes const isSelected = computed(() => { - return selectedNodeIds.value.has(props.nodeData.id) + return selectedNodeIds.value.has(nodeData.id) }) // Use execution state composable -const { executing, progress } = useNodeExecutionState(props.nodeData.id) +const { executing, progress } = useNodeExecutionState(nodeData.id) + +// Direct access to execution store for error state +const executionStore = useExecutionStore() +const hasExecutionError = computed( + () => executionStore.lastExecutionErrorNodeId === nodeData.id +) + +// Computed error states for styling +const hasAnyError = computed( + (): boolean => !!(hasExecutionError.value || nodeData.hasErrors || error) +) // LOD (Level of Detail) system based on zoom level -const zoomRef = toRef(() => props.zoomLevel ?? 1) +const zoomRef = toRef(() => zoomLevel) const { lodLevel, shouldRenderWidgets, @@ -234,7 +251,7 @@ const { startDrag, handleDrag: handleLayoutDrag, endDrag -} = useNodeLayout(props.nodeData.id) +} = useNodeLayout(nodeData.id) // Drag state for styling const isDragging = ref(false) @@ -247,11 +264,11 @@ const lastX = ref(0) const DRAG_THRESHOLD_PX = 4 // Track collapsed state -const isCollapsed = ref(props.nodeData.flags?.collapsed ?? false) +const isCollapsed = ref(nodeData.flags?.collapsed ?? false) // Watch for external changes to the collapsed state watch( - () => props.nodeData.flags?.collapsed, + () => nodeData.flags?.collapsed, (newCollapsed: boolean | undefined) => { if (newCollapsed !== undefined && newCollapsed !== isCollapsed.value) { isCollapsed.value = newCollapsed @@ -272,7 +289,7 @@ const progressClasses = 'h-2 bg-primary-500 transition-all duration-300' // Common condition computations to avoid repetition const shouldShowWidgets = computed( - () => shouldRenderWidgets.value && props.nodeData.widgets?.length + () => shouldRenderWidgets.value && nodeData.widgets?.length ) const shouldShowContent = computed( @@ -281,7 +298,7 @@ const shouldShowContent = computed( // Event handlers const handlePointerDown = (event: PointerEvent) => { - if (!props.nodeData) { + if (!nodeData) { console.warn('LGraphNode: nodeData is null/undefined in handlePointerDown') return } @@ -308,13 +325,13 @@ const handlePointerUp = (event: PointerEvent) => { const dx = event.clientX - lastX.value const dy = event.clientY - lastY.value const wasDragging = Math.hypot(dx, dy) > DRAG_THRESHOLD_PX - emit('node-click', event, props.nodeData, wasDragging) + emit('node-click', event, nodeData, wasDragging) } const handleCollapse = () => { isCollapsed.value = !isCollapsed.value // Emit event so parent can sync with LiteGraph if needed - emit('update:collapsed', props.nodeData.id, isCollapsed.value) + emit('update:collapsed', nodeData.id, isCollapsed.value) } const handleSlotClick = ( @@ -322,15 +339,15 @@ const handleSlotClick = ( slotIndex: number, isInput: boolean ) => { - if (!props.nodeData) { + if (!nodeData) { console.warn('LGraphNode: nodeData is null/undefined in handleSlotClick') return } - emit('slot-click', event, props.nodeData, slotIndex, isInput) + emit('slot-click', event, nodeData, slotIndex, isInput) } const handleTitleUpdate = (newTitle: string) => { - emit('update:title', props.nodeData.id, newTitle) + emit('update:title', nodeData.id, newTitle) } const nodeOutputs = useNodeOutputStore() @@ -338,9 +355,9 @@ const nodeOutputs = useNodeOutputStore() const nodeImageUrls = ref([]) const onNodeOutputsUpdate = (newOutputs: ExecutedWsMessage['output']) => { // Construct proper locator ID using subgraph ID from VueNodeData - const locatorId = props.nodeData.subgraphId - ? `${props.nodeData.subgraphId}:${props.nodeData.id}` - : props.nodeData.id + const locatorId = nodeData.subgraphId + ? `${nodeData.subgraphId}:${nodeData.id}` + : nodeData.id // Use root graph for getNodeByLocatorId since it needs to traverse from root const rootGraph = app.graph?.rootGraph || app.graph @@ -363,9 +380,7 @@ const onNodeOutputsUpdate = (newOutputs: ExecutedWsMessage['output']) => { } const nodeOutputLocatorId = computed(() => - props.nodeData.subgraphId - ? `${props.nodeData.subgraphId}:${props.nodeData.id}` - : props.nodeData.id + nodeData.subgraphId ? `${nodeData.subgraphId}:${nodeData.id}` : nodeData.id ) watch( diff --git a/src/renderer/extensions/vueNodes/components/NodeHeader.vue b/src/renderer/extensions/vueNodes/components/NodeHeader.vue index 3197a872c..286c7ee4b 100644 --- a/src/renderer/extensions/vueNodes/components/NodeHeader.vue +++ b/src/renderer/extensions/vueNodes/components/NodeHeader.vue @@ -36,7 +36,7 @@