From 36027a858ff0cbdb978ca0f22b5f3a6b3534dfab Mon Sep 17 00:00:00 2001 From: Comfy Org PR Bot Date: Sat, 20 Dec 2025 09:21:11 +0900 Subject: [PATCH] [backport core/1.35] feat(manager): add Try Update button for nightly packs (#7635) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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> --- src/locales/en/main.json | 2 + .../components/manager/PackVersionBadge.vue | 2 +- .../manager/button/PackTryUpdateButton.vue | 63 ++++++++++++++++++ .../manager/infoPanel/InfoPanel.vue | 14 +++- .../manager/infoPanel/InfoPanelMultiItem.vue | 64 +++++++++++++++++-- .../nodePack/usePackUpdateStatus.ts | 27 ++++++-- .../composables/nodePack/usePacksSelection.ts | 21 +++++- 7 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 src/workbench/extensions/manager/components/manager/button/PackTryUpdateButton.vue diff --git a/src/locales/en/main.json b/src/locales/en/main.json index e68328269..977954f31 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -294,6 +294,8 @@ "uninstall": "Uninstall", "uninstalling": "Uninstalling {id}", "update": "Update", + "tryUpdate": "Try Update", + "tryUpdateTooltip": "Pull latest changes from repository. Nightly versions may have updates that cannot be detected automatically.", "uninstallSelected": "Uninstall Selected", "updateSelected": "Update Selected", "updateAll": "Update All", diff --git a/src/workbench/extensions/manager/components/manager/PackVersionBadge.vue b/src/workbench/extensions/manager/components/manager/PackVersionBadge.vue index 6c59af95d..3d22daaeb 100644 --- a/src/workbench/extensions/manager/components/manager/PackVersionBadge.vue +++ b/src/workbench/extensions/manager/components/manager/PackVersionBadge.vue @@ -63,7 +63,7 @@ const { fill?: boolean }>() -const { isUpdateAvailable } = usePackUpdateStatus(nodePack) +const { isUpdateAvailable } = usePackUpdateStatus(() => nodePack) const popoverRef = ref() const managerStore = useComfyManagerStore() diff --git a/src/workbench/extensions/manager/components/manager/button/PackTryUpdateButton.vue b/src/workbench/extensions/manager/components/manager/button/PackTryUpdateButton.vue new file mode 100644 index 000000000..4e21364a2 --- /dev/null +++ b/src/workbench/extensions/manager/components/manager/button/PackTryUpdateButton.vue @@ -0,0 +1,63 @@ + + + diff --git a/src/workbench/extensions/manager/components/manager/infoPanel/InfoPanel.vue b/src/workbench/extensions/manager/components/manager/infoPanel/InfoPanel.vue index 16a712792..9f25902cd 100644 --- a/src/workbench/extensions/manager/components/manager/infoPanel/InfoPanel.vue +++ b/src/workbench/extensions/manager/components/manager/infoPanel/InfoPanel.vue @@ -5,7 +5,14 @@ + > + +
{ isInstalling.value = false }) +const { canTryNightlyUpdate } = usePackUpdateStatus(() => nodePack) + const { checkNodeCompatibility } = useConflictDetection() const { getConflictsForPackageByID } = useConflictDetectionStore() diff --git a/src/workbench/extensions/manager/components/manager/infoPanel/InfoPanelMultiItem.vue b/src/workbench/extensions/manager/components/manager/infoPanel/InfoPanelMultiItem.vue index 26651d535..e75a0208d 100644 --- a/src/workbench/extensions/manager/components/manager/infoPanel/InfoPanelMultiItem.vue +++ b/src/workbench/extensions/manager/components/manager/infoPanel/InfoPanelMultiItem.vue @@ -18,12 +18,27 @@
{{ $t('manager.mixedSelectionMessage') }}
- - +
+ class="flex w-full justify-center gap-2" + > + + + + +
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 type { components } from '@/types/comfyRegistryTypes' 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 { usePacksStatus } from '@/workbench/extensions/manager/composables/nodePack/usePacksStatus' 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 { ImportFailedKey } from '@/workbench/extensions/manager/types/importFailedTypes' @@ -75,6 +94,8 @@ const { nodePacks } = defineProps<{ nodePacks: components['schemas']['Node'][] }>() +const { t } = useI18n() +const managerStore = useComfyManagerStore() const nodePacksRef = toRef(() => nodePacks) // Use new composables for cleaner code @@ -83,11 +104,40 @@ const { notInstalledPacks, isAllInstalled, isNoneInstalled, - isMixed + isMixed, + nightlyPacks, + hasNightlyPacks } = usePacksSelection(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 { getNodeDefs } = useComfyRegistryStore() diff --git a/src/workbench/extensions/manager/composables/nodePack/usePackUpdateStatus.ts b/src/workbench/extensions/manager/composables/nodePack/usePackUpdateStatus.ts index 5aec2700d..5286169a6 100644 --- a/src/workbench/extensions/manager/composables/nodePack/usePackUpdateStatus.ts +++ b/src/workbench/extensions/manager/composables/nodePack/usePackUpdateStatus.ts @@ -1,19 +1,26 @@ +import { toValue } from '@vueuse/core' import { compare, valid } from 'semver' +import type { MaybeRefOrGetter } from 'vue' import { computed } from 'vue' import type { components } from '@/types/comfyRegistryTypes' import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore' export const usePackUpdateStatus = ( - nodePack: components['schemas']['Node'] + nodePackSource: MaybeRefOrGetter ) => { - 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(() => - 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( () => !!installedVersion.value && !valid(installedVersion.value) @@ -31,9 +38,19 @@ export const usePackUpdateStatus = ( 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 { isUpdateAvailable, isNightlyPack, + canTryNightlyUpdate, installedVersion, latestVersion } diff --git a/src/workbench/extensions/manager/composables/nodePack/usePacksSelection.ts b/src/workbench/extensions/manager/composables/nodePack/usePacksSelection.ts index 760d2ba5a..814db951f 100644 --- a/src/workbench/extensions/manager/composables/nodePack/usePacksSelection.ts +++ b/src/workbench/extensions/manager/composables/nodePack/usePacksSelection.ts @@ -1,3 +1,4 @@ +import { valid } from 'semver' import { computed } from 'vue' import type { Ref } from 'vue' @@ -41,12 +42,30 @@ export function usePacksSelection(nodePacks: Ref) { 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 { installedPacks, notInstalledPacks, isAllInstalled, isNoneInstalled, isMixed, - selectionState + selectionState, + nightlyPacks, + hasNightlyPacks } }