JustinD commited on
Commit
3be379f
·
verified ·
1 Parent(s): 7a102c2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +422 -197
app.py CHANGED
@@ -30,7 +30,7 @@ logging.basicConfig(
30
  handlers=[logging.StreamHandler(sys.stdout)],
31
  )
32
  logger = logging.getLogger("NextGenEngine")
33
- logger.info("🚀 JustinD Fiction Lab boot (21+ · Multi-chef stacks · sexy UI)")
34
 
35
  if hasattr(sys.stdout, "reconfigure"):
36
  sys.stdout.reconfigure(encoding="utf-8")
@@ -41,6 +41,7 @@ BACKUP_DATASET_ID = "JustinD/generation-images"
41
  LAST_GEN_TIME = {}
42
  COOLDOWN_SECONDS = 12
43
  LOG_VISITOR_UPLOADS = os.environ.get("LOG_VISITOR_UPLOADS", "0").strip().lower() in {"1", "true", "yes"}
 
44
 
45
  HF_REPO_ID = "AriaVale2/my-safetensors-backup"
46
 
@@ -106,6 +107,8 @@ DIFFUSERS_MODELS = {
106
  }
107
 
108
  SDXL_CHOICES = list(DIFFUSERS_MODELS.keys()) + list(MODEL_REPOS.keys())
 
 
109
 
110
  # LoRA pantry from known SDXL repos. Fail soft if download/load breaks.
111
  LORA_NONE = "None"
@@ -121,9 +124,9 @@ LORA_REPOS = {
121
  }
122
  LORA_CHOICES = list(LORA_REPOS.keys())
123
 
124
- # Named multi-chef stacks. Every gen = Chef A txt2img then Chef B img2img remix.
125
- # Default stack = filthiest (AI + AI x AI) with sexy-slider LoRA.
126
- # Lots of mixes so Justin can shop output vibes. Plain UI; stacks are the product.
127
  STACK_PHOTO_ILLUSTRIOUS = "Photo + Illustrious"
128
  STACK_HARDCORE_FACE = "Hardcore + face keeper"
129
  STACK_ANIME_REAL = "Anime Desire × Real Porn"
@@ -138,95 +141,125 @@ STACK_FACE_ILLUSTRIOUS = "Face keeper × Illustrious"
138
  STACK_SPICY_WAI = "Spicy Realism × WAI"
139
  STACK_FUCKTASTIC_REAL = "Fucktastic × Real Porn"
140
  STACK_EGIRL_LUSTIFY = "Asian E-Girl × Lustify"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
  STACKS = {
143
- STACK_PHOTO_ILLUSTRIOUS: {
144
- "chef_a": "PornWorks Real Porn v04",
145
- "chef_b": "WAI NSFW Illustrious v150",
146
- "lora": LORA_NONE,
147
- "blend": 0.50,
148
- },
149
- STACK_HARDCORE_FACE: {
150
- "chef_a": "Hardcore Asian Cosplay Illustrious",
151
- "chef_b": "OhMySister Beautiful Asian Woman",
152
- "lora": LORA_NONE,
153
- "blend": 0.60,
154
- },
155
- STACK_ANIME_REAL: {
156
- "chef_a": "PornWorks Anime Desire NSFW",
157
- "chef_b": "PornWorks Real Porn v04",
158
- "lora": LORA_NONE,
159
- "blend": 0.55,
160
- },
161
- STACK_AI_AI_AI: {
162
- "chef_a": "Lustify NSFW GGWP v7",
163
- "chef_b": "WAI NSFW Illustrious v150",
164
- "lora": "sexy-slider (Soft Domme heat)",
165
- "blend": 0.55,
166
- },
167
- STACK_SOFT_DOMME: {
168
- "chef_a": "Lustify NSFW GGWP v7",
169
- "chef_b": "Lustify NSFW Endgame",
170
- "lora": "sexy-slider (Soft Domme heat)",
171
- "blend": 0.62,
172
- },
173
- STACK_MIRROR_HOTEL: {
174
- "chef_a": "PornWorks Real Porn v04",
175
- "chef_b": "WAI NSFW Illustrious v150",
176
- "lora": LORA_NONE,
177
- "blend": 0.48,
178
- },
179
- STACK_NEON_ALLEY: {
180
- "chef_a": "Hardcore Asian Cosplay Illustrious",
181
- "chef_b": "Lustify NSFW GGWP v7",
182
- "lora": LORA_NONE,
183
- "blend": 0.58,
184
- },
185
- STACK_LUSTIFY_REAL: {
186
- "chef_a": "Lustify NSFW GGWP v7",
187
- "chef_b": "PornWorks Real Porn v04",
188
- "lora": LORA_NONE,
189
- "blend": 0.52,
190
- },
191
- STACK_ANIME_ILLUSTRIOUS: {
192
- "chef_a": "PornWorks Anime Desire NSFW",
193
- "chef_b": "WAI NSFW Illustrious v150",
194
- "lora": LORA_NONE,
195
- "blend": 0.55,
196
- },
197
- STACK_HARDCORE_LUSTIFY: {
198
- "chef_a": "Hardcore Asian Cosplay Illustrious",
199
- "chef_b": "Lustify NSFW One Last Time",
200
- "lora": LORA_NONE,
201
- "blend": 0.57,
202
- },
203
- STACK_FACE_ILLUSTRIOUS: {
204
- "chef_a": "OhMySister Beautiful Asian Woman",
205
- "chef_b": "WAI NSFW Illustrious v150",
206
- "lora": LORA_NONE,
207
- "blend": 0.50,
208
- },
209
- STACK_SPICY_WAI: {
210
- "chef_a": "Spicy Realism NSFW Mix v3",
211
- "chef_b": "WAI NSFW Illustrious v150",
212
- "lora": LORA_NONE,
213
- "blend": 0.53,
214
- },
215
- STACK_FUCKTASTIC_REAL: {
216
- "chef_a": "Fucktastic Real Porn Pony v5.1",
217
- "chef_b": "PornWorks Real Porn v04",
218
- "lora": LORA_NONE,
219
- "blend": 0.54,
220
- },
221
- STACK_EGIRL_LUSTIFY: {
222
- "chef_a": "Asian E-Girl NSFW XL",
223
- "chef_b": "Lustify NSFW GGWP v7",
224
- "lora": "sexy-slider (Soft Domme heat)",
225
- "blend": 0.56,
226
- },
227
  }
228
  STACK_CHOICES = list(STACKS.keys())
229
  DEFAULT_STACK = STACK_AI_AI_AI
 
230
 
231
  # Style packs stack via CheckboxGroup. Testing Tits is the heat base; scene packs layer on top.
232
  # Cosmic e-girl booth is FICTIONAL DJ only (no real person names).
@@ -532,7 +565,7 @@ def _apply_lora(pipe, lora_name: str) -> bool:
532
  logger.info(f"✅ LoRA applied: {lora_name}")
533
  return True
534
  except Exception as e:
535
- logger.error(f"⚠️ LoRA load failed for {lora_name}: {e} (continuing A+B blend alone)")
536
  return False
537
 
538
 
@@ -585,29 +618,194 @@ def enhance_prompt(prompt, styles, filth_level=1):
585
  return ", ".join([p for p in parts if p])
586
 
587
 
588
- def resolve_stack_slots(stack_name, chef_a, chef_b, lora_name, blend):
589
- """Use Advanced overrides when set; fill gaps from named stack."""
590
- cfg = STACKS.get(stack_name) or STACKS[DEFAULT_STACK]
591
- a = chef_a if chef_a and str(chef_a).strip() else cfg["chef_a"]
592
- b = chef_b if chef_b and str(chef_b).strip() else cfg["chef_b"]
593
- lora = lora_name if lora_name else cfg["lora"]
 
 
 
 
594
  try:
595
- strength = float(blend)
596
  except (TypeError, ValueError):
597
- strength = float(cfg["blend"])
598
- strength = max(0.05, min(0.95, strength))
599
- return a, b, lora, strength
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
600
 
601
 
602
- @spaces.GPU(duration=240) if SPACES_AVAILABLE else (lambda f: f)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
603
  def generate(
604
  prompt,
605
  styles,
606
- stack_name,
607
  chef_a,
608
  chef_b,
 
 
609
  lora_name,
610
- blend,
 
 
 
611
  steps,
612
  cfg,
613
  seed,
@@ -615,11 +813,8 @@ def generate(
615
  height,
616
  batch,
617
  filth_level,
618
- age_ok,
619
  username,
620
  ):
621
- if not age_ok:
622
- return None, "❌ Confirm you are 21+ before cooking", "", []
623
  if not prompt or not str(prompt).strip():
624
  return None, "❌ Prompt missing", "", []
625
  now = time.time()
@@ -633,86 +828,61 @@ def generate(
633
  gr.Warning("Restricted content")
634
  return None, "❌ Restricted content", "", []
635
 
636
- batch = int(max(1, min(3, int(batch))))
 
 
 
 
 
 
 
 
 
 
 
 
637
  seeds = [
638
  random.randint(0, 2**31 - 1) if int(seed) < 0 else int(seed) + i
639
  for i in range(batch)
640
  ]
641
  enhanced = enhance_prompt(prompt, styles, filth_level)
642
  neg = BASE_NEGATIVES
643
-
644
- model_a, model_b, lora, strength = resolve_stack_slots(
645
- stack_name, chef_a, chef_b, lora_name, blend
646
- )
647
  use_steps = int(max(10, min(40, int(steps))))
648
  remix_steps = max(10, int(use_steps * 0.75))
649
  LAST_GEN_TIME[user_key] = now
650
  gallery = []
651
  jobs = []
 
 
 
 
652
  device = "cuda" if torch.cuda.is_available() else "cpu"
653
 
654
  try:
655
- # Pass A: txt2img with Chef A
656
- pipe_a = load_sdxl_pipe(model_a, LORA_NONE)
657
- base_images = []
658
- for s in seeds:
659
- gen = torch.Generator(device=device).manual_seed(int(s))
660
- out = pipe_a(
661
- prompt=enhanced,
662
- negative_prompt=neg,
663
- num_inference_steps=use_steps,
664
- guidance_scale=float(cfg),
665
- width=int(width),
666
- height=int(height),
667
- generator=gen,
668
- ).images[0]
669
- base_images.append((out, s))
670
-
671
- # Unload A, load B (+ optional LoRA), img2img remix
672
- unload_all()
673
- from diffusers import StableDiffusionXLImg2ImgPipeline
674
-
675
- pipe_b = load_sdxl_pipe(model_b, lora)
676
- img2img = StableDiffusionXLImg2ImgPipeline(
677
- vae=pipe_b.vae,
678
- text_encoder=pipe_b.text_encoder,
679
- text_encoder_2=pipe_b.text_encoder_2,
680
- tokenizer=pipe_b.tokenizer,
681
- tokenizer_2=pipe_b.tokenizer_2,
682
- unet=pipe_b.unet,
683
- scheduler=pipe_b.scheduler,
684
- )
685
- if torch.cuda.is_available():
686
- img2img = img2img.to("cuda")
687
-
688
- remixed = []
689
- for img, s in base_images:
690
- gen = torch.Generator(device=device).manual_seed(int(s) + 17)
691
- out = img2img(
692
- image=img,
693
- prompt=enhanced,
694
- negative_prompt=neg,
695
- strength=float(strength),
696
- num_inference_steps=remix_steps,
697
- guidance_scale=float(cfg),
698
- generator=gen,
699
- ).images[0]
700
- remixed.append((out, s))
701
- del img2img
702
- gc.collect()
703
- if torch.cuda.is_available():
704
- torch.cuda.empty_cache()
705
-
706
- lora_tag = lora if lora and lora != LORA_NONE else "no-LoRA"
707
- chain = f"{stack_name} | A:{model_a} → B:{model_b}@{strength:.2f} | {lora_tag}"
708
- for out, s in remixed:
709
- label = f"seed {s} · {stack_name} · A→B {strength:.2f}"
710
- gallery.append((out, label))
711
- jobs.append((out, s, chain, prompt, username, width, height, use_steps, cfg))
712
 
713
  info = (
714
- f"{chain} | seeds {seeds} | {width}x{height} | "
715
- f"steps A={use_steps} B={remix_steps} | filth={filth_level} | enhanced prompt ready"
 
716
  )
717
  return gallery, info, enhanced, jobs
718
  except Exception as e:
@@ -723,13 +893,30 @@ def generate(
723
  return None, f"❌ {msg}", "", []
724
 
725
 
726
- def on_stack_change(stack_name):
727
- cfg = STACKS.get(stack_name) or STACKS[DEFAULT_STACK]
 
 
 
 
 
 
 
 
 
 
 
 
 
728
  return (
729
  gr.update(value=cfg["chef_a"]),
730
  gr.update(value=cfg["chef_b"]),
 
 
731
  gr.update(value=cfg["lora"]),
732
- gr.update(value=cfg["blend"]),
 
 
733
  )
734
 
735
 
@@ -766,26 +953,23 @@ def create_ui():
766
  user_id = gr.Textbox(visible=False)
767
  gr.Markdown(
768
  """# JustinD/1 · 21+ Fiction Lab
769
- ### Adult East / Southeast Asian fiction booth · Multi-chef stacks · ZeroGPU
770
 
771
  **Fiction only. Adult women 21+ (late 20s). No real dating names or faces.**
772
  Body lock baked in: tall slim small frame, tiny waist, large / full / heavy breasts, cleavage.
773
- Every cook runs **Chef A txt2img → unload → Chef B img2img remix**.
 
 
774
  Cosmic e-girl booth = fictional DJ only. Negatives stay built-in (not editable).
775
  Visitor gens are **not** saved unless secret `LOG_VISITOR_UPLOADS=1`.
776
  First model load is slow. Queues happen on ZeroGPU."""
777
  )
778
- with gr.Row(elem_classes=["age-gate"]):
779
- age_ok = gr.Checkbox(
780
- label="I confirm I am 21+ and want adult fiction only",
781
- value=False,
782
- )
783
  with gr.Row():
784
  with gr.Column(scale=5):
785
- stack = gr.Radio(
786
- label="Named stack (pick a chip)",
787
  choices=STACK_CHOICES,
788
- value=DEFAULT_STACK,
789
  )
790
  styles = gr.CheckboxGroup(
791
  label="Style packs (stackable · Testing Tits on by default)",
@@ -809,7 +993,15 @@ First model load is slow. Queues happen on ZeroGPU."""
809
  step=1,
810
  )
811
  go = gr.Button("🔥 GENERATE", variant="primary", size="lg")
812
- with gr.Accordion("Advanced (Chef A/B · LoRA · blend · size)", open=False):
 
 
 
 
 
 
 
 
813
  chef_a = gr.Dropdown(
814
  label="Chef A (txt2img base)",
815
  choices=SDXL_CHOICES,
@@ -820,21 +1012,51 @@ First model load is slow. Queues happen on ZeroGPU."""
820
  choices=SDXL_CHOICES,
821
  value=default_cfg["chef_b"],
822
  )
 
 
 
 
 
 
 
 
 
 
823
  lora_dd = gr.Dropdown(
824
- label="LoRA (None = blend only)",
825
  choices=LORA_CHOICES,
826
  value=default_cfg["lora"],
827
  )
828
- blend_sl = gr.Slider(
829
- label="Blend A→B strength (higher = more Chef B)",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
830
  minimum=0.30,
831
  maximum=0.70,
832
- value=default_cfg["blend"],
833
  step=0.05,
834
  )
835
  with gr.Row():
836
  seed = gr.Number(label="Seed (-1 = random / lucky)", value=-1, precision=0)
837
- batch = gr.Slider(1, 3, value=1, step=1, label="Batch (1 safe · 2-3 may OOM)")
 
 
 
 
 
 
838
  with gr.Row():
839
  width = gr.Slider(512, 1536, value=1024, step=64, label="Width")
840
  height = gr.Slider(512, 1536, value=1024, step=64, label="Height")
@@ -850,15 +1072,15 @@ First model load is slow. Queues happen on ZeroGPU."""
850
  object_fit="contain",
851
  preview=True,
852
  )
853
- info = gr.Textbox(label="Run info", lines=2)
854
  meta = gr.Textbox(label="Enhanced prompt (auto)", lines=4)
855
 
856
  demo.load(fn=None, inputs=None, outputs=user_id, js=js_visitor)
857
 
858
- stack.change(
859
- fn=on_stack_change,
860
- inputs=[stack],
861
- outputs=[chef_a, chef_b, lora_dd, blend_sl],
862
  )
863
  lucky_prompt_btn.click(fn=random_starter, inputs=None, outputs=prompt)
864
  lucky_seed_btn.click(fn=lucky_seed, inputs=None, outputs=seed)
@@ -877,11 +1099,16 @@ First model load is slow. Queues happen on ZeroGPU."""
877
  inputs=[
878
  prompt,
879
  styles,
880
- stack,
881
  chef_a,
882
  chef_b,
 
 
883
  lora_dd,
884
- blend_sl,
 
 
 
885
  steps,
886
  cfg,
887
  seed,
@@ -889,7 +1116,6 @@ First model load is slow. Queues happen on ZeroGPU."""
889
  height,
890
  batch,
891
  filth,
892
- age_ok,
893
  user_id,
894
  ],
895
  outputs=[gallery, info, meta],
@@ -902,4 +1128,3 @@ if __name__ == "__main__":
902
  app = create_ui()
903
  app.queue(max_size=12)
904
  app.launch(ssr_mode=False, theme=THEME, css=css)
905
-
 
30
  handlers=[logging.StreamHandler(sys.stdout)],
31
  )
32
  logger = logging.getLogger("NextGenEngine")
33
+ logger.info("🚀 JustinD Fiction Lab boot (21+ · multi-select stacks · optional quad chefs)")
34
 
35
  if hasattr(sys.stdout, "reconfigure"):
36
  sys.stdout.reconfigure(encoding="utf-8")
 
41
  LAST_GEN_TIME = {}
42
  COOLDOWN_SECONDS = 12
43
  LOG_VISITOR_UPLOADS = os.environ.get("LOG_VISITOR_UPLOADS", "0").strip().lower() in {"1", "true", "yes"}
44
+ MAX_STACK_BATCH_PRODUCT = 6
45
 
46
  HF_REPO_ID = "AriaVale2/my-safetensors-backup"
47
 
 
107
  }
108
 
109
  SDXL_CHOICES = list(DIFFUSERS_MODELS.keys()) + list(MODEL_REPOS.keys())
110
+ SKIP_CHEF = "(skip)"
111
+ CHEF_OPTIONAL_CHOICES = [SKIP_CHEF] + SDXL_CHOICES
112
 
113
  # LoRA pantry from known SDXL repos. Fail soft if download/load breaks.
114
  LORA_NONE = "None"
 
124
  }
125
  LORA_CHOICES = list(LORA_REPOS.keys())
126
 
127
+ # Named multi-chef stacks. Schema supports optional Chef C/D (quad).
128
+ # Two-chef stacks keep chef_c/d=None. Quad stacks set all four.
129
+ # LoRA applies on the last chef that actually runs.
130
  STACK_PHOTO_ILLUSTRIOUS = "Photo + Illustrious"
131
  STACK_HARDCORE_FACE = "Hardcore + face keeper"
132
  STACK_ANIME_REAL = "Anime Desire × Real Porn"
 
141
  STACK_SPICY_WAI = "Spicy Realism × WAI"
142
  STACK_FUCKTASTIC_REAL = "Fucktastic × Real Porn"
143
  STACK_EGIRL_LUSTIFY = "Asian E-Girl × Lustify"
144
+ STACK_QUAD_LUSTIFY_REAL_WAI_FACE = "Quad: Lustify → Real → WAI → Face"
145
+ STACK_QUAD_HARDCORE_LUSTIFY_ILLU_REAL = "Quad: Hardcore → Lustify → Illustrious → Real"
146
+ STACK_QUAD_ANIME_REAL_WAI_LUSTIFY = "Quad: Anime Desire → Real → WAI → Lustify"
147
+ STACK_QUAD_EGIRL_LUSTIFY_WAI_FACE = "Quad: E-Girl → Lustify → WAI → Face"
148
+
149
+
150
+ def _two(chef_a, chef_b, lora, blend_b):
151
+ return {
152
+ "chef_a": chef_a,
153
+ "chef_b": chef_b,
154
+ "chef_c": None,
155
+ "chef_d": None,
156
+ "lora": lora,
157
+ "blend_b": float(blend_b),
158
+ "blend_c": 0.50,
159
+ "blend_d": 0.45,
160
+ }
161
+
162
+
163
+ def _quad(chef_a, chef_b, chef_c, chef_d, lora, blend_b, blend_c, blend_d):
164
+ return {
165
+ "chef_a": chef_a,
166
+ "chef_b": chef_b,
167
+ "chef_c": chef_c,
168
+ "chef_d": chef_d,
169
+ "lora": lora,
170
+ "blend_b": float(blend_b),
171
+ "blend_c": float(blend_c),
172
+ "blend_d": float(blend_d),
173
+ }
174
+
175
 
176
  STACKS = {
177
+ STACK_PHOTO_ILLUSTRIOUS: _two(
178
+ "PornWorks Real Porn v04", "WAI NSFW Illustrious v150", LORA_NONE, 0.50
179
+ ),
180
+ STACK_HARDCORE_FACE: _two(
181
+ "Hardcore Asian Cosplay Illustrious", "OhMySister Beautiful Asian Woman", LORA_NONE, 0.60
182
+ ),
183
+ STACK_ANIME_REAL: _two(
184
+ "PornWorks Anime Desire NSFW", "PornWorks Real Porn v04", LORA_NONE, 0.55
185
+ ),
186
+ STACK_AI_AI_AI: _two(
187
+ "Lustify NSFW GGWP v7", "WAI NSFW Illustrious v150", "sexy-slider (Soft Domme heat)", 0.55
188
+ ),
189
+ STACK_SOFT_DOMME: _two(
190
+ "Lustify NSFW GGWP v7", "Lustify NSFW Endgame", "sexy-slider (Soft Domme heat)", 0.62
191
+ ),
192
+ STACK_MIRROR_HOTEL: _two(
193
+ "PornWorks Real Porn v04", "WAI NSFW Illustrious v150", LORA_NONE, 0.48
194
+ ),
195
+ STACK_NEON_ALLEY: _two(
196
+ "Hardcore Asian Cosplay Illustrious", "Lustify NSFW GGWP v7", LORA_NONE, 0.58
197
+ ),
198
+ STACK_LUSTIFY_REAL: _two(
199
+ "Lustify NSFW GGWP v7", "PornWorks Real Porn v04", LORA_NONE, 0.52
200
+ ),
201
+ STACK_ANIME_ILLUSTRIOUS: _two(
202
+ "PornWorks Anime Desire NSFW", "WAI NSFW Illustrious v150", LORA_NONE, 0.55
203
+ ),
204
+ STACK_HARDCORE_LUSTIFY: _two(
205
+ "Hardcore Asian Cosplay Illustrious", "Lustify NSFW One Last Time", LORA_NONE, 0.57
206
+ ),
207
+ STACK_FACE_ILLUSTRIOUS: _two(
208
+ "OhMySister Beautiful Asian Woman", "WAI NSFW Illustrious v150", LORA_NONE, 0.50
209
+ ),
210
+ STACK_SPICY_WAI: _two(
211
+ "Spicy Realism NSFW Mix v3", "WAI NSFW Illustrious v150", LORA_NONE, 0.53
212
+ ),
213
+ STACK_FUCKTASTIC_REAL: _two(
214
+ "Fucktastic Real Porn Pony v5.1", "PornWorks Real Porn v04", LORA_NONE, 0.54
215
+ ),
216
+ STACK_EGIRL_LUSTIFY: _two(
217
+ "Asian E-Girl NSFW XL", "Lustify NSFW GGWP v7", "sexy-slider (Soft Domme heat)", 0.56
218
+ ),
219
+ STACK_QUAD_LUSTIFY_REAL_WAI_FACE: _quad(
220
+ "Lustify NSFW GGWP v7",
221
+ "PornWorks Real Porn v04",
222
+ "WAI NSFW Illustrious v150",
223
+ "OhMySister Beautiful Asian Woman",
224
+ LORA_NONE,
225
+ 0.55,
226
+ 0.50,
227
+ 0.42,
228
+ ),
229
+ STACK_QUAD_HARDCORE_LUSTIFY_ILLU_REAL: _quad(
230
+ "Hardcore Asian Cosplay Illustrious",
231
+ "Lustify NSFW GGWP v7",
232
+ "WAI NSFW Illustrious v150",
233
+ "PornWorks Real Porn v04",
234
+ LORA_NONE,
235
+ 0.55,
236
+ 0.50,
237
+ 0.45,
238
+ ),
239
+ STACK_QUAD_ANIME_REAL_WAI_LUSTIFY: _quad(
240
+ "PornWorks Anime Desire NSFW",
241
+ "PornWorks Real Porn v04",
242
+ "WAI NSFW Illustrious v150",
243
+ "Lustify NSFW GGWP v7",
244
+ LORA_NONE,
245
+ 0.55,
246
+ 0.50,
247
+ 0.48,
248
+ ),
249
+ STACK_QUAD_EGIRL_LUSTIFY_WAI_FACE: _quad(
250
+ "Asian E-Girl NSFW XL",
251
+ "Lustify NSFW GGWP v7",
252
+ "WAI NSFW Illustrious v150",
253
+ "OhMySister Beautiful Asian Woman",
254
+ "sexy-slider (Soft Domme heat)",
255
+ 0.55,
256
+ 0.50,
257
+ 0.42,
258
+ ),
 
 
259
  }
260
  STACK_CHOICES = list(STACKS.keys())
261
  DEFAULT_STACK = STACK_AI_AI_AI
262
+ DEFAULT_STACKS = [DEFAULT_STACK]
263
 
264
  # Style packs stack via CheckboxGroup. Testing Tits is the heat base; scene packs layer on top.
265
  # Cosmic e-girl booth is FICTIONAL DJ only (no real person names).
 
565
  logger.info(f"✅ LoRA applied: {lora_name}")
566
  return True
567
  except Exception as e:
568
+ logger.error(f"⚠️ LoRA load failed for {lora_name}: {e} (continuing chef chain alone)")
569
  return False
570
 
571
 
 
618
  return ", ".join([p for p in parts if p])
619
 
620
 
621
+ def _norm_chef(name):
622
+ if name is None:
623
+ return None
624
+ s = str(name).strip()
625
+ if not s or s == SKIP_CHEF or s.lower() in {"none", "null", "skip"}:
626
+ return None
627
+ return s
628
+
629
+
630
+ def _clamp_blend(val, fallback=0.55):
631
  try:
632
+ strength = float(val)
633
  except (TypeError, ValueError):
634
+ strength = float(fallback)
635
+ return max(0.05, min(0.95, strength))
636
+
637
+
638
+ def resolve_chef_chain(cfg):
639
+ """Build ordered chef list from a stack cfg. Stops after last filled chef.
640
+ Returns (chefs, blends, lora) where chefs[0]=A (txt2img), rest are img2img.
641
+ blends aligns with chefs[1:] (blend for B/C/D).
642
+ """
643
+ a = _norm_chef(cfg.get("chef_a"))
644
+ b = _norm_chef(cfg.get("chef_b"))
645
+ c = _norm_chef(cfg.get("chef_c"))
646
+ d = _norm_chef(cfg.get("chef_d"))
647
+ if not a:
648
+ raise ValueError("Chef A is required")
649
+ chefs = [a]
650
+ blends = []
651
+ if b:
652
+ chefs.append(b)
653
+ blends.append(_clamp_blend(cfg.get("blend_b", 0.55)))
654
+ if c:
655
+ chefs.append(c)
656
+ blends.append(_clamp_blend(cfg.get("blend_c", 0.50)))
657
+ if d:
658
+ chefs.append(d)
659
+ blends.append(_clamp_blend(cfg.get("blend_d", 0.45)))
660
+ lora = cfg.get("lora") or LORA_NONE
661
+ return chefs, blends, lora
662
+
663
+
664
+ def _make_img2img(pipe):
665
+ from diffusers import StableDiffusionXLImg2ImgPipeline
666
+
667
+ img2img = StableDiffusionXLImg2ImgPipeline(
668
+ vae=pipe.vae,
669
+ text_encoder=pipe.text_encoder,
670
+ text_encoder_2=pipe.text_encoder_2,
671
+ tokenizer=pipe.tokenizer,
672
+ tokenizer_2=pipe.tokenizer_2,
673
+ unet=pipe.unet,
674
+ scheduler=pipe.scheduler,
675
+ )
676
+ if torch.cuda.is_available():
677
+ img2img = img2img.to("cuda")
678
+ return img2img
679
 
680
 
681
+ def run_chef_pipeline(
682
+ stack_label,
683
+ cfg,
684
+ enhanced,
685
+ neg,
686
+ seeds,
687
+ use_steps,
688
+ remix_steps,
689
+ cfg_scale,
690
+ width,
691
+ height,
692
+ device,
693
+ ):
694
+ """Run A txt2img then optional B/C/D img2img with unload between chefs.
695
+ LoRA applies on the last chef that runs.
696
+ Returns list of (pil_image, seed, chain_str, label).
697
+ """
698
+ chefs, blends, lora = resolve_chef_chain(cfg)
699
+ last_idx = len(chefs) - 1
700
+ lora_tag = lora if lora and lora != LORA_NONE else "no-LoRA"
701
+
702
+ # Pass A: txt2img (never LoRA on A unless A is the only chef, which should not happen)
703
+ lora_a = lora if last_idx == 0 else LORA_NONE
704
+ pipe_a = load_sdxl_pipe(chefs[0], lora_a)
705
+ current = []
706
+ for s in seeds:
707
+ gen = torch.Generator(device=device).manual_seed(int(s))
708
+ out = pipe_a(
709
+ prompt=enhanced,
710
+ negative_prompt=neg,
711
+ num_inference_steps=use_steps,
712
+ guidance_scale=float(cfg_scale),
713
+ width=int(width),
714
+ height=int(height),
715
+ generator=gen,
716
+ ).images[0]
717
+ current.append((out, s))
718
+
719
+ letter_names = ["A", "B", "C", "D"]
720
+ chain_bits = [f"A:{chefs[0]}"]
721
+
722
+ # Passes B/C/D: img2img remix, unload between, LoRA only on last chef
723
+ for pass_i, chef_name in enumerate(chefs[1:], start=1):
724
+ unload_all()
725
+ strength = blends[pass_i - 1]
726
+ lora_here = lora if pass_i == last_idx else LORA_NONE
727
+ pipe_n = load_sdxl_pipe(chef_name, lora_here)
728
+ img2img = _make_img2img(pipe_n)
729
+ remixed = []
730
+ seed_offset = 17 * pass_i
731
+ for img, s in current:
732
+ gen = torch.Generator(device=device).manual_seed(int(s) + seed_offset)
733
+ out = img2img(
734
+ image=img,
735
+ prompt=enhanced,
736
+ negative_prompt=neg,
737
+ strength=float(strength),
738
+ num_inference_steps=remix_steps,
739
+ guidance_scale=float(cfg_scale),
740
+ generator=gen,
741
+ ).images[0]
742
+ remixed.append((out, s))
743
+ current = remixed
744
+ letter = letter_names[pass_i] if pass_i < len(letter_names) else f"P{pass_i}"
745
+ chain_bits.append(f"{letter}:{chef_name}@{strength:.2f}")
746
+ del img2img
747
+ gc.collect()
748
+ if torch.cuda.is_available():
749
+ torch.cuda.empty_cache()
750
+
751
+ chain = f"{stack_label} | {' → '.join(chain_bits)} | {lora_tag}"
752
+ results = []
753
+ for out, s in current:
754
+ label = f"{stack_label} · seed {s}"
755
+ results.append((out, s, chain, label))
756
+ return results
757
+
758
+
759
+ def normalize_selected_stacks(selected):
760
+ if not selected:
761
+ return list(DEFAULT_STACKS)
762
+ if isinstance(selected, str):
763
+ selected = [selected]
764
+ out = [s for s in selected if s in STACKS]
765
+ return out if out else list(DEFAULT_STACKS)
766
+
767
+
768
+ def clamp_batch_for_stacks(n_stacks, batch):
769
+ """Cap stacks × batch to avoid ZeroGPU timeout. Prefer batch=1 when 3+ stacks."""
770
+ batch = int(max(1, min(3, int(batch))))
771
+ warn = ""
772
+ if n_stacks * batch > MAX_STACK_BATCH_PRODUCT:
773
+ if n_stacks >= 3:
774
+ batch = 1
775
+ warn = f"Batch forced to 1 (selected {n_stacks} stacks; stacks×batch cap is {MAX_STACK_BATCH_PRODUCT})."
776
+ else:
777
+ batch = max(1, MAX_STACK_BATCH_PRODUCT // max(1, n_stacks))
778
+ warn = f"Batch reduced to {batch} (selected {n_stacks} stacks; stacks×batch cap is {MAX_STACK_BATCH_PRODUCT})."
779
+ return batch, warn
780
+
781
+
782
+ def advanced_cfg_from_ui(chef_a, chef_b, chef_c, chef_d, lora_name, blend_b, blend_c, blend_d):
783
+ return {
784
+ "chef_a": _norm_chef(chef_a) or STACKS[DEFAULT_STACK]["chef_a"],
785
+ "chef_b": _norm_chef(chef_b) or STACKS[DEFAULT_STACK]["chef_b"],
786
+ "chef_c": _norm_chef(chef_c),
787
+ "chef_d": _norm_chef(chef_d),
788
+ "lora": lora_name if lora_name else LORA_NONE,
789
+ "blend_b": _clamp_blend(blend_b, 0.55),
790
+ "blend_c": _clamp_blend(blend_c, 0.50),
791
+ "blend_d": _clamp_blend(blend_d, 0.45),
792
+ }
793
+
794
+
795
+ @spaces.GPU(duration=360) if SPACES_AVAILABLE else (lambda f: f)
796
  def generate(
797
  prompt,
798
  styles,
799
+ selected_stacks,
800
  chef_a,
801
  chef_b,
802
+ chef_c,
803
+ chef_d,
804
  lora_name,
805
+ blend_b,
806
+ blend_c,
807
+ blend_d,
808
+ use_advanced,
809
  steps,
810
  cfg,
811
  seed,
 
813
  height,
814
  batch,
815
  filth_level,
 
816
  username,
817
  ):
 
 
818
  if not prompt or not str(prompt).strip():
819
  return None, "❌ Prompt missing", "", []
820
  now = time.time()
 
828
  gr.Warning("Restricted content")
829
  return None, "❌ Restricted content", "", []
830
 
831
+ use_advanced = bool(use_advanced)
832
+ stacks = normalize_selected_stacks(selected_stacks)
833
+ if use_advanced:
834
+ # Single custom path: Advanced overrides ignore named stacks
835
+ run_jobs = [("Advanced override", advanced_cfg_from_ui(
836
+ chef_a, chef_b, chef_c, chef_d, lora_name, blend_b, blend_c, blend_d
837
+ ))]
838
+ n_paths = 1
839
+ else:
840
+ run_jobs = [(name, STACKS[name]) for name in stacks]
841
+ n_paths = len(run_jobs)
842
+
843
+ batch, batch_warn = clamp_batch_for_stacks(n_paths, batch)
844
  seeds = [
845
  random.randint(0, 2**31 - 1) if int(seed) < 0 else int(seed) + i
846
  for i in range(batch)
847
  ]
848
  enhanced = enhance_prompt(prompt, styles, filth_level)
849
  neg = BASE_NEGATIVES
 
 
 
 
850
  use_steps = int(max(10, min(40, int(steps))))
851
  remix_steps = max(10, int(use_steps * 0.75))
852
  LAST_GEN_TIME[user_key] = now
853
  gallery = []
854
  jobs = []
855
+ info_bits = []
856
+ if batch_warn:
857
+ info_bits.append(batch_warn)
858
+ gr.Warning(batch_warn)
859
  device = "cuda" if torch.cuda.is_available() else "cpu"
860
 
861
  try:
862
+ for stack_label, cfg in run_jobs:
863
+ results = run_chef_pipeline(
864
+ stack_label=stack_label,
865
+ cfg=cfg,
866
+ enhanced=enhanced,
867
+ neg=neg,
868
+ seeds=seeds,
869
+ use_steps=use_steps,
870
+ remix_steps=remix_steps,
871
+ cfg_scale=cfg,
872
+ width=width,
873
+ height=height,
874
+ device=device,
875
+ )
876
+ for out, s, chain, label in results:
877
+ gallery.append((out, label))
878
+ jobs.append((out, s, chain, prompt, username, width, height, use_steps, cfg))
879
+ info_bits.append(chain)
880
+ unload_all()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
881
 
882
  info = (
883
+ f"{' || '.join(info_bits)} | seeds {seeds} | {width}x{height} | "
884
+ f"steps A={use_steps} remix={remix_steps} | filth={filth_level} | "
885
+ f"paths={n_paths} batch={batch} | enhanced prompt ready"
886
  )
887
  return gallery, info, enhanced, jobs
888
  except Exception as e:
 
893
  return None, f"❌ {msg}", "", []
894
 
895
 
896
+ def on_stacks_change(selected):
897
+ """Sync Advanced slots when exactly one named stack is selected."""
898
+ stacks = normalize_selected_stacks(selected)
899
+ if len(stacks) != 1:
900
+ return (
901
+ gr.update(),
902
+ gr.update(),
903
+ gr.update(),
904
+ gr.update(),
905
+ gr.update(),
906
+ gr.update(),
907
+ gr.update(),
908
+ gr.update(),
909
+ )
910
+ cfg = STACKS[stacks[0]]
911
  return (
912
  gr.update(value=cfg["chef_a"]),
913
  gr.update(value=cfg["chef_b"]),
914
+ gr.update(value=cfg["chef_c"] if cfg.get("chef_c") else SKIP_CHEF),
915
+ gr.update(value=cfg["chef_d"] if cfg.get("chef_d") else SKIP_CHEF),
916
  gr.update(value=cfg["lora"]),
917
+ gr.update(value=cfg["blend_b"]),
918
+ gr.update(value=cfg["blend_c"]),
919
+ gr.update(value=cfg["blend_d"]),
920
  )
921
 
922
 
 
953
  user_id = gr.Textbox(visible=False)
954
  gr.Markdown(
955
  """# JustinD/1 · 21+ Fiction Lab
956
+ ### Adult East / Southeast Asian fiction booth · Multi-select stacks · optional quad chefs · ZeroGPU
957
 
958
  **Fiction only. Adult women 21+ (late 20s). No real dating names or faces.**
959
  Body lock baked in: tall slim small frame, tiny waist, large / full / heavy breasts, cleavage.
960
+ Every cook runs **Chef A txt2img → unload → Chef B img2img** (then optional C / D).
961
+ Pick **multiple named stacks** to cook several recipes into one gallery (same prompt / styles / filth / seed base).
962
+ **GPU time tip:** stacks × batch is capped at 6. Selecting 3+ stacks forces batch=1.
963
  Cosmic e-girl booth = fictional DJ only. Negatives stay built-in (not editable).
964
  Visitor gens are **not** saved unless secret `LOG_VISITOR_UPLOADS=1`.
965
  First model load is slow. Queues happen on ZeroGPU."""
966
  )
 
 
 
 
 
967
  with gr.Row():
968
  with gr.Column(scale=5):
969
+ stacks = gr.CheckboxGroup(
970
+ label="Named stacks (multi-select · empty = AI + AI × AI alone)",
971
  choices=STACK_CHOICES,
972
+ value=list(DEFAULT_STACKS),
973
  )
974
  styles = gr.CheckboxGroup(
975
  label="Style packs (stackable · Testing Tits on by default)",
 
993
  step=1,
994
  )
995
  go = gr.Button("🔥 GENERATE", variant="primary", size="lg")
996
+ with gr.Accordion("Advanced (Chef A/B/C/D · LoRA · blends · size)", open=False):
997
+ use_advanced = gr.Checkbox(
998
+ label="Use Advanced override (ignore stack chefs)",
999
+ value=False,
1000
+ )
1001
+ gr.Markdown(
1002
+ "When override is **off**, each selected stack uses its own chefs. "
1003
+ "When **on**, one custom path uses the Advanced slots below (C/D can be skip)."
1004
+ )
1005
  chef_a = gr.Dropdown(
1006
  label="Chef A (txt2img base)",
1007
  choices=SDXL_CHOICES,
 
1012
  choices=SDXL_CHOICES,
1013
  value=default_cfg["chef_b"],
1014
  )
1015
+ chef_c = gr.Dropdown(
1016
+ label="Chef C (optional img2img)",
1017
+ choices=CHEF_OPTIONAL_CHOICES,
1018
+ value=SKIP_CHEF,
1019
+ )
1020
+ chef_d = gr.Dropdown(
1021
+ label="Chef D (optional img2img)",
1022
+ choices=CHEF_OPTIONAL_CHOICES,
1023
+ value=SKIP_CHEF,
1024
+ )
1025
  lora_dd = gr.Dropdown(
1026
+ label="LoRA on last chef that runs (None = blend only)",
1027
  choices=LORA_CHOICES,
1028
  value=default_cfg["lora"],
1029
  )
1030
+ blend_b_sl = gr.Slider(
1031
+ label="Blend B strength (higher = more Chef B)",
1032
+ minimum=0.30,
1033
+ maximum=0.70,
1034
+ value=default_cfg["blend_b"],
1035
+ step=0.05,
1036
+ )
1037
+ blend_c_sl = gr.Slider(
1038
+ label="Blend C strength (if Chef C set)",
1039
+ minimum=0.30,
1040
+ maximum=0.70,
1041
+ value=default_cfg["blend_c"],
1042
+ step=0.05,
1043
+ )
1044
+ blend_d_sl = gr.Slider(
1045
+ label="Blend D strength (if Chef D set)",
1046
  minimum=0.30,
1047
  maximum=0.70,
1048
+ value=default_cfg["blend_d"],
1049
  step=0.05,
1050
  )
1051
  with gr.Row():
1052
  seed = gr.Number(label="Seed (-1 = random / lucky)", value=-1, precision=0)
1053
+ batch = gr.Slider(
1054
+ 1,
1055
+ 3,
1056
+ value=1,
1057
+ step=1,
1058
+ label="Batch (1 safe · stacks×batch capped at 6 · 3+ stacks force batch=1)",
1059
+ )
1060
  with gr.Row():
1061
  width = gr.Slider(512, 1536, value=1024, step=64, label="Width")
1062
  height = gr.Slider(512, 1536, value=1024, step=64, label="Height")
 
1072
  object_fit="contain",
1073
  preview=True,
1074
  )
1075
+ info = gr.Textbox(label="Run info", lines=3)
1076
  meta = gr.Textbox(label="Enhanced prompt (auto)", lines=4)
1077
 
1078
  demo.load(fn=None, inputs=None, outputs=user_id, js=js_visitor)
1079
 
1080
+ stacks.change(
1081
+ fn=on_stacks_change,
1082
+ inputs=[stacks],
1083
+ outputs=[chef_a, chef_b, chef_c, chef_d, lora_dd, blend_b_sl, blend_c_sl, blend_d_sl],
1084
  )
1085
  lucky_prompt_btn.click(fn=random_starter, inputs=None, outputs=prompt)
1086
  lucky_seed_btn.click(fn=lucky_seed, inputs=None, outputs=seed)
 
1099
  inputs=[
1100
  prompt,
1101
  styles,
1102
+ stacks,
1103
  chef_a,
1104
  chef_b,
1105
+ chef_c,
1106
+ chef_d,
1107
  lora_dd,
1108
+ blend_b_sl,
1109
+ blend_c_sl,
1110
+ blend_d_sl,
1111
+ use_advanced,
1112
  steps,
1113
  cfg,
1114
  seed,
 
1116
  height,
1117
  batch,
1118
  filth,
 
1119
  user_id,
1120
  ],
1121
  outputs=[gallery, info, meta],
 
1128
  app = create_ui()
1129
  app.queue(max_size=12)
1130
  app.launch(ssr_mode=False, theme=THEME, css=css)