mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-23 00:04:06 +00:00
Feature Implemented: Warning displayed when frontend version mismatches (#4363)
Co-authored-by: bymyself <cbyrne@comfy.org> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
94
src/composables/useFrontendVersionMismatchWarning.ts
Normal file
94
src/composables/useFrontendVersionMismatchWarning.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
import { useVersionCompatibilityStore } from '@/stores/versionCompatibilityStore'
|
||||
|
||||
export interface UseFrontendVersionMismatchWarningOptions {
|
||||
immediate?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable for handling frontend version mismatch warnings.
|
||||
*
|
||||
* Displays toast notifications when the frontend version is incompatible with the backend,
|
||||
* either because the frontend is outdated or newer than the backend expects.
|
||||
* Automatically dismisses warnings when shown and persists dismissal state for 7 days.
|
||||
*
|
||||
* @param options - Configuration options
|
||||
* @param options.immediate - If true, automatically shows warning when version mismatch is detected
|
||||
* @returns Object with methods and computed properties for managing version warnings
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Show warning immediately when mismatch detected
|
||||
* const { showWarning, shouldShowWarning } = useFrontendVersionMismatchWarning({ immediate: true })
|
||||
*
|
||||
* // Manual control
|
||||
* const { showWarning } = useFrontendVersionMismatchWarning()
|
||||
* showWarning() // Call when needed
|
||||
* ```
|
||||
*/
|
||||
export function useFrontendVersionMismatchWarning(
|
||||
options: UseFrontendVersionMismatchWarningOptions = {}
|
||||
) {
|
||||
const { immediate = false } = options
|
||||
const { t } = useI18n()
|
||||
const toastStore = useToastStore()
|
||||
const versionCompatibilityStore = useVersionCompatibilityStore()
|
||||
|
||||
// Track if we've already shown the warning
|
||||
let hasShownWarning = false
|
||||
|
||||
const showWarning = () => {
|
||||
// Prevent showing the warning multiple times
|
||||
if (hasShownWarning) return
|
||||
|
||||
const message = versionCompatibilityStore.warningMessage
|
||||
if (!message) return
|
||||
|
||||
const detailMessage = t('g.frontendOutdated', {
|
||||
frontendVersion: message.frontendVersion,
|
||||
requiredVersion: message.requiredVersion
|
||||
})
|
||||
|
||||
const fullMessage = t('g.versionMismatchWarningMessage', {
|
||||
warning: t('g.versionMismatchWarning'),
|
||||
detail: detailMessage
|
||||
})
|
||||
|
||||
toastStore.addAlert(fullMessage)
|
||||
hasShownWarning = true
|
||||
|
||||
// Automatically dismiss the warning so it won't show again for 7 days
|
||||
versionCompatibilityStore.dismissWarning()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Only set up the watcher if immediate is true
|
||||
if (immediate) {
|
||||
whenever(
|
||||
() => versionCompatibilityStore.shouldShowWarning,
|
||||
() => {
|
||||
showWarning()
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
once: true
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
showWarning,
|
||||
shouldShowWarning: computed(
|
||||
() => versionCompatibilityStore.shouldShowWarning
|
||||
),
|
||||
dismissWarning: versionCompatibilityStore.dismissWarning,
|
||||
hasVersionMismatch: computed(
|
||||
() => versionCompatibilityStore.hasVersionMismatch
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,12 @@
|
||||
"nodes": "Nodes",
|
||||
"community": "Community",
|
||||
"all": "All",
|
||||
"versionMismatchWarning": "Version Compatibility Warning",
|
||||
"versionMismatchWarningMessage": "{warning}: {detail} Visit https://docs.comfy.org/installation/update_comfyui#common-update-issues for update instructions.",
|
||||
"frontendOutdated": "Frontend version {frontendVersion} is outdated. Backend requires {requiredVersion} or higher.",
|
||||
"frontendNewer": "Frontend version {frontendVersion} may not be compatible with backend version {backendVersion}.",
|
||||
"updateFrontend": "Update Frontend",
|
||||
"dismiss": "Dismiss",
|
||||
"update": "Update",
|
||||
"updated": "Updated",
|
||||
"resultsCount": "Found {count} Results",
|
||||
@@ -1351,6 +1357,13 @@
|
||||
"outdatedVersionGeneric": "Some nodes require a newer version of ComfyUI. Please update to use all nodes.",
|
||||
"coreNodesFromVersion": "Requires ComfyUI {version}:"
|
||||
},
|
||||
"versionMismatchWarning": {
|
||||
"title": "Version Compatibility Warning",
|
||||
"frontendOutdated": "Frontend version {frontendVersion} is outdated. Backend requires version {requiredVersion} or higher.",
|
||||
"frontendNewer": "Frontend version {frontendVersion} may not be compatible with backend version {backendVersion}.",
|
||||
"updateFrontend": "Update Frontend",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"errorDialog": {
|
||||
"defaultTitle": "An error occurred",
|
||||
"loadWorkflowTitle": "Loading aborted due to error reloading workflow data",
|
||||
|
||||
@@ -338,6 +338,7 @@ export const zSystemStats = z.object({
|
||||
embedded_python: z.boolean(),
|
||||
comfyui_version: z.string(),
|
||||
pytorch_version: z.string(),
|
||||
required_frontend_version: z.string().optional(),
|
||||
argv: z.array(z.string()),
|
||||
ram_total: z.number(),
|
||||
ram_free: z.number()
|
||||
|
||||
@@ -135,6 +135,7 @@ The following table lists ALL stores in the system as of 2025-01-30:
|
||||
| toastStore.ts | Manages toast notifications | UI |
|
||||
| userFileStore.ts | Manages user file operations | Files |
|
||||
| userStore.ts | Manages user data and preferences | User |
|
||||
| versionCompatibilityStore.ts | Manages frontend/backend version compatibility warnings | Core |
|
||||
| widgetStore.ts | Manages widget configurations | Widgets |
|
||||
| workflowStore.ts | Handles workflow data and operations | Workflows |
|
||||
| workflowTemplatesStore.ts | Manages workflow templates | Workflows |
|
||||
|
||||
138
src/stores/versionCompatibilityStore.ts
Normal file
138
src/stores/versionCompatibilityStore.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import * as semver from 'semver'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import config from '@/config'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
|
||||
const DISMISSAL_DURATION_MS = 7 * 24 * 60 * 60 * 1000 // 7 days
|
||||
|
||||
export const useVersionCompatibilityStore = defineStore(
|
||||
'versionCompatibility',
|
||||
() => {
|
||||
const systemStatsStore = useSystemStatsStore()
|
||||
|
||||
const frontendVersion = computed(() => config.app_version)
|
||||
const backendVersion = computed(
|
||||
() => systemStatsStore.systemStats?.system?.comfyui_version ?? ''
|
||||
)
|
||||
const requiredFrontendVersion = computed(
|
||||
() =>
|
||||
systemStatsStore.systemStats?.system?.required_frontend_version ?? ''
|
||||
)
|
||||
|
||||
const isFrontendOutdated = computed(() => {
|
||||
if (
|
||||
!frontendVersion.value ||
|
||||
!requiredFrontendVersion.value ||
|
||||
!semver.valid(frontendVersion.value) ||
|
||||
!semver.valid(requiredFrontendVersion.value)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
// Returns true if required version is greater than frontend version
|
||||
return semver.gt(requiredFrontendVersion.value, frontendVersion.value)
|
||||
})
|
||||
|
||||
const isFrontendNewer = computed(() => {
|
||||
// We don't warn about frontend being newer than backend
|
||||
// Only warn when frontend is outdated (behind required version)
|
||||
return false
|
||||
})
|
||||
|
||||
const hasVersionMismatch = computed(() => {
|
||||
return isFrontendOutdated.value
|
||||
})
|
||||
|
||||
const versionKey = computed(() => {
|
||||
if (
|
||||
!frontendVersion.value ||
|
||||
!backendVersion.value ||
|
||||
!requiredFrontendVersion.value
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return `${frontendVersion.value}-${backendVersion.value}-${requiredFrontendVersion.value}`
|
||||
})
|
||||
|
||||
// Use reactive storage for dismissals - creates a reactive ref that syncs with localStorage
|
||||
// All version mismatch dismissals are stored in a single object for clean localStorage organization
|
||||
const dismissalStorage = useStorage(
|
||||
'comfy.versionMismatch.dismissals',
|
||||
{} as Record<string, number>,
|
||||
localStorage,
|
||||
{
|
||||
serializer: {
|
||||
read: (value: string) => {
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
write: (value: Record<string, number>) => JSON.stringify(value)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const isDismissed = computed(() => {
|
||||
if (!versionKey.value) return false
|
||||
|
||||
const dismissedUntil = dismissalStorage.value[versionKey.value]
|
||||
if (!dismissedUntil) return false
|
||||
|
||||
// Check if dismissal has expired
|
||||
return Date.now() < dismissedUntil
|
||||
})
|
||||
|
||||
const shouldShowWarning = computed(() => {
|
||||
return hasVersionMismatch.value && !isDismissed.value
|
||||
})
|
||||
|
||||
const warningMessage = computed(() => {
|
||||
if (isFrontendOutdated.value) {
|
||||
return {
|
||||
type: 'outdated' as const,
|
||||
frontendVersion: frontendVersion.value,
|
||||
requiredVersion: requiredFrontendVersion.value
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
async function checkVersionCompatibility() {
|
||||
if (!systemStatsStore.systemStats) {
|
||||
await systemStatsStore.fetchSystemStats()
|
||||
}
|
||||
}
|
||||
|
||||
function dismissWarning() {
|
||||
if (!versionKey.value) return
|
||||
|
||||
const dismissUntil = Date.now() + DISMISSAL_DURATION_MS
|
||||
dismissalStorage.value = {
|
||||
...dismissalStorage.value,
|
||||
[versionKey.value]: dismissUntil
|
||||
}
|
||||
}
|
||||
|
||||
async function initialize() {
|
||||
await checkVersionCompatibility()
|
||||
}
|
||||
|
||||
return {
|
||||
frontendVersion,
|
||||
backendVersion,
|
||||
requiredFrontendVersion,
|
||||
hasVersionMismatch,
|
||||
shouldShowWarning,
|
||||
warningMessage,
|
||||
isFrontendOutdated,
|
||||
isFrontendNewer,
|
||||
checkVersionCompatibility,
|
||||
dismissWarning,
|
||||
initialize
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -23,7 +23,14 @@
|
||||
import { useBreakpoints, useEventListener } from '@vueuse/core'
|
||||
import type { ToastMessageOptions } from 'primevue/toast'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { computed, onBeforeUnmount, onMounted, watch, watchEffect } from 'vue'
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
watch,
|
||||
watchEffect
|
||||
} from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import MenuHamburger from '@/components/MenuHamburger.vue'
|
||||
@@ -35,6 +42,7 @@ import TopMenubar from '@/components/topbar/TopMenubar.vue'
|
||||
import { useBrowserTabTitle } from '@/composables/useBrowserTabTitle'
|
||||
import { useCoreCommands } from '@/composables/useCoreCommands'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import { useFrontendVersionMismatchWarning } from '@/composables/useFrontendVersionMismatchWarning'
|
||||
import { useProgressFavicon } from '@/composables/useProgressFavicon'
|
||||
import { SERVER_CONFIG_ITEMS } from '@/constants/serverConfig'
|
||||
import { i18n } from '@/i18n'
|
||||
@@ -54,6 +62,7 @@ import {
|
||||
} from '@/stores/queueStore'
|
||||
import { useServerConfigStore } from '@/stores/serverConfigStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useVersionCompatibilityStore } from '@/stores/versionCompatibilityStore'
|
||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
|
||||
@@ -70,6 +79,8 @@ const settingStore = useSettingStore()
|
||||
const executionStore = useExecutionStore()
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
const queueStore = useQueueStore()
|
||||
const versionCompatibilityStore = useVersionCompatibilityStore()
|
||||
|
||||
const breakpoints = useBreakpoints({ md: 961 })
|
||||
const isMobile = breakpoints.smaller('md')
|
||||
const showTopMenu = computed(() => isMobile.value || useNewMenu.value === 'Top')
|
||||
@@ -224,6 +235,17 @@ onBeforeUnmount(() => {
|
||||
useEventListener(window, 'keydown', useKeybindingService().keybindHandler)
|
||||
|
||||
const { wrapWithErrorHandling, wrapWithErrorHandlingAsync } = useErrorHandling()
|
||||
|
||||
// Initialize version mismatch warning in setup context
|
||||
// It will be triggered automatically when the store is ready
|
||||
useFrontendVersionMismatchWarning({ immediate: true })
|
||||
|
||||
void nextTick(() => {
|
||||
versionCompatibilityStore.initialize().catch((error) => {
|
||||
console.warn('Version compatibility check failed:', error)
|
||||
})
|
||||
})
|
||||
|
||||
const onGraphReady = () => {
|
||||
requestIdleCallback(
|
||||
() => {
|
||||
|
||||
Reference in New Issue
Block a user