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 @@