allfree commited on
Commit
2e5aab4
Β·
verified Β·
1 Parent(s): da30e4b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +641 -171
app.py CHANGED
@@ -1,194 +1,664 @@
1
  import gradio as gr
2
- import spaces
 
3
  import torch
4
- import os
5
- from compel import Compel, ReturnedEmbeddingsType
6
- from diffusers import DiffusionPipeline
7
-
8
- # Load model
9
- model_name = os.environ.get('MODEL_NAME', 'UnfilteredAI/NSFW-gen-v2.1')
10
- pipe = DiffusionPipeline.from_pretrained(
11
- model_name,
12
- torch_dtype=torch.float16
13
- )
14
- pipe.to('cuda')
15
-
16
- # Compel setup
17
- compel = Compel(
18
- tokenizer=[pipe.tokenizer, pipe.tokenizer_2],
19
- text_encoder=[pipe.text_encoder, pipe.text_encoder_2],
20
- returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED,
21
- requires_pooled=[False, True]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  )
 
 
 
 
23
 
24
- # Default negative prompt
25
- default_negative_prompt = "(low quality, worst quality:1.2), very displeasing, 3d, watermark, signature, ugly, poorly drawn, (deformed | distorted | disfigured:1.3), bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, mutated hands and fingers:1.4, disconnected limbs, blurry, amputation."
26
-
27
- # Example prompts
28
- example_prompts = [
29
- ["a beautiful 19 aged rusian girl in a summer dress at the beach, golden sunset, professional photography, 8k", default_negative_prompt, 40, 7.5, 1024, 1024, 4],
30
- ["a beautiful japan woman in a vikini at the beach, noon sunset, professional photography, 8k", default_negative_prompt, 45, 7.0, 1024, 1024, 4],
31
- ]
32
-
33
- # Image generation function
34
- @spaces.GPU(duration=120)
35
- def generate(prompt, negative_prompt, num_inference_steps, guidance_scale, width, height, num_samples, progress=gr.Progress()):
36
- progress(0, desc="Preparing")
37
- embeds, pooled = compel(prompt)
38
- neg_embeds, neg_pooled = compel(negative_prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
- progress(0.1, desc="Generating images")
 
 
 
 
41
 
42
- # Define proper callback for step end
43
- def callback_on_step_end(pipe, i, t, callback_kwargs):
44
- progress((i + 1) / num_inference_steps)
45
- return callback_kwargs
 
 
 
 
 
 
 
46
 
 
 
47
  images = pipe(
48
- prompt_embeds=embeds,
49
- pooled_prompt_embeds=pooled,
50
- negative_prompt_embeds=neg_embeds,
51
- negative_pooled_prompt_embeds=neg_pooled,
52
- num_inference_steps=num_inference_steps,
53
- guidance_scale=guidance_scale,
54
- width=width,
55
  height=height,
56
- num_images_per_prompt=num_samples,
57
- callback_on_step_end=callback_on_step_end
 
 
 
 
58
  ).images
59
-
60
- return images
61
 
62
- # CSS styles
 
 
 
 
 
 
 
63
  css = """
64
- .gallery-item {
65
- transition: transform 0.2s;
66
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
67
- border-radius: 10px;
68
- }
69
- .gallery-item:hover {
70
- transform: scale(1.03);
71
- box-shadow: 0 8px 16px rgba(0,0,0,0.2);
72
- }
73
- .container {
74
- max-width: 1200px;
75
- margin: auto;
76
- }
77
- .header {
78
- text-align: center;
79
- margin-bottom: 2rem;
80
- padding: 1rem;
81
- background: linear-gradient(90deg, rgba(76,0,161,0.8) 0%, rgba(28,110,164,0.8) 100%);
82
- border-radius: 10px;
83
- color: white;
84
- }
85
- .slider-container {
86
- background-color: #f5f5f5;
87
- padding: 1rem;
88
- border-radius: 10px;
89
- margin-bottom: 1rem;
90
- }
91
- .prompt-container {
92
- background-color: #f0f8ff;
93
- padding: 1rem;
94
- border-radius: 10px;
95
- margin-bottom: 1rem;
96
- border: 1px solid #d0e8ff;
97
- }
98
- .examples-header {
99
- background: linear-gradient(90deg, rgba(41,128,185,0.7) 0%, rgba(142,68,173,0.7) 100%);
100
- color: white;
101
- padding: 0.5rem;
102
- border-radius: 8px;
103
- text-align: center;
104
- margin-bottom: 0.5rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  }
106
  """
107
 
108
- # Gradio interface
109
- with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
110
- gr.HTML("""
111
- <style>
112
- .gradio-container {
113
- background: linear-gradient(135deg, #fef9f3 0%, #f0e6fa 50%, #e6f0fa 100%) !important;
114
- }
115
- footer {display: none !important;}
116
- </style>
117
-
118
- <div style="text-align: center; margin-bottom: 20px;">
119
- <h1 style="color: #6b5b7a; font-size: 2.2rem; font-weight: 700; margin-bottom: 0.3rem;">
120
- 🎨 Unfiltered AI NSFW Image Generator
121
- </h1>
122
-
123
- <p style="color: #8b7b9b; font-size: 1rem;">
124
- Enter creative prompts and generate high-quality images.
125
- </p>
126
-
127
- <div style="margin-top: 12px; display: flex; justify-content: center; gap: 12px;">
128
- <a href="https://huggingface.co/spaces/Heartsync/FREE-NSFW-HUB" target="_blank">
129
- <img src="https://img.shields.io/static/v1?label=FREE&message=NSFW%20HUB&color=%230000ff&labelColor=%23800080&logo=huggingface&logoColor=white&style=for-the-badge" alt="badge">
130
- </a>
131
- <a href="https://www.humangen.ai" target="_blank">
132
- <img src="https://img.shields.io/static/v1?label=100% FREE&message=AI%20Playground&color=%230000ff&labelColor=%23800080&logo=huggingface&logoColor=%23ffa500&style=for-the-badge" alt="badge">
133
- </a>
134
- <a href="https://ginigen.ai/en" target="_blank">
135
- <img src="https://img.shields.io/static/v1?label=Powered%20by&message=Hogwarts%20BANANA&color=%230000ff&labelColor=%23800080&logo=huggingface&logoColor=white&style=for-the-badge" alt="badge">
136
  </a>
137
  </div>
138
- </div>
139
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
-
143
- with gr.Row():
144
- with gr.Column(scale=2):
145
- with gr.Group(elem_classes="prompt-container"):
146
- prompt = gr.Textbox(label="Prompt", placeholder="Describe your desired image...", lines=3)
147
- negative_prompt = gr.Textbox(
148
- label="Negative Prompt",
149
- value=default_negative_prompt,
150
- lines=3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  )
152
 
153
- with gr.Group(elem_classes="slider-container"):
154
- with gr.Row():
155
- with gr.Column():
156
- steps = gr.Slider(minimum=20, maximum=100, value=60, step=1, label="Inference Steps (Quality)", info="Higher values improve quality (longer generation time)")
157
- guidance = gr.Slider(minimum=1, maximum=15, value=7, step=0.1, label="Guidance Scale (Creativity)", info="Lower values create more creative results")
158
-
159
- with gr.Column():
160
- with gr.Row():
161
- width = gr.Slider(minimum=512, maximum=1536, value=1024, step=128, label="Width")
162
- height = gr.Slider(minimum=512, maximum=1536, value=1024, step=128, label="Height")
163
-
164
- num_samples = gr.Slider(minimum=1, maximum=8, value=4, step=1, label="Number of Images", info="Number of images to generate at once")
165
-
166
- generate_btn = gr.Button("πŸš€ Generate Images", variant="primary", size="lg")
167
-
168
- with gr.Column(scale=3):
169
- output_gallery = gr.Gallery(label="Generated Images", elem_classes="gallery-item", columns=2, object_fit="contain", height=650)
170
-
171
- gr.HTML("""<div class="examples-header"><h3>✨ Example Prompts</h3></div>""")
172
- gr.Examples(
173
- examples=example_prompts,
174
- inputs=[prompt, negative_prompt, steps, guidance, width, height, num_samples],
175
- outputs=output_gallery,
176
- fn=generate,
177
- cache_examples=True,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  )
179
-
180
- # Event connections
181
- generate_btn.click(
182
- fn=generate,
183
- inputs=[prompt, negative_prompt, steps, guidance, width, height, num_samples],
184
- outputs=output_gallery
185
  )
186
-
187
- gr.HTML("""
188
- <div style="text-align: center; margin-top: 20px; padding: 10px; background-color: #f0f0f0; border-radius: 10px;">
189
- <p>πŸ’‘ Tip: For high-quality images, use detailed prompts and higher inference steps.</p>
190
- <p>Example: Add quality terms like "professional photography, 8k, highly detailed, sharp focus, HDR" to your prompts.</p>
191
- </div>
192
- """)
193
-
194
- demo.launch()
 
1
  import gradio as gr
2
+ import numpy as np
3
+ import random
4
  import torch
5
+ import spaces
6
+
7
+ from PIL import Image
8
+ from diffusers import FlowMatchEulerDiscreteScheduler, QwenImageEditPlusPipeline
9
+
10
+ import math
11
+
12
+ # --- Model Loading ---
13
+ dtype = torch.bfloat16
14
+ device = "cuda" if torch.cuda.is_available() else "cpu"
15
+
16
+ # Scheduler configuration for Lightning
17
+ scheduler_config = {
18
+ "base_image_seq_len": 256,
19
+ "base_shift": math.log(5),
20
+ "invert_sigmas": False,
21
+ "max_image_seq_len": 8192,
22
+ "max_shift": math.log(3),
23
+ "num_train_timesteps": 1000,
24
+ "shift": 1.0,
25
+ "shift_terminal": None,
26
+ "stochastic_sampling": False,
27
+ "time_shift_type": "exponential",
28
+ "use_beta_sigmas": False,
29
+ "use_dynamic_shifting": True,
30
+ "use_exponential_sigmas": False,
31
+ "use_karras_sigmas": False,
32
+ }
33
+
34
+ # Initialize scheduler with Lightning config
35
+ scheduler = FlowMatchEulerDiscreteScheduler.from_config(scheduler_config)
36
+
37
+ # Load the model pipeline
38
+ pipe = QwenImageEditPlusPipeline.from_pretrained("Qwen/Qwen-Image-Edit-2511",
39
+ scheduler=scheduler,
40
+ torch_dtype=dtype).to(device)
41
+ pipe.load_lora_weights(
42
+ "lightx2v/Qwen-Image-Edit-2511-Lightning",
43
+ weight_name="Qwen-Image-Edit-2511-Lightning-4steps-V1.0-fp32.safetensors"
44
  )
45
+ pipe.fuse_lora()
46
+
47
+ # --- UI Constants and Helpers ---
48
+ MAX_SEED = np.iinfo(np.int32).max
49
 
50
+ def use_output_as_input(output_images):
51
+ """Convert output images to input format for the gallery"""
52
+ if output_images is None or len(output_images) == 0:
53
+ return []
54
+ return output_images
55
+
56
+ # --- Main Inference Function (with hardcoded negative prompt) ---
57
+ @spaces.GPU()
58
+ def infer(
59
+ image_1,
60
+ image_2,
61
+ image_3,
62
+ prompt,
63
+ seed=42,
64
+ randomize_seed=False,
65
+ true_guidance_scale=1.0,
66
+ num_inference_steps=4,
67
+ height=None,
68
+ width=None,
69
+ num_images_per_prompt=1,
70
+ progress=gr.Progress(track_tqdm=True),
71
+ ):
72
+ """
73
+ Run image-editing inference using the Qwen-Image-Edit pipeline.
74
+ """
75
+
76
+ # Hardcode the negative prompt as requested
77
+ negative_prompt = " "
78
 
79
+ if randomize_seed:
80
+ seed = random.randint(0, MAX_SEED)
81
+
82
+ # Set up the generator for reproducibility
83
+ generator = torch.Generator(device=device).manual_seed(seed)
84
 
85
+ # Load input images into a list of PIL Images
86
+ pil_images = []
87
+ for item in [image_1, image_2, image_3]:
88
+ if item is None: continue
89
+ pil_images.append(item.convert("RGB"))
90
+
91
+ if height==256 and width==256:
92
+ height, width = None, None
93
+ print(f"Calling pipeline with prompt: '{prompt}'")
94
+ print(f"Negative Prompt: '{negative_prompt}'")
95
+ print(f"Seed: {seed}, Steps: {num_inference_steps}, Guidance: {true_guidance_scale}, Size: {width}x{height}")
96
 
97
+
98
+ # Generate the image
99
  images = pipe(
100
+ image=pil_images if len(pil_images) > 0 else None,
101
+ prompt=prompt,
 
 
 
 
 
102
  height=height,
103
+ width=width,
104
+ negative_prompt=negative_prompt,
105
+ num_inference_steps=num_inference_steps,
106
+ generator=generator,
107
+ true_cfg_scale=true_guidance_scale,
108
+ num_images_per_prompt=num_images_per_prompt,
109
  ).images
 
 
110
 
111
+ # Return images, seed, and make button visible
112
+ return images[0], seed, gr.update(visible=True)
113
+
114
+
115
+ # ============================================
116
+ # 🎨 Comic Classic Theme - Toon Playground
117
+ # ============================================
118
+
119
  css = """
120
+ /* ===== 🎨 Google Fonts Import ===== */
121
+ @import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap');
122
+
123
+ /* ===== 🎨 Comic Classic λ°°κ²½ - λΉˆν‹°μ§€ 페이퍼 + λ„νŠΈ νŒ¨ν„΄ ===== */
124
+ .gradio-container {
125
+ background-color: #FEF9C3 !important;
126
+ background-image:
127
+ radial-gradient(#1F2937 1px, transparent 1px) !important;
128
+ background-size: 20px 20px !important;
129
+ min-height: 100vh !important;
130
+ font-family: 'Comic Neue', cursive, sans-serif !important;
131
+ }
132
+
133
+ /* ===== ν—ˆκΉ…νŽ˜μ΄μŠ€ 상단 μš”μ†Œ μˆ¨κΉ€ ===== */
134
+ .huggingface-space-header,
135
+ #space-header,
136
+ .space-header,
137
+ [class*="space-header"],
138
+ .svelte-1ed2p3z,
139
+ .space-header-badge,
140
+ .header-badge,
141
+ [data-testid="space-header"],
142
+ .svelte-kqij2n,
143
+ .svelte-1ax1toq,
144
+ .embed-container > div:first-child {
145
+ display: none !important;
146
+ visibility: hidden !important;
147
+ height: 0 !important;
148
+ width: 0 !important;
149
+ overflow: hidden !important;
150
+ opacity: 0 !important;
151
+ pointer-events: none !important;
152
+ }
153
+
154
+ /* ===== Footer μ™„μ „ μˆ¨κΉ€ ===== */
155
+ footer,
156
+ .footer,
157
+ .gradio-container footer,
158
+ .built-with,
159
+ [class*="footer"],
160
+ .gradio-footer,
161
+ .main-footer,
162
+ div[class*="footer"],
163
+ .show-api,
164
+ .built-with-gradio,
165
+ a[href*="gradio.app"],
166
+ a[href*="huggingface.co/spaces"] {
167
+ display: none !important;
168
+ visibility: hidden !important;
169
+ height: 0 !important;
170
+ padding: 0 !important;
171
+ margin: 0 !important;
172
+ }
173
+
174
+ /* ===== 메인 μ»¨ν…Œμ΄λ„ˆ ===== */
175
+ #col-container {
176
+ max-width: 1100px;
177
+ margin: 0 auto;
178
+ }
179
+
180
+ /* ===== 🎨 헀더 타이틀 - μ½”λ―Ή μŠ€νƒ€μΌ ===== */
181
+ .header-text h1 {
182
+ font-family: 'Bangers', cursive !important;
183
+ color: #1F2937 !important;
184
+ font-size: 3.5rem !important;
185
+ font-weight: 400 !important;
186
+ text-align: center !important;
187
+ margin-bottom: 0.5rem !important;
188
+ text-shadow:
189
+ 4px 4px 0px #FACC15,
190
+ 6px 6px 0px #1F2937 !important;
191
+ letter-spacing: 3px !important;
192
+ -webkit-text-stroke: 2px #1F2937 !important;
193
+ }
194
+
195
+ /* ===== 🎨 μ„œλΈŒνƒ€μ΄ν‹€ ===== */
196
+ .subtitle {
197
+ text-align: center !important;
198
+ font-family: 'Comic Neue', cursive !important;
199
+ font-size: 1.2rem !important;
200
+ color: #1F2937 !important;
201
+ margin-bottom: 1.5rem !important;
202
+ font-weight: 700 !important;
203
+ }
204
+
205
+ /* ===== 🎨 μΉ΄λ“œ/νŒ¨λ„ - λ§Œν™” ν”„λ ˆμž„ μŠ€νƒ€μΌ ===== */
206
+ .gr-panel,
207
+ .gr-box,
208
+ .gr-form,
209
+ .block,
210
+ .gr-group {
211
+ background: #FFFFFF !important;
212
+ border: 3px solid #1F2937 !important;
213
+ border-radius: 8px !important;
214
+ box-shadow: 6px 6px 0px #1F2937 !important;
215
+ }
216
+
217
+ /* ===== 🎨 μž…λ ₯ ν•„λ“œ (Textbox) ===== */
218
+ textarea,
219
+ input[type="text"],
220
+ input[type="number"] {
221
+ background: #FFFFFF !important;
222
+ border: 3px solid #1F2937 !important;
223
+ border-radius: 8px !important;
224
+ color: #1F2937 !important;
225
+ font-family: 'Comic Neue', cursive !important;
226
+ font-size: 1rem !important;
227
+ font-weight: 700 !important;
228
+ transition: all 0.2s ease !important;
229
+ }
230
+
231
+ textarea:focus,
232
+ input[type="text"]:focus,
233
+ input[type="number"]:focus {
234
+ border-color: #3B82F6 !important;
235
+ box-shadow: 4px 4px 0px #3B82F6 !important;
236
+ outline: none !important;
237
+ }
238
+
239
+ textarea::placeholder {
240
+ color: #9CA3AF !important;
241
+ font-weight: 400 !important;
242
+ }
243
+
244
+ /* ===== 🎨 Primary λ²„νŠΌ - μ½”λ―Ή 블루 ===== */
245
+ .gr-button-primary,
246
+ button.primary,
247
+ .gr-button.primary,
248
+ .edit-btn {
249
+ background: #3B82F6 !important;
250
+ border: 3px solid #1F2937 !important;
251
+ border-radius: 8px !important;
252
+ color: #FFFFFF !important;
253
+ font-family: 'Bangers', cursive !important;
254
+ font-weight: 400 !important;
255
+ font-size: 1.3rem !important;
256
+ letter-spacing: 2px !important;
257
+ padding: 14px 28px !important;
258
+ box-shadow: 5px 5px 0px #1F2937 !important;
259
+ transition: all 0.1s ease !important;
260
+ text-shadow: 1px 1px 0px #1F2937 !important;
261
+ }
262
+
263
+ .gr-button-primary:hover,
264
+ button.primary:hover,
265
+ .gr-button.primary:hover,
266
+ .edit-btn:hover {
267
+ background: #2563EB !important;
268
+ transform: translate(-2px, -2px) !important;
269
+ box-shadow: 7px 7px 0px #1F2937 !important;
270
+ }
271
+
272
+ .gr-button-primary:active,
273
+ button.primary:active,
274
+ .gr-button.primary:active,
275
+ .edit-btn:active {
276
+ transform: translate(3px, 3px) !important;
277
+ box-shadow: 2px 2px 0px #1F2937 !important;
278
+ }
279
+
280
+ /* ===== 🎨 Secondary λ²„νŠΌ - μ½”λ―Ή λ ˆλ“œ ===== */
281
+ .gr-button-secondary,
282
+ button.secondary,
283
+ .use-output-btn {
284
+ background: #EF4444 !important;
285
+ border: 3px solid #1F2937 !important;
286
+ border-radius: 8px !important;
287
+ color: #FFFFFF !important;
288
+ font-family: 'Bangers', cursive !important;
289
+ font-weight: 400 !important;
290
+ font-size: 1.1rem !important;
291
+ letter-spacing: 1px !important;
292
+ box-shadow: 4px 4px 0px #1F2937 !important;
293
+ transition: all 0.1s ease !important;
294
+ text-shadow: 1px 1px 0px #1F2937 !important;
295
+ }
296
+
297
+ .gr-button-secondary:hover,
298
+ button.secondary:hover,
299
+ .use-output-btn:hover {
300
+ background: #DC2626 !important;
301
+ transform: translate(-2px, -2px) !important;
302
+ box-shadow: 6px 6px 0px #1F2937 !important;
303
+ }
304
+
305
+ .gr-button-secondary:active,
306
+ button.secondary:active,
307
+ .use-output-btn:active {
308
+ transform: translate(2px, 2px) !important;
309
+ box-shadow: 2px 2px 0px #1F2937 !important;
310
+ }
311
+
312
+ /* ===== 🎨 이미지 μ—…λ‘œλ“œ/좜λ ₯ μ˜μ—­ ===== */
313
+ .gr-image,
314
+ .image-container,
315
+ .image-upload {
316
+ border: 4px solid #1F2937 !important;
317
+ border-radius: 8px !important;
318
+ box-shadow: 8px 8px 0px #1F2937 !important;
319
+ overflow: hidden !important;
320
+ background: #FFFFFF !important;
321
+ }
322
+
323
+ /* ===== 🎨 μ•„μ½”λ””μ–Έ - 말풍선 μŠ€νƒ€μΌ ===== */
324
+ .gr-accordion {
325
+ background: #FACC15 !important;
326
+ border: 3px solid #1F2937 !important;
327
+ border-radius: 8px !important;
328
+ box-shadow: 4px 4px 0px #1F2937 !important;
329
+ }
330
+
331
+ .gr-accordion-header {
332
+ color: #1F2937 !important;
333
+ font-family: 'Comic Neue', cursive !important;
334
+ font-weight: 700 !important;
335
+ font-size: 1.1rem !important;
336
+ }
337
+
338
+ /* ===== 🎨 μŠ¬λΌμ΄λ” μŠ€νƒ€μΌ ===== */
339
+ .gr-slider input[type="range"] {
340
+ accent-color: #3B82F6 !important;
341
+ }
342
+
343
+ .gr-slider .gr-slider-label {
344
+ font-family: 'Comic Neue', cursive !important;
345
+ font-weight: 700 !important;
346
+ color: #1F2937 !important;
347
+ }
348
+
349
+ /* ===== 🎨 μ²΄ν¬λ°•μŠ€ μŠ€νƒ€μΌ ===== */
350
+ .gr-checkbox input[type="checkbox"] {
351
+ accent-color: #3B82F6 !important;
352
+ width: 20px !important;
353
+ height: 20px !important;
354
+ }
355
+
356
+ .gr-checkbox label {
357
+ font-family: 'Comic Neue', cursive !important;
358
+ font-weight: 700 !important;
359
+ color: #1F2937 !important;
360
+ }
361
+
362
+ /* ===== 🎨 라벨 μŠ€νƒ€μΌ ===== */
363
+ label,
364
+ .gr-input-label,
365
+ .gr-block-label {
366
+ color: #1F2937 !important;
367
+ font-family: 'Comic Neue', cursive !important;
368
+ font-weight: 700 !important;
369
+ font-size: 1rem !important;
370
+ }
371
+
372
+ span.gr-label {
373
+ color: #1F2937 !important;
374
+ }
375
+
376
+ /* ===== 🎨 정보 ν…μŠ€νŠΈ ===== */
377
+ .gr-info,
378
+ .info {
379
+ color: #6B7280 !important;
380
+ font-family: 'Comic Neue', cursive !important;
381
+ font-size: 0.9rem !important;
382
+ }
383
+
384
+ /* ===== 🎨 ν”„λ‘œκ·Έλ ˆμŠ€ λ°” ===== */
385
+ .progress-bar,
386
+ .gr-progress-bar {
387
+ background: #3B82F6 !important;
388
+ border: 2px solid #1F2937 !important;
389
+ border-radius: 4px !important;
390
+ }
391
+
392
+ /* ===== 🎨 μŠ€ν¬λ‘€λ°” - μ½”λ―Ή μŠ€νƒ€μΌ ===== */
393
+ ::-webkit-scrollbar {
394
+ width: 12px;
395
+ height: 12px;
396
+ }
397
+
398
+ ::-webkit-scrollbar-track {
399
+ background: #FEF9C3;
400
+ border: 2px solid #1F2937;
401
+ }
402
+
403
+ ::-webkit-scrollbar-thumb {
404
+ background: #3B82F6;
405
+ border: 2px solid #1F2937;
406
+ border-radius: 0px;
407
+ }
408
+
409
+ ::-webkit-scrollbar-thumb:hover {
410
+ background: #EF4444;
411
+ }
412
+
413
+ /* ===== 🎨 선택 ν•˜μ΄λΌμ΄νŠΈ ===== */
414
+ ::selection {
415
+ background: #FACC15;
416
+ color: #1F2937;
417
+ }
418
+
419
+ /* ===== 🎨 링크 μŠ€νƒ€μΌ ===== */
420
+ a {
421
+ color: #3B82F6 !important;
422
+ text-decoration: none !important;
423
+ font-weight: 700 !important;
424
+ }
425
+
426
+ a:hover {
427
+ color: #EF4444 !important;
428
+ }
429
+
430
+ /* ===== 🎨 Row/Column 간격 ===== */
431
+ .gr-row {
432
+ gap: 1.5rem !important;
433
+ }
434
+
435
+ .gr-column {
436
+ gap: 1rem !important;
437
+ }
438
+
439
+ /* ===== 🎨 ν”„λ‘¬ν”„νŠΈ μž…λ ₯ μ˜μ—­ νŠΉλ³„ μŠ€νƒ€μΌ ===== */
440
+ .prompt-input textarea {
441
+ background: #EFF6FF !important;
442
+ border: 4px dashed #3B82F6 !important;
443
+ border-radius: 12px !important;
444
+ font-size: 1.1rem !important;
445
+ min-height: 120px !important;
446
+ }
447
+
448
+ .prompt-input textarea:focus {
449
+ border-style: solid !important;
450
+ background: #FFFFFF !important;
451
+ }
452
+
453
+ /* ===== λ°˜μ‘ν˜• μ‘°μ • ===== */
454
+ @media (max-width: 768px) {
455
+ .header-text h1 {
456
+ font-size: 2.2rem !important;
457
+ text-shadow:
458
+ 3px 3px 0px #FACC15,
459
+ 4px 4px 0px #1F2937 !important;
460
+ }
461
+
462
+ .gr-button-primary,
463
+ button.primary {
464
+ padding: 12px 20px !important;
465
+ font-size: 1.1rem !important;
466
+ }
467
+
468
+ .gr-panel,
469
+ .block {
470
+ box-shadow: 4px 4px 0px #1F2937 !important;
471
+ }
472
+ }
473
+
474
+ /* ===== 🎨 닀크λͺ¨λ“œ λΉ„ν™œμ„±ν™” (코믹은 밝아야 함) ===== */
475
+ @media (prefers-color-scheme: dark) {
476
+ .gradio-container {
477
+ background-color: #FEF9C3 !important;
478
+ }
479
  }
480
  """
481
 
482
+ # --- Gradio UI Layout ---
483
+ with gr.Blocks(fill_height=True, css=css) as demo:
484
+ gr.LoginButton(value="Option: HuggingFace 'Login' for extra GPU quota +", size="sm")
485
+ with gr.Column(elem_id="col-container"):
486
+
487
+ # HOME Badge
488
+ gr.HTML("""
489
+ <div style="text-align: center; margin: 20px 0 10px 0;">
490
+ <a href="https://www.humangen.ai" target="_blank" style="text-decoration: none;">
491
+ <img src="https://img.shields.io/static/v1?label=🏠 HOME&message=HUMANGEN.AI&color=0000ff&labelColor=ffcc00&style=for-the-badge" alt="HOME">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
  </a>
493
  </div>
494
+ """)
495
+
496
+ # Header Title
497
+ gr.Markdown(
498
+ """
499
+ # 🎨 QWEN IMAGE EDIT STUDIO πŸ–ΌοΈ
500
+ """,
501
+ elem_classes="header-text"
502
+ )
503
+
504
+ gr.Markdown(
505
+ """
506
+ <p class="subtitle">✨ Upload an image and describe your edit - AI transforms it instantly! ✨</p>
507
+ """,
508
+ )
509
 
510
+ with gr.Row(equal_height=False):
511
+ # Left column - Input Images
512
+ with gr.Column(scale=1, min_width=320):
513
+ image_1 = gr.Image(
514
+ label="πŸ–ΌοΈ Main Image",
515
+ type="pil",
516
+ interactive=True,
517
+ elem_classes="image-upload"
518
+ )
519
+
520
+ with gr.Accordion("πŸ“Ž More Reference Images", open=False):
521
+ with gr.Row():
522
+ image_2 = gr.Image(
523
+ label="πŸ–ΌοΈ Reference 2",
524
+ type="pil",
525
+ interactive=True
526
+ )
527
+ image_3 = gr.Image(
528
+ label="πŸ–ΌοΈ Reference 3",
529
+ type="pil",
530
+ interactive=True
531
+ )
532
 
533
+ # Right column - Output
534
+ with gr.Column(scale=1, min_width=320):
535
+ result = gr.Image(
536
+ label="🎯 Result",
537
+ type="pil",
538
+ interactive=False,
539
+ elem_classes="image-container"
540
+ )
541
+
542
+ use_output_btn = gr.Button(
543
+ "↗️ USE AS INPUT!",
544
+ variant="secondary",
545
+ size="sm",
546
+ visible=False,
547
+ elem_classes="use-output-btn"
548
+ )
549
+
550
+ gr.Markdown(
551
+ """
552
+ <p style="text-align: center; margin-top: 10px; font-weight: 700; color: #1F2937; font-family: 'Comic Neue', cursive;">
553
+ πŸ’‘ Right-click on the image to save, or use the download button!
554
+ </p>
555
+ """
556
+ )
557
+
558
+ # Prompt Input Section
559
+ with gr.Row():
560
+ with gr.Column():
561
+ prompt = gr.Textbox(
562
+ label="✏️ Edit Instruction",
563
+ show_label=True,
564
+ placeholder="Describe the edit you want to make... e.g., 'Change the background to a beach sunset' or 'Add sunglasses to the person'",
565
+ lines=4,
566
+ elem_classes="prompt-input"
567
+ )
568
+
569
+ run_button = gr.Button(
570
+ "πŸš€ EDIT IMAGE!",
571
+ variant="primary",
572
+ size="lg",
573
+ elem_classes="edit-btn"
574
+ )
575
+
576
+ # Advanced Settings
577
+ with gr.Accordion("βš™οΈ Advanced Settings", open=False):
578
+ with gr.Row():
579
+ seed = gr.Slider(
580
+ label="🎲 Seed",
581
+ minimum=0,
582
+ maximum=MAX_SEED,
583
+ step=1,
584
+ value=0,
585
+ )
586
+ randomize_seed = gr.Checkbox(
587
+ label="πŸ”€ Randomize Seed",
588
+ value=True
589
+ )
590
+
591
+ with gr.Row():
592
+ true_guidance_scale = gr.Slider(
593
+ label="🎯 Guidance Scale",
594
+ minimum=1.0,
595
+ maximum=10.0,
596
+ step=0.1,
597
+ value=1.0
598
+ )
599
+ num_inference_steps = gr.Slider(
600
+ label="πŸ”„ Inference Steps",
601
+ minimum=1,
602
+ maximum=40,
603
+ step=1,
604
+ value=20,
605
  )
606
 
607
+ with gr.Row():
608
+ height = gr.Slider(
609
+ label="πŸ“ Height",
610
+ minimum=1024,
611
+ maximum=2048,
612
+ step=8,
613
+ value=1024,
614
+ )
615
+ width = gr.Slider(
616
+ label="πŸ“ Width",
617
+ minimum=1024,
618
+ maximum=2048,
619
+ step=8,
620
+ value=1024,
621
+ )
622
+
623
+ # Tips Section
624
+ gr.Markdown(
625
+ """
626
+ <div style="background: #FACC15; border: 3px solid #1F2937; border-radius: 8px; padding: 15px; margin-top: 20px; box-shadow: 4px 4px 0px #1F2937;">
627
+ <h3 style="font-family: 'Bangers', cursive; color: #1F2937; margin: 0 0 10px 0; font-size: 1.3rem;">πŸ’‘ PRO TIPS</h3>
628
+ <ul style="font-family: 'Comic Neue', cursive; color: #1F2937; font-weight: 700; margin: 0; padding-left: 20px;">
629
+ <li>Be specific in your edit instructions for better results!</li>
630
+ <li>Use reference images for style transfer or composition guidance</li>
631
+ <li>Adjust inference steps: more steps = better quality, slower speed</li>
632
+ <li>Try different seeds if you don't like the result</li>
633
+ </ul>
634
+ </div>
635
+ """
636
+ )
637
+
638
+ # Event Handlers
639
+ gr.on(
640
+ triggers=[run_button.click],
641
+ fn=infer,
642
+ inputs=[
643
+ image_1,
644
+ image_2,
645
+ image_3,
646
+ prompt,
647
+ seed,
648
+ randomize_seed,
649
+ true_guidance_scale,
650
+ num_inference_steps,
651
+ height,
652
+ width,
653
+ ],
654
+ outputs=[result, seed, use_output_btn],
655
  )
656
+
657
+ use_output_btn.click(
658
+ fn=use_output_as_input,
659
+ inputs=[result],
660
+ outputs=[image_1]
 
661
  )
662
+
663
+ if __name__ == "__main__":
664
+ demo.launch(mcp_server=True, show_error=True)