From b169772f9fe19e519f47af06e49befbc3b7d68bb Mon Sep 17 00:00:00 2001 From: Jin Yi Date: Fri, 25 Jul 2025 08:35:46 +0900 Subject: [PATCH] Manager Conflict Nofitication (#4443) Co-authored-by: github-actions Co-authored-by: bymyself Co-authored-by: Claude --- src/App.vue | 2 +- src/components/dialog/GlobalDialog.vue | 2 +- .../content/manager/ManagerDialogContent.vue | 102 ++- .../manager/NodeConflictDialogContent.vue | 163 +++++ .../content/manager/NodeConflictFooter.vue | 59 ++ .../content/manager/NodeConflictHeader.vue | 12 + .../content/manager/PackVersionBadge.test.ts | 8 + .../content/manager/PackVersionBadge.vue | 7 +- .../PackVersionSelectorPopover.test.ts | 407 ++++++++++- .../manager/PackVersionSelectorPopover.vue | 180 ++++- .../manager/button/PackActionButton.vue | 10 +- .../manager/button/PackEnableToggle.test.ts | 19 +- .../manager/button/PackEnableToggle.vue | 91 ++- .../manager/button/PackInstallButton.vue | 103 ++- .../content/manager/infoPanel/InfoPanel.vue | 61 +- .../manager/infoPanel/InfoPanelHeader.vue | 10 +- .../content/manager/infoPanel/InfoTabs.vue | 38 +- .../infoPanel/tabs/WarningTabPanel.vue | 24 + .../content/manager/packCard/PackCard.vue | 2 +- .../manager/packCard/PackCardFooter.vue | 55 +- .../manager/packCard/PackCardHeader.vue | 14 +- .../helpcenter/HelpCenterMenuContent.vue | 70 +- src/components/helpcenter/WhatsNewPopup.vue | 9 + src/components/icons/PuzzleIcon.vue | 43 ++ src/components/icons/VerifiedIcon.vue | 29 + .../sidebar/SidebarHelpCenterIcon.vue | 56 +- src/composables/useConflictAcknowledgment.ts | 223 ++++++ src/composables/useConflictBannerState.ts | 71 ++ src/composables/useConflictDetection.ts | 669 ++++++++++++++---- src/locales/en/main.json | 30 + src/locales/zh/main.json | 1 - src/services/comfyManagerService.ts | 10 +- src/services/dialogService.ts | 48 +- src/stores/comfyManagerStore.ts | 16 + src/stores/conflictDetectionStore.ts | 70 ++ src/stores/dialogStore.ts | 6 +- src/types/conflictDetectionTypes.ts | 63 +- src/utils/conflictMessageUtil.ts | 66 ++ src/utils/versionUtil.ts | 77 +- tailwind.config.js | 14 + .../manager/NodeConflictDialogContent.test.ts | 356 ++++++++++ .../content/manager/packCard/PackCard.test.ts | 222 ++++++ .../helpcenter/WhatsNewPopup.test.ts | 433 ++++++++++++ .../useConflictAcknowledgment.spec.ts | 130 ++++ .../useConflictAcknowledgment.test.ts | 426 +++++++++++ .../composables/useConflictDetection.test.ts | 272 +++++-- .../stores/conflictDetectionStore.test.ts | 271 +++++++ vitest.setup.ts | 8 + 48 files changed, 4684 insertions(+), 374 deletions(-) create mode 100644 src/components/dialog/content/manager/NodeConflictDialogContent.vue create mode 100644 src/components/dialog/content/manager/NodeConflictFooter.vue create mode 100644 src/components/dialog/content/manager/NodeConflictHeader.vue create mode 100644 src/components/dialog/content/manager/infoPanel/tabs/WarningTabPanel.vue create mode 100644 src/components/icons/PuzzleIcon.vue create mode 100644 src/components/icons/VerifiedIcon.vue create mode 100644 src/composables/useConflictAcknowledgment.ts create mode 100644 src/composables/useConflictBannerState.ts create mode 100644 src/stores/conflictDetectionStore.ts create mode 100644 src/utils/conflictMessageUtil.ts create mode 100644 tests-ui/tests/components/dialog/content/manager/NodeConflictDialogContent.test.ts create mode 100644 tests-ui/tests/components/dialog/content/manager/packCard/PackCard.test.ts create mode 100644 tests-ui/tests/components/helpcenter/WhatsNewPopup.test.ts create mode 100644 tests-ui/tests/composables/useConflictAcknowledgment.spec.ts create mode 100644 tests-ui/tests/composables/useConflictAcknowledgment.test.ts create mode 100644 tests-ui/tests/stores/conflictDetectionStore.test.ts diff --git a/src/App.vue b/src/App.vue index b99ce915e..1a4068a27 100644 --- a/src/App.vue +++ b/src/App.vue @@ -15,8 +15,8 @@ import ProgressSpinner from 'primevue/progressspinner' import { computed, onMounted } from 'vue' import GlobalDialog from '@/components/dialog/GlobalDialog.vue' -import config from '@/config' import { useConflictDetection } from '@/composables/useConflictDetection' +import config from '@/config' import { useWorkspaceStore } from '@/stores/workspaceStore' import { electronAPI, isElectron } from './utils/envUtil' diff --git a/src/components/dialog/GlobalDialog.vue b/src/components/dialog/GlobalDialog.vue index bdc3a28bb..0b37462c5 100644 --- a/src/components/dialog/GlobalDialog.vue +++ b/src/components/dialog/GlobalDialog.vue @@ -27,7 +27,7 @@ /> diff --git a/src/components/dialog/content/manager/ManagerDialogContent.vue b/src/components/dialog/content/manager/ManagerDialogContent.vue index 20c13f66a..b84a8c9eb 100644 --- a/src/components/dialog/content/manager/ManagerDialogContent.vue +++ b/src/components/dialog/content/manager/ManagerDialogContent.vue @@ -26,6 +26,27 @@ }" >
+ +
+ +
+

+ {{ $t('manager.conflicts.warningBanner.title') }} +

+

+ {{ $t('manager.conflicts.warningBanner.message') }} +

+

+ {{ $t('manager.conflicts.warningBanner.button') }} +

+
+
@@ -102,7 +125,8 @@ import { onMounted, onUnmounted, ref, - watch + watch, + watchEffect } from 'vue' import { useI18n } from 'vue-i18n' @@ -120,7 +144,9 @@ import { useManagerStatePersistence } from '@/composables/manager/useManagerStat import { useInstalledPacks } from '@/composables/nodePack/useInstalledPacks' import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus' import { useWorkflowPacks } from '@/composables/nodePack/useWorkflowPacks' +import { useConflictBannerState } from '@/composables/useConflictBannerState' import { useRegistrySearch } from '@/composables/useRegistrySearch' +import { useComfyRegistryService } from '@/services/comfyRegistryService' import { useComfyManagerStore } from '@/stores/comfyManagerStore' import { useComfyRegistryStore } from '@/stores/comfyRegistryStore' import type { TabItem } from '@/types/comfyManagerTypes' @@ -134,6 +160,8 @@ const { initialTab } = defineProps<{ const { t } = useI18n() const comfyManagerStore = useComfyManagerStore() const { getPackById } = useComfyRegistryStore() +const registryService = useComfyRegistryService() +const conflictBannerState = useConflictBannerState() const persistedState = useManagerStatePersistence() const initialState = persistedState.loadStoredState() @@ -150,6 +178,9 @@ const { toggle: toggleSideNav } = useResponsiveCollapse() +// Use conflict banner state from composable +const { shouldShowManagerBanner, markConflictsAsSeen } = conflictBannerState + const tabs = ref([ { id: ManagerTab.All, label: t('g.all'), icon: 'pi-list' }, { id: ManagerTab.Installed, label: t('g.installed'), icon: 'pi-box' }, @@ -313,6 +344,13 @@ watch([isAllTab, searchResults], () => { displayPacks.value = searchResults.value }) +const onClickWarningLink = () => { + window.open( + 'https://docs.comfy.org/troubleshooting/custom-node-issues', + '_blank' + ) +} + const onResultsChange = () => { switch (selectedTab.value?.id) { case ManagerTab.Installed: @@ -441,11 +479,57 @@ whenever(selectedNodePack, async () => { if (hasMultipleSelections.value) return // Only fetch if we haven't already for this pack if (lastFetchedPackId.value === pack.id) return - const data = await getPackById.call(pack.id) + + let data = null + + // For installed nodes only, fetch version-specific information + if (comfyManagerStore.isPackInstalled(pack.id)) { + const installedPack = comfyManagerStore.getInstalledPackByCnrId(pack.id) + if (installedPack?.ver) { + // Fetch information for the installed version + data = await registryService.getPackByVersion(pack.id, installedPack.ver) + + // Race condition check: ensure selected pack hasn't changed during async operation + if (selectedNodePack.value?.id !== pack.id) { + return + } + } + } + + // For uninstalled nodes or if version-specific data fetch failed, use default API + if (!data) { + data = await getPackById.call(pack.id) + } + + // Race condition check: ensure selected pack hasn't changed during async operations + if (selectedNodePack.value?.id !== pack.id) { + return + } + // If selected node hasn't changed since request, merge registry & Algolia data - if (data?.id === pack.id) { + const isNodeData = data && 'id' in data && data.id === pack.id + const isVersionData = data && 'node_id' in data && data.node_id === pack.id + + if (isNodeData || isVersionData) { lastFetchedPackId.value = pack.id - const mergedPack = merge({}, pack, data) + + // Merge API data first, then pack data (API data takes priority) + const mergedPack = merge({}, data, pack) + + // Ensure compatibility fields from API data take priority + if (data?.supported_os !== undefined) { + mergedPack.supported_os = data.supported_os + } + if (data?.supported_accelerators !== undefined) { + mergedPack.supported_accelerators = data.supported_accelerators + } + if (data?.supported_comfyui_version !== undefined) { + mergedPack.supported_comfyui_version = data.supported_comfyui_version + } + if (data?.supported_comfyui_frontend_version !== undefined) { + mergedPack.supported_comfyui_frontend_version = + data.supported_comfyui_frontend_version + } // Update the pack in current selection without changing selection state const packIndex = selectedNodePacks.value.findIndex( (p) => p.id === mergedPack.id @@ -473,6 +557,14 @@ watch([searchQuery, selectedTab], () => { } }) +// Automatically mark conflicts as seen when banner is displayed +// This ensures red dots disappear and banner is dismissed once user sees it +watchEffect(() => { + if (shouldShowManagerBanner.value) { + markConflictsAsSeen() + } +}) + onBeforeUnmount(() => { persistedState.persistState({ selectedTabId: selectedTab.value?.id, diff --git a/src/components/dialog/content/manager/NodeConflictDialogContent.vue b/src/components/dialog/content/manager/NodeConflictDialogContent.vue new file mode 100644 index 000000000..07814f600 --- /dev/null +++ b/src/components/dialog/content/manager/NodeConflictDialogContent.vue @@ -0,0 +1,163 @@ + + + + diff --git a/src/components/dialog/content/manager/NodeConflictFooter.vue b/src/components/dialog/content/manager/NodeConflictFooter.vue new file mode 100644 index 000000000..24d4a2a60 --- /dev/null +++ b/src/components/dialog/content/manager/NodeConflictFooter.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/components/dialog/content/manager/NodeConflictHeader.vue b/src/components/dialog/content/manager/NodeConflictHeader.vue new file mode 100644 index 000000000..70e30d129 --- /dev/null +++ b/src/components/dialog/content/manager/NodeConflictHeader.vue @@ -0,0 +1,12 @@ + diff --git a/src/components/dialog/content/manager/PackVersionBadge.test.ts b/src/components/dialog/content/manager/PackVersionBadge.test.ts index eb88f34e8..71d3383d8 100644 --- a/src/components/dialog/content/manager/PackVersionBadge.test.ts +++ b/src/components/dialog/content/manager/PackVersionBadge.test.ts @@ -10,6 +10,14 @@ import enMessages from '@/locales/en/main.json' import PackVersionBadge from './PackVersionBadge.vue' import PackVersionSelectorPopover from './PackVersionSelectorPopover.vue' +// Mock config to prevent __COMFYUI_FRONTEND_VERSION__ error +vi.mock('@/config', () => ({ + default: { + app_title: 'ComfyUI', + app_version: '1.0.0' + } +})) + const mockNodePack = { id: 'test-pack', name: 'Test Pack', diff --git a/src/components/dialog/content/manager/PackVersionBadge.vue b/src/components/dialog/content/manager/PackVersionBadge.vue index fd05240c8..26346093d 100644 --- a/src/components/dialog/content/manager/PackVersionBadge.vue +++ b/src/components/dialog/content/manager/PackVersionBadge.vue @@ -1,8 +1,8 @@