JustinD commited on
Commit
fa5b005
·
verified ·
1 Parent(s): c950286

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +355 -656
app.py CHANGED
@@ -11,212 +11,125 @@ import sys
11
  import os
12
  import re
13
  import random
14
- import torch
15
  import gc
16
  import logging
17
  import time
18
  import io
19
  import threading
20
- import urllib.request
21
- import zipfile
22
  from datetime import datetime
23
- from diffusers import (
24
- StableDiffusionXLPipeline,
25
- StableDiffusionXLImg2ImgPipeline,
26
- EulerDiscreteScheduler,
27
- )
28
- from huggingface_hub import hf_hub_download, HfApi, CommitOperationAdd
29
- from huggingface_hub.utils import HfHubHTTPError
30
  from PIL import Image
31
  import gradio as gr
 
 
32
 
33
- # ===== SETUP LOGGING CONFIGURATION =====
34
  logging.basicConfig(
35
  level=logging.INFO,
36
  format="%(asctime)s - %(levelname)s - %(message)s",
37
  handlers=[logging.StreamHandler(sys.stdout)],
38
  )
39
- logger = logging.getLogger("AdultAIEngine")
40
- logger.info("🚀 Initialization: Mad-mode engine live (3 chefs, stackable styles, batch 3).")
41
 
42
  if hasattr(sys.stdout, "reconfigure"):
43
  sys.stdout.reconfigure(encoding="utf-8")
44
  if hasattr(sys.stderr, "reconfigure"):
45
  sys.stderr.reconfigure(encoding="utf-8")
46
 
47
- # ===== CONFIGURATIONS & PRESETS =====
48
- HF_REPO_ID = "AriaVale2/my-safetensors-backup"
 
49
 
 
 
 
 
 
 
 
 
 
 
 
50
  MODEL_REPOS = {
 
 
 
 
51
  "Juggernaut-XL_v9_RunDiffusionPhoto_v2.safetensors": "saber-end/Juggernaut-XL-v9",
52
  "RealVisXL_V4.0.safetensors": "SG161222/RealVisXL_V4.0",
53
- "CyberRealisticXLPlay_V10.0_FP16.safetensors": "cyberdelia/CyberRealisticXL",
54
- "animagine-xl-4.0.safetensors": "cagliostrolab/animagine-xl-4.0",
55
- "OnomaAIResearch_Illustrious-XL-v1.0.safetensors": "AriaVale2/my-safetensors-backup",
56
  "Pony Diffusion V6 XL.safetensors": "AriaVale2/my-safetensors-backup",
57
  "Hassaku XL (Illustrious).safetensors": "AriaVale2/my-safetensors-backup",
58
- "pornworksRealPornPhoto_v04.safetensors": "Manjushri/pornworks-real-porn-photo-realistic-nsfw-sdxl-and-pony-chekpoint",
59
- "pornworksSexyBeauty_v04.safetensors": "AI-Porn/PornWorks-Sexy-Beauty-Aesthetic-Erotic-NSFW-SDXL-Chekpoint",
60
- "pornworksHardcoreFantasy_v04.safetensors": "AI-Porn/PornWorks-Hardcore-Fantasy-NSFW-3D-SDXL-Chekpoint",
61
  "UnfilteredAI_NSFW-GEN-ANIME-v2.safetensors": "AriaVale2/my-safetensors-backup",
62
  "Mala Anime Mix NSFW PonyXL.safetensors": "AriaVale2/my-safetensors-backup",
63
  "PornWorks_Anime_Desire_NSFW_Anime_Hentai_SDXL_Pony_Chekpoint.safetensors": "AriaVale2/my-safetensors-backup",
64
  }
65
 
66
  DIFFUSERS_MODELS = {
67
- "OhMyDaughter Beautiful Woman (Pony)": "John6666/omd-ohmaydaughter-beautiful-woman-pony-v10-sdxl",
68
  "OhMySister Beautiful Asian Woman": "John6666/oms-oh-my-sister-beautiful-asian-woman-v10-sdxl",
 
69
  "Fucktastic Anime Pony NSFW": "John6666/fucktastic-anime-checkpoint-pony-pdxl-porn-nsfw-sfw-22-sdxl",
70
  "Mala Anime Mix NSFW Pony v7": "John6666/mala-anime-mix-nsfw-pony-xl-v7-sdxl",
71
  "YAAI WAI NSFW Illustrious": "John6666/yaai-yet-another-anime-illustrious-model-full-fine-tune-based-on-wai-nsfw-v20-sdxl",
72
  "Flare Diffusion NSFW Anime": "John6666/flare-diffusion-nsfw-v16-semi-realism-anime-sdxl",
73
- "Perfect RSB Anime Cartoon Hentai": "John6666/perfect-rsb-mix-illustrious-real-anime-sfw-nsfw-definitive-iota-sdxl",
74
  "PornWorks Anime Desire (Diffusers)": "John6666/pornworks-anime-desire-nsfw-pony-sdxl",
75
- "NSFW Anime XL v1": "John6666/nsfw-anime-xl-v1-sdxl",
76
- "Coco Anime Pony Turnup": "John6666/coco-anime-pony-model-final-sfw-nsfw-turnup-sdxl",
77
- "Easy Full NSFW Pony": "John6666/easy-sfw-nsfw-pony-xl-fullnsfwv1-sdxl",
78
- "Perfect RSB Pony Real Anime": "John6666/perfect-rsb-mix-pony-real-anime-sfw-nsfw-lastversion-sdxl",
79
  }
80
 
81
- BACKUP_DATASET_ID = "JustinD/generation-images"
82
-
83
- LAST_GEN_TIME = {}
84
- COOLDOWN_SECONDS = 10
85
 
86
- # Stackable style packs (CheckboxGroup). Pick several at once.
87
- STYLE_KEYWORDS = {
88
  "Men Only": {
89
- "prefix": "for men's eyes only, hyper sexual pinup porn asina big tits, looking down her top, hungry male gaze with big tits, high angle looking down at her big tits and her top that is too small for her",
90
- "suffix": "deep cleavage focus, arched back, chest out and full tits, sexy eyes",
91
  },
92
- "Cartoon Slut": {
93
- "prefix": "score_9, score_8_up, score_7_up, source_anime, source_cartoon, rating_explicit, anime cartoon cel shading, thick ink lineart, glossy highlights",
94
- "suffix": "slutty pinup pose, exaggerated hourglass, large breasts, full breasts, heavy breasts, deep cleavage",
95
  },
96
- "Art Nude Meta": {
97
- "prefix": "classical art nude, museum erotic nude, adult East Asian woman early 40s, Chinese, Japanese, Korean",
98
- "suffix": "large breasts, full breasts, heavy breasts, bare full tits and breasts, masterpiece paint texture",
99
  },
100
  "Rembrandt Oil": {
101
- "prefix": "Rembrandt oil painting, warm Rembrandt side light, visible brushstrokes, rich brown shadows",
102
- "suffix": "classical academic nude energy, oil paint skin",
103
- },
104
- "Rubens Baroque": {
105
- "prefix": "Rubens baroque oil painting, lush soft flesh, warm golden glow, dramatic drapery",
106
- "suffix": "voluptuous hourglass, soft weighty breasts, old master nude",
107
- },
108
- "Caravaggio Light": {
109
- "prefix": "Caravaggio Baroque oil painting, dramatic chiaroscuro, candle lit, deep black background",
110
- "suffix": "spotlight on body, erotic old master composition",
111
- },
112
- "Ingres Salon": {
113
- "prefix": "Jean-Auguste-Dominique Ingres neoclassical oil painting, smooth porcelain skin, salon nude",
114
- "suffix": "elegant erotic academic pose, refined classical anatomy",
115
- },
116
- "Klimt Gold": {
117
- "prefix": "Gustav Klimt style, gold leaf patterns, ornate mosaic backdrop, Art Nouveau erotic",
118
- "suffix": "decorative gold frame energy, sensual patterned nude",
119
- },
120
- "Art Nouveau": {
121
- "prefix": "Alphonse Mucha Art Nouveau poster, gold ornate linework, floral decorative borders",
122
- "suffix": "elegant erotic illustration, flowing hair, classic poster nude",
123
- },
124
- "Titian Venetian": {
125
- "prefix": "Titian Venetian Renaissance oil, rich red drapery, warm skin glazes",
126
- "suffix": "reclining odalisque energy, classical erotic painting",
127
- },
128
- "Boucher Rococo": {
129
- "prefix": "Francois Boucher rococo oil, soft pastel light, silk pillows, playful erotic",
130
- "suffix": "flushed skin, intimate boudoir nude",
131
- },
132
- "Nude": {
133
- "prefix": "Full body",
134
- "suffix": "nude woman, large breasts, full breasts, heavy breasts, bare breasts",
135
- },
136
- "Editorial Lingerie": {
137
- "prefix": "photorealistic editorial fashion photo, dramatic side lighting, chiaroscuro",
138
- "suffix": "high-end lingerie campaign, soft warm interior background, sharp focus, cleavage",
139
- },
140
- "Erk Pin-up": {
141
- "prefix": "Erk IAart style, polished anime digital pin-up, clean lineart, soft airbrushed shading",
142
- "suffix": "hourglass figure, lace thigh highs, garter straps, soft warm lighting, large breasts, cleavage",
143
  },
144
  "4K TV Wide": {
145
- "prefix": "16:9 ultra wide composition, cinematic framing for 4K TV, razor sharp details, high contrast",
146
- "suffix": "clean edges when blown up large, living-room TV wall look",
147
- },
148
- "Harness Chains": {
149
- "prefix": "dark emerald leather body harness, thick straps, metal rings buckles, silver chains draped across chest and hips",
150
- "suffix": "harness framing and lifting breasts, oiled shiny skin, ultra detailed leather and chain metal",
151
  },
152
- "Library Sofa": {
153
- "prefix": "brown tufted leather Chesterfield sofa, busy classic library, floor to ceiling packed bookshelves, warm brass lamp, whiskey glass",
154
- "suffix": "soft golden light, ultra detailed books leather fabric skin",
155
  },
156
  }
157
 
158
- STYLE_CHOICES = list(STYLE_KEYWORDS.keys())
159
-
160
- DEFAULT_QUALITY_PREFIX = "(masterpiece huge tits:1.2), (best quality:1.2), (detailed face:1.2), beautifully framed, for men's eyes only"
161
- QUALITY_TAGS = (
162
- "perfect anatomy, proportional body, correct proportions, "
163
- "large breasts, full breasts, heavy breasts, cleavage, deep cleavage, "
164
- "perky breasts, round breasts, natural breasts, breasts straining fabric, "
165
- "tiny waist, wide hips, thick thighs, hourglass figure, "
166
- "ultra-detailed iris, sub-surface skin scattering, smooth detailed skin texture, "
167
- "seductive expression, bedroom eyes, parted glossy lips"
168
  )
169
 
170
  BASE_NEGATIVES = (
171
- "small tits, small breasts, small chest, flat chest, tiny breasts, "
172
- "bad hands, deformed hands, malformed hands, fused fingers, extra fingers, missing fingers, "
173
- "too many fingers, bad finger anatomy, webbed fingers, blurry hands, poorly drawn hands, "
174
- "bad anatomy, wrong anatomy, bad eyes, deformed eyes, mismatched eyes, dead eyes, empty eyes, "
175
- "blurry eyes, poorly drawn eyes, low quality eyes, extra limbs, extra arms, extra legs, mutated, "
176
- "disfigured, ugly, deformed, bad proportions, asymmetrical breasts, "
177
- "bad face, ugly face, blurry face, plastic skin, doll, barbie, child, loli, underage, censored, "
178
- "watermark, text, signature, grainy, noisy, lowres, worst quality, "
179
- "elongated torso, stretched body, distorted anatomy, long neck, deformed spine, elongated body"
180
  )
181
 
182
- MODEL_CHOICES = [
183
- "Fucktastic Anime Pony NSFW",
184
- "Mala Anime Mix NSFW Pony v7",
185
- "YAAI WAI NSFW Illustrious",
186
- "Flare Diffusion NSFW Anime",
187
- "Perfect RSB Anime Cartoon Hentai",
188
- "Perfect RSB Pony Real Anime",
189
- "PornWorks Anime Desire (Diffusers)",
190
- "NSFW Anime XL v1",
191
- "Coco Anime Pony Turnup",
192
- "Easy Full NSFW Pony",
193
- "OhMyDaughter Beautiful Woman (Pony)",
194
- "OhMySister Beautiful Asian Woman",
195
- "PornWorks_Anime_Desire_NSFW_Anime_Hentai_SDXL_Pony_Chekpoint.safetensors",
196
- "UnfilteredAI_NSFW-GEN-ANIME-v2.safetensors",
197
- "Mala Anime Mix NSFW PonyXL.safetensors",
198
- "pornworksSexyBeauty_v04.safetensors",
199
- "pornworksRealPornPhoto_v04.safetensors",
200
- "pornworksHardcoreFantasy_v04.safetensors",
201
- "CyberRealisticXLPlay_V10.0_FP16.safetensors",
202
- "Pony Diffusion V6 XL.safetensors",
203
- "Hassaku XL (Illustrious).safetensors",
204
- "animagine-xl-4.0.safetensors",
205
- "OnomaAIResearch_Illustrious-XL-v1.0.safetensors",
206
- "Juggernaut-XL_v9_RunDiffusionPhoto_v2.safetensors",
207
- "RealVisXL_V4.0.safetensors",
208
- ]
209
-
210
- BLEND_NONE = "(none - skip this chef)"
211
- MODEL_CHOICES_OPTIONAL = [BLEND_NONE] + MODEL_CHOICES
212
-
213
  RANDOM_PROMPTS = [
214
- "adult East Asian woman 20s early 20s, a lot of asians features, tit's face, bedroom sex eyes, parted glossy lips, long sleek black hair, extreme hourglass, tiny waist, wide hips, thick thighs, large breasts, full breasts, heavy breasts, bare breasts, erect nipples, classical art nude, reclining odalisque on silk drapery",
215
- "adult East Asian woman late 20s early 30s, Chinese Japanese Korean features, beautiful slutty face, wet parted lips, long messy black hair, extreme hourglass, large breasts, full breasts, heavy breasts, deep cleavage, modern erotic qipao ripped open, translucent lace, ultra high slit, oiled shiny skin, for men's eyes only",
216
- "adult East Asian woman, cartoon anime slut pinup, huge breasts, heavy cleavage, tiny waist, wide hips, sheer lingerie, shiny skin, looking at viewer, seductive smile, busy cluttered bedroom, male gaze low angle",
217
  ]
218
 
219
- # ===== LOAD RESTRICTED WORDS =====
 
 
220
  FORBIDDEN_KEYWORDS = []
221
  if os.path.exists("restricted_words.txt"):
222
  try:
@@ -227,17 +140,17 @@ if os.path.exists("restricted_words.txt"):
227
  if line.strip() and not line.strip().startswith("#")
228
  ]
229
  except Exception as e:
230
- logger.error(f"❌ Failed to load restricted words: {str(e)}")
231
 
232
  AGE_PATTERN = re.compile(
233
  r"\b(one|two|three|four|five|six|seven|eight|nine|ten|"
234
- r"eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen"
235
- r"|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17)"
236
  r"\s*[-\s]?\s*(year|yr|y\/o|yo)\b"
237
  )
238
 
239
 
240
- def check_prompt_safety(prompt):
241
  clean = prompt.lower().replace("-", " ").replace("_", " ")
242
  for word in FORBIDDEN_KEYWORDS:
243
  if word in clean:
@@ -248,15 +161,14 @@ def check_prompt_safety(prompt):
248
  return True, None
249
 
250
 
251
- def build_style_layers(selected_styles):
252
- """Stack multiple style packs into one prefix/suffix pair."""
253
- if not selected_styles:
254
  return "", ""
255
- if isinstance(selected_styles, str):
256
- selected_styles = [selected_styles]
257
  prefixes, suffixes = [], []
258
- for name in selected_styles:
259
- cfg = STYLE_KEYWORDS.get(name)
260
  if not cfg:
261
  continue
262
  if cfg.get("prefix"):
@@ -266,571 +178,358 @@ def build_style_layers(selected_styles):
266
  return ", ".join(prefixes), ", ".join(suffixes)
267
 
268
 
269
- def send_telegram_zipped_document(img_bytes, filename_base, username, model_name, prompt, seed, width, height, steps, cfg_scale):
270
- bot_token = os.environ.get("TELEGRAM_BOT_TOKEN")
271
- chat_id = os.environ.get("TELEGRAM_CHAT_ID")
272
- if not bot_token or not chat_id:
273
- return
274
- try:
275
- zip_buffer = io.BytesIO()
276
- with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
277
- zip_file.writestr(f"{filename_base}.webp", img_bytes)
278
- zip_buffer.seek(0)
279
- zip_data = zip_buffer.getvalue()
280
-
281
- safe_prompt = prompt if len(prompt) < 700 else prompt[:700] + "..."
282
- caption_text = (
283
- f"📦 ADULT AI ZIP ARCHIVE DISPATCH 📦\n"
284
- f"━━━━━━━━━━━━━━━━━━━\n"
285
- f"👤 User ID: {username} \n"
286
- f"🤖 Model: {model_name} \n"
287
- f"🎲 Seed Token: {seed} \n"
288
- f"📐 Resolution: {width}x{height} \n"
289
- f"📊 Configs: {steps} steps | CFG {cfg_scale}\n"
290
- f"━━━━━━━━━━━━━━━━━━━\n"
291
- f"💬 Prompt: \n {safe_prompt} "
292
- )
293
-
294
- boundary = "----AdultAIEngineBoundary" + str(int(time.time()))
295
- body = []
296
- parts = [
297
- ("chat_id", chat_id),
298
- ("caption", caption_text),
299
- ("parse_mode", "HTML"),
300
- ]
301
- for key, val in parts:
302
- body.append(f"--{boundary}".encode("utf-8"))
303
- body.append(f'Content-Disposition: form-data; name="{key}"'.encode("utf-8"))
304
- body.append(b"")
305
- body.append(str(val).encode("utf-8"))
306
-
307
- body.append(f"--{boundary}".encode("utf-8"))
308
- body.append(
309
- f'Content-Disposition: form-data; name="document"; filename="{filename_base}.zip"'.encode("utf-8")
310
- )
311
- body.append(b"Content-Type: application/zip")
312
- body.append(b"")
313
- body.append(zip_data)
314
- body.append(f"--{boundary}--".encode("utf-8"))
315
- body.append(b"")
316
-
317
- payload_body = b"\r\n".join(body)
318
- req = urllib.request.Request(
319
- f"https://api.telegram.org/bot{bot_token}/sendDocument",
320
- data=payload_body,
321
- method="POST",
322
- )
323
- req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}")
324
- req.add_header("Content-Length", str(len(payload_body)))
325
- with urllib.request.urlopen(req, timeout=15) as response:
326
- response.read()
327
- except Exception as e:
328
- logger.error(f"⚠️ Telegram dispatch failed: {str(e)}")
329
-
330
 
331
- def async_traffic_log_upload_worker(pil_img, seed, model_name, prompt, username, width, height, steps, cfg_scale):
332
  hf_token = os.environ.get("HF_TOKEN")
333
- if not username or username.strip() == "":
334
  username = "Unknown_Visitor"
335
-
336
  timestamp = int(time.time())
337
- clean_model_name = str(model_name).split(".")[0][:10]
338
- filename_base = f"gen_{timestamp}_seed_{seed}_{clean_model_name}"
339
-
340
- img_byte_arr = io.BytesIO()
341
- pil_img.save(img_byte_arr, format="WEBP", quality=95)
342
- raw_webp_bytes = img_byte_arr.getvalue()
343
-
344
- send_telegram_zipped_document(
345
- raw_webp_bytes, filename_base, username, model_name, prompt, seed, width, height, steps, cfg_scale
346
- )
347
-
348
  if not hf_token:
349
- logger.error(
350
- f"❌ HF_TOKEN not found in environment for {username}'s generation — upload skipped entirely (this is the silent failure)."
351
- )
352
  return
353
-
354
  api = HfApi(token=hf_token)
355
  date_str = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(timestamp))
356
  log_entry = f"[{date_str}] | User: {username} | Model: {model_name} | Seed: {seed} | Prompt: {prompt[:150]}...\n"
357
- user_log_filename = f"user_logs/{username}_history.txt"
358
-
359
- max_attempts = 4
360
- for attempt in range(1, max_attempts + 1):
361
  try:
362
  try:
363
- traffic_path = hf_hub_download(
364
- repo_id=BACKUP_DATASET_ID,
365
- filename="traffic_logs.txt",
366
- repo_type="dataset",
367
- token=hf_token,
368
- )
369
- with open(traffic_path, "r", encoding="utf-8") as f:
370
- current_logs = f.read()
371
  except Exception:
372
- current_logs = "=== TRAFFIC LOGS ===\n"
373
-
374
  try:
375
- user_log_path = hf_hub_download(
376
- repo_id=BACKUP_DATASET_ID,
377
- filename=user_log_filename,
378
- repo_type="dataset",
379
- token=hf_token,
380
- )
381
- with open(user_log_path, "r", encoding="utf-8") as f:
382
- user_logs = f.read()
383
  except Exception:
384
  user_logs = f"=== HISTORY FOR {username} ===\n"
385
-
386
- operations = [
387
- CommitOperationAdd(
388
- path_in_repo="traffic_logs.txt",
389
- path_or_fileobj=(current_logs + log_entry).encode("utf-8"),
390
- ),
391
- CommitOperationAdd(
392
- path_in_repo=user_log_filename,
393
- path_or_fileobj=(user_logs + log_entry).encode("utf-8"),
394
- ),
395
- CommitOperationAdd(
396
- path_in_repo=f"data/{filename_base}.webp",
397
- path_or_fileobj=raw_webp_bytes,
398
- ),
399
  ]
400
-
401
  api.create_commit(
402
  repo_id=BACKUP_DATASET_ID,
403
  repo_type="dataset",
404
- operations=operations,
405
  commit_message=f"gen for {username} @ {timestamp}",
406
  )
407
- logger.info(f"☁️ Cloud dataset completely updated for {username}. (attempt {attempt})")
408
  return
409
- except HfHubHTTPError as e:
410
- status = getattr(e.response, "status_code", None)
411
- if status == 429:
412
- wait = 2**attempt
413
- logger.error(
414
- f"⚠️ Rate limited (429) on attempt {attempt}/{max_attempts} for {username}. Retrying in {wait}s."
415
- )
416
- time.sleep(wait)
417
- continue
418
- logger.error(f"⚠️ Storage worker HTTP error ({status}) for {username}: {str(e)}")
419
- time.sleep(1.5 * attempt)
420
- continue
421
  except Exception as e:
422
- logger.error(f"⚠️ Storage worker failed for {username} on attempt {attempt}: {str(e)}")
423
  time.sleep(1.5 * attempt)
424
- continue
425
 
426
- logger.error(f"❌ Storage worker gave up after {max_attempts} attempts for {username}. Entry NOT saved.")
427
-
428
-
429
- pipeline = None
430
- device = None
431
- current_loaded_model = None
432
-
433
-
434
- def get_infinite_embeddings(pipe, prompt, negative_prompt):
435
- pos_ids_1 = pipe.tokenizer(prompt, return_tensors="pt", truncation=False).input_ids.to(device)
436
- neg_ids_1 = pipe.tokenizer(negative_prompt, return_tensors="pt", truncation=False).input_ids.to(device)
437
- pos_ids_2 = pipe.tokenizer_2(prompt, return_tensors="pt", truncation=False).input_ids.to(device)
438
- neg_ids_2 = pipe.tokenizer_2(negative_prompt, return_tensors="pt", truncation=False).input_ids.to(device)
439
-
440
- max_len = max(pos_ids_1.shape[1], neg_ids_1.shape[1], pos_ids_2.shape[1], neg_ids_2.shape[1])
441
- num_chunks = (max_len - 2 + 74) // 75
442
- if num_chunks == 0:
443
- num_chunks = 1
444
-
445
- def chunk_and_pad_tokens(token_ids, tokenizer, target_chunks):
446
- raw_ids = token_ids[0, 1:-1].tolist()
447
- chunks_list = []
448
- for idx in range(target_chunks):
449
- start_i = idx * 75
450
- end_i = start_i + 75
451
- chunk_slice = raw_ids[start_i:end_i]
452
- formatted_chunk = [tokenizer.bos_token_id] + chunk_slice + [tokenizer.eos_token_id]
453
- padding_needed = 77 - len(formatted_chunk)
454
- if padding_needed > 0:
455
- formatted_chunk += [tokenizer.pad_token_id] * padding_needed
456
- chunks_list.append(formatted_chunk)
457
- return torch.tensor(chunks_list, dtype=torch.long, device=device)
458
-
459
- pos_pieces_1 = chunk_and_pad_tokens(pos_ids_1, pipe.tokenizer, num_chunks)
460
- neg_pieces_1 = chunk_and_pad_tokens(neg_ids_1, pipe.tokenizer, num_chunks)
461
- pos_pieces_2 = chunk_and_pad_tokens(pos_ids_2, pipe.tokenizer_2, num_chunks)
462
- neg_pieces_2 = chunk_and_pad_tokens(neg_ids_2, pipe.tokenizer_2, num_chunks)
463
-
464
- pos_embeds_1_list, neg_embeds_1_list = [], []
465
- pos_embeds_2_list, neg_embeds_2_list = [], []
466
- pooled_pos_list, pooled_neg_list = [], []
467
-
468
- for i in range(num_chunks):
469
- out_p1 = pipe.text_encoder(pos_pieces_1[i : i + 1], output_hidden_states=True)
470
- out_n1 = pipe.text_encoder(neg_pieces_1[i : i + 1], output_hidden_states=True)
471
- pos_embeds_1_list.append(out_p1.hidden_states[-2])
472
- neg_embeds_1_list.append(out_n1.hidden_states[-2])
473
-
474
- out_p2 = pipe.text_encoder_2(pos_pieces_2[i : i + 1], output_hidden_states=True)
475
- out_n2 = pipe.text_encoder_2(neg_pieces_2[i : i + 1], output_hidden_states=True)
476
- pos_embeds_2_list.append(out_p2.hidden_states[-2])
477
- neg_embeds_2_list.append(out_n2.hidden_states[-2])
478
- pooled_pos_list.append(out_p2.text_embeds)
479
- pooled_neg_list.append(out_n2.text_embeds)
480
-
481
- pos_embeds_1 = torch.cat(pos_embeds_1_list, dim=1)
482
- neg_embeds_1 = torch.cat(neg_embeds_1_list, dim=1)
483
- pos_embeds_2 = torch.cat(pos_embeds_2_list, dim=1)
484
- neg_embeds_2 = torch.cat(neg_embeds_2_list, dim=1)
485
-
486
- prompt_embeds = torch.cat([pos_embeds_1, pos_embeds_2], dim=-1)
487
- negative_prompt_embeds = torch.cat([neg_embeds_1, neg_embeds_2], dim=-1)
488
- return prompt_embeds, pooled_pos_list[0], negative_prompt_embeds, pooled_neg_list[0]
489
-
490
-
491
- def initialize_model(model_name):
492
- global pipeline, device, current_loaded_model
493
- if current_loaded_model == model_name and pipeline is not None:
494
- return True
495
- try:
496
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
497
- if pipeline is not None:
498
- del pipeline
499
- pipeline = None
500
- gc.collect()
501
- if torch.cuda.is_available():
502
- torch.cuda.empty_cache()
503
-
504
- dtype = torch.float16 if torch.cuda.is_available() else torch.float32
505
-
506
- if model_name in DIFFUSERS_MODELS:
507
- repo_id = DIFFUSERS_MODELS[model_name]
508
- pipeline = StableDiffusionXLPipeline.from_pretrained(
509
- repo_id,
510
- torch_dtype=dtype,
511
- use_safetensors=True,
512
- )
513
- else:
514
- repo_id = MODEL_REPOS.get(model_name, HF_REPO_ID)
515
- local_model_path = hf_hub_download(repo_id=repo_id, filename=model_name)
516
- pipeline = StableDiffusionXLPipeline.from_single_file(
517
- local_model_path,
518
- torch_dtype=dtype,
519
- use_safetensors=True,
520
- safety_checker=None,
521
- requires_safety_checker=False,
522
- )
523
 
524
- if hasattr(pipeline, "scheduler"):
525
- pipeline.scheduler = EulerDiscreteScheduler.from_config(
526
- pipeline.scheduler.config,
527
- timestep_spacing="trailing",
528
- )
529
- pipeline = pipeline.to(device)
530
- if torch.cuda.is_available():
531
- pipeline.vae.enable_slicing()
532
- pipeline.vae.enable_tiling()
533
- current_loaded_model = model_name
534
- return True
535
- except Exception as e:
536
- logger.error(f"❌ CRITICAL LOAD ERROR for {model_name}: {str(e)}")
537
- return False
538
-
539
-
540
- def remix_pass(base_images, chef_name, strength, enhanced_prompt, final_negative, steps, cfg_scale, seed_offset):
541
- """Load chef and img2img-remix a list of (pil, seed)."""
542
- strength = max(0.05, min(0.95, float(strength)))
543
- if not initialize_model(chef_name):
544
- raise RuntimeError(f"Failed to load remix chef: {chef_name}")
545
-
546
- img2img = StableDiffusionXLImg2ImgPipeline(
547
- vae=pipeline.vae,
548
- text_encoder=pipeline.text_encoder,
549
- text_encoder_2=pipeline.text_encoder_2,
550
- tokenizer=pipeline.tokenizer,
551
- tokenizer_2=pipeline.tokenizer_2,
552
- unet=pipeline.unet,
553
- scheduler=pipeline.scheduler,
554
- )
555
- img2img = img2img.to(device)
556
- pe, ppe, npe, nppe = get_infinite_embeddings(pipeline, enhanced_prompt, final_negative)
557
- remix_steps = max(10, int(int(steps) * 0.7))
558
- remixed = []
559
- for img, current_seed in base_images:
560
- generator = torch.Generator(device).manual_seed(int(current_seed) + int(seed_offset))
561
- out = img2img(
562
- image=img,
563
- prompt_embeds=pe,
564
- pooled_prompt_embeds=ppe,
565
- negative_prompt_embeds=npe,
566
- negative_pooled_prompt_embeds=nppe,
567
- strength=strength,
568
- num_inference_steps=remix_steps,
569
- guidance_scale=float(cfg_scale),
570
- generator=generator,
571
- output_type="pil",
572
- ).images[0]
573
- remixed.append((out, current_seed))
574
- del img2img
575
  gc.collect()
576
  if torch.cuda.is_available():
577
  torch.cuda.empty_cache()
578
- return remixed
579
 
580
 
581
- @spaces.GPU(duration=240) if SPACES_AVAILABLE else (lambda f: f)
582
- def generate_image(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
583
  prompt,
584
  styles,
585
- negative_prompt,
 
586
  steps,
587
- cfg_scale,
588
  seed,
589
- num_images,
590
  width,
591
  height,
592
- model_a,
593
- model_b,
594
- blend_b,
595
- model_c,
596
- blend_c,
597
  username,
598
  ):
599
- if not prompt:
600
- return None, "", "❌ Prompt missing", []
601
 
602
  now = time.time()
603
- user_key = username if username else "anonymous"
604
- if user_key in LAST_GEN_TIME:
605
- elapsed = now - LAST_GEN_TIME[user_key]
606
- if elapsed < COOLDOWN_SECONDS:
607
- remaining = int(COOLDOWN_SECONDS - elapsed)
608
- gr.Warning(f"⏳ Slow down! Cooldown active. Please wait {remaining} seconds.")
609
- return None, f"⏳ Cooldown active. Wait {remaining}s.", f"Anti-Spam Block: {remaining}s remaining", []
610
-
611
- is_safe, flagged_term = check_prompt_safety(prompt)
612
- if not is_safe:
613
- gr.Warning("❌ Request denied. Restricted content detected.")
614
- return None, "❌ Restricted content detected.", "Content Policy Violation", []
615
-
616
- num_images = int(max(1, min(3, int(num_images))))
617
-
618
- if not initialize_model(model_a):
619
- return None, "", f"❌ Failed to load Chef A: {model_a}", []
620
-
621
- use_b = model_b and model_b != BLEND_NONE and model_b != model_a and float(blend_b) > 0.02
622
- use_c = model_c and model_c != BLEND_NONE and model_c != model_a and float(blend_c) > 0.02
623
- if use_c and use_b and model_c == model_b:
624
- # still ok, second pass with same chef; allow it
625
- pass
 
626
 
627
  try:
628
- LAST_GEN_TIME[user_key] = now
629
- seeds = [random.randint(0, 99999999) if seed == -1 else int(seed) + i for i in range(num_images)]
630
- style_prefix, style_suffix = build_style_layers(styles)
631
-
632
- components = [
633
- DEFAULT_QUALITY_PREFIX,
634
- style_prefix,
635
- prompt.strip(),
636
- style_suffix,
637
- QUALITY_TAGS,
638
- ]
639
- enhanced_prompt = ", ".join([x for x in components if x])
640
-
641
- final_negative = BASE_NEGATIVES
642
- if negative_prompt.strip():
643
- final_negative = f"{BASE_NEGATIVES}, {negative_prompt.strip()}"
644
-
645
- prompt_embeds, pooled_prompt_embeds, negative_prompt_embeds, negative_pooled_prompt_embeds = get_infinite_embeddings(
646
- pipeline, enhanced_prompt, final_negative
647
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
648
 
649
- base_images = []
650
- for current_seed in seeds:
651
- generator = torch.Generator(device).manual_seed(current_seed)
652
- out = pipeline(
653
- prompt_embeds=prompt_embeds,
654
- pooled_prompt_embeds=pooled_prompt_embeds,
655
- negative_prompt_embeds=negative_prompt_embeds,
656
- negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,
657
- num_inference_steps=int(steps),
658
- guidance_scale=float(cfg_scale),
659
- width=int(width),
660
- height=int(height),
661
- generator=generator,
662
- output_type="pil",
663
- ).images[0]
664
- base_images.append((out, current_seed))
665
-
666
- chain = [f"A:{model_a}"]
667
- if use_b:
668
- base_images = remix_pass(
669
- base_images, model_b, blend_b, enhanced_prompt, final_negative, steps, cfg_scale, seed_offset=17
670
- )
671
- chain.append(f"B:{model_b}@{float(blend_b):.2f}")
672
- if use_c:
673
- # if C equals current loaded B, still re-run remix with new strength
674
- base_images = remix_pass(
675
- base_images, model_c, blend_c, enhanced_prompt, final_negative, steps, cfg_scale, seed_offset=31
676
- )
677
- chain.append(f"C:{model_c}@{float(blend_c):.2f}")
678
-
679
- gallery_items = []
680
- upload_jobs = []
681
- log_model = " > ".join(chain)
682
- for out, current_seed in base_images:
683
- current_time = datetime.now().strftime("%I:%M:%S %p")
684
- display_label = f"✨ Seed: {current_seed} | {log_model} | ⏱️ {current_time}"
685
- gallery_items.append((out, display_label))
686
- upload_jobs.append((out, current_seed, log_model, prompt, username, width, height, steps, cfg_scale))
687
-
688
- style_note = ", ".join(styles) if isinstance(styles, (list, tuple)) else str(styles)
689
- gen_info = f"Seeds: {seeds} | {width}x{height} | {log_model} | styles: {style_note}"
690
- return gallery_items, gen_info, enhanced_prompt, upload_jobs
691
- except Exception as e:
692
- logger.error(f"❌ GENERATION CRASH: {str(e)}")
693
- return None, "", f"❌ Crashed: {str(e)}", []
694
 
 
 
 
 
 
 
695
 
696
- js_theme_injector = """
697
- function() {
698
- const formatDark = () => {
699
- document.documentElement.classList.add('dark');
700
- document.body.classList.add('dark');
701
- const root = document.querySelector(':root');
702
- if(root) {
703
- root.style.setProperty('--background-fill-primary', '#0b0f19');
704
- root.style.setProperty('--background-fill-secondary', '#111827');
705
- root.style.setProperty('--block-background-fill', '#111827');
706
- root.style.setProperty('--body-text-color', '#f3f4f6');
707
- root.style.setProperty('--border-color-primary', '#1f2937');
708
- }
709
- };
710
- formatDark();
711
- setTimeout(formatDark, 500);
712
- setTimeout(formatDark, 2000);
713
- }
714
- """
715
 
716
- js_get_visitor = """
 
 
 
717
  function() {
718
- let visitor_id = localStorage.getItem('adult_ai_visitor_id');
719
- if (!visitor_id) {
720
- let randomNum = Math.floor(1000 + Math.random() * 9000);
721
- visitor_id = "Visitor_" + randomNum;
722
- localStorage.setItem('adult_ai_visitor_id', visitor_id);
723
- }
724
- return visitor_id;
725
  }
726
  """
727
 
728
  css = """
729
- .gradio-container, html, body, .dark { background-color: #0b0b19 !important; color: #f3f4f6 !important; }
730
- .form, .block, .gr-box, .fieldset { background-color: #111827 !important; border: 1px solid #1f2937 !important; padding: 14px !important; border-radius: 8px !important; margin-bottom: 12px !important; }
731
- span, label, p, h1, h2, h3, .gr-label, .meta-text { color: #f3f4f6 !important; font-weight: 600 !important; }
732
- input, textarea, select { background-color: #1f2937 !important; color: #ffffff !important; border: 1px solid #374151 !important; border-radius: 6px !important; padding: 8px !important; }
733
- div[role="listbox"], .dropdown-menu, .v-select, select { background-color: #1f2937 !important; background: #1f2937 !important; border: 1px solid #4b5563 !important; color: #ffffff !important; z-index: 99999 !important; }
734
- option, .dark option { background-color: #1f2937 !important; color: #ffffff !important; }
735
- .gradio-gallery, .image-output, .image-container {
736
- max-height: 70vh !important;
737
- overflow-y: auto !important;
738
- }
739
  """
740
 
741
  THEME = gr.themes.Default(primary_hue="purple", secondary_hue="indigo", neutral_hue="slate")
742
 
743
 
744
- def create_interface():
745
- with gr.Blocks() as interface:
746
- user_id_store = gr.Textbox(visible=False, elem_id="user_id_store")
747
-
748
- gr.Markdown("# 🔥 MEN ONLY — Mad Mode")
749
  gr.Markdown(
750
- "### ⚠️ 18+ · stack styles · Chef A draws · optional B then C remix · batch up to 3\\n more porn yahoo.ca"
751
- "Tip: 3 chefs + batch 3 is heavy on ZeroGPU. Start with batch 1 while testing blends."
 
752
  )
753
 
754
  with gr.Row():
755
  with gr.Column(scale=2):
756
- prompt_input = gr.Textbox(
757
- label="Prompt (what you want her to look like)",
758
- value=random.choice(RANDOM_PROMPTS),
759
- lines=8,
 
 
760
  )
761
- negative_prompt_input = gr.Textbox(
762
- label="Negatives (stuff to kill)",
763
- value=BASE_NEGATIVES,
764
- lines=5,
765
- )
766
- style_input = gr.CheckboxGroup(
767
- label="Style packs (stack as many as you want)",
768
- choices=STYLE_CHOICES,
769
- value=["Men Only", "Art Nude Meta", "Rembrandt Oil"],
770
- )
771
- generate_button = gr.Button("GENERATE — HIT IT", variant="primary", size="lg")
772
-
773
  with gr.Column(scale=1):
774
- model_a_input = gr.Dropdown(label="Chef A (base draw)", choices=MODEL_CHOICES, value="pornworksSexyBeauty_v04.safetensors")
775
- model_b_input = gr.Dropdown(label="Chef B (remix 1)", choices=MODEL_CHOICES_OPTIONAL, value=BLEND_NONE)
776
- blend_b_input = gr.Slider(label="B blend", minimum=0.0, maximum=0.85, value=0.35, step=0.05)
777
- model_c_input = gr.Dropdown(label="Chef C (remix 2)", choices=MODEL_CHOICES_OPTIONAL, value=BLEND_NONE)
778
- blend_c_input = gr.Slider(label="C blend", minimum=0.0, maximum=0.85, value=0.25, step=0.05)
779
- seed_input = gr.Number(label="Seed (-1 = random)", value=-1, precision=0)
780
- width_input = gr.Slider(label="Width", minimum=512, maximum=1536, value=1024, step=64)
781
- height_input = gr.Slider(label="Height", minimum=512, maximum=1536, value=1024, step=64)
782
- steps_input = gr.Slider(label="Steps", minimum=10, maximum=40, value=28, step=1)
783
- cfg_input = gr.Slider(label="CFG", minimum=1.0, maximum=12.0, value=6.0, step=0.5)
784
- num_images_input = gr.Slider(label="Batch (up to 3 at once)", minimum=1, maximum=3, value=1, step=1)
785
-
786
- with gr.Row():
787
- image_output = gr.Gallery(label="Outputs", columns=3, height="auto")
788
-
789
- generation_info = gr.Textbox(label="Metadata Engine Logs", visible=False)
790
- metadata_display = gr.Textbox(label="Properties Dump", visible=False)
791
-
792
- interface.load(fn=None, inputs=None, outputs=user_id_store, js=js_get_visitor)
793
-
794
- def on_generate(*args):
795
- images, info, metadata, upload_jobs = generate_image(*args)
796
- for job in upload_jobs:
797
- threading.Thread(target=async_traffic_log_upload_worker, args=job, daemon=True).start()
798
- if images is not None:
799
- return images, info, metadata, gr.update(visible=True, value=info), gr.update(visible=True, value=metadata)
800
- return None, info, "", gr.update(visible=False), gr.update(visible=False)
801
-
802
- generate_button.click(
803
- fn=on_generate,
804
- inputs=[
805
- prompt_input,
806
- style_input,
807
- negative_prompt_input,
808
- steps_input,
809
- cfg_input,
810
- seed_input,
811
- num_images_input,
812
- width_input,
813
- height_input,
814
- model_a_input,
815
- model_b_input,
816
- blend_b_input,
817
- model_c_input,
818
- blend_c_input,
819
- user_id_store,
820
- ],
821
- outputs=[image_output, generation_info, metadata_display, generation_info, metadata_display],
822
  )
823
- return interface
824
 
825
 
826
  if __name__ == "__main__":
827
- os.environ["GRADIO_WATCH_DIRS"] = ""
828
  os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
829
- app = create_interface()
830
- app.queue(max_size=10)
831
- app.launch(
832
- ssr_mode=False,
833
- theme=THEME,
834
- css=css,
835
- js=js_theme_injector,
836
- )
 
11
  import os
12
  import re
13
  import random
 
14
  import gc
15
  import logging
16
  import time
17
  import io
18
  import threading
 
 
19
  from datetime import datetime
20
+
21
+ import torch
 
 
 
 
 
22
  from PIL import Image
23
  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")
38
  if hasattr(sys.stderr, "reconfigure"):
39
  sys.stderr.reconfigure(encoding="utf-8")
40
 
41
+ BACKUP_DATASET_ID = "JustinD/generation-images"
42
+ LAST_GEN_TIME = {}
43
+ COOLDOWN_SECONDS = 8
44
 
45
+ # ---------------------------------------------------------------------------
46
+ # ENGINES
47
+ # ---------------------------------------------------------------------------
48
+ ENGINE_FLUX = "FLUX.1 Schnell (latest fast)"
49
+ ENGINE_SD35 = "SD 3.5 Medium (latest quality)"
50
+ ENGINE_SDXL = "Adult SDXL (NSFW chefs)"
51
+
52
+ ENGINE_CHOICES = [ENGINE_FLUX, ENGINE_SD35, ENGINE_SDXL]
53
+
54
+ # Adult SDXL single-file + Diffusers (proven Justin lane)
55
+ HF_REPO_ID = "AriaVale2/my-safetensors-backup"
56
  MODEL_REPOS = {
57
+ "pornworksSexyBeauty_v04.safetensors": "AI-Porn/PornWorks-Sexy-Beauty-Aesthetic-Erotic-NSFW-SDXL-Chekpoint",
58
+ "pornworksRealPornPhoto_v04.safetensors": "Manjushri/pornworks-real-porn-photo-realistic-nsfw-sdxl-and-pony-chekpoint",
59
+ "pornworksHardcoreFantasy_v04.safetensors": "AI-Porn/PornWorks-Hardcore-Fantasy-NSFW-3D-SDXL-Chekpoint",
60
+ "CyberRealisticXLPlay_V10.0_FP16.safetensors": "cyberdelia/CyberRealisticXL",
61
  "Juggernaut-XL_v9_RunDiffusionPhoto_v2.safetensors": "saber-end/Juggernaut-XL-v9",
62
  "RealVisXL_V4.0.safetensors": "SG161222/RealVisXL_V4.0",
 
 
 
63
  "Pony Diffusion V6 XL.safetensors": "AriaVale2/my-safetensors-backup",
64
  "Hassaku XL (Illustrious).safetensors": "AriaVale2/my-safetensors-backup",
65
+ "animagine-xl-4.0.safetensors": "cagliostrolab/animagine-xl-4.0",
66
+ "OnomaAIResearch_Illustrious-XL-v1.0.safetensors": "AriaVale2/my-safetensors-backup",
 
67
  "UnfilteredAI_NSFW-GEN-ANIME-v2.safetensors": "AriaVale2/my-safetensors-backup",
68
  "Mala Anime Mix NSFW PonyXL.safetensors": "AriaVale2/my-safetensors-backup",
69
  "PornWorks_Anime_Desire_NSFW_Anime_Hentai_SDXL_Pony_Chekpoint.safetensors": "AriaVale2/my-safetensors-backup",
70
  }
71
 
72
  DIFFUSERS_MODELS = {
 
73
  "OhMySister Beautiful Asian Woman": "John6666/oms-oh-my-sister-beautiful-asian-woman-v10-sdxl",
74
+ "OhMyDaughter Beautiful Woman (Pony)": "John6666/omd-ohmaydaughter-beautiful-woman-pony-v10-sdxl",
75
  "Fucktastic Anime Pony NSFW": "John6666/fucktastic-anime-checkpoint-pony-pdxl-porn-nsfw-sfw-22-sdxl",
76
  "Mala Anime Mix NSFW Pony v7": "John6666/mala-anime-mix-nsfw-pony-xl-v7-sdxl",
77
  "YAAI WAI NSFW Illustrious": "John6666/yaai-yet-another-anime-illustrious-model-full-fine-tune-based-on-wai-nsfw-v20-sdxl",
78
  "Flare Diffusion NSFW Anime": "John6666/flare-diffusion-nsfw-v16-semi-realism-anime-sdxl",
 
79
  "PornWorks Anime Desire (Diffusers)": "John6666/pornworks-anime-desire-nsfw-pony-sdxl",
 
 
 
 
80
  }
81
 
82
+ SDXL_CHOICES = list(DIFFUSERS_MODELS.keys()) + list(MODEL_REPOS.keys())
 
 
 
83
 
84
+ STYLE_PACKS = {
 
85
  "Men Only": {
86
+ "prefix": "for men's eyes only, hyper sexual pinup, hungry male gaze",
87
+ "suffix": "deep cleavage, arched back, bedroom eyes, wet parted lips",
88
  },
89
+ "Art Nude": {
90
+ "prefix": "classical art nude, museum erotic nude",
91
+ "suffix": "bare breasts, soft weight, masterpiece lighting",
92
  },
93
+ "Cartoon Slut": {
94
+ "prefix": "score_9, score_8_up, source_anime, rating_explicit, anime cartoon cel shading",
95
+ "suffix": "slutty pinup, exaggerated hourglass, heavy cleavage",
96
  },
97
  "Rembrandt Oil": {
98
+ "prefix": "Rembrandt oil painting, warm side light, visible brushstrokes",
99
+ "suffix": "classical academic nude energy",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  },
101
  "4K TV Wide": {
102
+ "prefix": "16:9 ultra wide cinematic framing for 4K TV",
103
+ "suffix": "razor sharp when blown up large",
 
 
 
 
104
  },
105
+ "Photo Gloss": {
106
+ "prefix": "photorealistic, wet shiny skin, editorial flash",
107
+ "suffix": "ultra detailed skin pores and fabric",
108
  },
109
  }
110
 
111
+ DEFAULT_QUALITY = (
112
+ "masterpiece, best quality, detailed face, "
113
+ "adult Korean woman late 20s, beautiful Korean face, "
114
+ "large breasts, full breasts, heavy breasts, cleavage, "
115
+ "tiny waist, wide hips, hourglass figure"
 
 
 
 
 
116
  )
117
 
118
  BASE_NEGATIVES = (
119
+ "child, loli, underage, teen, young, "
120
+ "small breasts, flat chest, bad hands, extra fingers, deformed, "
121
+ "blurry, lowres, watermark, text, censored, ugly"
 
 
 
 
 
 
122
  )
123
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  RANDOM_PROMPTS = [
125
+ "adult Korean woman late 20s, beautiful Korean face, bedroom eyes, parted glossy lips, long wavy black hair, extreme hourglass, large breasts, full breasts, heavy breasts, bare breasts, soft weight, classical art nude, warm Rembrandt light",
126
+ "adult Korean woman late 20s, tight black crop top no bra, tiny skirt, deep cleavage, wet shiny skin, luxury condo night, for men's eyes only",
127
+ "adult Korean woman late 20s, anime cartoon slut pinup, huge breasts, heavy cleavage, sheer lingerie, looking at viewer, busy bedroom",
128
  ]
129
 
130
+ # ---------------------------------------------------------------------------
131
+ # SAFETY
132
+ # ---------------------------------------------------------------------------
133
  FORBIDDEN_KEYWORDS = []
134
  if os.path.exists("restricted_words.txt"):
135
  try:
 
140
  if line.strip() and not line.strip().startswith("#")
141
  ]
142
  except Exception as e:
143
+ logger.error(f"restricted words load failed: {e}")
144
 
145
  AGE_PATTERN = re.compile(
146
  r"\b(one|two|three|four|five|six|seven|eight|nine|ten|"
147
+ r"eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|"
148
+ r"1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17)"
149
  r"\s*[-\s]?\s*(year|yr|y\/o|yo)\b"
150
  )
151
 
152
 
153
+ def check_prompt_safety(prompt: str):
154
  clean = prompt.lower().replace("-", " ").replace("_", " ")
155
  for word in FORBIDDEN_KEYWORDS:
156
  if word in clean:
 
161
  return True, None
162
 
163
 
164
+ def build_styles(selected):
165
+ if not selected:
 
166
  return "", ""
167
+ if isinstance(selected, str):
168
+ selected = [selected]
169
  prefixes, suffixes = [], []
170
+ for name in selected:
171
+ cfg = STYLE_PACKS.get(name)
172
  if not cfg:
173
  continue
174
  if cfg.get("prefix"):
 
178
  return ", ".join(prefixes), ", ".join(suffixes)
179
 
180
 
181
+ # ---------------------------------------------------------------------------
182
+ # LOGGING UPLOAD (same pantry)
183
+ # ---------------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
+ def async_log_upload(pil_img, seed, model_name, prompt, username, width, height, steps, cfg_scale):
186
  hf_token = os.environ.get("HF_TOKEN")
187
+ if not username or not str(username).strip():
188
  username = "Unknown_Visitor"
 
189
  timestamp = int(time.time())
190
+ clean = str(model_name).replace("/", "_")[:18]
191
+ filename_base = f"gen_{timestamp}_seed_{seed}_{clean}"
192
+ buf = io.BytesIO()
193
+ pil_img.save(buf, format="WEBP", quality=95)
194
+ raw = buf.getvalue()
 
 
 
 
 
 
195
  if not hf_token:
196
+ logger.error("HF_TOKEN missing — log upload skipped")
 
 
197
  return
 
198
  api = HfApi(token=hf_token)
199
  date_str = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(timestamp))
200
  log_entry = f"[{date_str}] | User: {username} | Model: {model_name} | Seed: {seed} | Prompt: {prompt[:150]}...\n"
201
+ user_log = f"user_logs/{username}_history.txt"
202
+ for attempt in range(1, 4):
 
 
203
  try:
204
  try:
205
+ path = hf_hub_download(BACKUP_DATASET_ID, "traffic_logs.txt", repo_type="dataset", token=hf_token)
206
+ current = open(path, encoding="utf-8").read()
 
 
 
 
 
 
207
  except Exception:
208
+ current = "=== TRAFFIC LOGS ===\n"
 
209
  try:
210
+ upath = hf_hub_download(BACKUP_DATASET_ID, user_log, repo_type="dataset", token=hf_token)
211
+ user_logs = open(upath, encoding="utf-8").read()
 
 
 
 
 
 
212
  except Exception:
213
  user_logs = f"=== HISTORY FOR {username} ===\n"
214
+ ops = [
215
+ CommitOperationAdd(path_in_repo="traffic_logs.txt", path_or_fileobj=(current + log_entry).encode()),
216
+ CommitOperationAdd(path_in_repo=user_log, path_or_fileobj=(user_logs + log_entry).encode()),
217
+ CommitOperationAdd(path_in_repo=f"data/{filename_base}.webp", path_or_fileobj=raw),
 
 
 
 
 
 
 
 
 
 
218
  ]
 
219
  api.create_commit(
220
  repo_id=BACKUP_DATASET_ID,
221
  repo_type="dataset",
222
+ operations=ops,
223
  commit_message=f"gen for {username} @ {timestamp}",
224
  )
225
+ logger.info(f"☁️ logged {username} attempt {attempt}")
226
  return
 
 
 
 
 
 
 
 
 
 
 
 
227
  except Exception as e:
228
+ logger.error(f"log fail {attempt}: {e}")
229
  time.sleep(1.5 * attempt)
 
230
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
 
232
+ # ---------------------------------------------------------------------------
233
+ # PIPELINE CACHE
234
+ # ---------------------------------------------------------------------------
235
+ _pipes = {}
236
+ _current_key = None
237
+
238
+
239
+ def _dtype():
240
+ if torch.cuda.is_available():
241
+ # Flux prefers bfloat16 when available
242
+ return torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
243
+ return torch.float32
244
+
245
+
246
+ def unload_all():
247
+ global _pipes, _current_key
248
+ _pipes.clear()
249
+ _current_key = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  gc.collect()
251
  if torch.cuda.is_available():
252
  torch.cuda.empty_cache()
 
253
 
254
 
255
+ def get_flux_pipe():
256
+ key = "flux-schnell"
257
+ if key in _pipes:
258
+ return _pipes[key]
259
+ from diffusers import FluxPipeline
260
+
261
+ unload_all()
262
+ logger.info("Loading FLUX.1 Schnell…")
263
+ pipe = FluxPipeline.from_pretrained(
264
+ "black-forest-labs/FLUX.1-schnell",
265
+ torch_dtype=_dtype(),
266
+ )
267
+ if torch.cuda.is_available():
268
+ pipe.to("cuda")
269
+ _pipes[key] = pipe
270
+ return pipe
271
+
272
+
273
+ def get_sd35_pipe():
274
+ key = "sd35-medium"
275
+ if key in _pipes:
276
+ return _pipes[key]
277
+ from diffusers import StableDiffusion3Pipeline
278
+
279
+ unload_all()
280
+ logger.info("Loading SD 3.5 Medium…")
281
+ # May require accepting the model license on HF while logged in (Space has hf_oauth)
282
+ pipe = StableDiffusion3Pipeline.from_pretrained(
283
+ "stabilityai/stable-diffusion-3.5-medium",
284
+ torch_dtype=_dtype(),
285
+ )
286
+ if torch.cuda.is_available():
287
+ pipe.to("cuda")
288
+ _pipes[key] = pipe
289
+ return pipe
290
+
291
+
292
+ def get_sdxl_pipe(model_name: str):
293
+ key = f"sdxl:{model_name}"
294
+ if key in _pipes:
295
+ return _pipes[key]
296
+ from diffusers import StableDiffusionXLPipeline, EulerDiscreteScheduler
297
+
298
+ # Keep only one heavy pipe resident
299
+ unload_all()
300
+ dtype = torch.float16 if torch.cuda.is_available() else torch.float32
301
+ logger.info(f"Loading Adult SDXL chef: {model_name}")
302
+ if model_name in DIFFUSERS_MODELS:
303
+ pipe = StableDiffusionXLPipeline.from_pretrained(
304
+ DIFFUSERS_MODELS[model_name],
305
+ torch_dtype=dtype,
306
+ use_safetensors=True,
307
+ )
308
+ else:
309
+ repo = MODEL_REPOS.get(model_name, HF_REPO_ID)
310
+ path = hf_hub_download(repo_id=repo, filename=model_name)
311
+ pipe = StableDiffusionXLPipeline.from_single_file(
312
+ path,
313
+ torch_dtype=dtype,
314
+ use_safetensors=True,
315
+ )
316
+ if hasattr(pipe, "scheduler"):
317
+ pipe.scheduler = EulerDiscreteScheduler.from_config(
318
+ pipe.scheduler.config,
319
+ timestep_spacing="trailing",
320
+ )
321
+ if torch.cuda.is_available():
322
+ pipe.to("cuda")
323
+ if hasattr(pipe, "vae"):
324
+ pipe.vae.enable_slicing()
325
+ pipe.vae.enable_tiling()
326
+ _pipes[key] = pipe
327
+ return pipe
328
+
329
+
330
+ def enhance_prompt(prompt, styles):
331
+ pre, suf = build_styles(styles)
332
+ parts = [DEFAULT_QUALITY, pre, prompt.strip(), suf]
333
+ return ", ".join([p for p in parts if p])
334
+
335
+
336
+ # ---------------------------------------------------------------------------
337
+ # GENERATE
338
+ # ---------------------------------------------------------------------------
339
+ @spaces.GPU(duration=180) if SPACES_AVAILABLE else (lambda f: f)
340
+ def generate(
341
+ engine,
342
  prompt,
343
  styles,
344
+ negative,
345
+ sdxl_model,
346
  steps,
347
+ cfg,
348
  seed,
 
349
  width,
350
  height,
351
+ batch,
 
 
 
 
352
  username,
353
  ):
354
+ if not prompt or not str(prompt).strip():
355
+ return None, "❌ Prompt missing", "", []
356
 
357
  now = time.time()
358
+ user_key = username or "anonymous"
359
+ if user_key in LAST_GEN_TIME and now - LAST_GEN_TIME[user_key] < COOLDOWN_SECONDS:
360
+ left = int(COOLDOWN_SECONDS - (now - LAST_GEN_TIME[user_key]))
361
+ gr.Warning(f"Cooldown {left}s")
362
+ return None, f"⏳ Cooldown {left}s", "", []
363
+
364
+ ok, bad = check_prompt_safety(prompt)
365
+ if not ok:
366
+ gr.Warning("Restricted content")
367
+ return None, "❌ Restricted content", "", []
368
+
369
+ batch = int(max(1, min(3, int(batch))))
370
+ seeds = [
371
+ random.randint(0, 2**31 - 1) if int(seed) < 0 else int(seed) + i
372
+ for i in range(batch)
373
+ ]
374
+ enhanced = enhance_prompt(prompt, styles)
375
+ neg = BASE_NEGATIVES
376
+ if negative and str(negative).strip():
377
+ neg = f"{BASE_NEGATIVES}, {negative.strip()}"
378
+
379
+ LAST_GEN_TIME[user_key] = now
380
+ gallery = []
381
+ jobs = []
382
 
383
  try:
384
+ device = "cuda" if torch.cuda.is_available() else "cpu"
385
+
386
+ if engine == ENGINE_FLUX:
387
+ pipe = get_flux_pipe()
388
+ # Schnell: low steps, guidance often ~0
389
+ use_steps = int(max(1, min(8, int(steps) if int(steps) <= 8 else 4)))
390
+ for s in seeds:
391
+ gen = torch.Generator(device=device).manual_seed(int(s))
392
+ out = pipe(
393
+ prompt=enhanced,
394
+ num_inference_steps=use_steps,
395
+ guidance_scale=0.0,
396
+ width=int(width),
397
+ height=int(height),
398
+ generator=gen,
399
+ max_sequence_length=256,
400
+ ).images[0]
401
+ label = f"FLUX · seed {s} · {datetime.now().strftime('%I:%M:%S %p')}"
402
+ gallery.append((out, label))
403
+ jobs.append((out, s, "FLUX.1-schnell", prompt, username, width, height, use_steps, 0.0))
404
+
405
+ elif engine == ENGINE_SD35:
406
+ pipe = get_sd35_pipe()
407
+ use_steps = int(max(15, min(40, int(steps))))
408
+ for s in seeds:
409
+ gen = torch.Generator(device=device).manual_seed(int(s))
410
+ out = pipe(
411
+ prompt=enhanced,
412
+ negative_prompt=neg,
413
+ num_inference_steps=use_steps,
414
+ guidance_scale=float(cfg),
415
+ width=int(width),
416
+ height=int(height),
417
+ generator=gen,
418
+ ).images[0]
419
+ label = f"SD3.5 · seed {s} · {datetime.now().strftime('%I:%M:%S %p')}"
420
+ gallery.append((out, label))
421
+ jobs.append((out, s, "SD3.5-medium", prompt, username, width, height, use_steps, cfg))
422
 
423
+ else:
424
+ pipe = get_sdxl_pipe(sdxl_model)
425
+ use_steps = int(max(10, min(40, int(steps))))
426
+ for s in seeds:
427
+ gen = torch.Generator(device=device).manual_seed(int(s))
428
+ out = pipe(
429
+ prompt=enhanced,
430
+ negative_prompt=neg,
431
+ num_inference_steps=use_steps,
432
+ guidance_scale=float(cfg),
433
+ width=int(width),
434
+ height=int(height),
435
+ generator=gen,
436
+ ).images[0]
437
+ label = f"SDXL · {sdxl_model[:28]} · seed {s}"
438
+ gallery.append((out, label))
439
+ jobs.append((out, s, sdxl_model, prompt, username, width, height, use_steps, cfg))
440
+
441
+ info = f"{engine} | seeds {seeds} | {width}x{height} | enhanced prompt ready"
442
+ return gallery, info, enhanced, jobs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
 
444
+ except Exception as e:
445
+ logger.exception("generate failed")
446
+ msg = str(e)
447
+ if "gated" in msg.lower() or "401" in msg or "403" in msg:
448
+ msg += " — open the model page on HF, accept the license, ensure Space secret HF_TOKEN can read it (or use HF login)."
449
+ return None, f"❌ {msg}", "", []
450
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
 
452
+ # ---------------------------------------------------------------------------
453
+ # UI
454
+ # ---------------------------------------------------------------------------
455
+ js_visitor = """
456
  function() {
457
+ let id = localStorage.getItem('adult_ai_visitor_id');
458
+ if (!id) {
459
+ id = 'Visitor_' + Math.floor(1000 + Math.random() * 9000);
460
+ localStorage.setItem('adult_ai_visitor_id', id);
461
+ }
462
+ return id;
 
463
  }
464
  """
465
 
466
  css = """
467
+ .gradio-container, html, body { background:#0b0b19 !important; color:#f3f4f6 !important; }
468
+ .block { background:#111827 !important; border:1px solid #1f2937 !important; border-radius:8px !important; }
 
 
 
 
 
 
 
 
469
  """
470
 
471
  THEME = gr.themes.Default(primary_hue="purple", secondary_hue="indigo", neutral_hue="slate")
472
 
473
 
474
+ def create_ui():
475
+ with gr.Blocks() as demo:
476
+ user_id = gr.Textbox(visible=False)
477
+ gr.Markdown("# ⚡ Next-Gen Image Lab")
 
478
  gr.Markdown(
479
+ "### FLUX.1 Schnell · SD 3.5 Medium · Adult SDXL NSFW chefs · ZeroGPU\\n\\n"
480
+ "18+ only. FLUX = newest fast tech. SD3.5 = newest quality (may need HF license accept). "
481
+ "Adult SDXL = your proven smut chefs."
482
  )
483
 
484
  with gr.Row():
485
  with gr.Column(scale=2):
486
+ prompt = gr.Textbox(label="Prompt", value=random.choice(RANDOM_PROMPTS), lines=7)
487
+ negative = gr.Textbox(label="Negatives (SD3.5 / SDXL; ignored by FLUX)", value=BASE_NEGATIVES, lines=4)
488
+ styles = gr.CheckboxGroup(
489
+ label="Style packs (stackable)",
490
+ choices=list(STYLE_PACKS.keys()),
491
+ value=["Men Only", "Photo Gloss"],
492
  )
493
+ go = gr.Button("GENERATE", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
 
 
494
  with gr.Column(scale=1):
495
+ engine = gr.Radio(label="Engine", choices=ENGINE_CHOICES, value=ENGINE_FLUX)
496
+ sdxl_model = gr.Dropdown(
497
+ label="Adult SDXL chef (only used on Adult SDXL engine)",
498
+ choices=SDXL_CHOICES,
499
+ value="OhMySister Beautiful Asian Woman",
500
+ )
501
+ seed = gr.Number(label="Seed (-1 random)", value=-1, precision=0)
502
+ width = gr.Slider(512, 1536, value=1024, step=64, label="Width")
503
+ height = gr.Slider(512, 1536, value=1024, step=64, label="Height")
504
+ steps = gr.Slider(1, 40, value=4, step=1, label="Steps (FLUX~4, SD3.5~28, SDXL~28)")
505
+ cfg = gr.Slider(0.0, 12.0, value=4.5, step=0.5, label="CFG (FLUX uses 0)")
506
+ batch = gr.Slider(1, 3, value=1, step=1, label="Batch (max 3)")
507
+
508
+ gallery = gr.Gallery(label="Outputs", columns=3, height="auto")
509
+ info = gr.Textbox(label="Run info", visible=True)
510
+ meta = gr.Textbox(label="Enhanced prompt", visible=True)
511
+
512
+ demo.load(fn=None, inputs=None, outputs=user_id, js=js_visitor)
513
+
514
+ def on_go(*args):
515
+ images, run_info, enhanced, jobs = generate(*args)
516
+ if jobs:
517
+ for job in jobs:
518
+ threading.Thread(target=async_log_upload, args=job, daemon=True).start()
519
+ if images is None:
520
+ return None, run_info, enhanced or ""
521
+ return images, run_info, enhanced
522
+
523
+ go.click(
524
+ fn=on_go,
525
+ inputs=[engine, prompt, styles, negative, sdxl_model, steps, cfg, seed, width, height, batch, user_id],
526
+ outputs=[gallery, info, meta],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
  )
528
+ return demo
529
 
530
 
531
  if __name__ == "__main__":
 
532
  os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
533
+ app = create_ui()
534
+ app.queue(max_size=12)
535
+ app.launch(ssr_mode=False, theme=THEME, css=css)