Skip to content

Commit f97cf77

Browse files
viva-jinyiclaude
andauthored
Add cloud-specific missing nodes warning dialog (#6659)
## Summary Implements a cloud-specific dialog to warn users when loading workflows with unsupported custom nodes in Comfy Cloud. The new dialog follows the visual style of the node conflict dialog and provides appropriate messaging and actions. ## Changes - Add `CloudMissingNodesHeader`, `CloudMissingNodesContent`, and `CloudMissingNodesFooter` components - Add `showCloudLoadWorkflowWarning` to dialogService - Update app.ts to show cloud dialog when in cloud environment - Add `cloud.missingNodes` translations ## Screenshots The dialog displays: - Warning icon and title - Description of the issue - List of missing nodes - "Learn more" link and "Ok, got it" button ## Test plan 1. Load a workflow with custom nodes in cloud environment 2. Verify cloud-specific dialog appears with appropriate styling 3. Verify "Learn more" button opens cloud documentation 4. Verify "Ok, got it" button closes dialog ## Notes - Two unused i18n keys (`cloud.missingNodes.cannotRun` and `cloud.missingNodes.missingNodes`) are included for future PR that will add breadcrumb warning icons and run button disable functionality <img width="1367" height="988" alt="스크린샷 2025-11-12 오후 4 33 38" src="https://github.com/user-attachments/assets/75a6fced-959f-4e93-9b82-4e61b53a9ee4" /> 🤖 Generated with [Claude Code](https://claude.com/claude-code) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6659-Add-cloud-specific-missing-nodes-warning-dialog-2a96d73d36508161ae55fe157f55cd17) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude <[email protected]>
1 parent 2cf3739 commit f97cf77

File tree

6 files changed

+164
-1
lines changed

6 files changed

+164
-1
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<template>
2+
<div class="flex w-[490px] flex-col">
3+
<ContentDivider :width="1" />
4+
<div class="flex h-full w-full flex-col gap-4 p-4">
5+
<!-- Description -->
6+
<div>
7+
<p class="m-0 text-sm leading-4 text-muted-foreground">
8+
{{ $t('cloud.missingNodes.description') }}
9+
<br /><br />
10+
{{ $t('cloud.missingNodes.priorityMessage') }}
11+
</p>
12+
</div>
13+
14+
<!-- Missing Nodes List Wrapper -->
15+
<div
16+
class="flex flex-col max-h-[256px] rounded-lg py-2 scrollbar-custom bg-component-node-widget-background"
17+
>
18+
<div
19+
v-for="(node, i) in uniqueNodes"
20+
:key="i"
21+
class="flex min-h-8 items-center justify-between px-4 py-2 bg-component-node-widget-background text-text-secondary"
22+
>
23+
<span class="text-xs">
24+
{{ node.label }}
25+
</span>
26+
</div>
27+
</div>
28+
29+
<!-- Bottom instruction -->
30+
<div>
31+
<p class="m-0 text-sm leading-4 text-muted-foreground">
32+
{{ $t('cloud.missingNodes.replacementInstruction') }}
33+
</p>
34+
</div>
35+
</div>
36+
<ContentDivider :width="1" />
37+
</div>
38+
</template>
39+
40+
<script setup lang="ts">
41+
import { computed } from 'vue'
42+
43+
import ContentDivider from '@/components/common/ContentDivider.vue'
44+
import type { MissingNodeType } from '@/types/comfy'
45+
46+
const props = defineProps<{
47+
missingNodeTypes: MissingNodeType[]
48+
}>()
49+
50+
const uniqueNodes = computed(() => {
51+
const seenTypes = new Set()
52+
return props.missingNodeTypes
53+
.filter((node) => {
54+
const type = typeof node === 'object' ? node.type : node
55+
if (seenTypes.has(type)) return false
56+
seenTypes.add(type)
57+
return true
58+
})
59+
.map((node) => {
60+
if (typeof node === 'object') {
61+
return {
62+
label: node.type,
63+
hint: node.hint,
64+
action: node.action
65+
}
66+
}
67+
return { label: node }
68+
})
69+
})
70+
</script>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<template>
2+
<div class="flex w-full items-center justify-between gap-2 py-2 px-4">
3+
<IconTextButton
4+
:label="$t('cloud.missingNodes.learnMore')"
5+
type="transparent"
6+
size="sm"
7+
icon-position="left"
8+
@click="handleLearnMoreClick"
9+
>
10+
<template #icon>
11+
<i class="icon-[lucide--info]"></i>
12+
</template>
13+
</IconTextButton>
14+
<TextButton
15+
:label="$t('cloud.missingNodes.gotIt')"
16+
type="secondary"
17+
size="md"
18+
@click="handleGotItClick"
19+
/>
20+
</div>
21+
</template>
22+
23+
<script setup lang="ts">
24+
import IconTextButton from '@/components/button/IconTextButton.vue'
25+
import TextButton from '@/components/button/TextButton.vue'
26+
import { useDialogStore } from '@/stores/dialogStore'
27+
28+
const dialogStore = useDialogStore()
29+
30+
const handleLearnMoreClick = () => {
31+
window.open('https://www.comfy.org/cloud', '_blank')
32+
}
33+
34+
const handleGotItClick = () => {
35+
dialogStore.closeDialog({ key: 'global-cloud-missing-nodes' })
36+
}
37+
</script>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<template>
2+
<div class="flex w-full items-center justify-between p-4">
3+
<div class="flex items-center gap-2">
4+
<i class="icon-[lucide--triangle-alert] text-gold-600"></i>
5+
<p class="m-0 text-sm">
6+
{{ $t('cloud.missingNodes.title') }}
7+
</p>
8+
</div>
9+
</div>
10+
</template>

src/locales/en/main.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2049,6 +2049,18 @@
20492049
"vueNodesBanner": {
20502050
"message": "Nodes just got a new look and feel",
20512051
"tryItOut": "Try it out"
2052+
},
2053+
"cloud": {
2054+
"missingNodes": {
2055+
"title": "These nodes aren't available on Comfy Cloud yet",
2056+
"description": "This workflow uses custom nodes that aren't supported in the Cloud version yet.",
2057+
"priorityMessage": "We've automatically flagged these nodes so we can prioritize adding them.",
2058+
"missingNodes": "Missing Nodes",
2059+
"replacementInstruction": "In the meantime, replace these nodes (highlighted red on the canvas) with supported ones if possible, or try a different workflow.",
2060+
"learnMore": "Learn more",
2061+
"gotIt": "Ok, got it",
2062+
"cannotRun": "Workflow contains unsupported nodes (highlighted red). Remove these to run the workflow. "
2063+
}
20522064
}
20532065
}
20542066

src/scripts/app.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1001,7 +1001,11 @@ export class ComfyApp {
10011001

10021002
private showMissingNodesError(missingNodeTypes: MissingNodeType[]) {
10031003
if (useSettingStore().get('Comfy.Workflow.ShowMissingNodesWarning')) {
1004-
useDialogService().showLoadWorkflowWarning({ missingNodeTypes })
1004+
if (isCloud) {
1005+
useDialogService().showCloudLoadWorkflowWarning({ missingNodeTypes })
1006+
} else {
1007+
useDialogService().showLoadWorkflowWarning({ missingNodeTypes })
1008+
}
10051009
}
10061010
}
10071011

src/services/dialogService.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { merge } from 'es-toolkit/compat'
22
import type { Component } from 'vue'
33

44
import ApiNodesSignInContent from '@/components/dialog/content/ApiNodesSignInContent.vue'
5+
import CloudMissingNodesContent from '@/components/dialog/content/CloudMissingNodesContent.vue'
6+
import CloudMissingNodesFooter from '@/components/dialog/content/CloudMissingNodesFooter.vue'
7+
import CloudMissingNodesHeader from '@/components/dialog/content/CloudMissingNodesHeader.vue'
58
import ConfirmationDialogContent from '@/components/dialog/content/ConfirmationDialogContent.vue'
69
import ErrorDialogContent from '@/components/dialog/content/ErrorDialogContent.vue'
710
import LoadWorkflowWarning from '@/components/dialog/content/LoadWorkflowWarning.vue'
@@ -32,6 +35,7 @@ import NodeConflictDialogContent from '@/workbench/extensions/manager/components
3235
import NodeConflictFooter from '@/workbench/extensions/manager/components/manager/NodeConflictFooter.vue'
3336
import NodeConflictHeader from '@/workbench/extensions/manager/components/manager/NodeConflictHeader.vue'
3437
import type { ConflictDetectionResult } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
38+
import type { ComponentProps } from 'vue-component-type-helpers'
3539

3640
export type ConfirmationDialogType =
3741
| 'default'
@@ -53,6 +57,31 @@ export const useDialogService = () => {
5357
})
5458
}
5559

60+
function showCloudLoadWorkflowWarning(
61+
props: ComponentProps<typeof CloudMissingNodesContent>
62+
) {
63+
dialogStore.showDialog({
64+
key: 'global-cloud-missing-nodes',
65+
headerComponent: CloudMissingNodesHeader,
66+
footerComponent: CloudMissingNodesFooter,
67+
component: CloudMissingNodesContent,
68+
dialogComponentProps: {
69+
closable: true,
70+
pt: {
71+
header: { class: '!p-0 !m-0' },
72+
content: { class: '!p-0 overflow-y-hidden' },
73+
footer: { class: '!p-0' },
74+
pcCloseButton: {
75+
root: {
76+
class: '!w-7 !h-7 !border-none !outline-none !p-2 !m-1.5'
77+
}
78+
}
79+
}
80+
},
81+
props
82+
})
83+
}
84+
5685
function showMissingModelsWarning(
5786
props: InstanceType<typeof MissingModelsWarning>['$props']
5887
) {
@@ -520,6 +549,7 @@ export const useDialogService = () => {
520549

521550
return {
522551
showLoadWorkflowWarning,
552+
showCloudLoadWorkflowWarning,
523553
showMissingModelsWarning,
524554
showSettingsDialog,
525555
showAboutDialog,

0 commit comments

Comments
 (0)