JustinD commited on
Commit
28fa4c1
·
verified ·
1 Parent(s): 0fbee88

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +55 -114
app.py CHANGED
@@ -24,14 +24,13 @@ import gradio as gr
24
  from huggingface_hub import hf_hub_download, HfApi, CommitOperationAdd
25
  from huggingface_hub.utils import HfHubHTTPError
26
 
27
- # ===== LOGGING =====
28
  logging.basicConfig(
29
  level=logging.INFO,
30
  format="%(asctime)s - %(levelname)s - %(message)s",
31
  handlers=[logging.StreamHandler(sys.stdout)],
32
  )
33
  logger = logging.getLogger("NextGenEngine")
34
- logger.info("🚀 Next-gen multi-engine Space boot (FLUX / SD3.5 / Adult SDXL)")
35
 
36
  if hasattr(sys.stdout, "reconfigure"):
37
  sys.stdout.reconfigure(encoding="utf-8")
@@ -41,78 +40,51 @@ if hasattr(sys.stderr, "reconfigure"):
41
  BACKUP_DATASET_ID = "JustinD/generation-images"
42
  LAST_GEN_TIME = {}
43
  COOLDOWN_SECONDS = 12
44
-
45
- # Public-ready: visitor gens are NOT uploaded unless Space secret LOG_VISITOR_UPLOADS=1
46
  LOG_VISITOR_UPLOADS = os.environ.get("LOG_VISITOR_UPLOADS", "0").strip().lower() in {"1", "true", "yes"}
47
 
48
- # ---------------------------------------------------------------------------
49
- # ENGINES
50
- # ---------------------------------------------------------------------------
51
  ENGINE_FLUX = "FLUX.1 Schnell (latest fast)"
52
  ENGINE_SD35 = "SD 3.5 Medium (gated / optional)"
53
  ENGINE_SDXL = "Adult SDXL (NSFW chefs)"
54
-
55
  ENGINE_CHOICES = [ENGINE_SDXL, ENGINE_FLUX, ENGINE_SD35]
56
 
57
- # Adult SDXL single-file + Diffusers (proven Justin lane)
58
  HF_REPO_ID = "AriaVale2/my-safetensors-backup"
 
 
59
  MODEL_REPOS = {
 
 
60
  "pornworksSexyBeauty_v04.safetensors": "AI-Porn/PornWorks-Sexy-Beauty-Aesthetic-Erotic-NSFW-SDXL-Chekpoint",
61
  "pornworksRealPornPhoto_v04.safetensors": "Manjushri/pornworks-real-porn-photo-realistic-nsfw-sdxl-and-pony-chekpoint",
62
- "pornworksHardcoreFantasy_v04.safetensors": "AI-Porn/PornWorks-Hardcore-Fantasy-NSFW-3D-SDXL-Chekpoint",
63
- "CyberRealisticXLPlay_V10.0_FP16.safetensors": "cyberdelia/CyberRealisticXL",
64
- "Juggernaut-XL_v9_RunDiffusionPhoto_v2.safetensors": "saber-end/Juggernaut-XL-v9",
65
- "RealVisXL_V4.0.safetensors": "SG161222/RealVisXL_V4.0",
66
- "Pony Diffusion V6 XL.safetensors": "AriaVale2/my-safetensors-backup",
67
  "Hassaku XL (Illustrious).safetensors": "AriaVale2/my-safetensors-backup",
68
- "animagine-xl-4.0.safetensors": "cagliostrolab/animagine-xl-4.0",
69
- "OnomaAIResearch_Illustrious-XL-v1.0.safetensors": "AriaVale2/my-safetensors-backup",
70
- "UnfilteredAI_NSFW-GEN-ANIME-v2.safetensors": "AriaVale2/my-safetensors-backup",
71
  "Mala Anime Mix NSFW PonyXL.safetensors": "AriaVale2/my-safetensors-backup",
72
  "PornWorks_Anime_Desire_NSFW_Anime_Hentai_SDXL_Pony_Chekpoint.safetensors": "AriaVale2/my-safetensors-backup",
73
  }
74
 
 
75
  DIFFUSERS_MODELS = {
 
 
 
 
 
 
 
76
  "OhMySister Beautiful Asian Woman": "John6666/oms-oh-my-sister-beautiful-asian-woman-v10-sdxl",
77
- "OhMyDaughter Beautiful Woman (Pony)": "John6666/omd-ohmaydaughter-beautiful-woman-pony-v10-sdxl",
78
  "Fucktastic Anime Pony NSFW": "John6666/fucktastic-anime-checkpoint-pony-pdxl-porn-nsfw-sfw-22-sdxl",
79
- "Mala Anime Mix NSFW Pony v7": "John6666/mala-anime-mix-nsfw-pony-xl-v7-sdxl",
80
  "YAAI WAI NSFW Illustrious": "John6666/yaai-yet-another-anime-illustrious-model-full-fine-tune-based-on-wai-nsfw-v20-sdxl",
81
- "Flare Diffusion NSFW Anime": "John6666/flare-diffusion-nsfw-v16-semi-realism-anime-sdxl",
82
- "PornWorks Anime Desire (Diffusers)": "John6666/pornworks-anime-desire-nsfw-pony-sdxl",
83
  }
84
 
85
  SDXL_CHOICES = list(DIFFUSERS_MODELS.keys()) + list(MODEL_REPOS.keys())
 
86
 
 
87
  STYLE_PACKS = {
88
- "Men Only": {
89
- "prefix": "for men's eyes only, hyper sexual pinup, hungry male gaze",
90
- "suffix": "deep cleavage, arched back, bedroom eyes, wet parted lips",
91
- },
92
- "Art Nude": {
93
- "prefix": "classical art nude, museum erotic nude",
94
- "suffix": "bare breasts, soft weight, masterpiece lighting",
95
- },
96
- "Cartoon Slut": {
97
- "prefix": "score_9, score_8_up, source_anime, rating_explicit, anime cartoon cel shading",
98
- "suffix": "slutty pinup, exaggerated hourglass, heavy cleavage",
99
- },
100
- "Rembrandt Oil": {
101
- "prefix": "Rembrandt oil painting, warm side light, visible brushstrokes",
102
- "suffix": "classical academic nude energy",
103
- },
104
- "4K TV Wide": {
105
- "prefix": "16:9 ultra wide cinematic framing for 4K TV",
106
- "suffix": "razor sharp when blown up large",
107
- },
108
- "Photo Gloss": {
109
- "prefix": "photorealistic, wet shiny skin, editorial flash",
110
- "suffix": "ultra detailed skin pores and fabric",
111
- },
112
  "Testing Tits": {
113
- "prefix": "Nice tits, big tits, fun bags",
114
- "suffix": "No bra",
115
- }
116
  }
117
 
118
  DEFAULT_QUALITY = (
@@ -126,23 +98,23 @@ DEFAULT_QUALITY = (
126
  )
127
 
128
  BASE_NEGATIVES = (
129
- "child, loli, underage, teen, "
130
  "man, men, male, boy, guy, guys, male focus, masculine, "
131
  "old, elderly, aged, grandma, wrinkly, "
132
- "fat, chubby, obese, overweight, belly fat, "
133
  "beard, mustache, penis, dick, balls, "
134
  "small breasts, flat chest, bad hands, extra fingers, deformed, "
135
  "blurry, lowres, watermark, text, censored, ugly"
136
  )
137
 
138
  RANDOM_PROMPTS = [
139
- "asian slut",
140
- "nude woman"
 
 
 
141
  ]
142
 
143
- # ---------------------------------------------------------------------------
144
- # SAFETY
145
- # ---------------------------------------------------------------------------
146
  FORBIDDEN_KEYWORDS = []
147
  if os.path.exists("restricted_words.txt"):
148
  try:
@@ -155,10 +127,11 @@ if os.path.exists("restricted_words.txt"):
155
  except Exception as e:
156
  logger.error(f"restricted words load failed: {e}")
157
 
 
158
  AGE_PATTERN = re.compile(
159
  r"\b(one|two|three|four|five|six|seven|eight|nine|ten|"
160
- r"eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|"
161
- r"1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17)"
162
  r"\s*[-\s]?\s*(year|yr|y\/o|yo)\b"
163
  )
164
 
@@ -191,12 +164,7 @@ def build_styles(selected):
191
  return ", ".join(prefixes), ", ".join(suffixes)
192
 
193
 
194
- # ---------------------------------------------------------------------------
195
- # LOGGING UPLOAD (same pantry)
196
- # ---------------------------------------------------------------------------
197
-
198
  def should_log_upload(username) -> bool:
199
- """Default off for public visitors. Owner can set Space secret LOG_VISITOR_UPLOADS=1."""
200
  if not LOG_VISITOR_UPLOADS:
201
  return False
202
  return True
@@ -206,7 +174,6 @@ def async_log_upload(pil_img, seed, model_name, prompt, username, width, height,
206
  if not should_log_upload(username):
207
  logger.info("⏭️ visitor log upload skipped (LOG_VISITOR_UPLOADS not enabled)")
208
  return
209
-
210
  hf_token = os.environ.get("HF_TOKEN")
211
  if not username or not str(username).strip():
212
  username = "Unknown_Visitor"
@@ -253,35 +220,32 @@ def async_log_upload(pil_img, seed, model_name, prompt, username, width, height,
253
  time.sleep(1.5 * attempt)
254
 
255
 
256
- # ---------------------------------------------------------------------------
257
- # PIPELINE CACHE
258
- # ---------------------------------------------------------------------------
259
  _pipes = {}
260
- _current_key = None
261
 
262
 
263
  def _dtype():
264
  if torch.cuda.is_available():
265
- # Flux prefers bfloat16 when available
266
  return torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
267
  return torch.float32
268
 
269
 
270
  def unload_all():
271
- global _pipes, _current_key
272
  _pipes.clear()
273
- _current_key = None
274
  gc.collect()
275
  if torch.cuda.is_available():
276
  torch.cuda.empty_cache()
277
 
278
 
 
 
 
 
279
  def get_flux_pipe():
280
  key = "flux-schnell"
281
  if key in _pipes:
282
  return _pipes[key]
283
  from diffusers import FluxPipeline
284
-
285
  unload_all()
286
  logger.info("Loading FLUX.1 Schnell…")
287
  token = _hf_token()
@@ -296,28 +260,22 @@ def get_flux_pipe():
296
  return pipe
297
 
298
 
299
- def _hf_token():
300
- return os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_HUB_TOKEN")
301
-
302
-
303
  def get_sd35_pipe():
304
  key = "sd35-medium"
305
  if key in _pipes:
306
  return _pipes[key]
307
  from diffusers import StableDiffusion3Pipeline
308
  from huggingface_hub import login as hf_login
309
-
310
  token = _hf_token()
311
  if not token:
312
  raise RuntimeError(
313
- "SD 3.5 is gated. Add a Write/Read HF token as Space secret named HF_TOKEN, "
314
- "and accept the license at https://huggingface.co/stabilityai/stable-diffusion-3.5-medium"
315
  )
316
  try:
317
  hf_login(token=token, add_to_git_credential=False)
318
  except Exception as e:
319
  logger.warning(f"hf_login warning: {e}")
320
-
321
  unload_all()
322
  logger.info("Loading SD 3.5 Medium (authenticated)…")
323
  pipe = StableDiffusion3Pipeline.from_pretrained(
@@ -336,8 +294,6 @@ def get_sdxl_pipe(model_name: str):
336
  if key in _pipes:
337
  return _pipes[key]
338
  from diffusers import StableDiffusionXLPipeline, EulerDiscreteScheduler
339
-
340
- # Keep only one heavy pipe resident
341
  unload_all()
342
  dtype = torch.float16 if torch.cuda.is_available() else torch.float32
343
  logger.info(f"Loading Adult SDXL chef: {model_name}")
@@ -375,9 +331,6 @@ def enhance_prompt(prompt, styles):
375
  return ", ".join([p for p in parts if p])
376
 
377
 
378
- # ---------------------------------------------------------------------------
379
- # GENERATE
380
- # ---------------------------------------------------------------------------
381
  @spaces.GPU(duration=180) if SPACES_AVAILABLE else (lambda f: f)
382
  def generate(
383
  engine,
@@ -395,19 +348,16 @@ def generate(
395
  ):
396
  if not prompt or not str(prompt).strip():
397
  return None, "❌ Prompt missing", "", []
398
-
399
  now = time.time()
400
  user_key = username or "anonymous"
401
  if user_key in LAST_GEN_TIME and now - LAST_GEN_TIME[user_key] < COOLDOWN_SECONDS:
402
  left = int(COOLDOWN_SECONDS - (now - LAST_GEN_TIME[user_key]))
403
  gr.Warning(f"Cooldown {left}s")
404
  return None, f"⏳ Cooldown {left}s", "", []
405
-
406
  ok, bad = check_prompt_safety(prompt)
407
  if not ok:
408
  gr.Warning("Restricted content")
409
  return None, "❌ Restricted content", "", []
410
-
411
  batch = int(max(1, min(3, int(batch))))
412
  seeds = [
413
  random.randint(0, 2**31 - 1) if int(seed) < 0 else int(seed) + i
@@ -417,22 +367,18 @@ def generate(
417
  neg = BASE_NEGATIVES
418
  if negative and str(negative).strip():
419
  neg = f"{BASE_NEGATIVES}, {negative.strip()}"
420
-
421
  LAST_GEN_TIME[user_key] = now
422
  gallery = []
423
  jobs = []
424
-
425
  try:
426
  device = "cuda" if torch.cuda.is_available() else "cpu"
427
-
428
  if engine == ENGINE_FLUX:
429
  pipe = get_flux_pipe()
430
- # Schnell: low steps, guidance often ~0
431
  use_steps = int(max(1, min(8, int(steps) if int(steps) <= 8 else 4)))
432
- # FLUX ignores negatives — shove woman-only steer into the prompt
433
  flux_prompt = (
434
  enhanced
435
- + ", 1girl, solo, adult woman only, no men, no males, no old people, slim fit body"
 
436
  )
437
  for s in seeds:
438
  gen = torch.Generator(device=device).manual_seed(int(s))
@@ -448,7 +394,6 @@ def generate(
448
  label = f"FLUX · seed {s} · {datetime.now().strftime('%I:%M:%S %p')}"
449
  gallery.append((out, label))
450
  jobs.append((out, s, "FLUX.1-schnell", prompt, username, width, height, use_steps, 0.0))
451
-
452
  elif engine == ENGINE_SD35:
453
  pipe = get_sd35_pipe()
454
  use_steps = int(max(15, min(40, int(steps))))
@@ -466,7 +411,6 @@ def generate(
466
  label = f"SD3.5 · seed {s} · {datetime.now().strftime('%I:%M:%S %p')}"
467
  gallery.append((out, label))
468
  jobs.append((out, s, "SD3.5-medium", prompt, username, width, height, use_steps, cfg))
469
-
470
  else:
471
  pipe = get_sdxl_pipe(sdxl_model)
472
  use_steps = int(max(10, min(40, int(steps))))
@@ -484,21 +428,16 @@ def generate(
484
  label = f"SDXL · {sdxl_model[:28]} · seed {s}"
485
  gallery.append((out, label))
486
  jobs.append((out, s, sdxl_model, prompt, username, width, height, use_steps, cfg))
487
-
488
  info = f"{engine} | seeds {seeds} | {width}x{height} | enhanced prompt ready"
489
  return gallery, info, enhanced, jobs
490
-
491
  except Exception as e:
492
  logger.exception("generate failed")
493
  msg = str(e)
494
  if "gated" in msg.lower() or "401" in msg or "403" in msg:
495
- msg += " — open the model page on HF, accept the license, ensure Space secret HF_TOKEN can read it (or use HF login)."
496
  return None, f"❌ {msg}", "", []
497
 
498
 
499
- # ---------------------------------------------------------------------------
500
- # UI
501
- # ---------------------------------------------------------------------------
502
  js_visitor = """
503
  function() {
504
  let id = localStorage.getItem('adult_ai_visitor_id');
@@ -521,35 +460,39 @@ THEME = gr.themes.Default(primary_hue="purple", secondary_hue="indigo", neutral_
521
  def create_ui():
522
  with gr.Blocks() as demo:
523
  user_id = gr.Textbox(visible=False)
524
- gr.Markdown("# ⚡ Next-Gen Image Lab")
525
  gr.Markdown(
526
- """### 18+ · ZeroGPU · first model load is slow · queues happen
527
 
528
- **Defaults for this lane:** Adult SDXL first (NSFW chefs, anti-man/old/fat locked).
529
- FLUX is optional tech demo (weaker NSFW control). SD 3.5 is gated/optional.
530
- Visitor gens are **not** saved unless secret `LOG_VISITOR_UPLOADS=1`."""
 
531
  )
532
-
533
  with gr.Row():
534
  with gr.Column(scale=2):
535
  prompt = gr.Textbox(label="Prompt", value=random.choice(RANDOM_PROMPTS), lines=7)
536
- negative = gr.Textbox(label="Negatives (SD3.5 / SDXL; ignored by FLUX)", value=BASE_NEGATIVES, lines=4)
 
 
 
 
537
  styles = gr.CheckboxGroup(
538
- label="Style packs (stackable)",
539
  choices=list(STYLE_PACKS.keys()),
540
- value=["Men Only", "Photo Gloss"],
541
  )
542
  go = gr.Button("GENERATE", variant="primary", size="lg")
543
  with gr.Column(scale=1):
544
  engine = gr.Radio(
545
- label="Engine (Adult SDXL = default for this lane)",
546
  choices=ENGINE_CHOICES,
547
  value=ENGINE_SDXL,
548
  )
549
  sdxl_model = gr.Dropdown(
550
  label="Adult SDXL chef (only used on Adult SDXL engine)",
551
  choices=SDXL_CHOICES,
552
- value="OhMySister Beautiful Asian Woman",
553
  )
554
  seed = gr.Number(label="Seed (-1 random)", value=-1, precision=0)
555
  width = gr.Slider(512, 1536, value=1024, step=64, label="Width")
@@ -557,11 +500,9 @@ Visitor gens are **not** saved unless secret `LOG_VISITOR_UPLOADS=1`."""
557
  steps = gr.Slider(1, 40, value=28, step=1, label="Steps (SDXL~28, FLUX~4, SD3.5~28)")
558
  cfg = gr.Slider(0.0, 12.0, value=6.0, step=0.5, label="CFG (SDXL~6, FLUX uses 0)")
559
  batch = gr.Slider(1, 3, value=1, step=1, label="Batch (max 3)")
560
-
561
  gallery = gr.Gallery(label="Outputs", columns=3, height="auto")
562
  info = gr.Textbox(label="Run info", visible=True)
563
  meta = gr.Textbox(label="Enhanced prompt", visible=True)
564
-
565
  demo.load(fn=None, inputs=None, outputs=user_id, js=js_visitor)
566
 
567
  def on_go(*args):
 
24
  from huggingface_hub import hf_hub_download, HfApi, CommitOperationAdd
25
  from huggingface_hub.utils import HfHubHTTPError
26
 
 
27
  logging.basicConfig(
28
  level=logging.INFO,
29
  format="%(asctime)s - %(levelname)s - %(message)s",
30
  handlers=[logging.StreamHandler(sys.stdout)],
31
  )
32
  logger = logging.getLogger("NextGenEngine")
33
+ logger.info("🚀 NSFW East Asian Lab boot (19+ · Adult SDXL / FLUX / SD3.5)")
34
 
35
  if hasattr(sys.stdout, "reconfigure"):
36
  sys.stdout.reconfigure(encoding="utf-8")
 
40
  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
  ENGINE_FLUX = "FLUX.1 Schnell (latest fast)"
46
  ENGINE_SD35 = "SD 3.5 Medium (gated / optional)"
47
  ENGINE_SDXL = "Adult SDXL (NSFW chefs)"
 
48
  ENGINE_CHOICES = [ENGINE_SDXL, ENGINE_FLUX, ENGINE_SD35]
49
 
 
50
  HF_REPO_ID = "AriaVale2/my-safetensors-backup"
51
+
52
+ # Single-file chefs (Asian realism + proven NSFW pantry)
53
  MODEL_REPOS = {
54
+ "asianRealismByStable_v20FP16.safetensors": "Stableyogi/Asian-Realism-Checkpoints",
55
+ "asianRealismByStable_v30FP16.safetensors": "Stableyogi/Asian-Realism-Checkpoints",
56
  "pornworksSexyBeauty_v04.safetensors": "AI-Porn/PornWorks-Sexy-Beauty-Aesthetic-Erotic-NSFW-SDXL-Chekpoint",
57
  "pornworksRealPornPhoto_v04.safetensors": "Manjushri/pornworks-real-porn-photo-realistic-nsfw-sdxl-and-pony-chekpoint",
 
 
 
 
 
58
  "Hassaku XL (Illustrious).safetensors": "AriaVale2/my-safetensors-backup",
 
 
 
59
  "Mala Anime Mix NSFW PonyXL.safetensors": "AriaVale2/my-safetensors-backup",
60
  "PornWorks_Anime_Desire_NSFW_Anime_Hentai_SDXL_Pony_Chekpoint.safetensors": "AriaVale2/my-safetensors-backup",
61
  }
62
 
63
+ # Newer Diffusers Asian NSFW / realism chefs (2025–2026)
64
  DIFFUSERS_MODELS = {
65
+ "Diving Real Asian Illustrious v5": "John6666/diving-illustrious-real-asian-v50-sdxl",
66
+ "Nova Asian Illustrious v6": "John6666/nova-asian-xl-illustrious-v60-sdxl",
67
+ "WinPic Asian Realism v1.1": "John6666/winpic-asian-realism-v11-sdxl",
68
+ "Evalisennia East Asian Female v3": "John6666/evalisennia-realistic-east-asian-female-v30-sdxl",
69
+ "Hardcore Asian Cosplay Illustrious": "John6666/hardcore-asian-cosplay-porn-il-v11-sdxl",
70
+ "Perfection Asian Illustrious NSFW": "John6666/perfection-asian-ilxl-illustrious-xl-nsfw-sfw-checkpoint-v10-sdxl",
71
+ "Your Asian GF 2.8D v3": "John6666/your-asian-gf-28d-v30-sdxl",
72
  "OhMySister Beautiful Asian Woman": "John6666/oms-oh-my-sister-beautiful-asian-woman-v10-sdxl",
73
+ "Asian E-Girl NSFW XL": "John6666/asian-egirl-xl-realistic-uwu-egirl-tattoo-model-sdxl-nsfw-v10-sdxl",
74
  "Fucktastic Anime Pony NSFW": "John6666/fucktastic-anime-checkpoint-pony-pdxl-porn-nsfw-sfw-22-sdxl",
 
75
  "YAAI WAI NSFW Illustrious": "John6666/yaai-yet-another-anime-illustrious-model-full-fine-tune-based-on-wai-nsfw-v20-sdxl",
76
+ "Mala Anime Mix NSFW Pony v7": "John6666/mala-anime-mix-nsfw-pony-xl-v7-sdxl",
 
77
  }
78
 
79
  SDXL_CHOICES = list(DIFFUSERS_MODELS.keys()) + list(MODEL_REPOS.keys())
80
+ DEFAULT_SDXL = "Diving Real Asian Illustrious v5"
81
 
82
+ # B + C: East Asian slim/tall/big tits lane; Testing Tits is the only style pack
83
  STYLE_PACKS = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  "Testing Tits": {
85
+ "prefix": "nice tits, big tits, fun bags, heavy cleavage focus",
86
+ "suffix": "no bra, bare breasts energy, chest forward",
87
+ },
88
  }
89
 
90
  DEFAULT_QUALITY = (
 
98
  )
99
 
100
  BASE_NEGATIVES = (
101
+ "child, loli, underage, teen, 18, eighteen, "
102
  "man, men, male, boy, guy, guys, male focus, masculine, "
103
  "old, elderly, aged, grandma, wrinkly, "
104
+ "fat, chubby, obese, overweight, belly fat, thick thighs, "
105
  "beard, mustache, penis, dick, balls, "
106
  "small breasts, flat chest, bad hands, extra fingers, deformed, "
107
  "blurry, lowres, watermark, text, censored, ugly"
108
  )
109
 
110
  RANDOM_PROMPTS = [
111
+ "adult Japanese woman late 20s, sheer lace lingerie, bedroom eyes",
112
+ "adult Chinese woman late 20s, nude, glossy skin, heavy cleavage",
113
+ "adult Taiwanese woman late 20s, soft editorial heat, wet parted lips",
114
+ "adult Vietnamese woman late 20s, no bra, chest forward",
115
+ "adult Korean woman late 20s, nude woman, large breasts",
116
  ]
117
 
 
 
 
118
  FORBIDDEN_KEYWORDS = []
119
  if os.path.exists("restricted_words.txt"):
120
  try:
 
127
  except Exception as e:
128
  logger.error(f"restricted words load failed: {e}")
129
 
130
+ # 19+: block ages under 19 in prompt text
131
  AGE_PATTERN = re.compile(
132
  r"\b(one|two|three|four|five|six|seven|eight|nine|ten|"
133
+ r"eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|"
134
+ r"1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18)"
135
  r"\s*[-\s]?\s*(year|yr|y\/o|yo)\b"
136
  )
137
 
 
164
  return ", ".join(prefixes), ", ".join(suffixes)
165
 
166
 
 
 
 
 
167
  def should_log_upload(username) -> bool:
 
168
  if not LOG_VISITOR_UPLOADS:
169
  return False
170
  return True
 
174
  if not should_log_upload(username):
175
  logger.info("⏭️ visitor log upload skipped (LOG_VISITOR_UPLOADS not enabled)")
176
  return
 
177
  hf_token = os.environ.get("HF_TOKEN")
178
  if not username or not str(username).strip():
179
  username = "Unknown_Visitor"
 
220
  time.sleep(1.5 * attempt)
221
 
222
 
 
 
 
223
  _pipes = {}
 
224
 
225
 
226
  def _dtype():
227
  if torch.cuda.is_available():
 
228
  return torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
229
  return torch.float32
230
 
231
 
232
  def unload_all():
233
+ global _pipes
234
  _pipes.clear()
 
235
  gc.collect()
236
  if torch.cuda.is_available():
237
  torch.cuda.empty_cache()
238
 
239
 
240
+ def _hf_token():
241
+ return os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_HUB_TOKEN")
242
+
243
+
244
  def get_flux_pipe():
245
  key = "flux-schnell"
246
  if key in _pipes:
247
  return _pipes[key]
248
  from diffusers import FluxPipeline
 
249
  unload_all()
250
  logger.info("Loading FLUX.1 Schnell…")
251
  token = _hf_token()
 
260
  return pipe
261
 
262
 
 
 
 
 
263
  def get_sd35_pipe():
264
  key = "sd35-medium"
265
  if key in _pipes:
266
  return _pipes[key]
267
  from diffusers import StableDiffusion3Pipeline
268
  from huggingface_hub import login as hf_login
 
269
  token = _hf_token()
270
  if not token:
271
  raise RuntimeError(
272
+ "SD 3.5 is gated. Add HF_TOKEN secret and accept "
273
+ "https://huggingface.co/stabilityai/stable-diffusion-3.5-medium"
274
  )
275
  try:
276
  hf_login(token=token, add_to_git_credential=False)
277
  except Exception as e:
278
  logger.warning(f"hf_login warning: {e}")
 
279
  unload_all()
280
  logger.info("Loading SD 3.5 Medium (authenticated)…")
281
  pipe = StableDiffusion3Pipeline.from_pretrained(
 
294
  if key in _pipes:
295
  return _pipes[key]
296
  from diffusers import StableDiffusionXLPipeline, EulerDiscreteScheduler
 
 
297
  unload_all()
298
  dtype = torch.float16 if torch.cuda.is_available() else torch.float32
299
  logger.info(f"Loading Adult SDXL chef: {model_name}")
 
331
  return ", ".join([p for p in parts if p])
332
 
333
 
 
 
 
334
  @spaces.GPU(duration=180) if SPACES_AVAILABLE else (lambda f: f)
335
  def generate(
336
  engine,
 
348
  ):
349
  if not prompt or not str(prompt).strip():
350
  return None, "❌ Prompt missing", "", []
 
351
  now = time.time()
352
  user_key = username or "anonymous"
353
  if user_key in LAST_GEN_TIME and now - LAST_GEN_TIME[user_key] < COOLDOWN_SECONDS:
354
  left = int(COOLDOWN_SECONDS - (now - LAST_GEN_TIME[user_key]))
355
  gr.Warning(f"Cooldown {left}s")
356
  return None, f"⏳ Cooldown {left}s", "", []
 
357
  ok, bad = check_prompt_safety(prompt)
358
  if not ok:
359
  gr.Warning("Restricted content")
360
  return None, "❌ Restricted content", "", []
 
361
  batch = int(max(1, min(3, int(batch))))
362
  seeds = [
363
  random.randint(0, 2**31 - 1) if int(seed) < 0 else int(seed) + i
 
367
  neg = BASE_NEGATIVES
368
  if negative and str(negative).strip():
369
  neg = f"{BASE_NEGATIVES}, {negative.strip()}"
 
370
  LAST_GEN_TIME[user_key] = now
371
  gallery = []
372
  jobs = []
 
373
  try:
374
  device = "cuda" if torch.cuda.is_available() else "cpu"
 
375
  if engine == ENGINE_FLUX:
376
  pipe = get_flux_pipe()
 
377
  use_steps = int(max(1, min(8, int(steps) if int(steps) <= 8 else 4)))
 
378
  flux_prompt = (
379
  enhanced
380
+ + ", 1girl, solo, adult East Asian woman only, no men, no males, "
381
+ "no old people, tall slim body, large breasts"
382
  )
383
  for s in seeds:
384
  gen = torch.Generator(device=device).manual_seed(int(s))
 
394
  label = f"FLUX · seed {s} · {datetime.now().strftime('%I:%M:%S %p')}"
395
  gallery.append((out, label))
396
  jobs.append((out, s, "FLUX.1-schnell", prompt, username, width, height, use_steps, 0.0))
 
397
  elif engine == ENGINE_SD35:
398
  pipe = get_sd35_pipe()
399
  use_steps = int(max(15, min(40, int(steps))))
 
411
  label = f"SD3.5 · seed {s} · {datetime.now().strftime('%I:%M:%S %p')}"
412
  gallery.append((out, label))
413
  jobs.append((out, s, "SD3.5-medium", prompt, username, width, height, use_steps, cfg))
 
414
  else:
415
  pipe = get_sdxl_pipe(sdxl_model)
416
  use_steps = int(max(10, min(40, int(steps))))
 
428
  label = f"SDXL · {sdxl_model[:28]} · seed {s}"
429
  gallery.append((out, label))
430
  jobs.append((out, s, sdxl_model, prompt, username, width, height, use_steps, cfg))
 
431
  info = f"{engine} | seeds {seeds} | {width}x{height} | enhanced prompt ready"
432
  return gallery, info, enhanced, jobs
 
433
  except Exception as e:
434
  logger.exception("generate failed")
435
  msg = str(e)
436
  if "gated" in msg.lower() or "401" in msg or "403" in msg:
437
+ msg += " — accept the model license and ensure Space secret HF_TOKEN can read it."
438
  return None, f"❌ {msg}", "", []
439
 
440
 
 
 
 
441
  js_visitor = """
442
  function() {
443
  let id = localStorage.getItem('adult_ai_visitor_id');
 
460
  def create_ui():
461
  with gr.Blocks() as demo:
462
  user_id = gr.Textbox(visible=False)
463
+ gr.Markdown("# ⚡ NSFW East Asian Lab")
464
  gr.Markdown(
465
+ """### 19+ only · ZeroGPU · first model load is slow · queues happen
466
 
467
+ **This Space is adult NSFW East Asian women only** (slim / tall / big tits).
468
+ Default engine: Adult SDXL with newer Asian Illustrious / Pony chefs.
469
+ Style pack: **Testing Tits** only.
470
+ FLUX and SD 3.5 stay optional. Visitor gens are **not** saved unless secret `LOG_VISITOR_UPLOADS=1`."""
471
  )
 
472
  with gr.Row():
473
  with gr.Column(scale=2):
474
  prompt = gr.Textbox(label="Prompt", value=random.choice(RANDOM_PROMPTS), lines=7)
475
+ negative = gr.Textbox(
476
+ label="Negatives (SD3.5 / SDXL; ignored by FLUX)",
477
+ value=BASE_NEGATIVES,
478
+ lines=4,
479
+ )
480
  styles = gr.CheckboxGroup(
481
+ label="Style packs",
482
  choices=list(STYLE_PACKS.keys()),
483
+ value=["Testing Tits"],
484
  )
485
  go = gr.Button("GENERATE", variant="primary", size="lg")
486
  with gr.Column(scale=1):
487
  engine = gr.Radio(
488
+ label="Engine (Adult SDXL = default)",
489
  choices=ENGINE_CHOICES,
490
  value=ENGINE_SDXL,
491
  )
492
  sdxl_model = gr.Dropdown(
493
  label="Adult SDXL chef (only used on Adult SDXL engine)",
494
  choices=SDXL_CHOICES,
495
+ value=DEFAULT_SDXL,
496
  )
497
  seed = gr.Number(label="Seed (-1 random)", value=-1, precision=0)
498
  width = gr.Slider(512, 1536, value=1024, step=64, label="Width")
 
500
  steps = gr.Slider(1, 40, value=28, step=1, label="Steps (SDXL~28, FLUX~4, SD3.5~28)")
501
  cfg = gr.Slider(0.0, 12.0, value=6.0, step=0.5, label="CFG (SDXL~6, FLUX uses 0)")
502
  batch = gr.Slider(1, 3, value=1, step=1, label="Batch (max 3)")
 
503
  gallery = gr.Gallery(label="Outputs", columns=3, height="auto")
504
  info = gr.Textbox(label="Run info", visible=True)
505
  meta = gr.Textbox(label="Enhanced prompt", visible=True)
 
506
  demo.load(fn=None, inputs=None, outputs=user_id, js=js_visitor)
507
 
508
  def on_go(*args):