[refactor] Simplify conflict detection types and improve code maintainability (#4589)

This commit is contained in:
Jin Yi
2025-07-30 15:51:36 +09:00
parent 956bc705d7
commit 38e2fa8399
14 changed files with 255 additions and 551 deletions

View File

@@ -53,7 +53,7 @@ const mockNodePack = {
// Create mock functions // Create mock functions
const mockGetPackVersions = vi.fn() const mockGetPackVersions = vi.fn()
const mockInstallPack = vi.fn().mockResolvedValue(undefined) const mockInstallPack = vi.fn().mockResolvedValue(undefined)
const mockCheckVersionCompatibility = vi.fn() const mockCheckNodeCompatibility = vi.fn()
// Mock the registry service // Mock the registry service
vi.mock('@/services/comfyRegistryService', () => ({ vi.mock('@/services/comfyRegistryService', () => ({
@@ -77,7 +77,7 @@ vi.mock('@/stores/comfyManagerStore', () => ({
// Mock the conflict detection composable // Mock the conflict detection composable
vi.mock('@/composables/useConflictDetection', () => ({ vi.mock('@/composables/useConflictDetection', () => ({
useConflictDetection: vi.fn(() => ({ useConflictDetection: vi.fn(() => ({
checkVersionCompatibility: mockCheckVersionCompatibility checkNodeCompatibility: mockCheckNodeCompatibility
})) }))
})) }))
@@ -91,7 +91,7 @@ describe('PackVersionSelectorPopover', () => {
vi.clearAllMocks() vi.clearAllMocks()
mockGetPackVersions.mockReset() mockGetPackVersions.mockReset()
mockInstallPack.mockReset().mockResolvedValue(undefined) mockInstallPack.mockReset().mockResolvedValue(undefined)
mockCheckVersionCompatibility mockCheckNodeCompatibility
.mockReset() .mockReset()
.mockReturnValue({ hasConflict: false, conflicts: [] }) .mockReturnValue({ hasConflict: false, conflicts: [] })
}) })
@@ -376,7 +376,7 @@ describe('PackVersionSelectorPopover', () => {
mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions) mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions)
// Mock compatibility check to return conflict for specific version // Mock compatibility check to return conflict for specific version
mockCheckVersionCompatibility.mockImplementation((versionData) => { mockCheckNodeCompatibility.mockImplementation((versionData) => {
if (versionData.supported_os?.includes('linux')) { if (versionData.supported_os?.includes('linux')) {
return { return {
hasConflict: true, hasConflict: true,
@@ -404,7 +404,7 @@ describe('PackVersionSelectorPopover', () => {
await waitForPromises() await waitForPromises()
// Check that compatibility checking function was called // Check that compatibility checking function was called
expect(mockCheckVersionCompatibility).toHaveBeenCalled() expect(mockCheckNodeCompatibility).toHaveBeenCalled()
// The warning icon should be shown for incompatible versions // The warning icon should be shown for incompatible versions
const warningIcons = wrapper.findAll('.pi-exclamation-triangle') const warningIcons = wrapper.findAll('.pi-exclamation-triangle')
@@ -416,7 +416,7 @@ describe('PackVersionSelectorPopover', () => {
mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions) mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions)
// Mock compatibility check to return no conflicts // Mock compatibility check to return no conflicts
mockCheckVersionCompatibility.mockReturnValue({ mockCheckNodeCompatibility.mockReturnValue({
hasConflict: false, hasConflict: false,
conflicts: [] conflicts: []
}) })
@@ -425,7 +425,7 @@ describe('PackVersionSelectorPopover', () => {
await waitForPromises() await waitForPromises()
// Check that compatibility checking function was called // Check that compatibility checking function was called
expect(mockCheckVersionCompatibility).toHaveBeenCalled() expect(mockCheckNodeCompatibility).toHaveBeenCalled()
// The verified icon should be shown for compatible versions // The verified icon should be shown for compatible versions
// Look for the VerifiedIcon component or SVG elements // Look for the VerifiedIcon component or SVG elements
@@ -469,20 +469,24 @@ describe('PackVersionSelectorPopover', () => {
}) })
await waitForPromises() await waitForPromises()
// Clear previous calls from component mounting/rendering
mockCheckNodeCompatibility.mockClear()
// Trigger compatibility check by accessing getVersionCompatibility // Trigger compatibility check by accessing getVersionCompatibility
const vm = wrapper.vm as any const vm = wrapper.vm as any
vm.getVersionCompatibility('1.0.0') vm.getVersionCompatibility('1.0.0')
// Verify that checkVersionCompatibility was called with correct data // Verify that checkNodeCompatibility was called with correct data
// Since 1.0.0 is the latest version, it should use latest_version data // Since 1.0.0 is the latest version, it should use latest_version data
expect(mockCheckVersionCompatibility).toHaveBeenCalledWith({ expect(mockCheckNodeCompatibility).toHaveBeenCalledWith({
supported_os: ['windows', 'linux'], supported_os: ['windows', 'linux'],
supported_accelerators: ['CPU'], // latest_version data takes precedence supported_accelerators: ['CPU'], // latest_version data takes precedence
supported_comfyui_version: '>=0.1.0', supported_comfyui_version: '>=0.1.0',
supported_comfyui_frontend_version: '>=1.0.0', supported_comfyui_frontend_version: '>=1.0.0',
supported_python_version: '>=3.8', supported_python_version: '>=3.8',
is_banned: false, is_banned: false,
has_registry_data: true has_registry_data: true,
version: '1.0.0'
}) })
}) })
@@ -491,7 +495,7 @@ describe('PackVersionSelectorPopover', () => {
mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions) mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions)
// Mock compatibility check to return version conflicts // Mock compatibility check to return version conflicts
mockCheckVersionCompatibility.mockImplementation((versionData) => { mockCheckNodeCompatibility.mockImplementation((versionData) => {
const conflicts = [] const conflicts = []
if (versionData.supported_comfyui_version) { if (versionData.supported_comfyui_version) {
conflicts.push({ conflicts.push({
@@ -525,7 +529,7 @@ describe('PackVersionSelectorPopover', () => {
await waitForPromises() await waitForPromises()
// Check that compatibility checking function was called // Check that compatibility checking function was called
expect(mockCheckVersionCompatibility).toHaveBeenCalled() expect(mockCheckNodeCompatibility).toHaveBeenCalled()
// The warning icon should be shown for version incompatible packages // The warning icon should be shown for version incompatible packages
const warningIcons = wrapper.findAll('.pi-exclamation-triangle') const warningIcons = wrapper.findAll('.pi-exclamation-triangle')
@@ -559,76 +563,56 @@ describe('PackVersionSelectorPopover', () => {
const vm = wrapper.vm as any const vm = wrapper.vm as any
// Clear previous calls from component mounting/rendering
mockCheckNodeCompatibility.mockClear()
// Test latest version // Test latest version
vm.getVersionCompatibility('latest') vm.getVersionCompatibility('latest')
expect(mockCheckVersionCompatibility).toHaveBeenCalledWith({ expect(mockCheckNodeCompatibility).toHaveBeenCalledWith({
supported_os: ['windows'], supported_os: ['windows'],
supported_accelerators: ['CPU'], supported_accelerators: ['CPU'],
supported_comfyui_version: '>=0.1.0', supported_comfyui_version: '>=0.1.0',
supported_comfyui_frontend_version: '>=1.0.0', supported_comfyui_frontend_version: '>=1.0.0',
supported_python_version: '>=3.8', supported_python_version: '>=3.8',
is_banned: false, is_banned: false,
has_registry_data: true has_registry_data: true,
version: '1.0.0'
}) })
// Clear for next test call
mockCheckNodeCompatibility.mockClear()
// Test nightly version // Test nightly version
vm.getVersionCompatibility('nightly') vm.getVersionCompatibility('nightly')
expect(mockCheckVersionCompatibility).toHaveBeenCalledWith({ expect(mockCheckNodeCompatibility).toHaveBeenCalledWith({
id: 'test-pack',
name: 'Test Pack',
supported_os: ['windows'], supported_os: ['windows'],
supported_accelerators: ['CPU'], supported_accelerators: ['CPU'],
supported_comfyui_version: '>=0.1.0', supported_comfyui_version: '>=0.1.0',
supported_comfyui_frontend_version: '>=1.0.0', supported_comfyui_frontend_version: '>=1.0.0',
supported_python_version: undefined, repository: 'https://github.com/user/repo',
is_banned: false, has_registry_data: true,
has_registry_data: false latest_version: {
}) supported_os: ['windows'],
}) supported_accelerators: ['CPU'],
supported_python_version: '>=3.8',
it('shows python version conflict warnings', async () => { is_banned: false,
// Set up the mock for versions has_registry_data: true,
mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions) version: '1.0.0',
supported_comfyui_version: '>=0.1.0',
// Mock compatibility check to return python version conflicts supported_comfyui_frontend_version: '>=1.0.0'
mockCheckVersionCompatibility.mockImplementation((versionData) => {
if (versionData.supported_python_version) {
return {
hasConflict: true,
conflicts: [
{
type: 'python_version',
current_value: '3.8.0',
required_value: versionData.supported_python_version
}
]
}
} }
return { hasConflict: false, conflicts: [] }
}) })
const nodePackWithPythonRequirement = {
...mockNodePack,
supported_python_version: '>=3.9.0'
}
const wrapper = mountComponent({
props: { nodePack: nodePackWithPythonRequirement }
})
await waitForPromises()
// Check that compatibility checking function was called
expect(mockCheckVersionCompatibility).toHaveBeenCalled()
// The warning icon should be shown for python version incompatible packages
const warningIcons = wrapper.findAll('.pi-exclamation-triangle')
expect(warningIcons.length).toBeGreaterThan(0)
}) })
it('shows banned package warnings', async () => { it('shows banned package warnings', async () => {
// Set up the mock for versions // Set up the mock for versions
mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions) mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions)
// Mock compatibility check to return banned conflicts // Mock compatibility check to return banned conflicts
mockCheckVersionCompatibility.mockImplementation((versionData) => { mockCheckNodeCompatibility.mockImplementation((versionData) => {
if (versionData.is_banned === true) { if (versionData.is_banned === true) {
return { return {
hasConflict: true, hasConflict: true,
@@ -659,7 +643,7 @@ describe('PackVersionSelectorPopover', () => {
await waitForPromises() await waitForPromises()
// Check that compatibility checking function was called // Check that compatibility checking function was called
expect(mockCheckVersionCompatibility).toHaveBeenCalled() expect(mockCheckNodeCompatibility).toHaveBeenCalled()
// Open the dropdown to see the options // Open the dropdown to see the options
const select = wrapper.find('.p-select') const select = wrapper.find('.p-select')
@@ -684,13 +668,13 @@ describe('PackVersionSelectorPopover', () => {
mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions) mockGetPackVersions.mockResolvedValueOnce(defaultMockVersions)
// Mock compatibility check to return security pending conflicts // Mock compatibility check to return security pending conflicts
mockCheckVersionCompatibility.mockImplementation((versionData) => { mockCheckNodeCompatibility.mockImplementation((versionData) => {
if (versionData.has_registry_data === false) { if (versionData.has_registry_data === false) {
return { return {
hasConflict: true, hasConflict: true,
conflicts: [ conflicts: [
{ {
type: 'security_pending', type: 'pending',
current_value: 'no_registry_data', current_value: 'no_registry_data',
required_value: 'registry_data_available' required_value: 'registry_data_available'
} }
@@ -715,7 +699,7 @@ describe('PackVersionSelectorPopover', () => {
await waitForPromises() await waitForPromises()
// Check that compatibility checking function was called // Check that compatibility checking function was called
expect(mockCheckVersionCompatibility).toHaveBeenCalled() expect(mockCheckNodeCompatibility).toHaveBeenCalled()
// The warning icon should be shown for security pending packages // The warning icon should be shown for security pending packages
const warningIcons = wrapper.findAll('.pi-exclamation-triangle') const warningIcons = wrapper.findAll('.pi-exclamation-triangle')

View File

@@ -25,7 +25,7 @@
v-model="selectedVersion" v-model="selectedVersion"
option-label="label" option-label="label"
option-value="value" option-value="value"
:options="versionOptions" :options="processedVersionOptions"
:highlight-on-select="false" :highlight-on-select="false"
class="w-full max-h-[50vh] border-none shadow-none rounded-md" class="w-full max-h-[50vh] border-none shadow-none rounded-md"
:pt="{ :pt="{
@@ -35,19 +35,14 @@
<template #option="slotProps"> <template #option="slotProps">
<div class="flex justify-between items-center w-full p-1"> <div class="flex justify-between items-center w-full p-1">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<!-- Show no icon for nightly versions since compatibility is uncertain -->
<template v-if="slotProps.option.value === 'nightly'"> <template v-if="slotProps.option.value === 'nightly'">
<div class="w-4"></div> <div class="w-4"></div>
<!-- Empty space to maintain alignment -->
</template> </template>
<template v-else> <template v-else>
<i <i
v-if=" v-if="slotProps.option.hasConflict"
getVersionCompatibility(slotProps.option.value).hasConflict
"
v-tooltip="{ v-tooltip="{
value: getVersionCompatibility(slotProps.option.value) value: slotProps.option.conflictMessage,
.conflictMessage,
showDelay: 300 showDelay: 300
}" }"
class="pi pi-exclamation-triangle text-yellow-500" class="pi pi-exclamation-triangle text-yellow-500"
@@ -57,7 +52,7 @@
<span>{{ slotProps.option.label }}</span> <span>{{ slotProps.option.label }}</span>
</div> </div>
<i <i
v-if="selectedVersion === slotProps.option.value" v-if="slotProps.option.isSelected"
class="pi pi-check text-highlight" class="pi pi-check text-highlight"
/> />
</div> </div>
@@ -89,7 +84,7 @@ import { whenever } from '@vueuse/core'
import Button from 'primevue/button' import Button from 'primevue/button'
import Listbox from 'primevue/listbox' import Listbox from 'primevue/listbox'
import ProgressSpinner from 'primevue/progressspinner' import ProgressSpinner from 'primevue/progressspinner'
import { onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ContentDivider from '@/components/common/ContentDivider.vue' import ContentDivider from '@/components/common/ContentDivider.vue'
@@ -115,11 +110,12 @@ const emit = defineEmits<{
const { t } = useI18n() const { t } = useI18n()
const registryService = useComfyRegistryService() const registryService = useComfyRegistryService()
const managerStore = useComfyManagerStore() const managerStore = useComfyManagerStore()
const { checkVersionCompatibility } = useConflictDetection() const { checkNodeCompatibility } = useConflictDetection()
const isQueueing = ref(false) const isQueueing = ref(false)
const selectedVersion = ref<string>('latest') const selectedVersion = ref<string>('latest')
onMounted(() => { onMounted(() => {
const initialVersion = getInitialSelectedVersion() ?? 'latest' const initialVersion = getInitialSelectedVersion() ?? 'latest'
selectedVersion.value = selectedVersion.value =
@@ -130,14 +126,14 @@ onMounted(() => {
const getInitialSelectedVersion = () => { const getInitialSelectedVersion = () => {
if (!nodePack.id) return if (!nodePack.id) return
// If unclaimed, set selected version to nightly
if (nodePack.publisher?.name === 'Unclaimed')
return 'nightly' as ManagerComponents['schemas']['SelectedVersion']
// If node pack is installed, set selected version to the installed version // If node pack is installed, set selected version to the installed version
if (managerStore.isPackInstalled(nodePack.id)) if (managerStore.isPackInstalled(nodePack.id))
return managerStore.getInstalledPackVersion(nodePack.id) return managerStore.getInstalledPackVersion(nodePack.id)
// If unclaimed, set selected version to nightly
if (nodePack.publisher?.name === 'Unclaimed')
return 'nightly' as ManagerComponents['schemas']['SelectedVersion']
// If node pack is not installed, set selected version to latest // If node pack is not installed, set selected version to latest
return nodePack.latest_version?.version return nodePack.latest_version?.version
} }
@@ -235,76 +231,35 @@ const handleSubmit = async () => {
emit('submit') emit('submit')
} }
// Function to get version data (either from nodePack or fetchedVersions)
const getVersionData = (version: string) => { const getVersionData = (version: string) => {
// Use latest_version data for both "latest" and the actual latest version number
const latestVersionNumber = nodePack.latest_version?.version const latestVersionNumber = nodePack.latest_version?.version
const useLatestVersionData = const useLatestVersionData =
version === 'latest' || version === latestVersionNumber version === 'latest' || version === latestVersionNumber
if (useLatestVersionData) { if (useLatestVersionData) {
// For "latest" and the actual latest version number, use consistent data from latest_version
const latestVersionData = nodePack.latest_version const latestVersionData = nodePack.latest_version
return { return {
supported_os: latestVersionData?.supported_os ?? nodePack.supported_os, ...latestVersionData
supported_accelerators:
latestVersionData?.supported_accelerators ??
nodePack.supported_accelerators,
supported_comfyui_version:
latestVersionData?.supported_comfyui_version ??
nodePack.supported_comfyui_version,
supported_comfyui_frontend_version:
latestVersionData?.supported_comfyui_frontend_version ??
nodePack.supported_comfyui_frontend_version
} }
} }
if (version === 'nightly') {
// For nightly, we can't determine exact compatibility since it's dynamic Git HEAD
// But we can assume it's generally compatible (nightly = latest development)
// Use nodePack data as fallback, but nightly is typically more permissive
return {
supported_os: nodePack.supported_os || [], // If no OS restrictions, assume all supported
supported_accelerators: nodePack.supported_accelerators || [], // If no accelerator restrictions, assume all supported
supported_comfyui_version: nodePack.supported_comfyui_version, // Use latest known requirement
supported_comfyui_frontend_version:
nodePack.supported_comfyui_frontend_version // Use latest known requirement
}
}
// For specific versions, find in fetched versions
const versionData = fetchedVersions.value.find((v) => v.version === version) const versionData = fetchedVersions.value.find((v) => v.version === version)
if (versionData) { if (versionData) {
return { return {
supported_os: versionData.supported_os, ...versionData
supported_accelerators: versionData.supported_accelerators,
supported_comfyui_version: versionData.supported_comfyui_version,
supported_comfyui_frontend_version:
versionData.supported_comfyui_frontend_version
} }
} }
// Fallback to nodePack data // Fallback to nodePack data
return { return {
supported_os: nodePack.supported_os, ...nodePack
supported_accelerators: nodePack.supported_accelerators,
supported_comfyui_version: nodePack.supported_comfyui_version,
supported_comfyui_frontend_version:
nodePack.supported_comfyui_frontend_version
} }
} }
// Function to check version compatibility using centralized logic
const checkVersionCompatibilityLocal = (
versionData: ReturnType<typeof getVersionData>
) => {
return checkVersionCompatibility(versionData)
}
// Main function to get version compatibility info // Main function to get version compatibility info
const getVersionCompatibility = (version: string) => { const getVersionCompatibility = (version: string) => {
const versionData = getVersionData(version) const versionData = getVersionData(version)
const compatibility = checkVersionCompatibilityLocal(versionData) const compatibility = checkNodeCompatibility(versionData)
const conflictMessage = compatibility.hasConflict const conflictMessage = compatibility.hasConflict
? getJoinedConflictMessages(compatibility.conflicts, t) ? getJoinedConflictMessages(compatibility.conflicts, t)
@@ -315,4 +270,33 @@ const getVersionCompatibility = (version: string) => {
conflictMessage conflictMessage
} }
} }
// Helper to determine if an option is selected.
const isOptionSelected = (optionValue: string) => {
if (selectedVersion.value === optionValue) {
return true
}
if (
optionValue === 'latest' &&
selectedVersion.value === nodePack.latest_version?.version
) {
return true
}
return false
}
// Checks if an option is selected, treating 'latest' as an alias for the actual latest version number.
const processedVersionOptions = computed(() => {
return versionOptions.value.map((option) => {
const compatibility = getVersionCompatibility(option.value)
const isSelected = isOptionSelected(option.value)
return {
...option,
hasConflict: compatibility.hasConflict,
conflictMessage: compatibility.conflictMessage,
isSelected: isSelected
}
})
})
</script> </script>

View File

@@ -17,36 +17,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { inject, ref } from 'vue' import { inject, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import PackActionButton from '@/components/dialog/content/manager/button/PackActionButton.vue' import PackActionButton from '@/components/dialog/content/manager/button/PackActionButton.vue'
import { useConflictAcknowledgment } from '@/composables/useConflictAcknowledgment'
import { useConflictDetection } from '@/composables/useConflictDetection'
import { useDialogService } from '@/services/dialogService'
import { useComfyManagerStore } from '@/stores/comfyManagerStore' import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { IsInstallingKey } from '@/types/comfyManagerTypes' import { IsInstallingKey } from '@/types/comfyManagerTypes'
import type { components } from '@/types/comfyRegistryTypes' import type { components } from '@/types/comfyRegistryTypes'
import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes'
import { components as ManagerComponents } from '@/types/generatedManagerTypes' import { components as ManagerComponents } from '@/types/generatedManagerTypes'
type NodePack = components['schemas']['Node'] type NodePack = components['schemas']['Node']
const { nodePacks, variant, label, hasConflict, skipConflictCheck } = const { nodePacks, variant, label, hasConflict } = defineProps<{
defineProps<{ nodePacks: NodePack[]
nodePacks: NodePack[] variant?: 'default' | 'black'
variant?: 'default' | 'black' label?: string
label?: string hasConflict?: boolean
hasConflict?: boolean }>()
skipConflictCheck?: boolean
}>()
const { t } = useI18n()
const isInstalling = inject(IsInstallingKey, ref(false)) const isInstalling = inject(IsInstallingKey, ref(false))
const managerStore = useComfyManagerStore() const managerStore = useComfyManagerStore()
const { showNodeConflictDialog } = useDialogService()
const { checkVersionCompatibility } = useConflictDetection()
const { acknowledgeConflict, isConflictAcknowledged } =
useConflictAcknowledgment()
const onClick = (): void => { const onClick = (): void => {
isInstalling.value = true isInstalling.value = true
@@ -78,81 +66,37 @@ const createPayload = (
const installPack = (item: NodePack) => const installPack = (item: NodePack) =>
managerStore.installPack.call(createPayload(item)) managerStore.installPack.call(createPayload(item))
// Function to check compatibility for uninstalled packages using centralized logic
function checkUninstalledPackageCompatibility(
pack: NodePack
): ConflictDetectionResult | null {
const compatibility = checkVersionCompatibility({
supported_os: pack.supported_os,
supported_accelerators: pack.supported_accelerators,
supported_comfyui_version: pack.supported_comfyui_version,
supported_comfyui_frontend_version: pack.supported_comfyui_frontend_version
})
if (compatibility.hasConflict) {
return {
package_id: pack.id || 'unknown',
package_name: pack.name || 'unknown',
has_conflict: true,
conflicts: compatibility.conflicts,
is_compatible: false
}
}
return null
}
const installAllPacks = async () => { const installAllPacks = async () => {
if (!nodePacks?.length) return if (!nodePacks?.length) return
// TBD Install Anyway modal
// if (hasConflict && !isConflictAcknowledged) {
// showNodeConflictDialog({
// conflictedPackages: nodePacks,
// buttonText: t('manager.conflicts.installAnyway'),
// onButtonClick: async () => {
// // User chose "Install Anyway" - acknowledge all conflicts and proceed
// for (const conflictedPack of packsWithConflicts) {
// for (const conflict of conflictedPack.conflicts) {
// acknowledgeConflict(
// conflictedPack.package_id,
// conflict.type,
// '0.1.0'
// )
// }
// }
// // Proceed with installation
// await performInstallation(uninstalledPacks)
// }
// })
// return
// }
const uninstalledPacks = nodePacks.filter( const uninstalledPacks = nodePacks.filter(
(pack) => !managerStore.isPackInstalled(pack.id) (pack) => !managerStore.isPackInstalled(pack.id)
) )
if (!uninstalledPacks.length) return if (!uninstalledPacks.length) return
// Skip conflict check if explicitly requested (e.g., from "Install Anyway" button)
if (!skipConflictCheck) {
// Check for conflicts in uninstalled packages
const packsWithConflicts: ConflictDetectionResult[] = []
for (const pack of uninstalledPacks) {
const conflicts = checkUninstalledPackageCompatibility(pack)
if (conflicts) {
// Check if conflicts have been acknowledged
const hasUnacknowledgedConflicts = conflicts.conflicts.some(
(conflict) => !isConflictAcknowledged(pack.id || '', conflict.type)
)
if (hasUnacknowledgedConflicts) {
packsWithConflicts.push(conflicts)
}
}
}
// If there are unacknowledged conflicts, show modal
if (packsWithConflicts.length > 0) {
showNodeConflictDialog({
conflictedPackages: packsWithConflicts,
buttonText: t('manager.conflicts.installAnyway'),
onButtonClick: async () => {
// User chose "Install Anyway" - acknowledge all conflicts and proceed
for (const conflictedPack of packsWithConflicts) {
for (const conflict of conflictedPack.conflicts) {
acknowledgeConflict(
conflictedPack.package_id,
conflict.type,
'0.1.0'
)
}
}
// Proceed with installation
await performInstallation(uninstalledPacks)
}
})
return
}
}
// No conflicts or conflicts acknowledged - proceed with installation // No conflicts or conflicts acknowledged - proceed with installation
await performInstallation(uninstalledPacks) await performInstallation(uninstalledPacks)
} }

View File

@@ -94,7 +94,7 @@ whenever(isInstalled, () => {
isInstalling.value = false isInstalling.value = false
}) })
const { checkVersionCompatibility } = useConflictDetection() const { checkNodeCompatibility } = useConflictDetection()
const { getConflictsForPackageByID } = useConflictDetectionStore() const { getConflictsForPackageByID } = useConflictDetectionStore()
const { t, d, n } = useI18n() const { t, d, n } = useI18n()
@@ -107,22 +107,12 @@ const conflictResult = computed((): ConflictDetectionResult | null => {
} }
// For non-installed packages, perform compatibility check // For non-installed packages, perform compatibility check
const compatibility = checkVersionCompatibility({ const compatibility = checkNodeCompatibility(nodePack)
supported_os: nodePack.supported_os,
supported_accelerators: nodePack.supported_accelerators,
supported_comfyui_version: nodePack.supported_comfyui_version,
supported_comfyui_frontend_version:
nodePack.supported_comfyui_frontend_version
// TODO: Add when API provides these fields
// supported_python_version: nodePack.supported_python_version,
// is_banned: nodePack.is_banned,
// has_registry_data: nodePack.has_registry_data
})
if (compatibility.hasConflict && nodePack.id && nodePack.name) { if (compatibility.hasConflict) {
return { return {
package_id: nodePack.id, package_id: nodePack.id || '',
package_name: nodePack.name, package_name: nodePack.name || '',
has_conflict: true, has_conflict: true,
conflicts: compatibility.conflicts, conflicts: compatibility.conflicts,
is_compatible: false is_compatible: false
@@ -133,7 +123,7 @@ const conflictResult = computed((): ConflictDetectionResult | null => {
}) })
const hasCompatibilityIssues = computed(() => { const hasCompatibilityIssues = computed(() => {
return isInstalled.value && conflictResult.value?.has_conflict ? true : false return conflictResult.value?.has_conflict
}) })
const infoItems = computed<InfoItem[]>(() => [ const infoItems = computed<InfoItem[]>(() => [

View File

@@ -5,7 +5,7 @@
:key="index" :key="index"
class="p-3 bg-yellow-800/20 rounded-md" class="p-3 bg-yellow-800/20 rounded-md"
> >
<div class="text-sm"> <div class="text-sm break-words">
{{ getConflictMessage(conflict, $t) }} {{ getConflictMessage(conflict, $t) }}
</div> </div>
</div> </div>

View File

@@ -45,7 +45,7 @@ const formattedDownloads = computed(() =>
) )
const { getConflictsForPackageByID } = useConflictDetectionStore() const { getConflictsForPackageByID } = useConflictDetectionStore()
const { checkVersionCompatibility } = useConflictDetection() const { checkNodeCompatibility } = useConflictDetection()
const hasConflict = computed(() => { const hasConflict = computed(() => {
if (!nodePack.id) return false if (!nodePack.id) return false
@@ -60,21 +60,7 @@ const hasConflict = computed(() => {
} }
// For uninstalled packages, check compatibility directly // For uninstalled packages, check compatibility directly
if ( const compatibility = checkNodeCompatibility(nodePack)
nodePack.supported_os || return compatibility.hasConflict
nodePack.supported_accelerators ||
nodePack.supported_comfyui_version
) {
const compatibility = checkVersionCompatibility({
supported_os: nodePack.supported_os,
supported_accelerators: nodePack.supported_accelerators,
supported_comfyui_version: nodePack.supported_comfyui_version,
supported_comfyui_frontend_version:
nodePack.supported_comfyui_frontend_version
})
return compatibility.hasConflict
}
return false
}) })
</script> </script>

View File

@@ -17,15 +17,14 @@ import type {
ConflictDetectionResult, ConflictDetectionResult,
ConflictDetectionSummary, ConflictDetectionSummary,
ConflictType, ConflictType,
Node,
NodePackRequirements, NodePackRequirements,
SupportedAccelerator,
SupportedOS,
SystemEnvironment SystemEnvironment
} from '@/types/conflictDetectionTypes' } from '@/types/conflictDetectionTypes'
import { import {
cleanVersion, cleanVersion,
satisfiesVersion, satisfiesVersion,
checkVersionCompatibility as utilCheckVersionCompatibility utilCheckVersionCompatibility
} from '@/utils/versionUtil' } from '@/utils/versionUtil'
/** /**
@@ -33,10 +32,8 @@ import {
* Error-resilient and asynchronous to avoid affecting other components. * Error-resilient and asynchronous to avoid affecting other components.
*/ */
export function useConflictDetection() { export function useConflictDetection() {
// Store references
const managerStore = useComfyManagerStore() const managerStore = useComfyManagerStore()
// Use installed packs composable instead of direct API calls
const { const {
startFetchInstalled, startFetchInstalled,
installedPacks, installedPacks,
@@ -44,15 +41,12 @@ export function useConflictDetection() {
isReady: installedPacksReady isReady: installedPacksReady
} = useInstalledPacks() } = useInstalledPacks()
// State management
const isDetecting = ref(false) const isDetecting = ref(false)
const lastDetectionTime = ref<string | null>(null) const lastDetectionTime = ref<string | null>(null)
const detectionError = ref<string | null>(null) const detectionError = ref<string | null>(null)
// System environment information
const systemEnvironment = ref<SystemEnvironment | null>(null) const systemEnvironment = ref<SystemEnvironment | null>(null)
// Conflict detection results
const detectionResults = ref<ConflictDetectionResult[]>([]) const detectionResults = ref<ConflictDetectionResult[]>([])
// Store merged conflicts separately for testing // Store merged conflicts separately for testing
const storedMergedConflicts = ref<ConflictDetectionResult[]>([]) const storedMergedConflicts = ref<ConflictDetectionResult[]>([])
@@ -61,13 +55,10 @@ export function useConflictDetection() {
// Registry API request cancellation // Registry API request cancellation
const abortController = ref<AbortController | null>(null) const abortController = ref<AbortController | null>(null)
// Acknowledgment management
const acknowledgment = useConflictAcknowledgment() const acknowledgment = useConflictAcknowledgment()
// Store management
const conflictStore = useConflictDetectionStore() const conflictStore = useConflictDetectionStore()
// Computed properties - use store instead of local state
const hasConflicts = computed(() => conflictStore.hasConflicts) const hasConflicts = computed(() => conflictStore.hasConflicts)
const conflictedPackages = computed(() => { const conflictedPackages = computed(() => {
return conflictStore.conflictedPackages return conflictStore.conflictedPackages
@@ -97,7 +88,6 @@ export function useConflictDetection() {
// Extract system information from system stats // Extract system information from system stats
const systemStats = systemStatsStore.systemStats const systemStats = systemStatsStore.systemStats
const comfyuiVersion = systemStats?.system?.comfyui_version || 'unknown' const comfyuiVersion = systemStats?.system?.comfyui_version || 'unknown'
const pythonVersion = systemStats?.system?.python_version || 'unknown'
// Use system stats for OS detection (more accurate than browser detection) // Use system stats for OS detection (more accurate than browser detection)
const systemOS = systemStats?.system?.os || 'unknown' const systemOS = systemStats?.system?.os || 'unknown'
@@ -118,7 +108,6 @@ export function useConflictDetection() {
frontendVersion.status === 'fulfilled' frontendVersion.status === 'fulfilled'
? frontendVersion.value ? frontendVersion.value
: 'unknown', : 'unknown',
python_version: pythonVersion,
// Platform information (from system stats) // Platform information (from system stats)
os: detectedOS, os: detectedOS,
@@ -159,7 +148,6 @@ export function useConflictDetection() {
const fallbackEnvironment: SystemEnvironment = { const fallbackEnvironment: SystemEnvironment = {
comfyui_version: 'unknown', comfyui_version: 'unknown',
frontend_version: frontendVersion, frontend_version: frontendVersion,
python_version: 'unknown',
os: detectOSFromSystemStats(navigator.platform), os: detectOSFromSystemStats(navigator.platform),
platform_details: navigator.platform, platform_details: navigator.platform,
architecture: getArchitecture(), architecture: getArchitecture(),
@@ -283,18 +271,12 @@ export function useConflictDetection() {
supported_comfyui_frontend_version: supported_comfyui_frontend_version:
versionData.supported_comfyui_frontend_version, versionData.supported_comfyui_frontend_version,
supported_os: normalizeOSValues(versionData.supported_os), supported_os: normalizeOSValues(versionData.supported_os),
supported_accelerators: supported_accelerators: versionData.supported_accelerators,
versionData.supported_accelerators as SupportedAccelerator[],
dependencies: versionData.dependencies || [],
// Status information // Status information
registry_status: undefined, // Node status - not critical for conflict detection
version_status: versionData.status, version_status: versionData.status,
is_banned: versionData.status === 'NodeVersionStatusBanned', is_banned: versionData.status === 'NodeVersionStatusBanned',
is_pending: versionData.status === 'NodeVersionStatusPending'
// Metadata
registry_fetch_time: new Date().toISOString(),
has_registry_data: true
} }
requirements.push(requirement) requirements.push(requirement)
@@ -310,8 +292,7 @@ export function useConflictDetection() {
installed_version: installedVersion, installed_version: installedVersion,
is_enabled: isEnabled, is_enabled: isEnabled,
is_banned: false, is_banned: false,
registry_fetch_time: new Date().toISOString(), is_pending: false
has_registry_data: false
} }
requirements.push(fallbackRequirement) requirements.push(fallbackRequirement)
@@ -350,10 +331,7 @@ export function useConflictDetection() {
} }
// 1. ComfyUI version conflict check // 1. ComfyUI version conflict check
if ( if (!isCompatibleWithAll(packageReq.supported_comfyui_version)) {
packageReq.has_registry_data &&
!isCompatibleWithAll(packageReq.supported_comfyui_version)
) {
const versionConflict = checkVersionConflict( const versionConflict = checkVersionConflict(
'comfyui_version', 'comfyui_version',
sysEnv.comfyui_version, sysEnv.comfyui_version,
@@ -363,10 +341,7 @@ export function useConflictDetection() {
} }
// 2. Frontend version conflict check // 2. Frontend version conflict check
if ( if (!isCompatibleWithAll(packageReq.supported_comfyui_frontend_version)) {
packageReq.has_registry_data &&
!isCompatibleWithAll(packageReq.supported_comfyui_frontend_version)
) {
const versionConflict = checkVersionConflict( const versionConflict = checkVersionConflict(
'frontend_version', 'frontend_version',
sysEnv.frontend_version, sysEnv.frontend_version,
@@ -376,19 +351,13 @@ export function useConflictDetection() {
} }
// 3. OS compatibility check // 3. OS compatibility check
if ( if (!isCompatibleWithAll(packageReq.supported_os)) {
packageReq.has_registry_data &&
!isCompatibleWithAll(packageReq.supported_os)
) {
const osConflict = checkOSConflict(packageReq.supported_os!, sysEnv.os) const osConflict = checkOSConflict(packageReq.supported_os!, sysEnv.os)
if (osConflict) conflicts.push(osConflict) if (osConflict) conflicts.push(osConflict)
} }
// 4. Accelerator compatibility check // 4. Accelerator compatibility check
if ( if (!isCompatibleWithAll(packageReq.supported_accelerators)) {
packageReq.has_registry_data &&
!isCompatibleWithAll(packageReq.supported_accelerators)
) {
const acceleratorConflict = checkAcceleratorConflict( const acceleratorConflict = checkAcceleratorConflict(
packageReq.supported_accelerators!, packageReq.supported_accelerators!,
sysEnv.available_accelerators sysEnv.available_accelerators
@@ -403,9 +372,9 @@ export function useConflictDetection() {
} }
// 6. Registry data availability check using shared logic // 6. Registry data availability check using shared logic
const securityConflict = checkSecurityStatus(packageReq.has_registry_data) const pendingConflict = checkPendingStatus(packageReq.is_pending)
if (securityConflict) { if (pendingConflict) {
conflicts.push(securityConflict) conflicts.push(pendingConflict)
} }
// Generate result // Generate result
@@ -481,11 +450,13 @@ export function useConflictDetection() {
* @param importFailInfo Import failure data from Manager API * @param importFailInfo Import failure data from Manager API
* @returns Array of conflict detection results for failed imports * @returns Array of conflict detection results for failed imports
*/ */
function detectPythonImportConflicts( function detectImportFailConflicts(
importFailInfo: Record<string, any> importFailInfo: Record<string, { msg: string; name: string; path: string }>
): ConflictDetectionResult[] { ): ConflictDetectionResult[] {
const results: ConflictDetectionResult[] = [] const results: ConflictDetectionResult[] = []
console.log('@@@', importFailInfo)
if (!importFailInfo || typeof importFailInfo !== 'object') { if (!importFailInfo || typeof importFailInfo !== 'object') {
return results return results
} }
@@ -497,22 +468,17 @@ export function useConflictDetection() {
const errorMsg = failureInfo.msg || 'Unknown import error' const errorMsg = failureInfo.msg || 'Unknown import error'
const modulePath = failureInfo.path || '' const modulePath = failureInfo.path || ''
// Parse error message to extract missing dependency
const missingDependency = extractMissingDependency(errorMsg)
const conflicts: ConflictDetail[] = [
{
type: 'python_dependency',
current_value: 'missing',
required_value: missingDependency
}
]
results.push({ results.push({
package_id: packageId, package_id: packageId,
package_name: packageId, package_name: packageId,
has_conflict: true, has_conflict: true,
conflicts, conflicts: [
{
type: 'import_failed',
current_value: 'installed',
required_value: failureInfo.msg
}
],
is_compatible: false is_compatible: false
}) })
@@ -520,8 +486,7 @@ export function useConflictDetection() {
`[ConflictDetection] Python import failure detected for ${packageId}:`, `[ConflictDetection] Python import failure detected for ${packageId}:`,
{ {
path: modulePath, path: modulePath,
error: errorMsg, error: errorMsg
missingDependency
} }
) )
} }
@@ -579,7 +544,7 @@ export function useConflictDetection() {
// 4. Detect Python import failures // 4. Detect Python import failures
const importFailInfo = await fetchImportFailInfo() const importFailInfo = await fetchImportFailInfo()
const importFailResults = detectPythonImportConflicts(importFailInfo) const importFailResults = detectImportFailConflicts(importFailInfo)
console.log( console.log(
'[ConflictDetection] Python import failures detected:', '[ConflictDetection] Python import failures detected:',
importFailResults importFailResults
@@ -769,18 +734,12 @@ export function useConflictDetection() {
} }
/** /**
* Check compatibility for a specific version of a package. * Check compatibility for a node.
* Used by components like PackVersionSelectorPopover. * Used by components like PackVersionSelectorPopover.
*/ */
function checkVersionCompatibility(versionData: { function checkNodeCompatibility(
supported_os?: string[] node: Node | components['schemas']['NodeVersion']
supported_accelerators?: string[] ) {
supported_comfyui_version?: string
supported_comfyui_frontend_version?: string
supported_python_version?: string
is_banned?: boolean
has_registry_data?: boolean
}) {
const systemStatsStore = useSystemStatsStore() const systemStatsStore = useSystemStatsStore()
const systemStats = systemStatsStore.systemStats const systemStats = systemStatsStore.systemStats
if (!systemStats) return { hasConflict: false, conflicts: [] } if (!systemStats) return { hasConflict: false, conflicts: [] }
@@ -788,34 +747,28 @@ export function useConflictDetection() {
const conflicts: ConflictDetail[] = [] const conflicts: ConflictDetail[] = []
// Check OS compatibility using centralized function // Check OS compatibility using centralized function
if (versionData.supported_os && versionData.supported_os.length > 0) { if (node.supported_os && node.supported_os.length > 0) {
const currentOS = systemStats.system?.os || 'unknown' const currentOS = systemStats.system?.os || 'unknown'
const osConflict = checkOSConflict( const osConflict = checkOSConflict(node.supported_os, currentOS)
versionData.supported_os as SupportedOS[],
currentOS as SupportedOS
)
if (osConflict) { if (osConflict) {
conflicts.push(osConflict) conflicts.push(osConflict)
} }
} }
// Check accelerator compatibility using centralized function // Check accelerator compatibility using centralized function
if ( if (node.supported_accelerators && node.supported_accelerators.length > 0) {
versionData.supported_accelerators &&
versionData.supported_accelerators.length > 0
) {
// Extract available accelerators from system stats // Extract available accelerators from system stats
const acceleratorInfo = extractAcceleratorInfo(systemStats) const acceleratorInfo = extractAcceleratorInfo(systemStats)
const availableAccelerators: SupportedAccelerator[] = [] const availableAccelerators: Node['supported_accelerators'] = []
acceleratorInfo.available.forEach((accel) => { acceleratorInfo.available?.forEach((accel) => {
if (accel === 'CUDA') availableAccelerators.push('CUDA') if (accel === 'CUDA') availableAccelerators.push('CUDA')
if (accel === 'Metal') availableAccelerators.push('Metal') if (accel === 'Metal') availableAccelerators.push('Metal')
if (accel === 'CPU') availableAccelerators.push('CPU') if (accel === 'CPU') availableAccelerators.push('CPU')
}) })
const acceleratorConflict = checkAcceleratorConflict( const acceleratorConflict = checkAcceleratorConflict(
versionData.supported_accelerators as SupportedAccelerator[], node.supported_accelerators,
availableAccelerators availableAccelerators
) )
if (acceleratorConflict) { if (acceleratorConflict) {
@@ -824,13 +777,13 @@ export function useConflictDetection() {
} }
// Check ComfyUI version compatibility // Check ComfyUI version compatibility
if (versionData.supported_comfyui_version) { if (node.supported_comfyui_version) {
const currentComfyUIVersion = systemStats.system?.comfyui_version const currentComfyUIVersion = systemStats.system?.comfyui_version
if (currentComfyUIVersion && currentComfyUIVersion !== 'unknown') { if (currentComfyUIVersion && currentComfyUIVersion !== 'unknown') {
const versionConflict = utilCheckVersionCompatibility( const versionConflict = utilCheckVersionCompatibility(
'comfyui_version', 'comfyui_version',
currentComfyUIVersion, currentComfyUIVersion,
versionData.supported_comfyui_version node.supported_comfyui_version
) )
if (versionConflict) { if (versionConflict) {
conflicts.push(versionConflict) conflicts.push(versionConflict)
@@ -839,28 +792,13 @@ export function useConflictDetection() {
} }
// Check ComfyUI Frontend version compatibility // Check ComfyUI Frontend version compatibility
if (versionData.supported_comfyui_frontend_version) { if (node.supported_comfyui_frontend_version) {
const currentFrontendVersion = config.app_version const currentFrontendVersion = config.app_version
if (currentFrontendVersion && currentFrontendVersion !== 'unknown') { if (currentFrontendVersion && currentFrontendVersion !== 'unknown') {
const versionConflict = utilCheckVersionCompatibility( const versionConflict = utilCheckVersionCompatibility(
'frontend_version', 'frontend_version',
currentFrontendVersion, currentFrontendVersion,
versionData.supported_comfyui_frontend_version node.supported_comfyui_frontend_version
)
if (versionConflict) {
conflicts.push(versionConflict)
}
}
}
// Check Python version compatibility
if (versionData.supported_python_version) {
const currentPythonVersion = systemStats.system?.python_version
if (currentPythonVersion && currentPythonVersion !== 'unknown') {
const versionConflict = utilCheckVersionCompatibility(
'python_version',
currentPythonVersion,
versionData.supported_python_version
) )
if (versionConflict) { if (versionConflict) {
conflicts.push(versionConflict) conflicts.push(versionConflict)
@@ -869,15 +807,20 @@ export function useConflictDetection() {
} }
// Check banned package status using shared logic // Check banned package status using shared logic
const bannedConflict = checkBannedStatus(versionData.is_banned) const bannedConflict = checkBannedStatus(
node.status === 'NodeStatusBanned' ||
node.status === 'NodeVersionStatusBanned'
)
if (bannedConflict) { if (bannedConflict) {
conflicts.push(bannedConflict) conflicts.push(bannedConflict)
} }
// Check security/registry data status using shared logic // Check pending status using shared logic
const securityConflict = checkSecurityStatus(versionData.has_registry_data) const pendingConflict = checkPendingStatus(
if (securityConflict) { node.status === 'NodeVersionStatusPending'
conflicts.push(securityConflict) )
if (pendingConflict) {
conflicts.push(pendingConflict)
} }
return { return {
@@ -917,7 +860,7 @@ export function useConflictDetection() {
acknowledgePackageConflict, acknowledgePackageConflict,
// Helper functions for other components // Helper functions for other components
checkVersionCompatibility checkNodeCompatibility
} }
} }
@@ -973,44 +916,6 @@ function mergeConflictsByPackageName(
return Array.from(mergedMap.values()) return Array.from(mergedMap.values())
} }
/**
* Extracts missing dependency name from Python error message.
* @param errorMsg Error message from Python import failure
* @returns Name of missing dependency
*/
function extractMissingDependency(errorMsg: string): string {
// Try to extract module name from common error patterns
// Pattern 1: "ModuleNotFoundError: No module named 'module_name'"
const moduleNotFoundMatch = errorMsg.match(/No module named '([^']+)'/)
if (moduleNotFoundMatch) {
return moduleNotFoundMatch[1]
}
// Pattern 2: "ImportError: cannot import name 'something' from 'module_name'"
const importErrorMatch = errorMsg.match(
/cannot import name '[^']+' from '([^']+)'/
)
if (importErrorMatch) {
return importErrorMatch[1]
}
// Pattern 3: "from module_name import something" in the traceback
const fromImportMatch = errorMsg.match(/from ([a-zA-Z_][a-zA-Z0-9_]*) import/)
if (fromImportMatch) {
return fromImportMatch[1]
}
// Pattern 4: "import module_name" in the traceback
const importMatch = errorMsg.match(/import ([a-zA-Z_][a-zA-Z0-9_]*)/)
if (importMatch) {
return importMatch[1]
}
// Fallback: return generic message
return 'unknown dependency'
}
/** /**
* Fetches frontend version from config. * Fetches frontend version from config.
* @returns Promise that resolves to frontend version string * @returns Promise that resolves to frontend version string
@@ -1044,7 +949,9 @@ function getArchitecture(): string {
* @param osValues OS values from Registry API * @param osValues OS values from Registry API
* @returns Normalized OS values * @returns Normalized OS values
*/ */
function normalizeOSValues(osValues: string[] | undefined): SupportedOS[] { function normalizeOSValues(
osValues: string[] | undefined
): Node['supported_os'] {
if (!osValues || osValues.length === 0) { if (!osValues || osValues.length === 0) {
return [] return []
} }
@@ -1065,7 +972,7 @@ function normalizeOSValues(osValues: string[] | undefined): SupportedOS[] {
} }
// Return as-is if it matches standard format // Return as-is if it matches standard format
return os as SupportedOS return os
}) })
} }
@@ -1078,7 +985,7 @@ function normalizeOSValues(osValues: string[] | undefined): SupportedOS[] {
function detectOSFromSystemStats( function detectOSFromSystemStats(
systemOS: string, systemOS: string,
systemStats?: SystemStats | null systemStats?: SystemStats | null
): SupportedOS { ): string {
const os = systemOS.toLowerCase() const os = systemOS.toLowerCase()
// Handle specific OS strings (return Registry standard format) // Handle specific OS strings (return Registry standard format)
@@ -1100,14 +1007,7 @@ function detectOSFromSystemStats(
} }
} }
// Method 2: Check Python version string for platform hints // Method 2: Check user agent as fallback
if (systemStats?.system?.python_version) {
const pythonVersion = systemStats.system.python_version.toLowerCase()
if (pythonVersion.includes('darwin')) return 'macOS'
if (pythonVersion.includes('linux')) return 'Linux'
}
// Method 3: Check user agent as fallback
const userAgent = navigator.userAgent.toLowerCase() const userAgent = navigator.userAgent.toLowerCase()
if (userAgent.includes('mac')) return 'macOS' if (userAgent.includes('mac')) return 'macOS'
if (userAgent.includes('linux')) return 'Linux' if (userAgent.includes('linux')) return 'Linux'
@@ -1187,14 +1087,14 @@ function extractArchitectureFromSystemStats(
* @returns Accelerator information object * @returns Accelerator information object
*/ */
function extractAcceleratorInfo(systemStats: SystemStats | null): { function extractAcceleratorInfo(systemStats: SystemStats | null): {
available: SupportedAccelerator[] available: Node['supported_accelerators']
primary: SupportedAccelerator primary: string
memory_mb?: number memory_mb?: number
} { } {
try { try {
if (systemStats?.devices && systemStats.devices.length > 0) { if (systemStats?.devices && systemStats.devices.length > 0) {
const accelerators = new Set<SupportedAccelerator>() const accelerators = new Set<string>()
let primaryDevice: SupportedAccelerator = 'CPU' let primaryDevice: string = 'CPU'
let totalMemory = 0 let totalMemory = 0
let maxDevicePriority = 0 let maxDevicePriority = 0
@@ -1226,7 +1126,7 @@ function extractAcceleratorInfo(systemStats: SystemStats | null): {
const priority = getDevicePriority(deviceType) const priority = getDevicePriority(deviceType)
// Map device type to SupportedAccelerator (Registry standard format) // Map device type to SupportedAccelerator (Registry standard format)
let acceleratorType: SupportedAccelerator = 'CPU' let acceleratorType: string = 'CPU'
if (deviceType === 'cuda') { if (deviceType === 'cuda') {
acceleratorType = 'CUDA' acceleratorType = 'CUDA'
} else if (deviceType === 'mps') { } else if (deviceType === 'mps') {
@@ -1329,17 +1229,17 @@ function checkVersionConflict(
* Checks for OS compatibility conflicts. * Checks for OS compatibility conflicts.
*/ */
function checkOSConflict( function checkOSConflict(
supportedOS: SupportedOS[], supportedOS: Node['supported_os'],
currentOS: SupportedOS currentOS: string
): ConflictDetail | null { ): ConflictDetail | null {
if (supportedOS.includes('any') || supportedOS.includes(currentOS)) { if (supportedOS?.includes('any') || supportedOS?.includes(currentOS)) {
return null return null
} }
return { return {
type: 'os', type: 'os',
current_value: currentOS, current_value: currentOS,
required_value: supportedOS.join(', ') required_value: supportedOS ? supportedOS?.join(', ') : ''
} }
} }
@@ -1347,20 +1247,24 @@ function checkOSConflict(
* Checks for accelerator compatibility conflicts. * Checks for accelerator compatibility conflicts.
*/ */
function checkAcceleratorConflict( function checkAcceleratorConflict(
supportedAccelerators: SupportedAccelerator[], supportedAccelerators: Node['supported_accelerators'],
availableAccelerators: SupportedAccelerator[] availableAccelerators: Node['supported_accelerators']
): ConflictDetail | null { ): ConflictDetail | null {
if ( if (
supportedAccelerators.includes('any') || supportedAccelerators?.includes('any') ||
supportedAccelerators.some((acc) => availableAccelerators.includes(acc)) supportedAccelerators?.some((acc) => availableAccelerators?.includes(acc))
) { ) {
return null return null
} }
return { return {
type: 'accelerator', type: 'accelerator',
current_value: availableAccelerators.join(', '), current_value: availableAccelerators
required_value: supportedAccelerators.join(', ') ? availableAccelerators.join(', ')
: '',
required_value: supportedAccelerators
? supportedAccelerators.join(', ')
: ''
} }
} }
@@ -1379,14 +1283,14 @@ function checkBannedStatus(isBanned?: boolean): ConflictDetail | null {
} }
/** /**
* Checks for security/registry data availability conflicts. * Checks for pending package status conflicts.
*/ */
function checkSecurityStatus(hasRegistryData?: boolean): ConflictDetail | null { function checkPendingStatus(isPending?: boolean): ConflictDetail | null {
if (hasRegistryData === false) { if (isPending === true) {
return { return {
type: 'security_pending', type: 'pending',
current_value: 'no_registry_data', current_value: 'installed',
required_value: 'registry_data_available' required_value: 'not_pending'
} }
} }
return null return null
@@ -1402,23 +1306,23 @@ function generateSummary(
const conflictsByType: Record<ConflictType, number> = { const conflictsByType: Record<ConflictType, number> = {
comfyui_version: 0, comfyui_version: 0,
frontend_version: 0, frontend_version: 0,
python_version: 0, import_failed: 0,
os: 0, os: 0,
accelerator: 0, accelerator: 0,
banned: 0, banned: 0,
security_pending: 0, pending: 0
python_dependency: 0 // python_version: 0
} }
const conflictsByTypeDetails: Record<ConflictType, string[]> = { const conflictsByTypeDetails: Record<ConflictType, string[]> = {
comfyui_version: [], comfyui_version: [],
frontend_version: [], frontend_version: [],
python_version: [], import_failed: [],
os: [], os: [],
accelerator: [], accelerator: [],
banned: [], banned: [],
security_pending: [], pending: []
python_dependency: [] // python_version: [],
} }
let bannedCount = 0 let bannedCount = 0
@@ -1433,7 +1337,7 @@ function generateSummary(
} }
if (conflict.type === 'banned') bannedCount++ if (conflict.type === 'banned') bannedCount++
if (conflict.type === 'security_pending') securityPendingCount++ if (conflict.type === 'pending') securityPendingCount++
}) })
}) })
@@ -1442,7 +1346,7 @@ function generateSummary(
compatible_packages: results.filter((r) => r.is_compatible).length, compatible_packages: results.filter((r) => r.is_compatible).length,
conflicted_packages: results.filter((r) => r.has_conflict).length, conflicted_packages: results.filter((r) => r.has_conflict).length,
banned_packages: bannedCount, banned_packages: bannedCount,
security_pending_packages: securityPendingCount, pending_packages: securityPendingCount,
conflicts_by_type_details: conflictsByTypeDetails, conflicts_by_type_details: conflictsByTypeDetails,
last_check_timestamp: new Date().toISOString(), last_check_timestamp: new Date().toISOString(),
check_duration_ms: durationMs check_duration_ms: durationMs
@@ -1458,16 +1362,16 @@ function getEmptySummary(): ConflictDetectionSummary {
compatible_packages: 0, compatible_packages: 0,
conflicted_packages: 0, conflicted_packages: 0,
banned_packages: 0, banned_packages: 0,
security_pending_packages: 0, pending_packages: 0,
conflicts_by_type_details: { conflicts_by_type_details: {
comfyui_version: [], comfyui_version: [],
frontend_version: [], frontend_version: [],
python_version: [], import_failed: [],
os: [], os: [],
accelerator: [], accelerator: [],
banned: [], banned: [],
security_pending: [], pending: []
python_dependency: [] // python_version: [],
}, },
last_check_timestamp: new Date().toISOString(), last_check_timestamp: new Date().toISOString(),
check_duration_ms: 0 check_duration_ms: 0

View File

@@ -227,13 +227,11 @@
"conflictMessages": { "conflictMessages": {
"comfyui_version": "ComfyUI version mismatch (current: {current}, required: {required})", "comfyui_version": "ComfyUI version mismatch (current: {current}, required: {required})",
"frontend_version": "Frontend version mismatch (current: {current}, required: {required})", "frontend_version": "Frontend version mismatch (current: {current}, required: {required})",
"python_version": "Python version mismatch (current: {current}, required: {required})",
"os": "Operating system not supported (current: {current}, required: {required})", "os": "Operating system not supported (current: {current}, required: {required})",
"accelerator": "GPU/Accelerator not supported (available: {current}, required: {required})", "accelerator": "GPU/Accelerator not supported (available: {current}, required: {required})",
"generic": "Compatibility issue (current: {current}, required: {required})", "generic": "Compatibility issue (current: {current}, required: {required})",
"banned": "This package is banned for security reasons", "banned": "This package is banned for security reasons",
"security_pending": "Security verification pending - compatibility cannot be verified", "pending": "Security verification pending - compatibility cannot be verified"
"python_dependency": "Missing Python dependency: {required}"
}, },
"warningTooltip": "This package may have compatibility issues with your current environment" "warningTooltip": "This package may have compatibility issues with your current environment"
} }

View File

@@ -29,7 +29,7 @@ export const useConflictDetectionStore = defineStore(
const securityPendingPackages = computed(() => const securityPendingPackages = computed(() =>
conflictedPackages.value.filter((pkg) => conflictedPackages.value.filter((pkg) =>
pkg.conflicts.some((conflict) => conflict.type === 'security_pending') pkg.conflicts.some((conflict) => conflict.type === 'pending')
) )
) )

View File

@@ -1,7 +1,17 @@
/** /**
* Type definitions for the conflict detection system. * Type definitions for the conflict detection system.
* These types are used to detect compatibility issues between Node Packs and the system environment. * These types are used to detect compatibility issues between Node Packs and the system environment.
*
* This file extends and uses types from comfyRegistryTypes.ts to maintain consistency
* with the Registry API schema.
*/ */
import type { components } from './comfyRegistryTypes'
// Re-export core types from Registry API
export type Node = components['schemas']['Node']
export type NodeVersion = components['schemas']['NodeVersion']
export type NodeStatus = components['schemas']['NodeStatus']
export type NodeVersionStatus = components['schemas']['NodeVersionStatus']
/** /**
* Conflict types that can be detected in the system * Conflict types that can be detected in the system
@@ -10,30 +20,12 @@
export type ConflictType = export type ConflictType =
| 'comfyui_version' // ComfyUI version mismatch | 'comfyui_version' // ComfyUI version mismatch
| 'frontend_version' // Frontend version mismatch | 'frontend_version' // Frontend version mismatch
| 'python_version' // Python version mismatch | 'import_failed'
// | 'python_version' // Python version mismatch
| 'os' // Operating system incompatibility | 'os' // Operating system incompatibility
| 'accelerator' // GPU/accelerator incompatibility | 'accelerator' // GPU/accelerator incompatibility
| 'banned' // Banned package | 'banned' // Banned package
| 'security_pending' // Security verification pending | 'pending' // Security verification pending
| 'python_dependency' // Python module dependency missing
/**
* Security scan status for packages
* @enum {string}
*/
export type SecurityScanStatus = 'pending' | 'passed' | 'failed' | 'unknown'
/**
* Supported operating systems (as per Registry Admin guide)
* @enum {string}
*/
export type SupportedOS = 'Windows' | 'macOS' | 'Linux' | 'any'
/**
* Supported accelerators for GPU computation (as per Registry Admin guide)
* @enum {string}
*/
export type SupportedAccelerator = 'CUDA' | 'ROCm' | 'Metal' | 'CPU' | 'any'
/** /**
* Version comparison operators * Version comparison operators
@@ -53,48 +45,17 @@ export interface VersionRequirement {
/** /**
* Node Pack requirements from Registry API * Node Pack requirements from Registry API
* Extends Node type with additional installation and compatibility metadata
*/ */
export interface NodePackRequirements { export interface NodePackRequirements extends Node {
/** @description Unique package identifier */
package_id: string
/** @description Human-readable package name */
package_name: string
/** @description Currently installed version */
installed_version: string installed_version: string
/** @description Whether the package is enabled locally */
is_enabled: boolean is_enabled: boolean
/** @description Supported ComfyUI version from Registry */
supported_comfyui_version?: string
/** @description Supported frontend version from Registry */
supported_comfyui_frontend_version?: string
/** @description List of supported operating systems from Registry */
supported_os?: SupportedOS[]
/** @description List of supported accelerators from Registry */
supported_accelerators?: SupportedAccelerator[]
/** @description Package dependencies from Registry */
dependencies?: string[]
/** @description Node status from Registry (Active/Banned/Deleted) */
registry_status?:
| 'NodeStatusActive'
| 'NodeStatusBanned'
| 'NodeStatusDeleted'
/** @description Node version status from Registry */
version_status?:
| 'NodeVersionStatusActive'
| 'NodeVersionStatusBanned'
| 'NodeVersionStatusDeleted'
| 'NodeVersionStatusPending'
| 'NodeVersionStatusFlagged'
/** @description Whether package is banned (derived from status) */
is_banned: boolean is_banned: boolean
is_pending: boolean
// Metadata // Aliases for backwards compatibility with existing code
/** @description Registry data fetch timestamp */ package_id: string
registry_fetch_time: string package_name: string
/** @description Whether Registry data was successfully fetched */ version_status?: string
has_registry_data: boolean
} }
/** /**
@@ -102,33 +63,22 @@ export interface NodePackRequirements {
*/ */
export interface SystemEnvironment { export interface SystemEnvironment {
// Version information // Version information
/** @description Current ComfyUI version */
comfyui_version: string comfyui_version: string
/** @description Current frontend version */
frontend_version: string frontend_version: string
/** @description Current Python version */ // python_version: string
python_version: string
// Platform information // Platform information
/** @description Operating system type */ os: string
os: SupportedOS
/** @description Detailed platform information (e.g., 'Darwin 24.5.0', 'Windows 10') */
platform_details: string platform_details: string
/** @description System architecture (e.g., 'x64', 'arm64') */
architecture: string architecture: string
// GPU/accelerator information // GPU/accelerator information
/** @description List of available accelerators */ available_accelerators: Node['supported_accelerators']
available_accelerators: SupportedAccelerator[] primary_accelerator: string
/** @description Primary accelerator in use */
primary_accelerator: SupportedAccelerator
/** @description GPU memory in megabytes, if available */
gpu_memory_mb?: number gpu_memory_mb?: number
// Runtime information // Runtime information
/** @description Node.js environment mode */
node_env: 'development' | 'production' node_env: 'development' | 'production'
/** @description Browser user agent string */
user_agent: string user_agent: string
} }
@@ -136,15 +86,10 @@ export interface SystemEnvironment {
* Individual conflict detection result for a package * Individual conflict detection result for a package
*/ */
export interface ConflictDetectionResult { export interface ConflictDetectionResult {
/** @description Package identifier */
package_id: string package_id: string
/** @description Package name */
package_name: string package_name: string
/** @description Whether any conflicts were detected */
has_conflict: boolean has_conflict: boolean
/** @description List of detected conflicts */
conflicts: ConflictDetail[] conflicts: ConflictDetail[]
/** @description Overall compatibility status */
is_compatible: boolean is_compatible: boolean
} }
@@ -152,11 +97,8 @@ export interface ConflictDetectionResult {
* Detailed information about a specific conflict * Detailed information about a specific conflict
*/ */
export interface ConflictDetail { export interface ConflictDetail {
/** @description Type of conflict detected */
type: ConflictType type: ConflictType
/** @description Human-readable description of the conflict */
current_value: string current_value: string
/** @description Required value for compatibility */
required_value: string required_value: string
} }
@@ -164,21 +106,13 @@ export interface ConflictDetail {
* Overall conflict detection summary * Overall conflict detection summary
*/ */
export interface ConflictDetectionSummary { export interface ConflictDetectionSummary {
/** @description Total number of packages checked */
total_packages: number total_packages: number
/** @description Number of compatible packages */
compatible_packages: number compatible_packages: number
/** @description Number of packages with conflicts */
conflicted_packages: number conflicted_packages: number
/** @description Number of banned packages */
banned_packages: number banned_packages: number
/** @description Number of packages pending security verification */ pending_packages: number
security_pending_packages: number
/** @description Node IDs grouped by conflict type */
conflicts_by_type_details: Record<ConflictType, string[]> conflicts_by_type_details: Record<ConflictType, string[]>
/** @description Timestamp of the last conflict check */
last_check_timestamp: string last_check_timestamp: string
/** @description Duration of the conflict check in milliseconds */
check_duration_ms: number check_duration_ms: number
} }
@@ -186,16 +120,9 @@ export interface ConflictDetectionSummary {
* Response payload from conflict detection API * Response payload from conflict detection API
*/ */
export interface ConflictDetectionResponse { export interface ConflictDetectionResponse {
/** @description Whether the API request was successful */
success: boolean success: boolean
/** @description Error message if the request failed */
error_message?: string error_message?: string
/** @description Summary of the conflict detection results */
summary: ConflictDetectionSummary summary: ConflictDetectionSummary
/** @description Detailed results for each package */
results: ConflictDetectionResult[] results: ConflictDetectionResult[]
/** @description System environment information detected by the server (for comparison) */
detected_system_environment?: Partial<SystemEnvironment> detected_system_environment?: Partial<SystemEnvironment>
} }

View File

@@ -18,7 +18,6 @@ export function getConflictMessage(
if ( if (
conflict.type === 'comfyui_version' || conflict.type === 'comfyui_version' ||
conflict.type === 'frontend_version' || conflict.type === 'frontend_version' ||
conflict.type === 'python_version' ||
conflict.type === 'os' || conflict.type === 'os' ||
conflict.type === 'accelerator' conflict.type === 'accelerator'
) { ) {
@@ -28,15 +27,8 @@ export function getConflictMessage(
}) })
} }
// For dependency conflicts, show the missing dependency // For banned and pending, use simple message
if (conflict.type === 'python_dependency') { if (conflict.type === 'banned' || conflict.type === 'pending') {
return t(messageKey, {
required: conflict.required_value
})
}
// For banned and security_pending, use simple message
if (conflict.type === 'banned' || conflict.type === 'security_pending') {
return t(messageKey) return t(messageKey)
} }

View File

@@ -65,7 +65,7 @@ export function isValidVersion(version: string): boolean {
* @param supportedVersion Required version range string * @param supportedVersion Required version range string
* @returns ConflictDetail object if incompatible, null if compatible * @returns ConflictDetail object if incompatible, null if compatible
*/ */
export function checkVersionCompatibility( export function utilCheckVersionCompatibility(
type: ConflictType, type: ConflictType,
currentVersion: string, currentVersion: string,
supportedVersion: string supportedVersion: string

View File

@@ -98,7 +98,6 @@ describe.skip('useConflictDetection with Registry Store', () => {
systemStats: { systemStats: {
system: { system: {
comfyui_version: '0.3.41', comfyui_version: '0.3.41',
python_version: '3.12.11',
os: 'Darwin' os: 'Darwin'
}, },
devices: [ devices: [
@@ -120,7 +119,6 @@ describe.skip('useConflictDetection with Registry Store', () => {
mockSystemStatsStore.systemStats = { mockSystemStatsStore.systemStats = {
system: { system: {
comfyui_version: '0.3.41', comfyui_version: '0.3.41',
python_version: '3.12.11',
os: 'Darwin' os: 'Darwin'
}, },
devices: [ devices: [
@@ -178,7 +176,6 @@ describe.skip('useConflictDetection with Registry Store', () => {
expect(environment.comfyui_version).toBe('0.3.41') expect(environment.comfyui_version).toBe('0.3.41')
expect(environment.frontend_version).toBe('1.24.0-1') expect(environment.frontend_version).toBe('1.24.0-1')
expect(environment.python_version).toBe('3.12.11')
expect(environment.available_accelerators).toContain('Metal') expect(environment.available_accelerators).toContain('Metal')
expect(environment.available_accelerators).toContain('CPU') expect(environment.available_accelerators).toContain('CPU')
expect(environment.primary_accelerator).toBe('Metal') expect(environment.primary_accelerator).toBe('Metal')
@@ -196,7 +193,6 @@ describe.skip('useConflictDetection with Registry Store', () => {
expect(environment.comfyui_version).toBe('unknown') expect(environment.comfyui_version).toBe('unknown')
expect(environment.frontend_version).toBe('1.24.0-1') expect(environment.frontend_version).toBe('1.24.0-1')
expect(environment.python_version).toBe('unknown')
expect(environment.available_accelerators).toEqual(['CPU']) expect(environment.available_accelerators).toEqual(['CPU'])
}) })
}) })
@@ -325,7 +321,7 @@ describe.skip('useConflictDetection with Registry Store', () => {
expect(unknownPackage.conflicts).toEqual( expect(unknownPackage.conflicts).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
type: 'security_pending', type: 'pending',
current_value: 'no_registry_data', current_value: 'no_registry_data',
required_value: 'registry_data_available' required_value: 'registry_data_available'
}) })
@@ -1001,7 +997,6 @@ describe.skip('useConflictDetection with Registry Store', () => {
mockSystemStatsStore.systemStats = { mockSystemStatsStore.systemStats = {
system: { system: {
comfyui_version: '0.3.41', comfyui_version: '0.3.41',
python_version: '3.12.11',
os: 'Darwin' os: 'Darwin'
}, },
devices: [] devices: []

View File

@@ -17,7 +17,7 @@ describe('useConflictDetectionStore', () => {
is_compatible: false, is_compatible: false,
conflicts: [ conflicts: [
{ {
type: 'security_pending', type: 'pending',
current_value: 'no_registry_data', current_value: 'no_registry_data',
required_value: 'registry_data_available' required_value: 'registry_data_available'
} }
@@ -130,7 +130,7 @@ describe('useConflictDetectionStore', () => {
}) })
describe('securityPendingPackages', () => { describe('securityPendingPackages', () => {
it('should filter packages with security_pending conflicts', () => { it('should filter packages with pending conflicts', () => {
const store = useConflictDetectionStore() const store = useConflictDetectionStore()
store.setConflictedPackages(mockConflictedPackages) store.setConflictedPackages(mockConflictedPackages)
@@ -234,7 +234,7 @@ describe('useConflictDetectionStore', () => {
required_value: 'not_banned' required_value: 'not_banned'
}, },
{ {
type: 'security_pending', type: 'pending',
current_value: 'no_registry_data', current_value: 'no_registry_data',
required_value: 'registry_data_available' required_value: 'registry_data_available'
} }