Connect character profiles to slots

This commit is contained in:
2026-06-24 20:15:09 +02:00
parent 5f4f166631
commit aca1e2f421
3 changed files with 65 additions and 19 deletions
+18 -11
View File
@@ -181,12 +181,15 @@ nodes remain useful for one reusable primary character, but slots are better
when you need different settings for each participant.
`SxCP Character Profile Save` extracts a reusable woman/man profile from
`metadata_json` or from manual fields. The profile stores age, body/body phrase,
skin, hair, eyes, figure, and subject type. It only writes a file when
the `Save Profile Now` button is clicked; otherwise it just outputs profile JSON
for direct wiring. Saved files are written under `profiles/<profile_name>.json`;
saved profile files are ignored by git. The button is backed by the hidden
`save_now` trigger and queues the workflow once.
`metadata_json`, a `character_slot`, or from manual fields. The profile stores
age, body/body phrase, skin, hair, eyes, figure, and subject type. To save a
Woman Slot, connect `Woman Slot.character_slot` to
`Character Profile Save.character_slot`, set `source=character_slot`, enter a
name, then click `Save Profile Now`. It only writes a file when the button is
clicked; otherwise it just outputs profile JSON for direct wiring. Saved files
are written under `profiles/<profile_name>.json`; saved profile files are
ignored by git. The button is backed by the hidden `save_now` trigger and queues
the workflow once.
`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
@@ -202,12 +205,16 @@ buttons are backed by hidden `delete_now` and `rename_now` triggers and queue
the workflow once.
Connect the loader's `character_profile` output to `SxCP Prompt Builder`,
`SxCP Prompt Builder From Configs`, or `SxCP Insta/OF Prompt Pair`.
`SxCP Prompt Builder From Configs`, or `SxCP Insta/OF Prompt Pair` for the
older single-primary-character path. Connect `character_cast` instead when you
want the loaded profile to act like a slot in a cast chain.
Profile reuse currently applies to structured JSON-category single woman/man
rows and to the primary creator in Insta/OF pair mode. The outfit, scene, pose,
expression, and composition can still change while the saved character
appearance remains stable.
When loaded through `character_profile`, profile reuse applies to structured
JSON-category single woman/man rows and to the primary creator in Insta/OF pair
mode. When loaded through `character_cast`, the same saved appearance behaves
like an auto-labeled slot and can participate in couple/group casts. The outfit,
scene, pose, expression, and composition can still change while the saved
character appearance remains stable.
`SxCP Seed Control` outputs `seed_config`, which can be connected to the prompt
builder's optional `seed_config` input. When an axis is set to `random`, the
+24 -7
View File
@@ -989,7 +989,7 @@ class SxCPCharacterProfileSave:
return {
"required": {
"profile_name": ("STRING", {"default": "saved_character"}),
"source": (["metadata_json", "manual"], {"default": "metadata_json"}),
"source": (["metadata_json", "character_slot", "manual"], {"default": "metadata_json"}),
"subject_type": (["woman", "man"], {"default": "woman"}),
"age": ("STRING", {"default": ""}),
"body": ("STRING", {"default": ""}),
@@ -1002,11 +1002,12 @@ class SxCPCharacterProfileSave:
},
"optional": {
"metadata_json": ("STRING", {"default": "", "multiline": True}),
"character_slot": ("STRING", {"default": "", "multiline": True}),
},
}
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING")
RETURN_NAMES = ("character_profile", "descriptor", "profile_name", "saved_path", "status")
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
RETURN_NAMES = ("character_profile", "descriptor", "profile_name", "saved_path", "status", "character_cast")
FUNCTION = "build"
CATEGORY = "prompt_builder"
@@ -1024,11 +1025,13 @@ class SxCPCharacterProfileSave:
figure,
save_now,
metadata_json="",
character_slot="",
):
profile = build_character_profile_json(
profile_name=profile_name,
source=source,
metadata_json=metadata_json or "",
character_slot=character_slot or "",
subject_type=subject_type,
age=age,
body=body,
@@ -1039,7 +1042,14 @@ class SxCPCharacterProfileSave:
figure=figure,
save_now=save_now,
)
return profile["profile_json"], profile["descriptor"], profile["profile_name"], profile["saved_path"], profile["status"]
return (
profile["profile_json"],
profile["descriptor"],
profile["profile_name"],
profile["saved_path"],
profile["status"],
profile["profile_json"],
)
class SxCPCharacterProfileLoad:
@@ -1059,8 +1069,8 @@ class SxCPCharacterProfileLoad:
},
}
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING")
RETURN_NAMES = ("character_profile", "descriptor", "profile_name", "saved_path", "status")
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
RETURN_NAMES = ("character_profile", "descriptor", "profile_name", "saved_path", "status", "character_cast")
FUNCTION = "build"
CATEGORY = "prompt_builder"
@@ -1083,7 +1093,14 @@ class SxCPCharacterProfileLoad:
rename_now=rename_now,
rename_to=rename_to,
)
return profile["profile_json"], profile["descriptor"], profile["profile_name"], profile["saved_path"], profile["status"]
return (
profile["profile_json"],
profile["descriptor"],
profile["profile_name"],
profile["saved_path"],
profile["status"],
profile["profile_json"],
)
class SxCPCaptionNaturalizer:
+23 -1
View File
@@ -2847,6 +2847,13 @@ def _row_from_profile_metadata(metadata_json: str | dict[str, Any] | None) -> di
return row
def _row_from_character_slot(character_slot: str | dict[str, Any] | None) -> dict[str, Any]:
slots = _parse_character_cast(character_slot)
if not slots:
return {}
return slots[-1]
def _character_profile_descriptor(profile: dict[str, Any]) -> str:
subject = str(profile.get("subject_type") or profile.get("subject") or "person").strip()
return _descriptor_from_parts(
@@ -2890,6 +2897,7 @@ def build_character_profile_json(
profile_name: str = "",
source: str = "metadata_json",
metadata_json: str | dict[str, Any] | None = "",
character_slot: str | dict[str, Any] | None = "",
subject_type: str = "woman",
age: str = "",
body: str = "",
@@ -2900,7 +2908,21 @@ def build_character_profile_json(
figure: str = "",
save_now: bool = False,
) -> dict[str, str]:
if source == "metadata_json":
if source == "character_slot":
row = _row_from_character_slot(character_slot or metadata_json)
raw_profile = {
"profile_name": profile_name,
"subject_type": row.get("subject_type") or subject_type,
"age": row.get("age") or age,
"body": row.get("body") or body,
"body_phrase": row.get("body_phrase") or body_phrase,
"skin": row.get("skin") or skin,
"hair": row.get("hair") or hair,
"eyes": row.get("eyes") or eyes,
"figure": row.get("figure") or figure,
"descriptor_detail": row.get("descriptor_detail") or "auto",
}
elif source == "metadata_json":
row = _row_from_profile_metadata(metadata_json)
raw_profile = {
"profile_name": profile_name,