Extract node tooltip policy
This commit is contained in:
+4
-366
@@ -1,9 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
import random
|
|
||||||
import re
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from server import PromptServer
|
from server import PromptServer
|
||||||
@@ -29,369 +25,11 @@ SXCP_CHARACTER_CAST = "SXCP_CHARACTER_CAST"
|
|||||||
SXCP_CHARACTER_SLOT = "SXCP_CHARACTER_SLOT"
|
SXCP_CHARACTER_SLOT = "SXCP_CHARACTER_SLOT"
|
||||||
SXCP_CHARACTER_PROFILE = "SXCP_CHARACTER_PROFILE"
|
SXCP_CHARACTER_PROFILE = "SXCP_CHARACTER_PROFILE"
|
||||||
|
|
||||||
COMMON_INPUT_TOOLTIPS = {
|
try:
|
||||||
"row_number": "Generation row to use. Changing it advances the deterministic selection without changing the main seed.",
|
from .node_tooltips import install_input_tooltips as _install_input_tooltips
|
||||||
"start_index": "Metadata/output index offset only. It does not limit category pools or random choices.",
|
except ImportError:
|
||||||
"seed": "Main seed used when no more specific seed config overrides an axis.",
|
from node_tooltips import install_input_tooltips as _install_input_tooltips
|
||||||
"global_seed": "One seed that locks all prompt axes so the same inputs can recreate the same result.",
|
|
||||||
"base_seed": "Base seed used by Seed Locker before applying a selected reroll axis.",
|
|
||||||
"reroll_seed": "Seed for the selected reroll axis. Use -1 to derive it from the base seed.",
|
|
||||||
"category": "Main category source. auto_weighted is legacy random; auto_full mixes legacy random with JSON categories including hardcore.",
|
|
||||||
"subcategory": "Specific subcategory, or random to choose within the selected category.",
|
|
||||||
"category_config": "Category/subcategory config from SxCP Category Preset.",
|
|
||||||
"cast_config": "Cast size config from SxCP Cast Control.",
|
|
||||||
"generation_profile": "General style/intensity profile from SxCP Generation Profile.",
|
|
||||||
"filter_config": "Ethnicity/body filter config. Ethnicity List can feed this too.",
|
|
||||||
"ethnicity_list": "Optional ethnicity pool. When connected, it overrides the slot or generator ethnicity picker.",
|
|
||||||
"seed_config": "Per-axis seed config. Connect Global Seed, Seed Locker, or Seed Control here.",
|
|
||||||
"camera_config": "Camera config used by the prompt formatter when camera mode is from_camera_config.",
|
|
||||||
"location_config": "Location config from SxCP Location Pool. It can replace or add to the category scene pool.",
|
|
||||||
"composition_config": "Composition config from SxCP Composition Pool or Location Theme. It can replace or add framing options.",
|
|
||||||
"softcore_camera_config": "Camera config used only for the softcore Insta/OF prompt. Falls back to camera_config if empty.",
|
|
||||||
"hardcore_camera_config": "Camera config used only for the hardcore Insta/OF prompt. Falls back to camera_config if empty.",
|
|
||||||
"character_profile": "Saved or loaded single-character profile. Character slots override this for configured casts.",
|
|
||||||
"character_cast": "Chain character slots here. The node closest to the final generator becomes the next auto_chain label.",
|
|
||||||
"character_slot": "Single slot payload for saving/loading profiles or debugging one character.",
|
|
||||||
"hardcore_position_config": "Hardcore action/position config. Chain Position Pool into Action Filter, then into the generator.",
|
|
||||||
"custom_locations": "One custom location per line. Use plain text, or slug: location text.",
|
|
||||||
"custom_compositions": "One custom composition/framing phrase per line.",
|
|
||||||
"theme": "Matched location and composition theme, useful when the place needs compatible framing.",
|
|
||||||
"metadata_json": "Structured metadata from an SxCP generator. Prefer this over raw prompt text for formatters and profile save.",
|
|
||||||
"source_text": "Raw prompt, caption, or metadata JSON depending on input_hint.",
|
|
||||||
"source_text_input": "Optional linked raw prompt/caption input. When connected, it overrides the source_text widget.",
|
|
||||||
"input_hint": "Tells the node how to interpret source_text. auto tries metadata first.",
|
|
||||||
"target": "For dual prompts, choose which side to output as the main Krea prompt.",
|
|
||||||
"detail_level": "Controls how much detail the rewriter keeps. concise is shorter, dense keeps more clauses.",
|
|
||||||
"style_mode": "How strongly the formatter rewrites visual style terms.",
|
|
||||||
"preserve_trigger": "Keep the trigger token in the formatted prompt instead of stripping it.",
|
|
||||||
"negative_prompt": "Negative prompt text to pass through or merge with generated negatives.",
|
|
||||||
"extra_positive": "Extra positive text appended after the generated prompt.",
|
|
||||||
"extra_negative": "Extra negative text appended after the generated negative prompt.",
|
|
||||||
"trigger": "Training or style trigger token.",
|
|
||||||
"prepend_trigger_to_prompt": "If enabled, put the trigger token at the start of generated prompts.",
|
|
||||||
"bucket_index": "0 picks a random bucket. 1+ picks that position inside the selected orientation pool.",
|
|
||||||
"megapixels": "Approximate megapixel count for the selected bucket.",
|
|
||||||
"enabled": "Enable this node's effect while keeping it wired in the graph.",
|
|
||||||
"combine_mode": "replace starts a new pool/config; add merges selected values into the incoming config.",
|
|
||||||
"manual": "Manual character details config. Non-empty manual fields override generated slot details.",
|
|
||||||
"characteristics": "Chainable character characteristic pool such as age/body/eyes/clothing.",
|
|
||||||
"hair_config": "Chainable hair pool. Combine length, color, and style nodes before the character slot.",
|
|
||||||
"summary": "Human-readable description of the config produced by this node.",
|
|
||||||
"status": "Operation result or warning text.",
|
|
||||||
"profile_name": "Name of the profile to save, load, rename, or delete.",
|
|
||||||
"manual_profile_name": "Free-text profile name used when profile_name is set to manual.",
|
|
||||||
"fallback_profile_json": "Profile JSON to use when a named profile cannot be loaded.",
|
|
||||||
"rename_to": "New profile name used only when rename_now is enabled.",
|
|
||||||
"save_now": "Writes the profile to disk only when enabled. Keep off while adjusting fields.",
|
|
||||||
"delete_now": "Deletes the selected profile when enabled.",
|
|
||||||
"rename_now": "Renames the selected profile when enabled.",
|
|
||||||
"source": "Where the save node reads character data from.",
|
|
||||||
"subject_type": "Character type for this slot or saved profile.",
|
|
||||||
"label": "Character label. auto_chain assigns the next Woman/Man label based on incoming cast order.",
|
|
||||||
"slot_seed": "Per-character seed. Use -1 to follow the generator person seed.",
|
|
||||||
"age": "Age choice for this slot. Use Age Range node for a custom random age pool.",
|
|
||||||
"manual_age": "Exact age phrase override, for example '32-year-old adult'.",
|
|
||||||
"ethnicity": "Ethnicity choice for this slot. A connected Ethnicity List overrides this picker.",
|
|
||||||
"figure": "General figure bias for generated body descriptors.",
|
|
||||||
"figure_bias": "Woman-slot figure bias. Body pool can give more precise body choices.",
|
|
||||||
"women_count": "Number of women in the generated cast when no Insta/OF preset overrides it.",
|
|
||||||
"men_count": "Number of men in the generated cast when no Insta/OF preset overrides it.",
|
|
||||||
"hardcore_women_count": "Number of women in the hardcore cast when hardcore_cast is use_counts.",
|
|
||||||
"hardcore_men_count": "Number of men in the hardcore cast when hardcore_cast is use_counts.",
|
|
||||||
"body": "Body choice for this slot. A Body Pool node can replace the random list.",
|
|
||||||
"manual_body": "Exact body phrase override.",
|
|
||||||
"body_phrase": "Full custom body wording. Use only when the body picker is not specific enough.",
|
|
||||||
"skin": "Manual skin/complexion phrase.",
|
|
||||||
"hair": "Manual hair phrase. Hair config nodes are better for controlled random choices.",
|
|
||||||
"eyes": "Manual eye description.",
|
|
||||||
"descriptor_detail": "How detailed this character's descriptor should be. Men usually work better compact.",
|
|
||||||
"expression_enabled": "Master expression toggle for this generator or character.",
|
|
||||||
"expression_intensity": "Expression intensity from 0 to 1. On the direct builder, -1 randomizes per row; on slots, -1 inherits the generator setting.",
|
|
||||||
"expression_intensity_mode": "For Generation Profile, choose profile_default, random, or fixed value from expression_intensity.",
|
|
||||||
"softcore_expression_intensity": "Optional expression intensity override for this character in softcore prompts. -1 inherits.",
|
|
||||||
"hardcore_expression_intensity": "Optional expression intensity override for this character in hardcore prompts. -1 inherits.",
|
|
||||||
"presence_mode": "Controls whether the character is visible, implied POV, or otherwise present.",
|
|
||||||
"softcore_outfit": "Manual softcore outfit text for this character.",
|
|
||||||
"hardcore_clothing": "Manual hardcore clothing/body exposure text for this character.",
|
|
||||||
"custom_softcore_outfits": "One custom softcore outfit per line. Used when softcore_source is custom.",
|
|
||||||
"custom_hardcore_clothing": "One custom hardcore clothing/body exposure state per line.",
|
|
||||||
"condition": "Loop condition. When false, the loop stops and passes current values through.",
|
|
||||||
"total": "Total number of loop iterations.",
|
|
||||||
"skip": "Number of leading loop indexes to skip. skip=1 starts generation at index 2.",
|
|
||||||
"collection": "Existing accumulated value or batch.",
|
|
||||||
"value": "Value to append, store, or pass through.",
|
|
||||||
"store_key": "Accumulator memory key. Same key shares stored entries across executions.",
|
|
||||||
"store_key_input": "Connect SxCP Accumulator store_key here so preview/delete/save uses the same accumulator and graph dependency.",
|
|
||||||
"action": "Accumulator operation: append, replace, clear, read, or append a variant.",
|
|
||||||
"max_items": "Maximum stored entries kept in this accumulator.",
|
|
||||||
"image_batch_mode": "How image entries are batched when dimensions differ.",
|
|
||||||
"skip_empty": "Ignore empty inputs instead of adding blank entries.",
|
|
||||||
"image": "Image to store in the accumulator.",
|
|
||||||
"entry_id": "Stable ID used for replace_by_entry_id or grouping variants.",
|
|
||||||
"entry_tag": "Optional suffix added to entry_id.",
|
|
||||||
"preview_limit": "Maximum number of accumulator images to show in the preview panel.",
|
|
||||||
"view_mode": "Accumulator Preview layout: grid shows many images, carousel shows one large image at a time.",
|
|
||||||
"zoom_level": "Accumulator Preview image scale. Higher values make grid thumbnails or carousel image area larger.",
|
|
||||||
"carousel_index": "1-based image position shown in carousel mode. The previous/next buttons update this value.",
|
|
||||||
"delete_action": "Optional execution-time delete operation. JS buttons can delete interactively without setting this.",
|
|
||||||
"delete_entry_id": "Entry id to delete when delete_action is delete_entry_id.",
|
|
||||||
"delete_index": "1-based entry index to delete when delete_action is delete_index. 0 disables it.",
|
|
||||||
"save_batch": "When enabled, save all current accumulator images once finished is true.",
|
|
||||||
"finished": "Gate for saving. Outside a loop, leave true; inside a loop, wire a final-iteration signal.",
|
|
||||||
"save_path": "Folder to save the accumulator batch. Relative paths are inside ComfyUI output; absolute paths are used directly.",
|
|
||||||
"filename_prefix": "Filename prefix for saved accumulator images.",
|
|
||||||
"clear_after_save": "Clear the accumulator store after a successful batch save.",
|
|
||||||
"preview_text": "Serialized persistent text preview. It is updated after execution and saved with the workflow.",
|
|
||||||
"preview_format": "How to convert an arbitrary input to preview text.",
|
|
||||||
"max_chars": "Maximum stored preview characters. 0 disables truncation.",
|
|
||||||
"mode": "Switch direction: pick_input selects one input to value, route_output sends route_value to one output.",
|
|
||||||
"index": "Index used by SxCP Index Switch. For Loop Start outputs one_based indexes by default.",
|
|
||||||
"index_base": "one_based means index 1 selects input_1. zero_based means index 0 selects input_1.",
|
|
||||||
"missing_behavior": "What to do when the requested switch input is not connected: use fallback, output none, clamp, or wrap.",
|
|
||||||
"fallback": "Optional value used by SxCP Index Switch when the requested input is missing and missing_behavior is fallback.",
|
|
||||||
"route_value": "Value routed to output_N when mode is route_output.",
|
|
||||||
"clothing": "Built-in clothing density for legacy direct generation. random picks full/minimal from the seeded row.",
|
|
||||||
"poses": "Built-in pose pool for legacy direct generation. random picks standard/evocative from the seeded row.",
|
|
||||||
"backside_bias": "Legacy bias toward rear/backside poses where that category supports it.",
|
|
||||||
"minimal_clothing_ratio": "Legacy weighted ratio override. -1 keeps the category/profile default.",
|
|
||||||
"standard_pose_ratio": "Legacy weighted ratio override. -1 keeps the category/profile default.",
|
|
||||||
"profile": "Generation profile preset for broad style, clothing, pose, and expression defaults.",
|
|
||||||
"clothing_override": "Override the profile clothing setting, or leave profile_default.",
|
|
||||||
"poses_override": "Override the profile pose setting, or leave profile_default.",
|
|
||||||
"trigger_policy": "Controls whether the profile prepends the trigger token.",
|
|
||||||
"cast_mode": "Preset cast shape. Custom counts are used when the preset allows them.",
|
|
||||||
"women_weights": "Comma-separated count weights. First value maps to women_start_count, second to +1, and so on.",
|
|
||||||
"men_weights": "Comma-separated count weights. First value maps to men_start_count, second to +1, and so on.",
|
|
||||||
"women_start_count": "Woman count represented by the first women_weights value.",
|
|
||||||
"men_start_count": "Man count represented by the first men_weights value.",
|
|
||||||
"empty_behavior": "What to do if the weighted pick selects zero women and zero men.",
|
|
||||||
"preset": "Category preset for common workflow lanes.",
|
|
||||||
"camera_mode": "Camera style preset.",
|
|
||||||
"shot_size": "How much of the body/frame should be visible.",
|
|
||||||
"angle": "Camera angle relative to the subject.",
|
|
||||||
"lens": "Lens wording to include in the prompt.",
|
|
||||||
"distance": "Camera distance wording.",
|
|
||||||
"orientation": "Horizontal/vertical framing wording.",
|
|
||||||
"phone_visibility": "Whether the prompt mentions a visible/hidden phone.",
|
|
||||||
"priority": "How strictly the prompt should enforce the camera wording.",
|
|
||||||
"camera_detail": "off omits camera text, compact keeps one line, full emits detailed camera wording.",
|
|
||||||
"subject_focus": "Optional camera focus phrase, such as face/body/contact emphasis.",
|
|
||||||
"strict_excludes": "When enabled, only selected ethnicity groups are used. When off, selections act more like soft includes.",
|
|
||||||
"min_age": "Minimum adult age in this custom age pool.",
|
|
||||||
"max_age": "Maximum adult age in this custom age pool.",
|
|
||||||
"softcore_source": "Softcore outfit source for this character. custom reads custom_softcore_outfits.",
|
|
||||||
"hardcore_state": "Hardcore clothing/body exposure state for this character.",
|
|
||||||
"softcore_expression_enabled": "Enable expression text in the softcore prompt.",
|
|
||||||
"hardcore_expression_enabled": "Enable expression text in the hardcore prompt.",
|
|
||||||
"flow": "Loop flow-control socket. Wire from the matching loop start node.",
|
|
||||||
"collection_mode": "How the loop end collects per-iteration values.",
|
|
||||||
"skip_none": "Do not add empty values to the collection.",
|
|
||||||
"collected": "Current accumulated value carried through the loop.",
|
|
||||||
"collect_value": "Value captured from the current loop iteration.",
|
|
||||||
"a": "First integer/boolean helper input.",
|
|
||||||
"b": "Second integer/boolean helper input.",
|
|
||||||
}
|
|
||||||
|
|
||||||
NODE_INPUT_TOOLTIPS = {
|
|
||||||
"SxCPSeedControl": {
|
|
||||||
"category_seed_mode": "auto/follow_main follows the main seed; fixed uses category_seed; random rerolls this axis each queue.",
|
|
||||||
"subcategory_seed_mode": "Controls which subcategory is selected. Change this to switch oral vs penetration when both are allowed.",
|
|
||||||
"content_seed_mode": "Controls item/outfit content for non-pose categories.",
|
|
||||||
"person_seed_mode": "Controls generated character appearance unless a slot seed overrides it.",
|
|
||||||
"scene_seed_mode": "Controls location/scene selection.",
|
|
||||||
"pose_seed_mode": "Controls pose/item selection for pose categories, including hardcore positions.",
|
|
||||||
"role_seed_mode": "Controls role assignment and secondary action details.",
|
|
||||||
"expression_seed_mode": "Controls selected expression text.",
|
|
||||||
"composition_seed_mode": "Controls framing/composition text.",
|
|
||||||
},
|
|
||||||
"SxCPSeedLocker": {
|
|
||||||
"reroll_axis": "Choose the one axis to change while the rest stays locked. Use pose for sexual pose, scene for location, person for appearance.",
|
|
||||||
},
|
|
||||||
"SxCPCastBias": {
|
|
||||||
"seed": "Fixed cast-bias seed. Use -1 for a fresh cast each queue, or connect Global Seed/Seed Locker through seed_config.",
|
|
||||||
"seed_config": "Optional seed config. The category seed controls weighted cast selection.",
|
|
||||||
"women_weights": "Example with women_start_count=1: 0.6,0.25,0.1 means 60% one woman, 25% two women, 10% three women.",
|
|
||||||
"men_weights": "Example with men_start_count=0: 0.5,0.35,0.1 means 50% no man, 35% one man, 10% two men.",
|
|
||||||
"empty_behavior": "Prevents accidental empty casts when both weighted pools pick zero.",
|
|
||||||
},
|
|
||||||
"SxCPSDXLBucketSize": {
|
|
||||||
"orientation": "Bucket orientation filter. any uses the full table; portrait/square/landscape restrict random selection.",
|
|
||||||
"seed": "Fixed bucket seed. Use -1 for a fresh random bucket each queue, or connect Global Seed for reproducible sizes.",
|
|
||||||
"row_number": "Deterministic row offset for the bucket. With a fixed seed, changing this advances the bucket choice.",
|
|
||||||
"bucket_index": "0=random. 1+ selects that bucket position inside the selected orientation pool and ignores seed.",
|
|
||||||
"seed_config": "Optional seed config. The composition seed controls bucket choice, so Seed Locker can keep sizes fixed while rerolling pose/person.",
|
|
||||||
},
|
|
||||||
"SxCPKrea2ResolutionSelector": {
|
|
||||||
"megapixels": "Target megapixel preset. If it cannot fit the aspect ratio under the 2K Krea2 Turbo limit, the node clamps to the maximum valid size.",
|
|
||||||
"aspect_ratio": "Krea API ratios are listed first; local-only helper ratios like 8:9 are included after them.",
|
|
||||||
},
|
|
||||||
"SxCPCameraControl": {
|
|
||||||
"camera_mode": "Camera style preset. Use from_camera_config in Insta/OF options to consume this.",
|
|
||||||
"priority": "locked makes the camera wording strict; soft_hint allows the model more freedom.",
|
|
||||||
"camera_detail": "off omits camera text, compact keeps one short line, full emits detailed camera constraints.",
|
|
||||||
"phone_visibility": "Use phone_hidden or suppress_phone_visibility when you do not want 'phone hidden' text in prompts.",
|
|
||||||
},
|
|
||||||
"SxCPCameraOrbitControl": {
|
|
||||||
"horizontal_angle": "Orbit angle in degrees. 0=front, 90=right side, 180=back, 270=left side.",
|
|
||||||
"vertical_angle": "Camera elevation. Negative looks up, positive looks down.",
|
|
||||||
"zoom": "Maps to distance/framing when framing is from_zoom.",
|
|
||||||
"framing": "How zoom should be translated into shot size/distance wording.",
|
|
||||||
"include_degrees": "Include numeric degree wording in addition to human camera direction.",
|
|
||||||
},
|
|
||||||
"SxCPQwenCameraTranslator": {
|
|
||||||
"qwen_prompt": "Camera prompt from Qwen MultiAngle, for example '<sks> front-right quarter view eye-level shot medium shot'.",
|
|
||||||
"camera_info": "Optional structured camera_info from Qwen MultiAngle. Used before qwen_prompt when prefer_camera_info is true.",
|
|
||||||
"prefer_camera_info": "Use structured camera_info values when available instead of parsing the text prompt.",
|
|
||||||
"suppress_phone_visibility": "Avoid adding phone visibility text unless you explicitly set a phone option.",
|
|
||||||
},
|
|
||||||
"SxCPHardcorePositionPool": {
|
|
||||||
"family": "Restrict the broad hardcore family. Use any when you want oral and penetration to both be possible.",
|
|
||||||
"combine_mode": "replace discards incoming position choices; add merges these choices with the incoming config.",
|
|
||||||
"hardcore_position_config": "Optional incoming config. Usually connect previous Position Pool here only when chaining pools.",
|
|
||||||
},
|
|
||||||
"SxCPHardcoreActionFilter": {
|
|
||||||
"focus": "keep_pool preserves/broadens the incoming pool; *_only modes force one action family.",
|
|
||||||
"allow_toys": "Allow toy/strap-on wording in hardcore actions.",
|
|
||||||
"allow_double": "Allow double-penetration or second-contact wording.",
|
|
||||||
"allow_penetration": "Allow vaginal/penetrative sex subcategories.",
|
|
||||||
"allow_foreplay": "Allow hardcore teasing/foreplay setup actions such as kissing, caressing, breast/face touching, and undressing.",
|
|
||||||
"allow_interaction": "Allow non-act interaction pools such as body worship, clothing transitions, guidance, camera presentation, watching, and aftercare.",
|
|
||||||
"allow_manual": "Allow manual stimulation pools such as fingering, clit rubbing, and mutual masturbation.",
|
|
||||||
"allow_oral": "Allow oral sex subcategories.",
|
|
||||||
"allow_outercourse": "Allow non-penetrative penis-contact acts such as boobjob/titjob, footjob, penis licking, and testicle sucking.",
|
|
||||||
"allow_anal": "Allow anal subcategories.",
|
|
||||||
"allow_climax": "Allow cumshot/climax aftermath subcategories.",
|
|
||||||
},
|
|
||||||
"SxCPInstaOFOptions": {
|
|
||||||
"softcore_cast": "solo keeps softcore focused on Woman A; same_as_hardcore includes the same cast as the hardcore prompt.",
|
|
||||||
"hardcore_cast": "use_counts reads hardcore_women_count/hardcore_men_count; presets set the counts automatically.",
|
|
||||||
"softcore_level": "Controls the soft prompt exposure/outfit level.",
|
|
||||||
"hardcore_level": "Controls how explicit the hardcore prompt style is.",
|
|
||||||
"platform_style": "Instagram/OnlyFans styling bias for the dual prompt pair.",
|
|
||||||
"continuity": "Whether the softcore and hardcore prompts share the room/creator setup.",
|
|
||||||
"hardcore_clothing_continuity": "How clothing carries from softcore to hardcore. explicit_nude omits clothing references.",
|
|
||||||
"softcore_camera_mode": "Camera mode for the softcore prompt, or from_camera_config.",
|
|
||||||
"hardcore_camera_mode": "Camera mode for the hardcore prompt. same_as_softcore reuses the softcore setting.",
|
|
||||||
"camera_detail": "Global camera verbosity for the pair unless a camera config overrides it.",
|
|
||||||
"hardcore_detail_density": "How dense the hardcore action sentence should be in the Krea formatter.",
|
|
||||||
},
|
|
||||||
"SxCPInstaOFPromptPair": {
|
|
||||||
"options_json": "Options from SxCP Insta/OF Options. If empty, defaults are used.",
|
|
||||||
"ethnicity": "Fallback ethnicity when no filter/ethnicity list or character slots are connected.",
|
|
||||||
"figure": "Fallback figure bias when no character slot overrides it.",
|
|
||||||
},
|
|
||||||
"SxCPPromptBuilderFromConfigs": {
|
|
||||||
"seed": "Main seed. Connect Seed Config for per-axis control.",
|
|
||||||
},
|
|
||||||
"SxCPCharacterProfileSave": {
|
|
||||||
"profile_name": "Profile filename stem. Saving requires save_now=true.",
|
|
||||||
"metadata_json": "Use generator metadata to save the currently generated character without regenerating it.",
|
|
||||||
"character_slot": "Use this when saving a configured slot directly.",
|
|
||||||
},
|
|
||||||
"SxCPCharacterProfileLoad": {
|
|
||||||
"enabled": "When false, outputs an empty profile and leaves downstream generation unchanged.",
|
|
||||||
"override_age": "Optional loaded-profile override. Empty keeps the profile value.",
|
|
||||||
"override_body": "Optional body override. Empty keeps the profile value.",
|
|
||||||
"override_descriptor_detail": "Override descriptor verbosity while keeping the rest of the loaded profile.",
|
|
||||||
},
|
|
||||||
"SxCPKrea2Formatter": {
|
|
||||||
"metadata_json": "Best input for Krea2 formatting because it preserves cast, camera, and hardcore action metadata.",
|
|
||||||
"preserve_trigger": "Reminder: Krea2 formatting is intended to remove training/style triggers. Leave false unless you intentionally want a raw text trigger preserved.",
|
|
||||||
"source_text": "Raw prompt fallback. Known trigger tokens are stripped by default for Krea2.",
|
|
||||||
},
|
|
||||||
"SxCPSDXLFormatter": {
|
|
||||||
"metadata_json": "Best input for SDXL tag formatting because it preserves cast, camera, outfit, and explicit action metadata.",
|
|
||||||
"formatter_profile": "High-level formatter defaults. manual_controls keeps style_preset and quality_preset authoritative.",
|
|
||||||
"style_preset": "Positive style anchor preset. flat_vector_pony matches the old SDXL tag style.",
|
|
||||||
"quality_preset": "Quality/score tag tail for SDXL or Pony-style checkpoints.",
|
|
||||||
"custom_style": "Optional replacement for the style preset. Leave empty to use style_preset.",
|
|
||||||
"custom_quality": "Optional replacement for the quality preset. Leave empty to use quality_preset.",
|
|
||||||
"nude_weight": "Weight used when explicit nude/body exposure tags are inferred.",
|
|
||||||
},
|
|
||||||
"SxCPCaptionNaturalizer": {
|
|
||||||
"metadata_json": "Best input for training captions because it preserves structured generator details.",
|
|
||||||
"caption_profile": "Preset behavior for the caption rewrite. manual_controls keeps detail/style/include-trigger widgets authoritative.",
|
|
||||||
"style_policy": "drop_style_tail removes generation/style boilerplate; keep_style_terms preserves more of it.",
|
|
||||||
"include_trigger": "Keep this true for LoRA/training captions so the trigger token is learned.",
|
|
||||||
},
|
|
||||||
"SxCPForLoopStart": {
|
|
||||||
"index": "Output loop index. First generated index is skip + 1.",
|
|
||||||
"collected": "Current accumulated value carried through the loop.",
|
|
||||||
},
|
|
||||||
"SxCPLoopAppend": {
|
|
||||||
"mode": "auto_batch tries tensor/latent batching first, then falls back to a list.",
|
|
||||||
},
|
|
||||||
"SxCPAccumulator": {
|
|
||||||
"image_batch_mode": "same_size_only keeps incompatible sizes separate; resize_to_first forces one image batch.",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _tooltip_for_input(node_name: str, input_name: str) -> str:
|
|
||||||
node_tooltips = NODE_INPUT_TOOLTIPS.get(node_name, {})
|
|
||||||
if input_name in node_tooltips:
|
|
||||||
return node_tooltips[input_name]
|
|
||||||
if input_name in COMMON_INPUT_TOOLTIPS:
|
|
||||||
return COMMON_INPUT_TOOLTIPS[input_name]
|
|
||||||
if input_name.endswith("_seed_mode"):
|
|
||||||
axis = input_name[: -len("_seed_mode")]
|
|
||||||
return f"How the {axis} seed is resolved: follow the main seed, use the fixed field, or reroll randomly."
|
|
||||||
if input_name.endswith("_seed"):
|
|
||||||
axis = input_name[: -len("_seed")]
|
|
||||||
return f"Fixed {axis} seed value. Used only when the matching seed mode is fixed, or as a fallback for auto modes."
|
|
||||||
if input_name.startswith("include_"):
|
|
||||||
value = input_name[len("include_") :].replace("_", " ")
|
|
||||||
return f"Include {value} in this random pool."
|
|
||||||
if input_name.startswith("initial_value"):
|
|
||||||
return "Carry value passed into the loop body and returned on the matching output."
|
|
||||||
if re.match(r"^input_\d+$", input_name):
|
|
||||||
return "Autoscaling switch input. Connect the last visible input to reveal the next one."
|
|
||||||
if re.match(r"^output_\d+$", input_name):
|
|
||||||
return "Autoscaling routed output. Connect the last visible output to reveal the next one."
|
|
||||||
if input_name.startswith("override_"):
|
|
||||||
return "Optional loaded-profile override. Leave empty or keep_profile to preserve the profile value."
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def _copy_input_spec_with_tooltip(input_spec, tooltip: str):
|
|
||||||
if not tooltip or not isinstance(input_spec, tuple):
|
|
||||||
return input_spec
|
|
||||||
if len(input_spec) >= 2 and isinstance(input_spec[1], dict):
|
|
||||||
options = dict(input_spec[1])
|
|
||||||
options.setdefault("tooltip", tooltip)
|
|
||||||
return (input_spec[0], options, *input_spec[2:])
|
|
||||||
if len(input_spec) == 1:
|
|
||||||
return (input_spec[0], {"tooltip": tooltip})
|
|
||||||
return input_spec
|
|
||||||
|
|
||||||
|
|
||||||
def _inject_input_tooltips(input_types: dict, node_name: str) -> dict:
|
|
||||||
patched = dict(input_types)
|
|
||||||
for group_name in ("required", "optional"):
|
|
||||||
group = patched.get(group_name)
|
|
||||||
if not isinstance(group, dict):
|
|
||||||
continue
|
|
||||||
patched_group = {}
|
|
||||||
for input_name, input_spec in group.items():
|
|
||||||
patched_group[input_name] = _copy_input_spec_with_tooltip(
|
|
||||||
input_spec,
|
|
||||||
_tooltip_for_input(node_name, input_name),
|
|
||||||
)
|
|
||||||
patched[group_name] = patched_group
|
|
||||||
return patched
|
|
||||||
|
|
||||||
|
|
||||||
def _install_input_tooltips(node_classes: dict[str, type]) -> None:
|
|
||||||
for node_name, node_class in node_classes.items():
|
|
||||||
original = getattr(node_class, "INPUT_TYPES", None)
|
|
||||||
if original is None or getattr(node_class, "_sxcp_tooltips_installed", False):
|
|
||||||
continue
|
|
||||||
|
|
||||||
def input_types(cls, _original=original, _node_name=node_name):
|
|
||||||
return _inject_input_tooltips(_original(), _node_name)
|
|
||||||
|
|
||||||
node_class.INPUT_TYPES = classmethod(input_types)
|
|
||||||
node_class._sxcp_tooltips_installed = True
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .loop_nodes import (
|
from .loop_nodes import (
|
||||||
|
|||||||
@@ -533,6 +533,9 @@ Already isolated:
|
|||||||
route-output selection, status text, and lazy-input selection live in
|
route-output selection, status text, and lazy-input selection live in
|
||||||
`index_switch_policy.py`; `loop_nodes.py` keeps the ComfyUI node wrapper and
|
`index_switch_policy.py`; `loop_nodes.py` keeps the ComfyUI node wrapper and
|
||||||
accumulator/loop runtime logic.
|
accumulator/loop runtime logic.
|
||||||
|
- node input tooltip inventory, node-specific tooltip overrides, dynamic input
|
||||||
|
fallback tooltip rules, and tooltip injection live in `node_tooltips.py`;
|
||||||
|
`__init__.py` only applies the installer to the assembled node registry.
|
||||||
- profile-save and accumulator server payload handling lives in
|
- profile-save and accumulator server payload handling lives in
|
||||||
`server_routes.py`; `__init__.py` only wires those pure handlers to ComfyUI
|
`server_routes.py`; `__init__.py` only wires those pure handlers to ComfyUI
|
||||||
JSON responses, and `tools/prompt_smoke.py` covers the handlers without
|
JSON responses, and `tools/prompt_smoke.py` covers the handlers without
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ Core helper ownership:
|
|||||||
| `prompt_hygiene.py` | Generic prompt, caption, and negative-prompt cleanup. |
|
| `prompt_hygiene.py` | Generic prompt, caption, and negative-prompt cleanup. |
|
||||||
| `row_normalization.py` | Final prompt-row and pair metadata normalization: trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, embedded soft/hard row output and side-metadata synchronization, and embedded row sanitation. |
|
| `row_normalization.py` | Final prompt-row and pair metadata normalization: trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, embedded soft/hard row output and side-metadata synchronization, and embedded row sanitation. |
|
||||||
| `formatter_input.py` | Shared formatter input parsing: text cleanup, metadata/source JSON detection, trigger-prefix stripping, shared prompt field-label inventory, fallback field-label stripping, `Avoid:` splitting, prompt-field extraction, and metadata row-value fallback. |
|
| `formatter_input.py` | Shared formatter input parsing: text cleanup, metadata/source JSON detection, trigger-prefix stripping, shared prompt field-label inventory, fallback field-label stripping, `Avoid:` splitting, prompt-field extraction, and metadata row-value fallback. |
|
||||||
|
| `node_tooltips.py` | Node input tooltip inventory, node-specific overrides, dynamic-input fallback rules, and tooltip injection installer used by `__init__.py`. |
|
||||||
| `server_routes.py` | Pure payload handlers for profile-save and accumulator server endpoints, used by ComfyUI routes and smoke tests without importing ComfyUI. |
|
| `server_routes.py` | Pure payload handlers for profile-save and accumulator server endpoints, used by ComfyUI routes and smoke tests without importing ComfyUI. |
|
||||||
| `sdxl_presets.py` | SDXL formatter profiles, style presets, quality presets, default negative prompt, and metadata-family tag hints used by the SDXL formatter and node choice lists. |
|
| `sdxl_presets.py` | SDXL formatter profiles, style presets, quality presets, default negative prompt, and metadata-family tag hints used by the SDXL formatter and node choice lists. |
|
||||||
| `caption_policy.py` | Caption naturalizer policy data and helpers: caption profiles, style tails, item labels, metadata-family caption labels, detail/style-policy normalization, clothing cleanup, and composition cleanup. |
|
| `caption_policy.py` | Caption naturalizer policy data and helpers: caption profiles, style tails, item labels, metadata-family caption labels, detail/style-policy normalization, clothing cleanup, and composition cleanup. |
|
||||||
|
|||||||
@@ -0,0 +1,367 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
COMMON_INPUT_TOOLTIPS = {
|
||||||
|
"row_number": "Generation row to use. Changing it advances the deterministic selection without changing the main seed.",
|
||||||
|
"start_index": "Metadata/output index offset only. It does not limit category pools or random choices.",
|
||||||
|
"seed": "Main seed used when no more specific seed config overrides an axis.",
|
||||||
|
"global_seed": "One seed that locks all prompt axes so the same inputs can recreate the same result.",
|
||||||
|
"base_seed": "Base seed used by Seed Locker before applying a selected reroll axis.",
|
||||||
|
"reroll_seed": "Seed for the selected reroll axis. Use -1 to derive it from the base seed.",
|
||||||
|
"category": "Main category source. auto_weighted is legacy random; auto_full mixes legacy random with JSON categories including hardcore.",
|
||||||
|
"subcategory": "Specific subcategory, or random to choose within the selected category.",
|
||||||
|
"category_config": "Category/subcategory config from SxCP Category Preset.",
|
||||||
|
"cast_config": "Cast size config from SxCP Cast Control.",
|
||||||
|
"generation_profile": "General style/intensity profile from SxCP Generation Profile.",
|
||||||
|
"filter_config": "Ethnicity/body filter config. Ethnicity List can feed this too.",
|
||||||
|
"ethnicity_list": "Optional ethnicity pool. When connected, it overrides the slot or generator ethnicity picker.",
|
||||||
|
"seed_config": "Per-axis seed config. Connect Global Seed, Seed Locker, or Seed Control here.",
|
||||||
|
"camera_config": "Camera config used by the prompt formatter when camera mode is from_camera_config.",
|
||||||
|
"location_config": "Location config from SxCP Location Pool. It can replace or add to the category scene pool.",
|
||||||
|
"composition_config": "Composition config from SxCP Composition Pool or Location Theme. It can replace or add framing options.",
|
||||||
|
"softcore_camera_config": "Camera config used only for the softcore Insta/OF prompt. Falls back to camera_config if empty.",
|
||||||
|
"hardcore_camera_config": "Camera config used only for the hardcore Insta/OF prompt. Falls back to camera_config if empty.",
|
||||||
|
"character_profile": "Saved or loaded single-character profile. Character slots override this for configured casts.",
|
||||||
|
"character_cast": "Chain character slots here. The node closest to the final generator becomes the next auto_chain label.",
|
||||||
|
"character_slot": "Single slot payload for saving/loading profiles or debugging one character.",
|
||||||
|
"hardcore_position_config": "Hardcore action/position config. Chain Position Pool into Action Filter, then into the generator.",
|
||||||
|
"custom_locations": "One custom location per line. Use plain text, or slug: location text.",
|
||||||
|
"custom_compositions": "One custom composition/framing phrase per line.",
|
||||||
|
"theme": "Matched location and composition theme, useful when the place needs compatible framing.",
|
||||||
|
"metadata_json": "Structured metadata from an SxCP generator. Prefer this over raw prompt text for formatters and profile save.",
|
||||||
|
"source_text": "Raw prompt, caption, or metadata JSON depending on input_hint.",
|
||||||
|
"source_text_input": "Optional linked raw prompt/caption input. When connected, it overrides the source_text widget.",
|
||||||
|
"input_hint": "Tells the node how to interpret source_text. auto tries metadata first.",
|
||||||
|
"target": "For dual prompts, choose which side to output as the main Krea prompt.",
|
||||||
|
"detail_level": "Controls how much detail the rewriter keeps. concise is shorter, dense keeps more clauses.",
|
||||||
|
"style_mode": "How strongly the formatter rewrites visual style terms.",
|
||||||
|
"preserve_trigger": "Keep the trigger token in the formatted prompt instead of stripping it.",
|
||||||
|
"negative_prompt": "Negative prompt text to pass through or merge with generated negatives.",
|
||||||
|
"extra_positive": "Extra positive text appended after the generated prompt.",
|
||||||
|
"extra_negative": "Extra negative text appended after the generated negative prompt.",
|
||||||
|
"trigger": "Training or style trigger token.",
|
||||||
|
"prepend_trigger_to_prompt": "If enabled, put the trigger token at the start of generated prompts.",
|
||||||
|
"bucket_index": "0 picks a random bucket. 1+ picks that position inside the selected orientation pool.",
|
||||||
|
"megapixels": "Approximate megapixel count for the selected bucket.",
|
||||||
|
"enabled": "Enable this node's effect while keeping it wired in the graph.",
|
||||||
|
"combine_mode": "replace starts a new pool/config; add merges selected values into the incoming config.",
|
||||||
|
"manual": "Manual character details config. Non-empty manual fields override generated slot details.",
|
||||||
|
"characteristics": "Chainable character characteristic pool such as age/body/eyes/clothing.",
|
||||||
|
"hair_config": "Chainable hair pool. Combine length, color, and style nodes before the character slot.",
|
||||||
|
"summary": "Human-readable description of the config produced by this node.",
|
||||||
|
"status": "Operation result or warning text.",
|
||||||
|
"profile_name": "Name of the profile to save, load, rename, or delete.",
|
||||||
|
"manual_profile_name": "Free-text profile name used when profile_name is set to manual.",
|
||||||
|
"fallback_profile_json": "Profile JSON to use when a named profile cannot be loaded.",
|
||||||
|
"rename_to": "New profile name used only when rename_now is enabled.",
|
||||||
|
"save_now": "Writes the profile to disk only when enabled. Keep off while adjusting fields.",
|
||||||
|
"delete_now": "Deletes the selected profile when enabled.",
|
||||||
|
"rename_now": "Renames the selected profile when enabled.",
|
||||||
|
"source": "Where the save node reads character data from.",
|
||||||
|
"subject_type": "Character type for this slot or saved profile.",
|
||||||
|
"label": "Character label. auto_chain assigns the next Woman/Man label based on incoming cast order.",
|
||||||
|
"slot_seed": "Per-character seed. Use -1 to follow the generator person seed.",
|
||||||
|
"age": "Age choice for this slot. Use Age Range node for a custom random age pool.",
|
||||||
|
"manual_age": "Exact age phrase override, for example '32-year-old adult'.",
|
||||||
|
"ethnicity": "Ethnicity choice for this slot. A connected Ethnicity List overrides this picker.",
|
||||||
|
"figure": "General figure bias for generated body descriptors.",
|
||||||
|
"figure_bias": "Woman-slot figure bias. Body pool can give more precise body choices.",
|
||||||
|
"women_count": "Number of women in the generated cast when no Insta/OF preset overrides it.",
|
||||||
|
"men_count": "Number of men in the generated cast when no Insta/OF preset overrides it.",
|
||||||
|
"hardcore_women_count": "Number of women in the hardcore cast when hardcore_cast is use_counts.",
|
||||||
|
"hardcore_men_count": "Number of men in the hardcore cast when hardcore_cast is use_counts.",
|
||||||
|
"body": "Body choice for this slot. A Body Pool node can replace the random list.",
|
||||||
|
"manual_body": "Exact body phrase override.",
|
||||||
|
"body_phrase": "Full custom body wording. Use only when the body picker is not specific enough.",
|
||||||
|
"skin": "Manual skin/complexion phrase.",
|
||||||
|
"hair": "Manual hair phrase. Hair config nodes are better for controlled random choices.",
|
||||||
|
"eyes": "Manual eye description.",
|
||||||
|
"descriptor_detail": "How detailed this character's descriptor should be. Men usually work better compact.",
|
||||||
|
"expression_enabled": "Master expression toggle for this generator or character.",
|
||||||
|
"expression_intensity": "Expression intensity from 0 to 1. On the direct builder, -1 randomizes per row; on slots, -1 inherits the generator setting.",
|
||||||
|
"expression_intensity_mode": "For Generation Profile, choose profile_default, random, or fixed value from expression_intensity.",
|
||||||
|
"softcore_expression_intensity": "Optional expression intensity override for this character in softcore prompts. -1 inherits.",
|
||||||
|
"hardcore_expression_intensity": "Optional expression intensity override for this character in hardcore prompts. -1 inherits.",
|
||||||
|
"presence_mode": "Controls whether the character is visible, implied POV, or otherwise present.",
|
||||||
|
"softcore_outfit": "Manual softcore outfit text for this character.",
|
||||||
|
"hardcore_clothing": "Manual hardcore clothing/body exposure text for this character.",
|
||||||
|
"custom_softcore_outfits": "One custom softcore outfit per line. Used when softcore_source is custom.",
|
||||||
|
"custom_hardcore_clothing": "One custom hardcore clothing/body exposure state per line.",
|
||||||
|
"condition": "Loop condition. When false, the loop stops and passes current values through.",
|
||||||
|
"total": "Total number of loop iterations.",
|
||||||
|
"skip": "Number of leading loop indexes to skip. skip=1 starts generation at index 2.",
|
||||||
|
"collection": "Existing accumulated value or batch.",
|
||||||
|
"value": "Value to append, store, or pass through.",
|
||||||
|
"store_key": "Accumulator memory key. Same key shares stored entries across executions.",
|
||||||
|
"store_key_input": "Connect SxCP Accumulator store_key here so preview/delete/save uses the same accumulator and graph dependency.",
|
||||||
|
"action": "Accumulator operation: append, replace, clear, read, or append a variant.",
|
||||||
|
"max_items": "Maximum stored entries kept in this accumulator.",
|
||||||
|
"image_batch_mode": "How image entries are batched when dimensions differ.",
|
||||||
|
"skip_empty": "Ignore empty inputs instead of adding blank entries.",
|
||||||
|
"image": "Image to store in the accumulator.",
|
||||||
|
"entry_id": "Stable ID used for replace_by_entry_id or grouping variants.",
|
||||||
|
"entry_tag": "Optional suffix added to entry_id.",
|
||||||
|
"preview_limit": "Maximum number of accumulator images to show in the preview panel.",
|
||||||
|
"view_mode": "Accumulator Preview layout: grid shows many images, carousel shows one large image at a time.",
|
||||||
|
"zoom_level": "Accumulator Preview image scale. Higher values make grid thumbnails or carousel image area larger.",
|
||||||
|
"carousel_index": "1-based image position shown in carousel mode. The previous/next buttons update this value.",
|
||||||
|
"delete_action": "Optional execution-time delete operation. JS buttons can delete interactively without setting this.",
|
||||||
|
"delete_entry_id": "Entry id to delete when delete_action is delete_entry_id.",
|
||||||
|
"delete_index": "1-based entry index to delete when delete_action is delete_index. 0 disables it.",
|
||||||
|
"save_batch": "When enabled, save all current accumulator images once finished is true.",
|
||||||
|
"finished": "Gate for saving. Outside a loop, leave true; inside a loop, wire a final-iteration signal.",
|
||||||
|
"save_path": "Folder to save the accumulator batch. Relative paths are inside ComfyUI output; absolute paths are used directly.",
|
||||||
|
"filename_prefix": "Filename prefix for saved accumulator images.",
|
||||||
|
"clear_after_save": "Clear the accumulator store after a successful batch save.",
|
||||||
|
"preview_text": "Serialized persistent text preview. It is updated after execution and saved with the workflow.",
|
||||||
|
"preview_format": "How to convert an arbitrary input to preview text.",
|
||||||
|
"max_chars": "Maximum stored preview characters. 0 disables truncation.",
|
||||||
|
"mode": "Switch direction: pick_input selects one input to value, route_output sends route_value to one output.",
|
||||||
|
"index": "Index used by SxCP Index Switch. For Loop Start outputs one_based indexes by default.",
|
||||||
|
"index_base": "one_based means index 1 selects input_1. zero_based means index 0 selects input_1.",
|
||||||
|
"missing_behavior": "What to do when the requested switch input is not connected: use fallback, output none, clamp, or wrap.",
|
||||||
|
"fallback": "Optional value used by SxCP Index Switch when the requested input is missing and missing_behavior is fallback.",
|
||||||
|
"route_value": "Value routed to output_N when mode is route_output.",
|
||||||
|
"clothing": "Built-in clothing density for legacy direct generation. random picks full/minimal from the seeded row.",
|
||||||
|
"poses": "Built-in pose pool for legacy direct generation. random picks standard/evocative from the seeded row.",
|
||||||
|
"backside_bias": "Legacy bias toward rear/backside poses where that category supports it.",
|
||||||
|
"minimal_clothing_ratio": "Legacy weighted ratio override. -1 keeps the category/profile default.",
|
||||||
|
"standard_pose_ratio": "Legacy weighted ratio override. -1 keeps the category/profile default.",
|
||||||
|
"profile": "Generation profile preset for broad style, clothing, pose, and expression defaults.",
|
||||||
|
"clothing_override": "Override the profile clothing setting, or leave profile_default.",
|
||||||
|
"poses_override": "Override the profile pose setting, or leave profile_default.",
|
||||||
|
"trigger_policy": "Controls whether the profile prepends the trigger token.",
|
||||||
|
"cast_mode": "Preset cast shape. Custom counts are used when the preset allows them.",
|
||||||
|
"women_weights": "Comma-separated count weights. First value maps to women_start_count, second to +1, and so on.",
|
||||||
|
"men_weights": "Comma-separated count weights. First value maps to men_start_count, second to +1, and so on.",
|
||||||
|
"women_start_count": "Woman count represented by the first women_weights value.",
|
||||||
|
"men_start_count": "Man count represented by the first men_weights value.",
|
||||||
|
"empty_behavior": "What to do if the weighted pick selects zero women and zero men.",
|
||||||
|
"preset": "Category preset for common workflow lanes.",
|
||||||
|
"camera_mode": "Camera style preset.",
|
||||||
|
"shot_size": "How much of the body/frame should be visible.",
|
||||||
|
"angle": "Camera angle relative to the subject.",
|
||||||
|
"lens": "Lens wording to include in the prompt.",
|
||||||
|
"distance": "Camera distance wording.",
|
||||||
|
"orientation": "Horizontal/vertical framing wording.",
|
||||||
|
"phone_visibility": "Whether the prompt mentions a visible/hidden phone.",
|
||||||
|
"priority": "How strictly the prompt should enforce the camera wording.",
|
||||||
|
"camera_detail": "off omits camera text, compact keeps one line, full emits detailed camera wording.",
|
||||||
|
"subject_focus": "Optional camera focus phrase, such as face/body/contact emphasis.",
|
||||||
|
"strict_excludes": "When enabled, only selected ethnicity groups are used. When off, selections act more like soft includes.",
|
||||||
|
"min_age": "Minimum adult age in this custom age pool.",
|
||||||
|
"max_age": "Maximum adult age in this custom age pool.",
|
||||||
|
"softcore_source": "Softcore outfit source for this character. custom reads custom_softcore_outfits.",
|
||||||
|
"hardcore_state": "Hardcore clothing/body exposure state for this character.",
|
||||||
|
"softcore_expression_enabled": "Enable expression text in the softcore prompt.",
|
||||||
|
"hardcore_expression_enabled": "Enable expression text in the hardcore prompt.",
|
||||||
|
"flow": "Loop flow-control socket. Wire from the matching loop start node.",
|
||||||
|
"collection_mode": "How the loop end collects per-iteration values.",
|
||||||
|
"skip_none": "Do not add empty values to the collection.",
|
||||||
|
"collected": "Current accumulated value carried through the loop.",
|
||||||
|
"collect_value": "Value captured from the current loop iteration.",
|
||||||
|
"a": "First integer/boolean helper input.",
|
||||||
|
"b": "Second integer/boolean helper input.",
|
||||||
|
}
|
||||||
|
|
||||||
|
NODE_INPUT_TOOLTIPS = {
|
||||||
|
"SxCPSeedControl": {
|
||||||
|
"category_seed_mode": "auto/follow_main follows the main seed; fixed uses category_seed; random rerolls this axis each queue.",
|
||||||
|
"subcategory_seed_mode": "Controls which subcategory is selected. Change this to switch oral vs penetration when both are allowed.",
|
||||||
|
"content_seed_mode": "Controls item/outfit content for non-pose categories.",
|
||||||
|
"person_seed_mode": "Controls generated character appearance unless a slot seed overrides it.",
|
||||||
|
"scene_seed_mode": "Controls location/scene selection.",
|
||||||
|
"pose_seed_mode": "Controls pose/item selection for pose categories, including hardcore positions.",
|
||||||
|
"role_seed_mode": "Controls role assignment and secondary action details.",
|
||||||
|
"expression_seed_mode": "Controls selected expression text.",
|
||||||
|
"composition_seed_mode": "Controls framing/composition text.",
|
||||||
|
},
|
||||||
|
"SxCPSeedLocker": {
|
||||||
|
"reroll_axis": "Choose the one axis to change while the rest stays locked. Use pose for sexual pose, scene for location, person for appearance.",
|
||||||
|
},
|
||||||
|
"SxCPCastBias": {
|
||||||
|
"seed": "Fixed cast-bias seed. Use -1 for a fresh cast each queue, or connect Global Seed/Seed Locker through seed_config.",
|
||||||
|
"seed_config": "Optional seed config. The category seed controls weighted cast selection.",
|
||||||
|
"women_weights": "Example with women_start_count=1: 0.6,0.25,0.1 means 60% one woman, 25% two women, 10% three women.",
|
||||||
|
"men_weights": "Example with men_start_count=0: 0.5,0.35,0.1 means 50% no man, 35% one man, 10% two men.",
|
||||||
|
"empty_behavior": "Prevents accidental empty casts when both weighted pools pick zero.",
|
||||||
|
},
|
||||||
|
"SxCPSDXLBucketSize": {
|
||||||
|
"orientation": "Bucket orientation filter. any uses the full table; portrait/square/landscape restrict random selection.",
|
||||||
|
"seed": "Fixed bucket seed. Use -1 for a fresh random bucket each queue, or connect Global Seed for reproducible sizes.",
|
||||||
|
"row_number": "Deterministic row offset for the bucket. With a fixed seed, changing this advances the bucket choice.",
|
||||||
|
"bucket_index": "0=random. 1+ selects that bucket position inside the selected orientation pool and ignores seed.",
|
||||||
|
"seed_config": "Optional seed config. The composition seed controls bucket choice, so Seed Locker can keep sizes fixed while rerolling pose/person.",
|
||||||
|
},
|
||||||
|
"SxCPKrea2ResolutionSelector": {
|
||||||
|
"megapixels": "Target megapixel preset. If it cannot fit the aspect ratio under the 2K Krea2 Turbo limit, the node clamps to the maximum valid size.",
|
||||||
|
"aspect_ratio": "Krea API ratios are listed first; local-only helper ratios like 8:9 are included after them.",
|
||||||
|
},
|
||||||
|
"SxCPCameraControl": {
|
||||||
|
"camera_mode": "Camera style preset. Use from_camera_config in Insta/OF options to consume this.",
|
||||||
|
"priority": "locked makes the camera wording strict; soft_hint allows the model more freedom.",
|
||||||
|
"camera_detail": "off omits camera text, compact keeps one short line, full emits detailed camera constraints.",
|
||||||
|
"phone_visibility": "Use phone_hidden or suppress_phone_visibility when you do not want 'phone hidden' text in prompts.",
|
||||||
|
},
|
||||||
|
"SxCPCameraOrbitControl": {
|
||||||
|
"horizontal_angle": "Orbit angle in degrees. 0=front, 90=right side, 180=back, 270=left side.",
|
||||||
|
"vertical_angle": "Camera elevation. Negative looks up, positive looks down.",
|
||||||
|
"zoom": "Maps to distance/framing when framing is from_zoom.",
|
||||||
|
"framing": "How zoom should be translated into shot size/distance wording.",
|
||||||
|
"include_degrees": "Include numeric degree wording in addition to human camera direction.",
|
||||||
|
},
|
||||||
|
"SxCPQwenCameraTranslator": {
|
||||||
|
"qwen_prompt": "Camera prompt from Qwen MultiAngle, for example '<sks> front-right quarter view eye-level shot medium shot'.",
|
||||||
|
"camera_info": "Optional structured camera_info from Qwen MultiAngle. Used before qwen_prompt when prefer_camera_info is true.",
|
||||||
|
"prefer_camera_info": "Use structured camera_info values when available instead of parsing the text prompt.",
|
||||||
|
"suppress_phone_visibility": "Avoid adding phone visibility text unless you explicitly set a phone option.",
|
||||||
|
},
|
||||||
|
"SxCPHardcorePositionPool": {
|
||||||
|
"family": "Restrict the broad hardcore family. Use any when you want oral and penetration to both be possible.",
|
||||||
|
"combine_mode": "replace discards incoming position choices; add merges these choices with the incoming config.",
|
||||||
|
"hardcore_position_config": "Optional incoming config. Usually connect previous Position Pool here only when chaining pools.",
|
||||||
|
},
|
||||||
|
"SxCPHardcoreActionFilter": {
|
||||||
|
"focus": "keep_pool preserves/broadens the incoming pool; *_only modes force one action family.",
|
||||||
|
"allow_toys": "Allow toy/strap-on wording in hardcore actions.",
|
||||||
|
"allow_double": "Allow double-penetration or second-contact wording.",
|
||||||
|
"allow_penetration": "Allow vaginal/penetrative sex subcategories.",
|
||||||
|
"allow_foreplay": "Allow hardcore teasing/foreplay setup actions such as kissing, caressing, breast/face touching, and undressing.",
|
||||||
|
"allow_interaction": "Allow non-act interaction pools such as body worship, clothing transitions, guidance, camera presentation, watching, and aftercare.",
|
||||||
|
"allow_manual": "Allow manual stimulation pools such as fingering, clit rubbing, and mutual masturbation.",
|
||||||
|
"allow_oral": "Allow oral sex subcategories.",
|
||||||
|
"allow_outercourse": "Allow non-penetrative penis-contact acts such as boobjob/titjob, footjob, penis licking, and testicle sucking.",
|
||||||
|
"allow_anal": "Allow anal subcategories.",
|
||||||
|
"allow_climax": "Allow cumshot/climax aftermath subcategories.",
|
||||||
|
},
|
||||||
|
"SxCPInstaOFOptions": {
|
||||||
|
"softcore_cast": "solo keeps softcore focused on Woman A; same_as_hardcore includes the same cast as the hardcore prompt.",
|
||||||
|
"hardcore_cast": "use_counts reads hardcore_women_count/hardcore_men_count; presets set the counts automatically.",
|
||||||
|
"softcore_level": "Controls the soft prompt exposure/outfit level.",
|
||||||
|
"hardcore_level": "Controls how explicit the hardcore prompt style is.",
|
||||||
|
"platform_style": "Instagram/OnlyFans styling bias for the dual prompt pair.",
|
||||||
|
"continuity": "Whether the softcore and hardcore prompts share the room/creator setup.",
|
||||||
|
"hardcore_clothing_continuity": "How clothing carries from softcore to hardcore. explicit_nude omits clothing references.",
|
||||||
|
"softcore_camera_mode": "Camera mode for the softcore prompt, or from_camera_config.",
|
||||||
|
"hardcore_camera_mode": "Camera mode for the hardcore prompt. same_as_softcore reuses the softcore setting.",
|
||||||
|
"camera_detail": "Global camera verbosity for the pair unless a camera config overrides it.",
|
||||||
|
"hardcore_detail_density": "How dense the hardcore action sentence should be in the Krea formatter.",
|
||||||
|
},
|
||||||
|
"SxCPInstaOFPromptPair": {
|
||||||
|
"options_json": "Options from SxCP Insta/OF Options. If empty, defaults are used.",
|
||||||
|
"ethnicity": "Fallback ethnicity when no filter/ethnicity list or character slots are connected.",
|
||||||
|
"figure": "Fallback figure bias when no character slot overrides it.",
|
||||||
|
},
|
||||||
|
"SxCPPromptBuilderFromConfigs": {
|
||||||
|
"seed": "Main seed. Connect Seed Config for per-axis control.",
|
||||||
|
},
|
||||||
|
"SxCPCharacterProfileSave": {
|
||||||
|
"profile_name": "Profile filename stem. Saving requires save_now=true.",
|
||||||
|
"metadata_json": "Use generator metadata to save the currently generated character without regenerating it.",
|
||||||
|
"character_slot": "Use this when saving a configured slot directly.",
|
||||||
|
},
|
||||||
|
"SxCPCharacterProfileLoad": {
|
||||||
|
"enabled": "When false, outputs an empty profile and leaves downstream generation unchanged.",
|
||||||
|
"override_age": "Optional loaded-profile override. Empty keeps the profile value.",
|
||||||
|
"override_body": "Optional body override. Empty keeps the profile value.",
|
||||||
|
"override_descriptor_detail": "Override descriptor verbosity while keeping the rest of the loaded profile.",
|
||||||
|
},
|
||||||
|
"SxCPKrea2Formatter": {
|
||||||
|
"metadata_json": "Best input for Krea2 formatting because it preserves cast, camera, and hardcore action metadata.",
|
||||||
|
"preserve_trigger": "Reminder: Krea2 formatting is intended to remove training/style triggers. Leave false unless you intentionally want a raw text trigger preserved.",
|
||||||
|
"source_text": "Raw prompt fallback. Known trigger tokens are stripped by default for Krea2.",
|
||||||
|
},
|
||||||
|
"SxCPSDXLFormatter": {
|
||||||
|
"metadata_json": "Best input for SDXL tag formatting because it preserves cast, camera, outfit, and explicit action metadata.",
|
||||||
|
"formatter_profile": "High-level formatter defaults. manual_controls keeps style_preset and quality_preset authoritative.",
|
||||||
|
"style_preset": "Positive style anchor preset. flat_vector_pony matches the old SDXL tag style.",
|
||||||
|
"quality_preset": "Quality/score tag tail for SDXL or Pony-style checkpoints.",
|
||||||
|
"custom_style": "Optional replacement for the style preset. Leave empty to use style_preset.",
|
||||||
|
"custom_quality": "Optional replacement for the quality preset. Leave empty to use quality_preset.",
|
||||||
|
"nude_weight": "Weight used when explicit nude/body exposure tags are inferred.",
|
||||||
|
},
|
||||||
|
"SxCPCaptionNaturalizer": {
|
||||||
|
"metadata_json": "Best input for training captions because it preserves structured generator details.",
|
||||||
|
"caption_profile": "Preset behavior for the caption rewrite. manual_controls keeps detail/style/include-trigger widgets authoritative.",
|
||||||
|
"style_policy": "drop_style_tail removes generation/style boilerplate; keep_style_terms preserves more of it.",
|
||||||
|
"include_trigger": "Keep this true for LoRA/training captions so the trigger token is learned.",
|
||||||
|
},
|
||||||
|
"SxCPForLoopStart": {
|
||||||
|
"index": "Output loop index. First generated index is skip + 1.",
|
||||||
|
"collected": "Current accumulated value carried through the loop.",
|
||||||
|
},
|
||||||
|
"SxCPLoopAppend": {
|
||||||
|
"mode": "auto_batch tries tensor/latent batching first, then falls back to a list.",
|
||||||
|
},
|
||||||
|
"SxCPAccumulator": {
|
||||||
|
"image_batch_mode": "same_size_only keeps incompatible sizes separate; resize_to_first forces one image batch.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _tooltip_for_input(node_name: str, input_name: str) -> str:
|
||||||
|
node_tooltips = NODE_INPUT_TOOLTIPS.get(node_name, {})
|
||||||
|
if input_name in node_tooltips:
|
||||||
|
return node_tooltips[input_name]
|
||||||
|
if input_name in COMMON_INPUT_TOOLTIPS:
|
||||||
|
return COMMON_INPUT_TOOLTIPS[input_name]
|
||||||
|
if input_name.endswith("_seed_mode"):
|
||||||
|
axis = input_name[: -len("_seed_mode")]
|
||||||
|
return f"How the {axis} seed is resolved: follow the main seed, use the fixed field, or reroll randomly."
|
||||||
|
if input_name.endswith("_seed"):
|
||||||
|
axis = input_name[: -len("_seed")]
|
||||||
|
return f"Fixed {axis} seed value. Used only when the matching seed mode is fixed, or as a fallback for auto modes."
|
||||||
|
if input_name.startswith("include_"):
|
||||||
|
value = input_name[len("include_") :].replace("_", " ")
|
||||||
|
return f"Include {value} in this random pool."
|
||||||
|
if input_name.startswith("initial_value"):
|
||||||
|
return "Carry value passed into the loop body and returned on the matching output."
|
||||||
|
if re.match(r"^input_\d+$", input_name):
|
||||||
|
return "Autoscaling switch input. Connect the last visible input to reveal the next one."
|
||||||
|
if re.match(r"^output_\d+$", input_name):
|
||||||
|
return "Autoscaling routed output. Connect the last visible output to reveal the next one."
|
||||||
|
if input_name.startswith("override_"):
|
||||||
|
return "Optional loaded-profile override. Leave empty or keep_profile to preserve the profile value."
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _copy_input_spec_with_tooltip(input_spec, tooltip: str):
|
||||||
|
if not tooltip or not isinstance(input_spec, tuple):
|
||||||
|
return input_spec
|
||||||
|
if len(input_spec) >= 2 and isinstance(input_spec[1], dict):
|
||||||
|
options = dict(input_spec[1])
|
||||||
|
options.setdefault("tooltip", tooltip)
|
||||||
|
return (input_spec[0], options, *input_spec[2:])
|
||||||
|
if len(input_spec) == 1:
|
||||||
|
return (input_spec[0], {"tooltip": tooltip})
|
||||||
|
return input_spec
|
||||||
|
|
||||||
|
|
||||||
|
def _inject_input_tooltips(input_types: dict, node_name: str) -> dict:
|
||||||
|
patched = dict(input_types)
|
||||||
|
for group_name in ("required", "optional"):
|
||||||
|
group = patched.get(group_name)
|
||||||
|
if not isinstance(group, dict):
|
||||||
|
continue
|
||||||
|
patched_group = {}
|
||||||
|
for input_name, input_spec in group.items():
|
||||||
|
patched_group[input_name] = _copy_input_spec_with_tooltip(
|
||||||
|
input_spec,
|
||||||
|
_tooltip_for_input(node_name, input_name),
|
||||||
|
)
|
||||||
|
patched[group_name] = patched_group
|
||||||
|
return patched
|
||||||
|
|
||||||
|
|
||||||
|
def install_input_tooltips(node_classes: dict[str, type]) -> None:
|
||||||
|
for node_name, node_class in node_classes.items():
|
||||||
|
original = getattr(node_class, "INPUT_TYPES", None)
|
||||||
|
if original is None or getattr(node_class, "_sxcp_tooltips_installed", False):
|
||||||
|
continue
|
||||||
|
|
||||||
|
def input_types(cls, _original=original, _node_name=node_name):
|
||||||
|
return _inject_input_tooltips(_original(), _node_name)
|
||||||
|
|
||||||
|
node_class.INPUT_TYPES = classmethod(input_types)
|
||||||
|
node_class._sxcp_tooltips_installed = True
|
||||||
@@ -42,6 +42,7 @@ import hardcore_position_config # noqa: E402
|
|||||||
import __init__ as sxcp_nodes # noqa: E402
|
import __init__ as sxcp_nodes # noqa: E402
|
||||||
import generation_profile_config # noqa: E402
|
import generation_profile_config # noqa: E402
|
||||||
import index_switch_policy # noqa: E402
|
import index_switch_policy # noqa: E402
|
||||||
|
import node_tooltips # noqa: E402
|
||||||
import krea_cast # noqa: E402
|
import krea_cast # noqa: E402
|
||||||
import krea_configured_cast_formatter # noqa: E402
|
import krea_configured_cast_formatter # noqa: E402
|
||||||
import krea_formatter # noqa: E402
|
import krea_formatter # noqa: E402
|
||||||
@@ -4482,6 +4483,15 @@ def smoke_node_utility_registration() -> None:
|
|||||||
seed_inputs = seed_control.INPUT_TYPES().get("required") or {}
|
seed_inputs = seed_control.INPUT_TYPES().get("required") or {}
|
||||||
_expect("category_seed_mode" in seed_inputs, "Seed Control lost category seed mode input")
|
_expect("category_seed_mode" in seed_inputs, "Seed Control lost category seed mode input")
|
||||||
_expect("tooltip" in seed_inputs["category_seed_mode"][1], "Seed Control tooltip injection missing")
|
_expect("tooltip" in seed_inputs["category_seed_mode"][1], "Seed Control tooltip injection missing")
|
||||||
|
_expect(
|
||||||
|
node_tooltips._tooltip_for_input("SxCPSeedControl", "category_seed_mode")
|
||||||
|
== "auto/follow_main follows the main seed; fixed uses category_seed; random rerolls this axis each queue.",
|
||||||
|
"Node tooltip policy lost Seed Control override",
|
||||||
|
)
|
||||||
|
_expect(
|
||||||
|
"Autoscaling switch input" in node_tooltips._tooltip_for_input("SxCPIndexSwitch", "input_12"),
|
||||||
|
"Node tooltip policy lost autoscaling input fallback",
|
||||||
|
)
|
||||||
|
|
||||||
seed, seed_config, summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPGlobalSeed"]().build(12345)
|
seed, seed_config, summary = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPGlobalSeed"]().build(12345)
|
||||||
parsed_seed = json.loads(seed_config)
|
parsed_seed = json.loads(seed_config)
|
||||||
|
|||||||
Reference in New Issue
Block a user