diff --git a/locales/en.json b/locales/en.json index b256f3279..a328f1a6e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -749,6 +749,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 0aa109a23..f6501c9c0 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -749,6 +749,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 4ba1421f3..8548baeb5 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 /** Core beta grants the activation notice has already announced, as the arg * tokens themselves (`['--enable-assets']`). A list rather than a boolean so * a beta feature granted later still gets its own heads-up; append-only, so @@ -280,6 +284,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' } }, betaNoticeAnnouncedArgs: { nullable: false, telemetry: { policy: 'omit' } }, hideCloudFromPicker: { nullable: false, diff --git a/src/renderer/src/comfyTitleBar/TitleBarApp.vue b/src/renderer/src/comfyTitleBar/TitleBarApp.vue index 091ea3e50..6f42368a9 100644 --- a/src/renderer/src/comfyTitleBar/TitleBarApp.vue +++ b/src/renderer/src/comfyTitleBar/TitleBarApp.vue @@ -320,7 +320,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) @@ -333,7 +333,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 } @@ -343,7 +343,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..8cc27b42d 100644 --- a/src/renderer/src/components/AnnouncementModal.vue +++ b/src/renderer/src/components/AnnouncementModal.vue @@ -5,36 +5,35 @@ 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 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() 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 +98,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 +123,7 @@ onUnmounted(() => { muted loop playsinline - :aria-label="$t('announcement.cloudNodes.imageAlt')" + :aria-label="$t('announcement.comfyRouter.imageAlt')" > @@ -133,8 +132,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 +146,9 @@ onUnmounted(() => {
-

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

+

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

-

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

+

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

  • @@ -157,23 +156,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 }) })