[Manager] Add 'Missing' and 'In Workflow' tabs (#3133)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Christian Byrne
2025-03-18 17:21:03 -07:00
committed by GitHub
parent 91a8591249
commit 8997ff4b2a
12 changed files with 324 additions and 57 deletions

View File

@@ -0,0 +1,34 @@
import { computed, onUnmounted } from 'vue'
import { useNodePacks } from '@/composables/nodePack/useNodePacks'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import { UseNodePacksOptions } from '@/types/comfyManagerTypes'
import type { components } from '@/types/comfyRegistryTypes'
export const useInstalledPacks = (options: UseNodePacksOptions = {}) => {
const comfyManagerStore = useComfyManagerStore()
const installedPackIds = computed(() =>
Array.from(comfyManagerStore.installedPacksIds)
)
const { startFetch, cleanup, error, isLoading, nodePacks } = useNodePacks(
installedPackIds,
options
)
const filterInstalledPack = (packs: components['schemas']['Node'][]) =>
packs.filter((pack) => comfyManagerStore.isPackInstalled(pack.id))
onUnmounted(() => {
cleanup()
})
return {
error,
isLoading,
installedPacks: nodePacks,
startFetchInstalled: startFetch,
filterInstalledPack
}
}

View File

@@ -0,0 +1,78 @@
import { useAsyncState } from '@vueuse/core'
import { chunk } from 'lodash'
import { Ref, computed, isRef, ref } from 'vue'
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
import { UseNodePacksOptions } from '@/types/comfyManagerTypes'
import { components } from '@/types/comfyRegistryTypes'
const DEFAULT_MAX_CONCURRENT = 6
type NodePack = components['schemas']['Node']
/**
* Handles fetching node packs from the registry given a list of node pack IDs
*/
export const useNodePacks = (
packsIds: string[] | Ref<string[]>,
options: UseNodePacksOptions = {}
) => {
const { immediate = false, maxConcurrent = DEFAULT_MAX_CONCURRENT } = options
const { getPackById, cancelRequests } = useComfyRegistryStore()
const nodePacks = ref<NodePack[]>([])
const processedIds = ref<Set<string>>(new Set())
const queuedPackIds = isRef(packsIds) ? packsIds : ref<string[]>(packsIds)
const remainingIds = computed(() =>
queuedPackIds.value?.filter((id) => !processedIds.value.has(id))
)
const chunks = computed(() =>
remainingIds.value?.length ? chunk(remainingIds.value, maxConcurrent) : []
)
const fetchPack = (ids: Parameters<typeof getPackById>[0]) =>
ids ? getPackById(ids) : null
const toRequestBatch = async (ids: string[]) =>
Promise.all(ids.map(fetchPack))
const isValidResponse = (response: NodePack | null) => response !== null
const fetchPacks = async () => {
for (const chunk of chunks.value) {
const resolvedChunk = await toRequestBatch(chunk)
chunk.forEach((id) => processedIds.value.add(id))
if (!resolvedChunk) continue
nodePacks.value.push(...resolvedChunk.filter(isValidResponse))
}
}
const { isReady, isLoading, error, execute } = useAsyncState(
fetchPacks,
null,
{
immediate
}
)
const clear = () => {
queuedPackIds.value = []
isReady.value = false
isLoading.value = false
}
const cleanup = () => {
cancelRequests()
clear()
}
return {
error,
isLoading,
isReady,
nodePacks,
startFetch: execute,
cleanup
}
}

View File

@@ -0,0 +1,87 @@
import { LGraphNode } from '@comfyorg/litegraph'
import { computed, onUnmounted } from 'vue'
import { useNodePacks } from '@/composables/nodePack/useNodePacks'
import { ComfyWorkflowJSON } from '@/schemas/comfyWorkflowSchema'
import { app } from '@/scripts/app'
import { UseNodePacksOptions } from '@/types/comfyManagerTypes'
import type { components } from '@/types/comfyRegistryTypes'
type WorkflowPack = {
id:
| ComfyWorkflowJSON['nodes'][number]['properties']['cnr_id']
| ComfyWorkflowJSON['nodes'][number]['properties']['aux_id']
version: ComfyWorkflowJSON['nodes'][number]['properties']['ver']
}
const CORE_NODES_PACK_NAME = 'comfy-core'
/**
* Handles parsing node pack metadata from nodes on the graph and fetching the
* associated node packs from the registry
*/
export const useWorkflowPacks = (options: UseNodePacksOptions = {}) => {
const getWorkflowNodeId = (node: LGraphNode): string | undefined => {
if (typeof node.properties?.cnr_id === 'string') {
return node.properties.cnr_id
}
if (typeof node.properties?.aux_id === 'string') {
return node.properties.aux_id
}
return undefined
}
const workflowNodeToPack = (node: LGraphNode): WorkflowPack | undefined => {
const id = getWorkflowNodeId(node)
if (!id) return undefined
if (id === CORE_NODES_PACK_NAME) return undefined
const version =
typeof node.properties.ver === 'string' ? node.properties.ver : undefined
return {
id,
version
}
}
const workflowPacks = computed<WorkflowPack[]>(() => {
if (!app.graph?.nodes?.length) return []
return app.graph.nodes
.map(workflowNodeToPack)
.filter((pack) => pack !== undefined)
})
const packsToUniqueIds = (packs: WorkflowPack[]) =>
packs.reduce((acc, pack) => {
if (pack?.id) acc.add(pack.id)
return acc
}, new Set<string>())
const workflowPacksIds = computed(() =>
Array.from(packsToUniqueIds(workflowPacks.value))
)
const { startFetch, cleanup, error, isLoading, nodePacks } = useNodePacks(
workflowPacksIds,
options
)
const isIdInWorkflow = (packId: string) =>
workflowPacksIds.value.includes(packId)
const filterWorkflowPack = (packs: components['schemas']['Node'][]) =>
packs.filter((pack) => !!pack.id && isIdInWorkflow(pack.id))
onUnmounted(() => {
cleanup()
})
return {
error,
isLoading,
workflowPacks: nodePacks,
startFetchWorkflowPacks: startFetch,
filterWorkflowPack
}
}