Skip to content

Commit a2c7db9

Browse files
arjansinghgithub-actions
andauthored
[fix] add InputSlot and dot error state (#5813)
## Summary Error states were not getting propagated down to the InputSlots from the API Repsonse I created a provider and injected error state. It seemed like a way better idea than prop drilling or building a composable that only two nodes (`InputSlot` and `OutputSlot`) would need. ## Changes The follow are now error code red when an input node has errors: 1. There's a error round border around the dot. 2. The dot is error colored. 3. The input text is error colored. This treatment was okay after feedback from design. ## Screenshots - Error State <img width="749" height="616" alt="Screenshot 2025-09-26 at 9 02 58 PM" src="https://github.com/user-attachments/assets/55c7edc9-081b-4a9d-9753-120465959b5d" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5813-fix-add-InputSlot-and-dot-error-state-27b6d73d36508151a955e485f00a2d05) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <[email protected]>
1 parent 840f7f0 commit a2c7db9

File tree

10 files changed

+66
-55
lines changed

10 files changed

+66
-55
lines changed
77 Bytes
Loading
-76 Bytes
Loading
163 Bytes
Loading
235 Bytes
Loading
209 Bytes
Loading
200 Bytes
Loading
202 Bytes
Loading

src/components/graph/GraphCanvas.vue

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ import { useGlobalLitegraph } from '@/composables/useGlobalLitegraph'
104104
import { usePaste } from '@/composables/usePaste'
105105
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
106106
import { i18n, t } from '@/i18n'
107-
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
108107
import { useLitegraphSettings } from '@/platform/settings/composables/useLitegraphSettings'
109108
import { CORE_SETTINGS } from '@/platform/settings/constants/coreSettings'
110109
import { useSettingStore } from '@/platform/settings/settingStore'
@@ -293,45 +292,36 @@ watch(
293292
{ deep: true }
294293
)
295294
296-
// Update node slot errors
295+
// Update node slot errors for LiteGraph nodes
296+
// (Vue nodes read from store directly)
297297
watch(
298298
() => executionStore.lastNodeErrors,
299299
(lastNodeErrors) => {
300-
const removeSlotError = (node: LGraphNode) => {
300+
if (!comfyApp.graph) return
301+
302+
for (const node of comfyApp.graph.nodes) {
303+
// Clear existing errors
301304
for (const slot of node.inputs) {
302305
delete slot.hasErrors
303306
}
304307
for (const slot of node.outputs) {
305308
delete slot.hasErrors
306309
}
307-
}
308310
309-
for (const node of comfyApp.graph.nodes) {
310-
removeSlotError(node)
311311
const nodeErrors = lastNodeErrors?.[node.id]
312312
if (!nodeErrors) continue
313313
314314
const validErrors = nodeErrors.errors.filter(
315315
(error) => error.extra_info?.input_name !== undefined
316316
)
317-
const slotErrorsChanged =
318-
validErrors.length > 0 &&
319-
validErrors.some((error) => {
320-
const inputName = error.extra_info!.input_name!
321-
const inputIndex = node.findInputSlot(inputName)
322-
if (inputIndex !== -1) {
323-
node.inputs[inputIndex].hasErrors = true
324-
return true
325-
}
326-
return false
327-
})
328-
329-
// Trigger Vue node data update if slot errors changed
330-
if (slotErrorsChanged && comfyApp.graph.onTrigger) {
331-
comfyApp.graph.onTrigger('node:slot-errors:changed', {
332-
nodeId: node.id
333-
})
334-
}
317+
318+
validErrors.forEach((error) => {
319+
const inputName = error.extra_info!.input_name!
320+
const inputIndex = node.findInputSlot(inputName)
321+
if (inputIndex !== -1) {
322+
node.inputs[inputIndex].hasErrors = true
323+
}
324+
})
335325
}
336326
337327
comfyApp.canvas.draw(true, true)

src/renderer/extensions/vueNodes/components/InputSlot.vue

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55
<SlotConnectionDot
66
ref="connectionDotRef"
77
:color="slotColor"
8-
class="-translate-x-1/2"
8+
:class="cn('-translate-x-1/2', errorClassesDot)"
99
v-on="readonly ? {} : { pointerdown: onPointerDown }"
1010
/>
1111

1212
<!-- Slot Name -->
1313
<div class="relative">
1414
<span
1515
v-if="!dotOnly"
16-
class="whitespace-nowrap text-sm font-normal dark-theme:text-slate-200 text-stone-200 lod-toggle"
16+
:class="
17+
cn('whitespace-nowrap text-sm font-normal lod-toggle', labelClasses)
18+
"
1719
>
1820
{{ slotData.localized_name || slotData.name || `Input ${index}` }}
1921
</span>
@@ -39,6 +41,7 @@ import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
3941
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
4042
import { useSlotElementTracking } from '@/renderer/extensions/vueNodes/composables/useSlotElementTracking'
4143
import { useSlotLinkInteraction } from '@/renderer/extensions/vueNodes/composables/useSlotLinkInteraction'
44+
import { useExecutionStore } from '@/stores/executionStore'
4245
import { cn } from '@/utils/tailwindUtil'
4346
4447
import LODFallback from './LODFallback.vue'
@@ -57,7 +60,30 @@ interface InputSlotProps {
5760
5861
const props = defineProps<InputSlotProps>()
5962
60-
// Error boundary implementation
63+
const executionStore = useExecutionStore()
64+
65+
const hasSlotError = computed(() => {
66+
const nodeErrors = executionStore.lastNodeErrors?.[props.nodeId ?? '']
67+
if (!nodeErrors) return false
68+
69+
const slotName = props.slotData.name
70+
return nodeErrors.errors.some(
71+
(error) => error.extra_info?.input_name === slotName
72+
)
73+
})
74+
75+
const errorClassesDot = computed(() => {
76+
return hasSlotError.value
77+
? 'ring-2 ring-error dark-theme:ring-error ring-offset-0 rounded-full'
78+
: ''
79+
})
80+
81+
const labelClasses = computed(() =>
82+
hasSlotError.value
83+
? 'text-error dark-theme:text-error font-medium'
84+
: 'dark-theme:text-slate-200 text-stone-200'
85+
)
86+
6187
const renderError = ref<string | null>(null)
6288
const { toastErrorHandler } = useErrorHandling()
6389
@@ -81,8 +107,12 @@ onErrorCaptured((error) => {
81107
return false
82108
})
83109
84-
// Get slot color based on type
85-
const slotColor = computed(() => getSlotColor(props.slotData.type))
110+
const slotColor = computed(() => {
111+
if (hasSlotError.value) {
112+
return 'var(--color-error)'
113+
}
114+
return getSlotColor(props.slotData.type)
115+
})
86116
87117
const slotWrapperClass = computed(() =>
88118
cn(
@@ -103,8 +133,6 @@ const connectionDotRef = ref<ComponentPublicInstance<{
103133
}> | null>(null)
104134
const slotElRef = ref<HTMLElement | null>(null)
105135
106-
// Watch for when the child component's ref becomes available
107-
// Vue automatically unwraps the Ref when exposing it
108136
watchEffect(() => {
109137
const el = connectionDotRef.value?.slotElRef
110138
slotElRef.value = el || null

src/renderer/extensions/vueNodes/components/LGraphNode.vue

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
cn(
1111
'bg-white dark-theme:bg-charcoal-800',
1212
'lg-node absolute rounded-2xl',
13-
'border border-solid border-sand-100 dark-theme:border-charcoal-600',
13+
'border-2 border-solid border-sand-100 dark-theme:border-charcoal-600',
1414
// hover (only when node should handle events)
1515
shouldHandleNodePointerEvents &&
1616
'hover:ring-7 ring-gray-500/50 dark-theme:ring-gray-500/20',
@@ -101,7 +101,11 @@
101101
>
102102
<!-- Slots only rendered at full detail -->
103103
<NodeSlots
104-
v-memo="[nodeData.inputs?.length, nodeData.outputs?.length]"
104+
v-memo="[
105+
nodeData.inputs?.length,
106+
nodeData.outputs?.length,
107+
executionStore.lastNodeErrors
108+
]"
105109
:node-data="nodeData"
106110
:readonly="readonly"
107111
/>
@@ -215,16 +219,12 @@ const hasExecutionError = computed(
215219
() => executionStore.lastExecutionErrorNodeId === nodeData.id
216220
)
217221
218-
// Computed error states for styling
219222
const hasAnyError = computed((): boolean => {
220223
return !!(
221224
hasExecutionError.value ||
222225
nodeData.hasErrors ||
223226
error ||
224-
// Type assertions needed because VueNodeData.inputs/outputs are typed as unknown[]
225-
// but at runtime they contain INodeInputSlot/INodeOutputSlot objects
226-
nodeData.inputs?.some((slot) => slot?.hasErrors) ||
227-
nodeData.outputs?.some((slot) => slot?.hasErrors)
227+
(executionStore.lastNodeErrors?.[nodeData.id]?.errors.length ?? 0) > 0
228228
)
229229
})
230230
@@ -316,26 +316,19 @@ const { latestPreviewUrl, shouldShowPreviewImg } = useNodePreviewState(
316316
)
317317
318318
const borderClass = computed(() => {
319-
if (hasAnyError.value) {
320-
return 'border-error dark-theme:border-error'
321-
}
322-
if (executing.value) {
323-
return 'border-blue-500'
324-
}
325-
return undefined
319+
return (
320+
(hasAnyError.value && 'border-error dark-theme:border-error') ||
321+
(executing.value && 'border-blue-500')
322+
)
326323
})
327324
328325
const outlineClass = computed(() => {
329-
if (!isSelected.value) {
330-
return undefined
331-
}
332-
if (hasAnyError.value) {
333-
return 'outline-error dark-theme:outline-error'
334-
}
335-
if (executing.value) {
336-
return 'outline-blue-500 dark-theme:outline-blue-500'
337-
}
338-
return 'outline-black dark-theme:outline-white'
326+
return (
327+
isSelected.value &&
328+
((hasAnyError.value && 'outline-error dark-theme:outline-error') ||
329+
(executing.value && 'outline-blue-500 dark-theme:outline-blue-500') ||
330+
'outline-black dark-theme:outline-white')
331+
)
339332
})
340333
341334
// Event handlers

0 commit comments

Comments
 (0)