mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-04 15:10:06 +00:00
[feat] Add managerStateStore for three-state manager UI logic
- Create managerStateStore to determine manager UI state (disabled, legacy, new) - Check command line args, feature flags, and legacy API endpoints - Update useCoreCommands to use the new store instead of async API calls - Initialize manager state after system stats are loaded in GraphView - Add comprehensive tests for all manager state scenarios 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,6 @@ import {
|
||||
import { Point } from '@/lib/litegraph/src/litegraph'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import { useWorkflowService } from '@/services/workflowService'
|
||||
@@ -26,6 +25,10 @@ import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { useCanvasStore, useTitleEditorStore } from '@/stores/graphStore'
|
||||
import { useHelpCenterStore } from '@/stores/helpCenterStore'
|
||||
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
||||
import {
|
||||
ManagerUIState,
|
||||
useManagerStateStore
|
||||
} from '@/stores/managerStateStore'
|
||||
import { useQueueSettingsStore, useQueueStore } from '@/stores/queueStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
||||
@@ -739,27 +742,36 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
icon: 'pi pi-objects-column',
|
||||
label: 'Custom Nodes Manager',
|
||||
versionAdded: '1.12.10',
|
||||
function: async () => {
|
||||
const { is_legacy_manager_ui } =
|
||||
(await useComfyManagerService().isLegacyManagerUI()) ?? {}
|
||||
function: () => {
|
||||
const managerStore = useManagerStateStore()
|
||||
const state = managerStore.managerUIState
|
||||
|
||||
if (is_legacy_manager_ui === true) {
|
||||
try {
|
||||
await useCommandStore().execute(
|
||||
'Comfy.Manager.Menu.ToggleVisibility' // This command is registered by legacy manager FE extension
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('error', error)
|
||||
useToastStore().add({
|
||||
switch (state) {
|
||||
case ManagerUIState.DISABLED:
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('manager.legacyMenuNotAvailable'),
|
||||
detail: t('manager.notAvailable'),
|
||||
life: 3000
|
||||
})
|
||||
break
|
||||
|
||||
case ManagerUIState.LEGACY_UI:
|
||||
useCommandStore()
|
||||
.execute('Comfy.Manager.Menu.ToggleVisibility')
|
||||
.catch(() => {
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: t('manager.legacyMenuNotAvailable'),
|
||||
life: 3000
|
||||
})
|
||||
})
|
||||
break
|
||||
|
||||
case ManagerUIState.NEW_UI:
|
||||
dialogService.showManagerDialog()
|
||||
}
|
||||
} else {
|
||||
dialogService.showManagerDialog()
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
59
src/stores/managerStateStore.ts
Normal file
59
src/stores/managerStateStore.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { readonly, ref } from 'vue'
|
||||
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
|
||||
export enum ManagerUIState {
|
||||
DISABLED = 'disabled',
|
||||
LEGACY_UI = 'legacy',
|
||||
NEW_UI = 'new'
|
||||
}
|
||||
|
||||
export const useManagerStateStore = defineStore('managerState', () => {
|
||||
const managerUIState = ref<ManagerUIState | null>(null)
|
||||
const isInitialized = ref(false)
|
||||
|
||||
const initializeManagerState = async () => {
|
||||
if (isInitialized.value) return
|
||||
|
||||
const systemStats = useSystemStatsStore().systemStats
|
||||
const { flags } = useFeatureFlags()
|
||||
const clientSupportsV4 =
|
||||
api.getClientFeatureFlags().supports_manager_v4_ui ?? false
|
||||
|
||||
// Check command line args first
|
||||
if (systemStats?.system?.argv?.includes('--disable-manager')) {
|
||||
managerUIState.value = ManagerUIState.DISABLED
|
||||
} else if (
|
||||
systemStats?.system?.argv?.includes('--enable-manager-legacy-ui')
|
||||
) {
|
||||
managerUIState.value = ManagerUIState.LEGACY_UI
|
||||
} else {
|
||||
// Check if we can use new UI
|
||||
if (clientSupportsV4 && flags.supportsManagerV4) {
|
||||
managerUIState.value = ManagerUIState.NEW_UI
|
||||
} else {
|
||||
// For old frontend, we need to check if legacy manager exists
|
||||
try {
|
||||
await useComfyManagerService().isLegacyManagerUI()
|
||||
// Route exists but we can't use v4
|
||||
managerUIState.value = ManagerUIState.LEGACY_UI
|
||||
} catch {
|
||||
// Route doesn't exist = old manager OR no manager
|
||||
// Old frontend will handle this itself
|
||||
managerUIState.value = ManagerUIState.LEGACY_UI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isInitialized.value = true
|
||||
}
|
||||
|
||||
return {
|
||||
managerUIState: readonly(managerUIState),
|
||||
initializeManagerState
|
||||
}
|
||||
})
|
||||
@@ -53,6 +53,7 @@ import { setupAutoQueueHandler } from '@/services/autoQueueService'
|
||||
import { useKeybindingService } from '@/services/keybindingService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { useManagerStateStore } from '@/stores/managerStateStore'
|
||||
import { useMenuItemStore } from '@/stores/menuItemStore'
|
||||
import { useModelStore } from '@/stores/modelStore'
|
||||
import { useNodeDefStore, useNodeFrequencyStore } from '@/stores/nodeDefStore'
|
||||
@@ -250,6 +251,13 @@ void nextTick(() => {
|
||||
versionCompatibilityStore.initialize().catch((error) => {
|
||||
console.warn('Version compatibility check failed:', error)
|
||||
})
|
||||
|
||||
// Initialize manager state after system stats are loaded
|
||||
useManagerStateStore()
|
||||
.initializeManagerState()
|
||||
.catch((error) => {
|
||||
console.warn('Manager state initialization failed:', error)
|
||||
})
|
||||
})
|
||||
|
||||
const onGraphReady = () => {
|
||||
|
||||
196
tests-ui/tests/stores/managerStateStore.test.ts
Normal file
196
tests-ui/tests/stores/managerStateStore.test.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||
import {
|
||||
ManagerUIState,
|
||||
useManagerStateStore
|
||||
} from '@/stores/managerStateStore'
|
||||
import { useSystemStatsStore } from '@/stores/systemStatsStore'
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('@/scripts/api', () => ({
|
||||
api: {
|
||||
getClientFeatureFlags: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useFeatureFlags', () => ({
|
||||
useFeatureFlags: vi.fn(() => ({
|
||||
flags: { supportsManagerV4: false },
|
||||
featureFlag: vi.fn()
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/services/comfyManagerService', () => ({
|
||||
useComfyManagerService: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/systemStatsStore', () => ({
|
||||
useSystemStatsStore: vi.fn()
|
||||
}))
|
||||
|
||||
describe('useManagerStateStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('initializeManagerState', () => {
|
||||
it('should set DISABLED state when --disable-manager is present', async () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: {
|
||||
system: { argv: ['python', 'main.py', '--disable-manager'] }
|
||||
}
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
|
||||
const store = useManagerStateStore()
|
||||
await store.initializeManagerState()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.DISABLED)
|
||||
})
|
||||
|
||||
it('should set LEGACY_UI state when --enable-manager-legacy-ui is present', async () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: {
|
||||
system: { argv: ['python', 'main.py', '--enable-manager-legacy-ui'] }
|
||||
}
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
|
||||
const store = useManagerStateStore()
|
||||
await store.initializeManagerState()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.LEGACY_UI)
|
||||
})
|
||||
|
||||
it('should set NEW_UI state when client and server both support v4', async () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: { system: { argv: ['python', 'main.py'] } }
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: true
|
||||
})
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: true },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useComfyManagerService).mockReturnValue({
|
||||
isLegacyManagerUI: vi.fn().mockResolvedValue({})
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
await store.initializeManagerState()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.NEW_UI)
|
||||
})
|
||||
|
||||
it('should set LEGACY_UI state when client does not support v4', async () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: { system: { argv: ['python', 'main.py'] } }
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: false
|
||||
})
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: true },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useComfyManagerService).mockReturnValue({
|
||||
isLegacyManagerUI: vi.fn().mockResolvedValue({})
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
await store.initializeManagerState()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.LEGACY_UI)
|
||||
})
|
||||
|
||||
it('should set LEGACY_UI state when server does not support v4', async () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: { system: { argv: ['python', 'main.py'] } }
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: true
|
||||
})
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: false },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useComfyManagerService).mockReturnValue({
|
||||
isLegacyManagerUI: vi.fn().mockResolvedValue({})
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
await store.initializeManagerState()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.LEGACY_UI)
|
||||
})
|
||||
|
||||
it('should set LEGACY_UI state when isLegacyManagerUI route does not exist', async () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: { system: { argv: ['python', 'main.py'] } }
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({})
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: false },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useComfyManagerService).mockReturnValue({
|
||||
isLegacyManagerUI: vi.fn().mockRejectedValue(new Error('404'))
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
await store.initializeManagerState()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.LEGACY_UI)
|
||||
})
|
||||
|
||||
it('should not re-initialize if already initialized', async () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: {
|
||||
system: { argv: ['python', 'main.py', '--disable-manager'] }
|
||||
}
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
await store.initializeManagerState()
|
||||
expect(store.managerUIState).toBe(ManagerUIState.DISABLED)
|
||||
|
||||
// Change the mock to return different value
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: { system: { argv: ['python', 'main.py'] } }
|
||||
} as any)
|
||||
|
||||
// Try to initialize again
|
||||
await store.initializeManagerState()
|
||||
|
||||
// Should still be DISABLED from first initialization
|
||||
expect(store.managerUIState).toBe(ManagerUIState.DISABLED)
|
||||
})
|
||||
|
||||
it('should handle null systemStats gracefully', async () => {
|
||||
vi.mocked(useSystemStatsStore).mockReturnValue({
|
||||
systemStats: null
|
||||
} as any)
|
||||
vi.mocked(api.getClientFeatureFlags).mockReturnValue({
|
||||
supports_manager_v4_ui: true
|
||||
})
|
||||
vi.mocked(useFeatureFlags).mockReturnValue({
|
||||
flags: { supportsManagerV4: true },
|
||||
featureFlag: vi.fn()
|
||||
} as any)
|
||||
vi.mocked(useComfyManagerService).mockReturnValue({
|
||||
isLegacyManagerUI: vi.fn().mockResolvedValue({})
|
||||
} as any)
|
||||
|
||||
const store = useManagerStateStore()
|
||||
await store.initializeManagerState()
|
||||
|
||||
expect(store.managerUIState).toBe(ManagerUIState.NEW_UI)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user