refactor: flatten architecture with auto-registration (Google best practices)

- Moved all nodes to flat nodes/ directory:
  - llm_loader.py
  - openai_compatible.py
  - image_processor.py
  - json_extractor.py (NEW)

- Implemented auto-registration in __init__.py:
  - Scans nodes/ directory automatically
  - No need to manually update imports
  - Follows YAGNI principle (4 nodes don't need deep nesting)

- Added JSON Extractor node:
  - Extract values from JSON strings by key
  - Supports nested keys (e.g., 'user.name')
  - Category: 🚦ComfyUI_LLMs_Toolkit/JSON

- Removed src/ multi-level structure for better discoverability
This commit is contained in:
科林_Cyril Pilgrim
2026-02-09 21:53:02 +08:00
parent b1d6fd6a57
commit f843bbfe95
10 changed files with 114 additions and 16 deletions
+36 -14
View File
@@ -1,19 +1,41 @@
from .src.core.llm_loader import NODE_CLASS_MAPPINGS as LLM_LOADER_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as LLM_LOADER_DISPLAY_MAPPINGS
from .src.core.openai_compatible import NODE_CLASS_MAPPINGS as OPENAI_COMPATIBLE_LOADER_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as OPENAI_COMPATIBLE_LOADER_DISPLAY_MAPPINGS
from .src.processors.image_processor import NODE_CLASS_MAPPINGS as IMAGE_PREPROCESSOR_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as IMAGE_PREPROCESSOR_DISPLAY_MAPPINGS
"""
ComfyUI-LLMs-Toolkit: Lightweight LLM API integration for ComfyUI.
# 合并所有节点映射
NODE_CLASS_MAPPINGS = {
**LLM_LOADER_MAPPINGS,
**OPENAI_COMPATIBLE_LOADER_MAPPINGS,
**IMAGE_PREPROCESSOR_MAPPINGS,
}
Auto-registration mechanism for all nodes.
"""
from pathlib import Path
import importlib.util
import sys
NODE_DISPLAY_NAME_MAPPINGS = {
**LLM_LOADER_DISPLAY_MAPPINGS,
**OPENAI_COMPATIBLE_LOADER_DISPLAY_MAPPINGS,
**IMAGE_PREPROCESSOR_DISPLAY_MAPPINGS,
}
# Initialize mappings
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
# Auto-load all nodes from nodes/ directory
nodes_dir = Path(__file__).parent / "nodes"
if nodes_dir.exists():
for py_file in sorted(nodes_dir.glob("*.py")):
if py_file.stem.startswith("_"):
continue
try:
# Load module
module_name = f"ComfyUI-LLMs-Toolkit.nodes.{py_file.stem}"
spec = importlib.util.spec_from_file_location(module_name, py_file)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
# Register node mappings
if hasattr(module, "NODE_CLASS_MAPPINGS"):
NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS)
if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS"):
NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS)
except Exception as e:
print(f"[LLMs_Toolkit] Failed to load {py_file.stem}: {e}")
WEB_DIRECTORY = "./web"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
+78
View File
@@ -0,0 +1,78 @@
import json
from typing import Tuple
class JSONExtractor:
"""
Extract value from JSON string by key.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"json_string": ("STRING", {
"multiline": True,
"default": "{}"
}),
"key": ("STRING", {
"default": "",
"label": "Key to extract"
})
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("value",)
FUNCTION = "extract"
CATEGORY = "🚦ComfyUI_LLMs_Toolkit/JSON"
def extract(self, json_string: str, key: str) -> Tuple[str]:
"""
Extract value from JSON string by key.
Args:
json_string: JSON string to parse
key: Key to extract (supports nested keys like 'user.name')
Returns:
Extracted value as string
"""
try:
# Parse JSON string
data = json.loads(json_string)
# Handle nested keys (e.g., "user.profile.name")
keys = key.split('.')
value = data
for k in keys:
if isinstance(value, dict):
value = value.get(k)
if value is None:
return (f"Key '{k}' not found",)
else:
return (f"Cannot access key '{k}' on non-dict value",)
# Convert value to string
if isinstance(value, (dict, list)):
result = json.dumps(value, ensure_ascii=False)
else:
result = str(value)
print(f"[LLMs_Toolkit] extracted {key}={result[:50]}...")
return (result,)
except json.JSONDecodeError as e:
error_msg = f"Invalid JSON: {str(e)}"
print(f"[LLMs_Toolkit] ✗ {error_msg}")
return (error_msg,)
except Exception as e:
error_msg = f"Extraction error: {str(e)}"
print(f"[LLMs_Toolkit] ✗ {error_msg}")
return (error_msg,)
# Register the node
NODE_CLASS_MAPPINGS = {"JSONExtractor": JSONExtractor}
NODE_DISPLAY_NAME_MAPPINGS = {"JSONExtractor": "JSON Extractor"}
View File
View File
View File
View File
-2
View File
@@ -1,2 +0,0 @@
from . import file_uploader
__all__ = ["file_uploader"]