add blueprints dir for built-in blueprints (#11853)

This commit is contained in:
ric-yu 2026-01-13 16:14:40 -08:00 committed by GitHub
parent e4b4fb3479
commit 79f6bb5e4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 50 additions and 30 deletions

View File

@ -10,6 +10,7 @@ import hashlib
class Source: class Source:
custom_node = "custom_node" custom_node = "custom_node"
templates = "templates"
class SubgraphEntry(TypedDict): class SubgraphEntry(TypedDict):
source: str source: str
@ -38,6 +39,18 @@ class CustomNodeSubgraphEntryInfo(TypedDict):
class SubgraphManager: class SubgraphManager:
def __init__(self): def __init__(self):
self.cached_custom_node_subgraphs: dict[SubgraphEntry] | None = None self.cached_custom_node_subgraphs: dict[SubgraphEntry] | None = None
self.cached_blueprint_subgraphs: dict[SubgraphEntry] | None = None
def _create_entry(self, file: str, source: str, node_pack: str) -> tuple[str, SubgraphEntry]:
"""Create a subgraph entry from a file path. Expects normalized path (forward slashes)."""
entry_id = hashlib.sha256(f"{source}{file}".encode()).hexdigest()
entry: SubgraphEntry = {
"source": source,
"name": os.path.splitext(os.path.basename(file))[0],
"path": file,
"info": {"node_pack": node_pack},
}
return entry_id, entry
async def load_entry_data(self, entry: SubgraphEntry): async def load_entry_data(self, entry: SubgraphEntry):
with open(entry['path'], 'r') as f: with open(entry['path'], 'r') as f:
@ -60,53 +73,60 @@ class SubgraphManager:
return entries return entries
async def get_custom_node_subgraphs(self, loadedModules, force_reload=False): async def get_custom_node_subgraphs(self, loadedModules, force_reload=False):
# if not forced to reload and cached, return cache """Load subgraphs from custom nodes."""
if not force_reload and self.cached_custom_node_subgraphs is not None: if not force_reload and self.cached_custom_node_subgraphs is not None:
return self.cached_custom_node_subgraphs return self.cached_custom_node_subgraphs
# Load subgraphs from custom nodes
subfolder = "subgraphs"
subgraphs_dict: dict[SubgraphEntry] = {}
subgraphs_dict: dict[SubgraphEntry] = {}
for folder in folder_paths.get_folder_paths("custom_nodes"): for folder in folder_paths.get_folder_paths("custom_nodes"):
pattern = os.path.join(folder, f"*/{subfolder}/*.json") pattern = os.path.join(folder, "*/subgraphs/*.json")
matched_files = glob.glob(pattern) for file in glob.glob(pattern):
for file in matched_files:
# replace backslashes with forward slashes
file = file.replace('\\', '/') file = file.replace('\\', '/')
info: CustomNodeSubgraphEntryInfo = { node_pack = "custom_nodes." + file.split('/')[-3]
"node_pack": "custom_nodes." + file.split('/')[-3] entry_id, entry = self._create_entry(file, Source.custom_node, node_pack)
} subgraphs_dict[entry_id] = entry
source = Source.custom_node
# hash source + path to make sure id will be as unique as possible, but
# reproducible across backend reloads
id = hashlib.sha256(f"{source}{file}".encode()).hexdigest()
entry: SubgraphEntry = {
"source": Source.custom_node,
"name": os.path.splitext(os.path.basename(file))[0],
"path": file,
"info": info,
}
subgraphs_dict[id] = entry
self.cached_custom_node_subgraphs = subgraphs_dict self.cached_custom_node_subgraphs = subgraphs_dict
return subgraphs_dict return subgraphs_dict
async def get_custom_node_subgraph(self, id: str, loadedModules): async def get_blueprint_subgraphs(self, force_reload=False):
subgraphs = await self.get_custom_node_subgraphs(loadedModules) """Load subgraphs from the blueprints directory."""
entry: SubgraphEntry = subgraphs.get(id, None) if not force_reload and self.cached_blueprint_subgraphs is not None:
if entry is not None and entry.get('data', None) is None: return self.cached_blueprint_subgraphs
subgraphs_dict: dict[SubgraphEntry] = {}
blueprints_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'blueprints')
if os.path.exists(blueprints_dir):
for file in glob.glob(os.path.join(blueprints_dir, "*.json")):
file = file.replace('\\', '/')
entry_id, entry = self._create_entry(file, Source.templates, "comfyui")
subgraphs_dict[entry_id] = entry
self.cached_blueprint_subgraphs = subgraphs_dict
return subgraphs_dict
async def get_all_subgraphs(self, loadedModules, force_reload=False):
"""Get all subgraphs from all sources (custom nodes and blueprints)."""
custom_node_subgraphs = await self.get_custom_node_subgraphs(loadedModules, force_reload)
blueprint_subgraphs = await self.get_blueprint_subgraphs(force_reload)
return {**custom_node_subgraphs, **blueprint_subgraphs}
async def get_subgraph(self, id: str, loadedModules):
"""Get a specific subgraph by ID from any source."""
entry = (await self.get_all_subgraphs(loadedModules)).get(id)
if entry is not None and entry.get('data') is None:
await self.load_entry_data(entry) await self.load_entry_data(entry)
return entry return entry
def add_routes(self, routes, loadedModules): def add_routes(self, routes, loadedModules):
@routes.get("/global_subgraphs") @routes.get("/global_subgraphs")
async def get_global_subgraphs(request): async def get_global_subgraphs(request):
subgraphs_dict = await self.get_custom_node_subgraphs(loadedModules) subgraphs_dict = await self.get_all_subgraphs(loadedModules)
# NOTE: we may want to include other sources of global subgraphs such as templates in the future;
# that's the reasoning for the current implementation
return web.json_response(await self.sanitize_entries(subgraphs_dict, remove_data=True)) return web.json_response(await self.sanitize_entries(subgraphs_dict, remove_data=True))
@routes.get("/global_subgraphs/{id}") @routes.get("/global_subgraphs/{id}")
async def get_global_subgraph(request): async def get_global_subgraph(request):
id = request.match_info.get("id", None) id = request.match_info.get("id", None)
subgraph = await self.get_custom_node_subgraph(id, loadedModules) subgraph = await self.get_subgraph(id, loadedModules)
return web.json_response(await self.sanitize_entry(subgraph)) return web.json_response(await self.sanitize_entry(subgraph))

View File