Closes #127, #128, #129. - manifest.json (new knowledge_base_manifest schema) records a dataset version, deterministic content_hash, source_vocab digest, optional entry_schema_version. Loader reads/skips it; regeneration stamps it deterministically. - resolver.knowledge_base_version config option (Configuration + KB Loader nodes) pins a version; unavailable version -> clear SC0004. - In-memory entry migration adapts older datasets to the current entry schema at load time without rewriting pinned files; too-old versions error clearly. Co-authored-by: kunail0804 <kunail0804@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
118 lines
4.7 KiB
Python
118 lines
4.7 KiB
Python
"""ComfyUI node for the compiler Configuration (MASTER_SPEC §20, §23).
|
|
|
|
A thin interface: it collects the configuration options as node inputs and emits
|
|
a single COMPILER_CONFIG that the other nodes consume, so behaviour can change
|
|
without editing the workflow. No compiler logic; it never imports ComfyUI.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from compiler.common.config import Config, ConfigError
|
|
|
|
from .adapters import format_messages
|
|
|
|
_DEBUG_LEVELS = ["none", "basic", "verbose", "developer"]
|
|
|
|
|
|
class ConfigurationNode:
|
|
"""Centralizes compiler configuration and emits it for the other nodes."""
|
|
|
|
CATEGORY = "Scene Compiler"
|
|
FUNCTION = "run"
|
|
RETURN_TYPES = ("COMPILER_CONFIG", "STRING")
|
|
RETURN_NAMES = ("config", "errors")
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls) -> dict[str, Any]:
|
|
return {
|
|
"required": {
|
|
"analyzer_model": ("STRING", {"default": "llama3"}),
|
|
"analyzer_temperature": (
|
|
"FLOAT",
|
|
{"default": 0.0, "min": 0.0, "max": 2.0, "step": 0.1},
|
|
),
|
|
"analyzer_max_retries": ("INT", {"default": 3, "min": 0, "max": 10}),
|
|
"analyzer_timeout": ("INT", {"default": 300, "min": 1, "max": 3600}),
|
|
"knowledge_base": ("STRING", {"default": "knowledge_base/"}),
|
|
"resolver_strict_mode": ("BOOLEAN", {"default": True}),
|
|
"resolver_allow_aliases": ("BOOLEAN", {"default": True}),
|
|
"resolver_expansion_enabled": ("BOOLEAN", {"default": True}),
|
|
"resolver_max_expansion_depth": ("INT", {"default": 8, "min": 1, "max": 32}),
|
|
"resolver_include_nsfw": ("BOOLEAN", {"default": False}),
|
|
"validator_allow_unknown_fields": ("BOOLEAN", {"default": False}),
|
|
"prompt_target": ("STRING", {"default": "easy_illustrious"}),
|
|
"prompt_separator": ("STRING", {"default": ","}),
|
|
"prompt_remove_duplicate_tags": ("BOOLEAN", {"default": True}),
|
|
"debug_enabled": ("BOOLEAN", {"default": False}),
|
|
"debug_level": (_DEBUG_LEVELS, {"default": "basic"}),
|
|
},
|
|
# Appended at the END so saved workflows keep their positional widget
|
|
# values (ComfyUI stores widget values positionally).
|
|
"optional": {
|
|
"analyzer_system_prompt": ("STRING", {"multiline": True, "default": ""}),
|
|
"resolver_knowledge_base_version": ("STRING", {"default": ""}),
|
|
},
|
|
}
|
|
|
|
def run(
|
|
self,
|
|
analyzer_model: str,
|
|
analyzer_temperature: float,
|
|
analyzer_max_retries: int,
|
|
analyzer_timeout: int,
|
|
knowledge_base: str,
|
|
resolver_strict_mode: bool,
|
|
resolver_allow_aliases: bool,
|
|
resolver_expansion_enabled: bool,
|
|
resolver_max_expansion_depth: int,
|
|
resolver_include_nsfw: bool,
|
|
validator_allow_unknown_fields: bool,
|
|
prompt_target: str,
|
|
prompt_separator: str,
|
|
prompt_remove_duplicate_tags: bool,
|
|
debug_enabled: bool,
|
|
debug_level: str,
|
|
analyzer_system_prompt: str = "",
|
|
resolver_knowledge_base_version: str = "",
|
|
) -> tuple[Any, str]:
|
|
analyzer: dict[str, Any] = {
|
|
"model": analyzer_model,
|
|
"temperature": analyzer_temperature,
|
|
"max_retries": analyzer_max_retries,
|
|
"timeout": analyzer_timeout,
|
|
}
|
|
if analyzer_system_prompt:
|
|
analyzer["system_prompt"] = analyzer_system_prompt
|
|
|
|
document = {
|
|
"analyzer": analyzer,
|
|
"resolver": {
|
|
"knowledge_base": knowledge_base,
|
|
"strict_mode": resolver_strict_mode,
|
|
"allow_aliases": resolver_allow_aliases,
|
|
"expansion_enabled": resolver_expansion_enabled,
|
|
"max_expansion_depth": resolver_max_expansion_depth,
|
|
"include_nsfw": resolver_include_nsfw,
|
|
**(
|
|
{"knowledge_base_version": resolver_knowledge_base_version}
|
|
if resolver_knowledge_base_version
|
|
else {}
|
|
),
|
|
},
|
|
"validator": {"allow_unknown_fields": validator_allow_unknown_fields},
|
|
"prompt_builder": {
|
|
"target": prompt_target,
|
|
"separator": prompt_separator,
|
|
"remove_duplicate_tags": prompt_remove_duplicate_tags,
|
|
},
|
|
"debug": {"enabled": debug_enabled, "level": debug_level},
|
|
}
|
|
|
|
try:
|
|
config = Config.from_json(document)
|
|
except ConfigError as error:
|
|
return (None, format_messages([error.message]))
|
|
return (config, "")
|