Skip to content

Commit a6f823e

Browse files
viva-jinyiclaude
andcommitted
[feat] Add missing nodes warning UI to queue button and breadcrumb
Display visual warnings when workflow contains missing nodes. Key changes: - Convert useMissingNodes to singleton with createSharedComposable - Add activeWorkflow watch for automatic updates on workflow changes - Add warning icon + disabled state + tooltip to ComfyQueueButton - Add warning icon + tooltip to root SubgraphBreadcrumbItem - Add i18n keys (menu.runWorkflowDisabled, breadcrumbsMenu.missingNodesWarning) Technical rationale: - createSharedComposable: All components share same instance, prevents duplicate API calls - watch: Ensures persistent components (ComfyQueueButton) update automatically on workflow changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ba768c3 commit a6f823e

File tree

4 files changed

+65
-9
lines changed

4 files changed

+65
-9
lines changed

src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,24 @@
22
<div class="queue-button-group flex">
33
<SplitButton
44
v-tooltip.bottom="{
5-
value: workspaceStore.shiftDown
6-
? $t('menu.runWorkflowFront')
7-
: $t('menu.runWorkflow'),
5+
value: queueButtonTooltip,
86
showDelay: 600
97
}"
108
class="comfyui-queue-button"
119
:label="String(activeQueueModeMenuItem?.label ?? '')"
1210
severity="primary"
1311
size="small"
1412
:model="queueModeMenuItems"
13+
:disabled="hasMissingNodes"
1514
data-testid="queue-button"
1615
@click="queuePrompt"
1716
>
1817
<template #icon>
19-
<i v-if="workspaceStore.shiftDown" class="icon-[lucide--list-start]" />
18+
<i v-if="hasMissingNodes" class="icon-[lucide--triangle-alert]" />
19+
<i
20+
v-else-if="workspaceStore.shiftDown"
21+
class="icon-[lucide--list-start]"
22+
/>
2023
<i v-else-if="queueMode === 'disabled'" class="icon-[lucide--play]" />
2124
<i
2225
v-else-if="queueMode === 'instant'"
@@ -95,13 +98,16 @@ import {
9598
useQueueSettingsStore
9699
} from '@/stores/queueStore'
97100
import { useWorkspaceStore } from '@/stores/workspaceStore'
101+
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
98102
99103
import BatchCountEdit from '../BatchCountEdit.vue'
100104
101105
const workspaceStore = useWorkspaceStore()
102106
const queueCountStore = storeToRefs(useQueuePendingTaskCountStore())
103107
const { mode: queueMode, batchCount } = storeToRefs(useQueueSettingsStore())
104108
109+
const { hasMissingNodes } = useMissingNodes()
110+
105111
const { t } = useI18n()
106112
const queueModeMenuItemLookup = computed(() => {
107113
const items: Record<string, MenuItem> = {
@@ -157,6 +163,16 @@ const hasPendingTasks = computed(
157163
() => queueCountStore.count.value > 1 || queueMode.value !== 'disabled'
158164
)
159165
166+
const queueButtonTooltip = computed(() => {
167+
if (hasMissingNodes.value) {
168+
return t('menu.runWorkflowDisabled')
169+
}
170+
if (workspaceStore.shiftDown) {
171+
return t('menu.runWorkflowFront')
172+
}
173+
return t('menu.runWorkflow')
174+
})
175+
160176
const commandStore = useCommandStore()
161177
const queuePrompt = async (e: Event) => {
162178
const isShiftPressed = 'shiftKey' in e && e.shiftKey

src/components/breadcrumb/SubgraphBreadcrumbItem.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<a
33
ref="wrapperRef"
44
v-tooltip.bottom="{
5-
value: item.label,
5+
value: tooltipText,
66
showDelay: 512
77
}"
88
draggable="false"
@@ -16,6 +16,10 @@
1616
}"
1717
@click="handleClick"
1818
>
19+
<i
20+
v-if="hasMissingNodes && isRoot"
21+
class="icon-[lucide--triangle-alert] text-gold-600"
22+
/>
1923
<span class="p-breadcrumb-item-label px-2">{{ item.label }}</span>
2024
<Tag v-if="item.isBlueprint" :value="'Blueprint'" severity="primary" />
2125
<i v-if="isActive" class="pi pi-angle-down text-[10px]"></i>
@@ -64,6 +68,7 @@ import { useDialogService } from '@/services/dialogService'
6468
import { useCommandStore } from '@/stores/commandStore'
6569
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
6670
import { appendJsonExt } from '@/utils/formatUtil'
71+
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
6772
6873
interface Props {
6974
item: MenuItem
@@ -74,6 +79,8 @@ const props = withDefaults(defineProps<Props>(), {
7479
isActive: false
7580
})
7681
82+
const { hasMissingNodes } = useMissingNodes()
83+
7784
const { t } = useI18n()
7885
const menu = ref<InstanceType<typeof Menu> & MenuState>()
7986
const dialogService = useDialogService()
@@ -115,6 +122,14 @@ const rename = async (
115122
}
116123
117124
const isRoot = props.item.key === 'root'
125+
126+
const tooltipText = computed(() => {
127+
if (hasMissingNodes.value && isRoot) {
128+
return t('breadcrumbsMenu.missingNodesWarning')
129+
}
130+
return props.item.label
131+
})
132+
118133
const menuItems = computed<MenuItem[]>(() => {
119134
return [
120135
{

src/locales/en/main.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@
737737
"onChangeTooltip": "The workflow will be queued once a change is made",
738738
"runWorkflow": "Run workflow (Shift to queue at front)",
739739
"runWorkflowFront": "Run workflow (Queue at front)",
740+
"runWorkflowDisabled": "Workflow contains unsupported nodes (highlighted red). Remove these to run the workflow.",
740741
"run": "Run",
741742
"execute": "Execute",
742743
"interrupt": "Cancel current run",
@@ -1850,7 +1851,8 @@
18501851
"clearWorkflow": "Clear Workflow",
18511852
"deleteWorkflow": "Delete Workflow",
18521853
"deleteBlueprint": "Delete Blueprint",
1853-
"enterNewName": "Enter new name"
1854+
"enterNewName": "Enter new name",
1855+
"missingNodesWarning": "Workflow contains unsupported nodes (highlighted red)."
18541856
},
18551857
"shortcuts": {
18561858
"shortcuts": "Shortcuts",

src/workbench/extensions/manager/composables/nodePack/useMissingNodes.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { groupBy } from 'es-toolkit/compat'
2-
import { computed, onMounted } from 'vue'
2+
import { createSharedComposable } from '@vueuse/core'
3+
import { computed, onMounted, watch } from 'vue'
34

45
import type { NodeProperty } from '@/lib/litegraph/src/LGraphNode'
56
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
7+
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
68
import { app } from '@/scripts/app'
79
import { useNodeDefStore } from '@/stores/nodeDefStore'
810
import type { components } from '@/types/comfyRegistryTypes'
@@ -14,10 +16,12 @@ import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comf
1416
* Composable to find missing NodePacks from workflow
1517
* Uses the same filtering approach as ManagerDialogContent.vue
1618
* Automatically fetches workflow pack data when initialized
19+
* This is a shared singleton composable - all components use the same instance
1720
*/
18-
export const useMissingNodes = () => {
21+
export const useMissingNodes = createSharedComposable(() => {
1922
const nodeDefStore = useNodeDefStore()
2023
const comfyManagerStore = useComfyManagerStore()
24+
const workflowStore = useWorkflowStore()
2125
const { workflowPacks, isLoading, error, startFetchWorkflowPacks } =
2226
useWorkflowPacks()
2327

@@ -61,17 +65,36 @@ export const useMissingNodes = () => {
6165
return groupBy(missingNodes, (node) => String(node.properties?.ver || ''))
6266
})
6367

68+
// Check if workflow has any missing nodes
69+
const hasMissingNodes = computed(() => {
70+
return (
71+
missingNodePacks.value.length > 0 ||
72+
Object.keys(missingCoreNodes.value).length > 0
73+
)
74+
})
75+
6476
// Automatically fetch workflow pack data when composable is used
6577
onMounted(async () => {
6678
if (!workflowPacks.value.length && !isLoading.value) {
6779
await startFetchWorkflowPacks()
6880
}
6981
})
7082

83+
// Re-fetch workflow packs when active workflow changes
84+
watch(
85+
() => workflowStore.activeWorkflow,
86+
async (newWorkflow, oldWorkflow) => {
87+
if (newWorkflow !== oldWorkflow) {
88+
await startFetchWorkflowPacks()
89+
}
90+
}
91+
)
92+
7193
return {
7294
missingNodePacks,
7395
missingCoreNodes,
96+
hasMissingNodes,
7497
isLoading,
7598
error
7699
}
77-
}
100+
})

0 commit comments

Comments
 (0)