mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
refactor: conflict detect
This commit is contained in:
@@ -1,10 +1,9 @@
|
|||||||
import { until } from '@vueuse/core'
|
import { until } from '@vueuse/core'
|
||||||
import { uniqBy } from 'es-toolkit/compat'
|
import { find } from 'es-toolkit/compat'
|
||||||
import { computed, getCurrentInstance, onUnmounted, readonly, ref } from 'vue'
|
import { computed, getCurrentInstance, onUnmounted, readonly, ref } from 'vue'
|
||||||
|
|
||||||
import { useInstalledPacks } from '@/composables/nodePack/useInstalledPacks'
|
import { useInstalledPacks } from '@/composables/nodePack/useInstalledPacks'
|
||||||
import { useConflictAcknowledgment } from '@/composables/useConflictAcknowledgment'
|
import { useConflictAcknowledgment } from '@/composables/useConflictAcknowledgment'
|
||||||
import config from '@/config'
|
|
||||||
import { useComfyManagerService } from '@/services/comfyManagerService'
|
import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||||
import { useComfyRegistryService } from '@/services/comfyRegistryService'
|
import { useComfyRegistryService } from '@/services/comfyRegistryService'
|
||||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||||
@@ -16,16 +15,25 @@ import type {
|
|||||||
ConflictDetectionResponse,
|
ConflictDetectionResponse,
|
||||||
ConflictDetectionResult,
|
ConflictDetectionResult,
|
||||||
ConflictDetectionSummary,
|
ConflictDetectionSummary,
|
||||||
ConflictType,
|
|
||||||
Node,
|
Node,
|
||||||
NodeRequirements,
|
NodeRequirements,
|
||||||
SystemEnvironment
|
SystemEnvironment
|
||||||
} from '@/types/conflictDetectionTypes'
|
} from '@/types/conflictDetectionTypes'
|
||||||
import { normalizePackId } from '@/utils/packUtils'
|
|
||||||
import {
|
import {
|
||||||
cleanVersion,
|
consolidateConflictsByPackage,
|
||||||
satisfiesVersion,
|
createBannedConflict,
|
||||||
utilCheckVersionCompatibility
|
createPendingConflict,
|
||||||
|
generateConflictSummary
|
||||||
|
} from '@/utils/conflictUtils'
|
||||||
|
import {
|
||||||
|
checkAcceleratorCompatibility,
|
||||||
|
checkOSCompatibility,
|
||||||
|
normalizeAcceleratorList,
|
||||||
|
normalizeOSList
|
||||||
|
} from '@/utils/simpleCompatibility'
|
||||||
|
import {
|
||||||
|
checkVersionCompatibility,
|
||||||
|
getFrontendVersion
|
||||||
} from '@/utils/versionUtil'
|
} from '@/utils/versionUtil'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,7 +59,7 @@ export function useConflictDetection() {
|
|||||||
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[]>([])
|
||||||
const detectionSummary = ref<ConflictDetectionSummary | null>(null)
|
const detectionSummary = ref<ConflictDetectionSummary | undefined>(undefined)
|
||||||
|
|
||||||
// Registry API request cancellation
|
// Registry API request cancellation
|
||||||
const abortController = ref<AbortController | null>(null)
|
const abortController = ref<AbortController | null>(null)
|
||||||
@@ -83,7 +91,7 @@ export function useConflictDetection() {
|
|||||||
useSystemStatsStore()
|
useSystemStatsStore()
|
||||||
await until(systemStatsInitialized)
|
await until(systemStatsInitialized)
|
||||||
|
|
||||||
const frontendVersion = await fetchFrontendVersion()
|
const frontendVersion = getFrontendVersion()
|
||||||
|
|
||||||
const environment: SystemEnvironment = {
|
const environment: SystemEnvironment = {
|
||||||
comfyui_version: systemStats?.system.comfyui_version ?? '',
|
comfyui_version: systemStats?.system.comfyui_version ?? '',
|
||||||
@@ -199,9 +207,9 @@ export function useConflictDetection() {
|
|||||||
const isEnabled = managerStore.isPackEnabled(installedPackVersion.id)
|
const isEnabled = managerStore.isPackEnabled(installedPackVersion.id)
|
||||||
|
|
||||||
// Find the pack info from Registry if available
|
// Find the pack info from Registry if available
|
||||||
const packInfo = installedPacks.value.find(
|
const packInfo = find(installedPacks.value, {
|
||||||
(p) => p.id === installedPackVersion.id
|
id: installedPackVersion.id
|
||||||
)
|
})
|
||||||
|
|
||||||
if (versionData) {
|
if (versionData) {
|
||||||
// Combine local installation data with version-specific Registry data
|
// Combine local installation data with version-specific Registry data
|
||||||
@@ -216,7 +224,9 @@ export function useConflictDetection() {
|
|||||||
supported_comfyui_version: versionData.supported_comfyui_version,
|
supported_comfyui_version: versionData.supported_comfyui_version,
|
||||||
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: normalizeOSList(
|
||||||
|
versionData.supported_os
|
||||||
|
) as Node['supported_os'],
|
||||||
supported_accelerators: versionData.supported_accelerators,
|
supported_accelerators: versionData.supported_accelerators,
|
||||||
|
|
||||||
// Status information
|
// Status information
|
||||||
@@ -268,51 +278,35 @@ export function useConflictDetection() {
|
|||||||
): ConflictDetectionResult {
|
): ConflictDetectionResult {
|
||||||
const conflicts: ConflictDetail[] = []
|
const conflicts: ConflictDetail[] = []
|
||||||
|
|
||||||
// Helper function to check if a value indicates "compatible with all"
|
|
||||||
const isCompatibleWithAll = (value: any): boolean => {
|
|
||||||
if (value === null || value === undefined) return true
|
|
||||||
if (typeof value === 'string' && value.trim() === '') return true
|
|
||||||
if (Array.isArray(value) && value.length === 0) return true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. ComfyUI version conflict check
|
// 1. ComfyUI version conflict check
|
||||||
if (!isCompatibleWithAll(packageReq.supported_comfyui_version)) {
|
const versionConflict = checkVersionCompatibility(
|
||||||
const versionConflict = checkVersionConflict(
|
'comfyui_version',
|
||||||
'comfyui_version',
|
systemEnvInfo.comfyui_version,
|
||||||
systemEnvInfo.comfyui_version,
|
packageReq.supported_comfyui_version
|
||||||
packageReq.supported_comfyui_version!
|
)
|
||||||
)
|
if (versionConflict) conflicts.push(versionConflict)
|
||||||
if (versionConflict) conflicts.push(versionConflict)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Frontend version conflict check
|
// 2. Frontend version conflict check
|
||||||
if (!isCompatibleWithAll(packageReq.supported_comfyui_frontend_version)) {
|
const frontendConflict = checkVersionCompatibility(
|
||||||
const versionConflict = checkVersionConflict(
|
'frontend_version',
|
||||||
'frontend_version',
|
systemEnvInfo.frontend_version,
|
||||||
systemEnvInfo.frontend_version,
|
packageReq.supported_comfyui_frontend_version
|
||||||
packageReq.supported_comfyui_frontend_version!
|
)
|
||||||
)
|
if (frontendConflict) conflicts.push(frontendConflict)
|
||||||
if (versionConflict) conflicts.push(versionConflict)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. OS compatibility check
|
// 3. OS compatibility check
|
||||||
if (!isCompatibleWithAll(packageReq.supported_os)) {
|
const osConflict = checkOSCompatibility(
|
||||||
const osConflict = checkOSConflict(
|
packageReq.supported_os as any,
|
||||||
packageReq.supported_os!,
|
systemEnvInfo.os
|
||||||
systemEnvInfo.os
|
)
|
||||||
)
|
if (osConflict) conflicts.push(osConflict)
|
||||||
if (osConflict) conflicts.push(osConflict)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Accelerator compatibility check
|
// 4. Accelerator compatibility check
|
||||||
if (!isCompatibleWithAll(packageReq.supported_accelerators)) {
|
const acceleratorConflict = checkAcceleratorCompatibility(
|
||||||
const acceleratorConflict = checkAcceleratorConflict(
|
packageReq.supported_accelerators as any,
|
||||||
packageReq.supported_accelerators!,
|
systemEnvInfo.accelerator
|
||||||
systemEnvInfo.accelerator
|
)
|
||||||
)
|
if (acceleratorConflict) conflicts.push(acceleratorConflict)
|
||||||
if (acceleratorConflict) conflicts.push(acceleratorConflict)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Banned package check using shared logic
|
// 5. Banned package check using shared logic
|
||||||
const bannedConflict = createBannedConflict(packageReq.is_banned)
|
const bannedConflict = createBannedConflict(packageReq.is_banned)
|
||||||
@@ -496,7 +490,10 @@ export function useConflictDetection() {
|
|||||||
const allResults = [...packageResults, ...importFailResults]
|
const allResults = [...packageResults, ...importFailResults]
|
||||||
|
|
||||||
// 6. Generate summary information
|
// 6. Generate summary information
|
||||||
const summary = generateSummary(allResults, Date.now() - startTime)
|
const summary = generateConflictSummary(
|
||||||
|
allResults,
|
||||||
|
Date.now() - startTime
|
||||||
|
)
|
||||||
|
|
||||||
// 7. Update state
|
// 7. Update state
|
||||||
detectionResults.value = allResults
|
detectionResults.value = allResults
|
||||||
@@ -516,7 +513,7 @@ export function useConflictDetection() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Merge conflicts for packages with the same name
|
// Merge conflicts for packages with the same name
|
||||||
const mergedConflicts = mergeConflictsByPackageName(conflictedResults)
|
const mergedConflicts = consolidateConflictsByPackage(conflictedResults)
|
||||||
|
|
||||||
console.debug(
|
console.debug(
|
||||||
'[ConflictDetection] Conflicts detected (stored for UI):',
|
'[ConflictDetection] Conflicts detected (stored for UI):',
|
||||||
@@ -567,7 +564,7 @@ export function useConflictDetection() {
|
|||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error_message: detectionError.value,
|
error_message: detectionError.value,
|
||||||
summary: detectionSummary.value || generateEmptySummary(),
|
summary: detectionSummary.value,
|
||||||
results: []
|
results: []
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -663,7 +660,7 @@ export function useConflictDetection() {
|
|||||||
* Check compatibility for a node.
|
* Check compatibility for a node.
|
||||||
* Used by components like PackVersionSelectorPopover.
|
* Used by components like PackVersionSelectorPopover.
|
||||||
*/
|
*/
|
||||||
async function checkNodeCompatibility(
|
function checkNodeCompatibility(
|
||||||
node: Node | components['schemas']['NodeVersion']
|
node: Node | components['schemas']['NodeVersion']
|
||||||
) {
|
) {
|
||||||
const systemStatsStore = useSystemStatsStore()
|
const systemStatsStore = useSystemStatsStore()
|
||||||
@@ -672,17 +669,20 @@ export function useConflictDetection() {
|
|||||||
|
|
||||||
const conflicts: ConflictDetail[] = []
|
const conflicts: ConflictDetail[] = []
|
||||||
|
|
||||||
// Check OS compatibility using centralized function
|
// Check OS compatibility
|
||||||
const currentOS = systemStats.system?.os
|
const currentOS = systemStats.system?.os
|
||||||
const OSConflict = checkOSConflict(node.supported_os, currentOS)
|
const osConflict = checkOSCompatibility(
|
||||||
if (OSConflict) {
|
normalizeOSList(node.supported_os),
|
||||||
conflicts.push(OSConflict)
|
currentOS
|
||||||
|
)
|
||||||
|
if (osConflict) {
|
||||||
|
conflicts.push(osConflict)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Accelerator compatibility using centralized function
|
// Check Accelerator compatibility
|
||||||
const currentAccelerator = systemStats.devices?.[0].type
|
const currentAccelerator = systemStats.devices?.[0]?.type
|
||||||
const acceleratorConflict = checkAcceleratorConflict(
|
const acceleratorConflict = checkAcceleratorCompatibility(
|
||||||
node.supported_accelerators,
|
normalizeAcceleratorList(node.supported_accelerators),
|
||||||
currentAccelerator
|
currentAccelerator
|
||||||
)
|
)
|
||||||
if (acceleratorConflict) {
|
if (acceleratorConflict) {
|
||||||
@@ -691,7 +691,7 @@ export function useConflictDetection() {
|
|||||||
|
|
||||||
// Check ComfyUI version compatibility
|
// Check ComfyUI version compatibility
|
||||||
const currentComfyUIVersion = systemStats.system?.comfyui_version
|
const currentComfyUIVersion = systemStats.system?.comfyui_version
|
||||||
const comfyUIVersionConflict = utilCheckVersionCompatibility(
|
const comfyUIVersionConflict = checkVersionCompatibility(
|
||||||
'comfyui_version',
|
'comfyui_version',
|
||||||
currentComfyUIVersion,
|
currentComfyUIVersion,
|
||||||
node.supported_comfyui_version
|
node.supported_comfyui_version
|
||||||
@@ -701,8 +701,8 @@ export function useConflictDetection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check ComfyUI Frontend version compatibility
|
// Check ComfyUI Frontend version compatibility
|
||||||
const currentFrontendVersion = await fetchFrontendVersion()
|
const currentFrontendVersion = getFrontendVersion()
|
||||||
const frontendVersionConflict = utilCheckVersionCompatibility(
|
const frontendVersionConflict = checkVersionCompatibility(
|
||||||
'frontend_version',
|
'frontend_version',
|
||||||
currentFrontendVersion,
|
currentFrontendVersion,
|
||||||
node.supported_comfyui_frontend_version
|
node.supported_comfyui_frontend_version
|
||||||
@@ -760,348 +760,3 @@ export function useConflictDetection() {
|
|||||||
checkNodeCompatibility
|
checkNodeCompatibility
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper Functions Implementation
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges conflict results for packages with the same name.
|
|
||||||
* Combines all conflicts from different detection sources (registry, python, extension)
|
|
||||||
* into a single result per package name.
|
|
||||||
* @param conflicts Array of conflict detection results
|
|
||||||
* @returns Array of merged conflict detection results
|
|
||||||
*/
|
|
||||||
function mergeConflictsByPackageName(
|
|
||||||
conflicts: ConflictDetectionResult[]
|
|
||||||
): ConflictDetectionResult[] {
|
|
||||||
const mergedMap = new Map<string, ConflictDetectionResult>()
|
|
||||||
|
|
||||||
conflicts.forEach((conflict) => {
|
|
||||||
// Normalize package name by removing version suffix (@1_0_3) for consistent merging
|
|
||||||
const normalizedPackageName = normalizePackId(conflict.package_name)
|
|
||||||
|
|
||||||
if (mergedMap.has(normalizedPackageName)) {
|
|
||||||
// Package already exists, merge conflicts
|
|
||||||
const existing = mergedMap.get(normalizedPackageName)!
|
|
||||||
|
|
||||||
// Combine all conflicts, avoiding duplicates using es-toolkit uniqBy for O(n) performance
|
|
||||||
const allConflicts = [...existing.conflicts, ...conflict.conflicts]
|
|
||||||
const uniqueConflicts = uniqBy(
|
|
||||||
allConflicts,
|
|
||||||
(conflict) =>
|
|
||||||
`${conflict.type}|${conflict.current_value}|${conflict.required_value}`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update the existing entry with normalized package name
|
|
||||||
mergedMap.set(normalizedPackageName, {
|
|
||||||
...existing,
|
|
||||||
package_name: normalizedPackageName,
|
|
||||||
conflicts: uniqueConflicts,
|
|
||||||
has_conflict: uniqueConflicts.length > 0,
|
|
||||||
is_compatible: uniqueConflicts.length === 0
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// New package, add with normalized package name
|
|
||||||
mergedMap.set(normalizedPackageName, {
|
|
||||||
...conflict,
|
|
||||||
package_name: normalizedPackageName
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return Array.from(mergedMap.values())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches frontend version from config.
|
|
||||||
* @returns Promise that resolves to frontend version string
|
|
||||||
*/
|
|
||||||
async function fetchFrontendVersion(): Promise<string> {
|
|
||||||
try {
|
|
||||||
// Get frontend version from vite build-time constant or fallback to config
|
|
||||||
return config.app_version || import.meta.env.VITE_APP_VERSION || 'unknown'
|
|
||||||
} catch {
|
|
||||||
return 'unknown'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalizes OS values from the Registry API to match our SupportedOS type.
|
|
||||||
*
|
|
||||||
* Rules:
|
|
||||||
* - Registry Admin guide specifies: Windows, macOS, Linux
|
|
||||||
* - null, undefined, or an empty array → treated as "supports all OS"
|
|
||||||
* - ['OS Independent'] → treated as "supports all OS"
|
|
||||||
* - Otherwise, map each string to a standard OS value
|
|
||||||
*
|
|
||||||
* @param osValues OS values from the Registry API
|
|
||||||
* @returns Normalized OS values
|
|
||||||
*/
|
|
||||||
function normalizeOSValues(
|
|
||||||
osValues: string[] | null | undefined
|
|
||||||
): Node['supported_os'] {
|
|
||||||
// Default set meaning "supports all OS"
|
|
||||||
const allOS: Node['supported_os'] = ['Windows', 'macOS', 'Linux']
|
|
||||||
|
|
||||||
// null, undefined, or empty array → all OS
|
|
||||||
if (!osValues || osValues.length === 0) {
|
|
||||||
return allOS
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the array contains "OS Independent" → all OS
|
|
||||||
if (osValues.some((os) => os.toLowerCase() === 'os independent')) {
|
|
||||||
return allOS
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map each value to standardized OS names
|
|
||||||
return osValues.flatMap((os) => {
|
|
||||||
const lower = os.toLowerCase()
|
|
||||||
if (os === 'Windows' || lower.includes('win')) return ['Windows']
|
|
||||||
if (os === 'macOS' || lower.includes('mac') || os === 'darwin')
|
|
||||||
return ['macOS']
|
|
||||||
if (os === 'Linux' || lower.includes('linux')) return ['Linux']
|
|
||||||
// Ignore anything unrecognized
|
|
||||||
return []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detects operating system from system stats OS string.
|
|
||||||
* @param systemOS OS string from system stats API
|
|
||||||
* @returns Operating system type
|
|
||||||
*/
|
|
||||||
// TODO: move to type file
|
|
||||||
type OS_TYPE = 'Windows' | 'Linux' | 'MacOS' | 'unknown'
|
|
||||||
|
|
||||||
function mapSystemOSToRegistry(systemOS?: string): OS_TYPE {
|
|
||||||
const os = systemOS?.toLowerCase()
|
|
||||||
|
|
||||||
if (os?.includes('win')) return 'Windows'
|
|
||||||
if (os?.includes('linux')) return 'Linux'
|
|
||||||
if (os?.includes('darwin')) return 'MacOS'
|
|
||||||
|
|
||||||
return 'unknown'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts accelerator information from system stats.
|
|
||||||
* @param systemStats System stats data from store
|
|
||||||
* @returns Accelerator information object
|
|
||||||
*/
|
|
||||||
function mapDeviceTypeToAccelerator(systemDeviceType?: string): string {
|
|
||||||
const deviceType = systemDeviceType?.toLowerCase()
|
|
||||||
|
|
||||||
switch (deviceType) {
|
|
||||||
case 'cuda':
|
|
||||||
return 'CUDA'
|
|
||||||
case 'mps':
|
|
||||||
return 'Metal'
|
|
||||||
case 'rocm':
|
|
||||||
return 'ROCm'
|
|
||||||
default:
|
|
||||||
return 'CPU'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unified version conflict check using Registry API version strings.
|
|
||||||
* Uses shared versionUtil functions for consistent version handling.
|
|
||||||
* @param type Type of version being checked
|
|
||||||
* @param currentVersion Current version
|
|
||||||
* @param supportedVersion Supported version from Registry
|
|
||||||
* @returns Conflict detail if conflict exists, null otherwise
|
|
||||||
*/
|
|
||||||
function checkVersionConflict(
|
|
||||||
type: ConflictType,
|
|
||||||
currentVersion?: string,
|
|
||||||
supportedVersion?: string
|
|
||||||
): ConflictDetail | null {
|
|
||||||
// If current version is undefined, assume compatible (no conflict)
|
|
||||||
if (!currentVersion) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// If Registry doesn't specify version requirements, assume compatible
|
|
||||||
if (!supportedVersion || supportedVersion.trim() === '') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Clean the current version using shared utility
|
|
||||||
const cleanCurrent = cleanVersion(currentVersion)
|
|
||||||
|
|
||||||
// Check version compatibility using shared utility
|
|
||||||
const isCompatible = satisfiesVersion(cleanCurrent, supportedVersion)
|
|
||||||
|
|
||||||
if (!isCompatible) {
|
|
||||||
return {
|
|
||||||
type,
|
|
||||||
current_value: currentVersion,
|
|
||||||
required_value: supportedVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
`[ConflictDetection] Failed to parse version requirement: ${supportedVersion}`,
|
|
||||||
error
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
type,
|
|
||||||
current_value: currentVersion,
|
|
||||||
required_value: supportedVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for OS compatibility conflicts.
|
|
||||||
*/
|
|
||||||
function checkOSConflict(
|
|
||||||
supportedOS: Node['supported_os'],
|
|
||||||
currentOS?: string
|
|
||||||
): ConflictDetail | null {
|
|
||||||
const currentOsBySupportOS = mapSystemOSToRegistry(currentOS)
|
|
||||||
const hasOSConflict =
|
|
||||||
currentOS && !supportedOS?.includes(currentOsBySupportOS)
|
|
||||||
if (hasOSConflict) {
|
|
||||||
return {
|
|
||||||
type: 'os',
|
|
||||||
current_value: currentOsBySupportOS,
|
|
||||||
required_value: supportedOS ? supportedOS?.join(', ') : ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for accelerator compatibility conflicts.
|
|
||||||
*/
|
|
||||||
function checkAcceleratorConflict(
|
|
||||||
supportedAccelerators: Node['supported_accelerators'],
|
|
||||||
currentAccelerator?: string
|
|
||||||
): ConflictDetail | null {
|
|
||||||
const currentAcceleratorByAccelerator =
|
|
||||||
mapDeviceTypeToAccelerator(currentAccelerator)
|
|
||||||
const hasAcceleratorConflict =
|
|
||||||
currentAccelerator &&
|
|
||||||
!supportedAccelerators?.includes(currentAcceleratorByAccelerator)
|
|
||||||
if (hasAcceleratorConflict) {
|
|
||||||
return {
|
|
||||||
type: 'accelerator',
|
|
||||||
current_value: currentAcceleratorByAccelerator,
|
|
||||||
required_value: supportedAccelerators
|
|
||||||
? supportedAccelerators.join(', ')
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for banned package status conflicts.
|
|
||||||
*/
|
|
||||||
function createBannedConflict(isBanned?: boolean): ConflictDetail | null {
|
|
||||||
if (isBanned === true) {
|
|
||||||
return {
|
|
||||||
type: 'banned',
|
|
||||||
current_value: 'installed',
|
|
||||||
required_value: 'not_banned'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks for pending package status conflicts.
|
|
||||||
*/
|
|
||||||
function createPendingConflict(isPending?: boolean): ConflictDetail | null {
|
|
||||||
if (isPending === true) {
|
|
||||||
return {
|
|
||||||
type: 'pending',
|
|
||||||
current_value: 'installed',
|
|
||||||
required_value: 'not_pending'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates summary of conflict detection results.
|
|
||||||
*/
|
|
||||||
function generateSummary(
|
|
||||||
results: ConflictDetectionResult[],
|
|
||||||
durationMs: number
|
|
||||||
): ConflictDetectionSummary {
|
|
||||||
const conflictsByType: Record<ConflictType, number> = {
|
|
||||||
comfyui_version: 0,
|
|
||||||
frontend_version: 0,
|
|
||||||
import_failed: 0,
|
|
||||||
os: 0,
|
|
||||||
accelerator: 0,
|
|
||||||
banned: 0,
|
|
||||||
pending: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const conflictsByTypeDetails: Record<ConflictType, string[]> = {
|
|
||||||
comfyui_version: [],
|
|
||||||
frontend_version: [],
|
|
||||||
import_failed: [],
|
|
||||||
os: [],
|
|
||||||
accelerator: [],
|
|
||||||
banned: [],
|
|
||||||
pending: []
|
|
||||||
}
|
|
||||||
|
|
||||||
let bannedCount = 0
|
|
||||||
let securityPendingCount = 0
|
|
||||||
|
|
||||||
results.forEach((result) => {
|
|
||||||
result.conflicts.forEach((conflict) => {
|
|
||||||
conflictsByType[conflict.type]++
|
|
||||||
|
|
||||||
if (!conflictsByTypeDetails[conflict.type].includes(result.package_id)) {
|
|
||||||
conflictsByTypeDetails[conflict.type].push(result.package_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conflict.type === 'banned') bannedCount++
|
|
||||||
if (conflict.type === 'pending') securityPendingCount++
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
total_packages: results.length,
|
|
||||||
compatible_packages: results.filter((r) => r.is_compatible).length,
|
|
||||||
conflicted_packages: results.filter((r) => r.has_conflict).length,
|
|
||||||
banned_packages: bannedCount,
|
|
||||||
pending_packages: securityPendingCount,
|
|
||||||
conflicts_by_type_details: conflictsByTypeDetails,
|
|
||||||
last_check_timestamp: new Date().toISOString(),
|
|
||||||
check_duration_ms: durationMs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an empty summary for error cases.
|
|
||||||
*/
|
|
||||||
function generateEmptySummary(): ConflictDetectionSummary {
|
|
||||||
return {
|
|
||||||
total_packages: 0,
|
|
||||||
compatible_packages: 0,
|
|
||||||
conflicted_packages: 0,
|
|
||||||
banned_packages: 0,
|
|
||||||
pending_packages: 0,
|
|
||||||
conflicts_by_type_details: {
|
|
||||||
comfyui_version: [],
|
|
||||||
frontend_version: [],
|
|
||||||
import_failed: [],
|
|
||||||
os: [],
|
|
||||||
accelerator: [],
|
|
||||||
banned: [],
|
|
||||||
pending: []
|
|
||||||
},
|
|
||||||
last_check_timestamp: new Date().toISOString(),
|
|
||||||
check_duration_ms: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
10
src/types/compatibility.types.ts
Normal file
10
src/types/compatibility.types.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Simple compatibility type definitions
|
||||||
|
* Registry supports exactly these values, null/undefined means compatible with all
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Registry OS
|
||||||
|
export type RegistryOS = 'Windows' | 'macOS' | 'Linux'
|
||||||
|
|
||||||
|
// Registry Accelerator
|
||||||
|
export type RegistryAccelerator = 'CUDA' | 'ROCm' | 'Metal' | 'CPU'
|
||||||
@@ -89,7 +89,7 @@ export interface ConflictDetectionSummary {
|
|||||||
export interface ConflictDetectionResponse {
|
export interface ConflictDetectionResponse {
|
||||||
success: boolean
|
success: boolean
|
||||||
error_message?: string
|
error_message?: string
|
||||||
summary: ConflictDetectionSummary
|
summary?: ConflictDetectionSummary
|
||||||
results: ConflictDetectionResult[]
|
results: ConflictDetectionResult[]
|
||||||
detected_system_environment?: Partial<SystemEnvironment>
|
detected_system_environment?: Partial<SystemEnvironment>
|
||||||
}
|
}
|
||||||
|
|||||||
123
src/utils/conflictUtils.ts
Normal file
123
src/utils/conflictUtils.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { groupBy, isEmpty, isNil, uniqBy } from 'es-toolkit/compat'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ConflictDetail,
|
||||||
|
ConflictDetectionResult,
|
||||||
|
ConflictDetectionSummary,
|
||||||
|
ConflictType
|
||||||
|
} from '@/types/conflictDetectionTypes'
|
||||||
|
import { normalizePackId } from '@/utils/packUtils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to check if a value indicates "compatible with all"
|
||||||
|
* @param value Value to check
|
||||||
|
* @returns True if compatible with all
|
||||||
|
*/
|
||||||
|
export function isCompatibleWithAll(value: any): boolean {
|
||||||
|
return isNil(value) || isEmpty(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for banned package status conflicts.
|
||||||
|
*/
|
||||||
|
export function createBannedConflict(
|
||||||
|
isBanned?: boolean
|
||||||
|
): ConflictDetail | null {
|
||||||
|
if (isBanned === true) {
|
||||||
|
return {
|
||||||
|
type: 'banned',
|
||||||
|
current_value: 'installed',
|
||||||
|
required_value: 'not_banned'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for pending package status conflicts.
|
||||||
|
*/
|
||||||
|
export function createPendingConflict(
|
||||||
|
isPending?: boolean
|
||||||
|
): ConflictDetail | null {
|
||||||
|
if (isPending === true) {
|
||||||
|
return {
|
||||||
|
type: 'pending',
|
||||||
|
current_value: 'installed',
|
||||||
|
required_value: 'not_pending'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups and deduplicates conflicts by normalized package name.
|
||||||
|
* Consolidates multiple conflict sources (registry checks, import failures, disabled packages with version suffix)
|
||||||
|
* into a single UI entry per package.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* - Input: [{name: "pack@1_0_3", conflicts: [...]}, {name: "pack", conflicts: [...]}]
|
||||||
|
* - Output: [{name: "pack", conflicts: [...combined unique conflicts...]}]
|
||||||
|
*
|
||||||
|
* @param conflicts Array of conflict detection results (may have duplicate packages with version suffixes)
|
||||||
|
* @returns Array of deduplicated conflict results grouped by normalized package name
|
||||||
|
*/
|
||||||
|
export function consolidateConflictsByPackage(
|
||||||
|
conflicts: ConflictDetectionResult[]
|
||||||
|
): ConflictDetectionResult[] {
|
||||||
|
// Group conflicts by normalized package name using es-toolkit
|
||||||
|
const grouped = groupBy(conflicts, (conflict) =>
|
||||||
|
normalizePackId(conflict.package_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Merge conflicts for each group
|
||||||
|
return Object.entries(grouped).map(([packageName, packageConflicts]) => {
|
||||||
|
// Flatten all conflicts from the group
|
||||||
|
const allConflicts = packageConflicts.flatMap((pc) => pc.conflicts)
|
||||||
|
|
||||||
|
// Remove duplicate conflicts using uniqBy
|
||||||
|
const uniqueConflicts = uniqBy(
|
||||||
|
allConflicts,
|
||||||
|
(conflict) =>
|
||||||
|
`${conflict.type}|${conflict.current_value}|${conflict.required_value}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use the first item as base and update with merged data
|
||||||
|
const baseItem = packageConflicts[0]
|
||||||
|
return {
|
||||||
|
...baseItem,
|
||||||
|
package_name: packageName, // Use normalized name
|
||||||
|
conflicts: uniqueConflicts,
|
||||||
|
has_conflict: uniqueConflicts.length > 0,
|
||||||
|
is_compatible: uniqueConflicts.length === 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates summary of conflict detection results.
|
||||||
|
*/
|
||||||
|
export function generateConflictSummary(
|
||||||
|
results: ConflictDetectionResult[],
|
||||||
|
durationMs: number
|
||||||
|
): ConflictDetectionSummary {
|
||||||
|
const conflictsByTypeDetails: Record<ConflictType, string[]> = {
|
||||||
|
comfyui_version: [],
|
||||||
|
frontend_version: [],
|
||||||
|
import_failed: [],
|
||||||
|
os: [],
|
||||||
|
accelerator: [],
|
||||||
|
banned: [],
|
||||||
|
pending: []
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
total_packages: results.length,
|
||||||
|
compatible_packages: results.filter((r) => r.is_compatible).length,
|
||||||
|
conflicted_packages: results.filter((r) => r.has_conflict).length,
|
||||||
|
banned_packages: conflictsByTypeDetails['banned'].length,
|
||||||
|
pending_packages: conflictsByTypeDetails['pending'].length,
|
||||||
|
conflicts_by_type_details: conflictsByTypeDetails,
|
||||||
|
last_check_timestamp: new Date().toISOString(),
|
||||||
|
check_duration_ms: durationMs
|
||||||
|
}
|
||||||
|
}
|
||||||
146
src/utils/simpleCompatibility.ts
Normal file
146
src/utils/simpleCompatibility.ts
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import { isEmpty, isNil } from 'es-toolkit/compat'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
RegistryAccelerator,
|
||||||
|
RegistryOS
|
||||||
|
} from '@/types/compatibility.types'
|
||||||
|
import type { ConflictDetail } from '@/types/conflictDetectionTypes'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps system OS string to Registry OS format
|
||||||
|
* @param systemOS Raw OS string from system stats ('darwin', 'win32', 'linux', etc)
|
||||||
|
* @returns Registry OS or undefined if unknown
|
||||||
|
*/
|
||||||
|
export function getRegistryOS(systemOS?: string): RegistryOS | undefined {
|
||||||
|
if (!systemOS) return undefined
|
||||||
|
|
||||||
|
const lower = systemOS.toLowerCase()
|
||||||
|
if (lower.includes('win')) return 'Windows'
|
||||||
|
if (lower.includes('darwin') || lower.includes('mac')) return 'macOS'
|
||||||
|
if (lower.includes('linux')) return 'Linux'
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps device type to Registry accelerator format
|
||||||
|
* @param deviceType Raw device type from system stats ('cuda', 'mps', 'rocm', 'cpu', etc)
|
||||||
|
* @returns Registry accelerator
|
||||||
|
*/
|
||||||
|
export function getRegistryAccelerator(
|
||||||
|
deviceType?: string
|
||||||
|
): RegistryAccelerator {
|
||||||
|
if (!deviceType) return 'CPU'
|
||||||
|
|
||||||
|
const lower = deviceType.toLowerCase()
|
||||||
|
if (lower === 'cuda') return 'CUDA'
|
||||||
|
if (lower === 'mps') return 'Metal'
|
||||||
|
if (lower === 'rocm') return 'ROCm'
|
||||||
|
|
||||||
|
return 'CPU'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks OS compatibility
|
||||||
|
* @param supported Supported OS list from Registry (null/undefined = all OS supported)
|
||||||
|
* @param current Current system OS
|
||||||
|
* @returns ConflictDetail if incompatible, null if compatible
|
||||||
|
*/
|
||||||
|
export function checkOSCompatibility(
|
||||||
|
supported?: RegistryOS[] | null,
|
||||||
|
current?: string
|
||||||
|
): ConflictDetail | null {
|
||||||
|
// null/undefined/empty = all OS supported
|
||||||
|
if (isNil(supported) || isEmpty(supported)) return null
|
||||||
|
|
||||||
|
const currentOS = getRegistryOS(current)
|
||||||
|
if (!currentOS) {
|
||||||
|
return {
|
||||||
|
type: 'os',
|
||||||
|
current_value: 'Unknown',
|
||||||
|
required_value: supported.join(', ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supported.includes(currentOS)) {
|
||||||
|
return {
|
||||||
|
type: 'os',
|
||||||
|
current_value: currentOS,
|
||||||
|
required_value: supported.join(', ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks accelerator compatibility
|
||||||
|
* @param supported Supported accelerators from Registry (null/undefined = all accelerators supported)
|
||||||
|
* @param current Current device type
|
||||||
|
* @returns ConflictDetail if incompatible, null if compatible
|
||||||
|
*/
|
||||||
|
export function checkAcceleratorCompatibility(
|
||||||
|
supported?: RegistryAccelerator[] | null,
|
||||||
|
current?: string
|
||||||
|
): ConflictDetail | null {
|
||||||
|
// null/undefined/empty = all accelerator supported
|
||||||
|
if (isNil(supported) || isEmpty(supported)) return null
|
||||||
|
|
||||||
|
const currentAcc = getRegistryAccelerator(current)
|
||||||
|
|
||||||
|
if (!supported.includes(currentAcc)) {
|
||||||
|
return {
|
||||||
|
type: 'accelerator',
|
||||||
|
current_value: currentAcc,
|
||||||
|
required_value: supported.join(', ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes OS values from Registry API
|
||||||
|
* Handles edge cases like "OS Independent"
|
||||||
|
* @returns undefined if all OS supported, otherwise filtered valid OS list
|
||||||
|
*/
|
||||||
|
export function normalizeOSList(
|
||||||
|
osValues?: string[] | null
|
||||||
|
): RegistryOS[] | undefined {
|
||||||
|
if (isNil(osValues) || isEmpty(osValues)) return undefined
|
||||||
|
|
||||||
|
// "OS Independent" means all OS supported
|
||||||
|
if (osValues.some((os) => os.toLowerCase() === 'os independent')) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter to valid Registry OS values only
|
||||||
|
const validOS: RegistryOS[] = []
|
||||||
|
osValues.forEach((os) => {
|
||||||
|
if (os === 'Windows' || os === 'macOS' || os === 'Linux') {
|
||||||
|
if (!validOS.includes(os)) validOS.push(os)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return validOS.length > 0 ? validOS : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes accelerator values from Registry API
|
||||||
|
* @returns undefined if all accelerators supported, otherwise filtered valid list
|
||||||
|
*/
|
||||||
|
export function normalizeAcceleratorList(
|
||||||
|
accelerators?: string[] | null
|
||||||
|
): RegistryAccelerator[] | undefined {
|
||||||
|
if (isNil(accelerators) || isEmpty(accelerators)) return undefined
|
||||||
|
|
||||||
|
// Filter to valid Registry accelerator values only
|
||||||
|
const validAcc: RegistryAccelerator[] = []
|
||||||
|
accelerators.forEach((acc) => {
|
||||||
|
if (acc === 'CUDA' || acc === 'ROCm' || acc === 'Metal' || acc === 'CPU') {
|
||||||
|
if (!validAcc.includes(acc)) validAcc.push(acc)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return validAcc.length > 0 ? validAcc : undefined
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { isEmpty, isNil } from 'es-toolkit/compat'
|
||||||
import * as semver from 'semver'
|
import * as semver from 'semver'
|
||||||
|
|
||||||
|
import config from '@/config'
|
||||||
import type {
|
import type {
|
||||||
ConflictDetail,
|
ConflictDetail,
|
||||||
ConflictType
|
ConflictType
|
||||||
@@ -37,47 +39,38 @@ export function satisfiesVersion(version: string, range: 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 utilCheckVersionCompatibility(
|
export function checkVersionCompatibility(
|
||||||
type: ConflictType,
|
type: ConflictType,
|
||||||
currentVersion: string,
|
currentVersion?: string,
|
||||||
supportedVersion?: string
|
supportedVersion?: string
|
||||||
): ConflictDetail | null {
|
): ConflictDetail | null {
|
||||||
// If current version is unknown, assume compatible (no conflict)
|
// Use es-toolkit for null/empty checks
|
||||||
if (!currentVersion || currentVersion === 'unknown') {
|
if (isNil(currentVersion) || isEmpty(currentVersion)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no version requirement specified, assume compatible (no conflict)
|
// Use es-toolkit for supported version validation
|
||||||
if (!supportedVersion || supportedVersion.trim() === '') {
|
if (isNil(supportedVersion) || isEmpty(supportedVersion?.trim())) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Clean and check version compatibility
|
||||||
// Clean the current version using semver utilities
|
const cleanCurrent = cleanVersion(currentVersion)
|
||||||
const cleanCurrent = cleanVersion(currentVersion)
|
const isCompatible = satisfiesVersion(cleanCurrent, supportedVersion)
|
||||||
|
|
||||||
// Check version compatibility using semver library
|
if (isCompatible) return null
|
||||||
const isCompatible = satisfiesVersion(cleanCurrent, supportedVersion)
|
|
||||||
|
|
||||||
if (!isCompatible) {
|
return {
|
||||||
return {
|
type,
|
||||||
type,
|
current_value: currentVersion,
|
||||||
current_value: currentVersion,
|
required_value: supportedVersion
|
||||||
required_value: supportedVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
`[VersionUtil] Failed to parse version requirement: ${supportedVersion}`,
|
|
||||||
error
|
|
||||||
)
|
|
||||||
// On error, assume incompatible to be safe
|
|
||||||
return {
|
|
||||||
type,
|
|
||||||
current_value: currentVersion,
|
|
||||||
required_value: supportedVersion
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get frontend version from config.
|
||||||
|
* @returns frontend version string or undefined
|
||||||
|
*/
|
||||||
|
export function getFrontendVersion(): string | undefined {
|
||||||
|
return config.app_version || import.meta.env.VITE_APP_VERSION || undefined
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user