Compare commits

...

4 Commits

Author SHA1 Message Date
Deep Mehta
f5c26130f2 Remove cloud notification badge from topbar
- Remove persistent 'NEW' badge that appeared after modal dismissal
- Keep one-time modal notification for macOS desktop users
- Clean up unused badge translations
2025-11-25 02:12:08 +05:30
Deep Mehta
7bba8f09f6 Fix: Cloud notification badge reactivity and modal timing
- Fix badge not appearing by accessing settingStore.settingValues directly for proper reactivity
- Increase modal delay to 2s to ensure it appears after missing models dialog
- Move setting update inside setTimeout to only save when modal actually shows

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-24 04:17:25 +05:30
Deep Mehta
de39ece33f Add analytics tracking for cloud notification feature
Adds Mixpanel event tracking and UTM parameters to measure:
- Modal impression rate (cloud_notification_modal_shown)
- Click-through rate (cloud_notification_explore_cloud_clicked)
- Dismiss rate (cloud_notification_continue_locally_clicked)
- Badge engagement (cloud_notification_badge_clicked)

UTM parameters enable full funnel tracking:
- Desktop users clicking Explore Cloud
- Cloud website signups with utm_source=desktop
- Subscription conversions attributed to desktop

This enables measurement of the desktop→cloud conversion funnel to validate the 5.6% signup-to-paid conversion rate goal.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-24 02:00:00 +05:30
Deep Mehta
e5ba351dcc [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>
2025-11-23 20:53:58 +05:30
6 changed files with 180 additions and 1 deletions

View File

@@ -17,6 +17,7 @@ import { computed, onMounted } from 'vue'
import GlobalDialog from '@/components/dialog/GlobalDialog.vue'
import config from '@/config'
import { t } from '@/i18n'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { app } from '@/scripts/app'
import { useDialogService } from '@/services/dialogService'
@@ -47,7 +48,7 @@ const showContextMenu = (event: MouseEvent) => {
}
}
onMounted(() => {
onMounted(async () => {
window['__COMFYUI_FRONTEND_VERSION__'] = config.app_version
if (isElectron()) {
@@ -77,5 +78,25 @@ onMounted(() => {
// Initialize conflict detection in background
// This runs async and doesn't block UI setup
void conflictDetection.initializeConflictDetection()
// Show cloud notification for macOS desktop users (one-time)
// Delayed to ensure it appears after workflow loading (missing models dialog, etc.)
if (isElectron()) {
const isMacOS = navigator.platform.toLowerCase().includes('mac')
if (isMacOS) {
const settingStore = useSettingStore()
const hasShownNotification = settingStore.get(
'Comfy.Desktop.CloudNotificationShown'
)
if (!hasShownNotification) {
// Delay to show after initial workflow loading completes
setTimeout(async () => {
dialogService.showCloudNotification()
await settingStore.set('Comfy.Desktop.CloudNotificationShown', true)
}, 2000)
}
}
}
})
</script>

View File

@@ -0,0 +1,126 @@
<template>
<div class="w-[480px] p-6">
<!-- Header with Logo -->
<div class="mb-6">
<div class="mb-2 flex items-center gap-3">
<img
src="/assets/images/comfy-cloud-logo.svg"
alt="Comfy Cloud"
class="h-8 w-8 shrink-0"
/>
<h1 class="text-2xl font-semibold">
{{ t('cloudNotification.title') }}
</h1>
</div>
<p class="text-base text-muted">
{{ t('cloudNotification.message') }}
</p>
</div>
<!-- Features -->
<div class="mb-6 space-y-4">
<div class="flex gap-3">
<i class="pi pi-check-circle mt-0.5 shrink-0 text-xl text-blue-500"></i>
<div class="flex-1">
<div class="mb-1 font-medium">
{{ t('cloudNotification.feature1Title') }}
</div>
<div class="text-sm text-muted">
{{ t('cloudNotification.feature1') }}
</div>
</div>
</div>
<div class="flex gap-3">
<i class="pi pi-server mt-0.5 shrink-0 text-xl text-blue-500"></i>
<div class="flex-1">
<div class="mb-1 font-medium">
{{ t('cloudNotification.feature2Title') }}
</div>
<div class="text-sm text-muted">
{{ t('cloudNotification.feature2') }}
</div>
</div>
</div>
<div class="flex gap-3">
<i class="pi pi-tag mt-0.5 shrink-0 text-xl text-blue-500"></i>
<div class="flex-1">
<div class="mb-1 font-medium">
{{ t('cloudNotification.feature3Title') }}
</div>
<div class="text-sm text-muted">
{{ t('cloudNotification.feature3') }}
</div>
</div>
</div>
</div>
<!-- Footer Note -->
<div
class="mb-6 rounded border-l-2 border-blue-500 bg-blue-500/5 py-2.5 pl-3 pr-4"
>
<p class="whitespace-pre-line text-sm text-muted">
{{ t('cloudNotification.feature4') }}
</p>
</div>
<!-- Actions -->
<div class="flex gap-3">
<Button
:label="t('cloudNotification.continueLocally')"
severity="secondary"
outlined
class="flex-1"
@click="onDismiss"
/>
<Button
:label="t('cloudNotification.exploreCloud')"
icon="pi pi-arrow-right"
icon-pos="right"
class="flex-1"
@click="onExplore"
/>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useTelemetry } from '@/platform/telemetry'
import { useDialogStore } from '@/stores/dialogStore'
const { t } = useI18n()
// Track when modal is shown
onMounted(() => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'cloud_notification_modal_shown'
})
})
const onDismiss = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'cloud_notification_continue_locally_clicked'
})
useDialogStore().closeDialog()
}
const onExplore = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'cloud_notification_explore_cloud_clicked'
})
// Add UTM parameters for attribution tracking
const url = new URL('https://www.comfy.org/cloud')
url.searchParams.set('utm_source', 'desktop')
url.searchParams.set('utm_medium', 'notification')
url.searchParams.set('utm_campaign', 'macos_first_launch')
window.open(url.toString(), '_blank')
useDialogStore().closeDialog()
}
</script>

View File

@@ -2212,5 +2212,18 @@
"description": "This workflow uses custom nodes you haven't installed yet.",
"replacementInstruction": "Install these nodes to run this workflow, or replace them with installed alternatives. Missing nodes are highlighted in red on the canvas."
}
},
"cloudNotification": {
"title": "Discover Comfy Cloud",
"message": "Get access to industry-grade GPUs and run workflows up to 10x faster",
"feature1Title": "No Setup Required",
"feature1": "Start creating instantly with popular models pre-installed",
"feature2Title": "Powerful GPUs",
"feature2": "A100 and RTX PRO 6000 GPUs for heavy video models",
"feature3Title": "$20/month",
"feature3": "Simple subscription with unlimited workflow runs",
"feature4": "ComfyUI stays free and open source.\nCloud is optional—for instant access to high-end GPUs.",
"continueLocally": "Continue Locally",
"exploreCloud": "Explore Cloud"
}
}

View File

@@ -293,6 +293,12 @@ export const CORE_SETTINGS: SettingParams[] = [
type: 'boolean',
defaultValue: true
},
{
id: 'Comfy.Desktop.CloudNotificationShown',
name: 'Cloud notification shown',
type: 'boolean',
defaultValue: false
},
{
id: 'Comfy.Graph.ZoomSpeed',
category: ['LiteGraph', 'Canvas', 'ZoomSpeed'],

View File

@@ -374,6 +374,7 @@ const zSettings = z.object({
'Comfy.Workflow.ShowMissingNodesWarning': z.boolean(),
'Comfy.Workflow.ShowMissingModelsWarning': z.boolean(),
'Comfy.Workflow.WarnBlueprintOverwrite': z.boolean(),
'Comfy.Desktop.CloudNotificationShown': z.boolean(),
'Comfy.DisableFloatRounding': z.boolean(),
'Comfy.DisableSliders': z.boolean(),
'Comfy.DOMClippingEnabled': z.boolean(),

View File

@@ -2,6 +2,7 @@ import { merge } from 'es-toolkit/compat'
import type { Component } from 'vue'
import ApiNodesSignInContent from '@/components/dialog/content/ApiNodesSignInContent.vue'
import CloudNotificationContent from '@/components/dialog/content/CloudNotificationContent.vue'
import MissingNodesContent from '@/components/dialog/content/MissingNodesContent.vue'
import MissingNodesFooter from '@/components/dialog/content/MissingNodesFooter.vue'
import MissingNodesHeader from '@/components/dialog/content/MissingNodesHeader.vue'
@@ -541,6 +542,16 @@ export const useDialogService = () => {
show()
}
function showCloudNotification() {
dialogStore.showDialog({
key: 'global-cloud-notification',
component: CloudNotificationContent,
dialogComponentProps: {
closable: true
}
})
}
return {
showLoadWorkflowWarning,
showMissingModelsWarning,
@@ -555,6 +566,7 @@ export const useDialogService = () => {
showTopUpCreditsDialog,
showUpdatePasswordDialog,
showExtensionDialog,
showCloudNotification,
prompt,
showErrorDialog,
confirm,