Extract builder config route
This commit is contained in:
@@ -0,0 +1,101 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class PromptFromConfigsRequest:
|
||||||
|
row_number: int
|
||||||
|
start_index: int
|
||||||
|
seed: int
|
||||||
|
category_config: str | dict[str, Any] | None = ""
|
||||||
|
cast_config: str | dict[str, Any] | None = ""
|
||||||
|
generation_profile: str | dict[str, Any] | None = ""
|
||||||
|
filter_config: str | dict[str, Any] | None = ""
|
||||||
|
seed_config: str | dict[str, Any] | None = ""
|
||||||
|
camera_config: str | dict[str, Any] | None = ""
|
||||||
|
character_profile: str | dict[str, Any] | None = ""
|
||||||
|
character_cast: str | dict[str, Any] | list[Any] | None = ""
|
||||||
|
hardcore_position_config: str | dict[str, Any] | None = ""
|
||||||
|
location_config: str | dict[str, Any] | None = ""
|
||||||
|
composition_config: str | dict[str, Any] | None = ""
|
||||||
|
extra_positive: str = ""
|
||||||
|
extra_negative: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class PromptFromConfigsRoute:
|
||||||
|
row: dict[str, Any]
|
||||||
|
category: str
|
||||||
|
subcategory: str
|
||||||
|
cast: dict[str, Any]
|
||||||
|
profile: dict[str, Any]
|
||||||
|
filters: dict[str, Any]
|
||||||
|
build_kwargs: dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class PromptFromConfigsDependencies:
|
||||||
|
parse_category_config: Callable[[str | dict[str, Any] | None], tuple[str, str]]
|
||||||
|
parse_cast_config: Callable[[str | dict[str, Any] | None], dict[str, Any]]
|
||||||
|
parse_generation_profile: Callable[[str | dict[str, Any] | None], dict[str, Any]]
|
||||||
|
parse_filter_config: Callable[[str | dict[str, Any] | None], dict[str, Any]]
|
||||||
|
build_prompt: Callable[..., dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
|
def build_prompt_from_configs_result(
|
||||||
|
request: PromptFromConfigsRequest,
|
||||||
|
deps: PromptFromConfigsDependencies,
|
||||||
|
) -> PromptFromConfigsRoute:
|
||||||
|
category, subcategory = deps.parse_category_config(request.category_config)
|
||||||
|
cast = deps.parse_cast_config(request.cast_config)
|
||||||
|
profile = deps.parse_generation_profile(request.generation_profile)
|
||||||
|
filters = deps.parse_filter_config(request.filter_config)
|
||||||
|
build_kwargs: dict[str, Any] = {
|
||||||
|
"category": category,
|
||||||
|
"subcategory": subcategory,
|
||||||
|
"row_number": request.row_number,
|
||||||
|
"start_index": request.start_index,
|
||||||
|
"seed": request.seed,
|
||||||
|
"clothing": profile["clothing"],
|
||||||
|
"ethnicity": filters["ethnicity"],
|
||||||
|
"poses": profile["poses"],
|
||||||
|
"expression_enabled": profile["expression_enabled"],
|
||||||
|
"expression_intensity": profile["expression_intensity"],
|
||||||
|
"backside_bias": profile["backside_bias"],
|
||||||
|
"figure": filters["figure"],
|
||||||
|
"no_plus_women": filters["no_plus_women"],
|
||||||
|
"no_black": filters["no_black"],
|
||||||
|
"women_count": int(cast["women_count"]),
|
||||||
|
"men_count": int(cast["men_count"]),
|
||||||
|
"minimal_clothing_ratio": profile["minimal_clothing_ratio"],
|
||||||
|
"standard_pose_ratio": profile["standard_pose_ratio"],
|
||||||
|
"trigger": profile["trigger"],
|
||||||
|
"prepend_trigger_to_prompt": profile["prepend_trigger_to_prompt"],
|
||||||
|
"extra_positive": request.extra_positive or "",
|
||||||
|
"extra_negative": request.extra_negative or "",
|
||||||
|
"seed_config": request.seed_config or "",
|
||||||
|
"camera_config": request.camera_config or "",
|
||||||
|
"character_profile": request.character_profile or "",
|
||||||
|
"character_cast": request.character_cast or "",
|
||||||
|
"hardcore_position_config": request.hardcore_position_config or "",
|
||||||
|
"location_config": request.location_config or "",
|
||||||
|
"composition_config": request.composition_config or "",
|
||||||
|
}
|
||||||
|
return PromptFromConfigsRoute(
|
||||||
|
row=deps.build_prompt(**build_kwargs),
|
||||||
|
category=category,
|
||||||
|
subcategory=subcategory,
|
||||||
|
cast=dict(cast),
|
||||||
|
profile=dict(profile),
|
||||||
|
filters=dict(filters),
|
||||||
|
build_kwargs=build_kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_prompt_from_configs(
|
||||||
|
request: PromptFromConfigsRequest,
|
||||||
|
deps: PromptFromConfigsDependencies,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
return build_prompt_from_configs_result(request, deps).row
|
||||||
@@ -120,6 +120,9 @@ Move or isolate later:
|
|||||||
|
|
||||||
Already isolated:
|
Already isolated:
|
||||||
|
|
||||||
|
- config-driven prompt-builder request parsing, helper-node config mapping, and
|
||||||
|
direct `build_prompt` kwarg assembly live in `builder_config_route.py`;
|
||||||
|
`prompt_builder.py` keeps the public wrapper.
|
||||||
- JSON category loading, subcategory normalization, named scene/expression/
|
- JSON category loading, subcategory normalization, named scene/expression/
|
||||||
composition pool loading, cast compatibility filtering, exact subcategory
|
composition pool loading, cast compatibility filtering, exact subcategory
|
||||||
lookup, and inheritance-based pool merging live in `category_library.py`.
|
lookup, and inheritance-based pool merging live in `category_library.py`.
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ call the same core generation functions.
|
|||||||
| ComfyUI node | Python entry | What it owns |
|
| ComfyUI node | Python entry | What it owns |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `SxCP Prompt Builder` | `build_prompt` | Direct single prompt generation. Can use built-in categories or JSON categories. |
|
| `SxCP Prompt Builder` | `build_prompt` | Direct single prompt generation. Can use built-in categories or JSON categories. |
|
||||||
| `SxCP Prompt Builder From Configs` | `build_prompt_from_configs` -> `build_prompt` | Same generator, but inputs come from category/cast/profile/filter helper nodes. |
|
| `SxCP Prompt Builder From Configs` | `build_prompt_from_configs` -> `builder_config_route.py` -> `build_prompt` | Same generator, but inputs come from category/cast/profile/filter helper nodes. |
|
||||||
| `SxCP Insta/OF Prompt Pair` | `build_insta_of_pair` | Builds a softcore row and hardcore row with shared cast/continuity options. |
|
| `SxCP Insta/OF Prompt Pair` | `build_insta_of_pair` | Builds a softcore row and hardcore row with shared cast/continuity options. |
|
||||||
| `SxCP Krea2 Formatter` | `format_krea2_prompt` | Converts metadata rows or pair metadata into Krea2-friendly prose. |
|
| `SxCP Krea2 Formatter` | `format_krea2_prompt` | Converts metadata rows or pair metadata into Krea2-friendly prose. |
|
||||||
| `SxCP SDXL Formatter` | `format_sdxl_prompt` | Converts metadata rows or pair metadata into SDXL/tag style prompts. |
|
| `SxCP SDXL Formatter` | `format_sdxl_prompt` | Converts metadata rows or pair metadata into SDXL/tag style prompts. |
|
||||||
@@ -72,6 +72,7 @@ Core helper ownership:
|
|||||||
| Python module | What it owns |
|
| Python module | What it owns |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `category_library.py` | JSON category loading, subcategory normalization, named scene/expression/composition pool loading, cast compatibility filtering, exact subcategory lookup, and inheritance-based pool merging. |
|
| `category_library.py` | JSON category loading, subcategory normalization, named scene/expression/composition pool loading, cast compatibility filtering, exact subcategory lookup, and inheritance-based pool merging. |
|
||||||
|
| `builder_config_route.py` | Config-driven prompt-builder request parsing, category/cast/profile/filter helper-node mapping, and direct `build_prompt` kwarg assembly. |
|
||||||
| `category_extensions.py` | JSON `pool_extensions`, legacy pool patching, built-in category choice lists, and category/subcategory UI choices. |
|
| `category_extensions.py` | JSON `pool_extensions`, legacy pool patching, built-in category choice lists, and category/subcategory UI choices. |
|
||||||
| `category_template_metadata.py` | Object-style item-template metadata extraction, action/position family normalization, position-key normalization, key merging, and audit validation errors. |
|
| `category_template_metadata.py` | Object-style item-template metadata extraction, action/position family normalization, position-key normalization, key merging, and audit validation errors. |
|
||||||
| `row_item.py` | Row item selection, weighted item/pair choice, item-template axis filling, and oral/outercourse axis compatibility filters. |
|
| `row_item.py` | Row item selection, weighted item/pair choice, item-template axis filling, and oral/outercourse axis compatibility filters. |
|
||||||
|
|||||||
+29
-31
@@ -5,6 +5,7 @@ from pathlib import Path
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
from . import builder_config_route as builder_config_route_policy
|
||||||
from .category_library import (
|
from .category_library import (
|
||||||
compatible_entries as _compatible_entries,
|
compatible_entries as _compatible_entries,
|
||||||
compatible_entry as _compatible_entry,
|
compatible_entry as _compatible_entry,
|
||||||
@@ -51,6 +52,7 @@ try:
|
|||||||
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
|
sanitize_hardcore_environment_anchors as _sanitize_hardcore_environment_anchors,
|
||||||
)
|
)
|
||||||
except ImportError: # Allows local smoke tests with `python -c`.
|
except ImportError: # Allows local smoke tests with `python -c`.
|
||||||
|
import builder_config_route as builder_config_route_policy
|
||||||
from category_library import (
|
from category_library import (
|
||||||
compatible_entries as _compatible_entries,
|
compatible_entries as _compatible_entries,
|
||||||
compatible_entry as _compatible_entry,
|
compatible_entry as _compatible_entry,
|
||||||
@@ -2609,6 +2611,16 @@ def build_prompt(
|
|||||||
return row
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def _prompt_from_configs_dependencies() -> builder_config_route_policy.PromptFromConfigsDependencies:
|
||||||
|
return builder_config_route_policy.PromptFromConfigsDependencies(
|
||||||
|
parse_category_config=_parse_category_config,
|
||||||
|
parse_cast_config=_parse_cast_config,
|
||||||
|
parse_generation_profile=_parse_generation_profile,
|
||||||
|
parse_filter_config=_parse_filter_config,
|
||||||
|
build_prompt=build_prompt,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_prompt_from_configs(
|
def build_prompt_from_configs(
|
||||||
row_number: int,
|
row_number: int,
|
||||||
start_index: int,
|
start_index: int,
|
||||||
@@ -2627,40 +2639,26 @@ def build_prompt_from_configs(
|
|||||||
extra_positive: str = "",
|
extra_positive: str = "",
|
||||||
extra_negative: str = "",
|
extra_negative: str = "",
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
category, subcategory = _parse_category_config(category_config)
|
return builder_config_route_policy.build_prompt_from_configs(
|
||||||
cast = _parse_cast_config(cast_config)
|
builder_config_route_policy.PromptFromConfigsRequest(
|
||||||
profile = _parse_generation_profile(generation_profile)
|
|
||||||
filters = _parse_filter_config(filter_config)
|
|
||||||
return build_prompt(
|
|
||||||
category=category,
|
|
||||||
subcategory=subcategory,
|
|
||||||
row_number=row_number,
|
row_number=row_number,
|
||||||
start_index=start_index,
|
start_index=start_index,
|
||||||
seed=seed,
|
seed=seed,
|
||||||
clothing=profile["clothing"],
|
category_config=category_config,
|
||||||
ethnicity=filters["ethnicity"],
|
cast_config=cast_config,
|
||||||
poses=profile["poses"],
|
generation_profile=generation_profile,
|
||||||
expression_enabled=profile["expression_enabled"],
|
filter_config=filter_config,
|
||||||
expression_intensity=profile["expression_intensity"],
|
seed_config=seed_config,
|
||||||
backside_bias=profile["backside_bias"],
|
camera_config=camera_config,
|
||||||
figure=filters["figure"],
|
character_profile=character_profile,
|
||||||
no_plus_women=filters["no_plus_women"],
|
character_cast=character_cast,
|
||||||
no_black=filters["no_black"],
|
hardcore_position_config=hardcore_position_config,
|
||||||
women_count=int(cast["women_count"]),
|
location_config=location_config,
|
||||||
men_count=int(cast["men_count"]),
|
composition_config=composition_config,
|
||||||
minimal_clothing_ratio=profile["minimal_clothing_ratio"],
|
extra_positive=extra_positive,
|
||||||
standard_pose_ratio=profile["standard_pose_ratio"],
|
extra_negative=extra_negative,
|
||||||
trigger=profile["trigger"],
|
),
|
||||||
prepend_trigger_to_prompt=profile["prepend_trigger_to_prompt"],
|
_prompt_from_configs_dependencies(),
|
||||||
extra_positive=extra_positive or "",
|
|
||||||
extra_negative=extra_negative or "",
|
|
||||||
seed_config=seed_config or "",
|
|
||||||
camera_config=camera_config or "",
|
|
||||||
character_profile=character_profile or "",
|
|
||||||
character_cast=character_cast or "",
|
|
||||||
hardcore_position_config=hardcore_position_config or "",
|
|
||||||
location_config=location_config or "",
|
|
||||||
composition_config=composition_config or "",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import caption_naturalizer # noqa: E402
|
|||||||
import caption_metadata_routes # noqa: E402
|
import caption_metadata_routes # noqa: E402
|
||||||
import caption_policy # noqa: E402
|
import caption_policy # noqa: E402
|
||||||
import caption_text_policy # noqa: E402
|
import caption_text_policy # noqa: E402
|
||||||
|
import builder_config_route # noqa: E402
|
||||||
import cast_context # noqa: E402
|
import cast_context # noqa: E402
|
||||||
import category_extensions # noqa: E402
|
import category_extensions # noqa: E402
|
||||||
import category_template_metadata # noqa: E402
|
import category_template_metadata # noqa: E402
|
||||||
@@ -604,6 +605,62 @@ def smoke_config_route_location_theme() -> None:
|
|||||||
_expect_formatter_outputs(row, "config_route_location_theme", target="single")
|
_expect_formatter_outputs(row, "config_route_location_theme", target="single")
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_builder_config_route_policy() -> None:
|
||||||
|
category_config = pb.build_category_config_json("women_casual", "Casual clothes / Smart casual")
|
||||||
|
cast_config = pb.build_cast_config_json("solo_woman")
|
||||||
|
generation_profile = pb.build_generation_profile_json(
|
||||||
|
profile="casual_clean",
|
||||||
|
trigger_policy="prepend_trigger",
|
||||||
|
)
|
||||||
|
filter_config = pb.build_filter_config_json(
|
||||||
|
ethnicity="french_european",
|
||||||
|
figure="balanced",
|
||||||
|
)
|
||||||
|
seed_config_json = pb.build_seed_lock_config_json(base_seed=3401, reroll_axis="scene", reroll_seed=3402)
|
||||||
|
request = builder_config_route.PromptFromConfigsRequest(
|
||||||
|
row_number=2,
|
||||||
|
start_index=5,
|
||||||
|
seed=3401,
|
||||||
|
category_config=category_config,
|
||||||
|
cast_config=cast_config,
|
||||||
|
generation_profile=generation_profile,
|
||||||
|
filter_config=filter_config,
|
||||||
|
seed_config=seed_config_json,
|
||||||
|
extra_positive="clean route marker",
|
||||||
|
extra_negative="bad route marker",
|
||||||
|
)
|
||||||
|
typed_route = builder_config_route.build_prompt_from_configs_result(
|
||||||
|
request,
|
||||||
|
pb._prompt_from_configs_dependencies(),
|
||||||
|
)
|
||||||
|
legacy_row = pb.build_prompt_from_configs(
|
||||||
|
row_number=request.row_number,
|
||||||
|
start_index=request.start_index,
|
||||||
|
seed=request.seed,
|
||||||
|
category_config=category_config,
|
||||||
|
cast_config=cast_config,
|
||||||
|
generation_profile=generation_profile,
|
||||||
|
filter_config=filter_config,
|
||||||
|
seed_config=seed_config_json,
|
||||||
|
extra_positive=request.extra_positive,
|
||||||
|
extra_negative=request.extra_negative,
|
||||||
|
)
|
||||||
|
_expect(typed_route.row == legacy_row, "Prompt Builder From Configs route should match public wrapper output")
|
||||||
|
_expect(typed_route.category == "Casual clothes", "Config route lost category preset")
|
||||||
|
_expect(typed_route.subcategory == "Casual clothes / Smart casual", "Config route lost requested subcategory")
|
||||||
|
_expect(typed_route.cast["women_count"] == 1 and typed_route.cast["men_count"] == 0, "Config route lost cast preset")
|
||||||
|
_expect(typed_route.profile["trigger"] == "sxcpinup_coloredpencil", "Config route lost generation profile trigger")
|
||||||
|
_expect(typed_route.filters["ethnicity"] == "french_european", "Config route lost filter ethnicity")
|
||||||
|
kwargs = typed_route.build_kwargs
|
||||||
|
_expect(kwargs["category"] == typed_route.category, "Config route build kwargs category drifted")
|
||||||
|
_expect(kwargs["subcategory"] == typed_route.subcategory, "Config route build kwargs subcategory drifted")
|
||||||
|
_expect(kwargs["women_count"] == 1 and kwargs["men_count"] == 0, "Config route build kwargs cast counts drifted")
|
||||||
|
_expect(kwargs["seed_config"] == seed_config_json, "Config route build kwargs seed config drifted")
|
||||||
|
_expect(kwargs["extra_positive"] == "clean route marker", "Config route build kwargs extra positive drifted")
|
||||||
|
_expect("clean route marker" in typed_route.row.get("prompt", ""), "Config route row lost extra positive")
|
||||||
|
_expect("bad route marker" in typed_route.row.get("negative_prompt", ""), "Config route row lost extra negative")
|
||||||
|
|
||||||
|
|
||||||
def smoke_krea_normal_row_routes() -> None:
|
def smoke_krea_normal_row_routes() -> None:
|
||||||
single = {
|
single = {
|
||||||
"subject_type": "woman",
|
"subject_type": "woman",
|
||||||
@@ -5365,6 +5422,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
|||||||
("camera_scene_single", smoke_camera_scene_single),
|
("camera_scene_single", smoke_camera_scene_single),
|
||||||
("row_camera_policy", smoke_row_camera_policy),
|
("row_camera_policy", smoke_row_camera_policy),
|
||||||
("config_route_location_theme", smoke_config_route_location_theme),
|
("config_route_location_theme", smoke_config_route_location_theme),
|
||||||
|
("builder_config_route_policy", smoke_builder_config_route_policy),
|
||||||
("krea_normal_row_routes", smoke_krea_normal_row_routes),
|
("krea_normal_row_routes", smoke_krea_normal_row_routes),
|
||||||
("krea_row_fields_policy", smoke_krea_row_fields_policy),
|
("krea_row_fields_policy", smoke_krea_row_fields_policy),
|
||||||
("location_config_policy", smoke_location_config_policy),
|
("location_config_policy", smoke_location_config_policy),
|
||||||
|
|||||||
Reference in New Issue
Block a user