diff --git a/src/components/dialog/content/manager/button/PackInstallButton.vue b/src/components/dialog/content/manager/button/PackInstallButton.vue index 4228eb92e2..0224c1cb5b 100644 --- a/src/components/dialog/content/manager/button/PackInstallButton.vue +++ b/src/components/dialog/content/manager/button/PackInstallButton.vue @@ -18,6 +18,7 @@ import { inject, ref } from 'vue' import PackActionButton from '@/components/dialog/content/manager/button/PackActionButton.vue' +import { useConflictDetection } from '@/composables/useConflictDetection' import { t } from '@/i18n' import { useDialogService } from '@/services/dialogService' import { useComfyManagerStore } from '@/stores/comfyManagerStore' @@ -75,15 +76,20 @@ const installAllPacks = async () => { if (!nodePacks?.length) return if (hasConflict && conflictInfo) { - const conflictedPackages: ConflictDetectionResult[] = nodePacks.map( - (pack) => ({ - package_id: pack.id || '', - package_name: pack.name || '', - has_conflict: true, - conflicts: conflictInfo || [], - is_compatible: false + // Check each package individually for conflicts + const { checkNodeCompatibility } = useConflictDetection() + const conflictedPackages: ConflictDetectionResult[] = nodePacks + .map((pack) => { + const compatibilityCheck = checkNodeCompatibility(pack) + return { + package_id: pack.id || '', + package_name: pack.name || '', + has_conflict: compatibilityCheck.hasConflict, + conflicts: compatibilityCheck.conflicts, + is_compatible: !compatibilityCheck.hasConflict + } }) - ) + .filter((result) => result.has_conflict) // Only show packages with conflicts showNodeConflictDialog({ conflictedPackages, diff --git a/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue b/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue index f89628dce5..692650435c 100644 --- a/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue +++ b/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue @@ -14,12 +14,32 @@
- + import { useAsyncState } from '@vueuse/core' -import { computed, onUnmounted } from 'vue' +import { computed, onUnmounted, provide } from 'vue' import PackStatusMessage from '@/components/dialog/content/manager/PackStatusMessage.vue' import PackInstallButton from '@/components/dialog/content/manager/button/PackInstallButton.vue' +import PackUninstallButton from '@/components/dialog/content/manager/button/PackUninstallButton.vue' import InfoPanelHeader from '@/components/dialog/content/manager/infoPanel/InfoPanelHeader.vue' import MetadataRow from '@/components/dialog/content/manager/infoPanel/MetadataRow.vue' import PackIconStacked from '@/components/dialog/content/manager/packIcon/PackIconStacked.vue' +import { useConflictDetection } from '@/composables/useConflictDetection' +import { useComfyManagerStore } from '@/stores/comfyManagerStore' import { useComfyRegistryStore } from '@/stores/comfyRegistryStore' +import { useConflictDetectionStore } from '@/stores/conflictDetectionStore' import { components } from '@/types/comfyRegistryTypes' +import type { ConflictDetail } from '@/types/conflictDetectionTypes' +import { ImportFailedKey } from '@/types/importFailedTypes' const { nodePacks } = defineProps<{ nodePacks: components['schemas']['Node'][] }>() +const managerStore = useComfyManagerStore() +const conflictDetectionStore = useConflictDetectionStore() +const { checkNodeCompatibility } = useConflictDetection() + const { getNodeDefs } = useComfyRegistryStore() +// Check if any package has import failed status +const hasImportFailed = computed(() => { + return nodePacks.some((pack) => { + if (!pack.id) return false + const conflicts = conflictDetectionStore.getConflictsForPackageByID(pack.id) + return ( + conflicts?.conflicts?.some((c) => c.type === 'import_failed') || false + ) + }) +}) + +// Provide import failed context for PackStatusMessage +provide(ImportFailedKey, { + importFailed: hasImportFailed, + showImportFailedDialog: () => {} // No-op for multi-selection +}) + +// Check installation status +const installedPacks = computed(() => + nodePacks.filter((pack) => managerStore.isPackInstalled(pack.id)) +) + +const notInstalledPacks = computed(() => + nodePacks.filter((pack) => !managerStore.isPackInstalled(pack.id)) +) + +const isAllInstalled = computed( + () => installedPacks.value.length === nodePacks.length +) + +const isNoneInstalled = computed( + () => notInstalledPacks.value.length === nodePacks.length +) + +const isMixed = computed( + () => installedPacks.value.length > 0 && notInstalledPacks.value.length > 0 +) + +// Check for conflicts in not-installed packages - store per package +const packageConflicts = computed(() => { + const conflictsByPackage = new Map() + + for (const pack of notInstalledPacks.value) { + const compatibilityCheck = checkNodeCompatibility(pack) + if (compatibilityCheck.hasConflict && pack.id) { + conflictsByPackage.set(pack.id, compatibilityCheck.conflicts) + } + } + + return conflictsByPackage +}) + +// Aggregate all unique conflicts for display +const conflictInfo = computed(() => { + const conflictMap = new Map() + + packageConflicts.value.forEach((conflicts) => { + conflicts.forEach((conflict) => { + const key = `${conflict.type}-${conflict.current_value}-${conflict.required_value}` + if (!conflictMap.has(key)) { + conflictMap.set(key, conflict) + } + }) + }) + + return Array.from(conflictMap.values()) +}) + +const hasConflicts = computed(() => conflictInfo.value.length > 0) + +// Determine the most important status from all selected packages +const overallStatus = computed(() => { + // Check for import failed first (highest priority for installed packages) + if (hasImportFailed.value) { + // Import failed doesn't have a specific status enum, so we return active + // but the PackStatusMessage will handle it via hasImportFailed prop + return 'NodeVersionStatusActive' as components['schemas']['NodeVersionStatus'] + } + + // Priority order: banned > deleted > flagged > pending > active + const statusPriority = [ + 'NodeStatusBanned', + 'NodeVersionStatusBanned', + 'NodeStatusDeleted', + 'NodeVersionStatusDeleted', + 'NodeVersionStatusFlagged', + 'NodeVersionStatusPending', + 'NodeStatusActive', + 'NodeVersionStatusActive' + ] + + for (const priorityStatus of statusPriority) { + if (nodePacks.some((pack) => pack.status === priorityStatus)) { + return priorityStatus as + | components['schemas']['NodeStatus'] + | components['schemas']['NodeVersionStatus'] + } + } + + // Default to active if no specific status found + return 'NodeVersionStatusActive' as components['schemas']['NodeVersionStatus'] +}) + const getPackNodes = async (pack: components['schemas']['Node']) => { if (!pack.latest_version?.version) return [] const nodeDefs = await getNodeDefs.call({ diff --git a/src/components/dialog/content/manager/packIcon/PackIcon.vue b/src/components/dialog/content/manager/packIcon/PackIcon.vue index 71ec4a400e..ae6d188c20 100644 --- a/src/components/dialog/content/manager/packIcon/PackIcon.vue +++ b/src/components/dialog/content/manager/packIcon/PackIcon.vue @@ -13,7 +13,7 @@