mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-04 07:00:23 +00:00
refactor: conflict detect
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
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 { useInstalledPacks } from '@/composables/nodePack/useInstalledPacks'
|
||||
import { useConflictAcknowledgment } from '@/composables/useConflictAcknowledgment'
|
||||
import config from '@/config'
|
||||
import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||
import { useComfyRegistryService } from '@/services/comfyRegistryService'
|
||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||
@@ -16,16 +15,25 @@ import type {
|
||||
ConflictDetectionResponse,
|
||||
ConflictDetectionResult,
|
||||
ConflictDetectionSummary,
|
||||
ConflictType,
|
||||
Node,
|
||||
NodeRequirements,
|
||||
SystemEnvironment
|
||||
} from '@/types/conflictDetectionTypes'
|
||||
import { normalizePackId } from '@/utils/packUtils'
|
||||
import {
|
||||
cleanVersion,
|
||||
satisfiesVersion,
|
||||
utilCheckVersionCompatibility
|
||||
consolidateConflictsByPackage,
|
||||
createBannedConflict,
|
||||
createPendingConflict,
|
||||
generateConflictSummary
|
||||
} from '@/utils/conflictUtils'
|
||||
import {
|
||||
checkAcceleratorCompatibility,
|
||||
checkOSCompatibility,
|
||||
normalizeAcceleratorList,
|
||||
normalizeOSList
|
||||
} from '@/utils/simpleCompatibility'
|
||||
import {
|
||||
checkVersionCompatibility,
|
||||
getFrontendVersion
|
||||
} from '@/utils/versionUtil'
|
||||
|
||||
/**
|
||||
@@ -51,7 +59,7 @@ export function useConflictDetection() {
|
||||
const detectionResults = ref<ConflictDetectionResult[]>([])
|
||||
// Store merged conflicts separately for testing
|
||||
const storedMergedConflicts = ref<ConflictDetectionResult[]>([])
|
||||
const detectionSummary = ref<ConflictDetectionSummary | null>(null)
|
||||
const detectionSummary = ref<ConflictDetectionSummary | undefined>(undefined)
|
||||
|
||||
// Registry API request cancellation
|
||||
const abortController = ref<AbortController | null>(null)
|
||||
@@ -83,7 +91,7 @@ export function useConflictDetection() {
|
||||
useSystemStatsStore()
|
||||
await until(systemStatsInitialized)
|
||||
|
||||
const frontendVersion = await fetchFrontendVersion()
|
||||
const frontendVersion = getFrontendVersion()
|
||||
|
||||
const environment: SystemEnvironment = {
|
||||
comfyui_version: systemStats?.system.comfyui_version ?? '',
|
||||
@@ -199,9 +207,9 @@ export function useConflictDetection() {
|
||||
const isEnabled = managerStore.isPackEnabled(installedPackVersion.id)
|
||||
|
||||
// Find the pack info from Registry if available
|
||||
const packInfo = installedPacks.value.find(
|
||||
(p) => p.id === installedPackVersion.id
|
||||
)
|
||||
const packInfo = find(installedPacks.value, {
|
||||
id: installedPackVersion.id
|
||||
})
|
||||
|
||||
if (versionData) {
|
||||
// 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_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,
|
||||
|
||||
// Status information
|
||||
@@ -268,51 +278,35 @@ export function useConflictDetection() {
|
||||
): ConflictDetectionResult {
|
||||
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
|
||||
if (!isCompatibleWithAll(packageReq.supported_comfyui_version)) {
|
||||
const versionConflict = checkVersionConflict(
|
||||
'comfyui_version',
|
||||
systemEnvInfo.comfyui_version,
|
||||
packageReq.supported_comfyui_version!
|
||||
)
|
||||
if (versionConflict) conflicts.push(versionConflict)
|
||||
}
|
||||
const versionConflict = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
systemEnvInfo.comfyui_version,
|
||||
packageReq.supported_comfyui_version
|
||||
)
|
||||
if (versionConflict) conflicts.push(versionConflict)
|
||||
|
||||
// 2. Frontend version conflict check
|
||||
if (!isCompatibleWithAll(packageReq.supported_comfyui_frontend_version)) {
|
||||
const versionConflict = checkVersionConflict(
|
||||
'frontend_version',
|
||||
systemEnvInfo.frontend_version,
|
||||
packageReq.supported_comfyui_frontend_version!
|
||||
)
|
||||
if (versionConflict) conflicts.push(versionConflict)
|
||||
}
|
||||
const frontendConflict = checkVersionCompatibility(
|
||||
'frontend_version',
|
||||
systemEnvInfo.frontend_version,
|
||||
packageReq.supported_comfyui_frontend_version
|
||||
)
|
||||
if (frontendConflict) conflicts.push(frontendConflict)
|
||||
|
||||
// 3. OS compatibility check
|
||||
if (!isCompatibleWithAll(packageReq.supported_os)) {
|
||||
const osConflict = checkOSConflict(
|
||||
packageReq.supported_os!,
|
||||
systemEnvInfo.os
|
||||
)
|
||||
if (osConflict) conflicts.push(osConflict)
|
||||
}
|
||||
const osConflict = checkOSCompatibility(
|
||||
packageReq.supported_os as any,
|
||||
systemEnvInfo.os
|
||||
)
|
||||
if (osConflict) conflicts.push(osConflict)
|
||||
|
||||
// 4. Accelerator compatibility check
|
||||
if (!isCompatibleWithAll(packageReq.supported_accelerators)) {
|
||||
const acceleratorConflict = checkAcceleratorConflict(
|
||||
packageReq.supported_accelerators!,
|
||||
systemEnvInfo.accelerator
|
||||
)
|
||||
if (acceleratorConflict) conflicts.push(acceleratorConflict)
|
||||
}
|
||||
const acceleratorConflict = checkAcceleratorCompatibility(
|
||||
packageReq.supported_accelerators as any,
|
||||
systemEnvInfo.accelerator
|
||||
)
|
||||
if (acceleratorConflict) conflicts.push(acceleratorConflict)
|
||||
|
||||
// 5. Banned package check using shared logic
|
||||
const bannedConflict = createBannedConflict(packageReq.is_banned)
|
||||
@@ -496,7 +490,10 @@ export function useConflictDetection() {
|
||||
const allResults = [...packageResults, ...importFailResults]
|
||||
|
||||
// 6. Generate summary information
|
||||
const summary = generateSummary(allResults, Date.now() - startTime)
|
||||
const summary = generateConflictSummary(
|
||||
allResults,
|
||||
Date.now() - startTime
|
||||
)
|
||||
|
||||
// 7. Update state
|
||||
detectionResults.value = allResults
|
||||
@@ -516,7 +513,7 @@ export function useConflictDetection() {
|
||||
)
|
||||
|
||||
// Merge conflicts for packages with the same name
|
||||
const mergedConflicts = mergeConflictsByPackageName(conflictedResults)
|
||||
const mergedConflicts = consolidateConflictsByPackage(conflictedResults)
|
||||
|
||||
console.debug(
|
||||
'[ConflictDetection] Conflicts detected (stored for UI):',
|
||||
@@ -567,7 +564,7 @@ export function useConflictDetection() {
|
||||
return {
|
||||
success: false,
|
||||
error_message: detectionError.value,
|
||||
summary: detectionSummary.value || generateEmptySummary(),
|
||||
summary: detectionSummary.value,
|
||||
results: []
|
||||
}
|
||||
} finally {
|
||||
@@ -663,7 +660,7 @@ export function useConflictDetection() {
|
||||
* Check compatibility for a node.
|
||||
* Used by components like PackVersionSelectorPopover.
|
||||
*/
|
||||
async function checkNodeCompatibility(
|
||||
function checkNodeCompatibility(
|
||||
node: Node | components['schemas']['NodeVersion']
|
||||
) {
|
||||
const systemStatsStore = useSystemStatsStore()
|
||||
@@ -672,17 +669,20 @@ export function useConflictDetection() {
|
||||
|
||||
const conflicts: ConflictDetail[] = []
|
||||
|
||||
// Check OS compatibility using centralized function
|
||||
// Check OS compatibility
|
||||
const currentOS = systemStats.system?.os
|
||||
const OSConflict = checkOSConflict(node.supported_os, currentOS)
|
||||
if (OSConflict) {
|
||||
conflicts.push(OSConflict)
|
||||
const osConflict = checkOSCompatibility(
|
||||
normalizeOSList(node.supported_os),
|
||||
currentOS
|
||||
)
|
||||
if (osConflict) {
|
||||
conflicts.push(osConflict)
|
||||
}
|
||||
|
||||
// Check Accelerator compatibility using centralized function
|
||||
const currentAccelerator = systemStats.devices?.[0].type
|
||||
const acceleratorConflict = checkAcceleratorConflict(
|
||||
node.supported_accelerators,
|
||||
// Check Accelerator compatibility
|
||||
const currentAccelerator = systemStats.devices?.[0]?.type
|
||||
const acceleratorConflict = checkAcceleratorCompatibility(
|
||||
normalizeAcceleratorList(node.supported_accelerators),
|
||||
currentAccelerator
|
||||
)
|
||||
if (acceleratorConflict) {
|
||||
@@ -691,7 +691,7 @@ export function useConflictDetection() {
|
||||
|
||||
// Check ComfyUI version compatibility
|
||||
const currentComfyUIVersion = systemStats.system?.comfyui_version
|
||||
const comfyUIVersionConflict = utilCheckVersionCompatibility(
|
||||
const comfyUIVersionConflict = checkVersionCompatibility(
|
||||
'comfyui_version',
|
||||
currentComfyUIVersion,
|
||||
node.supported_comfyui_version
|
||||
@@ -701,8 +701,8 @@ export function useConflictDetection() {
|
||||
}
|
||||
|
||||
// Check ComfyUI Frontend version compatibility
|
||||
const currentFrontendVersion = await fetchFrontendVersion()
|
||||
const frontendVersionConflict = utilCheckVersionCompatibility(
|
||||
const currentFrontendVersion = getFrontendVersion()
|
||||
const frontendVersionConflict = checkVersionCompatibility(
|
||||
'frontend_version',
|
||||
currentFrontendVersion,
|
||||
node.supported_comfyui_frontend_version
|
||||
@@ -760,348 +760,3 @@ export function useConflictDetection() {
|
||||
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 {
|
||||
success: boolean
|
||||
error_message?: string
|
||||
summary: ConflictDetectionSummary
|
||||
summary?: ConflictDetectionSummary
|
||||
results: ConflictDetectionResult[]
|
||||
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 config from '@/config'
|
||||
import type {
|
||||
ConflictDetail,
|
||||
ConflictType
|
||||
@@ -37,47 +39,38 @@ export function satisfiesVersion(version: string, range: string): boolean {
|
||||
* @param supportedVersion Required version range string
|
||||
* @returns ConflictDetail object if incompatible, null if compatible
|
||||
*/
|
||||
export function utilCheckVersionCompatibility(
|
||||
export function checkVersionCompatibility(
|
||||
type: ConflictType,
|
||||
currentVersion: string,
|
||||
currentVersion?: string,
|
||||
supportedVersion?: string
|
||||
): ConflictDetail | null {
|
||||
// If current version is unknown, assume compatible (no conflict)
|
||||
if (!currentVersion || currentVersion === 'unknown') {
|
||||
// Use es-toolkit for null/empty checks
|
||||
if (isNil(currentVersion) || isEmpty(currentVersion)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// If no version requirement specified, assume compatible (no conflict)
|
||||
if (!supportedVersion || supportedVersion.trim() === '') {
|
||||
// Use es-toolkit for supported version validation
|
||||
if (isNil(supportedVersion) || isEmpty(supportedVersion?.trim())) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
// Clean the current version using semver utilities
|
||||
const cleanCurrent = cleanVersion(currentVersion)
|
||||
// Clean and check version compatibility
|
||||
const cleanCurrent = cleanVersion(currentVersion)
|
||||
const isCompatible = satisfiesVersion(cleanCurrent, supportedVersion)
|
||||
|
||||
// Check version compatibility using semver library
|
||||
const isCompatible = satisfiesVersion(cleanCurrent, supportedVersion)
|
||||
if (isCompatible) return null
|
||||
|
||||
if (!isCompatible) {
|
||||
return {
|
||||
type,
|
||||
current_value: currentVersion,
|
||||
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
|
||||
}
|
||||
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