alexander00001 commited on
Commit
0ea5e0e
·
verified ·
1 Parent(s): 0225e33

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +746 -0
app.py ADDED
@@ -0,0 +1,746 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ===== 必须首先导入spaces =====
2
+ try:
3
+ import spaces
4
+ SPACES_AVAILABLE = True
5
+ print("✅ Spaces available - ZeroGPU mode")
6
+ except ImportError:
7
+ SPACES_AVAILABLE = False
8
+ print("⚠️ Spaces not available - running in regular mode")
9
+
10
+ # ===== 其他导入 =====
11
+ import os
12
+ import uuid
13
+ from datetime import datetime
14
+ import random
15
+ import torch
16
+ import gradio as gr
17
+ from diffusers import StableDiffusionXLPipeline, EulerDiscreteScheduler
18
+ from PIL import Image
19
+ import traceback
20
+ import numpy as np
21
+
22
+ # ===== 长提示词处理 =====
23
+ try:
24
+ from compel import Compel, ReturnedEmbeddingsType
25
+ COMPEL_AVAILABLE = True
26
+ print("✅ Compel available for long prompt processing")
27
+ except ImportError:
28
+ COMPEL_AVAILABLE = False
29
+ print("⚠️ Compel not available - using standard prompt processing")
30
+
31
+ # ===== 优化后的配置 =====
32
+ STYLE_KEYWORDS = {
33
+ "None": {"prefix": "", "suffix": ""},
34
+ "Realistic": {
35
+ "prefix": "(RAW photo:1.3), (photorealistic:1.4), (hyperrealistic:1.3), 8k uhd, (ultra realistic skin texture:1.2), cinematic lighting, vibrant colors, masterpiece, realistic skin texture, detailed anatomy, professional photography",
36
+ "suffix": "sharp focus, (everything in focus:1.3), (no bokeh:1.2), realistic skin texture, subsurface scattering, detailed anatomy, (perfect anatomy:1.2), detailed face, detailed background, lifelike, professional photography, realistic proportions, (detailed face:1.1), natural pose, expressive eyes, 8k resolution"
37
+ },
38
+ "Anime": {
39
+ "prefix": "(anime style:1.3), (anime artwork:1.2), vibrant, key visual, studio anime, highly detailed anime",
40
+ "suffix": "cel shading, clean linework, vibrant anime colors, detailed anime eyes, smooth anime skin, perfect anime proportions, manga illustration"
41
+ },
42
+ "Comic": {
43
+ "prefix": "(comic book art:1.3), (graphic novel:1.2), bold inking, comic art style",
44
+ "suffix": "bold outlines, halftone dots, pop art colors, dynamic panel, graphic illustration, cel shading, comic book style"
45
+ },
46
+ "Watercolor": {
47
+ "prefix": "(watercolor painting:1.3), (watercolor art:1.2), soft edges, delicate washes, artistic",
48
+ "suffix": "soft gradients, pastel colors, paper texture, artistic brush strokes, traditional watercolor, hand-painted"
49
+ }
50
+ }
51
+
52
+ QUALITY_TAGS = "masterpiece, best quality, high resolution, detailed"
53
+
54
+ # 🔧 修正:使用正确的模型名称
55
+ FIXED_MODEL = "votepurchase/pornmasterPro_noobV3VAE"
56
+
57
+ # 🔧 修正:使用正确的 LoRA 配置
58
+ LORA_CONFIGS = [
59
+ {
60
+ "repo_id": "OedoSoldier/detail-tweaker-lora",
61
+ "weight_name": "add_detail.safetensors",
62
+ "adapter_name": "detail_tweaker",
63
+ "scale": 0.8
64
+ }
65
+ ]
66
+
67
+ SAVE_DIR = "generated_images"
68
+ os.makedirs(SAVE_DIR, exist_ok=True)
69
+
70
+ # ===== 模型相关变量 =====
71
+ pipeline = None
72
+ compel_processor = None
73
+ device = None
74
+ model_loaded = False
75
+
76
+ def initialize_model():
77
+ """优化的模型初始化 - 修复设备不一致问题"""
78
+ global pipeline, compel_processor, device, model_loaded
79
+
80
+ if model_loaded and pipeline is not None:
81
+ print("✅ Model already loaded, skipping initialization")
82
+ return True
83
+
84
+ try:
85
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
86
+ print(f"🖥️ Using device: {device}")
87
+
88
+ print(f"📦 Loading model: {FIXED_MODEL}")
89
+
90
+ # 基础模型加载
91
+ pipeline = StableDiffusionXLPipeline.from_pretrained(
92
+ FIXED_MODEL,
93
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
94
+ variant="fp16" if torch.cuda.is_available() else None,
95
+ use_safetensors=True,
96
+ safety_checker=None,
97
+ requires_safety_checker=False
98
+ )
99
+
100
+ # 优化调度器
101
+ pipeline.scheduler = EulerDiscreteScheduler.from_config(
102
+ pipeline.scheduler.config,
103
+ timestep_spacing="trailing"
104
+ )
105
+
106
+ # 先移到设备
107
+ pipeline = pipeline.to(device)
108
+
109
+ # 加载 LoRA
110
+ print("🎨 Loading LoRA models...")
111
+ adapter_names = []
112
+ adapter_scales = []
113
+
114
+ for lora_config in LORA_CONFIGS:
115
+ try:
116
+ print(f" Loading: {lora_config['repo_id']}/{lora_config['weight_name']}")
117
+ pipeline.load_lora_weights(
118
+ lora_config["repo_id"],
119
+ weight_name=lora_config["weight_name"],
120
+ adapter_name=lora_config["adapter_name"]
121
+ )
122
+
123
+ # 🔧 关键修复: 确保 LoRA 权重在正确的设备上
124
+ if hasattr(pipeline, 'unet') and hasattr(pipeline.unet, 'to'):
125
+ pipeline.unet.to(device)
126
+
127
+ adapter_names.append(lora_config["adapter_name"])
128
+ adapter_scales.append(lora_config.get("scale", 0.8))
129
+ print(f" ✅ LoRA loaded: {lora_config['adapter_name']} (scale: {lora_config.get('scale', 0.8)})")
130
+ except Exception as lora_error:
131
+ print(f" ⚠️ Failed to load LoRA {lora_config['adapter_name']}: {lora_error}")
132
+ print(traceback.format_exc())
133
+
134
+ # 设置 LoRA 强度
135
+ if adapter_names:
136
+ try:
137
+ pipeline.set_adapters(adapter_names, adapter_weights=adapter_scales)
138
+ print(f"✅ LoRA adapters activated with scales: {adapter_scales}")
139
+
140
+ # 🔧 再次确保所有组件在同一设备
141
+ pipeline.to(device)
142
+ except Exception as e:
143
+ print(f"⚠️ Failed to set adapter scales: {e}")
144
+
145
+ # GPU优化
146
+ if torch.cuda.is_available():
147
+ try:
148
+ pipeline.enable_vae_slicing()
149
+ pipeline.enable_vae_tiling()
150
+
151
+ try:
152
+ pipeline.enable_xformers_memory_efficient_attention()
153
+ print("✅ xFormers enabled")
154
+ except:
155
+ print("⚠️ xFormers not available, using default attention")
156
+
157
+ print("ℹ️ Skipping torch.compile for ZeroGPU compatibility")
158
+
159
+ except Exception as opt_error:
160
+ print(f"⚠️ Optimization warning: {opt_error}")
161
+
162
+ # 初始化Compel
163
+ if COMPEL_AVAILABLE:
164
+ try:
165
+ compel_processor = Compel(
166
+ tokenizer=[pipeline.tokenizer, pipeline.tokenizer_2],
167
+ text_encoder=[pipeline.text_encoder, pipeline.text_encoder_2],
168
+ returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED,
169
+ requires_pooled=[False, True],
170
+ truncate_long_prompts=False
171
+ )
172
+ print("✅ Compel processor initialized")
173
+ except Exception as compel_error:
174
+ print(f"⚠️ Compel initialization failed: {compel_error}")
175
+ compel_processor = None
176
+
177
+ # 🔧 最终设备检查
178
+ print(f"🔍 Final device check:")
179
+ print(f" - UNet device: {next(pipeline.unet.parameters()).device}")
180
+ print(f" - VAE device: {next(pipeline.vae.parameters()).device}")
181
+ print(f" - Text Encoder device: {next(pipeline.text_encoder.parameters()).device}")
182
+
183
+ model_loaded = True
184
+ print("✅ Model initialization complete")
185
+ return True
186
+
187
+ except Exception as e:
188
+ print(f"❌ Model loading error: {e}")
189
+ print(traceback.format_exc())
190
+ model_loaded = False
191
+ return False
192
+
193
+ def enhance_prompt(prompt: str, style: str) -> str:
194
+ """优化的提示词增强"""
195
+ if not prompt or prompt.strip() == "":
196
+ return ""
197
+
198
+ style_config = STYLE_KEYWORDS.get(style, STYLE_KEYWORDS["None"])
199
+ parts = []
200
+
201
+ if style_config["prefix"]:
202
+ parts.append(style_config["prefix"])
203
+
204
+ parts.append(prompt.strip())
205
+
206
+ if style_config["suffix"]:
207
+ parts.append(style_config["suffix"])
208
+
209
+ parts.append(QUALITY_TAGS)
210
+
211
+ enhanced = ", ".join(parts)
212
+
213
+ print(f"\n🎨 Style: {style}")
214
+ print(f"📝 User prompt: {prompt[:100]}...")
215
+ print(f"✨ Enhanced: {enhanced[:200]}...\n")
216
+
217
+ return enhanced
218
+
219
+ def build_negative_prompt(style: str, custom_negative: str = "") -> str:
220
+ """根据风格构建负面提示词"""
221
+ base_negative = "(low quality:1.4), (worst quality:1.4), (bad anatomy:1.3), (bad hands:1.2), blurry, watermark, text, error, cropped, jpeg artifacts, ugly, duplicate, deformed"
222
+
223
+ style_negatives = {
224
+ "Realistic": ", (cartoon:1.3), (anime:1.3), (3d render:1.2), (illustration:1.2), (painting:1.2), (drawing:1.2), (art:1.2), (sketch:1.2), artificial, unrealistic",
225
+ "Anime": ", (realistic:1.3), (photorealistic:1.3), (photo:1.2), (3d:1.2), (hyperrealistic:1.2)",
226
+ "Comic": ", (realistic:1.2), (photorealistic:1.2), (blurry lines:1.2), (soft edges:1.2)",
227
+ "Watercolor": ", (digital art:1.2), (sharp edges:1.2), (vector art:1.2), (3d:1.2)"
228
+ }
229
+
230
+ negative = base_negative
231
+ if style in style_negatives:
232
+ negative += style_negatives[style]
233
+
234
+ if custom_negative.strip():
235
+ negative += f", {custom_negative.strip()}"
236
+
237
+ return negative
238
+
239
+ def process_with_compel(prompt, negative_prompt):
240
+ """使用Compel处理长提示词"""
241
+ if not compel_processor:
242
+ return None, None
243
+
244
+ try:
245
+ conditioning, pooled = compel_processor([prompt, negative_prompt])
246
+ print("✅ Long prompt processed with Compel")
247
+ return conditioning, pooled
248
+ except Exception as e:
249
+ print(f"⚠️ Compel processing failed: {e}")
250
+ return None, None
251
+
252
+ def apply_spaces_decorator(func):
253
+ """应用spaces装饰器"""
254
+ if SPACES_AVAILABLE:
255
+ return spaces.GPU(duration=45)(func)
256
+ return func
257
+
258
+ def create_metadata_content(prompt, enhanced_prompt, seed, steps, cfg_scale, width, height, style):
259
+ """创建元数据"""
260
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
261
+ lora_info = ", ".join([f"{lora['adapter_name']}({lora.get('scale', 1.0)})" for lora in LORA_CONFIGS])
262
+
263
+ return f"""Generated Image Metadata
264
+ ======================
265
+ Timestamp: {timestamp}
266
+ Original Prompt: {prompt}
267
+ Enhanced Prompt: {enhanced_prompt}
268
+ Seed: {seed}
269
+ Steps: {steps}
270
+ CFG Scale: {cfg_scale}
271
+ Dimensions: {width}x{height}
272
+ Style: {style}
273
+ LoRA: {lora_info}
274
+ """
275
+
276
+ def cleanup_pipeline():
277
+ """清理 pipeline 状态"""
278
+ global pipeline
279
+
280
+ if pipeline is None:
281
+ return
282
+
283
+ try:
284
+ if torch.cuda.is_available():
285
+ torch.cuda.empty_cache()
286
+ torch.cuda.ipc_collect()
287
+
288
+ if hasattr(pipeline, 'unet'):
289
+ if hasattr(pipeline.unet, 'set_attn_processor'):
290
+ try:
291
+ from diffusers.models.attention_processor import AttnProcessor
292
+ pipeline.unet.set_attn_processor(AttnProcessor())
293
+ except:
294
+ pass
295
+
296
+ if hasattr(pipeline, 'vae'):
297
+ pipeline.vae.to('cpu')
298
+ pipeline.vae.to(device)
299
+
300
+ print("🧹 Pipeline cleaned")
301
+
302
+ except Exception as e:
303
+ print(f"⚠️ Cleanup warning: {e}")
304
+
305
+ @apply_spaces_decorator
306
+ def generate_image(prompt: str, style: str, negative_prompt: str = "",
307
+ steps: int = 25, cfg_scale: float = 7.0,
308
+ seed: int = -1, width: int = 1024, height: int = 1024,
309
+ progress=gr.Progress()):
310
+ """图像生成主函数"""
311
+
312
+ if not prompt or prompt.strip() == "":
313
+ return None, "", "❌ Please enter a prompt"
314
+
315
+ progress(0.05, desc="Initializing...")
316
+
317
+ if not initialize_model():
318
+ return None, "", "❌ Failed to load model"
319
+
320
+ cleanup_pipeline()
321
+
322
+ progress(0.1, desc="Processing prompt...")
323
+
324
+ try:
325
+ if seed == -1:
326
+ seed = random.randint(0, np.iinfo(np.int32).max)
327
+
328
+ generator = torch.Generator(device).manual_seed(seed)
329
+
330
+ enhanced_prompt = enhance_prompt(prompt, style)
331
+ final_negative = build_negative_prompt(style, negative_prompt)
332
+
333
+ print(f"🔧 Generation params: seed={seed}, steps={steps}, cfg={cfg_scale}, size={width}x{height}")
334
+ print(f"📝 Prompt preview: {enhanced_prompt[:100]}...")
335
+
336
+ progress(0.2, desc="Generating image...")
337
+
338
+ prompt_length = len(enhanced_prompt.split())
339
+ use_compel = prompt_length > 50 and compel_processor is not None
340
+
341
+ if use_compel:
342
+ print(f"📏 Long prompt detected ({prompt_length} words), using Compel")
343
+ conditioning, pooled = process_with_compel(enhanced_prompt, final_negative)
344
+
345
+ if conditioning is not None:
346
+ result = pipeline(
347
+ prompt_embeds=conditioning[0:1],
348
+ pooled_prompt_embeds=pooled[0:1],
349
+ negative_prompt_embeds=conditioning[1:2],
350
+ negative_pooled_prompt_embeds=pooled[1:2],
351
+ num_inference_steps=steps,
352
+ guidance_scale=cfg_scale,
353
+ width=width,
354
+ height=height,
355
+ generator=generator,
356
+ output_type="pil"
357
+ ).images[0]
358
+ else:
359
+ print("⚠️ Falling back to standard generation")
360
+ result = pipeline(
361
+ prompt=enhanced_prompt,
362
+ negative_prompt=final_negative,
363
+ num_inference_steps=steps,
364
+ guidance_scale=cfg_scale,
365
+ width=width,
366
+ height=height,
367
+ generator=generator,
368
+ output_type="pil"
369
+ ).images[0]
370
+ else:
371
+ print(f"📝 Standard generation ({prompt_length} words)")
372
+ result = pipeline(
373
+ prompt=enhanced_prompt,
374
+ negative_prompt=final_negative,
375
+ num_inference_steps=steps,
376
+ guidance_scale=cfg_scale,
377
+ width=width,
378
+ height=height,
379
+ generator=generator,
380
+ output_type="pil"
381
+ ).images[0]
382
+
383
+ progress(0.95, desc="Finalizing...")
384
+
385
+ if not isinstance(result, Image.Image):
386
+ if isinstance(result, np.ndarray):
387
+ if result.dtype != np.uint8:
388
+ result = (result * 255).astype(np.uint8)
389
+ result = Image.fromarray(result)
390
+
391
+ metadata = create_metadata_content(
392
+ prompt, enhanced_prompt, seed, steps, cfg_scale,
393
+ width, height, style
394
+ )
395
+
396
+ generation_info = f"Style: {style} | Seed: {seed} | Size: {width}×{height} | Steps: {steps} | CFG: {cfg_scale}"
397
+
398
+ if torch.cuda.is_available():
399
+ torch.cuda.empty_cache()
400
+
401
+ progress(1.0, desc="Complete!")
402
+ print("✅ Generation successful\n")
403
+
404
+ return result, generation_info, metadata
405
+
406
+ except Exception as e:
407
+ error_msg = str(e)
408
+ print(f"❌ Generation error: {error_msg}")
409
+ print(traceback.format_exc())
410
+
411
+ try:
412
+ cleanup_pipeline()
413
+ except:
414
+ pass
415
+
416
+ return None, "", f"❌ Generation failed: {error_msg}"
417
+
418
+ # ===== CSS样式 =====
419
+ css = """
420
+ .gradio-container {
421
+ max-width: 100% !important;
422
+ margin: 0 !important;
423
+ padding: 0 !important;
424
+ background: linear-gradient(135deg, #e6a4f2 0%, #1197e4 100%) !important;
425
+ min-height: 100vh !important;
426
+ font-family: 'Segoe UI', Arial, sans-serif !important;
427
+ }
428
+
429
+ .main-content {
430
+ background: rgba(255, 255, 255, 0.9) !important;
431
+ border-radius: 20px !important;
432
+ padding: 20px !important;
433
+ margin: 15px !important;
434
+ box-shadow: 0 10px 25px rgba(255, 255, 255, 0.2) !important;
435
+ min-height: calc(100vh - 30px) !important;
436
+ color: #3e3e3e !important;
437
+ backdrop-filter: blur(10px) !important;
438
+ }
439
+
440
+ .title {
441
+ text-align: center !important;
442
+ background: linear-gradient(45deg, #bb6ded, #08676b) !important;
443
+ -webkit-background-clip: text !important;
444
+ -webkit-text-fill-color: transparent !important;
445
+ background-clip: text !important;
446
+ font-size: 2rem !important;
447
+ margin-bottom: 15px !important;
448
+ font-weight: bold !important;
449
+ }
450
+
451
+ .warning-box {
452
+ background: linear-gradient(45deg, #bb6ded, #08676b) !important;
453
+ color: white !important;
454
+ padding: 8px !important;
455
+ border-radius: 8px !important;
456
+ margin-bottom: 15px !important;
457
+ text-align: center !important;
458
+ font-weight: bold !important;
459
+ font-size: 14px !important;
460
+ }
461
+
462
+ .prompt-box textarea, .prompt-box input {
463
+ border-radius: 10px !important;
464
+ border: 2px solid #bb6ded !important;
465
+ padding: 15px !important;
466
+ font-size: 18px !important;
467
+ background: linear-gradient(135deg, rgba(245, 243, 255, 0.9), rgba(237, 233, 254, 0.9)) !important;
468
+ color: #2d2d2d !important;
469
+ }
470
+
471
+ .prompt-box textarea:focus, .prompt-box input:focus {
472
+ border-color: #08676b !important;
473
+ box-shadow: 0 0 15px rgba(77, 8, 161, 0.3) !important;
474
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(248, 249, 250, 0.95)) !important;
475
+ }
476
+
477
+ .controls-section {
478
+ background: linear-gradient(135deg, rgba(224, 218, 255, 0.8), rgba(196, 181, 253, 0.8)) !important;
479
+ border-radius: 12px !important;
480
+ padding: 15px !important;
481
+ margin-bottom: 8px !important;
482
+ border: 2px solid rgba(187, 109, 237, 0.3) !important;
483
+ backdrop-filter: blur(5px) !important;
484
+ }
485
+
486
+ .controls-section label {
487
+ font-weight: 600 !important;
488
+ color: #2d2d2d !important;
489
+ margin-bottom: 8px !important;
490
+ }
491
+
492
+ .controls-section input[type="radio"] {
493
+ accent-color: #bb6ded !important;
494
+ }
495
+
496
+ .controls-section input[type="number"],
497
+ .controls-section input[type="range"] {
498
+ background: rgba(255, 255, 255, 0.9) !important;
499
+ border: 1px solid #bb6ded !important;
500
+ border-radius: 6px !important;
501
+ padding: 8px !important;
502
+ color: #2d2d2d !important;
503
+ }
504
+
505
+ .generate-btn {
506
+ background: linear-gradient(45deg, #bb6ded, #08676b) !important;
507
+ color: white !important;
508
+ border: none !important;
509
+ padding: 15px 25px !important;
510
+ border-radius: 25px !important;
511
+ font-size: 16px !important;
512
+ font-weight: bold !important;
513
+ width: 100% !important;
514
+ cursor: pointer !important;
515
+ transition: all 0.3s ease !important;
516
+ text-transform: uppercase !important;
517
+ letter-spacing: 1px !important;
518
+ }
519
+
520
+ .generate-btn:hover {
521
+ transform: translateY(-2px) !important;
522
+ box-shadow: 0 8px 25px rgba(187, 109, 237, 0.5) !important;
523
+ }
524
+
525
+ .image-output {
526
+ border-radius: 15px !important;
527
+ overflow: hidden !important;
528
+ max-width: 100% !important;
529
+ max-height: 70vh !important;
530
+ border: 3px solid #08676b !important;
531
+ box-shadow: 0 8px 20px rgba(0,0,0,0.15) !important;
532
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(248, 249, 250, 0.9)) !important;
533
+ }
534
+
535
+ .image-info {
536
+ background: linear-gradient(135deg, rgba(248, 249, 250, 0.2), rgba(233, 236, 239, 0.9)) !important;
537
+ border-radius: 8px !important;
538
+ padding: 12px !important;
539
+ margin-top: 10px !important;
540
+ font-size: 12px !important;
541
+ color: #495057 !important;
542
+ border: 2px solid rgba(187, 109, 237, 0.2) !important;
543
+ backdrop-filter: blur(5px) !important;
544
+ }
545
+
546
+ .metadata-box {
547
+ background: linear-gradient(135deg, rgba(248, 249, 250, 0.2), rgba(233, 236, 239, 0.9)) !important;
548
+ border-radius: 8px !important;
549
+ padding: 15px !important;
550
+ margin-top: 15px !important;
551
+ font-family: 'Courier New', monospace !important;
552
+ font-size: 12px !important;
553
+ color: #495057 !important;
554
+ border: 2px solid rgba(187, 109, 237, 0.2) !important;
555
+ backdrop-filter: blur(5px) !important;
556
+ white-space: pre-wrap !important;
557
+ overflow-y: auto !important;
558
+ max-height: 300px !important;
559
+ }
560
+
561
+ @media (max-width: 768px) {
562
+ .main-content {
563
+ margin: 10px !important;
564
+ padding: 15px !important;
565
+ }
566
+ .title {
567
+ font-size: 1.5rem !important;
568
+ }
569
+ }
570
+ """
571
+
572
+ # ===== 创建UI =====
573
+ def create_interface():
574
+ with gr.Blocks(css=css, title="Adult NSFW AI Image Generator") as interface:
575
+ with gr.Column(elem_classes=["main-content"]):
576
+ gr.HTML('<div class="title">Adult NSFW AI Image Generator</div>')
577
+ gr.HTML('<div class="warning-box">⚠️ 18+ CONTENT WARNING ⚠️</div>')
578
+
579
+ with gr.Row():
580
+ with gr.Column(scale=2):
581
+ prompt_input = gr.Textbox(
582
+ label="Detailed Prompt",
583
+ placeholder="Enter your detailed prompt here...",
584
+ lines=15,
585
+ elem_classes=["prompt-box"]
586
+ )
587
+
588
+ negative_prompt_input = gr.Textbox(
589
+ label="Negative Prompt (Optional)",
590
+ placeholder="Additional things you don't want...",
591
+ lines=4,
592
+ elem_classes=["prompt-box"]
593
+ )
594
+
595
+ with gr.Column(scale=1):
596
+ with gr.Group(elem_classes=["controls-section"]):
597
+ style_input = gr.Radio(
598
+ label="Style Preset",
599
+ choices=list(STYLE_KEYWORDS.keys()),
600
+ value="Realistic"
601
+ )
602
+
603
+ with gr.Group(elem_classes=["controls-section"]):
604
+ seed_input = gr.Number(
605
+ label="Seed (-1 for random)",
606
+ value=-1,
607
+ precision=0
608
+ )
609
+
610
+ with gr.Group(elem_classes=["controls-section"]):
611
+ width_input = gr.Slider(
612
+ label="Width",
613
+ minimum=512,
614
+ maximum=2048,
615
+ value=1024,
616
+ step=64
617
+ )
618
+
619
+ with gr.Group(elem_classes=["controls-section"]):
620
+ height_input = gr.Slider(
621
+ label="Height",
622
+ minimum=512,
623
+ maximum=2048,
624
+ value=1024,
625
+ step=64
626
+ )
627
+
628
+ with gr.Group(elem_classes=["controls-section"]):
629
+ steps_input = gr.Slider(
630
+ label="Steps",
631
+ minimum=10,
632
+ maximum=50,
633
+ value=25,
634
+ step=1
635
+ )
636
+
637
+ cfg_input = gr.Slider(
638
+ label="CFG Scale",
639
+ minimum=1.0,
640
+ maximum=15.0,
641
+ value=7.0,
642
+ step=0.1
643
+ )
644
+
645
+ generate_button = gr.Button(
646
+ "GENERATE",
647
+ elem_classes=["generate-btn"],
648
+ variant="primary"
649
+ )
650
+
651
+ image_output = gr.Image(
652
+ label="Generated Image",
653
+ elem_classes=["image-output"],
654
+ show_label=False,
655
+ container=True
656
+ )
657
+
658
+ with gr.Row():
659
+ generation_info = gr.Textbox(
660
+ label="Generation Info",
661
+ interactive=False,
662
+ elem_classes=["image-info"],
663
+ show_label=True,
664
+ visible=False
665
+ )
666
+
667
+ with gr.Row():
668
+ metadata_display = gr.Textbox(
669
+ label="Image Metadata",
670
+ interactive=True,
671
+ elem_classes=["metadata-box"],
672
+ show_label=True,
673
+ lines=15,
674
+ visible=False
675
+ )
676
+
677
+ def on_generate(prompt, style, neg_prompt, steps, cfg, seed, width, height):
678
+ image, info, metadata = generate_image(
679
+ prompt, style, neg_prompt, steps, cfg, seed, width, height
680
+ )
681
+
682
+ if image is not None:
683
+ return (
684
+ image,
685
+ info,
686
+ metadata,
687
+ gr.update(visible=True, value=info),
688
+ gr.update(visible=True, value=metadata)
689
+ )
690
+ else:
691
+ return (
692
+ None,
693
+ info,
694
+ "",
695
+ gr.update(visible=False),
696
+ gr.update(visible=False)
697
+ )
698
+
699
+ generate_button.click(
700
+ fn=on_generate,
701
+ inputs=[
702
+ prompt_input, style_input, negative_prompt_input,
703
+ steps_input, cfg_input, seed_input, width_input, height_input
704
+ ],
705
+ outputs=[
706
+ image_output, generation_info, metadata_display,
707
+ generation_info, metadata_display
708
+ ],
709
+ show_progress=True
710
+ )
711
+
712
+ prompt_input.submit(
713
+ fn=on_generate,
714
+ inputs=[
715
+ prompt_input, style_input, negative_prompt_input,
716
+ steps_input, cfg_input, seed_input, width_input, height_input
717
+ ],
718
+ outputs=[
719
+ image_output, generation_info, metadata_display,
720
+ generation_info, metadata_display
721
+ ],
722
+ show_progress=True
723
+ )
724
+
725
+ return interface
726
+
727
+ # ===== 启动应用 =====
728
+ if __name__ == "__main__":
729
+ print("\n" + "="*50)
730
+ print("🚀 Starting NSFW Image Generator")
731
+ print("="*50)
732
+ print(f"📦 Model: {FIXED_MODEL}")
733
+ print(f"🖥️ Device: {'CUDA' if torch.cuda.is_available() else 'CPU'}")
734
+ print(f"⚡ ZeroGPU: {'Enabled' if SPACES_AVAILABLE else 'Disabled'}")
735
+ print(f"📝 Compel: {'Available' if COMPEL_AVAILABLE else 'Not Available'}")
736
+ print(f"🎨 LoRA: detail-tweaker-lora (scale: {LORA_CONFIGS[0].get('scale', 0.8)})")
737
+ print("="*50 + "\n")
738
+
739
+ app = create_interface()
740
+ app.queue(max_size=10, default_concurrency_limit=2)
741
+
742
+ app.launch(
743
+ server_name="0.0.0.0",
744
+ server_port=7860,
745
+ share=False
746
+ )