From fec53237b0124527050e31b37de694d219b7baea Mon Sep 17 00:00:00 2001 From: Deep Mehta Date: Mon, 21 Sep 2026 19:53:40 -0700 Subject: [PATCH 1/2] feat(announcement): swap the launch modal to Comfy Router Points the title-bar news bell at the Comfy Router launch, reusing AnnouncementModal by swapping the id, copy, URLs and i18n namespace (the pattern its header comment and the Cloud nodes swap in #1480 established). Copy is Steven's finalized in-app version. A new comfyRouterAnnouncementSeen flag re-lights the bell for everyone who dismissed the Cloud nodes announcement; the old key stays defined. Single CTA (dropped the docs button and beta line). Hero points at media.comfy.org/website/router/, which is not uploaded yet, so it 404s to the gradient fallback until the asset lands. zh strings are machine-written, pending native review. --- locales/en.json | 13 +++++ locales/zh.json | 13 +++++ src/main/settings.ts | 5 ++ .../src/comfyTitleBar/TitleBarApp.vue | 6 +-- .../src/components/AnnouncementModal.vue | 52 ++++++++----------- .../src/composables/useAnnouncement.ts | 4 +- 6 files changed, 58 insertions(+), 35 deletions(-) diff --git a/locales/en.json b/locales/en.json index feda5029d..69b0289e5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -739,6 +739,19 @@ "imageAlt": "Comfy Cloud nodes announcement", "unmute": "Unmute video", "mute": "Mute video" + }, + "comfyRouter": { + "title": "Introducing Comfy Router", + "lead": "One API for the latest frontier media models", + "highlights": [ + "Call the latest image, video, 3D, and audio models from your own scripts, tools, and apps using the Comfy credits you already have.", + "Add new models without setting up a separate account or integration for every provider.", + "When a model has multiple providers, choose based on price or availability and switch without rewriting your integration." + ], + "primaryCta": "Learn About Comfy Router", + "imageAlt": "Comfy Router announcement", + "unmute": "Unmute video", + "mute": "Mute video" } }, "tray": { diff --git a/locales/zh.json b/locales/zh.json index 14c114ca7..cab6be463 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -739,6 +739,19 @@ "imageAlt": "Comfy Cloud \u8282\u70b9\u516c\u544a", "unmute": "\u53d6\u6d88\u9759\u97f3", "mute": "\u9759\u97f3" + }, + "comfyRouter": { + "title": "Comfy Router \u73b0\u5df2\u63a8\u51fa", + "lead": "\u4e00\u4e2a API\uff0c\u8c03\u7528\u6700\u65b0\u7684\u524d\u6cbf\u5a92\u4f53\u6a21\u578b", + "highlights": [ + "\u7528\u4f60\u5df2\u6709\u7684 Comfy \u79ef\u5206\uff0c\u4ece\u81ea\u5df1\u7684\u811a\u672c\u3001\u5de5\u5177\u548c\u5e94\u7528\u4e2d\u8c03\u7528\u6700\u65b0\u7684\u56fe\u50cf\u3001\u89c6\u9891\u30013D \u548c\u97f3\u9891\u6a21\u578b\u3002", + "\u65e0\u9700\u4e3a\u6bcf\u4e2a\u63d0\u4f9b\u5546\u5355\u72ec\u6ce8\u518c\u8d26\u53f7\u6216\u505a\u96c6\u6210\uff0c\u5373\u53ef\u6dfb\u52a0\u65b0\u6a21\u578b\u3002", + "\u5f53\u4e00\u4e2a\u6a21\u578b\u6709\u591a\u4e2a\u63d0\u4f9b\u5546\u65f6\uff0c\u53ef\u6309\u4ef7\u683c\u6216\u53ef\u7528\u6027\u9009\u62e9\uff0c\u5e76\u5728\u4e0d\u91cd\u5199\u96c6\u6210\u7684\u60c5\u51b5\u4e0b\u5207\u6362\u3002" + ], + "primaryCta": "\u4e86\u89e3 Comfy Router", + "imageAlt": "Comfy Router \u516c\u544a", + "unmute": "\u53d6\u6d88\u9759\u97f3", + "mute": "\u9759\u97f3" } }, "tray": { diff --git a/src/main/settings.ts b/src/main/settings.ts index 5e5e14258..91d55a992 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -66,6 +66,10 @@ export interface KnownSettings { * rather than a reset of minimaxAnnouncementSeen: everyone who dismissed the * previous announcement must still get the bell for this one. */ cloudNodesAnnouncementSeen?: boolean + /** Seen-flag for the Comfy Router announcement. New key again, same reasoning + * as cloudNodesAnnouncementSeen: everyone who dismissed the previous + * announcement must still get the bell for this one. */ + comfyRouterAnnouncementSeen?: boolean /** When true, hide the Cloud tile (and the Try-Cloud CTA) from the * Dashboard / Instance Picker. Local-only users who never use Cloud * can opt out of seeing it without us removing the feature. Default @@ -274,6 +278,7 @@ const SETTINGS_SCHEMA = { firstUseCompleted: { nullable: false, telemetry: { policy: 'omit' } }, minimaxAnnouncementSeen: { nullable: false, telemetry: { policy: 'omit' } }, cloudNodesAnnouncementSeen: { nullable: false, telemetry: { policy: 'omit' } }, + comfyRouterAnnouncementSeen: { nullable: false, telemetry: { policy: 'omit' } }, hideCloudFromPicker: { nullable: false, telemetry: { policy: 'value', toTelemetry: (raw) => raw === true } diff --git a/src/renderer/src/comfyTitleBar/TitleBarApp.vue b/src/renderer/src/comfyTitleBar/TitleBarApp.vue index 4b2d7a54c..1fdd4303a 100644 --- a/src/renderer/src/comfyTitleBar/TitleBarApp.vue +++ b/src/renderer/src/comfyTitleBar/TitleBarApp.vue @@ -295,7 +295,7 @@ function handleResetZoom(): void { } // News bell with a subtle unread dot until the announcement is opened. Its state -// lives in the `cloudNodesAnnouncementSeen` setting (shared with the panel view); +// lives in the `comfyRouterAnnouncementSeen` setting (shared with the panel view); // opening the modal writes it, and the settings-changed broadcast clears the // dot here without a dedicated channel. const announcementUnread = ref(false) @@ -308,7 +308,7 @@ function handleAnnouncement(): void { async function refreshAnnouncementUnread(): Promise { try { announcementUnread.value = - (await window.api?.getSetting?.('cloudNodesAnnouncementSeen')) !== true + (await window.api?.getSetting?.('comfyRouterAnnouncementSeen')) !== true } catch { announcementUnread.value = false } @@ -318,7 +318,7 @@ onMounted(() => { void refreshAnnouncementUnread() unsubAnnouncementSettings = window.api?.onSettingsChanged?.(({ key }) => { - if (key === 'cloudNodesAnnouncementSeen') void refreshAnnouncementUnread() + if (key === 'comfyRouterAnnouncementSeen') void refreshAnnouncementUnread() }) ?? null }) diff --git a/src/renderer/src/components/AnnouncementModal.vue b/src/renderer/src/components/AnnouncementModal.vue index 79fa57ed5..daf35ba1b 100644 --- a/src/renderer/src/components/AnnouncementModal.vue +++ b/src/renderer/src/components/AnnouncementModal.vue @@ -5,36 +5,37 @@ import { Check, Volume2, VolumeX, X } from 'lucide-vue-next' import { emitTelemetryAction } from '../lib/telemetry' /** - * One-off announcement modal (currently the Comfy Cloud nodes launch). + * One-off announcement modal (currently the Comfy Router launch). * Deliberately mirrors `WhyTryCloudModal.vue`'s layout and styling so it feels * native. Swapping to the next announcement means changing ANNOUNCEMENT_ID, the * URLs, the hero media and the i18n namespace below, plus a new seen-flag in * settings so previously-dismissed users still get the bell. * - * Strings live in `announcement.cloudNodes.*` (locales/en.json). + * Strings live in `announcement.comfyRouter.*` (locales/en.json). */ // Announcement id, carried in telemetry so this component can be reused for a // future announcement by swapping the id + copy. -const ANNOUNCEMENT_ID = 'comfy_cloud_nodes' +const ANNOUNCEMENT_ID = 'comfy_router' -// CTA destinations. The UTM tags clicks as Desktop-origin so they're +// CTA destination. The UTM tags clicks as Desktop-origin so they're // attributable in analytics. -const UTM = '?utm_source=comfy_desktop&utm_medium=announcement&utm_campaign=comfy_cloud_nodes' -const PRIMARY_CTA_URL = `https://comfy.org/cloud-nodes${UTM}` -const DOCS_CTA_URL = `https://docs.comfy.org/cloud-nodes/overview${UTM}` +const UTM = '?utm_source=comfy_desktop&utm_medium=announcement&utm_campaign=comfy_router' +const PRIMARY_CTA_URL = `https://comfy.org/platform/router${UTM}` -// Same 15s launch film the /cloud-nodes landing page leads with. Poster paints -// immediately; the video muted-autoplays + loops. media.comfy.org objects are -// cached for an hour, so bump the `_v1` suffix rather than re-uploading a key. -const HERO_VIDEO_URL = 'https://media.comfy.org/website/cloud-nodes/hero_v1.mp4' -const HERO_POSTER_URL = 'https://media.comfy.org/website/cloud-nodes/hero-poster_v1.webp' +// Router hero. These objects are not uploaded yet: until the launch clip lands +// at media.comfy.org/website/router/, both 404 and the modal shows the +// .announce-media gradient fallback. Do not ship before the asset is live. +// media.comfy.org caches for an hour, so bump the `_v1` suffix rather than +// re-uploading a key. Poster paints immediately; the video muted-autoplays + loops. +const HERO_VIDEO_URL = 'https://media.comfy.org/website/router/hero_v1.mp4' +const HERO_POSTER_URL = 'https://media.comfy.org/website/router/hero-poster_v1.webp' const emit = defineEmits<{ close: [] }>() const { tm } = useI18n() const highlights = computed(() => { - const raw = tm('announcement.cloudNodes.highlights') + const raw = tm('announcement.comfyRouter.highlights') return Array.isArray(raw) ? (raw as unknown as string[]) : [] }) @@ -99,7 +100,7 @@ onUnmounted(() => { class="announce-overlay" role="dialog" aria-modal="true" - :aria-label="$t('announcement.cloudNodes.title')" + :aria-label="$t('announcement.comfyRouter.title')" tabindex="-1" @mousedown="onOverlayMouseDown" @click="onOverlayClick" @@ -124,7 +125,7 @@ onUnmounted(() => { muted loop playsinline - :aria-label="$t('announcement.cloudNodes.imageAlt')" + :aria-label="$t('announcement.comfyRouter.imageAlt')" > @@ -133,8 +134,8 @@ onUnmounted(() => { type="button" :aria-label=" isMuted - ? $t('announcement.cloudNodes.unmute') - : $t('announcement.cloudNodes.mute') + ? $t('announcement.comfyRouter.unmute') + : $t('announcement.comfyRouter.mute') " :aria-pressed="!isMuted" data-testid="announcement-sound-toggle" @@ -147,9 +148,9 @@ onUnmounted(() => {
-

{{ $t('announcement.cloudNodes.title') }}

+

{{ $t('announcement.comfyRouter.title') }}

-

{{ $t('announcement.cloudNodes.lead') }}

+

{{ $t('announcement.comfyRouter.lead') }}

  • @@ -157,23 +158,14 @@ onUnmounted(() => {
-

{{ $t('announcement.cloudNodes.beta') }}

-
diff --git a/src/renderer/src/composables/useAnnouncement.ts b/src/renderer/src/composables/useAnnouncement.ts index bb221581c..ca8de5892 100644 --- a/src/renderer/src/composables/useAnnouncement.ts +++ b/src/renderer/src/composables/useAnnouncement.ts @@ -10,7 +10,7 @@ interface UseAnnouncementApi { /** * Title-bar news bell → main forwards `onOpenAnnouncement` here, mirroring the * Send Feedback flow (`useSendFeedback`). Opening marks the announcement seen - * (persisted via `cloudNodesAnnouncementSeen`); the settings-changed broadcast + * (persisted via `comfyRouterAnnouncementSeen`); the settings-changed broadcast * then clears the title-bar's unread dot. The modal owns its own telemetry. */ export function useAnnouncement(): UseAnnouncementApi { @@ -26,7 +26,7 @@ export function useAnnouncement(): UseAnnouncementApi { onMounted(() => { unsubOpen = window.api.onOpenAnnouncement(() => { - void window.api.setSetting('cloudNodesAnnouncementSeen', true) + void window.api.setSetting('comfyRouterAnnouncementSeen', true) announcementOpen.value = true }) }) From 0cd0ce401a65fa51b9893f1cb01c4262054d01d8 Mon Sep 17 00:00:00 2001 From: Deep Mehta Date: Tue, 22 Sep 2026 20:26:40 -0700 Subject: [PATCH 2/2] feat(announcement): point the Router hero at the launch animatic Swap the placeholder hero URLs for the uploaded launch clip and its poster on media.comfy.org (router-animatic-v019). --- src/renderer/src/components/AnnouncementModal.vue | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/components/AnnouncementModal.vue b/src/renderer/src/components/AnnouncementModal.vue index daf35ba1b..8cc27b42d 100644 --- a/src/renderer/src/components/AnnouncementModal.vue +++ b/src/renderer/src/components/AnnouncementModal.vue @@ -23,13 +23,11 @@ const ANNOUNCEMENT_ID = 'comfy_router' const UTM = '?utm_source=comfy_desktop&utm_medium=announcement&utm_campaign=comfy_router' const PRIMARY_CTA_URL = `https://comfy.org/platform/router${UTM}` -// Router hero. These objects are not uploaded yet: until the launch clip lands -// at media.comfy.org/website/router/, both 404 and the modal shows the -// .announce-media gradient fallback. Do not ship before the asset is live. -// media.comfy.org caches for an hour, so bump the `_v1` suffix rather than -// re-uploading a key. Poster paints immediately; the video muted-autoplays + loops. -const HERO_VIDEO_URL = 'https://media.comfy.org/website/router/hero_v1.mp4' -const HERO_POSTER_URL = 'https://media.comfy.org/website/router/hero-poster_v1.webp' +// Router launch hero on media.comfy.org. media.comfy.org caches for an hour, so +// bump the version in the filename rather than re-uploading a key. Poster paints +// immediately; the video muted-autoplays + loops. +const HERO_VIDEO_URL = 'https://media.comfy.org/website/router/router-animatic-v019.mp4' +const HERO_POSTER_URL = 'https://media.comfy.org/website/router/router-animatic-v019-poster.webp' const emit = defineEmits<{ close: [] }>() const { tm } = useI18n()