from __future__ import annotations from dataclasses import dataclass from typing import Any, Callable try: from . import pair_camera from . import pair_cast from . import pair_clothing from . import pair_output from . import pair_rows except ImportError: # Allows local smoke tests with top-level imports. import pair_camera import pair_cast import pair_clothing import pair_output import pair_rows BuildPrompt = Callable[..., dict[str, Any]] AxisRng = Callable[[dict[str, int], str, int, int], Any] Choose = Callable[[Any, list[str]], str] @dataclass(frozen=True) class InstaPairBuildRequest: row_number: int start_index: int seed: int ethnicity: str figure: str no_plus_women: bool no_black: bool trigger: str prepend_trigger_to_prompt: bool seed_config: str | dict[str, Any] | None = None softcore_seed_config: str | dict[str, Any] | None = None hardcore_seed_config: str | dict[str, Any] | None = None options_json: str | dict[str, Any] | None = None filter_config: str | dict[str, Any] | None = None camera_config: str | dict[str, Any] | None = None softcore_camera_config: str | dict[str, Any] | None = None hardcore_camera_config: str | dict[str, Any] | None = None character_profile: str | dict[str, Any] | None = "" character_cast: str | dict[str, Any] | list[Any] | None = "" hardcore_position_config: str | dict[str, Any] | None = "" location_config: str | dict[str, Any] | None = "" composition_config: str | dict[str, Any] | None = "" style_config: str | dict[str, Any] | None = "" extra_positive: str = "" extra_negative: str = "" @dataclass(frozen=True) class InstaPairBuildDependencies: default_trigger: str random_subcategory: str soft_negative_base: str hard_negative_base: str camera_detail_choices: list[str] | tuple[str, ...] hardcore_clothing_continuity: dict[str, str] platform_styles: dict[str, str] soft_levels: dict[str, str] hardcore_levels: dict[str, str] parse_options: Callable[[str | dict[str, Any] | None], dict[str, Any]] parse_filter_config: Callable[[str | dict[str, Any] | None], dict[str, Any]] parse_seed_config: Callable[[str | dict[str, Any] | None], dict[str, int]] parse_character_cast: Callable[[str | dict[str, Any] | list[Any] | None], list[dict[str, Any]]] character_slot_label_map: Callable[[list[dict[str, Any]]], dict[str, dict[str, Any]]] pov_character_labels: Callable[[dict[str, dict[str, Any]], int], list[str]] softcore_category: Callable[[str], tuple[str, str]] build_prompt: BuildPrompt axis_rng: AxisRng cast_expression_intensity_override: Callable[ [float, dict[str, dict[str, Any]], int, int, str], tuple[float | None, str], ] context_from_character_slot: Callable[[Any, dict[str, Any], str, str, str, bool, bool], dict[str, Any]] apply_character_context_to_row: Callable[[dict[str, Any], dict[str, Any]], dict[str, Any]] disable_row_expression: Callable[[dict[str, Any], str], dict[str, Any]] slot_softcore_outfit: Callable[[dict[str, Any] | None, Any], str] softcore_outfit: Callable[[Any, str], str] softcore_pose: Callable[[Any, str], str] softcore_item_prompt_label: Callable[[str], str] pov_prompt_directive: Callable[[list[str]], str] pov_composition_prompt: Callable[[Any, list[str]], str] hardcore_counts: Callable[[dict[str, Any]], tuple[int, int]] character_context_for_label: Callable[ [str, dict[str, dict[str, Any]], Any, str, str, bool, bool], tuple[dict[str, Any], dict[str, Any] | None], ] slot_is_pov: Callable[[dict[str, Any] | None], bool] choose: Choose camera_config_with_mode: Callable[[str | dict[str, Any] | None, str], dict[str, Any]] camera_directive: Callable[[str | dict[str, Any] | None], tuple[str, dict[str, Any]]] apply_contextual_composition: Callable[[dict[str, Any], str], dict[str, Any]] contextual_composition_prompt: Callable[[Any, Any, str], str] composition_prompt: Callable[[Any], str] camera_scene_directive_for_context: Callable[ [Any, Any, str | dict[str, Any] | None, list[str] | None, str], tuple[str, dict[str, Any]], ] slot_hardcore_clothing: Callable[[dict[str, Any] | None, Any], str] hardcore_detail_directive: Callable[[Any], str] camera_caption_text: Callable[[dict[str, Any]], str] def build_insta_of_pair(request: InstaPairBuildRequest, deps: InstaPairBuildDependencies) -> dict[str, Any]: options = deps.parse_options(request.options_json) ethnicity = request.ethnicity figure = request.figure no_plus_women = request.no_plus_women no_black = request.no_black if request.filter_config: filters = deps.parse_filter_config(request.filter_config) ethnicity = filters["ethnicity"] figure = filters["figure"] no_plus_women = filters["no_plus_women"] no_black = filters["no_black"] hard_women_count, hard_men_count = deps.hardcore_counts(options) active_trigger = request.trigger.strip() or deps.default_trigger parsed_seed_config = deps.parse_seed_config(request.seed_config) parsed_softcore_seed_config = ( deps.parse_seed_config(request.softcore_seed_config) if request.softcore_seed_config else parsed_seed_config ) parsed_hardcore_seed_config = ( deps.parse_seed_config(request.hardcore_seed_config) if request.hardcore_seed_config else parsed_seed_config ) character_slots = deps.parse_character_cast(request.character_cast) character_slot_map = deps.character_slot_label_map(character_slots) pov_character_labels = deps.pov_character_labels(character_slot_map, hard_men_count) softcore_level_key = str(options["softcore_level"]) soft_category, soft_subcategory = deps.softcore_category(softcore_level_key) row_route = pair_rows.build_insta_pair_rows_result( row_number=request.row_number, start_index=request.start_index, seed=request.seed, active_trigger=active_trigger, parsed_seed_config=parsed_seed_config, parsed_softcore_seed_config=parsed_softcore_seed_config, parsed_hardcore_seed_config=parsed_hardcore_seed_config, options=options, ethnicity=ethnicity, figure=figure, no_plus_women=no_plus_women, no_black=no_black, character_profile=request.character_profile, character_cast=request.character_cast or "", character_slot_map=character_slot_map, pov_character_labels=pov_character_labels, hard_women_count=hard_women_count, hard_men_count=hard_men_count, soft_category=soft_category, soft_subcategory=soft_subcategory, softcore_level_key=softcore_level_key, hardcore_random_subcategory=deps.random_subcategory, hardcore_position_config=request.hardcore_position_config, location_config=request.location_config or "", composition_config=request.composition_config or "", style_config=request.style_config or "", build_prompt=deps.build_prompt, axis_rng=deps.axis_rng, cast_expression_intensity_override=deps.cast_expression_intensity_override, context_from_character_slot=deps.context_from_character_slot, apply_character_context_to_row=deps.apply_character_context_to_row, disable_row_expression=deps.disable_row_expression, slot_softcore_outfit=deps.slot_softcore_outfit, softcore_outfit=deps.softcore_outfit, softcore_pose=deps.softcore_pose, softcore_item_prompt_label=deps.softcore_item_prompt_label, pov_prompt_directive=deps.pov_prompt_directive, pov_composition_prompt=deps.pov_composition_prompt, ) soft_row = row_route.soft_row hard_row = row_route.hard_row hard_content_rng = row_route.hard_content_rng cast_context = pair_cast.resolve_insta_pair_cast_context( soft_row=soft_row, options=options, parsed_seed_config=parsed_seed_config, parsed_softcore_seed_config=parsed_softcore_seed_config, seed=request.seed, row_number=request.row_number, ethnicity=ethnicity, figure=figure, no_plus_women=no_plus_women, no_black=no_black, hard_women_count=hard_women_count, hard_men_count=hard_men_count, character_slots=character_slots, character_slot_map=character_slot_map, pov_character_labels=pov_character_labels, platform_styles=deps.platform_styles, soft_levels=deps.soft_levels, hardcore_levels=deps.hardcore_levels, axis_rng=deps.axis_rng, character_context_for_label=deps.character_context_for_label, slot_is_pov=deps.slot_is_pov, choose=deps.choose, slot_softcore_outfit=deps.slot_softcore_outfit, ) camera_route = pair_camera.resolve_insta_pair_camera_result( soft_row=soft_row, hard_row=hard_row, options=options, camera_config=request.camera_config, softcore_camera_config=request.softcore_camera_config, hardcore_camera_config=request.hardcore_camera_config, hard_women_count=hard_women_count, hard_men_count=hard_men_count, pov_character_labels=pov_character_labels, camera_detail_choices=deps.camera_detail_choices, camera_config_with_mode=deps.camera_config_with_mode, camera_directive=deps.camera_directive, apply_contextual_composition=deps.apply_contextual_composition, contextual_composition_prompt=deps.contextual_composition_prompt, composition_prompt=deps.composition_prompt, camera_scene_directive_for_context=deps.camera_scene_directive_for_context, ) soft_row = camera_route.soft_row hard_row = camera_route.hard_row hard_scene = camera_route.hard_scene character_hardcore_clothing_entries = pair_clothing.character_hardcore_clothing_entries( character_slot_map, hard_women_count, hard_men_count, pov_character_labels, hard_content_rng, deps.slot_hardcore_clothing, ) clothing_route = pair_clothing.resolve_hardcore_pair_clothing_result( hard_row=hard_row, mode=options["hardcore_clothing_continuity"], softcore_outfit=soft_row["item"], character_hardcore_clothing_entries=character_hardcore_clothing_entries, men_count=hard_men_count, pov_labels=pov_character_labels, rng=hard_content_rng, continuity_map=deps.hardcore_clothing_continuity, choose=deps.choose, label_map=character_slot_map, slot_hardcore_clothing=deps.slot_hardcore_clothing, ) if clothing_route.requires_body_exposure_scene: hard_scene = pair_clothing.body_exposure_scene_text(hard_scene) hard_row["source_scene_text"] = hard_row.get("source_scene_text") or hard_row.get("scene_text", "") hard_row["scene_text"] = hard_scene hard_detail_density = options["hardcore_detail_density"] return pair_output.assemble_insta_pair_metadata( active_trigger=active_trigger, prepend_trigger_to_prompt=bool(request.prepend_trigger_to_prompt), extra_positive=request.extra_positive, extra_negative=request.extra_negative, soft_negative_base=deps.soft_negative_base, hard_negative_base=deps.hard_negative_base, options=options, platform_style=cast_context["platform_style"], soft_descriptor_sentence=cast_context["soft_descriptor_sentence"], soft_level=cast_context["soft_level"], soft_cast=cast_context["soft_cast"], soft_cast_presence=cast_context["soft_cast_presence"], soft_cast_styling_sentence=cast_context["soft_cast_styling_sentence"], soft_row=soft_row, soft_camera_scene_sentence=camera_route.soft_camera_scene_sentence, soft_camera_sentence=camera_route.soft_camera_sentence, hard_level=cast_context["hard_level"], hard_cast=cast_context["hard_cast"], cast_descriptor_text=cast_context["cast_descriptor_text"], pov_directive=deps.pov_prompt_directive(pov_character_labels), pov_character_labels=pov_character_labels, hard_clothing_sentence=clothing_route.hardcore_clothing_sentence, hard_row=hard_row, hard_scene=hard_scene, hard_camera_scene_sentence=camera_route.hard_camera_scene_sentence, hard_composition=camera_route.hard_composition, hard_detail_directive=deps.hardcore_detail_directive(hard_detail_density), hard_camera_sentence=camera_route.hard_camera_sentence, descriptor=cast_context["descriptor"], soft_partner_outfit_text=cast_context["soft_partner_outfit_text"], soft_partner_styling=cast_context["soft_partner_styling"], soft_camera_scene_directive=camera_route.soft_camera_scene_directive, soft_camera_config=camera_route.soft_camera_config, soft_camera_directive=camera_route.soft_camera_directive, hard_camera_scene_directive=camera_route.hard_camera_scene_directive, hard_camera_config=camera_route.hard_camera_config, hard_camera_directive=camera_route.hard_camera_directive, camera_caption_text=deps.camera_caption_text, cast_descriptors=cast_context["cast_descriptors"], character_hardcore_clothing_entries=character_hardcore_clothing_entries, pov_hardcore_clothing_entries=clothing_route.pov_hardcore_clothing, default_man_hardcore_clothing_entries=clothing_route.default_man_hardcore_clothing, hard_clothing_state=clothing_route.hardcore_clothing_state, hard_detail_density=hard_detail_density, hard_women_count=hard_women_count, hard_men_count=hard_men_count, character_slots=character_slots, character_slot_map=character_slot_map, )