diff --git a/README.md b/README.md index 53bffeb..bea2fb2 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,14 @@ button does not use it. `SxCP Character Profile Load` has an `enabled` switch. When disabled, it returns an empty profile so connected prompt builders ignore it. When enabled, it loads a saved profile by selector or passes through a connected fallback profile JSON. -It also has explicit file-operation triggers: +It also has load-time override fields. Leave them blank or on `keep_profile` to +use the saved value; fill only the attributes you want to change for this +workflow, such as `override_age`, `override_body`, `override_skin`, +`override_hair`, `override_eyes`, `override_figure`, or +`override_descriptor_detail`. Overrides affect both the `character_profile` and +`character_cast` outputs, but they do not rewrite the saved profile file. + +The load node also has explicit file-operation triggers: - `Delete Selected Profile`: deletes the selected saved profile. - `Rename Selected Profile` + `rename_to`: renames the selected saved profile. diff --git a/__init__.py b/__init__.py index e117286..69c21da 100644 --- a/__init__.py +++ b/__init__.py @@ -1101,6 +1101,15 @@ class SxCPCharacterProfileLoad: "optional": { "manual_profile_name": ("STRING", {"default": ""}), "fallback_profile_json": ("STRING", {"default": "", "multiline": True}), + "override_subject_type": (["keep_profile", "woman", "man"], {"default": "keep_profile"}), + "override_age": ("STRING", {"default": ""}), + "override_body": ("STRING", {"default": ""}), + "override_body_phrase": ("STRING", {"default": ""}), + "override_skin": ("STRING", {"default": ""}), + "override_hair": ("STRING", {"default": ""}), + "override_eyes": ("STRING", {"default": ""}), + "override_figure": ("STRING", {"default": ""}), + "override_descriptor_detail": (["keep_profile"] + character_descriptor_detail_choices(), {"default": "keep_profile"}), }, } @@ -1118,6 +1127,15 @@ class SxCPCharacterProfileLoad: rename_now, manual_profile_name="", fallback_profile_json="", + override_subject_type="keep_profile", + override_age="", + override_body="", + override_body_phrase="", + override_skin="", + override_hair="", + override_eyes="", + override_figure="", + override_descriptor_detail="keep_profile", ): chosen_name = manual_profile_name.strip() if profile_name == "manual" and manual_profile_name.strip() else profile_name profile = load_character_profile_json( @@ -1127,6 +1145,15 @@ class SxCPCharacterProfileLoad: delete_now=delete_now, rename_now=rename_now, rename_to=rename_to, + override_subject_type=override_subject_type, + override_age=override_age, + override_body=override_body, + override_body_phrase=override_body_phrase, + override_skin=override_skin, + override_hair=override_hair, + override_eyes=override_eyes, + override_figure=override_figure, + override_descriptor_detail=override_descriptor_detail, ) return ( profile["profile_json"], diff --git a/prompt_builder.py b/prompt_builder.py index 2c9227c..bf7d394 100644 --- a/prompt_builder.py +++ b/prompt_builder.py @@ -3054,6 +3054,45 @@ def _empty_profile_result(status: str = "empty") -> dict[str, str]: } +def _apply_character_profile_overrides( + profile: dict[str, Any], + override_subject_type: str = "", + override_age: str = "", + override_body: str = "", + override_body_phrase: str = "", + override_skin: str = "", + override_hair: str = "", + override_eyes: str = "", + override_figure: str = "", + override_descriptor_detail: str = "", +) -> dict[str, Any]: + updated = dict(profile) + subject_type = str(override_subject_type or "").strip() + if subject_type in ("woman", "man"): + updated["subject_type"] = subject_type + updated["subject"] = subject_type + updated["subject_phrase"] = subject_type + for key, value in ( + ("age", override_age), + ("body", override_body), + ("body_phrase", override_body_phrase), + ("skin", override_skin), + ("hair", override_hair), + ("eyes", override_eyes), + ("figure", override_figure), + ): + text = str(value or "").strip() + if text: + updated[key] = text + descriptor_detail = str(override_descriptor_detail or "").strip() + if descriptor_detail and descriptor_detail != "keep_profile": + updated["descriptor_detail"] = _normalize_descriptor_detail(descriptor_detail) + if not str(updated.get("body_phrase") or "").strip(): + updated["body_phrase"] = _body_phrase(updated.get("body"), updated.get("figure")) + updated["descriptor"] = _character_profile_descriptor(updated) + return updated + + def load_character_profile_json( profile_name: str = "", fallback_profile_json: str | dict[str, Any] | None = "", @@ -3061,6 +3100,15 @@ def load_character_profile_json( delete_now: bool = False, rename_now: bool = False, rename_to: str = "", + override_subject_type: str = "", + override_age: str = "", + override_body: str = "", + override_body_phrase: str = "", + override_skin: str = "", + override_hair: str = "", + override_eyes: str = "", + override_figure: str = "", + override_descriptor_detail: str = "", ) -> dict[str, str]: if not enabled: return _empty_profile_result("disabled") @@ -3103,6 +3151,18 @@ def load_character_profile_json( if not raw_profile: return _empty_profile_result("empty") profile = _normalize_character_profile(raw_profile, profile_name or raw_profile.get("profile_name", "")) + profile = _apply_character_profile_overrides( + profile, + override_subject_type=override_subject_type, + override_age=override_age, + override_body=override_body, + override_body_phrase=override_body_phrase, + override_skin=override_skin, + override_hair=override_hair, + override_eyes=override_eyes, + override_figure=override_figure, + override_descriptor_detail=override_descriptor_detail, + ) return { "profile_json": json.dumps(profile, ensure_ascii=True, sort_keys=True), "profile_name": profile["profile_name"],