# ===== 必须首先导入spaces ===== try: import spaces SPACES_AVAILABLE = True print("✅ Spaces available - ZeroGPU mode") except ImportError: SPACES_AVAILABLE = False print("⚠️ Spaces not available - running in regular mode") # ===== 其他导入 ===== import os import uuid from datetime import datetime import random import torch import gradio as gr from diffusers import StableDiffusionXLPipeline, EulerDiscreteScheduler from PIL import Image import traceback import numpy as np import io import base64 # ===== 长提示词处理 ===== try: from compel import Compel, ReturnedEmbeddingsType COMPEL_AVAILABLE = True print("✅ Compel available for long prompt processing") except ImportError: COMPEL_AVAILABLE = False print("⚠️ Compel not available - using standard prompt processing") # ===== 修复后的配置 ===== STYLE_PRESETS = { "None": "", "Realistic": "photorealistic, 8k, ultra-detailed, cinematic lighting, masterpiece, realistic skin texture, detailed anatomy, professional photography", "Anime": "anime style, detailed, high quality, masterpiece, best quality, detailed eyes, perfect anatomy, vibrant colors, clean art style", "Comic": "comic book style, bold outlines, vibrant colors, cel shading, dynamic pose, graphic illustration", "Watercolor": "watercolor illustration, soft gradients, pastel palette, artistic brush strokes, delicate textures" } # 固定模型配置 FIXED_MODEL = "votepurchase/pornmasterPro_noobV3VAE" # 固定LoRA配置 - 只保留Quality QUALITY_LORA = { "repo_id": "artificialguybr/LogoRedmond-LogoLoraForSDXL-V2", "filename": "LogoRedAF.safetensors", "scale": 0.8, "description": "Quality and realism enhancer" } # 质量增强提示词 QUALITY_ENHANCERS = [ "detailed anatomy", "(perfect anatomy:1.2)", "soft skin", "natural lighting", "high resolution", "(masterpiece:1.3)", "(best quality:1.2)", "professional photography", "artistic composition", "(perfect proportions:1.1)", "smooth textures", "intimate lighting", "realistic skin texture", "(detailed face:1.1)", "natural pose" ] # 修复后的风格专用增强词 - 更明显的差异 STYLE_ENHANCERS = { "Realistic": [ "photorealistic", "(ultra realistic:1.3)", "natural lighting", "detailed skin", "professional photography", "(hyperrealistic:1.2)", "lifelike", "raw photo", "cinematic lighting", "depth of field" ], "Anime": [ "anime style", "(high quality anime:1.3)", "detailed eyes", "perfect face", "clean art style", "(anime art:1.2)", "cel animation", "vibrant anime colors", "manga style", "Japanese animation" ], "Comic": [ "comic book style", "(comic art:1.3)", "bold outlines", "vibrant colors", "cel shading", "graphic illustration", "pop art", "cartoon style", "comic book illustration", "graphic novel art" ], "Watercolor": [ "watercolor style", "(watercolor painting:1.3)", "artistic", "soft gradients", "pastel palette", "delicate textures", "paint bleeding", "artistic brush strokes", "traditional art", "painted illustration" ] } SAVE_DIR = "generated_images" os.makedirs(SAVE_DIR, exist_ok=True) # ===== 模型相关变量 ===== pipeline = None compel_processor = None device = None model_loaded = False lora_loaded = False def initialize_model(): """优化的模型初始化函数""" global pipeline, compel_processor, device, model_loaded, lora_loaded if model_loaded and pipeline is not None: return True try: device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"🖥️ Using device: {device}") print(f"📦 Loading fixed model: {FIXED_MODEL}") # 基础模型加载 pipeline = StableDiffusionXLPipeline.from_pretrained( FIXED_MODEL, torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, variant="fp16" if torch.cuda.is_available() else None, use_safetensors=True, safety_checker=None, requires_safety_checker=False ) # 优化调度器 pipeline.scheduler = EulerDiscreteScheduler.from_config( pipeline.scheduler.config, timestep_spacing="trailing" ) pipeline = pipeline.to(device) # 统一数据类型 if torch.cuda.is_available(): pipeline.text_encoder.to(torch.float16) pipeline.text_encoder_2.to(torch.float16) pipeline.vae.to(torch.float16) pipeline.unet.to(torch.float16) # GPU优化 if torch.cuda.is_available(): try: pipeline.enable_vae_slicing() pipeline.enable_attention_slicing() try: pipeline.enable_xformers_memory_efficient_attention() except: pass except Exception as mem_error: print(f"⚠️ Memory optimization warning: {mem_error}") # 加载Quality LoRA try: print("🔧 Loading Quality LoRA...") pipeline.load_lora_weights( QUALITY_LORA["repo_id"], weight_name=QUALITY_LORA["filename"], adapter_name="quality_enhancer" ) pipeline.set_adapters(["quality_enhancer"], adapter_weights=[QUALITY_LORA["scale"]]) lora_loaded = True print("✅ Quality LoRA loaded successfully") except Exception as lora_error: print(f"⚠️ LoRA loading failed: {lora_error}") lora_loaded = False # 初始化Compel if COMPEL_AVAILABLE: try: compel_processor = Compel( tokenizer=[pipeline.tokenizer, pipeline.tokenizer_2], text_encoder=[pipeline.text_encoder, pipeline.text_encoder_2], returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED, requires_pooled=[False, True], truncate_long_prompts=False ) print("✅ Long prompt processor (Compel) initialized successfully") except Exception as compel_error: print(f"⚠️ Compel initialization failed: {compel_error}") compel_processor = None model_loaded = True print("✅ Model initialization complete") return True except Exception as e: print(f"❌ Critical model loading error: {e}") print(traceback.format_exc()) model_loaded = False return False def enhance_prompt(prompt: str, style: str) -> str: """修复后的增强提示词函数""" if not prompt or prompt.strip() == "": return "" enhanced_parts = [prompt.strip()] # 添加风格前缀 style_prefix = STYLE_PRESETS.get(style, "") if style_prefix and style != "None": enhanced_parts.insert(0, style_prefix) # 添加风格特定增强词 if style in STYLE_ENHANCERS and style != "None": style_terms = ", ".join(STYLE_ENHANCERS[style]) enhanced_parts.append(style_terms) # 添加质量增强词 quality_terms = ", ".join(QUALITY_ENHANCERS) enhanced_parts.append(quality_terms) enhanced_prompt = ", ".join(filter(None, enhanced_parts)) print(f"🎨 Style: {style}") print(f"📝 Original prompt: {prompt[:100]}...") print(f"✨ Enhanced prompt: {enhanced_prompt[:150]}...") return enhanced_prompt def process_long_prompt(prompt, negative_prompt=""): """处理长提示词""" if not compel_processor: return None, None try: conditioning, pooled = compel_processor([prompt, negative_prompt]) return conditioning, pooled except Exception as e: print(f"Long prompt processing failed: {e}") return None, None def apply_spaces_decorator(func): """应用spaces装饰器""" if SPACES_AVAILABLE: return spaces.GPU(duration=60)(func) return func def create_metadata_content(prompt, enhanced_prompt, seed, steps, cfg_scale, width, height, style): """创建元数据内容""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return f"""Generated Image Metadata ====================== Timestamp: {timestamp} Original Prompt: {prompt} Enhanced Prompt: {enhanced_prompt} Seed: {seed} Steps: {steps} CFG Scale: {cfg_scale} Dimensions: {width}x{height} Style: {style} Model: PornMasterPro LoRA: Quality Enhancer (scale: 0.8) """ def convert_to_pil_image(image_data): """转换图像数据为PIL Image对象""" if image_data is None: return None # 如果已经是PIL Image,直接返回 if isinstance(image_data, Image.Image): return image_data # 如果是numpy数组,转换为PIL Image if isinstance(image_data, np.ndarray): # 确保数据类型正确 if image_data.dtype != np.uint8: if image_data.max() <= 1.0: image_data = (image_data * 255).astype(np.uint8) else: image_data = image_data.astype(np.uint8) # 转换为PIL Image if len(image_data.shape) == 3: return Image.fromarray(image_data, 'RGB') elif len(image_data.shape) == 2: return Image.fromarray(image_data, 'L') return None @apply_spaces_decorator def generate_image(prompt: str, style: str, negative_prompt: str = "", steps: int = 28, cfg_scale: float = 7.0, seed: int = -1, width: int = 1024, height: int = 1024, progress=gr.Progress()): """图像生成函数""" if not prompt or prompt.strip() == "": return None, "", "" # 初始化模型 progress(0.1, desc="Loading model...") if not initialize_model(): return None, "", "❌ Failed to load model" progress(0.3, desc="Processing prompt...") try: # 处理seed if seed == -1: seed = random.randint(0, np.iinfo(np.int32).max) # 增强提示词 - 确保风格生效 enhanced_prompt = enhance_prompt(prompt.strip(), style) # 增强负面提示词 if not negative_prompt.strip(): 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" # 生成参数 generator = torch.Generator(device).manual_seed(seed) progress(0.5, desc="Generating image...") # 长提示词处理 use_long_prompt = len(enhanced_prompt.split()) > 60 or len(enhanced_prompt) > 300 if use_long_prompt and compel_processor: conditioning, pooled = process_long_prompt(enhanced_prompt, negative_prompt) if conditioning is not None: result = pipeline( prompt_embeds=conditioning[0:1], pooled_prompt_embeds=pooled[0:1], negative_prompt_embeds=conditioning[1:2], negative_pooled_prompt_embeds=pooled[1:2], num_inference_steps=steps, guidance_scale=cfg_scale, width=width, height=height, generator=generator ) image = result.images[0] else: result = pipeline( prompt=enhanced_prompt, negative_prompt=negative_prompt, num_inference_steps=steps, guidance_scale=cfg_scale, width=width, height=height, generator=generator ) image = result.images[0] else: result = pipeline( prompt=enhanced_prompt, negative_prompt=negative_prompt, num_inference_steps=steps, guidance_scale=cfg_scale, width=width, height=height, generator=generator ) image = result.images[0] progress(0.9, desc="Processing output...") # 确保图像是PIL Image对象 image = convert_to_pil_image(image) if image is None: return None, "", "❌ Failed to convert image" # 创建元数据内容 metadata_content = create_metadata_content( prompt, enhanced_prompt, seed, steps, cfg_scale, width, height, style ) progress(1.0, desc="Complete!") # 生成信息显示 generation_info = f"Style: {style} | Seed: {seed} | Size: {width}×{height} | Steps: {steps} | CFG: {cfg_scale}" return image, generation_info, metadata_content except Exception as e: error_msg = str(e) print(f"Generation error: {error_msg}") print(traceback.format_exc()) return None, "", f"❌ Generation failed: {error_msg}" # ===== CSS样式 - 修复版 ===== css = """ /* 全局容器 */ .gradio-container { max-width: 100% !important; margin: 0 !important; padding: 0 !important; background: linear-gradient(135deg, #e6a4f2 0%, #1197e4 100%) !important; min-height: 100vh !important; font-family: 'Segoe UI', Arial, sans-serif !important; } /* 主要内容区域 */ .main-content { background: rgba(255, 255, 255, 0.9) !important; border-radius: 20px !important; padding: 20px !important; margin: 15px !important; box-shadow: 0 10px 25px rgba(255, 255, 255, 0.2) !important; min-height: calc(100vh - 30px) !important; color: #3e3e3e !important; backdrop-filter: blur(10px) !important; } /* 简化标题 */ .title { text-align: center !important; background: linear-gradient(45deg, #bb6ded, #08676b) !important; -webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important; background-clip: text !important; font-size: 2rem !important; margin-bottom: 15px !important; font-weight: bold !important; } /* 简化警告信息 */ .warning-box { background: linear-gradient(45deg, #bb6ded, #08676b) !important; color: white !important; padding: 8px !important; border-radius: 8px !important; margin-bottom: 15px !important; text-align: center !important; font-weight: bold !important; font-size: 14px !important; } /* 输入框样式 - 修复背景色 */ .prompt-box textarea, .prompt-box input { border-radius: 10px !important; border: 2px solid #bb6ded !important; padding: 15px !important; font-size: 18px !important; background: linear-gradient(135deg, rgba(245, 243, 255, 0.9), rgba(237, 233, 254, 0.9)) !important; color: #2d2d2d !important; } .prompt-box textarea:focus, .prompt-box input:focus { border-color: #08676b !important; box-shadow: 0 0 15px rgba(77, 8, 161, 0.3) !important; background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(248, 249, 250, 0.95)) !important; } /* 右侧控制区域 - 修复背景色 */ .controls-section { background: linear-gradient(135deg, rgba(224, 218, 255, 0.8), rgba(196, 181, 253, 0.8)) !important; border-radius: 12px !important; padding: 15px !important; margin-bottom: 8px !important; border: 2px solid rgba(187, 109, 237, 0.3) !important; backdrop-filter: blur(5px) !important; } .controls-section label { font-weight: 600 !important; color: #2d2d2d !important; margin-bottom: 8px !important; } /* 修复单选按钮和输入框背景 */ .controls-section input[type="radio"] { accent-color: #bb6ded !important; } .controls-section input[type="number"], .controls-section input[type="range"] { background: rgba(255, 255, 255, 0.9) !important; border: 1px solid #bb6ded !important; border-radius: 6px !important; padding: 8px !important; color: #2d2d2d !important; } .controls-section select { background: rgba(255, 255, 255, 0.9) !important; border: 1px solid #bb6ded !important; border-radius: 6px !important; padding: 8px !important; color: #2d2d2d !important; } /* 生成按钮 */ .generate-btn { background: linear-gradient(45deg, #bb6ded, #08676b) !important; color: white !important; border: none !important; padding: 15px 25px !important; border-radius: 25px !important; font-size: 16px !important; font-weight: bold !important; width: 100% !important; cursor: pointer !important; transition: all 0.3s ease !important; text-transform: uppercase !important; letter-spacing: 1px !important; } .generate-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 8px 25px rgba(187, 109, 237, 0.5) !important; } /* 图片输出区域 */ .image-output { border-radius: 15px !important; overflow: hidden !important; max-width: 100% !important; max-height: 70vh !important; border: 3px solid #08676b !important; box-shadow: 0 8px 20px rgba(0,0,0,0.15) !important; background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(248, 249, 250, 0.9)) !important; } /* 图片信息区域 */ .image-info { background: linear-gradient(135deg, rgba(248, 249, 250, 0.2), rgba(233, 236, 239, 0.9)) !important; border-radius: 8px !important; padding: 12px !important; margin-top: 10px !important; font-size: 12px !important; color: #495057 !important; border: 2px solid rgba(187, 109, 237, 0.2) !important; backdrop-filter: blur(5px) !important; } /* 元数据区域样式 */ .metadata-box { background: linear-gradient(135deg, rgba(248, 249, 250, 0.2), rgba(233, 236, 239, 0.9)) !important; border-radius: 8px !important; padding: 15px !important; margin-top: 15px !important; font-family: 'Courier New', monospace !important; font-size: 12px !important; color: #495057 !important; border: 2px solid rgba(187, 109, 237, 0.2) !important; backdrop-filter: blur(5px) !important; white-space: pre-wrap !important; overflow-y: auto !important; max-height: 300px !important; } /* 滑块样式 */ .slider-container input[type="range"] { accent-color: #bb6ded !important; } /* 响应式设计 */ @media (max-width: 768px) { .main-content { margin: 10px !important; padding: 15px !important; } .title { font-size: 1.5rem !important; } } /* 隐藏占位符 */ .gr-image .image-container:empty::before { content: "Generated image will appear here" !important; display: flex !important; align-items: center !important; justify-content: center !important; height: 300px !important; background: linear-gradient(135deg, rgba(248, 249, 250, 0.8), rgba(233, 236, 239, 0.8)) !important; border-radius: 10px !important; color: #6c757d !important; font-size: 16px !important; font-weight: 500 !important; backdrop-filter: blur(5px) !important; /* 强制覆盖Gradio默认样式 */ .gradio-container .gr-textbox, .gradio-container .gr-radio-group, .gradio-container .gr-slider, .gradio-container .gr-number { background: rgba(255, 255, 255, 0.95) !important; border: 1px solid rgba(187, 109, 237, 0.5) !important; border-radius: 8px !important; } .gradio-container .gr-radio-group label { color: #2d2d2d !important; background: transparent !important; } """ # ===== 创建UI ===== def create_interface(): with gr.Blocks(css=css, title="Adult NSFW AI Image Generator") as interface: with gr.Column(elem_classes=["main-content"]): # 简化标题 gr.HTML('
Adult NSFW AI Image Generator
') # 简化警告信息 gr.HTML('''
⚠️ 18+ CONTENT WARNING ⚠️
''') # 主要输入区域 with gr.Row(): # 左侧:提示词输入 with gr.Column(scale=2): prompt_input = gr.Textbox( label="Detailed Prompt", placeholder="Enter your detailed prompt here...", lines=15, elem_classes=["prompt-box"] ) negative_prompt_input = gr.Textbox( label="Negative Prompt (Optional)", placeholder="Things you don't want in the image...", lines=4, elem_classes=["prompt-box"] ) # 右侧:控制选项 with gr.Column(scale=1): # Style选项 with gr.Group(elem_classes=["controls-section"]): style_input = gr.Radio( label="Style Preset", choices=list(STYLE_PRESETS.keys()), value="Realistic" ) # Seed选项 with gr.Group(elem_classes=["controls-section"]): seed_input = gr.Number( label="Seed (-1 for random)", value=-1, precision=0 ) # 宽度选择 with gr.Group(elem_classes=["controls-section"]): width_input = gr.Slider( label="Width", minimum=512, maximum=2048, value=1024, step=64 ) # 高度选择 with gr.Group(elem_classes=["controls-section"]): height_input = gr.Slider( label="Height", minimum=512, maximum=2048, value=1024, step=64 ) # 高级参数 with gr.Group(elem_classes=["controls-section"]): steps_input = gr.Slider( label="Steps", minimum=10, maximum=50, value=28, step=1 ) cfg_input = gr.Slider( label="CFG Scale", minimum=1.0, maximum=15.0, value=7.0, step=0.1 ) # 生成按钮 generate_button = gr.Button( "GENERATE", elem_classes=["generate-btn"], variant="primary" ) # 图片输出区域 image_output = gr.Image( label="Generated Image", elem_classes=["image-output"], show_label=False, container=True ) # 图片信息显示 with gr.Row(): generation_info = gr.Textbox( label="Generation Info", interactive=False, elem_classes=["image-info"], show_label=True, visible=False ) # 元数据显示区域 - 移除下载功能,只显示可复制的文本 with gr.Row(): metadata_display = gr.Textbox( label="Image Metadata (Copy to save)", interactive=True, elem_classes=["metadata-box"], show_label=True, lines=15, visible=False, placeholder="Generated image metadata will appear here..." ) # 生成图片的主要函数 def on_generate(prompt, style, neg_prompt, steps, cfg, seed, width, height): image, info, metadata = generate_image( prompt, style, neg_prompt, steps, cfg, seed, width, height ) if image is not None: return ( image, # 图片输出 info, # 生成信息 metadata, # 元数据 gr.update(visible=True, value=info), # 显示生成信息 gr.update(visible=True, value=metadata) # 显示元数据 ) else: return ( None, info, "", gr.update(visible=False), gr.update(visible=False) ) # 绑定生成事件 generate_button.click( fn=on_generate, inputs=[ prompt_input, style_input, negative_prompt_input, steps_input, cfg_input, seed_input, width_input, height_input ], outputs=[ image_output, generation_info, metadata_display, generation_info, metadata_display ], show_progress=True ) # 支持Enter键触发 prompt_input.submit( fn=on_generate, inputs=[ prompt_input, style_input, negative_prompt_input, steps_input, cfg_input, seed_input, width_input, height_input ], outputs=[ image_output, generation_info, metadata_display, generation_info, metadata_display ], show_progress=True ) # 启动时显示欢迎信息 interface.load( fn=lambda: ( None, "", "", gr.update(visible=False), gr.update(visible=False) ), outputs=[ image_output, generation_info, metadata_display, generation_info, metadata_display ] ) return interface # ===== 启动应用 ===== if __name__ == "__main__": print("🎨 Starting Simplified NSFW Image Generator...") print(f"🔧 Fixed Model: {FIXED_MODEL}") print(f"🔧 Quality LoRA: {QUALITY_LORA['description']}") print(f"🔧 Spaces GPU: {'✅ Available' if SPACES_AVAILABLE else '❌ Not Available'}") print(f"🔧 Compel Library: {'✅ Available' if COMPEL_AVAILABLE else '❌ Not Available'}") print(f"🔧 CUDA: {'✅ Available' if torch.cuda.is_available() else '❌ Not Available'}") app = create_interface() app.queue(max_size=10, default_concurrency_limit=2) app.launch( server_name="0.0.0.0", server_port=7860, show_error=True, share=False )