|
|
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 gradio as gr |
|
|
import torch |
|
|
from diffusers import DiffusionPipeline, StableDiffusionXLPipeline |
|
|
from PIL import Image |
|
|
import datetime |
|
|
import io |
|
|
import json |
|
|
import os |
|
|
import re |
|
|
from typing import Optional, List, Dict |
|
|
import numpy as np |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MODEL_CONFIGS = { |
|
|
"pornmasterPro_noobV3VAE": { |
|
|
"repo_id": "votepurchase/pornmasterPro_noobV3VAE", |
|
|
"type": "sdxl", |
|
|
"requires_safety_checker": False, |
|
|
"default_negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry", |
|
|
"optimal_settings": { |
|
|
"steps": 28, |
|
|
"cfg": 7.0, |
|
|
"sampler": "DPM++ 2M Karras" |
|
|
}, |
|
|
"description": "pornmasterPro_noobV3VAE - High-quality NSFW Image generator" |
|
|
}, |
|
|
"wai_nsfw_illustrious_v80": { |
|
|
"repo_id": "John6666/wai-nsfw-illustrious-v80-sdxl", |
|
|
"type": "sdxl", |
|
|
"requires_safety_checker": False, |
|
|
"default_negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry", |
|
|
"optimal_settings": { |
|
|
"steps": 28, |
|
|
"cfg": 7.0, |
|
|
"sampler": "DPM++ 2M Karras" |
|
|
}, |
|
|
"description": "WAI NSFW Illustrious v8.0 - High-quality illustration-style mockups" |
|
|
}, |
|
|
"wai_nsfw_illustrious_v90": { |
|
|
"repo_id": "John6666/wai-nsfw-illustrious-v90-sdxl", |
|
|
"type": "sdxl", |
|
|
"requires_safety_checker": False, |
|
|
"default_negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry", |
|
|
"optimal_settings": { |
|
|
"steps": 28, |
|
|
"cfg": 7.0, |
|
|
"sampler": "DPM++ 2M Karras" |
|
|
}, |
|
|
"description": "WAI NSFW Illustrious v9.0 - Latest version" |
|
|
}, |
|
|
"wai_nsfw_illustrious_v110": { |
|
|
"repo_id": "John6666/wai-nsfw-illustrious-v110-sdxl", |
|
|
"type": "sdxl", |
|
|
"requires_safety_checker": False, |
|
|
"default_negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry", |
|
|
"optimal_settings": { |
|
|
"steps": 30, |
|
|
"cfg": 7.5, |
|
|
"sampler": "DPM++ 2M Karras" |
|
|
}, |
|
|
"description": "WAI NSFW Illustrious v11.0 - Enhanced version" |
|
|
}, |
|
|
"sdxl_base": { |
|
|
"repo_id": "stabilityai/stable-diffusion-xl-base-1.0", |
|
|
"type": "sdxl", |
|
|
"requires_safety_checker": True, |
|
|
"default_negative": "blurry, low quality, deformed, cartoon, anime, text, watermark, signature, username, worst quality, low res, bad anatomy, bad hands", |
|
|
"optimal_settings": { |
|
|
"steps": 30, |
|
|
"cfg": 7.5, |
|
|
"sampler": "Default" |
|
|
}, |
|
|
"description": "Stable Diffusion XL Base 1.0 - Official base model" |
|
|
}, |
|
|
"realistic_vision": { |
|
|
"repo_id": "SG161222/RealVisXL_V4.0", |
|
|
"type": "sdxl", |
|
|
"requires_safety_checker": False, |
|
|
"default_negative": "blurry, low quality, deformed, text, watermark, signature, worst quality, bad anatomy", |
|
|
"optimal_settings": { |
|
|
"steps": 30, |
|
|
"cfg": 7.5, |
|
|
"sampler": "Default" |
|
|
}, |
|
|
"description": "RealVisXL V4.0 - High-quality realistic style" |
|
|
}, |
|
|
"anime_xl": { |
|
|
"repo_id": "Linaqruf/animagine-xl-3.1", |
|
|
"type": "sdxl", |
|
|
"requires_safety_checker": False, |
|
|
"default_negative": "lowres, bad anatomy, text, error, cropped, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated", |
|
|
"optimal_settings": { |
|
|
"steps": 28, |
|
|
"cfg": 7.0, |
|
|
"sampler": "Default" |
|
|
}, |
|
|
"description": "Animagine XL 3.1 - Anime style" |
|
|
}, |
|
|
"juggernaut_xl": { |
|
|
"repo_id": "RunDiffusion/Juggernaut-XL-v9", |
|
|
"type": "sdxl", |
|
|
"requires_safety_checker": False, |
|
|
"default_negative": "blurry, low quality, text, watermark, signature, worst quality", |
|
|
"optimal_settings": { |
|
|
"steps": 30, |
|
|
"cfg": 7.5, |
|
|
"sampler": "Default" |
|
|
}, |
|
|
"description": "Juggernaut XL v9 - Universal high-quality models" |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
DEFAULT_MODEL_KEY = "pornmasterPro_noobV3VAE" |
|
|
|
|
|
|
|
|
FIXED_LORAS = { |
|
|
"detail_enhancer": { |
|
|
"repo_id": "ostris/ikea-instructions-lora-sdxl", |
|
|
"filename": None, |
|
|
"weight": 0.5, |
|
|
"trigger_words": "high quality, detailed", |
|
|
"enabled": True |
|
|
}, |
|
|
"quality_boost": { |
|
|
"repo_id": "stabilityai/stable-diffusion-xl-offset-example-lora", |
|
|
"filename": None, |
|
|
"weight": 0.4, |
|
|
"trigger_words": "masterpiece, best quality", |
|
|
"enabled": True |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
STYLE_PROMPTS = { |
|
|
"None": "", |
|
|
"Realistic Photo": "photorealistic, ultra-detailed, natural lighting, 8k uhd, professional photography, DSLR, high quality, masterpiece, ", |
|
|
"Anime/Illustration": "anime style, high quality illustration, vibrant colors, detailed, masterpiece, best quality, ", |
|
|
"Artistic Illustration": "artistic illustration, painterly, detailed artwork, high quality, professional illustration, ", |
|
|
"Comic Book": "comic book style, bold lines, dynamic composition, pop art, high quality, ", |
|
|
"Watercolor": "watercolor painting, soft brush strokes, artistic, traditional art, masterpiece, ", |
|
|
"Cinematic": "cinematic lighting, dramatic atmosphere, film grain, professional color grading, high quality, ", |
|
|
} |
|
|
|
|
|
|
|
|
OPTIONAL_LORAS = { |
|
|
"None": { |
|
|
"repo_id": None, |
|
|
"weight": 0.0, |
|
|
"trigger_words": "", |
|
|
"description": "No additional LoRA" |
|
|
}, |
|
|
"Offset Noise": { |
|
|
"repo_id": "stabilityai/stable-diffusion-xl-offset-example-lora", |
|
|
"weight": 0.7, |
|
|
"trigger_words": "high contrast, dramatic lighting", |
|
|
"description": "Enhance contrast and lighting effects" |
|
|
}, |
|
|
"LCM LoRA": { |
|
|
"repo_id": "latent-consistency/lcm-lora-sdxl", |
|
|
"weight": 0.8, |
|
|
"trigger_words": "high quality", |
|
|
"description": "Rapid Generation Mode" |
|
|
}, |
|
|
"Pixel Art": { |
|
|
"repo_id": "nerijs/pixel-art-xl", |
|
|
"weight": 0.9, |
|
|
"trigger_words": "pixel art style, 8bit, retro", |
|
|
"description": "Pixel art style" |
|
|
}, |
|
|
"Watercolor": { |
|
|
"repo_id": "ostris/watercolor-style-lora-sdxl", |
|
|
"weight": 0.8, |
|
|
"trigger_words": "watercolor painting, soft colors", |
|
|
"description": "Watercolor style" |
|
|
}, |
|
|
"Sketch": { |
|
|
"repo_id": "ostris/crayon-style-lora-sdxl", |
|
|
"weight": 0.7, |
|
|
"trigger_words": "sketch style, pencil drawing", |
|
|
"description": "Sketch Style" |
|
|
}, |
|
|
"Portrait": { |
|
|
"repo_id": "ostris/face-helper-sdxl-lora", |
|
|
"weight": 0.8, |
|
|
"trigger_words": "portrait, beautiful face, detailed eyes", |
|
|
"description": "Portrait and face enhancement" |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
DEFAULT_SEED = -1 |
|
|
DEFAULT_WIDTH = 1024 |
|
|
DEFAULT_HEIGHT = 1024 |
|
|
DEFAULT_LORA_SCALE = 0.8 |
|
|
DEFAULT_STEPS = 28 |
|
|
DEFAULT_CFG = 7.0 |
|
|
|
|
|
|
|
|
SUPPORTED_LANGUAGES = { |
|
|
"en": "English", |
|
|
"zh": "中文", |
|
|
"ja": "日本語", |
|
|
"ko": "한국어" |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pipe = None |
|
|
current_model_key = None |
|
|
current_loras = {} |
|
|
device = "cuda" if torch.cuda.is_available() else "cpu" |
|
|
|
|
|
def load_pipeline(model_key: str = None): |
|
|
"""灵活加载pipeline,支持不同模型""" |
|
|
global pipe, current_model_key |
|
|
|
|
|
if model_key is None: |
|
|
model_key = DEFAULT_MODEL_KEY |
|
|
|
|
|
|
|
|
if pipe is not None and current_model_key == model_key: |
|
|
return pipe |
|
|
|
|
|
|
|
|
if pipe is not None: |
|
|
unload_pipeline() |
|
|
|
|
|
model_config = MODEL_CONFIGS.get(model_key) |
|
|
if not model_config: |
|
|
raise ValueError(f"未知的模型配置: {model_key}") |
|
|
|
|
|
print(f"🚀 加载模型: {model_config['description']} ({model_config['repo_id']})") |
|
|
|
|
|
try: |
|
|
|
|
|
if model_config["type"] == "sdxl": |
|
|
pipe = StableDiffusionXLPipeline.from_pretrained( |
|
|
model_config["repo_id"], |
|
|
torch_dtype=torch.float16, |
|
|
use_safetensors=True, |
|
|
variant="fp16", |
|
|
safety_checker=None if not model_config["requires_safety_checker"] else "default" |
|
|
).to(device) |
|
|
|
|
|
|
|
|
pipe.enable_attention_slicing() |
|
|
pipe.enable_vae_slicing() |
|
|
if hasattr(pipe, 'enable_model_cpu_offload'): |
|
|
pipe.enable_model_cpu_offload() |
|
|
if hasattr(pipe, 'enable_xformers_memory_efficient_attention'): |
|
|
try: |
|
|
pipe.enable_xformers_memory_efficient_attention() |
|
|
except: |
|
|
print("⚠️ xformers不可用,跳过") |
|
|
|
|
|
current_model_key = model_key |
|
|
print(f"✅ 成功加载模型: {model_config['description']}") |
|
|
return pipe |
|
|
else: |
|
|
raise ValueError(f"不支持的模型类型: {model_config['type']}") |
|
|
|
|
|
except Exception as e: |
|
|
print(f"❌ 加载模型失败: {e}") |
|
|
|
|
|
if model_key != "sdxl_base": |
|
|
print("🔄 尝试加载备用模型...") |
|
|
return load_pipeline("sdxl_base") |
|
|
else: |
|
|
raise Exception("无法加载任何模型") |
|
|
|
|
|
def unload_pipeline(): |
|
|
"""卸载pipeline释放内存""" |
|
|
global pipe, current_loras, current_model_key |
|
|
if pipe is not None: |
|
|
try: |
|
|
pipe.unload_lora_weights() |
|
|
except: |
|
|
pass |
|
|
del pipe |
|
|
torch.cuda.empty_cache() |
|
|
pipe = None |
|
|
current_loras = {} |
|
|
current_model_key = None |
|
|
print("🗑️ Pipeline已卸载") |
|
|
|
|
|
def load_lora_weights(lora_configs: List[Dict]): |
|
|
"""加载多个LoRA权重,带错误处理""" |
|
|
global pipe, current_loras |
|
|
|
|
|
if not lora_configs: |
|
|
return |
|
|
|
|
|
|
|
|
new_lora_ids = [config['repo_id'] for config in lora_configs if config['repo_id']] |
|
|
if set(current_loras.keys()) != set(new_lora_ids): |
|
|
try: |
|
|
pipe.unload_lora_weights() |
|
|
current_loras = {} |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
adapter_names = [] |
|
|
adapter_weights = [] |
|
|
|
|
|
for config in lora_configs: |
|
|
if config['repo_id'] and config['repo_id'] not in current_loras: |
|
|
try: |
|
|
adapter_name = config['name'].replace(' ', '_').lower() |
|
|
pipe.load_lora_weights( |
|
|
config['repo_id'], |
|
|
adapter_name=adapter_name |
|
|
) |
|
|
current_loras[config['repo_id']] = adapter_name |
|
|
print(f"✅ 加载LoRA: {config['name']}") |
|
|
except Exception as e: |
|
|
print(f"⚠️ LoRA加载失败 {config['name']}: {e}") |
|
|
continue |
|
|
|
|
|
if config['repo_id'] in current_loras: |
|
|
adapter_names.append(current_loras[config['repo_id']]) |
|
|
adapter_weights.append(config['weight']) |
|
|
|
|
|
|
|
|
if adapter_names: |
|
|
try: |
|
|
pipe.set_adapters(adapter_names, adapter_weights=adapter_weights) |
|
|
print(f"✅ 激活了 {len(adapter_names)} 个LoRA") |
|
|
except Exception as e: |
|
|
print(f"⚠️ 设置adapter权重警告: {e}") |
|
|
try: |
|
|
pipe.set_adapters(adapter_names) |
|
|
except: |
|
|
print("❌ 无法设置任何adapter") |
|
|
|
|
|
def process_long_prompt(prompt: str, max_length: int = 77) -> str: |
|
|
"""处理长提示词""" |
|
|
if len(prompt.split()) <= max_length: |
|
|
return prompt |
|
|
|
|
|
sentences = re.split(r'[.!?]+', prompt) |
|
|
sentences = [s.strip() for s in sentences if s.strip()] |
|
|
|
|
|
if sentences: |
|
|
result = sentences[0] |
|
|
remaining = max_length - len(result.split()) |
|
|
|
|
|
for sentence in sentences[1:]: |
|
|
words = sentence.split() |
|
|
if len(words) <= remaining: |
|
|
result += ". " + sentence |
|
|
remaining -= len(words) |
|
|
else: |
|
|
important_words = [w for w in words if len(w) > 3][:remaining] |
|
|
if important_words: |
|
|
result += ". " + " ".join(important_words) |
|
|
break |
|
|
|
|
|
return result |
|
|
|
|
|
return " ".join(prompt.split()[:max_length]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@spaces.GPU(duration=60) if SPACES_AVAILABLE else lambda x: x |
|
|
def generate_image( |
|
|
model_key: str, |
|
|
prompt: str, |
|
|
negative_prompt: str, |
|
|
style: str, |
|
|
seed: int, |
|
|
width: int, |
|
|
height: int, |
|
|
selected_loras: List[str], |
|
|
lora_scale: float, |
|
|
steps: int, |
|
|
cfg_scale: float, |
|
|
use_fixed_loras: bool, |
|
|
language: str = "en" |
|
|
): |
|
|
"""主图像生成函数,支持ZeroGPU优化""" |
|
|
global pipe |
|
|
|
|
|
try: |
|
|
|
|
|
pipe = load_pipeline(model_key) |
|
|
model_config = MODEL_CONFIGS[model_key] |
|
|
|
|
|
|
|
|
if seed == -1: |
|
|
seed = torch.randint(0, 2**32, (1,)).item() |
|
|
generator = torch.Generator(device=device).manual_seed(seed) |
|
|
|
|
|
|
|
|
style_prefix = STYLE_PROMPTS.get(style, "") |
|
|
processed_prompt = process_long_prompt(style_prefix + prompt, max_length=150) |
|
|
|
|
|
|
|
|
if not negative_prompt.strip(): |
|
|
negative_prompt = model_config["default_negative"] |
|
|
processed_negative = process_long_prompt(negative_prompt, max_length=100) |
|
|
|
|
|
|
|
|
lora_configs = [] |
|
|
active_trigger_words = [] |
|
|
|
|
|
|
|
|
if use_fixed_loras: |
|
|
for name, config in FIXED_LORAS.items(): |
|
|
if config["repo_id"] and config["enabled"]: |
|
|
lora_configs.append({ |
|
|
'name': name, |
|
|
'repo_id': config["repo_id"], |
|
|
'weight': config["weight"] |
|
|
}) |
|
|
if config["trigger_words"]: |
|
|
active_trigger_words.append(config["trigger_words"]) |
|
|
|
|
|
|
|
|
for lora_name in selected_loras: |
|
|
if lora_name != "None" and lora_name in OPTIONAL_LORAS: |
|
|
config = OPTIONAL_LORAS[lora_name] |
|
|
if config["repo_id"]: |
|
|
lora_configs.append({ |
|
|
'name': lora_name, |
|
|
'repo_id': config["repo_id"], |
|
|
'weight': config["weight"] * lora_scale |
|
|
}) |
|
|
if config["trigger_words"]: |
|
|
active_trigger_words.append(config["trigger_words"]) |
|
|
|
|
|
|
|
|
load_lora_weights(lora_configs) |
|
|
|
|
|
|
|
|
if active_trigger_words: |
|
|
trigger_text = ", ".join(active_trigger_words) |
|
|
final_prompt = f"{processed_prompt}, {trigger_text}" |
|
|
else: |
|
|
final_prompt = processed_prompt |
|
|
|
|
|
|
|
|
with torch.autocast(device): |
|
|
image = pipe( |
|
|
prompt=final_prompt, |
|
|
negative_prompt=processed_negative, |
|
|
num_inference_steps=steps, |
|
|
guidance_scale=cfg_scale, |
|
|
width=width, |
|
|
height=height, |
|
|
generator=generator, |
|
|
).images[0] |
|
|
|
|
|
|
|
|
timestamp = datetime.datetime.now() |
|
|
metadata = { |
|
|
"model": model_config["description"], |
|
|
"model_repo": model_config["repo_id"], |
|
|
"prompt": final_prompt, |
|
|
"original_prompt": prompt, |
|
|
"negative_prompt": processed_negative, |
|
|
"style": style, |
|
|
"fixed_loras_enabled": use_fixed_loras, |
|
|
"fixed_loras": [name for name, config in FIXED_LORAS.items() if config["enabled"]] if use_fixed_loras else [], |
|
|
"selected_loras": [name for name in selected_loras if name != "None"], |
|
|
"lora_scale": lora_scale, |
|
|
"seed": seed, |
|
|
"steps": steps, |
|
|
"cfg_scale": cfg_scale, |
|
|
"width": width, |
|
|
"height": height, |
|
|
"language": language, |
|
|
"timestamp": timestamp.isoformat(), |
|
|
"trigger_words": active_trigger_words |
|
|
} |
|
|
|
|
|
metadata_str = json.dumps(metadata, indent=2, ensure_ascii=False) |
|
|
|
|
|
return ( |
|
|
image, |
|
|
metadata_str, |
|
|
f"✅ Generate successfully! Seed: {seed}" |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
error_msg = f"Build Failure: {str(e)}" |
|
|
print(f"❌ {error_msg}") |
|
|
return None, error_msg, error_msg |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_interface(): |
|
|
"""创建Gradio界面""" |
|
|
|
|
|
with gr.Blocks( |
|
|
theme=gr.themes.Soft( |
|
|
primary_hue="blue", |
|
|
secondary_hue="purple", |
|
|
neutral_hue="slate", |
|
|
), |
|
|
css=""" |
|
|
.model-card { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
padding: 20px; |
|
|
border-radius: 12px; |
|
|
color: white; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
.control-section { |
|
|
background: rgba(255,255,255,0.05); |
|
|
border-radius: 12px; |
|
|
padding: 15px; |
|
|
margin: 10px 0; |
|
|
} |
|
|
""", |
|
|
title="AI Image Generator - Illustrious XL Multi-model version" |
|
|
) as demo: |
|
|
|
|
|
gr.Markdown(""" |
|
|
# 🎨 AI Image Generator - Illustrious XL Multi-Model Edition |
|
|
### Flexible switching between multiple SDXL models | Flexible LoRa combinations | Optimized parameter configuration |
|
|
""", elem_classes=["model-card"]) |
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
with gr.Column(scale=3): |
|
|
|
|
|
|
|
|
with gr.Group(elem_classes=["control-section"]): |
|
|
gr.Markdown("### 📦 Model selection") |
|
|
model_dropdown = gr.Dropdown( |
|
|
choices=[(config["description"], key) for key, config in MODEL_CONFIGS.items()], |
|
|
value=DEFAULT_MODEL_KEY, |
|
|
label="Basic Model", |
|
|
info="Choose different models to get different styles" |
|
|
) |
|
|
model_info = gr.Markdown(MODEL_CONFIGS[DEFAULT_MODEL_KEY]["description"]) |
|
|
|
|
|
|
|
|
with gr.Group(elem_classes=["control-section"]): |
|
|
gr.Markdown("### ✍️ Prompt") |
|
|
prompt_input = gr.Textbox( |
|
|
label="Prompt", |
|
|
placeholder="Describe the image you want to generate...", |
|
|
lines=4, |
|
|
max_lines=20 |
|
|
) |
|
|
|
|
|
negative_prompt_input = gr.Textbox( |
|
|
label="Negative Prompt(Leave blank to use the model default)", |
|
|
placeholder="The recommended negative prompt words of the selected model will be automatically used...", |
|
|
lines=3, |
|
|
max_lines=15 |
|
|
) |
|
|
|
|
|
style_radio = gr.Radio( |
|
|
choices=list(STYLE_PROMPTS.keys()), |
|
|
label="Style Template", |
|
|
value="None", |
|
|
info="Will be automatically added before the prompt word" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Group(elem_classes=["control-section"]): |
|
|
gr.Markdown("### ⚙️ Basic Parameters") |
|
|
|
|
|
with gr.Row(): |
|
|
seed_input = gr.Slider( |
|
|
minimum=-1, |
|
|
maximum=99999999, |
|
|
step=1, |
|
|
value=DEFAULT_SEED, |
|
|
label="Seed (-1=Random)" |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
width_input = gr.Slider( |
|
|
minimum=512, |
|
|
maximum=1536, |
|
|
step=64, |
|
|
value=DEFAULT_WIDTH, |
|
|
label="Width" |
|
|
) |
|
|
height_input = gr.Slider( |
|
|
minimum=512, |
|
|
maximum=1536, |
|
|
step=64, |
|
|
value=DEFAULT_HEIGHT, |
|
|
label="High" |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
steps_slider = gr.Slider( |
|
|
minimum=10, |
|
|
maximum=100, |
|
|
step=1, |
|
|
value=DEFAULT_STEPS, |
|
|
label="Sampling Steps" |
|
|
) |
|
|
cfg_slider = gr.Slider( |
|
|
minimum=1.0, |
|
|
maximum=20.0, |
|
|
step=0.5, |
|
|
value=DEFAULT_CFG, |
|
|
label="CFG Scale" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Group(elem_classes=["control-section"]): |
|
|
gr.Markdown("### 🎭 LoRA Configuration") |
|
|
|
|
|
use_fixed_loras = gr.Checkbox( |
|
|
label="Fixed LoRA enhancement (quality + details)", |
|
|
value=True, |
|
|
info="Automatically load quality and detail enhancements for LoRA" |
|
|
) |
|
|
|
|
|
lora_dropdown = gr.Dropdown( |
|
|
choices=list(OPTIONAL_LORAS.keys()), |
|
|
label="Additional LoRA (multiple selections possible)", |
|
|
value=["None"], |
|
|
multiselect=True, |
|
|
info="Select additional styles LoRA" |
|
|
) |
|
|
|
|
|
lora_scale_slider = gr.Slider( |
|
|
minimum=0.0, |
|
|
maximum=1.5, |
|
|
step=0.05, |
|
|
value=DEFAULT_LORA_SCALE, |
|
|
label="LoRA Strength" |
|
|
) |
|
|
|
|
|
|
|
|
generate_btn = gr.Button( |
|
|
"✨ Generat", |
|
|
variant="primary", |
|
|
size="lg" |
|
|
) |
|
|
|
|
|
status_text = gr.Textbox( |
|
|
label="Status", |
|
|
value="Ready", |
|
|
interactive=False |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Column(scale=2): |
|
|
image_output = gr.Image( |
|
|
label="Generated image", |
|
|
height=600, |
|
|
format="webp" |
|
|
) |
|
|
|
|
|
gr.Markdown("**Right click on the image to download**") |
|
|
|
|
|
metadata_output = gr.Textbox( |
|
|
label="Generated metadata", |
|
|
lines=15, |
|
|
max_lines=25 |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_model_info(model_key): |
|
|
config = MODEL_CONFIGS[model_key] |
|
|
info = f""" |
|
|
**模型:** {config['description']} |
|
|
**仓库:** `{config['repo_id']}` |
|
|
**推荐设置:** 步数={config['optimal_settings']['steps']}, CFG={config['optimal_settings']['cfg']} |
|
|
""" |
|
|
return ( |
|
|
info, |
|
|
config['optimal_settings']['steps'], |
|
|
config['optimal_settings']['cfg'], |
|
|
config['default_negative'] |
|
|
) |
|
|
|
|
|
model_dropdown.change( |
|
|
fn=update_model_info, |
|
|
inputs=[model_dropdown], |
|
|
outputs=[model_info, steps_slider, cfg_slider, negative_prompt_input] |
|
|
) |
|
|
|
|
|
|
|
|
generate_btn.click( |
|
|
fn=generate_image, |
|
|
inputs=[ |
|
|
model_dropdown, prompt_input, negative_prompt_input, style_radio, |
|
|
seed_input, width_input, height_input, |
|
|
lora_dropdown, lora_scale_slider, |
|
|
steps_slider, cfg_slider, use_fixed_loras, |
|
|
gr.Textbox(value="zh", visible=False) |
|
|
], |
|
|
outputs=[ |
|
|
image_output, metadata_output, status_text |
|
|
] |
|
|
) |
|
|
|
|
|
return demo |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo = create_interface() |
|
|
demo.queue(max_size=20) |
|
|
demo.launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=7860, |
|
|
share=False, |
|
|
show_error=True |
|
|
) |