Extract generation profile config policy
This commit is contained in:
@@ -107,6 +107,9 @@ Already isolated:
|
||||
- category/cast route preset schemas, config JSON builders, choice lists, and
|
||||
parsers live in `category_cast_config.py`; `prompt_builder.py` keeps public
|
||||
delegate wrappers for existing nodes and tests.
|
||||
- generation profile presets, override normalization, trigger policy, and
|
||||
profile config parsing live in `generation_profile_config.py`;
|
||||
`prompt_builder.py` keeps public delegate wrappers.
|
||||
- location/composition config presets, themed location packs, custom
|
||||
location/composition entry parsing, merge behavior, and config parsing live
|
||||
in `location_config.py`; `prompt_builder.py` still applies selected configs
|
||||
|
||||
@@ -70,6 +70,7 @@ Core helper ownership:
|
||||
| `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_cast_config.py` | Category preset and cast preset schemas, category/cast config JSON builders, choice lists, and config parsers used by route nodes. |
|
||||
| `camera_config.py` | Camera option schema, direct/orbit/Qwen camera JSON builders, camera config parsing, plain camera directive text, and camera caption labels. |
|
||||
| `generation_profile_config.py` | Generation profile presets, profile option overrides, trigger policy, expression/pose/clothing config normalization, and profile config parsing. |
|
||||
| `seed_config.py` | Seed axis salts/aliases, seed mode choices, global/axis lock JSON builders, seed config parsing, row seed math, and deterministic axis RNG construction. |
|
||||
| `location_config.py` | Location/composition preset schemas, themed location packs, custom location/composition parsing, pool merge behavior, and location/composition config parsing. |
|
||||
| `pair_options.py` | Insta/OF option schema/defaults, softcore category/outfit/pose pools, partner outfit pools, clothing-continuity labels, negatives, and hardcore cast count policy. |
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
|
||||
GENERATION_PROFILE_PRESETS = {
|
||||
"balanced": {
|
||||
"clothing": "full",
|
||||
"poses": "standard",
|
||||
"expression_enabled": True,
|
||||
"expression_intensity": 0.5,
|
||||
"backside_bias": 0.0,
|
||||
"minimal_clothing_ratio": -1.0,
|
||||
"standard_pose_ratio": -1.0,
|
||||
"trigger": "sxcpinup_coloredpencil",
|
||||
"prepend_trigger_to_prompt": True,
|
||||
},
|
||||
"casual_clean": {
|
||||
"clothing": "full",
|
||||
"poses": "standard",
|
||||
"expression_enabled": True,
|
||||
"expression_intensity": 0.35,
|
||||
"backside_bias": 0.0,
|
||||
"minimal_clothing_ratio": -1.0,
|
||||
"standard_pose_ratio": -1.0,
|
||||
"trigger": "sxcpinup_coloredpencil",
|
||||
"prepend_trigger_to_prompt": True,
|
||||
},
|
||||
"evocative_softcore": {
|
||||
"clothing": "minimal",
|
||||
"poses": "evocative",
|
||||
"expression_enabled": True,
|
||||
"expression_intensity": 0.65,
|
||||
"backside_bias": 0.2,
|
||||
"minimal_clothing_ratio": -1.0,
|
||||
"standard_pose_ratio": -1.0,
|
||||
"trigger": "sxcpinup_coloredpencil",
|
||||
"prepend_trigger_to_prompt": True,
|
||||
},
|
||||
"hardcore_intense": {
|
||||
"clothing": "minimal",
|
||||
"poses": "evocative",
|
||||
"expression_enabled": True,
|
||||
"expression_intensity": 0.9,
|
||||
"backside_bias": 0.0,
|
||||
"minimal_clothing_ratio": -1.0,
|
||||
"standard_pose_ratio": -1.0,
|
||||
"trigger": "sxcpinup_coloredpencil",
|
||||
"prepend_trigger_to_prompt": True,
|
||||
},
|
||||
"krea2_friendly": {
|
||||
"clothing": "full",
|
||||
"poses": "standard",
|
||||
"expression_enabled": True,
|
||||
"expression_intensity": 0.55,
|
||||
"backside_bias": 0.0,
|
||||
"minimal_clothing_ratio": -1.0,
|
||||
"standard_pose_ratio": -1.0,
|
||||
"trigger": "sxcpinup_coloredpencil",
|
||||
"prepend_trigger_to_prompt": False,
|
||||
},
|
||||
"flux_original": {
|
||||
"clothing": "full",
|
||||
"poses": "standard",
|
||||
"expression_enabled": True,
|
||||
"expression_intensity": 0.5,
|
||||
"backside_bias": 0.0,
|
||||
"minimal_clothing_ratio": -1.0,
|
||||
"standard_pose_ratio": -1.0,
|
||||
"trigger": "sxcpinup_coloredpencil",
|
||||
"prepend_trigger_to_prompt": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _is_false(value: Any) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value is False
|
||||
if isinstance(value, str):
|
||||
return value.strip().lower() in ("false", "0", "no", "off")
|
||||
return False
|
||||
|
||||
|
||||
def _clamped_float(value: Any, default: float = 0.5, min_value: float = 0.0, max_value: float = 1.0) -> float:
|
||||
try:
|
||||
number = float(value)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
return max(min_value, min(max_value, number))
|
||||
|
||||
|
||||
def generation_profile_choices() -> list[str]:
|
||||
return list(GENERATION_PROFILE_PRESETS)
|
||||
|
||||
|
||||
def build_generation_profile_json(
|
||||
profile: str = "balanced",
|
||||
clothing_override: str = "profile_default",
|
||||
poses_override: str = "profile_default",
|
||||
expression_intensity_mode: str = "profile_default",
|
||||
expression_intensity: float = -1.0,
|
||||
backside_bias: float = -1.0,
|
||||
minimal_clothing_ratio: float = -1.0,
|
||||
standard_pose_ratio: float = -1.0,
|
||||
trigger_policy: str = "profile_default",
|
||||
expression_enabled: bool = True,
|
||||
) -> str:
|
||||
profile = profile if profile in GENERATION_PROFILE_PRESETS else "balanced"
|
||||
config = dict(GENERATION_PROFILE_PRESETS[profile])
|
||||
if clothing_override in ("full", "minimal", "random"):
|
||||
config["clothing"] = clothing_override
|
||||
if poses_override in ("standard", "evocative", "random"):
|
||||
config["poses"] = poses_override
|
||||
config["expression_enabled"] = not _is_false(expression_enabled)
|
||||
if expression_intensity_mode == "random":
|
||||
config["expression_intensity"] = -1.0
|
||||
elif expression_intensity_mode == "fixed" and float(expression_intensity) >= 0:
|
||||
config["expression_intensity"] = _clamped_float(expression_intensity, config["expression_intensity"])
|
||||
if float(backside_bias) >= 0:
|
||||
config["backside_bias"] = _clamped_float(backside_bias, config["backside_bias"])
|
||||
if float(minimal_clothing_ratio) >= 0:
|
||||
config["minimal_clothing_ratio"] = _clamped_float(minimal_clothing_ratio, config["minimal_clothing_ratio"])
|
||||
if float(standard_pose_ratio) >= 0:
|
||||
config["standard_pose_ratio"] = _clamped_float(standard_pose_ratio, config["standard_pose_ratio"])
|
||||
if trigger_policy == "prepend_trigger":
|
||||
config["prepend_trigger_to_prompt"] = True
|
||||
elif trigger_policy == "do_not_prepend":
|
||||
config["prepend_trigger_to_prompt"] = False
|
||||
config["profile"] = profile
|
||||
return json.dumps(config, ensure_ascii=True, sort_keys=True)
|
||||
|
||||
|
||||
def parse_generation_profile(profile_config: str | dict[str, Any] | None) -> dict[str, Any]:
|
||||
if not profile_config:
|
||||
return dict(GENERATION_PROFILE_PRESETS["balanced"])
|
||||
if isinstance(profile_config, dict):
|
||||
raw = profile_config
|
||||
else:
|
||||
try:
|
||||
raw = json.loads(str(profile_config))
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError(f"Invalid generation_profile JSON: {exc}") from exc
|
||||
if not isinstance(raw, dict):
|
||||
raise ValueError("generation_profile must be a JSON object")
|
||||
profile = str(raw.get("profile") or "balanced")
|
||||
parsed = dict(GENERATION_PROFILE_PRESETS.get(profile, GENERATION_PROFILE_PRESETS["balanced"]))
|
||||
parsed.update(raw)
|
||||
parsed["clothing"] = parsed["clothing"] if parsed.get("clothing") in ("full", "minimal", "random") else "full"
|
||||
parsed["poses"] = parsed["poses"] if parsed.get("poses") in ("standard", "evocative", "random") else "standard"
|
||||
parsed["expression_enabled"] = not _is_false(parsed.get("expression_enabled", True))
|
||||
try:
|
||||
raw_expression_intensity = float(parsed.get("expression_intensity"))
|
||||
except (TypeError, ValueError):
|
||||
raw_expression_intensity = 0.5
|
||||
parsed["expression_intensity"] = -1.0 if raw_expression_intensity < 0 else _clamped_float(raw_expression_intensity, 0.5)
|
||||
parsed["backside_bias"] = _clamped_float(parsed.get("backside_bias"), 0.0)
|
||||
parsed["minimal_clothing_ratio"] = _clamped_float(parsed.get("minimal_clothing_ratio"), -1.0, -1.0, 1.0)
|
||||
parsed["standard_pose_ratio"] = _clamped_float(parsed.get("standard_pose_ratio"), -1.0, -1.0, 1.0)
|
||||
parsed["trigger"] = str(parsed.get("trigger") or "sxcpinup_coloredpencil")
|
||||
parsed["prepend_trigger_to_prompt"] = bool(parsed.get("prepend_trigger_to_prompt"))
|
||||
return parsed
|
||||
|
||||
|
||||
_parse_generation_profile = parse_generation_profile
|
||||
@@ -3,18 +3,22 @@ from __future__ import annotations
|
||||
import json
|
||||
|
||||
try:
|
||||
from .prompt_builder import (
|
||||
build_ethnicity_list_json,
|
||||
build_filter_config_json,
|
||||
from .generation_profile_config import (
|
||||
build_generation_profile_json,
|
||||
generation_profile_choices,
|
||||
)
|
||||
from .prompt_builder import (
|
||||
build_ethnicity_list_json,
|
||||
build_filter_config_json,
|
||||
)
|
||||
except ImportError: # Allows local smoke tests from the repository root.
|
||||
from generation_profile_config import (
|
||||
build_generation_profile_json,
|
||||
generation_profile_choices,
|
||||
)
|
||||
from prompt_builder import (
|
||||
build_ethnicity_list_json,
|
||||
build_filter_config_json,
|
||||
build_generation_profile_json,
|
||||
generation_profile_choices,
|
||||
)
|
||||
|
||||
|
||||
|
||||
+17
-120
@@ -26,6 +26,7 @@ try:
|
||||
from . import camera_config as camera_policy
|
||||
from . import category_cast_config as category_cast_policy
|
||||
from . import generate_prompt_batches as g
|
||||
from . import generation_profile_config as generation_profile_policy
|
||||
from . import location_config as location_policy
|
||||
from . import pair_clothing
|
||||
from . import pair_camera
|
||||
@@ -65,6 +66,7 @@ except ImportError: # Allows local smoke tests with `python -c`.
|
||||
import camera_config as camera_policy
|
||||
import category_cast_config as category_cast_policy
|
||||
import generate_prompt_batches as g
|
||||
import generation_profile_config as generation_profile_policy
|
||||
import location_config as location_policy
|
||||
import pair_clothing
|
||||
import pair_camera
|
||||
@@ -1035,74 +1037,7 @@ def seed_mode_choices() -> list[str]:
|
||||
CATEGORY_PRESETS = category_cast_policy.CATEGORY_PRESETS
|
||||
CAST_PRESETS = category_cast_policy.CAST_PRESETS
|
||||
|
||||
GENERATION_PROFILE_PRESETS = {
|
||||
"balanced": {
|
||||
"clothing": "full",
|
||||
"poses": "standard",
|
||||
"expression_enabled": True,
|
||||
"expression_intensity": 0.5,
|
||||
"backside_bias": 0.0,
|
||||
"minimal_clothing_ratio": -1.0,
|
||||
"standard_pose_ratio": -1.0,
|
||||
"trigger": "sxcpinup_coloredpencil",
|
||||
"prepend_trigger_to_prompt": True,
|
||||
},
|
||||
"casual_clean": {
|
||||
"clothing": "full",
|
||||
"poses": "standard",
|
||||
"expression_enabled": True,
|
||||
"expression_intensity": 0.35,
|
||||
"backside_bias": 0.0,
|
||||
"minimal_clothing_ratio": -1.0,
|
||||
"standard_pose_ratio": -1.0,
|
||||
"trigger": "sxcpinup_coloredpencil",
|
||||
"prepend_trigger_to_prompt": True,
|
||||
},
|
||||
"evocative_softcore": {
|
||||
"clothing": "minimal",
|
||||
"poses": "evocative",
|
||||
"expression_enabled": True,
|
||||
"expression_intensity": 0.65,
|
||||
"backside_bias": 0.2,
|
||||
"minimal_clothing_ratio": -1.0,
|
||||
"standard_pose_ratio": -1.0,
|
||||
"trigger": "sxcpinup_coloredpencil",
|
||||
"prepend_trigger_to_prompt": True,
|
||||
},
|
||||
"hardcore_intense": {
|
||||
"clothing": "minimal",
|
||||
"poses": "evocative",
|
||||
"expression_enabled": True,
|
||||
"expression_intensity": 0.9,
|
||||
"backside_bias": 0.0,
|
||||
"minimal_clothing_ratio": -1.0,
|
||||
"standard_pose_ratio": -1.0,
|
||||
"trigger": "sxcpinup_coloredpencil",
|
||||
"prepend_trigger_to_prompt": True,
|
||||
},
|
||||
"krea2_friendly": {
|
||||
"clothing": "full",
|
||||
"poses": "standard",
|
||||
"expression_enabled": True,
|
||||
"expression_intensity": 0.55,
|
||||
"backside_bias": 0.0,
|
||||
"minimal_clothing_ratio": -1.0,
|
||||
"standard_pose_ratio": -1.0,
|
||||
"trigger": "sxcpinup_coloredpencil",
|
||||
"prepend_trigger_to_prompt": False,
|
||||
},
|
||||
"flux_original": {
|
||||
"clothing": "full",
|
||||
"poses": "standard",
|
||||
"expression_enabled": True,
|
||||
"expression_intensity": 0.5,
|
||||
"backside_bias": 0.0,
|
||||
"minimal_clothing_ratio": -1.0,
|
||||
"standard_pose_ratio": -1.0,
|
||||
"trigger": "sxcpinup_coloredpencil",
|
||||
"prepend_trigger_to_prompt": True,
|
||||
},
|
||||
}
|
||||
GENERATION_PROFILE_PRESETS = generation_profile_policy.GENERATION_PROFILE_PRESETS
|
||||
|
||||
|
||||
def category_preset_choices() -> list[str]:
|
||||
@@ -1114,7 +1049,7 @@ def cast_preset_choices() -> list[str]:
|
||||
|
||||
|
||||
def generation_profile_choices() -> list[str]:
|
||||
return list(GENERATION_PROFILE_PRESETS)
|
||||
return generation_profile_policy.generation_profile_choices()
|
||||
|
||||
|
||||
def build_category_config_json(preset: str = "auto_weighted", subcategory: str = RANDOM_SUBCATEGORY) -> str:
|
||||
@@ -1145,60 +1080,22 @@ def build_generation_profile_json(
|
||||
trigger_policy: str = "profile_default",
|
||||
expression_enabled: bool = True,
|
||||
) -> str:
|
||||
profile = profile if profile in GENERATION_PROFILE_PRESETS else "balanced"
|
||||
config = dict(GENERATION_PROFILE_PRESETS[profile])
|
||||
if clothing_override in ("full", "minimal", "random"):
|
||||
config["clothing"] = clothing_override
|
||||
if poses_override in ("standard", "evocative", "random"):
|
||||
config["poses"] = poses_override
|
||||
config["expression_enabled"] = not _is_false(expression_enabled)
|
||||
if expression_intensity_mode == "random":
|
||||
config["expression_intensity"] = -1.0
|
||||
elif expression_intensity_mode == "fixed" and float(expression_intensity) >= 0:
|
||||
config["expression_intensity"] = _clamped_float(expression_intensity, config["expression_intensity"])
|
||||
if float(backside_bias) >= 0:
|
||||
config["backside_bias"] = _clamped_float(backside_bias, config["backside_bias"])
|
||||
if float(minimal_clothing_ratio) >= 0:
|
||||
config["minimal_clothing_ratio"] = _clamped_float(minimal_clothing_ratio, config["minimal_clothing_ratio"])
|
||||
if float(standard_pose_ratio) >= 0:
|
||||
config["standard_pose_ratio"] = _clamped_float(standard_pose_ratio, config["standard_pose_ratio"])
|
||||
if trigger_policy == "prepend_trigger":
|
||||
config["prepend_trigger_to_prompt"] = True
|
||||
elif trigger_policy == "do_not_prepend":
|
||||
config["prepend_trigger_to_prompt"] = False
|
||||
config["profile"] = profile
|
||||
return json.dumps(config, ensure_ascii=True, sort_keys=True)
|
||||
return generation_profile_policy.build_generation_profile_json(
|
||||
profile=profile,
|
||||
clothing_override=clothing_override,
|
||||
poses_override=poses_override,
|
||||
expression_intensity_mode=expression_intensity_mode,
|
||||
expression_intensity=expression_intensity,
|
||||
backside_bias=backside_bias,
|
||||
minimal_clothing_ratio=minimal_clothing_ratio,
|
||||
standard_pose_ratio=standard_pose_ratio,
|
||||
trigger_policy=trigger_policy,
|
||||
expression_enabled=expression_enabled,
|
||||
)
|
||||
|
||||
|
||||
def _parse_generation_profile(profile_config: str | dict[str, Any] | None) -> dict[str, Any]:
|
||||
if not profile_config:
|
||||
return dict(GENERATION_PROFILE_PRESETS["balanced"])
|
||||
if isinstance(profile_config, dict):
|
||||
raw = profile_config
|
||||
else:
|
||||
try:
|
||||
raw = json.loads(str(profile_config))
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError(f"Invalid generation_profile JSON: {exc}") from exc
|
||||
if not isinstance(raw, dict):
|
||||
raise ValueError("generation_profile must be a JSON object")
|
||||
profile = str(raw.get("profile") or "balanced")
|
||||
parsed = dict(GENERATION_PROFILE_PRESETS.get(profile, GENERATION_PROFILE_PRESETS["balanced"]))
|
||||
parsed.update(raw)
|
||||
parsed["clothing"] = parsed["clothing"] if parsed.get("clothing") in ("full", "minimal", "random") else "full"
|
||||
parsed["poses"] = parsed["poses"] if parsed.get("poses") in ("standard", "evocative", "random") else "standard"
|
||||
parsed["expression_enabled"] = not _is_false(parsed.get("expression_enabled", True))
|
||||
try:
|
||||
raw_expression_intensity = float(parsed.get("expression_intensity"))
|
||||
except (TypeError, ValueError):
|
||||
raw_expression_intensity = 0.5
|
||||
parsed["expression_intensity"] = -1.0 if raw_expression_intensity < 0 else _clamped_float(raw_expression_intensity, 0.5)
|
||||
parsed["backside_bias"] = _clamped_float(parsed.get("backside_bias"), 0.0)
|
||||
parsed["minimal_clothing_ratio"] = _clamped_float(parsed.get("minimal_clothing_ratio"), -1.0, -1.0, 1.0)
|
||||
parsed["standard_pose_ratio"] = _clamped_float(parsed.get("standard_pose_ratio"), -1.0, -1.0, 1.0)
|
||||
parsed["trigger"] = str(parsed.get("trigger") or "sxcpinup_coloredpencil")
|
||||
parsed["prepend_trigger_to_prompt"] = bool(parsed.get("prepend_trigger_to_prompt"))
|
||||
return parsed
|
||||
return generation_profile_policy.parse_generation_profile(profile_config)
|
||||
|
||||
|
||||
def build_filter_config_json(
|
||||
|
||||
@@ -27,6 +27,7 @@ import caption_naturalizer # noqa: E402
|
||||
import category_cast_config # noqa: E402
|
||||
import category_library # noqa: E402
|
||||
import __init__ as sxcp_nodes # noqa: E402
|
||||
import generation_profile_config # noqa: E402
|
||||
import krea_formatter # noqa: E402
|
||||
import location_config # noqa: E402
|
||||
import prompt_builder as pb # noqa: E402
|
||||
@@ -578,6 +579,47 @@ def smoke_category_cast_config_policy() -> None:
|
||||
_expect((empty_cast.get("women_count"), empty_cast.get("men_count")) == (1, 0), "Empty custom cast was not corrected")
|
||||
|
||||
|
||||
def smoke_generation_profile_config_policy() -> None:
|
||||
_expect(
|
||||
pb.GENERATION_PROFILE_PRESETS is generation_profile_config.GENERATION_PROFILE_PRESETS,
|
||||
"Prompt builder generation profile presets are not delegated",
|
||||
)
|
||||
_expect("krea2_friendly" in generation_profile_config.generation_profile_choices(), "Generation profile choices lost krea2_friendly")
|
||||
|
||||
profile = json.loads(
|
||||
pb.build_generation_profile_json(
|
||||
profile="krea2_friendly",
|
||||
clothing_override="minimal",
|
||||
poses_override="random",
|
||||
expression_enabled=False,
|
||||
expression_intensity_mode="random",
|
||||
expression_intensity=0.8,
|
||||
backside_bias=2,
|
||||
minimal_clothing_ratio=0.25,
|
||||
standard_pose_ratio=0.75,
|
||||
trigger_policy="prepend_trigger",
|
||||
)
|
||||
)
|
||||
_expect(profile.get("profile") == "krea2_friendly", "Generation profile output lost selected profile")
|
||||
_expect(profile.get("clothing") == "minimal", "Generation profile clothing override failed")
|
||||
_expect(profile.get("poses") == "random", "Generation profile poses override failed")
|
||||
_expect(profile.get("expression_enabled") is False, "Generation profile expression disable failed")
|
||||
_expect(profile.get("expression_intensity") == -1.0, "Generation profile random expression marker changed")
|
||||
_expect(profile.get("backside_bias") == 1.0, "Generation profile backside bias clamp changed")
|
||||
_expect(profile.get("prepend_trigger_to_prompt") is True, "Generation profile trigger override failed")
|
||||
|
||||
parsed = pb._parse_generation_profile(profile)
|
||||
_expect(parsed.get("clothing") == "minimal", "Generation profile parser wrapper lost clothing")
|
||||
_expect(parsed.get("expression_enabled") is False, "Generation profile parser wrapper lost expression disable")
|
||||
_expect(parsed.get("minimal_clothing_ratio") == 0.25, "Generation profile parser wrapper lost minimal clothing ratio")
|
||||
|
||||
fallback = generation_profile_config.parse_generation_profile({"profile": "unknown", "clothing": "bad", "poses": "bad"})
|
||||
_expect(fallback.get("profile") == "unknown", "Generation profile parser should preserve raw profile label")
|
||||
_expect(fallback.get("clothing") == "full", "Generation profile parser did not normalize invalid clothing")
|
||||
_expect(fallback.get("poses") == "standard", "Generation profile parser did not normalize invalid poses")
|
||||
_expect(fallback.get("trigger") == "sxcpinup_coloredpencil", "Generation profile parser lost default trigger")
|
||||
|
||||
|
||||
def smoke_category_library_route() -> None:
|
||||
categories = category_library.load_category_library()
|
||||
_expect(len(categories) >= 3, "category library should load JSON categories")
|
||||
@@ -2486,6 +2528,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [
|
||||
("config_route_location_theme", smoke_config_route_location_theme),
|
||||
("location_config_policy", smoke_location_config_policy),
|
||||
("category_cast_config_policy", smoke_category_cast_config_policy),
|
||||
("generation_profile_config_policy", smoke_generation_profile_config_policy),
|
||||
("category_library_route", smoke_category_library_route),
|
||||
("hardcore_category_routes", smoke_hardcore_category_routes),
|
||||
("krea_close_foreplay_route", smoke_krea_close_foreplay_route),
|
||||
|
||||
Reference in New Issue
Block a user