Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/components/dialog/content/VersionMismatchWarning.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<template>
<Message
v-if="versionStore.shouldShowWarning"
severity="warn"
icon="pi pi-exclamation-triangle"
class="my-2 mx-2"
:closable="true"
:pt="{
root: { class: 'flex-col' },
text: { class: 'flex-1' }
}"
@close="handleDismiss"
>
<div class="flex flex-col gap-3">
<!-- Warning Message -->
<div class="font-medium">
{{ $t('versionMismatchWarning.title') }}
</div>

<!-- Version Details -->
<div v-if="versionStore.warningMessage">
<div v-if="versionStore.warningMessage.type === 'outdated'">
{{
$t('versionMismatchWarning.frontendOutdated', {
frontendVersion: versionStore.warningMessage.frontendVersion,
requiredVersion: versionStore.warningMessage.requiredVersion
})
}}
</div>
<div v-else-if="versionStore.warningMessage.type === 'newer'">
{{
$t('versionMismatchWarning.frontendNewer', {
frontendVersion: versionStore.warningMessage.frontendVersion,
backendVersion: versionStore.warningMessage.backendVersion
})
}}
</div>
</div>

<!-- Action Buttons -->
<div class="flex gap-2 justify-end">
<Button
v-if="versionStore.isFrontendOutdated"
:label="$t('versionMismatchWarning.updateFrontend')"
size="small"
severity="warn"
@click="handleUpdate"
/>
<Button
:label="$t('versionMismatchWarning.dismiss')"
size="small"
severity="secondary"
@click="handleDismiss"
/>
</div>
</div>
</Message>
</template>

<script setup lang="ts">
import Button from 'primevue/button'
import Message from 'primevue/message'

import { useVersionCompatibilityStore } from '@/stores/versionCompatibilityStore'

const versionStore = useVersionCompatibilityStore()

const handleDismiss = () => {
void versionStore.dismissWarning()
}

const handleUpdate = () => {
// Open ComfyUI documentation or update instructions
window.open('https://docs.comfy.org/get_started/introduction', '_blank')
}
</script>

<style scoped>
/* Custom styles if needed */
</style>
6 changes: 6 additions & 0 deletions src/constants/coreSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -873,5 +873,11 @@ export const CORE_SETTINGS: SettingParams[] = [
name: 'Release seen timestamp',
type: 'hidden',
defaultValue: 0
},
{
id: 'Comfy.VersionMismatch.DismissedVersion',
name: 'Dismissed version mismatch warning',
type: 'hidden',
defaultValue: ''
}
]
7 changes: 7 additions & 0 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,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",
Expand Down
3 changes: 3 additions & 0 deletions src/schemas/apiSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,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()
Expand Down Expand Up @@ -481,6 +482,8 @@ const zSettings = z.object({
"what's new seen"
]),
'Comfy.Release.Timestamp': z.number(),
/** Version compatibility settings */
'Comfy.VersionMismatch.DismissedVersion': z.string(),
/** Settings used for testing */
'test.setting': z.any(),
'main.sub.setting.name': z.any(),
Expand Down
1 change: 1 addition & 0 deletions src/stores/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
125 changes: 125 additions & 0 deletions src/stores/versionCompatibilityStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'

import config from '@/config'
import { useSettingStore } from '@/stores/settingStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { compareVersions } from '@/utils/formatUtil'

export const useVersionCompatibilityStore = defineStore(
'versionCompatibility',
() => {
const systemStatsStore = useSystemStatsStore()
const settingStore = useSettingStore()

const isDismissed = ref(false)
const dismissedVersion = ref<string | null>(null)

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) {
return false
}
return (
compareVersions(requiredFrontendVersion.value, frontendVersion.value) >
0
)
})

const isFrontendNewer = computed(() => {
if (!frontendVersion.value || !backendVersion.value) {
return false
}
const versionDiff = compareVersions(
frontendVersion.value,
backendVersion.value
)
return versionDiff > 0
})

const hasVersionMismatch = computed(() => {
return isFrontendOutdated.value || isFrontendNewer.value
})

const shouldShowWarning = computed(() => {
if (!hasVersionMismatch.value || isDismissed.value) {
return false
}

const currentVersionKey = `${frontendVersion.value}-${backendVersion.value}-${requiredFrontendVersion.value}`
return dismissedVersion.value !== currentVersionKey
})

const warningMessage = computed(() => {
if (isFrontendOutdated.value) {
return {
type: 'outdated' as const,
frontendVersion: frontendVersion.value,
requiredVersion: requiredFrontendVersion.value
}
} else if (isFrontendNewer.value) {
return {
type: 'newer' as const,
frontendVersion: frontendVersion.value,
backendVersion: backendVersion.value
}
}
return null
})

async function checkVersionCompatibility() {
if (!systemStatsStore.systemStats) {
await systemStatsStore.fetchSystemStats()
}
}

async function dismissWarning() {
isDismissed.value = true
const currentVersionKey = `${frontendVersion.value}-${backendVersion.value}-${requiredFrontendVersion.value}`
dismissedVersion.value = currentVersionKey

await settingStore.set(
'Comfy.VersionMismatch.DismissedVersion',
currentVersionKey
)
}

function restoreDismissalState() {
const dismissed = settingStore.get(
'Comfy.VersionMismatch.DismissedVersion'
)
if (dismissed) {
dismissedVersion.value = dismissed
const currentVersionKey = `${frontendVersion.value}-${backendVersion.value}-${requiredFrontendVersion.value}`
isDismissed.value = dismissed === currentVersionKey
}
}

async function initialize() {
await checkVersionCompatibility()
restoreDismissalState()
}

return {
frontendVersion,
backendVersion,
requiredFrontendVersion,
hasVersionMismatch,
shouldShowWarning,
warningMessage,
isFrontendOutdated,
isFrontendNewer,
checkVersionCompatibility,
dismissWarning,
initialize
}
}
)
7 changes: 7 additions & 0 deletions src/views/GraphView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<div id="comfyui-body-left" class="comfyui-body-left" />
<div id="comfyui-body-right" class="comfyui-body-right" />
<div id="graph-canvas-container" class="graph-canvas-container">
<VersionMismatchWarning />
<GraphCanvas @ready="onGraphReady" />
</div>
</div>
Expand All @@ -28,6 +29,7 @@ import { useI18n } from 'vue-i18n'

import MenuHamburger from '@/components/MenuHamburger.vue'
import UnloadWindowConfirmDialog from '@/components/dialog/UnloadWindowConfirmDialog.vue'
import VersionMismatchWarning from '@/components/dialog/content/VersionMismatchWarning.vue'
import GraphCanvas from '@/components/graph/GraphCanvas.vue'
import GlobalToast from '@/components/toast/GlobalToast.vue'
import RerouteMigrationToast from '@/components/toast/RerouteMigrationToast.vue'
Expand All @@ -54,6 +56,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'
Expand All @@ -70,6 +73,7 @@ const settingStore = useSettingStore()
const executionStore = useExecutionStore()
const colorPaletteStore = useColorPaletteStore()
const queueStore = useQueueStore()
const versionCompatibilityStore = useVersionCompatibilityStore()

watch(
() => colorPaletteStore.completedActivePalette,
Expand Down Expand Up @@ -206,6 +210,9 @@ onMounted(() => {
} catch (e) {
console.error('Failed to init ComfyUI frontend', e)
}

// Initialize version compatibility checking (fire-and-forget)
void versionCompatibilityStore.initialize()
})

onBeforeUnmount(() => {
Expand Down
27 changes: 27 additions & 0 deletions tests-ui/tests/store/systemStatsStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ describe('useSystemStatsStore', () => {
embedded_python: false,
comfyui_version: '1.0.0',
pytorch_version: '2.0.0',
required_frontend_version: '1.24.0',
argv: [],
ram_total: 16000000000,
ram_free: 8000000000
Expand Down Expand Up @@ -92,6 +93,32 @@ describe('useSystemStatsStore', () => {

expect(store.isLoading).toBe(false)
})

it('should handle system stats updates', async () => {
const updatedStats = {
system: {
os: 'Windows',
python_version: '3.11.0',
embedded_python: false,
comfyui_version: '1.1.0',
pytorch_version: '2.1.0',
required_frontend_version: '1.25.0',
argv: [],
ram_total: 16000000000,
ram_free: 7000000000
},
devices: []
}

vi.mocked(api.getSystemStats).mockResolvedValue(updatedStats)

await store.fetchSystemStats()

expect(store.systemStats).toEqual(updatedStats)
expect(store.isLoading).toBe(false)
expect(store.error).toBeNull()
expect(api.getSystemStats).toHaveBeenCalled()
})
})

describe('getFormFactor', () => {
Expand Down
Loading
Loading