mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-06 08:00:05 +00:00
[feat] Add import failure detection and error handling for package manager (#4600)
This commit is contained in:
@@ -1,15 +1,66 @@
|
||||
<template>
|
||||
<div class="w-[552px] max-h-[246px] flex flex-col">
|
||||
<div class="w-[552px] flex flex-col">
|
||||
<ContentDivider :width="1" />
|
||||
<div class="px-4 py-6 w-full h-full flex flex-col gap-2">
|
||||
<!-- Description -->
|
||||
<!-- <div>
|
||||
<p class="text-sm leading-4 text-gray-100 m-0 mb-4">
|
||||
<div v-if="showAfterWhatsNew">
|
||||
<p
|
||||
class="text-sm leading-4 text-neutral-800 dark-theme:text-white m-0 mb-4"
|
||||
>
|
||||
{{ $t('manager.conflicts.description') }}
|
||||
<br /><br />
|
||||
{{ $t('manager.conflicts.info') }}
|
||||
</p>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- Import Failed List Wrapper -->
|
||||
<div
|
||||
v-if="importFailedConflicts.length > 0"
|
||||
class="w-full flex flex-col bg-neutral-200 dark-theme:bg-black min-h-8 rounded-lg"
|
||||
>
|
||||
<div
|
||||
class="w-full h-8 flex items-center justify-between gap-2 pl-4"
|
||||
@click="toggleImportFailedPanel"
|
||||
>
|
||||
<div class="flex-1 flex">
|
||||
<span
|
||||
class="text-xs font-bold text-yellow-600 dark-theme:text-yellow-400 mr-2"
|
||||
>{{ importFailedConflicts.length }}</span
|
||||
>
|
||||
<span
|
||||
class="text-xs font-bold text-neutral-600 dark-theme:text-white"
|
||||
>{{ $t('manager.conflicts.importFailedExtensions') }}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
:icon="
|
||||
importFailedExpanded
|
||||
? 'pi pi-chevron-down text-xs'
|
||||
: 'pi pi-chevron-right text-xs'
|
||||
"
|
||||
text
|
||||
class="text-neutral-600 dark-theme:text-neutral-300 !bg-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Import failed list -->
|
||||
<div
|
||||
v-if="importFailedExpanded"
|
||||
class="py-2 px-4 flex flex-col gap-2.5 max-h-[142px] overflow-y-auto scrollbar-hide"
|
||||
>
|
||||
<div
|
||||
v-for="(packageName, i) in importFailedConflicts"
|
||||
:key="i"
|
||||
class="flex items-center justify-between h-6 px-4 flex-shrink-0 conflict-list-item"
|
||||
>
|
||||
<span class="text-xs text-neutral-600 dark-theme:text-neutral-300">
|
||||
{{ packageName }}
|
||||
</span>
|
||||
<span class="pi pi-info-circle text-sm"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Conflict List Wrapper -->
|
||||
<div
|
||||
class="w-full flex flex-col bg-neutral-200 dark-theme:bg-black min-h-8 rounded-lg"
|
||||
@@ -111,48 +162,66 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { filter, flatMap, map, some } from 'lodash'
|
||||
import Button from 'primevue/button'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import ContentDivider from '@/components/common/ContentDivider.vue'
|
||||
import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes'
|
||||
import { useConflictDetection } from '@/composables/useConflictDetection'
|
||||
import { getConflictMessage } from '@/utils/conflictMessageUtil'
|
||||
|
||||
interface Props {
|
||||
conflicts?: ConflictDetectionResult[]
|
||||
conflictedPackages?: ConflictDetectionResult[]
|
||||
showAfterWhatsNew?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
conflicts: () => [],
|
||||
conflictedPackages: () => []
|
||||
const { showAfterWhatsNew } = withDefaults(defineProps<Props>(), {
|
||||
showAfterWhatsNew: false
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const { conflictedPackages } = useConflictDetection()
|
||||
|
||||
const conflictsExpanded = ref<boolean>(false)
|
||||
const extensionsExpanded = ref<boolean>(false)
|
||||
const importFailedExpanded = ref<boolean>(false)
|
||||
|
||||
// Use conflictedPackages if provided, otherwise fallback to conflicts
|
||||
const conflictData = computed(() =>
|
||||
props.conflictedPackages.length > 0
|
||||
? props.conflictedPackages
|
||||
: props.conflicts
|
||||
)
|
||||
const conflictData = computed(() => conflictedPackages.value)
|
||||
|
||||
const allConflictDetails = computed(() =>
|
||||
conflictData.value.flatMap((result) => result.conflicts)
|
||||
)
|
||||
const allConflictDetails = computed(() => {
|
||||
const allConflicts = flatMap(conflictData.value, (result) => result.conflicts)
|
||||
return filter(allConflicts, (conflict) => conflict.type !== 'import_failed')
|
||||
})
|
||||
|
||||
const packagesWithImportFailed = computed(() => {
|
||||
return filter(conflictData.value, (result) =>
|
||||
some(result.conflicts, (conflict) => conflict.type === 'import_failed')
|
||||
)
|
||||
})
|
||||
|
||||
const importFailedConflicts = computed(() => {
|
||||
return map(
|
||||
packagesWithImportFailed.value,
|
||||
(result) => result.package_name || result.package_id
|
||||
)
|
||||
})
|
||||
|
||||
const toggleImportFailedPanel = () => {
|
||||
importFailedExpanded.value = !importFailedExpanded.value
|
||||
conflictsExpanded.value = false
|
||||
extensionsExpanded.value = false
|
||||
}
|
||||
|
||||
const toggleConflictsPanel = () => {
|
||||
conflictsExpanded.value = !conflictsExpanded.value
|
||||
extensionsExpanded.value = false
|
||||
importFailedExpanded.value = false
|
||||
}
|
||||
|
||||
const toggleExtensionsPanel = () => {
|
||||
extensionsExpanded.value = !extensionsExpanded.value
|
||||
conflictsExpanded.value = false
|
||||
importFailedExpanded.value = false
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import Message from 'primevue/message'
|
||||
import { computed } from 'vue'
|
||||
import { computed, inject } from 'vue'
|
||||
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
import { ImportFailedKey } from '@/types/importFailedTypes'
|
||||
|
||||
type PackVersionStatus = components['schemas']['NodeVersionStatus']
|
||||
type PackStatus = components['schemas']['NodeStatus']
|
||||
@@ -37,6 +38,10 @@ const { statusType, hasCompatibilityIssues } = defineProps<{
|
||||
hasCompatibilityIssues?: boolean
|
||||
}>()
|
||||
|
||||
// Inject import failed context from parent
|
||||
const importFailedContext = inject(ImportFailedKey)
|
||||
const importFailed = importFailedContext?.importFailed
|
||||
|
||||
const statusPropsMap: Record<Status, StatusProps> = {
|
||||
NodeStatusActive: {
|
||||
label: 'active',
|
||||
@@ -72,14 +77,14 @@ const statusPropsMap: Record<Status, StatusProps> = {
|
||||
}
|
||||
}
|
||||
|
||||
const statusLabel = computed(() =>
|
||||
hasCompatibilityIssues
|
||||
? 'conflicting'
|
||||
: statusPropsMap[statusType]?.label || 'unknown'
|
||||
)
|
||||
const statusSeverity = computed(() =>
|
||||
hasCompatibilityIssues
|
||||
? 'error'
|
||||
: statusPropsMap[statusType]?.severity || 'secondary'
|
||||
)
|
||||
const statusLabel = computed(() => {
|
||||
if (importFailed?.value) return 'importFailed'
|
||||
if (hasCompatibilityIssues) return 'conflicting'
|
||||
return statusPropsMap[statusType]?.label || 'unknown'
|
||||
})
|
||||
|
||||
const statusSeverity = computed(() => {
|
||||
if (hasCompatibilityIssues || importFailed?.value) return 'error'
|
||||
return statusPropsMap[statusType]?.severity || 'secondary'
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
showDelay: 300
|
||||
}"
|
||||
class="flex items-center justify-center w-6 h-6 cursor-pointer"
|
||||
@click="showConflictModal"
|
||||
@click="showConflictModal(true)"
|
||||
>
|
||||
<i class="pi pi-exclamation-triangle text-yellow-500 text-xl"></i>
|
||||
</div>
|
||||
@@ -70,8 +70,10 @@ const canToggleDirectly = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
const showConflictModal = () => {
|
||||
if (packageConflict.value && !acknowledgmentState.value.modal_dismissed) {
|
||||
const showConflictModal = (skipModalDismissed: boolean) => {
|
||||
let modal_dismissed = acknowledgmentState.value.modal_dismissed
|
||||
if (skipModalDismissed) modal_dismissed = false
|
||||
if (packageConflict.value && !modal_dismissed) {
|
||||
showNodeConflictDialog({
|
||||
conflictedPackages: [packageConflict.value],
|
||||
buttonText: !isEnabled.value
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
import { inject, ref } from 'vue'
|
||||
|
||||
import PackActionButton from '@/components/dialog/content/manager/button/PackActionButton.vue'
|
||||
import { useConflictAcknowledgment } from '@/composables/useConflictAcknowledgment'
|
||||
import { t } from '@/i18n'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||
@@ -42,7 +41,6 @@ const { nodePacks, variant, label, hasConflict, conflictInfo } = defineProps<{
|
||||
|
||||
const isInstalling = inject(IsInstallingKey, ref(false))
|
||||
const managerStore = useComfyManagerStore()
|
||||
const { acknowledgmentState, markConflictsAsSeen } = useConflictAcknowledgment()
|
||||
const { showNodeConflictDialog } = useDialogService()
|
||||
|
||||
const createPayload = (
|
||||
@@ -74,11 +72,7 @@ const installPack = (item: NodePack) =>
|
||||
const installAllPacks = async () => {
|
||||
if (!nodePacks?.length) return
|
||||
|
||||
if (
|
||||
hasConflict &&
|
||||
conflictInfo &&
|
||||
!acknowledgmentState.value.modal_dismissed
|
||||
) {
|
||||
if (hasConflict && conflictInfo) {
|
||||
const conflictedPackages: ConflictDetectionResult[] = nodePacks.map(
|
||||
(pack) => ({
|
||||
package_id: pack.id || '',
|
||||
@@ -96,11 +90,6 @@ const installAllPacks = async () => {
|
||||
// Proceed with installation
|
||||
isInstalling.value = true
|
||||
await performInstallation(nodePacks)
|
||||
},
|
||||
dialogComponentProps: {
|
||||
onClose: () => {
|
||||
markConflictsAsSeen()
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
>
|
||||
<div class="mb-6">
|
||||
<MetadataRow
|
||||
v-if="isPackInstalled(nodePack.id)"
|
||||
v-if="!importFailed && isPackInstalled(nodePack.id)"
|
||||
:label="t('manager.filter.enabled')"
|
||||
class="flex"
|
||||
style="align-items: center"
|
||||
@@ -71,11 +71,13 @@ import InfoPanelHeader from '@/components/dialog/content/manager/infoPanel/InfoP
|
||||
import InfoTabs from '@/components/dialog/content/manager/infoPanel/InfoTabs.vue'
|
||||
import MetadataRow from '@/components/dialog/content/manager/infoPanel/MetadataRow.vue'
|
||||
import { useConflictDetection } from '@/composables/useConflictDetection'
|
||||
import { useImportFailedDetection } from '@/composables/useImportFailedDetection'
|
||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||
import { useConflictDetectionStore } from '@/stores/conflictDetectionStore'
|
||||
import { IsInstallingKey } from '@/types/comfyManagerTypes'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes'
|
||||
import { ImportFailedKey } from '@/types/importFailedTypes'
|
||||
|
||||
interface InfoItem {
|
||||
key: string
|
||||
@@ -129,6 +131,15 @@ const hasCompatibilityIssues = computed(() => {
|
||||
return conflictResult.value?.has_conflict
|
||||
})
|
||||
|
||||
const packageId = computed(() => nodePack.id || '')
|
||||
const { importFailed, showImportFailedDialog } =
|
||||
useImportFailedDetection(packageId)
|
||||
|
||||
provide(ImportFailedKey, {
|
||||
importFailed,
|
||||
showImportFailedDialog
|
||||
})
|
||||
|
||||
const infoItems = computed<InfoItem[]>(() => [
|
||||
{
|
||||
key: 'publisher',
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
<span class="inline-block text-base">{{ nodePacks[0].name }}</span>
|
||||
</slot>
|
||||
</h2>
|
||||
<div class="mt-2 mb-4 w-full max-w-xs flex justify-center">
|
||||
<div
|
||||
v-if="!importFailed"
|
||||
class="mt-2 mb-4 w-full max-w-xs flex justify-center"
|
||||
>
|
||||
<slot name="install-button">
|
||||
<PackUninstallButton
|
||||
v-if="isAllInstalled"
|
||||
@@ -36,7 +39,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { inject, ref, watch } from 'vue'
|
||||
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import PackInstallButton from '@/components/dialog/content/manager/button/PackInstallButton.vue'
|
||||
@@ -44,6 +47,7 @@ import PackUninstallButton from '@/components/dialog/content/manager/button/Pack
|
||||
import PackIcon from '@/components/dialog/content/manager/packIcon/PackIcon.vue'
|
||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
import { ImportFailedKey } from '@/types/importFailedTypes'
|
||||
|
||||
const { nodePacks, hasConflict } = defineProps<{
|
||||
nodePacks: components['schemas']['Node'][]
|
||||
@@ -52,6 +56,10 @@ const { nodePacks, hasConflict } = defineProps<{
|
||||
|
||||
const managerStore = useComfyManagerStore()
|
||||
|
||||
// Inject import failed context from parent
|
||||
const importFailedContext = inject(ImportFailedKey)
|
||||
const importFailed = importFailedContext?.importFailed
|
||||
|
||||
const isAllInstalled = ref(false)
|
||||
watch(
|
||||
[() => nodePacks, () => managerStore.installedPacks],
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<Tab v-if="hasCompatibilityIssues" value="warning" class="p-2 mr-6">
|
||||
<div class="flex items-center gap-1">
|
||||
<span>⚠️</span>
|
||||
{{ $t('g.warning') }}
|
||||
{{ importFailed ? $t('g.error') : $t('g.warning') }}
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="description" class="p-2 mr-6">
|
||||
@@ -43,13 +43,14 @@ import TabList from 'primevue/tablist'
|
||||
import TabPanel from 'primevue/tabpanel'
|
||||
import TabPanels from 'primevue/tabpanels'
|
||||
import Tabs from 'primevue/tabs'
|
||||
import { computed, ref, watchEffect } from 'vue'
|
||||
import { computed, inject, ref, watchEffect } from 'vue'
|
||||
|
||||
import DescriptionTabPanel from '@/components/dialog/content/manager/infoPanel/tabs/DescriptionTabPanel.vue'
|
||||
import NodesTabPanel from '@/components/dialog/content/manager/infoPanel/tabs/NodesTabPanel.vue'
|
||||
import WarningTabPanel from '@/components/dialog/content/manager/infoPanel/tabs/WarningTabPanel.vue'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes'
|
||||
import { ImportFailedKey } from '@/types/importFailedTypes'
|
||||
|
||||
const { nodePack, hasCompatibilityIssues, conflictResult } = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
@@ -57,6 +58,10 @@ const { nodePack, hasCompatibilityIssues, conflictResult } = defineProps<{
|
||||
conflictResult?: ConflictDetectionResult | null
|
||||
}>()
|
||||
|
||||
// Inject import failed context from parent
|
||||
const importFailedContext = inject(ImportFailedKey)
|
||||
const importFailed = importFailedContext?.importFailed
|
||||
|
||||
const nodeNames = computed(() => {
|
||||
// @ts-expect-error comfy_nodes is an Algolia-specific field
|
||||
const { comfy_nodes } = nodePack
|
||||
|
||||
@@ -1,24 +1,44 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-3">
|
||||
<button
|
||||
v-if="importFailedInfo"
|
||||
class="cursor-pointer outline-none border-none inline-flex items-center justify-end bg-transparent gap-1"
|
||||
@click="showImportFailedDialog"
|
||||
>
|
||||
<i class="pi pi-code text-base"></i>
|
||||
<span class="dark-theme:text-white text-sm">{{
|
||||
t('serverStart.openLogs')
|
||||
}}</span>
|
||||
</button>
|
||||
<div
|
||||
v-for="(conflict, index) in conflictResult?.conflicts || []"
|
||||
:key="index"
|
||||
class="p-3 bg-yellow-800/20 rounded-md"
|
||||
>
|
||||
<div class="text-sm break-words">
|
||||
{{ getConflictMessage(conflict, $t) }}
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="text-sm break-words flex-1">
|
||||
{{ getConflictMessage(conflict, $t) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useImportFailedDetection } from '@/composables/useImportFailedDetection'
|
||||
import { t } from '@/i18n'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
import type { ConflictDetectionResult } from '@/types/conflictDetectionTypes'
|
||||
import { ConflictDetectionResult } from '@/types/conflictDetectionTypes'
|
||||
import { getConflictMessage } from '@/utils/conflictMessageUtil'
|
||||
|
||||
const { conflictResult } = defineProps<{
|
||||
const { nodePack, conflictResult } = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
conflictResult?: ConflictDetectionResult | null
|
||||
conflictResult: ConflictDetectionResult | null
|
||||
}>()
|
||||
|
||||
const packageId = computed(() => nodePack?.id || '')
|
||||
const { importFailedInfo, showImportFailedDialog } =
|
||||
useImportFailedDetection(packageId)
|
||||
</script>
|
||||
|
||||
@@ -7,18 +7,31 @@
|
||||
<span>{{ formattedDownloads }}</span>
|
||||
</div>
|
||||
<div class="flex justify-end items-center gap-2">
|
||||
<template v-if="!isInstalled">
|
||||
<PackInstallButton
|
||||
:node-packs="[nodePack]"
|
||||
:has-conflict="uninstalledPackConflict.hasConflict"
|
||||
:conflict-info="uninstalledPackConflict.conflicts"
|
||||
/>
|
||||
<template v-if="importFailed">
|
||||
<div
|
||||
class="flex justify-center items-center gap-2 cursor-pointer"
|
||||
@click="showImportFailedDialog"
|
||||
>
|
||||
<i class="pi pi-exclamation-triangle text-red-500 text-sm"></i>
|
||||
<span class="text-red-500 text-xs pt-0.5">{{
|
||||
t('manager.failedToInstall')
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<PackEnableToggle
|
||||
:node-pack="nodePack"
|
||||
:has-conflict="installedPackHasConflict"
|
||||
/>
|
||||
<template v-if="!isInstalled">
|
||||
<PackInstallButton
|
||||
:node-packs="[nodePack]"
|
||||
:has-conflict="uninstalledPackConflict.hasConflict"
|
||||
:conflict-info="uninstalledPackConflict.conflicts"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<PackEnableToggle
|
||||
:node-pack="nodePack"
|
||||
:has-conflict="installedPackHasConflict"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -31,6 +44,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import PackEnableToggle from '@/components/dialog/content/manager/button/PackEnableToggle.vue'
|
||||
import PackInstallButton from '@/components/dialog/content/manager/button/PackInstallButton.vue'
|
||||
import { useConflictDetection } from '@/composables/useConflictDetection'
|
||||
import { useImportFailedDetection } from '@/composables/useImportFailedDetection'
|
||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||
import { useConflictDetectionStore } from '@/stores/conflictDetectionStore'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
@@ -42,7 +56,7 @@ const { nodePack } = defineProps<{
|
||||
const { isPackInstalled } = useComfyManagerStore()
|
||||
const isInstalled = computed(() => isPackInstalled(nodePack?.id))
|
||||
|
||||
const { n } = useI18n()
|
||||
const { n, t } = useI18n()
|
||||
|
||||
const formattedDownloads = computed(() =>
|
||||
nodePack.downloads ? n(nodePack.downloads) : ''
|
||||
@@ -51,14 +65,17 @@ const formattedDownloads = computed(() =>
|
||||
const { getConflictsForPackageByID } = useConflictDetectionStore()
|
||||
const { checkNodeCompatibility } = useConflictDetection()
|
||||
|
||||
const { importFailed, showImportFailedDialog } = useImportFailedDetection(
|
||||
nodePack.id
|
||||
)
|
||||
|
||||
const conflicts = computed(
|
||||
() => getConflictsForPackageByID(nodePack.id!) || null
|
||||
)
|
||||
|
||||
const installedPackHasConflict = computed(() => {
|
||||
if (!nodePack.id) return false
|
||||
|
||||
// Try exact match first
|
||||
let conflicts = getConflictsForPackageByID(nodePack.id)
|
||||
if (conflicts) return true
|
||||
|
||||
return false
|
||||
return !!conflicts.value
|
||||
})
|
||||
|
||||
const uninstalledPackConflict = computed(() => {
|
||||
|
||||
@@ -124,11 +124,8 @@ const handleWhatsNewDismissed = async () => {
|
||||
* Show the node conflict dialog with current conflict data
|
||||
*/
|
||||
const showConflictModal = () => {
|
||||
const conflictData = {
|
||||
conflictedPackages: conflictDetection.conflictedPackages.value
|
||||
}
|
||||
showNodeConflictDialog({
|
||||
...conflictData,
|
||||
showAfterWhatsNew: true,
|
||||
dialogComponentProps: {
|
||||
onClose: () => {
|
||||
markConflictsAsSeen()
|
||||
|
||||
85
src/composables/useImportFailedDetection.ts
Normal file
85
src/composables/useImportFailedDetection.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { type ComputedRef, computed, unref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||
import { useConflictDetectionStore } from '@/stores/conflictDetectionStore'
|
||||
import type { ConflictDetail } from '@/types/conflictDetectionTypes'
|
||||
|
||||
/**
|
||||
* Extracting import failed conflicts from conflict list
|
||||
*/
|
||||
function extractImportFailedConflicts(conflicts?: ConflictDetail[] | null) {
|
||||
if (!conflicts) return null
|
||||
|
||||
const importFailedConflicts = conflicts.filter(
|
||||
(item): item is ConflictDetail => item.type === 'import_failed'
|
||||
)
|
||||
|
||||
return importFailedConflicts.length > 0 ? importFailedConflicts : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating import failed dialog
|
||||
*/
|
||||
function createImportFailedDialog() {
|
||||
const { t } = useI18n()
|
||||
const { showErrorDialog } = useDialogService()
|
||||
|
||||
return (importFailedInfo: ConflictDetail[] | null) => {
|
||||
if (importFailedInfo) {
|
||||
const errorMessage =
|
||||
importFailedInfo
|
||||
.map((conflict) => conflict.required_value)
|
||||
.filter(Boolean)
|
||||
.join('\n') || t('manager.importFailedGenericError')
|
||||
|
||||
const error = new Error(errorMessage)
|
||||
|
||||
showErrorDialog(error, {
|
||||
title: t('manager.failedToInstall'),
|
||||
reportType: 'importFailedError'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable for detecting and handling import failed conflicts
|
||||
* @param packageId - Package ID string or computed ref
|
||||
* @returns Object with import failed detection and dialog handler
|
||||
*/
|
||||
export function useImportFailedDetection(
|
||||
packageId?: string | ComputedRef<string> | null
|
||||
) {
|
||||
const { isPackInstalled } = useComfyManagerStore()
|
||||
const { getConflictsForPackageByID } = useConflictDetectionStore()
|
||||
|
||||
const isInstalled = computed(() =>
|
||||
packageId ? isPackInstalled(unref(packageId)) : false
|
||||
)
|
||||
|
||||
const conflicts = computed(() => {
|
||||
const currentPackageId = unref(packageId)
|
||||
if (!currentPackageId || !isInstalled.value) return null
|
||||
return getConflictsForPackageByID(currentPackageId) || null
|
||||
})
|
||||
|
||||
const importFailedInfo = computed(() => {
|
||||
return extractImportFailedConflicts(conflicts.value?.conflicts)
|
||||
})
|
||||
|
||||
const importFailed = computed(() => {
|
||||
return importFailedInfo.value !== null
|
||||
})
|
||||
|
||||
const showImportFailedDialog = createImportFailedDialog()
|
||||
|
||||
return {
|
||||
importFailedInfo,
|
||||
importFailed,
|
||||
showImportFailedDialog: () =>
|
||||
showImportFailedDialog(importFailedInfo.value),
|
||||
isInstalled
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,10 @@
|
||||
"legacyMenuNotAvailable": "Legacy manager menu is not available, defaulting to the new manager menu.",
|
||||
"legacyManagerUI": "Use Legacy UI",
|
||||
"legacyManagerUIDescription": "To use the legacy Manager UI, start ComfyUI with --enable-manager-legacy-ui",
|
||||
"failed": "Failed ({count})",
|
||||
"failed": "Failed",
|
||||
"failedToInstall": "Failed to Install",
|
||||
"installError": "Install Error",
|
||||
"importFailedGenericError": "Package failed to import. Check the console for more details.",
|
||||
"noNodesFound": "No nodes found",
|
||||
"noNodesFoundDescription": "The pack's nodes either could not be parsed, or the pack is a frontend extension only and doesn't have any nodes.",
|
||||
"installationQueue": "Installation Queue",
|
||||
@@ -196,7 +199,8 @@
|
||||
"deleted": "Deleted",
|
||||
"banned": "Banned",
|
||||
"unknown": "Unknown",
|
||||
"conflicting": "Conflicting"
|
||||
"conflicting": "Conflicting",
|
||||
"importFailed": "Install Error"
|
||||
},
|
||||
"sort": {
|
||||
"downloads": "Most Popular",
|
||||
@@ -215,6 +219,7 @@
|
||||
"info": "If you continue with the update, the conflicting extensions will be disabled automatically. You can review and manage them anytime in the ComfyUI Manager.",
|
||||
"extensionAtRisk": "Extension at Risk",
|
||||
"conflicts": "Conflicts",
|
||||
"importFailedExtensions": "Import Failed Extensions",
|
||||
"conflictInfoTitle": "Why is this happening?",
|
||||
"installAnyway": "Install Anyway",
|
||||
"enableAnyway": "Enable Anyway",
|
||||
@@ -231,7 +236,8 @@
|
||||
"accelerator": "GPU/Accelerator not supported (available: {current}, required: {required})",
|
||||
"generic": "Compatibility issue (current: {current}, required: {required})",
|
||||
"banned": "This package is banned for security reasons",
|
||||
"pending": "Security verification pending - compatibility cannot be verified"
|
||||
"pending": "Security verification pending - compatibility cannot be verified",
|
||||
"import_failed": "Import Failed"
|
||||
},
|
||||
"warningTooltip": "This package may have compatibility issues with your current environment"
|
||||
}
|
||||
|
||||
@@ -432,14 +432,19 @@ export const useDialogService = () => {
|
||||
}
|
||||
|
||||
function showNodeConflictDialog(
|
||||
options: InstanceType<typeof NodeConflictDialogContent>['$props'] & {
|
||||
options: {
|
||||
showAfterWhatsNew?: boolean
|
||||
dialogComponentProps?: DialogComponentProps
|
||||
buttonText?: string
|
||||
onButtonClick?: () => void
|
||||
} = {}
|
||||
) {
|
||||
const { dialogComponentProps, buttonText, onButtonClick, ...props } =
|
||||
options
|
||||
const {
|
||||
dialogComponentProps,
|
||||
buttonText,
|
||||
onButtonClick,
|
||||
showAfterWhatsNew
|
||||
} = options
|
||||
|
||||
return dialogStore.showDialog({
|
||||
key: 'global-node-conflict',
|
||||
@@ -461,7 +466,9 @@ export const useDialogService = () => {
|
||||
},
|
||||
...dialogComponentProps
|
||||
},
|
||||
props,
|
||||
props: {
|
||||
showAfterWhatsNew
|
||||
},
|
||||
footerProps: {
|
||||
buttonText,
|
||||
onButtonClick
|
||||
|
||||
9
src/types/importFailedTypes.ts
Normal file
9
src/types/importFailedTypes.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { ComputedRef, InjectionKey } from 'vue'
|
||||
|
||||
export interface ImportFailedContext {
|
||||
importFailed: ComputedRef<boolean>
|
||||
showImportFailedDialog: () => void
|
||||
}
|
||||
|
||||
export const ImportFailedKey: InjectionKey<ImportFailedContext> =
|
||||
Symbol('ImportFailed')
|
||||
@@ -27,8 +27,12 @@ export function getConflictMessage(
|
||||
})
|
||||
}
|
||||
|
||||
// For banned and pending, use simple message
|
||||
if (conflict.type === 'banned' || conflict.type === 'pending') {
|
||||
// For banned, pending, and import_failed, use simple message
|
||||
if (
|
||||
conflict.type === 'banned' ||
|
||||
conflict.type === 'pending' ||
|
||||
conflict.type === 'import_failed'
|
||||
) {
|
||||
return t(messageKey)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user