mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-21 15:24:09 +00:00
feat: client-side distribution filtering for blueprint subgraphs (#8686)
Adds client-side filtering of blueprint subgraphs by distribution. **Changes:** - Added `includeOnDistributions` typed field to `GlobalSubgraphData` in `api.ts` - Distribution detection: `isCloud → 'cloud'`, `isDesktop → 'desktop'`, else `'localhost'` - Filters subgraphs before loading — excluded blueprints are never fetched **Depends on:** Comfy-Org/workflow_templates schema update (merge first) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8686-feat-client-side-distribution-filtering-for-blueprint-subgraphs-2ff6d73d365081d29f79c4e3cab174ac) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -11,9 +11,10 @@ import type {
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import type { IFuseOptions } from 'fuse.js'
|
||||
import {
|
||||
type TemplateInfo,
|
||||
type WorkflowTemplates
|
||||
import type {
|
||||
TemplateIncludeOnDistributionEnum,
|
||||
TemplateInfo,
|
||||
WorkflowTemplates
|
||||
} from '@/platform/workflow/templates/types/template'
|
||||
import type {
|
||||
ComfyApiWorkflow,
|
||||
@@ -241,6 +242,8 @@ export type GlobalSubgraphData = {
|
||||
node_pack: string
|
||||
category?: string
|
||||
search_aliases?: string[]
|
||||
requiresCustomNodes?: string[]
|
||||
includeOnDistributions?: TemplateIncludeOnDistributionEnum[]
|
||||
}
|
||||
data: string | Promise<string>
|
||||
essentials_category?: string
|
||||
|
||||
@@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema'
|
||||
import type { GlobalSubgraphData } from '@/scripts/api'
|
||||
import type { ExportedSubgraph } from '@/lib/litegraph/src/types/serialisation'
|
||||
import { TemplateIncludeOnDistributionEnum } from '@/platform/workflow/templates/types/template'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app as comfyApp } from '@/scripts/app'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
@@ -16,6 +17,12 @@ import {
|
||||
} from '@/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
|
||||
const mockDistributionTypes = vi.hoisted(() => ({
|
||||
isCloud: false,
|
||||
isDesktop: false
|
||||
}))
|
||||
vi.mock('@/platform/distribution/types', () => mockDistributionTypes)
|
||||
|
||||
// Mock telemetry to break circular dependency (telemetry → workflowStore → app → telemetry)
|
||||
vi.mock('@/platform/telemetry', () => ({
|
||||
useTelemetry: () => null
|
||||
@@ -85,6 +92,8 @@ describe('useSubgraphStore', () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockDistributionTypes.isCloud = false
|
||||
mockDistributionTypes.isDesktop = false
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useSubgraphStore()
|
||||
vi.clearAllMocks()
|
||||
@@ -305,4 +314,84 @@ describe('useSubgraphStore', () => {
|
||||
expect(subgraphExtra?.BlueprintSearchAliases).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('global blueprint filtering', () => {
|
||||
function globalBlueprint(
|
||||
overrides: Partial<GlobalSubgraphData['info']> = {}
|
||||
): GlobalSubgraphData {
|
||||
return {
|
||||
name: 'Filtered Blueprint',
|
||||
info: { node_pack: 'test_pack', ...overrides },
|
||||
data: JSON.stringify(mockGraph)
|
||||
}
|
||||
}
|
||||
|
||||
it('should exclude blueprints with requiresCustomNodes on non-cloud', async () => {
|
||||
await mockFetch(
|
||||
{},
|
||||
{
|
||||
bp: globalBlueprint({ requiresCustomNodes: ['custom-node-pack'] })
|
||||
}
|
||||
)
|
||||
expect(store.isGlobalBlueprint('bp')).toBe(false)
|
||||
})
|
||||
|
||||
it('should include blueprints with requiresCustomNodes on cloud', async () => {
|
||||
mockDistributionTypes.isCloud = true
|
||||
await mockFetch(
|
||||
{},
|
||||
{
|
||||
bp: globalBlueprint({ requiresCustomNodes: ['custom-node-pack'] })
|
||||
}
|
||||
)
|
||||
expect(store.isGlobalBlueprint('bp')).toBe(true)
|
||||
})
|
||||
|
||||
it('should include blueprints with empty requiresCustomNodes everywhere', async () => {
|
||||
await mockFetch({}, { bp: globalBlueprint({ requiresCustomNodes: [] }) })
|
||||
expect(store.isGlobalBlueprint('bp')).toBe(true)
|
||||
})
|
||||
|
||||
it('should exclude blueprints whose includeOnDistributions does not match', async () => {
|
||||
await mockFetch(
|
||||
{},
|
||||
{
|
||||
bp: globalBlueprint({
|
||||
includeOnDistributions: [TemplateIncludeOnDistributionEnum.Cloud]
|
||||
})
|
||||
}
|
||||
)
|
||||
expect(store.isGlobalBlueprint('bp')).toBe(false)
|
||||
})
|
||||
|
||||
it('should include blueprints whose includeOnDistributions matches current distribution', async () => {
|
||||
await mockFetch(
|
||||
{},
|
||||
{
|
||||
bp: globalBlueprint({
|
||||
includeOnDistributions: [TemplateIncludeOnDistributionEnum.Local]
|
||||
})
|
||||
}
|
||||
)
|
||||
expect(store.isGlobalBlueprint('bp')).toBe(true)
|
||||
})
|
||||
|
||||
it('should include blueprints on desktop when includeOnDistributions has desktop', async () => {
|
||||
mockDistributionTypes.isDesktop = true
|
||||
await mockFetch(
|
||||
{},
|
||||
{
|
||||
bp: globalBlueprint({
|
||||
includeOnDistributions: [TemplateIncludeOnDistributionEnum.Desktop]
|
||||
})
|
||||
}
|
||||
)
|
||||
expect(store.isGlobalBlueprint('bp')).toBe(true)
|
||||
})
|
||||
|
||||
it('should include blueprints with no filtering fields', async () => {
|
||||
await mockFetch({}, { bp: globalBlueprint() })
|
||||
expect(store.isGlobalBlueprint('bp')).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -20,6 +20,8 @@ import type {
|
||||
ComfyNodeDef as ComfyNodeDefV1,
|
||||
InputSpec
|
||||
} from '@/schemas/nodeDefSchema'
|
||||
import { isCloud, isDesktop } from '@/platform/distribution/types'
|
||||
import { TemplateIncludeOnDistributionEnum } from '@/platform/workflow/templates/types/template'
|
||||
import { api } from '@/scripts/api'
|
||||
import type { GlobalSubgraphData } from '@/scripts/api'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
@@ -221,9 +223,22 @@ export const useSubgraphStore = defineStore('subgraph', () => {
|
||||
)
|
||||
}
|
||||
const subgraphs = await api.getGlobalSubgraphs()
|
||||
await Promise.allSettled(
|
||||
Object.entries(subgraphs).map(loadGlobalBlueprint)
|
||||
)
|
||||
const currentDistribution: TemplateIncludeOnDistributionEnum = isCloud
|
||||
? TemplateIncludeOnDistributionEnum.Cloud
|
||||
: isDesktop
|
||||
? TemplateIncludeOnDistributionEnum.Desktop
|
||||
: TemplateIncludeOnDistributionEnum.Local
|
||||
const filteredEntries = Object.entries(subgraphs).filter(([, v]) => {
|
||||
if (!isCloud && (v.info.requiresCustomNodes?.length ?? 0) > 0)
|
||||
return false
|
||||
if (
|
||||
(v.info.includeOnDistributions?.length ?? 0) > 0 &&
|
||||
!v.info.includeOnDistributions!.includes(currentDistribution)
|
||||
)
|
||||
return false
|
||||
return true
|
||||
})
|
||||
await Promise.allSettled(filteredEntries.map(loadGlobalBlueprint))
|
||||
}
|
||||
|
||||
const userSubs = (
|
||||
|
||||
Reference in New Issue
Block a user