alexander00001 commited on
Commit
fefe278
·
verified ·
1 Parent(s): 1ca750f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +752 -130
app.py CHANGED
@@ -1,154 +1,776 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
 
 
 
2
  import numpy as np
3
- import random
 
4
 
5
- # import spaces #[uncomment to use ZeroGPU]
6
- from diffusers import DiffusionPipeline
7
- import torch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- device = "cuda" if torch.cuda.is_available() else "cpu"
10
- model_repo_id = "stabilityai/sdxl-turbo" # Replace to the model you would like to use
11
-
12
- if torch.cuda.is_available():
13
- torch_dtype = torch.float16
14
- else:
15
- torch_dtype = torch.float32
16
-
17
- pipe = DiffusionPipeline.from_pretrained(model_repo_id, torch_dtype=torch_dtype)
18
- pipe = pipe.to(device)
19
-
20
- MAX_SEED = np.iinfo(np.int32).max
21
- MAX_IMAGE_SIZE = 1024
22
-
23
-
24
- # @spaces.GPU #[uncomment to use ZeroGPU]
25
- def infer(
26
- prompt,
27
- negative_prompt,
28
- seed,
29
- randomize_seed,
30
- width,
31
- height,
32
- guidance_scale,
33
- num_inference_steps,
34
- progress=gr.Progress(track_tqdm=True),
35
- ):
36
- if randomize_seed:
37
- seed = random.randint(0, MAX_SEED)
38
-
39
- generator = torch.Generator().manual_seed(seed)
40
-
41
- image = pipe(
42
- prompt=prompt,
43
- negative_prompt=negative_prompt,
44
- guidance_scale=guidance_scale,
45
- num_inference_steps=num_inference_steps,
46
- width=width,
47
- height=height,
48
- generator=generator,
49
- ).images[0]
50
-
51
- return image, seed
52
-
53
-
54
- examples = [
55
- "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
56
- "An astronaut riding a green horse",
57
- "A delicious ceviche cheesecake slice",
58
  ]
59
 
60
- css = """
61
- #col-container {
62
- margin: 0 auto;
63
- max-width: 640px;
 
 
64
  }
65
- """
66
 
67
- with gr.Blocks(css=css) as demo:
68
- with gr.Column(elem_id="col-container"):
69
- gr.Markdown(" # Text-to-Image Gradio Template")
70
 
71
- with gr.Row():
72
- prompt = gr.Text(
73
- label="Prompt",
74
- show_label=False,
75
- max_lines=1,
76
- placeholder="Enter your prompt",
77
- container=False,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
- run_button = gr.Button("Run", scale=0, variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
- result = gr.Image(label="Result", show_label=False)
 
 
 
 
 
 
 
 
 
 
83
 
84
- with gr.Accordion("Advanced Settings", open=False):
85
- negative_prompt = gr.Text(
86
- label="Negative prompt",
87
- max_lines=1,
88
- placeholder="Enter a negative prompt",
89
- visible=False,
90
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
- seed = gr.Slider(
93
- label="Seed",
94
- minimum=0,
95
- maximum=MAX_SEED,
96
- step=1,
97
- value=0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
 
 
 
 
 
 
 
 
 
 
101
 
102
- with gr.Row():
103
- width = gr.Slider(
104
- label="Width",
105
- minimum=256,
106
- maximum=MAX_IMAGE_SIZE,
107
- step=32,
108
- value=1024, # Replace with defaults that work for your model
109
- )
 
 
 
110
 
111
- height = gr.Slider(
112
- label="Height",
113
- minimum=256,
114
- maximum=MAX_IMAGE_SIZE,
115
- step=32,
116
- value=1024, # Replace with defaults that work for your model
117
- )
 
 
 
 
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  with gr.Row():
120
- guidance_scale = gr.Slider(
121
- label="Guidance scale",
122
- minimum=0.0,
123
- maximum=10.0,
124
- step=0.1,
125
- value=0.0, # Replace with defaults that work for your model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  )
127
-
128
- num_inference_steps = gr.Slider(
129
- label="Number of inference steps",
130
- minimum=1,
131
- maximum=50,
132
- step=1,
133
- value=2, # Replace with defaults that work for your model
134
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
- gr.Examples(examples=examples, inputs=[prompt])
137
- gr.on(
138
- triggers=[run_button.click, prompt.submit],
139
- fn=infer,
140
- inputs=[
141
- prompt,
142
- negative_prompt,
143
- seed,
144
- randomize_seed,
145
- width,
146
- height,
147
- guidance_scale,
148
- num_inference_steps,
149
- ],
150
- outputs=[result, seed],
151
- )
152
-
153
  if __name__ == "__main__":
154
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 FluxPipeline, FlowMatchEulerDiscreteScheduler
18
+ from PIL import Image
19
+ import traceback
20
  import numpy as np
21
+ import io
22
+ import base64
23
 
24
+ # ===== 长提示词处理 =====
25
+ try:
26
+ from compel import Compel
27
+ COMPEL_AVAILABLE = True
28
+ print("✅ Compel available for long prompt processing")
29
+ except ImportError:
30
+ COMPEL_AVAILABLE = False
31
+ print("⚠️ Compel not available - using standard prompt processing")
32
+
33
+ # ===== 简化后的配置 =====
34
+ STYLE_PRESETS = {
35
+ "None": "",
36
+ "Realistic": "photorealistic, 8k, ultra-detailed, cinematic lighting, masterpiece, realistic skin texture, detailed anatomy",
37
+ "Anime": "anime style, detailed, high quality, masterpiece, best quality, detailed eyes, perfect anatomy",
38
+ "Comic": "comic book style, bold outlines, vibrant colors, cel shading, dynamic pose",
39
+ "Watercolor": "watercolor illustration, soft gradients, pastel palette, artistic brush strokes"
40
+ }
41
+
42
+ # 固定模型配置
43
+ FIXED_MODEL = "black-forest-labs/FLUX.1-dev"
44
+
45
+ # 固定LoRA配置 - FLUX 专用 NSFW LoRA(假设从 CivitAI 下载)
46
+ QUALITY_LORA = {
47
+ "repo_id": "path/to/flux-nsfw-lora", # 替换为实际 NSFW LoRA(如 CivitAI 下载)
48
+ "filename": "flux-nsfw.safetensors",
49
+ "scale": 0.8,
50
+ "description": "NSFW and realism enhancer for FLUX"
51
+ }
52
 
53
+ # 质量增强提示词(适配 NSFW)
54
+ QUALITY_ENHANCERS = [
55
+ "detailed anatomy", "(perfect anatomy:1.2)", "soft skin", "natural lighting",
56
+ "high resolution", "(masterpiece:1.3)", "(best quality:1.2)",
57
+ "professional photography", "artistic composition",
58
+ "(perfect proportions:1.1)", "smooth textures", "intimate lighting",
59
+ "realistic skin texture", "(detailed face:1.1)", "natural pose"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  ]
61
 
62
+ # 风格专用增强词
63
+ STYLE_ENHANCERS = {
64
+ "Realistic": ["photorealistic", "(ultra realistic:1.2)", "natural lighting", "detailed skin", "professional photography"],
65
+ "Anime": ["anime style", "(high quality anime:1.2)", "detailed eyes", "perfect face", "clean art style"],
66
+ "Comic": ["comic book style", "bold outlines", "vibrant colors", "cel shading"],
67
+ "Watercolor": ["watercolor style", "artistic", "soft gradients", "pastel palette"]
68
  }
 
69
 
70
+ SAVE_DIR = "generated_images"
71
+ os.makedirs(SAVE_DIR, exist_ok=True)
 
72
 
73
+ # ===== 模型相关变量 =====
74
+ pipeline = None
75
+ compel_processor = None
76
+ device = None
77
+ model_loaded = False
78
+ lora_loaded = False
79
+
80
+ def initialize_model():
81
+ """优化的模型初始化函数"""
82
+ global pipeline, compel_processor, device, model_loaded, lora_loaded
83
+
84
+ if model_loaded and pipeline is not None:
85
+ return True
86
+
87
+ try:
88
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
89
+ print(f"🖥️ Using device: {device}")
90
+
91
+ print(f"📦 Loading fixed model: {FIXED_MODEL}")
92
+
93
+ # 基础模型加载
94
+ pipeline = FluxPipeline.from_pretrained(
95
+ FIXED_MODEL,
96
+ torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
97
+ use_safetensors=True
98
+ )
99
+
100
+ # 优化调度器
101
+ pipeline.scheduler = FlowMatchEulerDiscreteScheduler.from_config(
102
+ pipeline.scheduler.config
103
+ )
104
+ pipeline = pipeline.to(device)
105
+
106
+ # 统一数据类型
107
+ if torch.cuda.is_available():
108
+ pipeline.transformer.to(torch.bfloat16)
109
+ pipeline.vae.to(torch.bfloat16)
110
+
111
+ # GPU优化
112
+ if torch.cuda.is_available():
113
+ try:
114
+ pipeline.enable_vae_slicing()
115
+ pipeline.enable_model_cpu_offload() # FLUX 高 VRAM 优化
116
+ pipeline.transformer = torch.compile(pipeline.transformer, mode="reduce-overhead")
117
+ print("✅ Torch.compile enabled for transformer")
118
+ except Exception as mem_error:
119
+ print(f"⚠️ Memory optimization warning: {mem_error}")
120
+
121
+ # 加载 NSFW LoRA
122
+ try:
123
+ print("🔧 Loading NSFW LoRA...")
124
+ pipeline.load_lora_weights(
125
+ QUALITY_LORA["repo_id"],
126
+ weight_name=QUALITY_LORA["filename"],
127
+ adapter_name="nsfw_enhancer"
128
  )
129
+ pipeline.set_adapters(["nsfw_enhancer"], adapter_weights=[QUALITY_LORA["scale"]])
130
+ lora_loaded = True
131
+ print("✅ NSFW LoRA loaded successfully")
132
+ except Exception as lora_error:
133
+ print(f"⚠️ LoRA loading failed: {lora_error}")
134
+ lora_loaded = False
135
+
136
+ # 初始化 Compel(FLUX 简化版)
137
+ if COMPEL_AVAILABLE:
138
+ try:
139
+ compel_processor = Compel(
140
+ tokenizer=pipeline.tokenizer,
141
+ text_encoder=pipeline.text_encoder,
142
+ truncate_long_prompts=False
143
+ )
144
+ print("✅ Long prompt processor (Compel) initialized successfully")
145
+ except Exception as compel_error:
146
+ print(f"⚠️ Compel initialization failed: {compel_error}")
147
+ compel_processor = None
148
+
149
+ model_loaded = True
150
+ print("✅ Model initialization complete")
151
+ return True
152
+
153
+ except Exception as e:
154
+ print(f"❌ Critical model loading error: {e}")
155
+ print(traceback.format_exc())
156
+ model_loaded = False
157
+ return False
158
 
159
+ def enhance_prompt(prompt: str, style: str) -> str:
160
+ """增强提示词"""
161
+ quality_terms = ", ".join(QUALITY_ENHANCERS)
162
+
163
+ style_terms = ""
164
+ if style in STYLE_ENHANCERS:
165
+ style_terms = ", " + ", ".join(STYLE_ENHANCERS[style])
166
+
167
+ style_suffix = STYLE_PRESETS.get(style, "")
168
+
169
+ enhanced_parts = [prompt.strip()]
170
+
171
+ if style_suffix:
172
+ enhanced_parts.append(style_suffix)
173
+
174
+ if style_terms:
175
+ enhanced_parts.append(style_terms.lstrip(", "))
176
+
177
+ enhanced_parts.append(quality_terms)
178
+
179
+ enhanced_prompt = ", ".join(filter(None, enhanced_parts))
180
+ return enhanced_prompt
181
 
182
+ def process_long_prompt(prompt, negative_prompt=""):
183
+ """处理长提示词(FLUX 简化版)"""
184
+ if not compel_processor:
185
+ return None
186
+
187
+ try:
188
+ conditioning = compel_processor(prompt)
189
+ return conditioning
190
+ except Exception as e:
191
+ print(f"Long prompt processing failed: {e}")
192
+ return None
193
 
194
+ def apply_spaces_decorator(func):
195
+ """应用 spaces 装饰器"""
196
+ if SPACES_AVAILABLE:
197
+ return spaces.GPU(duration=60)(func)
198
+ return func
199
+
200
+ def create_metadata_content(prompt, enhanced_prompt, seed, steps, cfg_scale, width, height, style):
201
+ """创建元数据内容"""
202
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
203
+ return f"""Generated Image Metadata
204
+ ======================
205
+ Timestamp: {timestamp}
206
+ Original Prompt: {prompt}
207
+ Enhanced Prompt: {enhanced_prompt}
208
+ Seed: {seed}
209
+ Steps: {steps}
210
+ CFG Scale: {cfg_scale}
211
+ Dimensions: {width}x{height}
212
+ Style: {style}
213
+ Model: FLUX.1-dev
214
+ LoRA: NSFW Enhancer (scale: 0.8)
215
+ """
216
 
217
+ @apply_spaces_decorator
218
+ def generate_image(prompt: str, style: str, negative_prompt: str = "", steps: int = 30, cfg_scale: float = 7.0,
219
+ seed: int = -1, width: int = 1024, height: int = 1024, progress=gr.Progress()):
220
+ """图像生成函数"""
221
+ if not prompt or prompt.strip() == "":
222
+ return None, "", ""
223
+
224
+ # 初始化模型
225
+ progress(0.1, desc="Loading model...")
226
+ if not initialize_model():
227
+ return None, "", "❌ Failed to load model"
228
+
229
+ progress(0.3, desc="Processing prompt...")
230
+
231
+ try:
232
+ # 处理 seed
233
+ if seed == -1:
234
+ seed = random.randint(0, np.iinfo(np.int32).max)
235
+
236
+ # 增强提示词
237
+ enhanced_prompt = enhance_prompt(prompt.strip(), style)
238
+
239
+ # 增强负面提示词
240
+ if not negative_prompt.strip():
241
+ negative_prompt = "(low quality, worst quality:1.4), (bad anatomy, bad hands:1.2), blurry, watermark, signature, text, error, missing limbs, extra limbs, cropped, normal quality, jpeg artifacts, deformed, mutated"
242
+
243
+ # 生成参数
244
+ generator = torch.Generator(device).manual_seed(seed)
245
+
246
+ progress(0.5, desc="Generating image...")
247
+
248
+ # 长提示词处理
249
+ use_long_prompt = len(enhanced_prompt.split()) > 60 or len(enhanced_prompt) > 300
250
+
251
+ if use_long_prompt and compel_processor:
252
+ conditioning = process_long_prompt(enhanced_prompt)
253
+
254
+ if conditioning is not None:
255
+ result = pipeline(
256
+ prompt_embeds=conditioning,
257
+ num_inference_steps=steps,
258
+ guidance_scale=cfg_scale,
259
+ width=width,
260
+ height=height,
261
+ generator=generator
262
+ )
263
+ image = result.images[0]
264
+ else:
265
+ result = pipeline(
266
+ prompt=enhanced_prompt,
267
+ negative_prompt=negative_prompt,
268
+ num_inference_steps=steps,
269
+ guidance_scale=cfg_scale,
270
+ width=width,
271
+ height=height,
272
+ generator=generator
273
+ )
274
+ image = result.images[0]
275
+ else:
276
+ result = pipeline(
277
+ prompt=enhanced_prompt,
278
+ negative_prompt=negative_prompt,
279
+ num_inference_steps=steps,
280
+ guidance_scale=cfg_scale,
281
+ width=width,
282
+ height=height,
283
+ generator=generator
284
  )
285
+ image = result.images[0]
286
+
287
+ progress(0.9, desc="Saving image...")
288
+
289
+ # 保存图像
290
+ filename = f"IMG_{seed}.png"
291
+ filepath = os.path.join(SAVE_DIR, filename)
292
+ image.save(filepath, quality=95, optimize=True)
293
+
294
+ # 创建元数据内容
295
+ metadata_content = create_metadata_content(
296
+ prompt, enhanced_prompt, seed, steps, cfg_scale, width, height, style
297
+ )
298
+
299
+ progress(1.0, desc="Complete!")
300
+
301
+ # 生成信息显示
302
+ generation_info = f"Prompt: {prompt}\nSeed: {seed} | Size: {width}×{height} | Steps: {steps} | CFG: {cfg_scale}"
303
+
304
+ return image, generation_info, metadata_content
305
+
306
+ except Exception as e:
307
+ error_msg = str(e)
308
+ print(f"Generation error: {error_msg}")
309
+ print(traceback.format_exc())
310
+ return None, "", f"❌ Generation failed: {error_msg}"
311
 
312
+ # ===== CSS 样式(保持不变) =====
313
+ css = """
314
+ /* 全局容器 */
315
+ .gradio-container {
316
+ max-width: 100% !important;
317
+ margin: 0 !important;
318
+ padding: 0 !important;
319
+ background: linear-gradient(135deg, #e6a4f2 0%, #1197e4 100%) !important;
320
+ min-height: 100vh !important;
321
+ font-family: 'Segoe UI', Arial, sans-serif !important;
322
+ }
323
 
324
+ /* 主要内容区域 */
325
+ .main-content {
326
+ background: rgba(255, 255, 255, 0.95) !important;
327
+ border-radius: 20px !important;
328
+ padding: 20px !important;
329
+ margin: 15px !important;
330
+ box-shadow: 0 10px 25px rgba(0,0,0,0.2) !important;
331
+ min-height: calc(100vh - 30px) !important;
332
+ color: #3e3e3e !important;
333
+ backdrop-filter: blur(10px) !important;
334
+ }
335
 
336
+ /* 简化标题 */
337
+ .title {
338
+ text-align: center !important;
339
+ background: linear-gradient(45deg, #bb6ded, #08676b) !important;
340
+ -webkit-background-clip: text !important;
341
+ -webkit-text-fill-color: transparent !important;
342
+ background-clip: text !important;
343
+ font-size: 2rem !important;
344
+ margin-bottom: 15px !important;
345
+ font-weight: bold !important;
346
+ }
347
 
348
+ /* 简化警告信息 */
349
+ .warning-box {
350
+ background: linear-gradient(45deg, #bb6ded, #08676b) !important;
351
+ color: white !important;
352
+ padding: 8px !important;
353
+ border-radius: 8px !important;
354
+ margin-bottom: 15px !important;
355
+ text-align: center !important;
356
+ font-weight: bold !important;
357
+ font-size: 14px !important;
358
+ }
359
+
360
+ /* 输入框样式 */
361
+ .prompt-box textarea, .prompt-box input {
362
+ border-radius: 10px !important;
363
+ border: 2px solid #bb6ded !important;
364
+ padding: 15px !important;
365
+ font-size: 14px !important;
366
+ background: linear-gradient(135deg, rgba(245, 243, 255, 0.9), rgba(237, 233, 254, 0.9)) !important;
367
+ color: #2d2d2d !important;
368
+ }
369
+
370
+ .prompt-box textarea:focus, .prompt-box input:focus {
371
+ border-color: #08676b !important;
372
+ box-shadow: 0 0 15px rgba(77, 8, 161, 0.3) !important;
373
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(248, 249, 250, 0.95)) !important;
374
+ }
375
+
376
+ /* 右侧控制区域 */
377
+ .controls-section {
378
+ background: linear-gradient(135deg, rgba(224, 218, 255, 0.8), rgba(196, 181, 253, 0.8)) !important;
379
+ border-radius: 12px !important;
380
+ padding: 15px !important;
381
+ margin-bottom: 10px !important;
382
+ border: 2px solid rgba(187, 109, 237, 0.3) !important;
383
+ backdrop-filter: blur(5px) !important;
384
+ }
385
+
386
+ .controls-section label {
387
+ font-weight: 600 !important;
388
+ color: #2d2d2d !important;
389
+ margin-bottom: 8px !important;
390
+ }
391
+
392
+ .controls-section input[type="radio"] {
393
+ accent-color: #bb6ded !important;
394
+ }
395
+
396
+ .controls-section input[type="number"],
397
+ .controls-section input[type="range"] {
398
+ background: rgba(255, 255, 255, 0.9) !important;
399
+ border: 1px solid #bb6ded !important;
400
+ border-radius: 6px !important;
401
+ padding: 8px !important;
402
+ color: #2d2d2d !important;
403
+ }
404
+
405
+ .controls-section select {
406
+ background: rgba(255, 255, 255, 0.9) !important;
407
+ border: 1px solid #bb6ded !important;
408
+ border-radius: 6px !important;
409
+ padding: 8px !important;
410
+ color: #2d2d2d !important;
411
+ }
412
+
413
+ /* 生成按钮 */
414
+ .generate-btn {
415
+ background: linear-gradient(45deg, #bb6ded, #08676b) !important;
416
+ color: white !important;
417
+ border: none !important;
418
+ padding: 15px 25px !important;
419
+ border-radius: 25px !important;
420
+ font-size: 16px !important;
421
+ font-weight: bold !important;
422
+ width: 100% !important;
423
+ cursor: pointer !important;
424
+ transition: all 0.3s ease !important;
425
+ text-transform: uppercase !important;
426
+ letter-spacing: 1px !important;
427
+ }
428
+
429
+ .generate-btn:hover {
430
+ transform: translateY(-2px) !important;
431
+ box-shadow: 0 8px 25px rgba(187, 109, 237, 0.5) !important;
432
+ }
433
+
434
+ /* 图片输出区域 */
435
+ .image-output {
436
+ border-radius: 15px !important;
437
+ overflow: hidden !important;
438
+ max-width: 100% !important;
439
+ max-height: 70vh !important;
440
+ border: 3px solid #08676b !important;
441
+ box-shadow: 0 8px 20px rgba(0,0,0,0.15) !important;
442
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(248, 249, 250, 0.9)) !important;
443
+ }
444
+
445
+ /* 图片信息区域 */
446
+ .image-info {
447
+ background: linear-gradient(135deg, rgba(248, 249, 250, 0.9), rgba(233, 236, 239, 0.9)) !important;
448
+ border-radius: 8px !important;
449
+ padding: 12px !important;
450
+ margin-top: 10px !important;
451
+ font-size: 12px !important;
452
+ color: #495057 !important;
453
+ border: 2px solid rgba(187, 109, 237, 0.2) !important;
454
+ backdrop-filter: blur(5px) !important;
455
+ }
456
+
457
+ /* 保存按钮 */
458
+ .save-btn {
459
+ background: linear-gradient(45deg, #28a745, #20c997) !important;
460
+ color: white !important;
461
+ border: none !important;
462
+ padding: 10px 20px !important;
463
+ border-radius: 8px !important;
464
+ font-size: 13px !important;
465
+ font-weight: 600 !important;
466
+ cursor: pointer !important;
467
+ margin-top: 8px !important;
468
+ margin-right: 10px !important;
469
+ transition: all 0.3s ease !important;
470
+ }
471
+
472
+ .save-btn:hover {
473
+ background: linear-gradient(45deg, #218838, #1ea471) !important;
474
+ transform: translateY(-1px) !important;
475
+ box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3) !important;
476
+ }
477
+
478
+ /* 滑块样式 */
479
+ .slider-container input[type="range"] {
480
+ accent-color: #bb6ded !important;
481
+ }
482
+
483
+ /* 响应式设计 */
484
+ @media (max-width: 768px) {
485
+ .main-content {
486
+ margin: 10px !important;
487
+ padding: 15px !important;
488
+ }
489
+
490
+ .title {
491
+ font-size: 1.5rem !important;
492
+ }
493
+ }
494
+
495
+ /* 隐藏占位符 */
496
+ .gr-image .image-container:empty::before {
497
+ content: "Generated image will appear here" !important;
498
+ display: flex !important;
499
+ align-items: center !important;
500
+ justify-content: center !important;
501
+ height: 300px !important;
502
+ background: linear-gradient(135deg, rgba(248, 249, 250, 0.8), rgba(233, 236, 239, 0.8)) !important;
503
+ border-radius: 10px !important;
504
+ color: #6c757d !important;
505
+ font-size: 16px !important;
506
+ font-weight: 500 !important;
507
+ backdrop-filter: blur(5px) !important;
508
+ }
509
+ """
510
+
511
+ # ===== 创建 UI =====
512
+ def create_interface():
513
+ with gr.Blocks(css=css, title="NSFW FLUX Image Generator") as interface:
514
+ with gr.Column(elem_classes=["main-content"]):
515
+ # 简化标题
516
+ gr.HTML('<div class="title">NSFW FLUX Image Generator</div>')
517
+
518
+ # 简化警告信息
519
+ gr.HTML('''
520
+ <div class="warning-box">
521
+ ⚠️ 18+ CONTENT WARNING ⚠️
522
+ </div>
523
+ ''')
524
+
525
+ # 主要输入区域
526
  with gr.Row():
527
+ # 左侧:提示词输入
528
+ with gr.Column(scale=2):
529
+ prompt_input = gr.Textbox(
530
+ label="Detailed Prompt",
531
+ placeholder="Enter your detailed prompt here...",
532
+ lines=12,
533
+ elem_classes=["prompt-box"]
534
+ )
535
+
536
+ negative_prompt_input = gr.Textbox(
537
+ label="Negative Prompt (Optional)",
538
+ placeholder="Things you don't want in the image...",
539
+ lines=4,
540
+ elem_classes=["prompt-box"]
541
+ )
542
+
543
+ # 右侧:控制选项
544
+ with gr.Column(scale=1):
545
+ # Style 选项
546
+ with gr.Group(elem_classes=["controls-section"]):
547
+ style_input = gr.Radio(
548
+ label="Style Preset",
549
+ choices=list(STYLE_PRESETS.keys()),
550
+ value="Realistic"
551
+ )
552
+
553
+ # Seed 选项
554
+ with gr.Group(elem_classes=["controls-section"]):
555
+ seed_input = gr.Number(
556
+ label="Seed (-1 for random)",
557
+ value=-1,
558
+ precision=0
559
+ )
560
+
561
+ # 宽度选择
562
+ with gr.Group(elem_classes=["controls-section"]):
563
+ width_input = gr.Slider(
564
+ label="Width",
565
+ minimum=512,
566
+ maximum=2048,
567
+ value=1024,
568
+ step=64
569
+ )
570
+
571
+ # 高度选择
572
+ with gr.Group(elem_classes=["controls-section"]):
573
+ height_input = gr.Slider(
574
+ label="Height",
575
+ minimum=512,
576
+ maximum=2048,
577
+ value=1024,
578
+ step=64
579
+ )
580
+
581
+ # 高级参数
582
+ with gr.Group(elem_classes=["controls-section"]):
583
+ steps_input = gr.Slider(
584
+ label="Steps",
585
+ minimum=10,
586
+ maximum=50,
587
+ value=30,
588
+ step=1
589
+ )
590
+
591
+ cfg_input = gr.Slider(
592
+ label="CFG Scale",
593
+ minimum=1.0,
594
+ maximum=15.0,
595
+ value=7.0,
596
+ step=0.1
597
+ )
598
+
599
+ # 生成按钮
600
+ generate_button = gr.Button(
601
+ "GENERATE",
602
+ elem_classes=["generate-btn"],
603
+ variant="primary"
604
+ )
605
+
606
+ # 图片输出区域
607
+ image_output = gr.Image(
608
+ label="Generated Image",
609
+ elem_classes=["image-output"],
610
+ show_label=False,
611
+ container=True
612
+ )
613
+
614
+ # 图片信息和保存按钮
615
+ generation_info = gr.Textbox(
616
+ label="Generation Info",
617
+ interactive=False,
618
+ elem_classes=["image-info"],
619
+ show_label=False,
620
+ visible=False
621
+ )
622
+
623
+ # 隐藏的变量存储
624
+ metadata_content = gr.Textbox(visible=False)
625
+ current_seed = gr.Number(visible=False)
626
+ current_image = gr.Image(visible=False)
627
+
628
+ # 下载按钮区域
629
+ with gr.Row(visible=False) as download_row:
630
+ download_image_btn = gr.Button(
631
+ "Save Image",
632
+ elem_classes=["save-btn"],
633
+ size="sm"
634
  )
635
+
636
+ download_metadata_btn = gr.Button(
637
+ "Save Metadata",
638
+ elem_classes=["save-btn"],
639
+ size="sm"
 
 
640
  )
641
+
642
+ # 生成图片的主要函数
643
+ def on_generate(prompt, style, neg_prompt, steps, cfg, seed, width, height):
644
+ image, info, metadata = generate_image(
645
+ prompt, style, neg_prompt, steps, cfg, seed, width, height
646
+ )
647
+
648
+ if image is not None:
649
+ # 提取实际使用的 seed
650
+ actual_seed = seed if seed != -1 else int(info.split("Seed:")[1].split("|")[0].strip())
651
+
652
+ return (
653
+ image, # 图片输出
654
+ info, # 生成信息
655
+ metadata, # 元数据
656
+ actual_seed, # 当前 seed
657
+ image, # 当前图片副本
658
+ gr.update(visible=True), # 显示生成信息
659
+ gr.update(visible=True) # 显示下载按钮区域
660
+ )
661
+ else:
662
+ return (
663
+ None,
664
+ info,
665
+ "",
666
+ 0,
667
+ None,
668
+ gr.update(visible=False),
669
+ gr.update(visible=False)
670
+ )
671
+
672
+ # 创建下载文件的函数
673
+ def create_download_image(image_data, seed_val):
674
+ if image_data is not None:
675
+ filename = f"IMG_{seed_val}.png"
676
+ filepath = os.path.join(SAVE_DIR, filename)
677
+ image_data.save(filepath, quality=95, optimize=True)
678
+ return filepath
679
+ return None
680
+
681
+ def create_download_metadata(metadata_text, seed_val):
682
+ if metadata_text:
683
+ filename = f"IMG_{seed_val}.txt"
684
+ filepath = os.path.join(SAVE_DIR, filename)
685
+ with open(filepath, 'w', encoding='utf-8') as f:
686
+ f.write(metadata_text)
687
+ return filepath
688
+ return None
689
+
690
+ # 绑定生成事件
691
+ generate_button.click(
692
+ fn=on_generate,
693
+ inputs=[
694
+ prompt_input, style_input, negative_prompt_input,
695
+ steps_input, cfg_input, seed_input, width_input, height_input
696
+ ],
697
+ outputs=[
698
+ image_output, generation_info, metadata_content,
699
+ current_seed, current_image, generation_info, download_row
700
+ ],
701
+ show_progress=True
702
+ )
703
+
704
+ # 支持 Enter 键触发
705
+ prompt_input.submit(
706
+ fn=on_generate,
707
+ inputs=[
708
+ prompt_input, style_input, negative_prompt_input,
709
+ steps_input, cfg_input, seed_input, width_input, height_input
710
+ ],
711
+ outputs=[
712
+ image_output, generation_info, metadata_content,
713
+ current_seed, current_image, generation_info, download_row
714
+ ],
715
+ show_progress=True
716
+ )
717
+
718
+ # 下载图片
719
+ def handle_image_download(image_data, seed_val):
720
+ filepath = create_download_image(image_data, seed_val)
721
+ if filepath:
722
+ return gr.File(value=filepath, visible=True)
723
+ return gr.File(visible=False)
724
+
725
+ download_image_btn.click(
726
+ fn=handle_image_download,
727
+ inputs=[current_image, current_seed],
728
+ outputs=[gr.File()]
729
+ )
730
+
731
+ # 下载元数据
732
+ def handle_metadata_download(metadata_text, seed_val):
733
+ filepath = create_download_metadata(metadata_text, seed_val)
734
+ if filepath:
735
+ return gr.File(value=filepath, visible=True)
736
+ return gr.File(visible=False)
737
+
738
+ download_metadata_btn.click(
739
+ fn=handle_metadata_download,
740
+ inputs=[metadata_content, current_seed],
741
+ outputs=[gr.File()]
742
+ )
743
+
744
+ # 启动时显示欢迎信息
745
+ interface.load(
746
+ fn=lambda: (
747
+ None, "", "", 0, None,
748
+ gr.update(visible=False),
749
+ gr.update(visible=False)
750
+ ),
751
+ outputs=[
752
+ image_output, generation_info, metadata_content,
753
+ current_seed, current_image, generation_info, download_row
754
+ ]
755
+ )
756
+
757
+ return interface
758
 
759
+ # ===== 启动应用 =====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760
  if __name__ == "__main__":
761
+ print("🎨 Starting NSFW FLUX Image Generator...")
762
+ print(f"🔧 Fixed Model: {FIXED_MODEL}")
763
+ print(f"🔧 NSFW LoRA: {QUALITY_LORA['description']}")
764
+ print(f"🔧 Spaces GPU: {'✅ Available' if SPACES_AVAILABLE else '❌ Not Available'}")
765
+ print(f"🔧 Compel Library: {'✅ Available' if COMPEL_AVAILABLE else '❌ Not Available'}")
766
+ print(f"🔧 CUDA: {'✅ Available' if torch.cuda.is_available() else '❌ Not Available'}")
767
+
768
+ app = create_interface()
769
+ app.queue(max_size=10, default_concurrency_limit=2)
770
+
771
+ app.launch(
772
+ server_name="0.0.0.0",
773
+ server_port=7860,
774
+ show_error=True,
775
+ share=False
776
+ )