mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-23 15:59:47 +00:00
[backport core/1.35] feat(manager): add Try Update button for nightly packs (#7635)
Backport of #7610 to `core/1.35` Automatically created by backport workflow. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7635-backport-core-1-35-feat-manager-add-Try-Update-button-for-nightly-packs-2ce6d73d36508135a070e17689cee5e7) by [Unito](https://www.unito.io) Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
This commit is contained in:
@@ -294,6 +294,8 @@
|
|||||||
"uninstall": "Uninstall",
|
"uninstall": "Uninstall",
|
||||||
"uninstalling": "Uninstalling {id}",
|
"uninstalling": "Uninstalling {id}",
|
||||||
"update": "Update",
|
"update": "Update",
|
||||||
|
"tryUpdate": "Try Update",
|
||||||
|
"tryUpdateTooltip": "Pull latest changes from repository. Nightly versions may have updates that cannot be detected automatically.",
|
||||||
"uninstallSelected": "Uninstall Selected",
|
"uninstallSelected": "Uninstall Selected",
|
||||||
"updateSelected": "Update Selected",
|
"updateSelected": "Update Selected",
|
||||||
"updateAll": "Update All",
|
"updateAll": "Update All",
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const {
|
|||||||
fill?: boolean
|
fill?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { isUpdateAvailable } = usePackUpdateStatus(nodePack)
|
const { isUpdateAvailable } = usePackUpdateStatus(() => nodePack)
|
||||||
const popoverRef = ref()
|
const popoverRef = ref()
|
||||||
|
|
||||||
const managerStore = useComfyManagerStore()
|
const managerStore = useComfyManagerStore()
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<IconTextButton
|
||||||
|
v-tooltip.top="$t('manager.tryUpdateTooltip')"
|
||||||
|
v-bind="$attrs"
|
||||||
|
type="transparent"
|
||||||
|
:label="computedLabel"
|
||||||
|
:border="true"
|
||||||
|
:size="size"
|
||||||
|
:disabled="isUpdating"
|
||||||
|
@click="tryUpdate"
|
||||||
|
>
|
||||||
|
<template v-if="isUpdating" #icon>
|
||||||
|
<DotSpinner duration="1s" :size="size === 'sm' ? 12 : 16" />
|
||||||
|
</template>
|
||||||
|
</IconTextButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||||
|
import DotSpinner from '@/components/common/DotSpinner.vue'
|
||||||
|
import type { ButtonSize } from '@/types/buttonTypes'
|
||||||
|
import type { components } from '@/types/comfyRegistryTypes'
|
||||||
|
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||||
|
|
||||||
|
type NodePack = components['schemas']['Node']
|
||||||
|
|
||||||
|
const { nodePack, size = 'sm' } = defineProps<{
|
||||||
|
nodePack: NodePack
|
||||||
|
size?: ButtonSize
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const managerStore = useComfyManagerStore()
|
||||||
|
|
||||||
|
const isUpdating = ref(false)
|
||||||
|
|
||||||
|
async function tryUpdate() {
|
||||||
|
if (!nodePack.id) {
|
||||||
|
console.warn('Pack missing required id:', nodePack)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isUpdating.value = true
|
||||||
|
try {
|
||||||
|
await managerStore.updatePack.call({
|
||||||
|
id: nodePack.id,
|
||||||
|
version: 'nightly'
|
||||||
|
})
|
||||||
|
managerStore.updatePack.clear()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Nightly update failed:', error)
|
||||||
|
} finally {
|
||||||
|
isUpdating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const computedLabel = computed(() =>
|
||||||
|
isUpdating.value ? t('g.updating') : t('manager.tryUpdate')
|
||||||
|
)
|
||||||
|
</script>
|
||||||
@@ -5,7 +5,14 @@
|
|||||||
<InfoPanelHeader
|
<InfoPanelHeader
|
||||||
:node-packs="[nodePack]"
|
:node-packs="[nodePack]"
|
||||||
:has-conflict="hasCompatibilityIssues"
|
:has-conflict="hasCompatibilityIssues"
|
||||||
/>
|
>
|
||||||
|
<template v-if="canTryNightlyUpdate" #install-button>
|
||||||
|
<div class="flex w-full justify-center gap-2">
|
||||||
|
<PackTryUpdateButton :node-pack="nodePack" size="md" />
|
||||||
|
<PackUninstallButton :node-packs="[nodePack]" size="md" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</InfoPanelHeader>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
ref="scrollContainer"
|
ref="scrollContainer"
|
||||||
@@ -68,9 +75,12 @@ import type { components } from '@/types/comfyRegistryTypes'
|
|||||||
import PackStatusMessage from '@/workbench/extensions/manager/components/manager/PackStatusMessage.vue'
|
import PackStatusMessage from '@/workbench/extensions/manager/components/manager/PackStatusMessage.vue'
|
||||||
import PackVersionBadge from '@/workbench/extensions/manager/components/manager/PackVersionBadge.vue'
|
import PackVersionBadge from '@/workbench/extensions/manager/components/manager/PackVersionBadge.vue'
|
||||||
import PackEnableToggle from '@/workbench/extensions/manager/components/manager/button/PackEnableToggle.vue'
|
import PackEnableToggle from '@/workbench/extensions/manager/components/manager/button/PackEnableToggle.vue'
|
||||||
|
import PackTryUpdateButton from '@/workbench/extensions/manager/components/manager/button/PackTryUpdateButton.vue'
|
||||||
|
import PackUninstallButton from '@/workbench/extensions/manager/components/manager/button/PackUninstallButton.vue'
|
||||||
import InfoPanelHeader from '@/workbench/extensions/manager/components/manager/infoPanel/InfoPanelHeader.vue'
|
import InfoPanelHeader from '@/workbench/extensions/manager/components/manager/infoPanel/InfoPanelHeader.vue'
|
||||||
import InfoTabs from '@/workbench/extensions/manager/components/manager/infoPanel/InfoTabs.vue'
|
import InfoTabs from '@/workbench/extensions/manager/components/manager/infoPanel/InfoTabs.vue'
|
||||||
import MetadataRow from '@/workbench/extensions/manager/components/manager/infoPanel/MetadataRow.vue'
|
import MetadataRow from '@/workbench/extensions/manager/components/manager/infoPanel/MetadataRow.vue'
|
||||||
|
import { usePackUpdateStatus } from '@/workbench/extensions/manager/composables/nodePack/usePackUpdateStatus'
|
||||||
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
||||||
import { useImportFailedDetection } from '@/workbench/extensions/manager/composables/useImportFailedDetection'
|
import { useImportFailedDetection } from '@/workbench/extensions/manager/composables/useImportFailedDetection'
|
||||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||||
@@ -99,6 +109,8 @@ whenever(isInstalled, () => {
|
|||||||
isInstalling.value = false
|
isInstalling.value = false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { canTryNightlyUpdate } = usePackUpdateStatus(() => nodePack)
|
||||||
|
|
||||||
const { checkNodeCompatibility } = useConflictDetection()
|
const { checkNodeCompatibility } = useConflictDetection()
|
||||||
const { getConflictsForPackageByID } = useConflictDetectionStore()
|
const { getConflictsForPackageByID } = useConflictDetectionStore()
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,27 @@
|
|||||||
<div v-if="isMixed" class="text-sm text-neutral-500">
|
<div v-if="isMixed" class="text-sm text-neutral-500">
|
||||||
{{ $t('manager.mixedSelectionMessage') }}
|
{{ $t('manager.mixedSelectionMessage') }}
|
||||||
</div>
|
</div>
|
||||||
<!-- All installed: Show uninstall button -->
|
<!-- All installed: Show update (if nightly) and uninstall buttons -->
|
||||||
<PackUninstallButton
|
<div
|
||||||
v-else-if="isAllInstalled"
|
v-else-if="isAllInstalled"
|
||||||
size="md"
|
class="flex w-full justify-center gap-2"
|
||||||
:node-packs="installedPacks"
|
>
|
||||||
/>
|
<IconTextButton
|
||||||
|
v-if="hasNightlyPacks"
|
||||||
|
v-tooltip.top="$t('manager.tryUpdateTooltip')"
|
||||||
|
type="transparent"
|
||||||
|
:label="updateSelectedLabel"
|
||||||
|
:border="true"
|
||||||
|
size="md"
|
||||||
|
:disabled="isUpdatingSelected"
|
||||||
|
@click="updateSelectedNightlyPacks"
|
||||||
|
>
|
||||||
|
<template v-if="isUpdatingSelected" #icon>
|
||||||
|
<DotSpinner duration="1s" :size="16" />
|
||||||
|
</template>
|
||||||
|
</IconTextButton>
|
||||||
|
<PackUninstallButton size="md" :node-packs="installedPacks" />
|
||||||
|
</div>
|
||||||
<!-- None installed: Show install button -->
|
<!-- None installed: Show install button -->
|
||||||
<PackInstallButton
|
<PackInstallButton
|
||||||
v-else-if="isNoneInstalled"
|
v-else-if="isNoneInstalled"
|
||||||
@@ -55,8 +70,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAsyncState } from '@vueuse/core'
|
import { useAsyncState } from '@vueuse/core'
|
||||||
import { computed, onUnmounted, provide, toRef } from 'vue'
|
import { computed, onUnmounted, provide, ref, toRef } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||||
|
import DotSpinner from '@/components/common/DotSpinner.vue'
|
||||||
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
|
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
|
||||||
import type { components } from '@/types/comfyRegistryTypes'
|
import type { components } from '@/types/comfyRegistryTypes'
|
||||||
import PackStatusMessage from '@/workbench/extensions/manager/components/manager/PackStatusMessage.vue'
|
import PackStatusMessage from '@/workbench/extensions/manager/components/manager/PackStatusMessage.vue'
|
||||||
@@ -68,6 +86,7 @@ import PackIconStacked from '@/workbench/extensions/manager/components/manager/p
|
|||||||
import { usePacksSelection } from '@/workbench/extensions/manager/composables/nodePack/usePacksSelection'
|
import { usePacksSelection } from '@/workbench/extensions/manager/composables/nodePack/usePacksSelection'
|
||||||
import { usePacksStatus } from '@/workbench/extensions/manager/composables/nodePack/usePacksStatus'
|
import { usePacksStatus } from '@/workbench/extensions/manager/composables/nodePack/usePacksStatus'
|
||||||
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
||||||
|
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||||
import type { ConflictDetail } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
|
import type { ConflictDetail } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
|
||||||
import { ImportFailedKey } from '@/workbench/extensions/manager/types/importFailedTypes'
|
import { ImportFailedKey } from '@/workbench/extensions/manager/types/importFailedTypes'
|
||||||
|
|
||||||
@@ -75,6 +94,8 @@ const { nodePacks } = defineProps<{
|
|||||||
nodePacks: components['schemas']['Node'][]
|
nodePacks: components['schemas']['Node'][]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const managerStore = useComfyManagerStore()
|
||||||
const nodePacksRef = toRef(() => nodePacks)
|
const nodePacksRef = toRef(() => nodePacks)
|
||||||
|
|
||||||
// Use new composables for cleaner code
|
// Use new composables for cleaner code
|
||||||
@@ -83,11 +104,40 @@ const {
|
|||||||
notInstalledPacks,
|
notInstalledPacks,
|
||||||
isAllInstalled,
|
isAllInstalled,
|
||||||
isNoneInstalled,
|
isNoneInstalled,
|
||||||
isMixed
|
isMixed,
|
||||||
|
nightlyPacks,
|
||||||
|
hasNightlyPacks
|
||||||
} = usePacksSelection(nodePacksRef)
|
} = usePacksSelection(nodePacksRef)
|
||||||
|
|
||||||
const { hasImportFailed, overallStatus } = usePacksStatus(nodePacksRef)
|
const { hasImportFailed, overallStatus } = usePacksStatus(nodePacksRef)
|
||||||
|
|
||||||
|
// Batch update state for nightly packs
|
||||||
|
const isUpdatingSelected = ref(false)
|
||||||
|
|
||||||
|
async function updateSelectedNightlyPacks() {
|
||||||
|
if (nightlyPacks.value.length === 0) return
|
||||||
|
|
||||||
|
isUpdatingSelected.value = true
|
||||||
|
try {
|
||||||
|
for (const pack of nightlyPacks.value) {
|
||||||
|
if (!pack.id) continue
|
||||||
|
await managerStore.updatePack.call({
|
||||||
|
id: pack.id,
|
||||||
|
version: 'nightly'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
managerStore.updatePack.clear()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Batch nightly update failed:', error)
|
||||||
|
} finally {
|
||||||
|
isUpdatingSelected.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSelectedLabel = computed(() =>
|
||||||
|
isUpdatingSelected.value ? t('g.updating') : t('manager.updateSelected')
|
||||||
|
)
|
||||||
|
|
||||||
const { checkNodeCompatibility } = useConflictDetection()
|
const { checkNodeCompatibility } = useConflictDetection()
|
||||||
const { getNodeDefs } = useComfyRegistryStore()
|
const { getNodeDefs } = useComfyRegistryStore()
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
|
import { toValue } from '@vueuse/core'
|
||||||
import { compare, valid } from 'semver'
|
import { compare, valid } from 'semver'
|
||||||
|
import type { MaybeRefOrGetter } from 'vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import type { components } from '@/types/comfyRegistryTypes'
|
import type { components } from '@/types/comfyRegistryTypes'
|
||||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||||
|
|
||||||
export const usePackUpdateStatus = (
|
export const usePackUpdateStatus = (
|
||||||
nodePack: components['schemas']['Node']
|
nodePackSource: MaybeRefOrGetter<components['schemas']['Node']>
|
||||||
) => {
|
) => {
|
||||||
const { isPackInstalled, getInstalledPackVersion } = useComfyManagerStore()
|
const { isPackInstalled, isPackEnabled, getInstalledPackVersion } =
|
||||||
|
useComfyManagerStore()
|
||||||
|
|
||||||
const isInstalled = computed(() => isPackInstalled(nodePack?.id))
|
// Use toValue to unwrap the source reactively inside computeds
|
||||||
|
const nodePack = computed(() => toValue(nodePackSource))
|
||||||
|
|
||||||
|
const isInstalled = computed(() => isPackInstalled(nodePack.value?.id))
|
||||||
|
const isEnabled = computed(() => isPackEnabled(nodePack.value?.id))
|
||||||
const installedVersion = computed(() =>
|
const installedVersion = computed(() =>
|
||||||
getInstalledPackVersion(nodePack.id ?? '')
|
getInstalledPackVersion(nodePack.value?.id ?? '')
|
||||||
)
|
)
|
||||||
const latestVersion = computed(() => nodePack.latest_version?.version)
|
const latestVersion = computed(() => nodePack.value?.latest_version?.version)
|
||||||
|
|
||||||
const isNightlyPack = computed(
|
const isNightlyPack = computed(
|
||||||
() => !!installedVersion.value && !valid(installedVersion.value)
|
() => !!installedVersion.value && !valid(installedVersion.value)
|
||||||
@@ -31,9 +38,19 @@ export const usePackUpdateStatus = (
|
|||||||
return compare(latestVersion.value, installedVersion.value) > 0
|
return compare(latestVersion.value, installedVersion.value) > 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nightly packs can always "try update" since we cannot compare git hashes
|
||||||
|
* to determine if an update is actually available. This allows users to
|
||||||
|
* pull the latest changes from the repository.
|
||||||
|
*/
|
||||||
|
const canTryNightlyUpdate = computed(
|
||||||
|
() => isInstalled.value && isEnabled.value && isNightlyPack.value
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isUpdateAvailable,
|
isUpdateAvailable,
|
||||||
isNightlyPack,
|
isNightlyPack,
|
||||||
|
canTryNightlyUpdate,
|
||||||
installedVersion,
|
installedVersion,
|
||||||
latestVersion
|
latestVersion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { valid } from 'semver'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
@@ -41,12 +42,30 @@ export function usePacksSelection(nodePacks: Ref<NodePack[]>) {
|
|||||||
return 'mixed'
|
return 'mixed'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nightly packs are installed packs with a non-semver version (git hash)
|
||||||
|
* that are also enabled
|
||||||
|
*/
|
||||||
|
const nightlyPacks = computed(() =>
|
||||||
|
installedPacks.value.filter((pack) => {
|
||||||
|
if (!pack.id) return false
|
||||||
|
const version = managerStore.getInstalledPackVersion(pack.id)
|
||||||
|
const isNightly = !!version && !valid(version)
|
||||||
|
const isEnabled = managerStore.isPackEnabled(pack.id)
|
||||||
|
return isNightly && isEnabled
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasNightlyPacks = computed(() => nightlyPacks.value.length > 0)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
installedPacks,
|
installedPacks,
|
||||||
notInstalledPacks,
|
notInstalledPacks,
|
||||||
isAllInstalled,
|
isAllInstalled,
|
||||||
isNoneInstalled,
|
isNoneInstalled,
|
||||||
isMixed,
|
isMixed,
|
||||||
selectionState
|
selectionState,
|
||||||
|
nightlyPacks,
|
||||||
|
hasNightlyPacks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user