Skip to content

Commit e5ba351

Browse files
deepme987claude
andcommitted
[feat] Add cloud notification modal for macOS desktop users
Adds a one-time informational modal that introduces Comfy Cloud to macOS desktop users. The modal emphasizes that ComfyUI remains free and open source, with Cloud as an optional service for GPU access. Key features: - Shows once on first launch for macOS + Electron users - Persistent badge in topbar after dismissal for easy re-access - Clean design with Comfy Cloud branding - Non-intrusive messaging focused on infrastructure benefits 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 09c888e commit e5ba351

File tree

7 files changed

+206
-1
lines changed

7 files changed

+206
-1
lines changed

src/App.vue

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { computed, onMounted } from 'vue'
1717
import GlobalDialog from '@/components/dialog/GlobalDialog.vue'
1818
import config from '@/config'
1919
import { t } from '@/i18n'
20+
import { useSettingStore } from '@/platform/settings/settingStore'
2021
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
2122
import { app } from '@/scripts/app'
2223
import { useDialogService } from '@/services/dialogService'
@@ -47,7 +48,7 @@ const showContextMenu = (event: MouseEvent) => {
4748
}
4849
}
4950
50-
onMounted(() => {
51+
onMounted(async () => {
5152
window['__COMFYUI_FRONTEND_VERSION__'] = config.app_version
5253
5354
if (isElectron()) {
@@ -77,5 +78,17 @@ onMounted(() => {
7778
// Initialize conflict detection in background
7879
// This runs async and doesn't block UI setup
7980
void conflictDetection.initializeConflictDetection()
81+
82+
// Show cloud notification for macOS desktop users (one-time)
83+
const isMacOS = navigator.platform.toLowerCase().includes('mac')
84+
const settingStore = useSettingStore()
85+
const hasShownNotification = settingStore.get(
86+
'Comfy.Desktop.CloudNotificationShown'
87+
)
88+
89+
if (isElectron() && isMacOS && !hasShownNotification) {
90+
dialogService.showCloudNotification()
91+
await settingStore.set('Comfy.Desktop.CloudNotificationShown', true)
92+
}
8093
})
8194
</script>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<template>
2+
<div class="w-[480px] p-6">
3+
<!-- Header with Logo -->
4+
<div class="mb-6">
5+
<div class="mb-2 flex items-center gap-3">
6+
<img
7+
src="/assets/images/comfy-cloud-logo.svg"
8+
alt="Comfy Cloud"
9+
class="h-8 w-8 shrink-0"
10+
/>
11+
<h1 class="text-2xl font-semibold">
12+
{{ t('cloudNotification.title') }}
13+
</h1>
14+
</div>
15+
<p class="text-base text-muted">
16+
{{ t('cloudNotification.message') }}
17+
</p>
18+
</div>
19+
20+
<!-- Features -->
21+
<div class="mb-6 space-y-4">
22+
<div class="flex gap-3">
23+
<i class="pi pi-check-circle mt-0.5 shrink-0 text-xl text-blue-500"></i>
24+
<div class="flex-1">
25+
<div class="mb-1 font-medium">
26+
{{ t('cloudNotification.feature1Title') }}
27+
</div>
28+
<div class="text-sm text-muted">
29+
{{ t('cloudNotification.feature1') }}
30+
</div>
31+
</div>
32+
</div>
33+
34+
<div class="flex gap-3">
35+
<i class="pi pi-server mt-0.5 shrink-0 text-xl text-blue-500"></i>
36+
<div class="flex-1">
37+
<div class="mb-1 font-medium">
38+
{{ t('cloudNotification.feature2Title') }}
39+
</div>
40+
<div class="text-sm text-muted">
41+
{{ t('cloudNotification.feature2') }}
42+
</div>
43+
</div>
44+
</div>
45+
46+
<div class="flex gap-3">
47+
<i class="pi pi-tag mt-0.5 shrink-0 text-xl text-blue-500"></i>
48+
<div class="flex-1">
49+
<div class="mb-1 font-medium">
50+
{{ t('cloudNotification.feature3Title') }}
51+
</div>
52+
<div class="text-sm text-muted">
53+
{{ t('cloudNotification.feature3') }}
54+
</div>
55+
</div>
56+
</div>
57+
</div>
58+
59+
<!-- Footer Note -->
60+
<div
61+
class="mb-6 rounded border-l-2 border-blue-500 bg-blue-500/5 py-2.5 pl-3 pr-4"
62+
>
63+
<p class="whitespace-pre-line text-sm text-muted">
64+
{{ t('cloudNotification.feature4') }}
65+
</p>
66+
</div>
67+
68+
<!-- Actions -->
69+
<div class="flex gap-3">
70+
<Button
71+
:label="t('cloudNotification.continueLocally')"
72+
severity="secondary"
73+
outlined
74+
class="flex-1"
75+
@click="onDismiss"
76+
/>
77+
<Button
78+
:label="t('cloudNotification.exploreCloud')"
79+
icon="pi pi-arrow-right"
80+
icon-pos="right"
81+
class="flex-1"
82+
@click="onExplore"
83+
/>
84+
</div>
85+
</div>
86+
</template>
87+
88+
<script setup lang="ts">
89+
import Button from 'primevue/button'
90+
import { useI18n } from 'vue-i18n'
91+
92+
import { useDialogStore } from '@/stores/dialogStore'
93+
94+
const { t } = useI18n()
95+
96+
const onDismiss = () => {
97+
useDialogStore().closeDialog()
98+
}
99+
100+
const onExplore = () => {
101+
window.open('https://www.comfy.org/cloud', '_blank')
102+
useDialogStore().closeDialog()
103+
}
104+
</script>

src/components/topbar/TopbarBadges.vue

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
<template>
22
<div class="flex h-full shrink-0 items-center">
3+
<!-- Cloud Notification Badge for Desktop -->
4+
<div
5+
v-if="cloudBadge"
6+
class="relative inline-flex h-full shrink-0 cursor-pointer items-center justify-center gap-2 px-3 transition-opacity hover:opacity-70"
7+
@click="handleCloudBadgeClick"
8+
>
9+
<div
10+
class="rounded-full bg-white px-1.5 py-0.5 text-xxxs font-semibold text-black"
11+
>
12+
{{ t('cloudNotification.badgeLabel') }}
13+
</div>
14+
<div v-if="displayMode !== 'icon-only'" class="text-sm font-inter">
15+
{{ t('cloudNotification.badgeText') }}
16+
</div>
17+
</div>
18+
19+
<!-- Extension Badges -->
320
<TopbarBadge
421
v-for="badge in topbarBadgeStore.badges"
522
:key="badge.text"
@@ -14,8 +31,13 @@
1431
<script lang="ts" setup>
1532
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
1633
import { computed } from 'vue'
34+
import { useI18n } from 'vue-i18n'
1735
36+
import { useSettingStore } from '@/platform/settings/settingStore'
37+
import { useDialogService } from '@/services/dialogService'
1838
import { useTopbarBadgeStore } from '@/stores/topbarBadgeStore'
39+
import type { TopbarBadge as TopbarBadgeType } from '@/types/comfy'
40+
import { isElectron } from '@/utils/envUtil'
1941
2042
import TopbarBadge from './TopbarBadge.vue'
2143
@@ -41,4 +63,35 @@ const displayMode = computed<'full' | 'compact' | 'icon-only'>(() => {
4163
})
4264
4365
const topbarBadgeStore = useTopbarBadgeStore()
66+
67+
// Cloud notification badge
68+
const { t } = useI18n()
69+
const settingStore = useSettingStore()
70+
const dialogService = useDialogService()
71+
72+
const isMacOS = computed(() => navigator.platform.toLowerCase().includes('mac'))
73+
74+
const hasShownNotification = computed(() =>
75+
settingStore.get('Comfy.Desktop.CloudNotificationShown')
76+
)
77+
78+
const shouldShowCloudBadge = computed(
79+
() => isElectron() && isMacOS.value && hasShownNotification.value
80+
)
81+
82+
const cloudBadge = computed<TopbarBadgeType | null>(() => {
83+
if (!shouldShowCloudBadge.value) return null
84+
85+
return {
86+
text: 'Discover Comfy Cloud',
87+
label: 'NEW',
88+
icon: 'pi pi-cloud',
89+
variant: 'info',
90+
tooltip: 'Learn about Comfy Cloud'
91+
}
92+
})
93+
94+
const handleCloudBadgeClick = () => {
95+
dialogService.showCloudNotification()
96+
}
4497
</script>

src/locales/en/main.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2212,5 +2212,21 @@
22122212
"description": "This workflow uses custom nodes you haven't installed yet.",
22132213
"replacementInstruction": "Install these nodes to run this workflow, or replace them with installed alternatives. Missing nodes are highlighted in red on the canvas."
22142214
}
2215+
},
2216+
"cloudNotification": {
2217+
"title": "Discover Comfy Cloud",
2218+
"message": "Get access to industry-grade GPUs and run workflows up to 10x faster",
2219+
"feature1Title": "No Setup Required",
2220+
"feature1": "Start creating instantly with popular models pre-installed",
2221+
"feature2Title": "Powerful GPUs",
2222+
"feature2": "A100 and RTX PRO 6000 GPUs for heavy video models",
2223+
"feature3Title": "$20/month",
2224+
"feature3": "Simple subscription with unlimited workflow runs",
2225+
"feature4": "ComfyUI stays free and open source.\nCloud is optional—for instant access to high-end GPUs.",
2226+
"continueLocally": "Continue Locally",
2227+
"exploreCloud": "Explore Cloud",
2228+
"badgeTooltip": "Learn about Comfy Cloud",
2229+
"badgeLabel": "NEW",
2230+
"badgeText": "Discover Comfy Cloud"
22152231
}
22162232
}

src/platform/settings/constants/coreSettings.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,12 @@ export const CORE_SETTINGS: SettingParams[] = [
293293
type: 'boolean',
294294
defaultValue: true
295295
},
296+
{
297+
id: 'Comfy.Desktop.CloudNotificationShown',
298+
name: 'Cloud notification shown',
299+
type: 'boolean',
300+
defaultValue: false
301+
},
296302
{
297303
id: 'Comfy.Graph.ZoomSpeed',
298304
category: ['LiteGraph', 'Canvas', 'ZoomSpeed'],

src/schemas/apiSchema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ const zSettings = z.object({
374374
'Comfy.Workflow.ShowMissingNodesWarning': z.boolean(),
375375
'Comfy.Workflow.ShowMissingModelsWarning': z.boolean(),
376376
'Comfy.Workflow.WarnBlueprintOverwrite': z.boolean(),
377+
'Comfy.Desktop.CloudNotificationShown': z.boolean(),
377378
'Comfy.DisableFloatRounding': z.boolean(),
378379
'Comfy.DisableSliders': z.boolean(),
379380
'Comfy.DOMClippingEnabled': z.boolean(),

src/services/dialogService.ts

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

44
import ApiNodesSignInContent from '@/components/dialog/content/ApiNodesSignInContent.vue'
5+
import CloudNotificationContent from '@/components/dialog/content/CloudNotificationContent.vue'
56
import MissingNodesContent from '@/components/dialog/content/MissingNodesContent.vue'
67
import MissingNodesFooter from '@/components/dialog/content/MissingNodesFooter.vue'
78
import MissingNodesHeader from '@/components/dialog/content/MissingNodesHeader.vue'
@@ -541,6 +542,16 @@ export const useDialogService = () => {
541542
show()
542543
}
543544

545+
function showCloudNotification() {
546+
dialogStore.showDialog({
547+
key: 'global-cloud-notification',
548+
component: CloudNotificationContent,
549+
dialogComponentProps: {
550+
closable: true
551+
}
552+
})
553+
}
554+
544555
return {
545556
showLoadWorkflowWarning,
546557
showMissingModelsWarning,
@@ -555,6 +566,7 @@ export const useDialogService = () => {
555566
showTopUpCreditsDialog,
556567
showUpdatePasswordDialog,
557568
showExtensionDialog,
569+
showCloudNotification,
558570
prompt,
559571
showErrorDialog,
560572
confirm,

0 commit comments

Comments
 (0)