Skip to content

Commit 8dd5a99

Browse files
viva-jinyiclaude
andauthored
[refactor] Unify Cloud/OSS Missing Nodes modal (#6673)
## Summary - Merged separate Cloud and OSS workflow warning modals into single unified modal - Removed legacy LoadWorkflowWarning.vue - Renamed CloudMissingNodes* components to MissingNodes* for clarity - Environment branching now handled internally via isCloud flag - Restructured i18n: removed loadWorkflowWarning, added missingNodes.cloud/oss sections - Improved OSS button styling to match Cloud consistency ## Key Changes - **OSS**: "Open Manager" + "Install All" buttons - **Cloud**: "Learn More" + "Got It" buttons (unchanged) - Single unified modal displays different UI/text based on environment ## 📝 Note on File Renames This PR renames the following files: - `CloudMissingNodesHeader.vue` → `MissingNodesHeader.vue` (R053, 53% similarity) - `CloudMissingNodesContent.vue` → `MissingNodesContent.vue` (R067, 67% similarity) - `LoadWorkflowWarning.vue` → `MissingNodesFooter.vue` (R051, 51% similarity) - `CloudMissingNodesFooter.vue` → Deleted (replaced by new MissingNodesFooter) **Why GitHub PR UI doesn't show renames properly:** GitHub detects renames only when file similarity is above 70%. In this PR, the Cloud/OSS unification significantly modified file contents, resulting in 51-67% similarity. However, **Git history correctly records these as renames**. You can verify with: ```bash git show <commit-hash> --name-status ``` While GitHub UI shows "additions/deletions", these are actually rename + modification operations. ## Test Plan - [x] Test OSS mode: missing nodes modal shows "Open Manager" and "Install All" buttons - [x] Test Cloud mode: missing nodes modal shows "Learn More" and "Got It" buttons - [x] Verify Install All button functionality in OSS - [x] Verify modal closes automatically after all nodes are installed (OSS) [missingnodes.webm](https://github.com/user-attachments/assets/36d3b4b0-ff8b-4b45-824c-3bc15d93f1a2) 🤖 Generated with [Claude Code](https://claude.com/claude-code) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6673-refactor-Unify-Cloud-OSS-Missing-Nodes-modal-2aa6d73d365081a88827d0fa85db4c63) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7a11dc5 commit 8dd5a99

File tree

10 files changed

+100
-180
lines changed

10 files changed

+100
-180
lines changed

src/components/button/IconTextButton.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ defineOptions({
3232
interface IconTextButtonProps extends BaseButtonProps {
3333
iconPosition?: 'left' | 'right'
3434
label: string
35-
onClick: () => void
35+
onClick?: () => void
3636
}
3737
3838
const {

src/components/dialog/content/CloudMissingNodesFooter.vue

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/components/dialog/content/MissingCoreNodesMessage.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ import { computed } from 'vue'
4949
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
5050
import { useSystemStatsStore } from '@/stores/systemStatsStore'
5151
52-
const props = defineProps<{
52+
const { missingCoreNodes } = defineProps<{
5353
missingCoreNodes: Record<string, LGraphNode[]>
5454
}>()
5555
5656
const systemStatsStore = useSystemStatsStore()
5757
5858
const hasMissingCoreNodes = computed(() => {
59-
return Object.keys(props.missingCoreNodes).length > 0
59+
return Object.keys(missingCoreNodes).length > 0
6060
})
6161
6262
// Use computed for reactive version tracking
@@ -66,7 +66,7 @@ const currentComfyUIVersion = computed<string | null>(() => {
6666
})
6767
6868
const sortedMissingCoreNodes = computed(() => {
69-
return Object.entries(props.missingCoreNodes).sort(([a], [b]) => {
69+
return Object.entries(missingCoreNodes).sort(([a], [b]) => {
7070
// Sort by version in descending order (newest first)
7171
return compare(b, a) // Reversed for descending order
7272
})

src/components/dialog/content/CloudMissingNodesContent.vue renamed to src/components/dialog/content/MissingNodesContent.vue

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@
66
<!-- Description -->
77
<div>
88
<p class="m-0 text-sm leading-4 text-muted-foreground">
9-
{{ $t('cloud.missingNodes.description') }}
10-
<br /><br />
11-
{{ $t('cloud.missingNodes.priorityMessage') }}
9+
{{
10+
isCloud
11+
? $t('missingNodes.cloud.description')
12+
: $t('missingNodes.oss.description')
13+
}}
1214
</p>
1315
</div>
16+
<MissingCoreNodesMessage v-if="!isCloud" :missing-core-nodes />
1417

1518
<!-- Missing Nodes List Wrapper -->
1619
<div
17-
class="flex flex-col max-h-[256px] rounded-lg py-2 scrollbar-custom bg-secondary-background"
20+
class="comfy-missing-nodes flex flex-col max-h-[256px] rounded-lg py-2 scrollbar-custom bg-secondary-background"
1821
>
1922
<div
2023
v-for="(node, i) in uniqueNodes"
@@ -24,13 +27,18 @@
2427
<span class="text-xs">
2528
{{ node.label }}
2629
</span>
30+
<span v-if="node.hint" class="text-xs">{{ node.hint }}</span>
2731
</div>
2832
</div>
2933

3034
<!-- Bottom instruction -->
3135
<div>
3236
<p class="m-0 text-sm leading-4 text-muted-foreground">
33-
{{ $t('cloud.missingNodes.replacementInstruction') }}
37+
{{
38+
isCloud
39+
? $t('missingNodes.cloud.replacementInstruction')
40+
: $t('missingNodes.oss.replacementInstruction')
41+
}}
3442
</p>
3543
</div>
3644
</div>
@@ -40,12 +48,18 @@
4048
<script setup lang="ts">
4149
import { computed } from 'vue'
4250
51+
import MissingCoreNodesMessage from '@/components/dialog/content/MissingCoreNodesMessage.vue'
52+
import { isCloud } from '@/platform/distribution/types'
4353
import type { MissingNodeType } from '@/types/comfy'
54+
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
4455
4556
const props = defineProps<{
4657
missingNodeTypes: MissingNodeType[]
4758
}>()
4859
60+
// Get missing core nodes for OSS mode
61+
const { missingCoreNodes } = useMissingNodes()
62+
4963
const uniqueNodes = computed(() => {
5064
const seenTypes = new Set()
5165
return props.missingNodeTypes
Lines changed: 48 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,42 @@
11
<template>
2-
<NoResultsPlaceholder
3-
class="pb-0"
4-
icon="pi pi-exclamation-circle"
5-
:title="$t('loadWorkflowWarning.missingNodesTitle')"
6-
:message="$t('loadWorkflowWarning.missingNodesDescription')"
7-
/>
8-
<MissingCoreNodesMessage :missing-core-nodes="missingCoreNodes" />
9-
<ListBox
10-
:options="uniqueNodes"
11-
option-label="label"
12-
scroll-height="100%"
13-
class="comfy-missing-nodes"
14-
:pt="{
15-
list: { class: 'border-none' }
16-
}"
2+
<!-- Cloud mode: Learn More + Got It buttons -->
3+
<div
4+
v-if="isCloud"
5+
class="flex w-full items-center justify-between gap-2 py-2 px-4"
176
>
18-
<template #option="slotProps">
19-
<div class="align-items-center flex">
20-
<span class="node-type">{{ slotProps.option.label }}</span>
21-
<span v-if="slotProps.option.hint" class="node-hint">{{
22-
slotProps.option.hint
23-
}}</span>
24-
<Button
25-
v-if="slotProps.option.action"
26-
:label="slotProps.option.action.text"
27-
size="small"
28-
outlined
29-
@click="slotProps.option.action.callback"
30-
/>
31-
</div>
32-
</template>
33-
</ListBox>
34-
<div v-if="showManagerButtons" class="flex justify-end py-3">
7+
<IconTextButton
8+
:label="$t('missingNodes.cloud.learnMore')"
9+
type="transparent"
10+
size="sm"
11+
icon-position="left"
12+
as="a"
13+
href="https://www.comfy.org/cloud"
14+
target="_blank"
15+
rel="noopener noreferrer"
16+
>
17+
<template #icon>
18+
<i class="icon-[lucide--info]"></i>
19+
</template>
20+
</IconTextButton>
21+
<TextButton
22+
:label="$t('missingNodes.cloud.gotIt')"
23+
type="secondary"
24+
size="md"
25+
@click="handleGotItClick"
26+
/>
27+
</div>
28+
29+
<!-- OSS mode: Open Manager + Install All buttons -->
30+
<div v-else-if="showManagerButtons" class="flex justify-end gap-1 py-2 px-4">
31+
<TextButton
32+
:label="$t('g.openManager')"
33+
type="transparent"
34+
size="sm"
35+
@click="openManager"
36+
/>
3537
<PackInstallButton
3638
v-if="showInstallAllButton"
39+
type="secondary"
3740
size="md"
3841
:disabled="
3942
isLoading || !!error || missingNodePacks.length === 0 || isInstalling
@@ -46,40 +49,32 @@
4649
: $t('manager.installAllMissingNodes')
4750
"
4851
/>
49-
<Button
50-
:label="$t('g.openManager')"
51-
size="small"
52-
outlined
53-
@click="openManager"
54-
/>
5552
</div>
5653
</template>
5754

5855
<script setup lang="ts">
59-
import Button from 'primevue/button'
60-
import ListBox from 'primevue/listbox'
6156
import { computed, nextTick, watch } from 'vue'
6257
import { useI18n } from 'vue-i18n'
6358
64-
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
65-
import MissingCoreNodesMessage from '@/components/dialog/content/MissingCoreNodesMessage.vue'
59+
import IconTextButton from '@/components/button/IconTextButton.vue'
60+
import TextButton from '@/components/button/TextButton.vue'
61+
import { isCloud } from '@/platform/distribution/types'
6662
import { useToastStore } from '@/platform/updates/common/toastStore'
6763
import { useDialogStore } from '@/stores/dialogStore'
68-
import type { MissingNodeType } from '@/types/comfy'
6964
import PackInstallButton from '@/workbench/extensions/manager/components/manager/button/PackInstallButton.vue'
7065
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
7166
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
7267
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
7368
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
7469
75-
const props = defineProps<{
76-
missingNodeTypes: MissingNodeType[]
77-
}>()
70+
const dialogStore = useDialogStore()
71+
const { t } = useI18n()
7872
79-
// Get missing node packs from workflow with loading and error states
80-
const { missingNodePacks, isLoading, error, missingCoreNodes } =
81-
useMissingNodes()
73+
const handleGotItClick = () => {
74+
dialogStore.closeDialog({ key: 'global-missing-nodes' })
75+
}
8276
77+
const { missingNodePacks, isLoading, error } = useMissingNodes()
8378
const comfyManagerStore = useComfyManagerStore()
8479
const managerState = useManagerState()
8580
@@ -91,27 +86,6 @@ const isInstalling = computed(() => {
9186
)
9287
})
9388
94-
const uniqueNodes = computed(() => {
95-
const seenTypes = new Set()
96-
return props.missingNodeTypes
97-
.filter((node) => {
98-
const type = typeof node === 'object' ? node.type : node
99-
if (seenTypes.has(type)) return false
100-
seenTypes.add(type)
101-
return true
102-
})
103-
.map((node) => {
104-
if (typeof node === 'object') {
105-
return {
106-
label: node.type,
107-
hint: node.hint,
108-
action: node.action
109-
}
110-
}
111-
return { label: node }
112-
})
113-
})
114-
11589
// Show manager buttons unless manager is disabled
11690
const showManagerButtons = computed(() => {
11791
return managerState.shouldShowManagerButtons.value
@@ -129,9 +103,6 @@ const openManager = async () => {
129103
})
130104
}
131105
132-
const { t } = useI18n()
133-
const dialogStore = useDialogStore()
134-
135106
// Computed to check if all missing nodes have been installed
136107
const allMissingNodesInstalled = computed(() => {
137108
return (
@@ -140,13 +111,14 @@ const allMissingNodesInstalled = computed(() => {
140111
missingNodePacks.value?.length === 0
141112
)
142113
})
143-
// Watch for completion and close dialog
114+
115+
// Watch for completion and close dialog (OSS mode only)
144116
watch(allMissingNodesInstalled, async (allInstalled) => {
145-
if (allInstalled && showInstallAllButton.value) {
117+
if (!isCloud && allInstalled && showInstallAllButton.value) {
146118
// Use nextTick to ensure state updates are complete
147119
await nextTick()
148120
149-
dialogStore.closeDialog({ key: 'global-load-workflow-warning' })
121+
dialogStore.closeDialog({ key: 'global-missing-nodes' })
150122
151123
// Show success toast
152124
useToastStore().add({
@@ -158,20 +130,3 @@ watch(allMissingNodesInstalled, async (allInstalled) => {
158130
}
159131
})
160132
</script>
161-
162-
<style scoped>
163-
.comfy-missing-nodes {
164-
max-height: 300px;
165-
overflow-y: auto;
166-
}
167-
168-
.node-hint {
169-
margin-left: 0.5rem;
170-
font-style: italic;
171-
color: var(--text-color-secondary);
172-
}
173-
174-
:deep(.p-button) {
175-
margin-left: auto;
176-
}
177-
</style>

src/components/dialog/content/CloudMissingNodesHeader.vue renamed to src/components/dialog/content/MissingNodesHeader.vue

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@
33
<div class="flex items-center gap-2">
44
<i class="icon-[lucide--triangle-alert] text-gold-600"></i>
55
<p class="m-0 text-sm">
6-
{{ $t('cloud.missingNodes.title') }}
6+
{{
7+
isCloud
8+
? $t('missingNodes.cloud.title')
9+
: $t('missingNodes.oss.title')
10+
}}
711
</p>
812
</div>
913
</div>
1014
</template>
15+
16+
<script setup lang="ts">
17+
import { isCloud } from '@/platform/distribution/types'
18+
</script>

0 commit comments

Comments
 (0)