From d2f5f0dce1f1eaf82f51fccab86305f90baeb5e2 Mon Sep 17 00:00:00 2001 From: Comfy Org PR Bot Date: Tue, 9 Dec 2025 19:41:29 +0900 Subject: [PATCH] [backport cloud/1.34] feat: Enable system notifications on cloud (#7287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport of #7277 to `cloud/1.34` Automatically created by backport workflow. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7287-backport-cloud-1-34-feat-Enable-system-notifications-on-cloud-2c46d73d365081aaaf90d4ff96d0ca52) by [Unito](https://www.unito.io) Co-authored-by: Christian Byrne --- src/platform/updates/common/releaseStore.ts | 52 ++++++++++++------- src/stores/systemStatsStore.ts | 5 ++ tests-ui/tests/store/releaseStore.test.ts | 44 ++++++++-------- tests-ui/tests/store/systemStatsStore.test.ts | 2 + 4 files changed, 63 insertions(+), 40 deletions(-) diff --git a/src/platform/updates/common/releaseStore.ts b/src/platform/updates/common/releaseStore.ts index 3e2849259..80c2bc05d 100644 --- a/src/platform/updates/common/releaseStore.ts +++ b/src/platform/updates/common/releaseStore.ts @@ -1,6 +1,6 @@ import { until } from '@vueuse/core' import { defineStore } from 'pinia' -import { compare } from 'semver' +import { compare, valid } from 'semver' import { computed, ref } from 'vue' import { isCloud } from '@/platform/distribution/types' @@ -24,10 +24,12 @@ export const useReleaseStore = defineStore('release', () => { const systemStatsStore = useSystemStatsStore() const settingStore = useSettingStore() - // Current ComfyUI version - const currentComfyUIVersion = computed( - () => systemStatsStore?.systemStats?.system?.comfyui_version ?? '' - ) + const currentVersion = computed(() => { + if (isCloud) { + return systemStatsStore?.systemStats?.system?.cloud_version ?? '' + } + return systemStatsStore?.systemStats?.system?.comfyui_version ?? '' + }) // Release data from settings const locale = computed(() => settingStore.get('Comfy.Locale')) @@ -55,22 +57,33 @@ export const useReleaseStore = defineStore('release', () => { // Helper constants const THREE_DAYS_MS = 3 * 24 * 60 * 60 * 1000 // 3 days + const compareVersions = ( + releaseVersion: string, + currentVer: string + ): number => { + if (valid(releaseVersion) && valid(currentVer)) { + return compare(releaseVersion, currentVer) + } + // Non-semver (e.g. git hash): assume different = newer + return releaseVersion === currentVer ? 0 : 1 + } + // New version available? const isNewVersionAvailable = computed( () => !!recentRelease.value && - compare( + compareVersions( recentRelease.value.version, - currentComfyUIVersion.value || '0.0.0' + currentVersion.value || '0.0.0' ) > 0 ) const isLatestVersion = computed( () => !!recentRelease.value && - compare( + compareVersions( recentRelease.value.version, - currentComfyUIVersion.value || '0.0.0' + currentVersion.value || '0.0.0' ) === 0 ) @@ -158,23 +171,25 @@ export const useReleaseStore = defineStore('release', () => { return true }) - // Show "What's New" popup const shouldShowPopup = computed(() => { - // Only show on desktop version - if (!isElectron() || isCloud) { + if (!isElectron() && !isCloud) { return false } - // Skip if notifications are disabled if (!showVersionUpdates.value) { return false } - if (!isLatestVersion.value) { + if (!recentRelease.value) { + return false + } + + // Skip version check if current version isn't semver (e.g. git hash) + const skipVersionCheck = !valid(currentVersion.value) + if (!skipVersionCheck && !isLatestVersion.value) { return false } - // Hide if already seen if ( releaseVersion.value === recentRelease.value.version && releaseStatus.value === "what's new seen" @@ -225,8 +240,7 @@ export const useReleaseStore = defineStore('release', () => { return } - // Skip fetching if notifications are disabled - if (!showVersionUpdates.value) { + if (!isCloud && !showVersionUpdates.value) { return } @@ -248,8 +262,8 @@ export const useReleaseStore = defineStore('release', () => { } const fetchedReleases = await releaseService.getReleases({ - project: 'comfyui', - current_version: currentComfyUIVersion.value, + project: isCloud ? 'cloud' : 'comfyui', + current_version: currentVersion.value, form_factor: systemStatsStore.getFormFactor(), locale: stringToLocale(locale.value) }) diff --git a/src/stores/systemStatsStore.ts b/src/stores/systemStatsStore.ts index 06362930b..38b57b66d 100644 --- a/src/stores/systemStatsStore.ts +++ b/src/stores/systemStatsStore.ts @@ -1,6 +1,7 @@ import { useAsyncState } from '@vueuse/core' import { defineStore } from 'pinia' +import { isCloud } from '@/platform/distribution/types' import type { SystemStats } from '@/schemas/apiSchema' import { api } from '@/scripts/api' import { isElectron } from '@/utils/envUtil' @@ -30,6 +31,10 @@ export const useSystemStatsStore = defineStore('systemStats', () => { ) function getFormFactor(): string { + if (isCloud) { + return 'cloud' + } + if (!systemStats.value?.system?.os) { return 'other' } diff --git a/tests-ui/tests/store/releaseStore.test.ts b/tests-ui/tests/store/releaseStore.test.ts index 4020eac63..dbd0f8998 100644 --- a/tests-ui/tests/store/releaseStore.test.ts +++ b/tests-ui/tests/store/releaseStore.test.ts @@ -1,5 +1,5 @@ import { createPinia, setActivePinia } from 'pinia' -import { compare as semverCompare } from 'semver' +import { compare, valid } from 'semver' import { beforeEach, describe, expect, it, vi } from 'vitest' import { useReleaseStore } from '@/platform/updates/common/releaseStore' @@ -7,6 +7,7 @@ import { useReleaseStore } from '@/platform/updates/common/releaseStore' // Mock the dependencies vi.mock('semver') vi.mock('@/utils/envUtil') +vi.mock('@/platform/distribution/types', () => ({ isCloud: false })) vi.mock('@/platform/updates/common/releaseService') vi.mock('@/platform/settings/settingStore') vi.mock('@/stores/systemStatsStore') @@ -72,6 +73,7 @@ describe('useReleaseStore', () => { vi.mocked(useSettingStore).mockReturnValue(mockSettingStore) vi.mocked(useSystemStatsStore).mockReturnValue(mockSystemStatsStore) vi.mocked(isElectron).mockReturnValue(true) + vi.mocked(valid).mockReturnValue('1.0.0') // Default showVersionUpdates to true mockSettingStore.get.mockImplementation((key: string) => { @@ -116,14 +118,14 @@ describe('useReleaseStore', () => { }) it('should show update button (shouldShowUpdateButton)', () => { - vi.mocked(semverCompare).mockReturnValue(1) // newer version available + vi.mocked(compare).mockReturnValue(1) // newer version available store.releases = [mockRelease] expect(store.shouldShowUpdateButton).toBe(true) }) it('should not show update button when no new version', () => { - vi.mocked(semverCompare).mockReturnValue(-1) // current version is newer + vi.mocked(compare).mockReturnValue(-1) // current version is newer store.releases = [mockRelease] expect(store.shouldShowUpdateButton).toBe(false) @@ -144,14 +146,14 @@ describe('useReleaseStore', () => { }) it('should show toast for medium/high attention releases', () => { - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) store.releases = [mockRelease] expect(store.shouldShowToast).toBe(true) }) it('should not show toast for low attention releases', () => { - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) const lowAttentionRelease = { ...mockRelease, @@ -164,7 +166,7 @@ describe('useReleaseStore', () => { }) it('should show red dot for new versions', () => { - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) expect(store.shouldShowRedDot).toBe(true) }) @@ -172,7 +174,7 @@ describe('useReleaseStore', () => { it('should show popup for latest version', () => { mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0' - vi.mocked(semverCompare).mockReturnValue(0) + vi.mocked(compare).mockReturnValue(0) expect(store.shouldShowPopup).toBe(true) }) @@ -200,13 +202,13 @@ describe('useReleaseStore', () => { }) it('should not show toast even with new version available', () => { - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) expect(store.shouldShowToast).toBe(false) }) it('should not show red dot even with new version available', () => { - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) expect(store.shouldShowRedDot).toBe(false) }) @@ -214,7 +216,7 @@ describe('useReleaseStore', () => { it('should not show popup even for latest version', () => { mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0' - vi.mocked(semverCompare).mockReturnValue(0) + vi.mocked(compare).mockReturnValue(0) expect(store.shouldShowPopup).toBe(false) }) @@ -494,7 +496,7 @@ describe('useReleaseStore', () => { return null }) - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) store.releases = [mockRelease] @@ -502,7 +504,7 @@ describe('useReleaseStore', () => { }) it('should show red dot for new versions', () => { - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) mockSettingStore.get.mockImplementation((key: string) => { if (key === 'Comfy.Notification.ShowVersionUpdates') return true return null @@ -520,7 +522,7 @@ describe('useReleaseStore', () => { return null }) - vi.mocked(semverCompare).mockReturnValue(0) // versions are equal (latest version) + vi.mocked(compare).mockReturnValue(0) // versions are equal (latest version) store.releases = [mockRelease] @@ -578,14 +580,14 @@ describe('useReleaseStore', () => { }) it('should show toast when conditions are met', () => { - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) store.releases = [mockRelease] expect(store.shouldShowToast).toBe(true) }) it('should show red dot when new version available', () => { - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) expect(store.shouldShowRedDot).toBe(true) }) @@ -593,7 +595,7 @@ describe('useReleaseStore', () => { it('should show popup for latest version', () => { mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0' - vi.mocked(semverCompare).mockReturnValue(0) + vi.mocked(compare).mockReturnValue(0) expect(store.shouldShowPopup).toBe(true) }) @@ -606,7 +608,7 @@ describe('useReleaseStore', () => { }) it('should NOT show toast even when all other conditions are met', () => { - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) // Set up all conditions that would normally show toast store.releases = [mockRelease] @@ -615,13 +617,13 @@ describe('useReleaseStore', () => { }) it('should NOT show red dot even when new version available', () => { - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) expect(store.shouldShowRedDot).toBe(false) }) it('should NOT show toast regardless of attention level', () => { - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) // Test with high attention releases const highRelease = { @@ -640,7 +642,7 @@ describe('useReleaseStore', () => { }) it('should NOT show red dot even with high attention release', () => { - vi.mocked(semverCompare).mockReturnValue(1) + vi.mocked(compare).mockReturnValue(1) store.releases = [{ ...mockRelease, attention: 'high' as const }] @@ -650,7 +652,7 @@ describe('useReleaseStore', () => { it('should NOT show popup even for latest version', () => { mockSystemStatsStore.systemStats.system.comfyui_version = '1.2.0' - vi.mocked(semverCompare).mockReturnValue(0) + vi.mocked(compare).mockReturnValue(0) expect(store.shouldShowPopup).toBe(false) }) diff --git a/tests-ui/tests/store/systemStatsStore.test.ts b/tests-ui/tests/store/systemStatsStore.test.ts index 67d76ee5a..49653c780 100644 --- a/tests-ui/tests/store/systemStatsStore.test.ts +++ b/tests-ui/tests/store/systemStatsStore.test.ts @@ -17,6 +17,8 @@ vi.mock('@/utils/envUtil', () => ({ isElectron: vi.fn() })) +vi.mock('@/platform/distribution/types', () => ({ isCloud: false })) + describe('useSystemStatsStore', () => { let store: ReturnType