refactor: conflict detect

This commit is contained in:
Jin Yi
2025-09-07 20:39:30 +09:00
parent 7f7189d1d0
commit 3689e5ccce
6 changed files with 370 additions and 443 deletions

View File

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

View 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'

View File

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

View 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
}

View File

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