mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-03 22:59:14 +00:00
## Summary - Replace custom `compareVersions()` with `semver.compare()` - Replace custom `isSemVer()` with `semver.valid()` - Remove deprecated version comparison functions from `formatUtil.ts` - Update all version comparison logic across components and stores - Fix tests to use semver mocking instead of formatUtil mocking ## Benefits - **Industry standard**: Uses well-maintained, battle-tested `semver` package - **Better reliability**: Handles edge cases more robustly than custom implementation - **Consistent behavior**: All version comparisons now use the same underlying logic - **Type safety**: Better TypeScript support with proper semver types Fixes #4787 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5653-refactor-Replace-manual-semantic-version-utilities-functions-with-semver-package-2736d73d365081fb8498ee11cbcc10e2) by [Unito](https://www.unito.io) --------- Co-authored-by: DrJKL <DrJKL@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
294 lines
7.3 KiB
TypeScript
294 lines
7.3 KiB
TypeScript
import { until } from '@vueuse/core'
|
|
import { defineStore } from 'pinia'
|
|
import { compare } from 'semver'
|
|
import { computed, ref } from 'vue'
|
|
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
|
import { isElectron } from '@/utils/envUtil'
|
|
import { stringToLocale } from '@/utils/formatUtil'
|
|
|
|
import { type ReleaseNote, useReleaseService } from './releaseService'
|
|
|
|
// Store for managing release notes
|
|
export const useReleaseStore = defineStore('release', () => {
|
|
// State
|
|
const releases = ref<ReleaseNote[]>([])
|
|
const isLoading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
|
|
// Services
|
|
const releaseService = useReleaseService()
|
|
const systemStatsStore = useSystemStatsStore()
|
|
const settingStore = useSettingStore()
|
|
|
|
// Current ComfyUI version
|
|
const currentComfyUIVersion = computed(
|
|
() => systemStatsStore?.systemStats?.system?.comfyui_version ?? ''
|
|
)
|
|
|
|
// Release data from settings
|
|
const locale = computed(() => settingStore.get('Comfy.Locale'))
|
|
const releaseVersion = computed(() =>
|
|
settingStore.get('Comfy.Release.Version')
|
|
)
|
|
const releaseStatus = computed(() => settingStore.get('Comfy.Release.Status'))
|
|
const releaseTimestamp = computed(() =>
|
|
settingStore.get('Comfy.Release.Timestamp')
|
|
)
|
|
const showVersionUpdates = computed(() =>
|
|
settingStore.get('Comfy.Notification.ShowVersionUpdates')
|
|
)
|
|
|
|
// Most recent release
|
|
const recentRelease = computed(() => {
|
|
return releases.value[0] ?? null
|
|
})
|
|
|
|
// 3 most recent releases
|
|
const recentReleases = computed(() => {
|
|
return releases.value.slice(0, 3)
|
|
})
|
|
|
|
// Helper constants
|
|
const THREE_DAYS_MS = 3 * 24 * 60 * 60 * 1000 // 3 days
|
|
|
|
// New version available?
|
|
const isNewVersionAvailable = computed(
|
|
() =>
|
|
!!recentRelease.value &&
|
|
compare(
|
|
recentRelease.value.version,
|
|
currentComfyUIVersion.value || '0.0.0'
|
|
) > 0
|
|
)
|
|
|
|
const isLatestVersion = computed(
|
|
() =>
|
|
!!recentRelease.value &&
|
|
compare(
|
|
recentRelease.value.version,
|
|
currentComfyUIVersion.value || '0.0.0'
|
|
) === 0
|
|
)
|
|
|
|
const hasMediumOrHighAttention = computed(() =>
|
|
recentReleases.value
|
|
.slice(0, -1)
|
|
.some(
|
|
(release) =>
|
|
release.attention === 'medium' || release.attention === 'high'
|
|
)
|
|
)
|
|
|
|
// Show toast if needed
|
|
const shouldShowToast = computed(() => {
|
|
// Only show on desktop version
|
|
if (!isElectron()) {
|
|
return false
|
|
}
|
|
|
|
// Skip if notifications are disabled
|
|
if (!showVersionUpdates.value) {
|
|
return false
|
|
}
|
|
|
|
if (!isNewVersionAvailable.value) {
|
|
return false
|
|
}
|
|
|
|
// Skip if low attention
|
|
if (!hasMediumOrHighAttention.value) {
|
|
return false
|
|
}
|
|
|
|
// Skip if user already skipped or changelog seen
|
|
if (
|
|
releaseVersion.value === recentRelease.value?.version &&
|
|
['skipped', 'changelog seen'].includes(releaseStatus.value)
|
|
) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
// Show red-dot indicator
|
|
const shouldShowRedDot = computed(() => {
|
|
// Only show on desktop version
|
|
if (!isElectron()) {
|
|
return false
|
|
}
|
|
|
|
// Skip if notifications are disabled
|
|
if (!showVersionUpdates.value) {
|
|
return false
|
|
}
|
|
|
|
// Already latest → no dot
|
|
if (!isNewVersionAvailable.value) {
|
|
return false
|
|
}
|
|
|
|
const { version } = recentRelease.value
|
|
|
|
// Changelog seen → clear dot
|
|
if (
|
|
releaseVersion.value === version &&
|
|
releaseStatus.value === 'changelog seen'
|
|
) {
|
|
return false
|
|
}
|
|
|
|
// Attention medium / high (levels 2 & 3)
|
|
if (hasMediumOrHighAttention.value) {
|
|
// Persist until changelog is opened
|
|
return true
|
|
}
|
|
|
|
// Attention low (level 1) and skipped → keep up to 3 d
|
|
if (
|
|
releaseVersion.value === version &&
|
|
releaseStatus.value === 'skipped' &&
|
|
releaseTimestamp.value &&
|
|
Date.now() - releaseTimestamp.value >= THREE_DAYS_MS
|
|
) {
|
|
return false
|
|
}
|
|
|
|
// Not skipped → show
|
|
return true
|
|
})
|
|
|
|
// Show "What's New" popup
|
|
const shouldShowPopup = computed(() => {
|
|
// Only show on desktop version
|
|
if (!isElectron()) {
|
|
return false
|
|
}
|
|
|
|
// Skip if notifications are disabled
|
|
if (!showVersionUpdates.value) {
|
|
return false
|
|
}
|
|
|
|
if (!isLatestVersion.value) {
|
|
return false
|
|
}
|
|
|
|
// Hide if already seen
|
|
if (
|
|
releaseVersion.value === recentRelease.value.version &&
|
|
releaseStatus.value === "what's new seen"
|
|
) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
// Action handlers for user interactions
|
|
async function handleSkipRelease(version: string): Promise<void> {
|
|
if (
|
|
version !== recentRelease.value?.version ||
|
|
releaseStatus.value === 'changelog seen'
|
|
) {
|
|
return
|
|
}
|
|
|
|
await settingStore.set('Comfy.Release.Version', version)
|
|
await settingStore.set('Comfy.Release.Status', 'skipped')
|
|
await settingStore.set('Comfy.Release.Timestamp', Date.now())
|
|
}
|
|
|
|
async function handleShowChangelog(version: string): Promise<void> {
|
|
if (version !== recentRelease.value?.version) {
|
|
return
|
|
}
|
|
|
|
await settingStore.set('Comfy.Release.Version', version)
|
|
await settingStore.set('Comfy.Release.Status', 'changelog seen')
|
|
await settingStore.set('Comfy.Release.Timestamp', Date.now())
|
|
}
|
|
|
|
async function handleWhatsNewSeen(version: string): Promise<void> {
|
|
if (version !== recentRelease.value?.version) {
|
|
return
|
|
}
|
|
|
|
await settingStore.set('Comfy.Release.Version', version)
|
|
await settingStore.set('Comfy.Release.Status', "what's new seen")
|
|
await settingStore.set('Comfy.Release.Timestamp', Date.now())
|
|
}
|
|
|
|
// Fetch releases from API
|
|
async function fetchReleases(): Promise<void> {
|
|
if (isLoading.value) {
|
|
return
|
|
}
|
|
|
|
// Skip fetching if notifications are disabled
|
|
if (!showVersionUpdates.value) {
|
|
return
|
|
}
|
|
|
|
// Skip fetching if API nodes are disabled via argv
|
|
if (
|
|
systemStatsStore.systemStats?.system?.argv?.includes(
|
|
'--disable-api-nodes'
|
|
)
|
|
) {
|
|
return
|
|
}
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
try {
|
|
// Ensure system stats are loaded
|
|
if (!systemStatsStore.systemStats) {
|
|
await until(systemStatsStore.isInitialized)
|
|
}
|
|
|
|
const fetchedReleases = await releaseService.getReleases({
|
|
project: 'comfyui',
|
|
current_version: currentComfyUIVersion.value,
|
|
form_factor: systemStatsStore.getFormFactor(),
|
|
locale: stringToLocale(locale.value)
|
|
})
|
|
|
|
if (fetchedReleases !== null) {
|
|
releases.value = fetchedReleases
|
|
} else if (releaseService.error.value) {
|
|
error.value = releaseService.error.value
|
|
}
|
|
} catch (err) {
|
|
error.value =
|
|
err instanceof Error ? err.message : 'Unknown error occurred'
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
// Initialize store
|
|
async function initialize(): Promise<void> {
|
|
await fetchReleases()
|
|
}
|
|
|
|
return {
|
|
releases,
|
|
isLoading,
|
|
error,
|
|
recentRelease,
|
|
recentReleases,
|
|
shouldShowToast,
|
|
shouldShowRedDot,
|
|
shouldShowPopup,
|
|
shouldShowUpdateButton: isNewVersionAvailable,
|
|
handleSkipRelease,
|
|
handleShowChangelog,
|
|
handleWhatsNewSeen,
|
|
fetchReleases,
|
|
initialize
|
|
}
|
|
})
|