Opera8 commited on
Commit
f0c29ea
·
verified ·
1 Parent(s): 89f5cac

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +697 -278
app.py CHANGED
@@ -5,11 +5,11 @@ import spaces
5
  import torch
6
  import random
7
  import json
8
- import hashlib
9
  from datetime import datetime
10
  from PIL import Image, ImageFilter
 
11
  from gradio.themes import Soft
12
- from gradio.themes.utils import colors
13
  from deep_translator import GoogleTranslator
14
  from transformers import pipeline
15
 
@@ -31,6 +31,12 @@ colors.steel_blue = colors.Color(
31
 
32
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
33
 
 
 
 
 
 
 
34
  # --- بارگذاری مدل تشخیص محتوای نامناسب (NSFW) ---
35
  print("Loading Safety Checker...")
36
  safety_classifier = pipeline("image-classification", model="Falconsai/nsfw_image_detection", device=-1)
@@ -47,13 +53,47 @@ def is_image_nsfw(image):
47
  print(f"Safety check error: {e}")
48
  return False
49
 
50
- # --- بارگذاری مدل اصلی Qwen ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  from diffusers import FlowMatchEulerDiscreteScheduler
52
  from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
53
  from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
54
  from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
55
 
56
  dtype = torch.bfloat16
 
 
57
  print("Loading pipeline...")
58
  pipe = QwenImageEditPlusPipeline.from_pretrained(
59
  "Qwen/Qwen-Image-Edit-2509",
@@ -90,6 +130,14 @@ LORA_MAPPING = {
90
  "افزایش کیفیت (Upscale)": "upscale-image"
91
  }
92
 
 
 
 
 
 
 
 
 
93
  ASPECT_RATIOS_MAP = {
94
  "خودکار (پیش‌فرض)": "Auto",
95
  "۱:۱ (مربع - 1024x1024)": (1024, 1024),
@@ -98,86 +146,6 @@ ASPECT_RATIOS_MAP = {
98
  "شخصی‌سازی (Custom)": "Custom"
99
  }
100
 
101
- # --- لیست کلمات ممنوعه (سختگیرانه و کامل) ---
102
- # این لیست شامل کلمات جنسی، خشونت آمیز و نامناسب است
103
- BANNED_WORDS = [
104
- "nsfw", "nude", "naked", "sex", "sexy", "porn", "erotic", "xxx",
105
- "breast", "nipple", "genital", "vagina", "penis", "ass", "butt", "sexual",
106
- "lingerie", "bikini", "swimwear", "underwear", "fetish", "topless",
107
- "exhibitionism", "hentai", "ecchi", "18+", "hot girl", "sensual",
108
- "intercourse", "bdsm", "bondage", "anus", "pussy", "dick", "cock",
109
- "cum", "orgasm", "masturbation", "slut", "whore", "milf", "orgy",
110
- "penetration", "striptease", "gore", "blood", "violence", "suicide",
111
- "kill", "death", "murder", "torture", "abuse", "rapist", "rape",
112
- "cleavage", "areola", "pubic", "upskirt", "intimate"
113
- ]
114
-
115
- # --- سیستم مدیریت اعتبار (حافظه موقت) ---
116
- # ساخت��ر: { "ip_fingerprint_hash": { "date": "YYYY-MM-DD", "count": 0 } }
117
- USAGE_DB = {}
118
- MAX_FREE_DAILY_LIMIT = 5
119
-
120
- def get_quota_status(fingerprint_hash, is_paid, request: gr.Request):
121
- """
122
- بررسی وضعیت اعتبار کاربر.
123
- اگر کاربر پولی باشد، همیشه مجاز است.
124
- اگر رایگان باشد، بر اساس ترکیب IP و Fingerprint بررسی می‌شود.
125
- """
126
- if is_paid:
127
- return True, "نامحدود", 0
128
-
129
- # ترکیب IP و Fingerprint برای جلوگیری از تغییر IP ساده
130
- client_ip = request.client.host if request else "unknown"
131
- # ساخت کلید یکتا برای کاربر
132
- user_key = f"{client_ip}_{fingerprint_hash}"
133
-
134
- current_date = datetime.now().strftime("%Y-%m-%d")
135
-
136
- # دریافت یا ایجاد رکورد کاربر
137
- user_data = USAGE_DB.get(user_key, {"date": current_date, "count": 0})
138
-
139
- # ریست کردن در روز جدید
140
- if user_data["date"] != current_date:
141
- user_data = {"date": current_date, "count": 0}
142
-
143
- # بررسی محدودیت
144
- if user_data["count"] >= MAX_FREE_DAILY_LIMIT:
145
- return False, "اتمام اعتبار", 0
146
-
147
- remaining = MAX_FREE_DAILY_LIMIT - user_data["count"]
148
- return True, "مجاز", remaining
149
-
150
- def increment_usage(fingerprint_hash, is_paid, request: gr.Request):
151
- if is_paid: return
152
-
153
- client_ip = request.client.host if request else "unknown"
154
- user_key = f"{client_ip}_{fingerprint_hash}"
155
- current_date = datetime.now().strftime("%Y-%m-%d")
156
-
157
- user_data = USAGE_DB.get(user_key, {"date": current_date, "count": 0})
158
- if user_data["date"] != current_date:
159
- user_data = {"date": current_date, "count": 0}
160
-
161
- user_data["count"] += 1
162
- USAGE_DB[user_key] = user_data
163
-
164
- # --- توابع کمکی ---
165
-
166
- def check_text_safety(text):
167
- text_lower = text.lower()
168
- # بررسی دقیق کلمات (با فاصله برای جلوگیری از تطابق اشتباه)
169
- for word in BANNED_WORDS:
170
- # چک کردن کلمه به صورت جداگانه
171
- if f" {word} " in f" {text_lower} " or \
172
- text_lower.startswith(f"{word} ") or \
173
- text_lower.endswith(f" {word}") or \
174
- text_lower == word:
175
- return False
176
- # چک کردن کلمات ترکیبی خطرناک
177
- if word in text_lower and len(word) > 4: # برای کلمات طولانی‌تر سختگیرانه‌تر
178
- return False
179
- return True
180
-
181
  def translate_prompt(text):
182
  if not text:
183
  return ""
@@ -204,6 +172,12 @@ def update_dimensions_on_upload(image):
204
  new_height = (new_height // 8) * 8
205
  return new_width, new_height
206
 
 
 
 
 
 
 
207
  def get_error_html(message):
208
  return f"""
209
  <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;">
@@ -220,7 +194,40 @@ def get_success_html(message):
220
  </div>
221
  """
222
 
223
- @spaces.GPU(duration=45)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  def infer(
225
  input_image,
226
  prompt,
@@ -232,31 +239,31 @@ def infer(
232
  aspect_ratio_selection,
233
  custom_width,
234
  custom_height,
235
- fingerprint_hash,
236
- is_paid_user_str,
237
- request: gr.Request,
238
  progress=gr.Progress(track_tqdm=True)
239
  ):
240
- # تبدیل وضعیت کاربر از رشته به بولین
241
- is_paid = is_paid_user_str == "true"
242
-
243
- # 1. بررسی اعتبار قبل از هر کاری
244
- allowed, status_msg, remaining = get_quota_status(fingerprint_hash, is_paid, request)
245
-
246
- if not allowed:
247
- return None, seed, get_error_html("اعتبار ساخت تصویر شما برای امروز تمام شده است. لطفاً برای استفاده نامحدود حساب خود را ارتقا دهید."), f"0 اعتبار"
248
 
249
  if input_image is None:
250
- return None, seed, get_error_html("لطفاً ابتدا یک تصویر بارگذاری کنید."), f"{remaining} اعتبار"
251
 
252
- # 2. بررسی ایمنی تصویر ورودی
 
 
 
 
 
 
 
253
  if is_image_nsfw(input_image):
254
- return None, seed, get_error_html("تصویر ورودی دارای محتوای نامناسب است و پردازش نمی‌شود."), f"{remaining} اعتبار"
255
 
256
- # 3. ترجمه و بررسی ایمنی متن
257
  english_prompt = translate_prompt(prompt)
258
  if not check_text_safety(english_prompt):
259
- return None, seed, get_error_html("متن درخواست شامل کلمات غیرمجاز، غیراخلاقی یا جنسی است."), f"{remaining} اعتبار"
260
 
261
  adapter_internal_name = LORA_MAPPING.get(lora_adapter_persian)
262
  if adapter_internal_name:
@@ -267,7 +274,7 @@ def infer(
267
 
268
  generator = torch.Generator(device=device).manual_seed(seed)
269
 
270
- safety_negative = "nsfw, nude, naked, porn, sexual, xxx, breast, nipple, genital, vagina, penis, ass, lingerie, bikini, swimwear, underwear, fetish, topless, gore, violence, blood"
271
  base_negative = "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
272
  final_negative_prompt = f"{safety_negative}, {base_negative}"
273
 
@@ -295,40 +302,59 @@ def infer(
295
  true_cfg_scale=guidance_scale,
296
  ).images[0]
297
 
298
- # 4. بررسی ایمنی تصویر خروجی
299
  if is_image_nsfw(result):
300
- return None, seed, get_error_html("تصویر تولید شده حاوی محتوای نامناسب بود و حذف شد."), f"{remaining} اعتبار"
301
-
302
- # 5. کسر اعتبار (فقط اگر همه چیز موفق بود)
303
- increment_usage(fingerprint_hash, is_paid, request)
304
 
305
- # محاسبه مجدد باقی مانده برای نمایش دقیق
306
- _, _, new_remaining = get_quota_status(fingerprint_hash, is_paid, request)
307
- credit_text = "نامحدود" if is_paid else f"{new_remaining} اعتبار برای امروز"
308
-
309
- return result, seed, get_success_html("تصویر با موفقیت ویرایش شد."), credit_text
 
310
 
311
  except Exception as e:
312
  error_str = str(e)
313
  if "quota" in error_str.lower() or "exceeded" in error_str.lower():
314
  raise e
315
- return None, seed, get_error_html(f"خطا در پردازش: {error_str}"), f"{remaining} اعتبار"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
 
317
- # --- جاوااسکریپت برای دانلود و ارتباطات ---
318
- js_func = """
319
  <script>
320
- // --- پیکربندی پرداخت ---
321
- const PREMIUM_PAGE_ID = '1149636';
322
  const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTc1NDIyNGVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
323
 
324
- // --- تولید Fingerprint مرورگر ---
325
- async function getBrowserFingerprint() {
326
- const components = [
327
- navigator.userAgent,
328
- navigator.language,
329
- screen.width + 'x' + screen.height,
330
- new Date().getTimezoneOffset()
331
- ];
332
  try {
333
  const canvas = document.createElement('canvas');
334
  const ctx = canvas.getContext('2d');
@@ -338,14 +364,12 @@ async function getBrowserFingerprint() {
338
  ctx.fillStyle = "#f60";
339
  ctx.fillRect(125, 1, 62, 20);
340
  ctx.fillStyle = "#069";
341
- ctx.fillText("alpha_img_edit_fp_v1", 2, 15);
342
  components.push(canvas.toDataURL());
343
  } catch (e) {
344
  components.push("canvas-error");
345
  }
346
  const fingerprintString = components.join('~~~');
347
-
348
- // Simple hash function
349
  let hash = 0;
350
  for (let i = 0; i < fingerprintString.length; i++) {
351
  const char = fingerprintString.charCodeAt(i);
@@ -355,49 +379,74 @@ async function getBrowserFingerprint() {
355
  return 'fp_' + Math.abs(hash).toString(16);
356
  }
357
 
358
- // --- مدیریت ارتباط با والد (Android/Web) ---
 
 
 
359
  function isUserPaid(userObject) {
360
- return userObject && userObject.isLogin && userObject.accessible_pages &&
361
- (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) ||
362
- userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID)));
363
  }
364
 
365
- function updateUI(isPaid) {
366
- const badge = document.getElementById('subscription-status-badge');
367
- const upgradeBtn = document.getElementById('upgrade-premium-btn');
368
- const hiddenStatus = document.querySelector('#is-paid-user textarea');
369
 
370
- // بروزرسانی مقدار مخفی برای پایتون
371
- if(hiddenStatus) {
372
- hiddenStatus.value = isPaid ? "true" : "false";
373
- hiddenStatus.dispatchEvent(new Event('input'));
374
- }
375
-
376
- if (isPaid) {
377
  if(badge) {
378
- badge.textContent = 'نسخه نامحدود';
379
- badge.className = 'paid-badge';
380
- badge.style.display = 'inline-block';
381
  }
382
- if(upgradeBtn) upgradeBtn.style.display = 'none';
383
-
384
- // Hide credits text if visible
385
- const creditDisplay = document.getElementById('credit-display-text');
386
- if(creditDisplay) creditDisplay.textContent = 'نامحدود';
387
-
388
  } else {
 
389
  if(badge) {
390
  badge.textContent = 'نسخه رایگان';
391
- badge.className = 'free-badge';
392
- badge.style.display = 'inline-block';
393
  }
394
- if(upgradeBtn) upgradeBtn.style.display = 'block';
395
  }
 
 
 
396
  }
397
 
398
- // --- مقداردهی اولیه ---
399
- document.addEventListener('DOMContentLoaded', async () => {
 
 
 
400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  // 1. Force Light Mode
402
  const forceLight = () => {
403
  const body = document.querySelector('body');
@@ -411,118 +460,200 @@ document.addEventListener('DOMContentLoaded', async () => {
411
  forceLight();
412
  setInterval(forceLight, 1000);
413
 
414
- // 2. Fingerprint Setup
415
- const fp = await getBrowserFingerprint();
416
- const fpInput = document.querySelector('#fingerprint-input textarea');
417
- if(fpInput) {
418
- fpInput.value = fp;
419
- fpInput.dispatchEvent(new Event('input'));
420
- }
421
-
422
- // 3. Request User Status
423
- window.parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
424
 
425
- // 4. Listen for Response
426
- window.addEventListener('message', (event) => {
427
- if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
428
- try {
429
- const userObject = JSON.parse(event.data.payload);
430
- const isPaid = isUserPaid(userObject);
431
- updateUI(isPaid);
432
- } catch (e) {
433
- console.error("Error parsing user status", e);
434
- updateUI(false);
435
- }
436
  }
437
- });
438
-
439
- // 5. Upgrade Button Logic
440
- window.triggerUpgrade = function() {
441
- window.parent.postMessage({
442
- type: 'NAVIGATE_TO_PREMIUM',
443
- payload: { url: PREMIUM_URL }
444
- }, '*');
 
445
  };
446
 
447
- // 6. Download Logic
448
- window.downloadImage = async (image) => {
449
- if (!image) { alert("لطفاً ابتدا تصویر را تولید کنید."); return; }
450
- let fileUrl = image.url;
451
- if (fileUrl && !fileUrl.startsWith('http')) {
452
- fileUrl = window.location.origin + fileUrl;
453
- } else if (!fileUrl && image.path) {
454
- fileUrl = window.location.origin + "/file=" + image.path;
455
- }
456
- window.parent.postMessage({
457
- type: 'DOWNLOAD_REQUEST',
458
- url: fileUrl
459
- }, '*');
460
- }
461
-
462
- // 7. Error Modal Logic (Quota Exceeded from Gradio Backend)
463
  const showQuotaModal = () => {
464
  if (document.getElementById('custom-quota-modal')) return;
465
- // ساخت مودال مشابه پادکست نیست، بلکه مودال IP Reset قبلی است
466
- // اما اینجا ما سیستم کردیت داریم، پس ارور کردیت را مدیریت می‌کنیم
467
- // اگر ارور سمت سرور "Quota Exceeded" بود یعنی کاربر رایگان محدودیتش تمام شده
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469
  });
 
 
 
 
 
 
470
  </script>
471
  """
472
 
 
473
  css_code = """
474
  <style>
475
- @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
476
 
477
  :root, .dark, body, .gradio-container {
478
  --body-background-fill: #f5f7fa !important;
479
  --body-text-color: #1f2937 !important;
480
- --app-font: 'Vazirmatn', sans-serif !important;
 
 
 
 
 
 
481
  color-scheme: light !important;
482
  }
483
 
484
- body { font-family: var(--app-font); direction: rtl; }
485
-
486
- /* --- Badges & Buttons Styles (From Podcast Code) --- */
487
- @keyframes badge-fade-in { from { opacity: 0; transform: translateY(-10px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
488
-
489
- #subscription-status-badge {
490
- display: none; /* Controlled by JS */
 
 
 
491
  padding: 6px 16px;
492
  border-radius: 20px;
493
  font-size: 0.9em;
494
  font-weight: 700;
495
- margin-top: 1rem;
496
- margin-bottom: 1rem;
497
  letter-spacing: 0.5px;
498
  text-shadow: 0 1px 2px rgba(0,0,0,0.1);
499
- animation: badge-fade-in 0.6s 0.5s ease-out backwards;
500
  }
501
-
502
  .free-badge {
503
  background: linear-gradient(45deg, #6c757d, #495057);
504
  color: white;
505
  box-shadow: 0 4px 10px rgba(108, 117, 125, 0.3);
506
  }
507
-
508
  .paid-badge {
509
- background: linear-gradient(45deg, #FFC107, #ffca2c);
510
  color: #333;
511
- box-shadow: 0 4px 10px rgba(255, 193, 7, 0.3);
512
  }
513
-
514
- @keyframes upgrade-btn-pulse {
515
- 0% { transform: scale(1); box-shadow: 0 8px 20px -5px rgba(255, 193, 7, 0.3); }
516
- 50% { transform: scale(1.02); box-shadow: 0 12px 25px -5px rgba(255, 193, 7, 0.3); }
517
- 100% { transform: scale(1); box-shadow: 0 8px 20px -5px rgba(255, 193, 7, 0.3); }
518
  }
519
 
 
 
 
 
 
 
520
  #upgrade-premium-btn {
521
- display: none; /* Controlled by JS */
522
  width: 100%;
523
- margin-top: 1.5rem;
524
- padding: 1rem;
525
- font-family: var(--app-font);
526
  font-size: 1.1em;
527
  font-weight: 800;
528
  color: #212529;
@@ -530,19 +661,167 @@ body { font-family: var(--app-font); direction: rtl; }
530
  border: none;
531
  border-radius: 14px;
532
  cursor: pointer;
533
- transition: transform 0.2s ease, box-shadow 0.2s ease;
534
- box-shadow: 0 8px 20px -5px rgba(255, 193, 7, 0.3);
535
- animation: upgrade-btn-pulse 2.5s infinite;
536
- text-align: center;
537
  }
538
-
539
  #upgrade-premium-btn:hover {
540
- transform: translateY(-3px);
541
- box-shadow: 0 12px 25px -5px rgba(255, 193, 7, 0.4);
542
- animation-play-state: paused;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
  }
544
 
545
- /* --- Layout --- */
546
  #col-container {
547
  margin: 0 auto;
548
  max-width: 980px;
@@ -559,7 +838,7 @@ body { font-family: var(--app-font); direction: rtl; }
559
  font-size: 2.4em !important;
560
  text-align: center;
561
  color: #1a202c !important;
562
- margin-bottom: 5px;
563
  font-weight: 800;
564
  background: -webkit-linear-gradient(45deg, #2563eb, #1e40af);
565
  -webkit-background-clip: text;
@@ -570,11 +849,39 @@ body { font-family: var(--app-font); direction: rtl; }
570
  text-align: center;
571
  font-size: 1.15em;
572
  color: #4b5563 !important;
573
- margin-bottom: 10px;
 
574
  }
575
 
576
- /* --- Gradio Overrides --- */
577
- .primary-btn {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
578
  background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
579
  border: none !important;
580
  color: white !important;
@@ -583,43 +890,111 @@ body { font-family: var(--app-font); direction: rtl; }
583
  padding: 14px 28px !important;
584
  border-radius: 14px !important;
585
  box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3) !important;
 
586
  cursor: pointer !important;
587
  width: 100%;
 
 
 
 
 
 
 
 
 
 
588
  }
589
- .primary-btn:hover { transform: translateY(-2px); }
590
 
591
  #download-btn {
592
  background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
593
  box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3) !important;
594
  }
 
 
 
595
 
596
- textarea, input[type="text"] {
597
- font-family: var(--app-font) !important;
 
 
 
598
  border-radius: 12px !important;
599
- border: 2px solid #e2e8f0 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
600
  }
601
 
602
  footer { display: none !important; }
603
  .flagging { display: none !important; }
604
 
605
- /* Toast/Error hiding */
606
- .toast-body, .toast-wrap {
607
- display: none !important;
608
- opacity: 0 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
609
  }
610
  </style>
611
  """
612
 
613
  # ادغام CSS و JS
614
- combined_html = css_code + js_func
615
 
616
- with gr.Blocks(title="Alpha Image Editor") as demo:
 
 
617
  gr.HTML(combined_html)
618
 
619
- # متغیرهای مخفی برای ارتباط با JS
620
- fingerprint_hash = gr.Textbox(elem_id="fingerprint-input", visible=False)
621
- is_paid_user = gr.Textbox(elem_id="is-paid-user", value="false", visible=False)
622
-
623
  with gr.Column(elem_id="col-container"):
624
  gr.Markdown("# **ویرایشگر هوشمند آلفا**", elem_id="main-title")
625
  gr.Markdown(
@@ -627,13 +1002,23 @@ with gr.Blocks(title="Alpha Image Editor") as demo:
627
  elem_id="main-description"
628
  )
629
 
630
- # بخش نشان وضعیت کاربر
631
  gr.HTML("""
632
- <div style="text-align: center;">
633
- <div id="subscription-status-badge">در حال بررسی...</div>
 
 
 
 
 
 
634
  </div>
635
  """)
636
 
 
 
 
 
637
  with gr.Row(equal_height=True):
638
  with gr.Column():
639
  input_image = gr.Image(label="بارگذاری تصویر", type="pil", height=320)
@@ -646,17 +1031,10 @@ with gr.Blocks(title="Alpha Image Editor") as demo:
646
  lines=3
647
  )
648
 
649
- credit_status = gr.Textbox(label="اعتبار باقی‌مانده", value="...", interactive=False, elem_id="credit-display-text")
650
  status_box = gr.HTML(label="وضعیت")
651
 
 
652
  run_button = gr.Button("✨ شروع پردازش و ساخت تصویر", variant="primary", elem_classes="primary-btn", elem_id="run-btn")
653
-
654
- # دکمه ارتقا (توسط JS کنترل می‌شود)
655
- gr.HTML("""
656
- <button type="button" id="upgrade-premium-btn" onclick="window.triggerUpgrade()">
657
- ⭐️ ارتقا به نسخه کامل و نامحدود
658
- </button>
659
- """)
660
 
661
  with gr.Column():
662
  output_image = gr.Image(label="تصویر نهایی", interactive=False, format="png", height=380)
@@ -673,37 +1051,78 @@ with gr.Blocks(title="Alpha Image Editor") as demo:
673
  with gr.Accordion("تنظیمات پیشرفته", open=False, visible=True):
674
  aspect_ratio_selection = gr.Dropdown(
675
  label="ابعاد تصویر خروجی",
676
- choices=list(ASPECT_RATIOS_MAP.keys()),
677
  value="خودکار (پیش‌فرض)",
678
  interactive=True
679
  )
680
 
681
  with gr.Row(visible=False) as custom_dims_row:
682
- custom_width = gr.Slider(minimum=256, maximum=2048, step=8, value=1024, label="Width")
683
- custom_height = gr.Slider(minimum=256, maximum=2048, step=8, value=1024, label="Height")
 
 
 
 
 
 
684
 
685
  seed = gr.Slider(label="دانه تصادفی (Seed)", minimum=0, maximum=MAX_SEED, step=1, value=0)
686
  randomize_seed = gr.Checkbox(label="استفاده از Seed تصادفی", value=True)
687
- guidance_scale = gr.Slider(label="Guidance Scale", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
688
- steps = gr.Slider(label="Steps", minimum=1, maximum=50, step=1, value=4)
689
 
 
690
  def toggle_row(choice):
691
- return gr.update(visible=(choice == "شخصی‌سازی (Custom)"))
 
 
 
 
 
 
 
 
692
 
693
- aspect_ratio_selection.change(fn=toggle_row, inputs=aspect_ratio_selection, outputs=custom_dims_row)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
694
 
695
- # اتصال رویداد کلیک
696
  run_button.click(
697
  fn=infer,
698
  inputs=[
699
  input_image, prompt, lora_adapter, seed, randomize_seed,
700
  guidance_scale, steps, aspect_ratio_selection, custom_width, custom_height,
701
- fingerprint_hash, is_paid_user
702
  ],
703
- outputs=[output_image, seed, status_box, credit_status]
 
 
704
  )
705
 
706
- download_button.click(fn=None, inputs=[output_image], outputs=None, js="window.downloadImage")
 
 
 
 
 
707
 
708
  if __name__ == "__main__":
709
  demo.queue(max_size=30).launch(show_error=True)
 
5
  import torch
6
  import random
7
  import json
 
8
  from datetime import datetime
9
  from PIL import Image, ImageFilter
10
+ from typing import Iterable
11
  from gradio.themes import Soft
12
+ from gradio.themes.utils import colors, fonts, sizes
13
  from deep_translator import GoogleTranslator
14
  from transformers import pipeline
15
 
 
31
 
32
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
33
 
34
+ # --- مدیریت کاربران و اعتبار (در حافظه) ---
35
+ # ساختار: { 'fingerprint_hash': {'date': '2023-10-27', 'count': 0} }
36
+ USER_CREDITS_DB = {}
37
+ MAX_FREE_DAILY_CREDITS = 5
38
+ PREMIUM_PAGE_ID = '1149636' # شناسه صفحه پرمیوم برای تشخیص
39
+
40
  # --- بارگذاری مدل تشخیص محتوای نامناسب (NSFW) ---
41
  print("Loading Safety Checker...")
42
  safety_classifier = pipeline("image-classification", model="Falconsai/nsfw_image_detection", device=-1)
 
53
  print(f"Safety check error: {e}")
54
  return False
55
 
56
+ # --- لیست کلمات ممنوعه بسیار سختگیرانه (انگلیسی) ---
57
+ BANNED_WORDS = [
58
+ "nsfw", "nude", "naked", "sex", "porn", "erotic", "xxx", "18+", "uncensored",
59
+ "breast", "nipple", "genital", "vagina", "penis", "ass", "butt", "buttocks",
60
+ "lingerie", "bikini", "swimwear", "underwear", "panties", "bra", "topless",
61
+ "fetish", "exhibitionism", "hentai", "ecchi", "ahegao", "futanari", "yuri", "yaoi",
62
+ "sexual", "intercourse", "masturbation", "orgasm", "cum", "semen", "sperm",
63
+ "blowjob", "handjob", "tit", "boob", "dick", "cock", "pussy", "cunt", "whore",
64
+ "slut", "prostitute", "stripper", "bondage", "bdsm", "slave", "dominatrix",
65
+ "rape", "incest", "pedophile", "lolicon", "shotacon", "bestiality", "zoophilia",
66
+ "gore", "blood", "kill", "murder", "suicide", "torture", "violence", "horror",
67
+ "dead", "corpse", "mutilation", "flesh", "viscera", "guts", "decapitation",
68
+ "milf", "dilf", "threesome", "orgy", "gangbang", "creampie", "deepthroat",
69
+ "upskirt", "boudoir", "sensual", "seductive", "horny", "lust", "desire",
70
+ "groin", "pubic", "rectum", "anus", "clitoris", "scrotum", "testicle",
71
+ "penetration", "dildo", "vibrator", "toy", "kink", "submission", "domination"
72
+ ]
73
+
74
+ def check_text_safety(text):
75
+ if not text: return True
76
+ text_lower = text.lower()
77
+ # بررسی کلمات دقیق به همراه فاصله برای جلوگیری از اشتباه (مثلا ass در class)
78
+ # و همچنین بررسی کلمات بدون فاصله برای اطمینان
79
+ for word in BANNED_WORDS:
80
+ # حالت اول: کلمه مستقل
81
+ if f" {word} " in f" {text_lower} ":
82
+ return False
83
+ # حالت دوم: کلمات خاص که حتی چسبیده هم خطرناکند
84
+ if word in ["porn", "hentai", "xxx", "nude", "sex"] and word in text_lower:
85
+ return False
86
+ return True
87
+
88
+ # --- بارگذاری مدل اصلی ---
89
  from diffusers import FlowMatchEulerDiscreteScheduler
90
  from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
91
  from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
92
  from qwenimage.qwen_fa3_processor import QwenDoubleStreamAttnProcessorFA3
93
 
94
  dtype = torch.bfloat16
95
+ device = "cuda" if torch.cuda.is_available() else "cpu"
96
+
97
  print("Loading pipeline...")
98
  pipe = QwenImageEditPlusPipeline.from_pretrained(
99
  "Qwen/Qwen-Image-Edit-2509",
 
130
  "افزایش کیفیت (Upscale)": "upscale-image"
131
  }
132
 
133
+ ASPECT_RATIOS_LIST = [
134
+ "خودکار (پیش‌فرض)",
135
+ "۱:۱ (مربع - 1024x1024)",
136
+ "۱۶:۹ (افقی - 1344x768)",
137
+ "۹:۱۶ (عمودی - 768x1344)",
138
+ "شخصی‌سازی (Custom)"
139
+ ]
140
+
141
  ASPECT_RATIOS_MAP = {
142
  "خودکار (پیش‌فرض)": "Auto",
143
  "۱:۱ (مربع - 1024x1024)": (1024, 1024),
 
146
  "شخصی‌سازی (Custom)": "Custom"
147
  }
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  def translate_prompt(text):
150
  if not text:
151
  return ""
 
172
  new_height = (new_height // 8) * 8
173
  return new_width, new_height
174
 
175
+ def update_sliders_visibility(choice):
176
+ if choice == "شخصی‌سازی (Custom)":
177
+ return gr.update(visible=True), gr.update(visible=True)
178
+ else:
179
+ return gr.update(visible=False), gr.update(visible=False)
180
+
181
  def get_error_html(message):
182
  return f"""
183
  <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;">
 
194
  </div>
195
  """
196
 
197
+ # --- منطق بررسی اعتبار کاربر ---
198
+ def check_and_update_credits(fingerprint, is_premium_user):
199
+ # اگر کاربر پولی است، محدودیت ندارد
200
+ if is_premium_user:
201
+ return True, "Unlimited"
202
+
203
+ if not fingerprint:
204
+ # اگر اثر انگشت دریافت نشد (مثلاً مرورگر خاص)، موقتا اجازه می‌دهیم اما هشدار لاگ می‌کنیم
205
+ print("Warning: No fingerprint received.")
206
+ return True, "Unknown"
207
+
208
+ today_str = datetime.now().strftime("%Y-%m-%d")
209
+
210
+ # ایجاد رکورد برای کاربر جدید
211
+ if fingerprint not in USER_CREDITS_DB:
212
+ USER_CREDITS_DB[fingerprint] = {'date': today_str, 'count': 0}
213
+
214
+ user_data = USER_CREDITS_DB[fingerprint]
215
+
216
+ # ریست کردن اعتبار اگر روز تغییر کرده باشد
217
+ if user_data['date'] != today_str:
218
+ user_data['date'] = today_str
219
+ user_data['count'] = 0
220
+
221
+ # بررسی تعداد استفاده
222
+ if user_data['count'] >= MAX_FREE_DAILY_CREDITS:
223
+ return False, 0
224
+
225
+ # کسر اعتبار (افزایش شمارنده)
226
+ user_data['count'] += 1
227
+ remaining = MAX_FREE_DAILY_CREDITS - user_data['count']
228
+ return True, remaining
229
+
230
+ @spaces.GPU(duration=30)
231
  def infer(
232
  input_image,
233
  prompt,
 
239
  aspect_ratio_selection,
240
  custom_width,
241
  custom_height,
242
+ user_fingerprint, # ورودی مخفی
243
+ user_is_premium, # ورودی مخفی (رشته "true" یا "false")
 
244
  progress=gr.Progress(track_tqdm=True)
245
  ):
246
+ # تبدیل وضعیت پرمیوم به بولین
247
+ is_premium = str(user_is_premium).lower() == 'true'
 
 
 
 
 
 
248
 
249
  if input_image is None:
250
+ return None, seed, get_error_html("لطفاً ابتدا یک تصویر بارگذاری کنید.")
251
 
252
+ # 1. بررسی سهمیه کاربر (قبل از هر پردازش سنگین)
253
+ allowed, remaining_credits = check_and_update_credits(user_fingerprint, is_premium)
254
+ if not allowed:
255
+ msg = "اعتبار رایگان امروز شما (۵ تصویر) به پایان رسیده است. برای استفاده نامحدود حساب خود را ارتقا دهید."
256
+ # تریگر کردن نمایش دکمه ارتقا در فرانت‌اند با یک کلاس خاص یا پیام
257
+ return None, seed, get_error_html(msg + " <span class='quota-limit-reached' style='display:none'></span>")
258
+
259
+ # 2. بررسی تصویر ورودی برای NSFW
260
  if is_image_nsfw(input_image):
261
+ return None, seed, get_error_html("تصویر ورودی دارای محتوای نامناسب است و پردازش نمی‌شود.")
262
 
263
+ # 3. ترجمه و بررسی متن
264
  english_prompt = translate_prompt(prompt)
265
  if not check_text_safety(english_prompt):
266
+ return None, seed, get_error_html("متن درخواست شامل کلمات غیرمجاز، غیراخلاقی یا جنسی است.")
267
 
268
  adapter_internal_name = LORA_MAPPING.get(lora_adapter_persian)
269
  if adapter_internal_name:
 
274
 
275
  generator = torch.Generator(device=device).manual_seed(seed)
276
 
277
+ safety_negative = "nsfw, nude, naked, porn, sexual, xxx, breast, nipple, genital, vagina, penis, ass, lingerie, bikini, swimwear, underwear, fetish, topless, gore, violence, blood, child abuse"
278
  base_negative = "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
279
  final_negative_prompt = f"{safety_negative}, {base_negative}"
280
 
 
302
  true_cfg_scale=guidance_scale,
303
  ).images[0]
304
 
305
+ # 4. بررسی تصویر خروجی
306
  if is_image_nsfw(result):
307
+ return None, seed, get_error_html("تصویر تولید شده حاوی محتوای نامناسب بود و حذف شد.")
 
 
 
308
 
309
+ # پیام موفقیت با نمایش اعتبار باقی‌مانده برای کاربران رایگان
310
+ success_msg = "تصویر با موفقیت ویرایش شد."
311
+ if not is_premium:
312
+ success_msg += f"<br><small>اعتبار باقی‌مانده امروز: {remaining_credits} تصویر</small>"
313
+
314
+ return result, seed, get_success_html(success_msg)
315
 
316
  except Exception as e:
317
  error_str = str(e)
318
  if "quota" in error_str.lower() or "exceeded" in error_str.lower():
319
  raise e
320
+ return None, seed, get_error_html(f"خطا در پردازش: {error_str}")
321
+
322
+ @spaces.GPU(duration=30)
323
+ def infer_example(input_image, prompt, lora_adapter):
324
+ # برای مثال‌ها، کاربر فرضی رایگان با اعتبار بی‌نهایت در نظر گرفته می‌شود یا اثر انگشت تست
325
+ res, s, status = infer(input_image, prompt, lora_adapter, 0, True, 1.0, 4, "خودکار (پیش‌فرض)", 1024, 1024, "example_user", "true")
326
+ return res, s, status
327
+
328
+ # --- جاوااسکریپت برای دکمه دانلود ---
329
+ js_download_func = """
330
+ async (image) => {
331
+ if (!image) {
332
+ alert("لطفاً ابتدا تصویر را تولید کنید.");
333
+ return;
334
+ }
335
+ let fileUrl = image.url;
336
+ if (fileUrl && !fileUrl.startsWith('http')) {
337
+ fileUrl = window.location.origin + fileUrl;
338
+ } else if (!fileUrl && image.path) {
339
+ fileUrl = window.location.origin + "/file=" + image.path;
340
+ }
341
+ window.parent.postMessage({
342
+ type: 'DOWNLOAD_REQUEST',
343
+ url: fileUrl
344
+ }, '*');
345
+ }
346
+ """
347
 
348
+ # --- جاوااسکریپت سراسری (شناسایی کاربر، اثر انگشت، UI) ---
349
+ js_global_content = """
350
  <script>
351
+ // --- پیکربندی ---
352
+ const PREMIUM_PAGE_ID = '1149636';
353
  const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTc1NDIyNGVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
354
 
355
+ // --- تولید اثر انگشت مرورگر ---
356
+ function getBrowserFingerprint() {
357
+ const components = [navigator.userAgent, navigator.language, screen.width + 'x' + screen.height, new Date().getTimezoneOffset()];
 
 
 
 
 
358
  try {
359
  const canvas = document.createElement('canvas');
360
  const ctx = canvas.getContext('2d');
 
364
  ctx.fillStyle = "#f60";
365
  ctx.fillRect(125, 1, 62, 20);
366
  ctx.fillStyle = "#069";
367
+ ctx.fillText("a1b2c3d4e5f6g7h8i9j0_!@#$%^&*()", 2, 15);
368
  components.push(canvas.toDataURL());
369
  } catch (e) {
370
  components.push("canvas-error");
371
  }
372
  const fingerprintString = components.join('~~~');
 
 
373
  let hash = 0;
374
  for (let i = 0; i < fingerprintString.length; i++) {
375
  const char = fingerprintString.charCodeAt(i);
 
379
  return 'fp_' + Math.abs(hash).toString(16);
380
  }
381
 
382
+ // --- مدیریت وضعیت اشتراک ---
383
+ let userFingerprint = getBrowserFingerprint();
384
+ let isUserPremium = false;
385
+
386
  function isUserPaid(userObject) {
387
+ return userObject && userObject.isLogin && userObject.accessible_pages && (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) || userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID)));
 
 
388
  }
389
 
390
+ function updateUIForSubscription(status) {
391
+ const badge = document.getElementById('user-badge');
392
+ const descText = document.getElementById('user-desc-text');
 
393
 
394
+ if (status === 'paid') {
395
+ isUserPremium = true;
 
 
 
 
 
396
  if(badge) {
397
+ badge.textContent = 'نسخه نامحدود 💎';
398
+ badge.className = 'status-badge paid-badge';
 
399
  }
400
+ if(descText) descText.textContent = 'برای کاربران پولی: استفاده نامحدود';
401
+ document.getElementById('upgrade-btn-container').style.display = 'none';
 
 
 
 
402
  } else {
403
+ isUserPremium = false;
404
  if(badge) {
405
  badge.textContent = 'نسخه رایگان';
406
+ badge.className = 'status-badge free-badge';
 
407
  }
408
+ if(descText) descText.textContent = 'برای کاربران رایگان: ۵ تصویر در روز';
409
  }
410
+
411
+ // آپدیت کردن اینپوت‌های مخفی گرادیو
412
+ updateGradioHiddenInputs();
413
  }
414
 
415
+ function updateGradioHiddenInputs() {
416
+ // پیدا کردن تکست‌باکس‌های مخفی و مقداردهی
417
+ // ما از کلاس‌های خاص استفاده می‌کنیم که در پایتون تعریف شده‌اند
418
+ const fpInput = document.querySelector('.hidden-fingerprint textarea');
419
+ const premiumInput = document.querySelector('.hidden-premium textarea');
420
 
421
+ if (fpInput) {
422
+ fpInput.value = userFingerprint;
423
+ fpInput.dispatchEvent(new Event('input', { bubbles: true }));
424
+ }
425
+ if (premiumInput) {
426
+ premiumInput.value = isUserPremium ? "true" : "false";
427
+ premiumInput.dispatchEvent(new Event('input', { bubbles: true }));
428
+ }
429
+ }
430
+
431
+ // گوش دادن به پیام از اپلیکیشن مادر
432
+ window.addEventListener('message', (event) => {
433
+ if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
434
+ if (event.data.error || !event.data.payload) {
435
+ updateUIForSubscription('free');
436
+ return;
437
+ }
438
+ try {
439
+ const userObject = JSON.parse(event.data.payload);
440
+ const status = isUserPaid(userObject) ? 'paid' : 'free';
441
+ updateUIForSubscription(status);
442
+ } catch (e) {
443
+ updateUIForSubscription('free');
444
+ }
445
+ }
446
+ });
447
+
448
+ // درخواست وضعیت کاربر هنگام لود
449
+ document.addEventListener('DOMContentLoaded', () => {
450
  // 1. Force Light Mode
451
  const forceLight = () => {
452
  const body = document.querySelector('body');
 
460
  forceLight();
461
  setInterval(forceLight, 1000);
462
 
463
+ // درخواست وضعیت از پرنت
464
+ parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
 
 
 
 
 
 
 
 
465
 
466
+ // اگر پاسخی نیامد پیش‌فرض رایگان
467
+ setTimeout(() => {
468
+ if (!document.getElementById('user-badge').textContent) {
469
+ updateUIForSubscription('free');
 
 
 
 
 
 
 
470
  }
471
+ }, 2000);
472
+
473
+ // 2. RETRY FUNCTION
474
+ window.retryGeneration = function() {
475
+ const modal = document.getElementById('custom-quota-modal');
476
+ if (modal) modal.remove();
477
+
478
+ const runBtn = document.getElementById('run-btn');
479
+ if(runBtn) runBtn.click();
480
  };
481
 
482
+ // Close function
483
+ window.closeErrorModal = function() {
484
+ const modal = document.getElementById('custom-quota-modal');
485
+ if (modal) modal.remove();
486
+ };
487
+
488
+ // هدایت به صفحه خرید
489
+ window.navigateToPremium = function() {
490
+ parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*');
491
+ };
492
+
493
+ // 3. SHOW MODAL FUNCTION (IP LIMIT)
 
 
 
 
494
  const showQuotaModal = () => {
495
  if (document.getElementById('custom-quota-modal')) return;
496
+
497
+ const modalHtml = `
498
+ <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;">
499
+ <div class="ip-reset-guide-container">
500
+ <div class="guide-header">
501
+ <svg class="guide-header-icon" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
502
+ <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>
503
+ <circle cx="50" cy="50" r="45" fill="url(#grad1)" opacity="0.1"></circle>
504
+ <circle cx="50" cy="50" r="35" fill="none" stroke="url(#grad1)" stroke-width="2" opacity="0.3"></circle>
505
+ <path d="M35 50 L45 60 L65 40" stroke="url(#grad1)" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"></path>
506
+ <circle cx="65" cy="35" r="8" fill="#fee140"></circle>
507
+ <path d="M62 35 L68 35 M65 32 L65 38" stroke="white" stroke-width="2" stroke-linecap="round"></path>
508
+ </svg>
509
+ <div>
510
+ <h2>یک قدم تا ساخت تصاویر جدید</h2>
511
+ <p>نیازمند تغییر نقطه دستیابی</p>
512
+ </div>
513
+ </div>
514
+
515
+ <div class="guide-content">
516
+ <div class="info-card">
517
+ <div class="info-card-header">
518
+ <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>
519
+ <span class="info-card-title">راه حل سریع</span>
520
+ </div>
521
+ <p>طبق ویدیو آموزشی پایین بین نقطه دستیابی جابجا شوید تلاش مجدد بزنید تا تصاویر مجدداً تولید بشه.</p>
522
+ </div>
523
+
524
+ <div class="summary-section">
525
+ <div class="summary-header">
526
+ <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>
527
+ <span class="summary-title">خلاصه راهنما</span>
528
+ </div>
529
+ <div class="summary-text">هربار که این صفحه را مشاهده کردید: از اینترنت سیم‌کارت استفاده کنید، VPN را خاموش کرده و طبق ویدیو آموزشی پایین نقطه دستیابی رو تغییر دهید. «تلاش مجدد» کلیک کنید. با این روش ساده می‌توانید به صورت نامحدود تصویر بسازید! ☘️</div>
530
+ </div>
531
+
532
+ <div class="video-button-container">
533
+ <button onclick="parent.postMessage({ type: 'NAVIGATE_TO_URL', url: '#/nav/online/news/getSingle/1149635/eyJpdiI6IjhHVGhPQWJwb3E0cjRXbnFWTW5BaUE9PSIsInZhbHVlIjoiS1V0dTdvT21wbXAwSXZaK1RCTG1pVXZqdlFJa1hXV1RKa2FLem9zU3pXMjd5MmlVOGc2YWY0NVdNR3h3Smp1aSIsIm1hYyI6IjY1NTA5ZDYzMjAzMTJhMGQyMWQ4NjA4ZDgyNGZjZDVlY2MyNjdiMjA2NWYzOWRjY2M4ZmVjYWRlMWNlMWQ3ODEiLCJ0YWciOiIifQ==/21135210' }, '*')" class="elegant-video-button">
534
+ <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>
535
+ <span>دیدن ویدیو آموزشی استفاده نامحدود</span>
536
+ </button>
537
+ </div>
538
+ </div>
539
+
540
+ <div class="guide-actions">
541
+ <button class="action-button back-button" onclick="window.closeErrorModal()">
542
+ <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>
543
+ <span>بازگشت</span>
544
+ </button>
545
+ <button class="action-button retry-button" onclick="window.retryGeneration()">
546
+ <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>
547
+ <span>تلاش مجدد</span>
548
+ </button>
549
+ </div>
550
+ </div>
551
+ </div>
552
+ `;
553
+
554
+ document.body.insertAdjacentHTML('beforeend', modalHtml);
555
+
556
+ // Auto close after 10 seconds
557
+ setTimeout(() => {
558
+ window.closeErrorModal();
559
+ }, 10000);
560
  };
561
+
562
+ // 4. SCANNER (Monitor for errors)
563
+ setInterval(() => {
564
+ // الف) چک کردن خطای Quota
565
+ const potentialErrors = document.querySelectorAll('.toast-body, .error, .toast-wrap, .eta-bar, div[class*="error"]');
566
+ potentialErrors.forEach(el => {
567
+ const text = el.innerText || "";
568
+ if (text.toLowerCase().includes('quota') || text.toLowerCase().includes('exceeded')) {
569
+ showQuotaModal();
570
+ el.style.display = 'none';
571
+ el.style.opacity = '0';
572
+ const parentWrap = el.closest('.toast-wrap');
573
+ if(parentWrap) parentWrap.style.display = 'none';
574
+ }
575
+ });
576
+
577
+ // ب) چک کردن اتمام اعتبار روزانه (از طریق کلاس HTML که در پایتون برگرداندیم)
578
+ const limitReached = document.querySelector('.quota-limit-reached');
579
+ if (limitReached) {
580
+ document.getElementById('upgrade-btn-container').style.display = 'block';
581
+ limitReached.remove(); // جلوگیری از اجرای مکرر
582
+ }
583
+
584
+ }, 200);
585
  });
586
+
587
+ // تابع کمکی برای اطمینان از ارسال مقادیر قبل از کلیک
588
+ function prepareSubmission() {
589
+ updateGradioHiddenInputs();
590
+ return [];
591
+ }
592
  </script>
593
  """
594
 
595
+ # --- CSS Updated ---
596
  css_code = """
597
  <style>
598
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;900&display=swap');
599
 
600
  :root, .dark, body, .gradio-container {
601
  --body-background-fill: #f5f7fa !important;
602
  --body-text-color: #1f2937 !important;
603
+ --background-fill-primary: #ffffff !important;
604
+ --background-fill-secondary: #f3f4f6 !important;
605
+ --border-color-primary: #e5e7eb !important;
606
+ --block-background-fill: #ffffff !important;
607
+ --block-label-text-color: #374151 !important;
608
+ --block-title-text-color: #111827 !important;
609
+ --input-background-fill: #ffffff !important;
610
  color-scheme: light !important;
611
  }
612
 
613
+ /* User Status Badge */
614
+ #user-status-container {
615
+ display: flex;
616
+ flex-direction: column;
617
+ align-items: center;
618
+ justify-content: center;
619
+ margin-bottom: 20px;
620
+ animation: fadeIn 0.8s ease-out;
621
+ }
622
+ .status-badge {
623
  padding: 6px 16px;
624
  border-radius: 20px;
625
  font-size: 0.9em;
626
  font-weight: 700;
627
+ margin-bottom: 8px;
 
628
  letter-spacing: 0.5px;
629
  text-shadow: 0 1px 2px rgba(0,0,0,0.1);
 
630
  }
 
631
  .free-badge {
632
  background: linear-gradient(45deg, #6c757d, #495057);
633
  color: white;
634
  box-shadow: 0 4px 10px rgba(108, 117, 125, 0.3);
635
  }
 
636
  .paid-badge {
637
+ background: linear-gradient(45deg, #FFD700, #FFA500);
638
  color: #333;
639
+ box-shadow: 0 4px 10px rgba(255, 193, 7, 0.4);
640
  }
641
+ #user-desc-text {
642
+ font-size: 0.9em;
643
+ color: #4b5563;
 
 
644
  }
645
 
646
+ /* Upgrade Button */
647
+ @keyframes pulse-gold {
648
+ 0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); }
649
+ 70% { transform: scale(1.02); box-shadow: 0 0 0 10px rgba(255, 193, 7, 0); }
650
+ 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); }
651
+ }
652
  #upgrade-premium-btn {
 
653
  width: 100%;
654
+ margin-top: 10px;
655
+ padding: 12px;
656
+ font-family: 'Vazirmatn', sans-serif;
657
  font-size: 1.1em;
658
  font-weight: 800;
659
  color: #212529;
 
661
  border: none;
662
  border-radius: 14px;
663
  cursor: pointer;
664
+ box-shadow: 0 8px 20px -5px rgba(255, 193, 7, 0.5);
665
+ animation: pulse-gold 2s infinite;
666
+ display: block;
 
667
  }
 
668
  #upgrade-premium-btn:hover {
669
+ transform: translateY(-2px);
670
+ box-shadow: 0 12px 25px -5px rgba(255, 193, 7, 0.6);
671
+ }
672
+
673
+ /* --- IP Reset Guide CSS --- */
674
+ :root {
675
+ --guide-bg: rgba(255, 255, 255, 0.98);
676
+ --guide-border: rgba(102, 126, 234, 0.2);
677
+ --guide-text-title: #2d3748;
678
+ --guide-text-body: #4a5568;
679
+ --guide-accent: #667eea;
680
+ --primary-gradient-guide: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
681
+ --success-gradient-guide: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
682
+ --radius-md-guide: 12px;
683
+ --radius-lg-guide: 16px;
684
+ --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
685
+ --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
686
+ --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
687
+ }
688
+
689
+ @keyframes float {
690
+ 0%, 100% { transform: translateY(0px); }
691
+ 50% { transform: translateY(-10px); }
692
+ }
693
+ @keyframes slideInUp {
694
+ from { opacity: 0; transform: translateY(30px); }
695
+ to { opacity: 1; transform: translateY(0); }
696
+ }
697
+
698
+ .ip-reset-guide-container {
699
+ text-align: right;
700
+ direction: rtl;
701
+ background: var(--guide-bg);
702
+ backdrop-filter: blur(10px);
703
+ padding: 20px; /* Increased Padding */
704
+ border-radius: var(--radius-lg-guide);
705
+ box-shadow: var(--shadow-xl);
706
+ border: 1px solid var(--guide-border);
707
+ animation: slideInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1) both;
708
+ width: 90%;
709
+ max-width: 420px; /* Increased width slightly */
710
+ max-height: 90vh; /* Prevent overflow on small screens */
711
+ overflow-y: auto; /* Enable scroll if needed */
712
+ position: relative;
713
+ box-sizing: border-box;
714
+ font-family: 'Vazirmatn', sans-serif !important;
715
+ }
716
+ .ip-reset-guide-container::before {
717
+ content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; background: var(--primary-gradient-guide);
718
+ }
719
+ .guide-header { display: flex; align-items: center; margin-bottom: 15px; }
720
+ .guide-header-icon { width: 45px; height: 45px; margin-left: 15px; animation: float 3s ease-in-out infinite; flex-shrink: 0; }
721
+ .guide-header h2 { font-size: 1.2rem; color: var(--guide-text-title); font-weight: 700; margin: 0; }
722
+ .guide-header p { color: var(--guide-text-body); font-size: 0.8rem; margin-top: 3px; margin-bottom: 0; }
723
+
724
+ .guide-content { font-size: 0.9rem; color: var(--guide-text-body); line-height: 1.6; }
725
+
726
+ .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: 12px; margin: 12px 0; position: relative; overflow: hidden; }
727
+ .info-card p { font-size: 0.85rem; line-height: 1.6; margin: 0; }
728
+ .info-card::before { content: ''; position: absolute; top: 0; right: 0; width: 3px; height: 100%; background: var(--primary-gradient-guide); }
729
+ .info-card-header { display: flex; align-items: center; margin-bottom: 8px; }
730
+ .info-card-icon { width: 18px; height: 18px; margin-left: 8px; }
731
+ .info-card-title { font-weight: 600; color: var(--guide-text-title); font-size: 0.95rem; }
732
+
733
+ .summary-section { margin-top: 12px; padding: 12px; 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; }
734
+ .summary-section::before { content: ''; position: absolute; top: 0; right: 0; width: 3px; height: 100%; background: var(--success-gradient-guide); }
735
+ .summary-header { display: flex; align-items: center; margin-bottom: 8px; }
736
+ .summary-icon { width: 18px; height: 18px; margin-left: 8px; }
737
+ .summary-title { font-weight: 600; color: #2f5a33; font-size: 0.95rem; }
738
+ .summary-text { color: #2f5a33; font-size: 0.85rem; line-height: 1.6; }
739
+
740
+ /* Tutorial Button */
741
+ .video-button-container { text-align: center; margin: 20px 0 15px 0; width: 100%; }
742
+ .elegant-video-button {
743
+ display: inline-flex !important;
744
+ align-items: center;
745
+ justify-content: center;
746
+ padding: 10px 20px !important;
747
+ background-color: #fff !important;
748
+ color: var(--guide-accent) !important;
749
+ border: 1px solid #e2e8f0 !important;
750
+ text-decoration: none;
751
+ border-radius: 50px !important;
752
+ font-weight: 600 !important;
753
+ font-size: 0.9rem !important;
754
+ cursor: pointer !important;
755
+ font-family: inherit;
756
+ transition: all 0.3s ease !important;
757
+ box-shadow: 0 2px 10px rgba(0,0,0,0.05) !important;
758
+ width: auto !important;
759
+ }
760
+ .elegant-video-button:hover {
761
+ background: var(--primary-gradient-guide) !important;
762
+ color: white !important;
763
+ border-color: transparent !important;
764
+ transform: translateY(-2px);
765
+ box-shadow: 0 6px 16px rgba(102, 126, 234, 0.3) !important;
766
+ }
767
+ .elegant-video-button-icon { width: 18px; height: 18px; margin-left: 8px; fill: currentColor; }
768
+
769
+ /* Action Buttons */
770
+ .guide-actions {
771
+ display: flex !important;
772
+ gap: 12px !important;
773
+ margin-top: 20px;
774
+ padding-top: 20px;
775
+ border-top: 1px solid #e2e8f0;
776
+ width: 100% !important;
777
+ }
778
+ .action-button {
779
+ padding: 12px 15px !important;
780
+ border: none !important;
781
+ border-radius: 12px !important;
782
+ font-size: 0.95rem !important;
783
+ font-weight: 600 !important;
784
+ cursor: pointer !important;
785
+ flex: 1 !important;
786
+ transition: all 0.3s ease !important;
787
+ display: flex !important;
788
+ align-items: center;
789
+ justify-content: center;
790
+ font-family: inherit;
791
+ height: 48px !important;
792
+ }
793
+ .action-button-icon { width: 20px; height: 20px; margin-right: 0; margin-left: 8px; }
794
+
795
+ .back-button {
796
+ background: white !important;
797
+ color: var(--guide-text-body) !important;
798
+ border: 2px solid #e2e8f0 !important;
799
+ }
800
+ .back-button:hover {
801
+ background: #f7fafc !important;
802
+ border-color: var(--guide-accent) !important;
803
+ transform: translateY(-2px);
804
+ box-shadow: var(--shadow-md) !important;
805
+ }
806
+
807
+ .retry-button {
808
+ background: var(--primary-gradient-guide) !important;
809
+ color: white !important;
810
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3) !important;
811
+ }
812
+ .retry-button:hover {
813
+ transform: translateY(-2px);
814
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4) !important;
815
+ }
816
+
817
+ /* --- Main App CSS --- */
818
+ body {
819
+ font-family: 'Vazirmatn', sans-serif !important;
820
+ background-color: #f5f7fa !important;
821
+ margin: 0;
822
+ padding: 10px;
823
  }
824
 
 
825
  #col-container {
826
  margin: 0 auto;
827
  max-width: 980px;
 
838
  font-size: 2.4em !important;
839
  text-align: center;
840
  color: #1a202c !important;
841
+ margin-bottom: 15px;
842
  font-weight: 800;
843
  background: -webkit-linear-gradient(45deg, #2563eb, #1e40af);
844
  -webkit-background-clip: text;
 
849
  text-align: center;
850
  font-size: 1.15em;
851
  color: #4b5563 !important;
852
+ margin-bottom: 20px;
853
+ line-height: 1.6;
854
  }
855
 
856
+ .gr-input-label, span.label-wrap, label span {
857
+ font-weight: 700 !important;
858
+ color: #374151 !important;
859
+ font-size: 0.95em !important;
860
+ margin-bottom: 8px !important;
861
+ }
862
+
863
+ textarea, input[type="text"] {
864
+ border: 2px solid #e2e8f0 !important;
865
+ border-radius: 12px !important;
866
+ background-color: #ffffff !important;
867
+ color: #111827 !important;
868
+ padding: 12px !important;
869
+ transition: all 0.3s ease;
870
+ font-family: 'Vazirmatn', sans-serif !important;
871
+ }
872
+
873
+ textarea:focus, input[type="text"]:focus {
874
+ border-color: #3b82f6 !important;
875
+ box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1) !important;
876
+ outline: none;
877
+ }
878
+
879
+ .gr-dropdown {
880
+ background: #ffffff !important;
881
+ border-radius: 12px !important;
882
+ }
883
+
884
+ .primary-btn, button.primary {
885
  background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
886
  border: none !important;
887
  color: white !important;
 
890
  padding: 14px 28px !important;
891
  border-radius: 14px !important;
892
  box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3) !important;
893
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
894
  cursor: pointer !important;
895
  width: 100%;
896
+ margin-top: 15px;
897
+ }
898
+
899
+ .primary-btn:hover, button.primary:hover {
900
+ transform: translateY(-2px);
901
+ box-shadow: 0 8px 25px rgba(16, 185, 129, 0.45) !important;
902
+ }
903
+
904
+ .primary-btn:active, button.primary:active {
905
+ transform: translateY(1px);
906
  }
 
907
 
908
  #download-btn {
909
  background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
910
  box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3) !important;
911
  }
912
+ #download-btn:hover {
913
+ box-shadow: 0 8px 25px rgba(59, 130, 246, 0.45) !important;
914
+ }
915
 
916
+ .gradio-container .prose table,
917
+ .gradio-container table {
918
+ background-color: #ffffff !important;
919
+ color: #111827 !important;
920
+ border: 1px solid #e5e7eb !important;
921
  border-radius: 12px !important;
922
+ overflow: hidden !important;
923
+ width: 100% !important;
924
+ margin-top: 20px !important;
925
+ }
926
+
927
+ .gradio-container thead th {
928
+ background-color: #f3f4f6 !important;
929
+ color: #374151 !important;
930
+ font-weight: 700 !important;
931
+ border-bottom: 2px solid #e5e7eb !important;
932
+ padding: 12px !important;
933
+ text-align: right !important;
934
+ }
935
+
936
+ .gradio-container tbody tr {
937
+ background-color: #ffffff !important;
938
+ border-bottom: 1px solid #f3f4f6 !important;
939
+ }
940
+
941
+ .gradio-container tbody tr:hover {
942
+ background-color: #f9fafb !important;
943
+ }
944
+
945
+ .gradio-container tbody td {
946
+ background-color: #ffffff !important;
947
+ color: #374151 !important;
948
+ padding: 10px !important;
949
+ }
950
+
951
+ .gradio-container tbody td span,
952
+ .gradio-container tbody td p {
953
+ color: #374151 !important;
954
  }
955
 
956
  footer { display: none !important; }
957
  .flagging { display: none !important; }
958
 
959
+ /* Force toast transparency */
960
+ .toast-body {
961
+ direction: rtl !important;
962
+ text-align: right !important;
963
+ background: transparent !important;
964
+ box-shadow: none !important;
965
+ border: none !important;
966
+ padding: 0 !important;
967
+ max-width: 100% !important;
968
+ width: auto !important;
969
+ }
970
+ .toast-wrap {
971
+ background: transparent !important;
972
+ border: none !important;
973
+ box-shadow: none !important;
974
+ }
975
+
976
+ /* Hidden inputs logic */
977
+ .hidden-fingerprint, .hidden-premium {
978
+ display: none !important;
979
+ }
980
+
981
+ @media (prefers-color-scheme: dark) {
982
+ body, .gradio-container, .prose, table, tr, td, th {
983
+ background-color: #ffffff !important;
984
+ color: #333333 !important;
985
+ }
986
  }
987
  </style>
988
  """
989
 
990
  # ادغام CSS و JS
991
+ combined_html = css_code + js_global_content
992
 
993
+ # استفاده از gr.Blocks
994
+ with gr.Blocks() as demo:
995
+ # تزریق کدها به عنوان HTML
996
  gr.HTML(combined_html)
997
 
 
 
 
 
998
  with gr.Column(elem_id="col-container"):
999
  gr.Markdown("# **ویرایشگر هوشمند آلفا**", elem_id="main-title")
1000
  gr.Markdown(
 
1002
  elem_id="main-description"
1003
  )
1004
 
1005
+ # --- بج وضعیت کاربر و دکمه ارتقا ---
1006
  gr.HTML("""
1007
+ <div id="user-status-container">
1008
+ <span id="user-badge" class="status-badge"></span>
1009
+ <span id="user-desc-text"></span>
1010
+ <div id="upgrade-btn-container" style="display:none; width: 100%;">
1011
+ <button id="upgrade-premium-btn" onclick="navigateToPremium()">
1012
+ ⭐️ ارتقا به نسخه کامل و نامحدود
1013
+ </button>
1014
+ </div>
1015
  </div>
1016
  """)
1017
 
1018
+ # --- ورودی‌های مخفی برای انتقال اطلاعات از JS به Python ---
1019
+ hidden_fingerprint = gr.Textbox(elem_classes="hidden-fingerprint", visible=False)
1020
+ hidden_premium = gr.Textbox(elem_classes="hidden-premium", visible=False)
1021
+
1022
  with gr.Row(equal_height=True):
1023
  with gr.Column():
1024
  input_image = gr.Image(label="بارگذاری تصویر", type="pil", height=320)
 
1031
  lines=3
1032
  )
1033
 
 
1034
  status_box = gr.HTML(label="وضعیت")
1035
 
1036
+ # IMPORTANT: Added elem_id="run-btn" here for JS targeting
1037
  run_button = gr.Button("✨ شروع پردازش و ساخت تصویر", variant="primary", elem_classes="primary-btn", elem_id="run-btn")
 
 
 
 
 
 
 
1038
 
1039
  with gr.Column():
1040
  output_image = gr.Image(label="تصویر نهایی", interactive=False, format="png", height=380)
 
1051
  with gr.Accordion("تنظیمات پیشرفته", open=False, visible=True):
1052
  aspect_ratio_selection = gr.Dropdown(
1053
  label="ابعاد تصویر خروجی",
1054
+ choices=ASPECT_RATIOS_LIST,
1055
  value="خودکار (پیش‌فرض)",
1056
  interactive=True
1057
  )
1058
 
1059
  with gr.Row(visible=False) as custom_dims_row:
1060
+ custom_width = gr.Slider(
1061
+ label="عرض دلخواه (Width)",
1062
+ minimum=256, maximum=2048, step=8, value=1024
1063
+ )
1064
+ custom_height = gr.Slider(
1065
+ label="ارتفاع دلخواه (Height)",
1066
+ minimum=256, maximum=2048, step=8, value=1024
1067
+ )
1068
 
1069
  seed = gr.Slider(label="دانه تصادفی (Seed)", minimum=0, maximum=MAX_SEED, step=1, value=0)
1070
  randomize_seed = gr.Checkbox(label="استفاده از Seed تصادفی", value=True)
1071
+ guidance_scale = gr.Slider(label="میزان وفاداری به متن (Guidance Scale)", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
1072
+ steps = gr.Slider(label="تعداد مراحل پردازش (Steps)", minimum=1, maximum=50, step=1, value=4)
1073
 
1074
+ # اصلاح تابع نمایش ردیف اسلایدرها
1075
  def toggle_row(choice):
1076
+ if choice == "شخصی‌سازی (Custom)":
1077
+ return gr.update(visible=True)
1078
+ return gr.update(visible=False)
1079
+
1080
+ aspect_ratio_selection.change(
1081
+ fn=toggle_row,
1082
+ inputs=aspect_ratio_selection,
1083
+ outputs=custom_dims_row
1084
+ )
1085
 
1086
+ gr.Examples(
1087
+ examples=[
1088
+ ["examples/1.jpg", "تبدیل به انیمه کن.", "تبدیل عکس به انیمه"],
1089
+ ["examples/5.jpg", "سایه‌ها را حذف کن و نورپردازی نرم به تصویر بده.", "اصلاح نور و سایه"],
1090
+ ["examples/4.jpg", "از فیلتر ساعت طلایی با پخش نور ملایم استفاده کن.", "نورپردازی مجدد (Relight)"],
1091
+ ["examples/2.jpeg", "دوربین را ۴۵ درجه به سمت چپ بچرخان.", "تغییر زاویه دید"],
1092
+ ["examples/7.jpg", "منبع نور را از سمت راست عقب قرار بده.", "نورپردازی چند زاویه‌ای"],
1093
+ ["examples/10.jpeg", "کیفیت تصویر را افزایش بده (Upscale).", "افزایش کیفیت (Upscale)"],
1094
+ ["examples/7.jpg", "منبع نور را از پایین بتابان.", "نورپردازی چند زاویه‌ای"],
1095
+ ["examples/2.jpeg", "زاویه دوربین را به نمای بالا گوشه راست تغییر بده.", "تغییر زاویه دید"],
1096
+ ["examples/9.jpg", "دوربین کمی به جلو حرکت می‌کند در حالی که نور خورشید از میان ابرها می‌تابد و درخششی نرم اطراف شبح شخصیت در مه ایجاد می‌کند. سبک سینمایی واقعی.", "صحنه بعدی (سینمایی)"],
1097
+ ["examples/8.jpg", "جزئیات پوست سوژه را برجسته‌تر و طبیعی‌تر کن.", "روتوش پوست"],
1098
+ ["examples/6.jpg", "دوربین را به نمای پایین به بالا تغییر بده.", "تغییر زاویه دید"],
1099
+ ],
1100
+ inputs=[input_image, prompt, lora_adapter],
1101
+ outputs=[output_image, seed, status_box],
1102
+ fn=infer_example,
1103
+ cache_examples=False,
1104
+ label="نمونه‌ها (برای تست کلیک کنید)"
1105
+ )
1106
 
1107
+ # اجرای پروسه با انتقال اطلاعات کاربر
1108
  run_button.click(
1109
  fn=infer,
1110
  inputs=[
1111
  input_image, prompt, lora_adapter, seed, randomize_seed,
1112
  guidance_scale, steps, aspect_ratio_selection, custom_width, custom_height,
1113
+ hidden_fingerprint, hidden_premium # ورودی‌های جدید
1114
  ],
1115
+ outputs=[output_image, seed, status_box],
1116
+ api_name="predict",
1117
+ js="prepareSubmission" # فراخوانی تابع JS قبل از ارسال به پایتون
1118
  )
1119
 
1120
+ download_button.click(
1121
+ fn=None,
1122
+ inputs=[output_image],
1123
+ outputs=None,
1124
+ js=js_download_func
1125
+ )
1126
 
1127
  if __name__ == "__main__":
1128
  demo.queue(max_size=30).launch(show_error=True)