Opera8 commited on
Commit
24231a0
·
verified ·
1 Parent(s): 1d36808

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +618 -169
app.py CHANGED
@@ -8,7 +8,10 @@ from PIL import Image
8
  from typing import Iterable
9
  from gradio.themes import Soft
10
  from gradio.themes.utils import colors, fonts, sizes
 
 
11
 
 
12
  colors.orange_red = colors.Color(
13
  name="orange_red",
14
  c50="#FFF0E5",
@@ -24,67 +27,486 @@ colors.orange_red = colors.Color(
24
  c950="#802200",
25
  )
26
 
27
- class OrangeRedTheme(Soft):
28
- def __init__(
29
- self,
30
- *,
31
- primary_hue: colors.Color | str = colors.gray,
32
- secondary_hue: colors.Color | str = colors.orange_red,
33
- neutral_hue: colors.Color | str = colors.slate,
34
- text_size: sizes.Size | str = sizes.text_lg,
35
- font: fonts.Font | str | Iterable[fonts.Font | str] = (
36
- fonts.GoogleFont("Outfit"), "Arial", "sans-serif",
37
- ),
38
- font_mono: fonts.Font | str | Iterable[fonts.Font | str] = (
39
- fonts.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace",
40
- ),
41
- ):
42
- super().__init__(
43
- primary_hue=primary_hue,
44
- secondary_hue=secondary_hue,
45
- neutral_hue=neutral_hue,
46
- text_size=text_size,
47
- font=font,
48
- font_mono=font_mono,
49
- )
50
- super().set(
51
- background_fill_primary="*primary_50",
52
- background_fill_primary_dark="*primary_900",
53
- body_background_fill="linear-gradient(135deg, *primary_200, *primary_100)",
54
- body_background_fill_dark="linear-gradient(135deg, *primary_900, *primary_800)",
55
- button_primary_text_color="white",
56
- button_primary_text_color_hover="white",
57
- button_primary_background_fill="linear-gradient(90deg, *secondary_500, *secondary_600)",
58
- button_primary_background_fill_hover="linear-gradient(90deg, *secondary_600, *secondary_700)",
59
- button_primary_background_fill_dark="linear-gradient(90deg, *secondary_600, *secondary_700)",
60
- button_primary_background_fill_hover_dark="linear-gradient(90deg, *secondary_500, *secondary_600)",
61
- button_secondary_text_color="black",
62
- button_secondary_text_color_hover="white",
63
- button_secondary_background_fill="linear-gradient(90deg, *primary_300, *primary_300)",
64
- button_secondary_background_fill_hover="linear-gradient(90deg, *primary_400, *primary_400)",
65
- button_secondary_background_fill_dark="linear-gradient(90deg, *primary_500, *primary_600)",
66
- button_secondary_background_fill_hover_dark="linear-gradient(90deg, *primary_500, *primary_500)",
67
- slider_color="*secondary_500",
68
- slider_color_dark="*secondary_600",
69
- block_title_text_weight="600",
70
- block_border_width="3px",
71
- block_shadow="*shadow_drop_lg",
72
- button_primary_shadow="*shadow_drop_lg",
73
- button_large_padding="11px",
74
- color_accent_soft="*primary_100",
75
- block_label_background_fill="*primary_200",
76
- )
 
 
 
 
 
 
 
 
 
 
 
77
 
78
- orange_red_theme = OrangeRedTheme()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
81
  dtype = torch.bfloat16
82
 
83
- from diffusers import FlowMatchEulerDiscreteScheduler
84
- from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
85
- from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
86
- from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
87
-
88
  print("Loading Qwen Image Edit Pipeline...")
89
  pipe = QwenImageEditPlusPipeline.from_pretrained(
90
  "Qwen/Qwen-Image-Edit-2509",
@@ -98,36 +520,16 @@ pipe = QwenImageEditPlusPipeline.from_pretrained(
98
  ).to(device)
99
 
100
  print("Loading and Fusing Lightning LoRA...")
101
- pipe.load_lora_weights("lightx2v/Qwen-Image-Lightning",
102
- weight_name="Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors",
103
- adapter_name="lightning")
104
  pipe.fuse_lora(adapter_names=["lightning"], lora_scale=1.0)
105
 
106
  print("Loading Task Adapters...")
107
-
108
- pipe.load_lora_weights("tarn59/apply_texture_qwen_image_edit_2509",
109
- weight_name="apply_texture_v2_qwen_image_edit_2509.safetensors",
110
- adapter_name="texture")
111
-
112
- pipe.load_lora_weights("ostris/qwen_image_edit_inpainting",
113
- weight_name="qwen_image_edit_inpainting.safetensors",
114
- adapter_name="fusion")
115
-
116
- pipe.load_lora_weights("ostris/qwen_image_edit_2509_shirt_design",
117
- weight_name="qwen_image_edit_2509_shirt_design.safetensors",
118
- adapter_name="shirt_design")
119
-
120
- pipe.load_lora_weights("dx8152/Qwen-Image-Edit-2509-Fusion",
121
- weight_name="溶图.safetensors",
122
- adapter_name="fusion-x")
123
-
124
- pipe.load_lora_weights("oumoumad/Qwen-Edit-2509-Material-transfer",
125
- weight_name="material-transfer_000004769.safetensors",
126
- adapter_name="material-transfer")
127
-
128
- pipe.load_lora_weights("dx8152/Qwen-Edit-2509-Light-Migration",
129
- weight_name="参考色调.safetensors",
130
- adapter_name="light-migration")
131
 
132
  try:
133
  pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
@@ -137,12 +539,18 @@ except Exception as e:
137
 
138
  MAX_SEED = np.iinfo(np.int32).max
139
 
 
 
 
 
 
 
 
 
 
140
  def update_dimensions_on_upload(image):
141
- if image is None:
142
- return 1024, 1024
143
-
144
  original_width, original_height = image.size
145
-
146
  if original_width > original_height:
147
  new_width = 1024
148
  aspect_ratio = original_height / original_width
@@ -151,19 +559,32 @@ def update_dimensions_on_upload(image):
151
  new_height = 1024
152
  aspect_ratio = original_width / original_height
153
  new_width = int(new_height * aspect_ratio)
154
-
155
- # Ensure dimensions are multiples of 16
156
  new_width = (new_width // 16) * 16
157
  new_height = (new_height // 16) * 16
158
-
159
  return new_width, new_height
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  @spaces.GPU(duration=30)
162
  def infer(
163
  image_1,
164
  image_2,
165
  prompt,
166
- lora_adapter,
167
  seed,
168
  randomize_seed,
169
  guidance_scale,
@@ -171,22 +592,26 @@ def infer(
171
  progress=gr.Progress(track_tqdm=True)
172
  ):
173
  if image_1 is None or image_2 is None:
174
- raise gr.Error("Please upload both images for Fusion/Texture/FaceSwap tasks.")
 
 
 
 
 
175
 
176
- if not prompt:
177
- if lora_adapter == "Cloth-Design-Fuse":
178
- prompt = "Put this design on their shirt."
179
- elif lora_adapter == "Texture Edit":
180
- prompt = "Apply texture to object."
181
- elif lora_adapter == "Fuse-Objects":
182
- prompt = "Fuse object into background."
183
- elif lora_adapter == "Super-Fusion":
184
- prompt = "Blend the product into the background, correct its perspective and lighting, and make it naturally integrated with the scene."
185
- elif lora_adapter == "Material-Transfer":
186
- prompt = "change materials of image1 to match the reference in image2"
187
- elif lora_adapter == "Light-Migration":
188
- prompt = "Refer to the color tone, remove the original lighting from Image 1, and relight Image 1 based on the lighting and color tone of Image 2."
189
-
190
  adapters_map = {
191
  "Texture Edit": "texture",
192
  "Fuse-Objects": "fusion",
@@ -197,7 +622,6 @@ def infer(
197
  }
198
 
199
  active_adapter = adapters_map.get(lora_adapter)
200
-
201
  if active_adapter:
202
  pipe.set_adapters([active_adapter], adapter_weights=[1.0])
203
  else:
@@ -207,110 +631,135 @@ def infer(
207
  seed = random.randint(0, MAX_SEED)
208
 
209
  generator = torch.Generator(device=device).manual_seed(seed)
210
- negative_prompt = "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
211
 
212
  img1_pil = image_1.convert("RGB")
213
  img2_pil = image_2.convert("RGB")
214
 
215
  width, height = update_dimensions_on_upload(img1_pil)
216
 
217
- result = pipe(
218
- image=[img1_pil, img2_pil],
219
- prompt=prompt,
220
- negative_prompt=negative_prompt,
221
- height=height,
222
- width=width,
223
- num_inference_steps=steps,
224
- generator=generator,
225
- true_cfg_scale=guidance_scale,
226
- ).images[0]
 
 
 
 
 
 
227
 
228
- return result, seed
 
 
 
 
229
 
230
  @spaces.GPU(duration=30)
231
- def infer_example(image_1, image_2, prompt, lora_adapter):
232
- if image_1 is None or image_2 is None:
233
- return None, 0
234
- result, seed = infer(
235
- image_1.convert("RGB"),
236
- image_2.convert("RGB"),
237
- prompt,
238
- lora_adapter,
239
- 0,
240
- True,
241
- 1.0,
242
- 4
243
- )
244
- return result, seed
245
 
246
- css="""
247
- #col-container {
248
- margin: 0 auto;
249
- max-width: 1100px;
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  }
251
- #main-title h1 {font-size: 2.1em !important;}
252
  """
253
 
254
  with gr.Blocks() as demo:
 
255
  with gr.Column(elem_id="col-container"):
256
- gr.Markdown("# **Qwen-Image-Edit-2509-LoRAs-Fast-Fusion**", elem_id="main-title")
257
- gr.Markdown("Perform diverse image edits using specialized [LoRA](https://huggingface.co/models?other=base_model:adapter:Qwen/Qwen-Image-Edit-2509) adapters for the [Qwen-Image-Edit](https://huggingface.co/Qwen/Qwen-Image-Edit-2509) model.")
 
258
  with gr.Row(equal_height=True):
259
-
260
  with gr.Column(scale=1):
261
  with gr.Row():
262
- image_1 = gr.Image(label="Base Image", type="pil", height=290)
263
- image_2 = gr.Image(label="Reference Image", type="pil", height=290)
264
 
265
  prompt = gr.Text(
266
- label="Edit Prompt",
267
  show_label=True,
268
- placeholder="e.g., Apply wood texture to the mug...",
 
 
269
  )
270
 
271
- run_button = gr.Button("Edit Image", variant="primary")
 
 
 
272
 
273
- with gr.Accordion("Advanced Settings", open=False, visible=False):
274
- seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
275
- randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
276
- guidance_scale = gr.Slider(label="True Guidance Scale", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
277
- steps = gr.Slider(label="Inference Steps", minimum=1, maximum=50, step=1, value=4)
278
 
279
  with gr.Column(scale=1):
280
- output_image = gr.Image(label="Output Image", interactive=False, format="png", height=350)
 
281
 
282
  with gr.Row():
283
  lora_adapter = gr.Dropdown(
284
- label="Choose Editing Style",
285
- choices=["Texture Edit", "Cloth-Design-Fuse", "Fuse-Objects", "Super-Fusion", "Light-Migration", "Material-Transfer"],
286
- value="Texture Edit",
287
  )
288
 
289
  gr.Examples(
290
  examples=[
291
- ["examples/M1.jpg", "examples/M2.jpg", "Refer to the color tone, remove the original lighting from Image 1, and relight Image 1 based on the lighting and color tone of Image 2.", "Light-Migration"],
292
- ["examples/Cloth2.jpg", "examples/Design2.png", "Put this design on their shirt.", "Cloth-Design-Fuse"],
293
- ["examples/Cup1.png", "examples/Wood1.png", "Apply wood texture to mug.", "Texture Edit"],
294
- ["examples/Cloth1.jpg", "examples/Design1.png", "Put this design on their shirt.", "Cloth-Design-Fuse"],
295
- ["examples/F3.jpg", "examples/F4.jpg", "Replace her glasses with the new glasses from image 1.", "Super-Fusion"],
296
- ["examples/Chair.jpg", "examples/Material.jpg", "Change materials of image1 to match the reference in image2.", "Material-Transfer"],
297
- ["examples/F1.jpg", "examples/F2.jpg", "Put the small bottle on the table.", "Super-Fusion"],
298
- ["examples/Mug1.jpg", "examples/Texture1.jpg", "Apply the design from image 2 to the mug.", "Texture Edit"],
299
- ["examples/Cat1.jpg", "examples/Glass1.webp", "A cat wearing glasses in image 2.", "Fuse-Objects"],
300
-
301
  ],
302
  inputs=[image_1, image_2, prompt, lora_adapter],
303
- outputs=[output_image, seed],
304
  fn=infer_example,
305
  cache_examples=False,
306
- label="Examples"
307
  )
308
 
309
  run_button.click(
310
  fn=infer,
311
  inputs=[image_1, image_2, prompt, lora_adapter, seed, randomize_seed, guidance_scale, steps],
312
- outputs=[output_image, seed]
 
 
 
 
 
 
 
 
313
  )
314
 
315
  if __name__ == "__main__":
316
- demo.queue(max_size=50).launch(css=css, theme=orange_red_theme, mcp_server=True, ssr_mode=False, show_error=True)
 
8
  from typing import Iterable
9
  from gradio.themes import Soft
10
  from gradio.themes.utils import colors, fonts, sizes
11
+ from deep_translator import GoogleTranslator
12
+ from transformers import pipeline
13
 
14
+ # --- تنظیمات تم و رنگ ---
15
  colors.orange_red = colors.Color(
16
  name="orange_red",
17
  c50="#FFF0E5",
 
27
  c950="#802200",
28
  )
29
 
30
+ # --- بارگذاری مدل تشخیص محتوای نامناسب (NSFW) ---
31
+ print("Loading Safety Checker...")
32
+ try:
33
+ safety_classifier = pipeline("image-classification", model="Falconsai/nsfw_image_detection", device=-1)
34
+ except:
35
+ safety_classifier = None
36
+
37
+ def is_image_nsfw(image):
38
+ if image is None or safety_classifier is None: return False
39
+ try:
40
+ results = safety_classifier(image)
41
+ for result in results:
42
+ if result['label'] == 'nsfw' and result['score'] > 0.75:
43
+ return True
44
+ return False
45
+ except Exception as e:
46
+ print(f"Safety check error: {e}")
47
+ return False
48
+
49
+ # --- توابع کمکی متن ---
50
+ BANNED_WORDS = [
51
+ "nude", "naked", "sex", "porn", "undressed", "nsfw", "erotic", "xxx",
52
+ "breast", "nipple", "genital", "vagina", "penis", "ass", "butt", "sexual",
53
+ "lingerie", "bikini", "swimwear", "underwear", "fetish", "topless",
54
+ "exhibitionism", "hentai", "ecchi", "18+"
55
+ ]
56
+
57
+ def check_text_safety(text):
58
+ text_lower = text.lower()
59
+ for word in BANNED_WORDS:
60
+ if f" {word} " in f" {text_lower} ":
61
+ return False
62
+ return True
63
+
64
+ def translate_prompt(text):
65
+ if not text:
66
+ return ""
67
+ try:
68
+ translated = GoogleTranslator(source='auto', target='en').translate(text)
69
+ return translated
70
+ except Exception as e:
71
+ print(f"Translation Error: {e}")
72
+ return text
73
+
74
+ # --- تنظیمات HTML/JS برای مدیریت خطا (نسخه اصلاح شده و قوی) ---
75
+ # این بخش دقیقاً همان لاجیک برنامه قبلی برای حذف خطا و نمایش پنجره راهنماست
76
+ js_global_content = """
77
+ <script>
78
+ document.addEventListener('DOMContentLoaded', () => {
79
+ // 1. Force Light Mode
80
+ const forceLight = () => {
81
+ const body = document.querySelector('body');
82
+ if (body) {
83
+ body.classList.remove('dark');
84
+ body.style.backgroundColor = '#f5f7fa';
85
+ body.style.color = '#333333';
86
+ }
87
+ document.querySelectorAll('.dark').forEach(el => el.classList.remove('dark'));
88
+ };
89
+ forceLight();
90
+ setInterval(forceLight, 1000);
91
 
92
+ // 2. RETRY FUNCTION
93
+ window.retryGeneration = function() {
94
+ const modal = document.getElementById('custom-quota-modal');
95
+ if (modal) modal.remove();
96
+
97
+ const runBtn = document.getElementById('run-btn');
98
+ if(runBtn) runBtn.click();
99
+ };
100
+
101
+ // Close function
102
+ window.closeErrorModal = function() {
103
+ const modal = document.getElementById('custom-quota-modal');
104
+ if (modal) modal.remove();
105
+ };
106
+
107
+ // 3. SHOW MODAL FUNCTION
108
+ const showQuotaModal = () => {
109
+ if (document.getElementById('custom-quota-modal')) return;
110
+
111
+ const modalHtml = `
112
+ <div id="custom-quota-modal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); backdrop-filter: blur(5px); z-index: 99999; display: flex; align-items: center; justify-content: center; font-family: 'Vazirmatn', sans-serif;">
113
+ <div class="ip-reset-guide-container" style="direction: rtl; text-align: right; max-width: 450px; width: 90%; background: white; border-radius: 20px; padding: 25px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); animation: slideInUp 0.4s ease;">
114
+ <div class="guide-header">
115
+ <svg class="guide-header-icon" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
116
+ <defs><lineargradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color: #667eea; stop-opacity: 1;"></stop><stop offset="100%" style="stop-color: #764ba2; stop-opacity: 1;"></stop></lineargradient></defs>
117
+ <circle cx="50" cy="50" r="45" fill="url(#grad1)" opacity="0.1"></circle>
118
+ <circle cx="50" cy="50" r="35" fill="none" stroke="url(#grad1)" stroke-width="2" opacity="0.3"></circle>
119
+ <path d="M35 50 L45 60 L65 40" stroke="url(#grad1)" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"></path>
120
+ <circle cx="65" cy="35" r="8" fill="#fee140"></circle>
121
+ <path d="M62 35 L68 35 M65 32 L65 38" stroke="white" stroke-width="2" stroke-linecap="round"></path>
122
+ </svg>
123
+ <div>
124
+ <h2>یک قدم تا ساخت تصاویر جدید</h2>
125
+ <p>نیازمند تغییر نقطه دستیابی</p>
126
+ </div>
127
+ </div>
128
+
129
+ <div class="guide-content">
130
+ <div class="info-card">
131
+ <div class="info-card-header">
132
+ <svg class="info-card-icon" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" fill="#667eea" opacity="0.2"></path><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" stroke="#667eea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
133
+ <span class="info-card-title">راه حل سریع</span>
134
+ </div>
135
+ <p>طبق ویدیو آموزشی پایین بین نقطه دستیابی جابجا شوید تلاش مجدد بزنید تا تصاویر مجدداً تولید بشه.</p>
136
+ </div>
137
+
138
+ <div class="summary-section">
139
+ <div class="summary-header">
140
+ <svg class="summary-icon" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" fill="#56ab2f" opacity="0.2"></circle><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z" stroke="#56ab2f" stroke-width="2"></path><path d="M9 12l2 2 4-4" stroke="#56ab2f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
141
+ <span class="summary-title">خلاصه راهنما</span>
142
+ </div>
143
+ <div class="summary-text">هربار که این صفحه را مشاهده کردید: از اینترنت سیم‌کارت استفاده کنید، VPN را خاموش کرده و طبق ویدیو آموزشی پایین نقطه دستیابی رو تغییر دهید. «تلاش مجدد» کلیک کنید. با این روش ساده می‌توانید به صورت نامحدود تصویر بسازید! ☘️</div>
144
+ </div>
145
+
146
+ <div class="video-button-container">
147
+ <button onclick="parent.postMessage({ type: 'NAVIGATE_TO_URL', url: '#/nav/online/news/getSingle/1149635/eyJpdiI6IjhHVGhPQWJwb3E0cjRXbnFWTW5BaUE9PSIsInZhbHVlIjoiS1V0dTdvT21wbXAwSXZaK1RCTG1pVXZqdlFJa1hXV1RKa2FLem9zU3pXMjd5MmlVOGc2YWY0NVdNR3h3Smp1aSIsIm1hYyI6IjY1NTA5ZDYzMjAzMTJhMGQyMWQ4NjA4ZDgyNGZjZDVlY2MyNjdiMjA2NWYzOWRjY2M4ZmVjYWRlMWNlMWQ3ODEiLCJ0YWciOiIifQ==/21135210' }, '*')" class="elegant-video-button">
148
+ <svg class="elegant-video-button-icon" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M8 5v14l11-7z"></path></svg>
149
+ <span>دیدن ویدیو آموزشی استفاده نامحدود</span>
150
+ </button>
151
+ </div>
152
+ </div>
153
+
154
+ <div class="guide-actions">
155
+ <button class="action-button back-button" onclick="window.closeErrorModal()">
156
+ <svg class="action-button-icon" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 12H5M12 19l-7-7 7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
157
+ <span>بازگشت</span>
158
+ </button>
159
+ <button class="action-button retry-button" onclick="window.retryGeneration()">
160
+ <svg class="action-button-icon" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M23 4v6h-6M1 20v-6h6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
161
+ <span>تلاش مجدد</span>
162
+ </button>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ `;
167
+
168
+ document.body.insertAdjacentHTML('beforeend', modalHtml);
169
+ };
170
+
171
+ // 4. SCANNER
172
+ setInterval(() => {
173
+ const potentialErrors = document.querySelectorAll('.toast-body, .error, .toast-wrap, .eta-bar, div[class*="error"]');
174
+
175
+ potentialErrors.forEach(el => {
176
+ const text = el.innerText || "";
177
+ if (text.toLowerCase().includes('quota') || text.toLowerCase().includes('exceeded')) {
178
+
179
+ showQuotaModal();
180
+
181
+ // Immediately hide the Gradio error
182
+ el.style.display = 'none';
183
+ el.style.opacity = '0';
184
+ el.innerText = '';
185
+
186
+ const parentWrap = el.closest('.toast-wrap');
187
+ if(parentWrap) parentWrap.style.display = 'none';
188
+ }
189
+ });
190
+ }, 100);
191
+ });
192
+ </script>
193
+ """
194
+
195
+ # --- CSS Updated ---
196
+ css_code = """
197
+ <style>
198
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
199
+
200
+ :root, .dark, body, .gradio-container {
201
+ --body-background-fill: #f5f7fa !important;
202
+ --body-text-color: #1f2937 !important;
203
+ --background-fill-primary: #ffffff !important;
204
+ --background-fill-secondary: #f3f4f6 !important;
205
+ --border-color-primary: #e5e7eb !important;
206
+ --block-background-fill: #ffffff !important;
207
+ --block-label-text-color: #374151 !important;
208
+ --block-title-text-color: #111827 !important;
209
+ --input-background-fill: #ffffff !important;
210
+ color-scheme: light !important;
211
+ }
212
+
213
+ /* --- IP Reset Guide CSS --- */
214
+ :root {
215
+ --guide-bg: rgba(255, 255, 255, 0.98);
216
+ --guide-border: rgba(102, 126, 234, 0.2);
217
+ --guide-text-title: #2d3748;
218
+ --guide-text-body: #4a5568;
219
+ --guide-accent: #667eea;
220
+ --primary-gradient-guide: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
221
+ --success-gradient-guide: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
222
+ --radius-md-guide: 12px;
223
+ --radius-lg-guide: 20px;
224
+ --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
225
+ --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
226
+ --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
227
+ }
228
+
229
+ @keyframes float {
230
+ 0%, 100% { transform: translateY(0px); }
231
+ 50% { transform: translateY(-10px); }
232
+ }
233
+ @keyframes slideInUp {
234
+ from { opacity: 0; transform: translateY(30px); }
235
+ to { opacity: 1; transform: translateY(0); }
236
+ }
237
+
238
+ .guide-header { display: flex; align-items: center; margin-bottom: 20px; }
239
+ .guide-header-icon { width: 50px; height: 50px; margin-left: 15px; animation: float 3s ease-in-out infinite; flex-shrink: 0; }
240
+ .guide-header h2 { font-size: 1.3rem; color: var(--guide-text-title); font-weight: 700; margin: 0; }
241
+ .guide-header p { color: var(--guide-text-body); font-size: 0.8rem; margin-top: 5px; margin-bottom: 0; }
242
+
243
+ .guide-content { font-size: 0.9rem; color: var(--guide-text-body); line-height: 1.6; }
244
+
245
+ .info-card { background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); border: 1px solid rgba(102, 126, 234, 0.2); border-radius: var(--radius-md-guide); padding: 15px; margin: 15px 0; position: relative; overflow: hidden; }
246
+ .info-card p { font-size: 0.85rem; line-height: 1.6; margin: 0; }
247
+ .info-card::before { content: ''; position: absolute; top: 0; right: 0; width: 4px; height: 100%; background: var(--primary-gradient-guide); }
248
+ .info-card-header { display: flex; align-items: center; margin-bottom: 10px; }
249
+ .info-card-icon { width: 20px; height: 20px; margin-left: 10px; }
250
+ .info-card-title { font-weight: 600; color: var(--guide-text-title); font-size: 0.95rem; }
251
+
252
+ .summary-section { margin-top: 15px; padding: 15px; border-radius: var(--radius-md-guide); background: linear-gradient(135deg, #56ab2f15 0%, #a8e06315 100%); border: 1px solid rgba(86, 171, 47, 0.2); position: relative; overflow: hidden; }
253
+ .summary-section::before { content: ''; position: absolute; top: 0; right: 0; width: 4px; height: 100%; background: var(--success-gradient-guide); }
254
+ .summary-header { display: flex; align-items: center; margin-bottom: 8px; }
255
+ .summary-icon { width: 20px; height: 20px; margin-left: 10px; }
256
+ .summary-title { font-weight: 600; color: #2f5a33; font-size: 0.95rem; }
257
+ .summary-text { color: #2f5a33; font-size: 0.85rem; line-height: 1.6; }
258
+
259
+ /* Tutorial Button */
260
+ .video-button-container { text-align: center; margin: 25px 0 15px 0; width: 100%; }
261
+ .elegant-video-button {
262
+ display: inline-flex !important;
263
+ align-items: center;
264
+ justify-content: center;
265
+ padding: 10px 24px !important;
266
+ background-color: #fff !important;
267
+ color: var(--guide-accent) !important;
268
+ border: 1px solid #e2e8f0 !important;
269
+ text-decoration: none;
270
+ border-radius: 50px !important;
271
+ font-weight: 600 !important;
272
+ font-size: 0.9rem !important;
273
+ cursor: pointer !important;
274
+ font-family: inherit;
275
+ transition: all 0.3s ease !important;
276
+ box-shadow: 0 2px 10px rgba(0,0,0,0.05) !important;
277
+ width: auto !important;
278
+ }
279
+ .elegant-video-button:hover {
280
+ background: var(--primary-gradient-guide) !important;
281
+ color: white !important;
282
+ border-color: transparent !important;
283
+ transform: translateY(-2px);
284
+ box-shadow: 0 6px 16px rgba(102, 126, 234, 0.3) !important;
285
+ }
286
+ .elegant-video-button-icon { width: 18px; height: 18px; margin-left: 8px; fill: currentColor; }
287
+
288
+ /* Action Buttons */
289
+ .guide-actions {
290
+ display: flex !important;
291
+ gap: 12px !important;
292
+ margin-top: 20px;
293
+ padding-top: 20px;
294
+ border-top: 1px solid #e2e8f0;
295
+ width: 100% !important;
296
+ }
297
+ .action-button {
298
+ padding: 12px 15px !important;
299
+ border: none !important;
300
+ border-radius: 12px !important;
301
+ font-size: 0.95rem !important;
302
+ font-weight: 600 !important;
303
+ cursor: pointer !important;
304
+ flex: 1 !important;
305
+ transition: all 0.3s ease !important;
306
+ display: flex !important;
307
+ align-items: center;
308
+ justify-content: center;
309
+ font-family: inherit;
310
+ height: 48px !important;
311
+ }
312
+ .action-button-icon { width: 20px; height: 20px; margin-right: 0; margin-left: 8px; }
313
+
314
+ .back-button {
315
+ background: white !important;
316
+ color: var(--guide-text-body) !important;
317
+ border: 2px solid #e2e8f0 !important;
318
+ }
319
+ .back-button:hover {
320
+ background: #f7fafc !important;
321
+ border-color: var(--guide-accent) !important;
322
+ transform: translateY(-2px);
323
+ box-shadow: var(--shadow-md) !important;
324
+ }
325
+
326
+ .retry-button {
327
+ background: var(--primary-gradient-guide) !important;
328
+ color: white !important;
329
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3) !important;
330
+ }
331
+ .retry-button:hover {
332
+ transform: translateY(-2px);
333
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4) !important;
334
+ }
335
+
336
+ /* --- Main App CSS --- */
337
+ body {
338
+ font-family: 'Vazirmatn', sans-serif !important;
339
+ background-color: #f5f7fa !important;
340
+ margin: 0;
341
+ padding: 10px;
342
+ }
343
+
344
+ #col-container {
345
+ margin: 0 auto;
346
+ max-width: 980px;
347
+ direction: rtl;
348
+ text-align: right;
349
+ padding: 30px;
350
+ background: #ffffff !important;
351
+ border-radius: 24px;
352
+ box-shadow: 0 10px 40px -10px rgba(0,0,0,0.08);
353
+ border: 1px solid rgba(255,255,255,0.8);
354
+ }
355
+
356
+ #main-title h1 {
357
+ font-size: 2.4em !important;
358
+ text-align: center;
359
+ color: #1a202c !important;
360
+ margin-bottom: 15px;
361
+ font-weight: 800;
362
+ background: -webkit-linear-gradient(45deg, #2563eb, #1e40af);
363
+ -webkit-background-clip: text;
364
+ -webkit-text-fill-color: transparent;
365
+ }
366
+
367
+ #main-description {
368
+ text-align: center;
369
+ font-size: 1.15em;
370
+ color: #4b5563 !important;
371
+ margin-bottom: 40px;
372
+ line-height: 1.6;
373
+ }
374
+
375
+ .gr-input-label, span.label-wrap, label span {
376
+ font-weight: 700 !important;
377
+ color: #374151 !important;
378
+ font-size: 0.95em !important;
379
+ margin-bottom: 8px !important;
380
+ }
381
+
382
+ textarea, input[type="text"] {
383
+ border: 2px solid #e2e8f0 !important;
384
+ border-radius: 12px !important;
385
+ background-color: #ffffff !important;
386
+ color: #111827 !important;
387
+ padding: 12px !important;
388
+ transition: all 0.3s ease;
389
+ font-family: 'Vazirmatn', sans-serif !important;
390
+ }
391
+
392
+ textarea:focus, input[type="text"]:focus {
393
+ border-color: #3b82f6 !important;
394
+ box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1) !important;
395
+ outline: none;
396
+ }
397
+
398
+ .gr-dropdown {
399
+ background: #ffffff !important;
400
+ border-radius: 12px !important;
401
+ }
402
+
403
+ .primary-btn, button.primary {
404
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
405
+ border: none !important;
406
+ color: white !important;
407
+ font-weight: 700 !important;
408
+ font-size: 1.1em !important;
409
+ padding: 14px 28px !important;
410
+ border-radius: 14px !important;
411
+ box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3) !important;
412
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
413
+ cursor: pointer !important;
414
+ width: 100%;
415
+ margin-top: 15px;
416
+ }
417
+
418
+ .primary-btn:hover, button.primary:hover {
419
+ transform: translateY(-2px);
420
+ box-shadow: 0 8px 25px rgba(16, 185, 129, 0.45) !important;
421
+ }
422
+
423
+ .primary-btn:active, button.primary:active {
424
+ transform: translateY(1px);
425
+ }
426
+
427
+ #download-btn {
428
+ background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
429
+ box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3) !important;
430
+ }
431
+ #download-btn:hover {
432
+ box-shadow: 0 8px 25px rgba(59, 130, 246, 0.45) !important;
433
+ }
434
+
435
+ .gradio-container .prose table,
436
+ .gradio-container table {
437
+ background-color: #ffffff !important;
438
+ color: #111827 !important;
439
+ border: 1px solid #e5e7eb !important;
440
+ border-radius: 12px !important;
441
+ overflow: hidden !important;
442
+ width: 100% !important;
443
+ margin-top: 20px !important;
444
+ }
445
+
446
+ .gradio-container thead th {
447
+ background-color: #f3f4f6 !important;
448
+ color: #374151 !important;
449
+ font-weight: 700 !important;
450
+ border-bottom: 2px solid #e5e7eb !important;
451
+ padding: 12px !important;
452
+ text-align: right !important;
453
+ }
454
+
455
+ .gradio-container tbody tr {
456
+ background-color: #ffffff !important;
457
+ border-bottom: 1px solid #f3f4f6 !important;
458
+ }
459
+
460
+ .gradio-container tbody tr:hover {
461
+ background-color: #f9fafb !important;
462
+ }
463
+
464
+ .gradio-container tbody td {
465
+ background-color: #ffffff !important;
466
+ color: #374151 !important;
467
+ padding: 10px !important;
468
+ }
469
+
470
+ .gradio-container tbody td span,
471
+ .gradio-container tbody td p {
472
+ color: #374151 !important;
473
+ }
474
+
475
+ footer { display: none !important; }
476
+ .flagging { display: none !important; }
477
+
478
+ /* Force toast transparency */
479
+ .toast-body {
480
+ direction: rtl !important;
481
+ text-align: right !important;
482
+ background: transparent !important;
483
+ box-shadow: none !important;
484
+ border: none !important;
485
+ padding: 0 !important;
486
+ max-width: 100% !important;
487
+ width: auto !important;
488
+ }
489
+ .toast-wrap {
490
+ background: transparent !important;
491
+ border: none !important;
492
+ box-shadow: none !important;
493
+ }
494
+
495
+ @media (prefers-color-scheme: dark) {
496
+ body, .gradio-container, .prose, table, tr, td, th {
497
+ background-color: #ffffff !important;
498
+ color: #333333 !important;
499
+ }
500
+ }
501
+ </style>
502
+ """
503
+
504
+ # ادغام CSS و JS
505
+ combined_html = css_code + js_global_content
506
 
507
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
508
  dtype = torch.bfloat16
509
 
 
 
 
 
 
510
  print("Loading Qwen Image Edit Pipeline...")
511
  pipe = QwenImageEditPlusPipeline.from_pretrained(
512
  "Qwen/Qwen-Image-Edit-2509",
 
520
  ).to(device)
521
 
522
  print("Loading and Fusing Lightning LoRA...")
523
+ pipe.load_lora_weights("lightx2v/Qwen-Image-Lightning", weight_name="Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors", adapter_name="lightning")
 
 
524
  pipe.fuse_lora(adapter_names=["lightning"], lora_scale=1.0)
525
 
526
  print("Loading Task Adapters...")
527
+ pipe.load_lora_weights("tarn59/apply_texture_qwen_image_edit_2509", weight_name="apply_texture_v2_qwen_image_edit_2509.safetensors", adapter_name="texture")
528
+ pipe.load_lora_weights("ostris/qwen_image_edit_inpainting", weight_name="qwen_image_edit_inpainting.safetensors", adapter_name="fusion")
529
+ pipe.load_lora_weights("ostris/qwen_image_edit_2509_shirt_design", weight_name="qwen_image_edit_2509_shirt_design.safetensors", adapter_name="shirt_design")
530
+ pipe.load_lora_weights("dx8152/Qwen-Image-Edit-2509-Fusion", weight_name="溶图.safetensors", adapter_name="fusion-x")
531
+ pipe.load_lora_weights("oumoumad/Qwen-Edit-2509-Material-transfer", weight_name="material-transfer_000004769.safetensors", adapter_name="material-transfer")
532
+ pipe.load_lora_weights("dx8152/Qwen-Edit-2509-Light-Migration", weight_name="参考色调.safetensors", adapter_name="light-migration")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
 
534
  try:
535
  pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
 
539
 
540
  MAX_SEED = np.iinfo(np.int32).max
541
 
542
+ LORA_MAPPING_PERSIAN = {
543
+ "ویرایش بافت (Texture)": "Texture Edit",
544
+ "طراحی لباس (Shirt Design)": "Cloth-Design-Fuse",
545
+ "ترکیب اشیاء (Fusion)": "Fuse-Objects",
546
+ "ترکیب پیشرفته (Super Fusion)": "Super-Fusion",
547
+ "انتقال نور (Light Migration)": "Light-Migration",
548
+ "انتقال متریال (Material Transfer)": "Material-Transfer"
549
+ }
550
+
551
  def update_dimensions_on_upload(image):
552
+ if image is None: return 1024, 1024
 
 
553
  original_width, original_height = image.size
 
554
  if original_width > original_height:
555
  new_width = 1024
556
  aspect_ratio = original_height / original_width
 
559
  new_height = 1024
560
  aspect_ratio = original_width / original_height
561
  new_width = int(new_height * aspect_ratio)
 
 
562
  new_width = (new_width // 16) * 16
563
  new_height = (new_height // 16) * 16
 
564
  return new_width, new_height
565
 
566
+ def get_error_html(message):
567
+ return f"""
568
+ <div style="background-color: #fee2e2; border: 1px solid #ef4444; color: #b91c1c; padding: 12px; border-radius: 8px; text-align: center; margin-bottom: 10px; font-weight: bold; display: flex; align-items: center; justify-content: center; gap: 8px; direction: rtl;">
569
+ <span style="font-size: 1.2em;">⛔</span>
570
+ {message}
571
+ </div>
572
+ """
573
+
574
+ def get_success_html(message):
575
+ return f"""
576
+ <div style="background-color: #dcfce7; border: 1px solid #22c55e; color: #15803d; padding: 12px; border-radius: 8px; text-align: center; margin-bottom: 10px; font-weight: bold; display: flex; align-items: center; justify-content: center; gap: 8px; direction: rtl;">
577
+ <span style="font-size: 1.2em;">✅</span>
578
+ {message}
579
+ </div>
580
+ """
581
+
582
  @spaces.GPU(duration=30)
583
  def infer(
584
  image_1,
585
  image_2,
586
  prompt,
587
+ lora_adapter_persian,
588
  seed,
589
  randomize_seed,
590
  guidance_scale,
 
592
  progress=gr.Progress(track_tqdm=True)
593
  ):
594
  if image_1 is None or image_2 is None:
595
+ return None, seed, get_error_html("لطفاً هر دو تصویر (تصویر پایه و تصویر مرجع) را بارگذاری کنید.")
596
+
597
+ if is_image_nsfw(image_1) or is_image_nsfw(image_2):
598
+ return None, seed, get_error_html("تصویر ورودی دارای محتوای نامناسب است و پردازش نمی‌شود.")
599
+
600
+ lora_adapter = LORA_MAPPING_PERSIAN.get(lora_adapter_persian, "Texture Edit")
601
 
602
+ english_prompt = translate_prompt(prompt)
603
+ if not english_prompt:
604
+ # Default prompts based on internal logic
605
+ if lora_adapter == "Cloth-Design-Fuse": english_prompt = "Put this design on their shirt."
606
+ elif lora_adapter == "Texture Edit": english_prompt = "Apply texture to object."
607
+ elif lora_adapter == "Fuse-Objects": english_prompt = "Fuse object into background."
608
+ elif lora_adapter == "Super-Fusion": english_prompt = "Blend the product into the background, correct its perspective and lighting, and make it naturally integrated with the scene."
609
+ elif lora_adapter == "Material-Transfer": english_prompt = "change materials of image1 to match the reference in image2"
610
+ elif lora_adapter == "Light-Migration": english_prompt = "Refer to the color tone, remove the original lighting from Image 1, and relight Image 1 based on the lighting and color tone of Image 2."
611
+
612
+ if not check_text_safety(english_prompt):
613
+ return None, seed, get_error_html("متن درخواست شامل کلمات غیرمجاز است.")
614
+
 
615
  adapters_map = {
616
  "Texture Edit": "texture",
617
  "Fuse-Objects": "fusion",
 
622
  }
623
 
624
  active_adapter = adapters_map.get(lora_adapter)
 
625
  if active_adapter:
626
  pipe.set_adapters([active_adapter], adapter_weights=[1.0])
627
  else:
 
631
  seed = random.randint(0, MAX_SEED)
632
 
633
  generator = torch.Generator(device=device).manual_seed(seed)
634
+ negative_prompt = "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry, nsfw, nude"
635
 
636
  img1_pil = image_1.convert("RGB")
637
  img2_pil = image_2.convert("RGB")
638
 
639
  width, height = update_dimensions_on_upload(img1_pil)
640
 
641
+ try:
642
+ result = pipe(
643
+ image=[img1_pil, img2_pil],
644
+ prompt=english_prompt,
645
+ negative_prompt=negative_prompt,
646
+ height=height,
647
+ width=width,
648
+ num_inference_steps=steps,
649
+ generator=generator,
650
+ true_cfg_scale=guidance_scale,
651
+ ).images[0]
652
+
653
+ if is_image_nsfw(result):
654
+ return None, seed, get_error_html("تصویر خروجی حاوی محتوای نامناسب بود و حذف شد.")
655
+
656
+ return result, seed, get_success_html("تصویر با موفقیت ترکیب و ویرایش شد.")
657
 
658
+ except Exception as e:
659
+ error_str = str(e)
660
+ if "quota" in error_str.lower() or "exceeded" in error_str.lower():
661
+ raise e
662
+ return None, seed, get_error_html(f"خطا در پردازش: {error_str}")
663
 
664
  @spaces.GPU(duration=30)
665
+ def infer_example(image_1, image_2, prompt, lora_adapter_persian):
666
+ res, s, status = infer(image_1, image_2, prompt, lora_adapter_persian, 0, True, 1.0, 4)
667
+ return res, s, status
 
 
 
 
 
 
 
 
 
 
 
668
 
669
+ # --- رابط کاربری ---
670
+ js_download_func = """
671
+ async (image) => {
672
+ if (!image) {
673
+ alert("لطفاً ابتدا تصویر را تولید کنید.");
674
+ return;
675
+ }
676
+ let fileUrl = image.url;
677
+ if (fileUrl && !fileUrl.startsWith('http')) {
678
+ fileUrl = window.location.origin + fileUrl;
679
+ } else if (!fileUrl && image.path) {
680
+ fileUrl = window.location.origin + "/file=" + image.path;
681
+ }
682
+ window.parent.postMessage({
683
+ type: 'DOWNLOAD_REQUEST',
684
+ url: fileUrl
685
+ }, '*');
686
  }
 
687
  """
688
 
689
  with gr.Blocks() as demo:
690
+ gr.HTML(combined_html)
691
  with gr.Column(elem_id="col-container"):
692
+ gr.Markdown("# **ویرایشگر هوشمند آلفا (ترکیبی)**", elem_id="main-title")
693
+ gr.Markdown("با هوش مصنوعی آلفا تصاویر خود را با متد‌های مختلف (بافت، ترکیب، نورپردازی) ویرایش کنید.", elem_id="main-description")
694
+
695
  with gr.Row(equal_height=True):
 
696
  with gr.Column(scale=1):
697
  with gr.Row():
698
+ image_1 = gr.Image(label="۱. تصویر پایه (اصلی)", type="pil", height=290)
699
+ image_2 = gr.Image(label="۲. تصویر مرجع (استایل/بافت)", type="pil", height=290)
700
 
701
  prompt = gr.Text(
702
+ label="دستور ویرایش (به فارسی)",
703
  show_label=True,
704
+ placeholder="مثال: بافت چوب را روی لیوان اعمال کن...",
705
+ rtl=True,
706
+ lines=3
707
  )
708
 
709
+ status_box = gr.HTML(label="وضعیت")
710
+
711
+ # IMPORTANT: elem_id="run-btn" for JS
712
+ run_button = gr.Button("✨ شروع پردازش و ترکیب تصاویر", variant="primary", elem_classes="primary-btn", elem_id="run-btn")
713
 
714
+ with gr.Accordion("تنظیمات پیشرفته", open=False, visible=True):
715
+ seed = gr.Slider(label="دانه تصادفی (Seed)", minimum=0, maximum=MAX_SEED, step=1, value=0)
716
+ randomize_seed = gr.Checkbox(label="استفاده از Seed تصادفی", value=True)
717
+ guidance_scale = gr.Slider(label="میزان وفاداری (Guidance Scale)", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
718
+ steps = gr.Slider(label="تعداد مراحل (Steps)", minimum=1, maximum=50, step=1, value=4)
719
 
720
  with gr.Column(scale=1):
721
+ output_image = gr.Image(label="تصویر نهایی", interactive=False, format="png", height=350)
722
+ download_button = gr.Button("📥 دانلود و ذخیره تصویر", variant="secondary", elem_id="download-btn", elem_classes="primary-btn")
723
 
724
  with gr.Row():
725
  lora_adapter = gr.Dropdown(
726
+ label="انتخاب سبک ویرایش",
727
+ choices=list(LORA_MAPPING_PERSIAN.keys()),
728
+ value="ویرایش بافت (Texture)",
729
  )
730
 
731
  gr.Examples(
732
  examples=[
733
+ ["examples/M1.jpg", "examples/M2.jpg", "با توجه به تن رنگ، نورپردازی اصلی تصویر ۱ را حذف کن و بر اساس نور و رنگ تصویر ۲ مجدداً نورپردازی کن.", "انتقال نور (Light Migration)"],
734
+ ["examples/Cloth2.jpg", "examples/Design2.png", "این طر�� را روی پیراهن قرار بده.", "طراحی لباس (Shirt Design)"],
735
+ ["examples/Cup1.png", "examples/Wood1.png", "بافت چوب را روی لیوان اعمال کن.", "ویرایش بافت (Texture)"],
736
+ ["examples/Cloth1.jpg", "examples/Design1.png", "این طرح را روی پیراهن قرار بده.", "طراحی لباس (Shirt Design)"],
737
+ ["examples/F3.jpg", "examples/F4.jpg", "عینک او را با عینک جدید از تصویر ۱ جایگزین کن.", "ترکیب پیشرفته (Super Fusion)"],
738
+ ["examples/Chair.jpg", "examples/Material.jpg", "متریال تصویر ۱ را تغییر بده تا با مرجع تصویر ۲ مطابقت داشته باشد.", "انتقال متریال (Material Transfer)"],
739
+ ["examples/F1.jpg", "examples/F2.jpg", "بطری کوچک را روی میز قرار بده.", "ترکیب پیشرفته (Super Fusion)"],
740
+ ["examples/Mug1.jpg", "examples/Texture1.jpg", "طرح تصویر ۲ را روی لیوان اعمال کن.", "ویرایش بافت (Texture)"],
741
+ ["examples/Cat1.jpg", "examples/Glass1.webp", "یک گربه که عینک زده است.", "ترکیب اشیاء (Fusion)"],
 
742
  ],
743
  inputs=[image_1, image_2, prompt, lora_adapter],
744
+ outputs=[output_image, seed, status_box],
745
  fn=infer_example,
746
  cache_examples=False,
747
+ label="نمونه‌ها (برای تست کلیک کنید)"
748
  )
749
 
750
  run_button.click(
751
  fn=infer,
752
  inputs=[image_1, image_2, prompt, lora_adapter, seed, randomize_seed, guidance_scale, steps],
753
+ outputs=[output_image, seed, status_box],
754
+ api_name="predict"
755
+ )
756
+
757
+ download_button.click(
758
+ fn=None,
759
+ inputs=[output_image],
760
+ outputs=None,
761
+ js=js_download_func
762
  )
763
 
764
  if __name__ == "__main__":
765
+ demo.queue(max_size=50).launch(show_error=True)