Opera8 commited on
Commit
9481e69
·
verified ·
1 Parent(s): ca97deb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +205 -425
app.py CHANGED
@@ -33,8 +33,8 @@ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
33
 
34
  # --- تنظیمات سیستم اعتبار ---
35
  USAGE_LIMIT = 5
36
- PREMIUM_PAGE_ID = '1149636' # شناسه محصول برای تشخیص کاربر پولی
37
- user_quotas = {} # حافظه موقت برای ذخیره تعداد درخواست‌های روزانه
38
 
39
  # --- بارگذاری مدل تشخیص محتوای نامناسب (NSFW) ---
40
  print("Loading Safety Checker...")
@@ -175,34 +175,38 @@ def get_success_html(message):
175
  </div>
176
  """
177
 
178
- # --- تابع بررسی اعتبار کاربر ---
179
- def check_user_quota(fingerprint, status):
180
- # اگر کاربر پولی است، محدودیت ندارد
181
- if status == 'paid':
182
- return True, ""
 
 
 
 
 
183
 
184
- # اگر فینگرپرینت وجود ندارد (خطای کلاینت)، سخت‌گیری نمی‌کنیم اما لاگ می‌کنیم
185
  if not fingerprint:
186
- return True, ""
 
 
 
 
 
 
 
 
 
 
187
 
188
- today = date.today().isoformat()
189
-
190
- # اگر کاربر جدید است یا روز تغییر کرده، رکورد جدید بساز
191
- if fingerprint not in user_quotas or user_quotas[fingerprint]['date'] != today:
192
- user_quotas[fingerprint] = {'date': today, 'count': 0}
193
-
194
  # بررسی محدودیت
195
- if user_quotas[fingerprint]['count'] >= USAGE_LIMIT:
196
- return False, "اعتبار رایگان روزانه شما (۵ تصویر) تمام شده است. لطفاً برای استفاده نامحدود ارتقا دهید یا فردا تلاش کنید."
197
 
198
- return True, ""
 
 
199
 
200
- # --- تابع افزایش شمارنده اعتبار ---
201
- def increment_user_quota(fingerprint, status):
202
- if status == 'paid' or not fingerprint:
203
- return
204
- if fingerprint in user_quotas:
205
- user_quotas[fingerprint]['count'] += 1
206
 
207
  @spaces.GPU(duration=30)
208
  def infer(
@@ -216,13 +220,16 @@ def infer(
216
  aspect_ratio_selection,
217
  custom_width,
218
  custom_height,
219
- fingerprint, # ورودی جدید مخفی
220
- user_status, # ورودی جدید مخفی
221
  progress=gr.Progress(track_tqdm=True)
222
  ):
223
- # 1. بررسی اعتبار قبل از هر پردازش
224
- is_allowed, msg = check_user_quota(fingerprint, user_status)
 
225
  if not is_allowed:
 
 
226
  return None, seed, get_error_html(msg)
227
 
228
  if input_image is None:
@@ -275,10 +282,11 @@ def infer(
275
  if is_image_nsfw(result):
276
  return None, seed, get_error_html("تصویر تولید شده حاوی محتوای نامناسب بود و حذف شد.")
277
 
278
- # اگر موفقیت‌آمیز بود، اعتبار را کم کن
279
- increment_user_quota(fingerprint, user_status)
280
-
281
- return result, seed, get_success_html("تصویر با موفقیت ویرایش شد.")
 
282
 
283
  except Exception as e:
284
  error_str = str(e)
@@ -288,11 +296,12 @@ def infer(
288
 
289
  @spaces.GPU(duration=30)
290
  def infer_example(input_image, prompt, lora_adapter):
291
- # برای مثال‌ها، اعتبار را چک نمی‌کنیم یا فینگرپرینت خالی می‌فرستیم
292
- res, s, status = infer(input_image, prompt, lora_adapter, 0, True, 1.0, 4, "خودکار (پیش‌فرض)", 1024, 1024, "", "free")
 
293
  return res, s, status
294
 
295
- # --- جاوااسکریپت برای دکمه دانلود ---
296
  js_download_func = """
297
  async (image) => {
298
  if (!image) {
@@ -312,54 +321,121 @@ async (image) => {
312
  }
313
  """
314
 
315
- # --- جاوااسکریپت سراسری (شامل فینگرپرینت، استعلام وضعیت کاربر و ریست آی‌پی) ---
316
  js_global_content = """
317
  <script>
318
- // --- START: Fingerprint & User Status Logic ---
319
- async function getBrowserFingerprint() {
320
- const components = [navigator.userAgent, navigator.language, screen.width + 'x' + screen.height, new Date().getTimezoneOffset()];
321
- try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.textBaseline = "top"; ctx.font = "14px 'Arial'"; ctx.textBaseline = "alphabetic"; ctx.fillStyle = "#f60"; ctx.fillRect(125, 1, 62, 20); ctx.fillStyle = "#069"; ctx.fillText("a1b2c3d4e5f6g7h8i9j0_!@#$%^&*()", 2, 15); components.push(canvas.toDataURL()); } catch (e) { components.push("canvas-error"); }
322
- const fingerprintString = components.join('~~~'); let hash = 0;
323
- for (let i = 0; i < fingerprintString.length; i++) { const char = fingerprintString.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash |= 0; }
324
- return 'fp_' + Math.abs(hash).toString(16);
325
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
- // Global variables to hold status
328
- window.userFingerprint = null;
329
- window.userSubscriptionStatus = 'free';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
 
331
- document.addEventListener('DOMContentLoaded', async () => {
332
- // 1. Calculate Fingerprint
333
- window.userFingerprint = await getBrowserFingerprint();
334
- console.log("Fingerprint:", window.userFingerprint);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
- // 2. Request User Status from Parent
337
  window.addEventListener('message', (event) => {
338
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
339
  try {
340
- const userObject = JSON.parse(event.data.payload);
341
- const PREMIUM_PAGE_ID = 1149636; // شناسه محصول
342
- // بررسی دسترسی کاربر
343
- if (userObject && userObject.isLogin && userObject.accessible_pages &&
344
- (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) || userObject.accessible_pages.includes(String(PREMIUM_PAGE_ID)))) {
345
- window.userSubscriptionStatus = 'paid';
346
- } else {
347
- window.userSubscriptionStatus = 'free';
348
- }
349
- console.log("User Status:", window.userSubscriptionStatus);
350
  } catch (e) {
351
  console.error("Error parsing user status:", e);
352
- window.userSubscriptionStatus = 'free';
353
  }
354
  }
355
  });
356
-
357
- // ارسال درخواست به پنجره والد
358
- window.parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
359
-
360
- // --- Existing Logic (Theme, IP Reset, Scanner) ---
361
-
362
- // 1. Force Light Mode
363
  const forceLight = () => {
364
  const body = document.querySelector('body');
365
  if (body) {
@@ -372,111 +448,72 @@ document.addEventListener('DOMContentLoaded', async () => {
372
  forceLight();
373
  setInterval(forceLight, 1000);
374
 
375
- // 2. RETRY FUNCTION
376
  window.retryGeneration = function() {
377
  const modal = document.getElementById('custom-quota-modal');
378
  if (modal) modal.remove();
379
-
380
  const runBtn = document.getElementById('run-btn');
381
  if(runBtn) runBtn.click();
382
  };
383
 
384
- // Close function
385
  window.closeErrorModal = function() {
386
  const modal = document.getElementById('custom-quota-modal');
387
  if (modal) modal.remove();
388
  };
389
 
390
- // 3. SHOW MODAL FUNCTION (IP Reset)
391
  const showQuotaModal = () => {
392
  if (document.getElementById('custom-quota-modal')) return;
393
-
394
  const modalHtml = `
395
  <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;">
396
  <div class="ip-reset-guide-container">
397
  <div class="guide-header">
398
- <svg class="guide-header-icon" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
399
- <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>
400
- <circle cx="50" cy="50" r="45" fill="url(#grad1)" opacity="0.1"></circle>
401
- <circle cx="50" cy="50" r="35" fill="none" stroke="url(#grad1)" stroke-width="2" opacity="0.3"></circle>
402
- <path d="M35 50 L45 60 L65 40" stroke="url(#grad1)" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"></path>
403
- <circle cx="65" cy="35" r="8" fill="#fee140"></circle>
404
- <path d="M62 35 L68 35 M65 32 L65 38" stroke="white" stroke-width="2" stroke-linecap="round"></path>
405
- </svg>
406
  <div>
407
- <h2>یک قدم تا ساخت تصاویر جدید</h2>
408
- <p>نیازمند تغییر نقطه دستیابی</p>
409
  </div>
410
  </div>
411
 
412
  <div class="guide-content">
413
- <div class="info-card">
414
- <div class="info-card-header">
415
- <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>
416
- <span class="info-card-title">راه حل سریع</span>
417
- </div>
418
- <p>طبق ویدیو آموزشی پایین بین نقطه دستیابی جابجا شوید تلاش مجدد بزنید تا تصاویر مجدداً تولید بشه.</p>
419
- </div>
420
-
421
- <div class="summary-section">
422
- <div class="summary-header">
423
- <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>
424
- <span class="summary-title">خلاصه راهنما</span>
425
- </div>
426
- <div class="summary-text">هربار که این صفحه را مشاهده کردید: از اینترنت سیم‌کارت استفاده کنید، VPN را خاموش کرده و طبق ویدیو آموزشی پایین نقطه دستیابی رو تغییر دهید. «تلاش مجدد» کلیک کنید. با این روش ساده می‌توانید به صورت نامحدود تصویر بسازید! ☘️</div>
427
  </div>
428
 
429
  <div class="video-button-container">
430
- <button onclick="parent.postMessage({ type: 'NAVIGATE_TO_URL', url: '#/nav/online/news/getSingle/1149635/eyJpdiI6IjhHVGhPQWJwb3E0cjRXbnFWTW5BaUE9PSIsInZhbHVlIjoiS1V0dTdvT21wbXAwSXZaK1RCTG1pVXZqdlFJa1hXV1RKa2FLem9zU3pXMjd5MmlVOGc2YWY0NVdNR3h3Smp1aSIsIm1hYyI6IjY1NTA5ZDYzMjAzMTJhMGQyMWQ4NjA4ZDgyNGZjZDVlY2MyNjdiMjA2NWYzOWRjY2M4ZmVjYWRlMWNlMWQ3ODEiLCJ0YWciOiIifQ==/21135210' }, '*')" class="elegant-video-button">
431
- <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>
432
- <span>دیدن ویدیو آموزشی استفاده نامحدود</span>
433
  </button>
434
  </div>
435
  </div>
436
 
437
  <div class="guide-actions">
438
  <button class="action-button back-button" onclick="window.closeErrorModal()">
439
- <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>
440
  <span>بازگشت</span>
441
  </button>
442
- <button class="action-button retry-button" onclick="window.retryGeneration()">
443
- <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>
444
- <span>تلاش مجدد</span>
445
- </button>
446
  </div>
447
  </div>
448
  </div>
449
  `;
450
-
451
  document.body.insertAdjacentHTML('beforeend', modalHtml);
452
-
453
- // Auto close after 10 seconds
454
- setTimeout(() => {
455
- window.closeErrorModal();
456
- }, 10000);
457
  };
458
 
459
- // 4. SCANNER
460
  setInterval(() => {
461
  const potentialErrors = document.querySelectorAll('.toast-body, .error, .toast-wrap, .eta-bar, div[class*="error"]');
462
-
463
  potentialErrors.forEach(el => {
464
  const text = el.innerText || "";
465
- if (text.toLowerCase().includes('quota') || text.toLowerCase().includes('exceeded')) {
 
466
  showQuotaModal();
467
  el.style.display = 'none';
468
- el.style.opacity = '0';
469
- el.innerText = '';
470
  const parentWrap = el.closest('.toast-wrap');
471
  if(parentWrap) parentWrap.style.display = 'none';
472
  }
473
  });
474
- }, 100);
475
  });
476
  </script>
477
  """
478
 
479
- # --- CSS Updated (Larger & Auto Scroll) ---
480
  css_code = """
481
  <style>
482
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
@@ -484,168 +521,45 @@ css_code = """
484
  :root, .dark, body, .gradio-container {
485
  --body-background-fill: #f5f7fa !important;
486
  --body-text-color: #1f2937 !important;
487
- --background-fill-primary: #ffffff !important;
488
- --background-fill-secondary: #f3f4f6 !important;
489
- --border-color-primary: #e5e7eb !important;
490
- --block-background-fill: #ffffff !important;
491
- --block-label-text-color: #374151 !important;
492
- --block-title-text-color: #111827 !important;
493
- --input-background-fill: #ffffff !important;
494
- color-scheme: light !important;
495
- }
496
-
497
- /* --- IP Reset Guide CSS --- */
498
- :root {
499
- --guide-bg: rgba(255, 255, 255, 0.98);
500
- --guide-border: rgba(102, 126, 234, 0.2);
501
- --guide-text-title: #2d3748;
502
- --guide-text-body: #4a5568;
503
- --guide-accent: #667eea;
504
- --primary-gradient-guide: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
505
- --success-gradient-guide: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
506
- --radius-md-guide: 12px;
507
- --radius-lg-guide: 16px;
508
- --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
509
- --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
510
- --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
511
  }
512
 
513
- @keyframes float {
514
- 0%, 100% { transform: translateY(0px); }
515
- 50% { transform: translateY(-10px); }
516
- }
517
- @keyframes slideInUp {
518
- from { opacity: 0; transform: translateY(30px); }
519
- to { opacity: 1; transform: translateY(0); }
 
 
 
520
  }
521
 
 
522
  .ip-reset-guide-container {
523
  text-align: right;
524
  direction: rtl;
525
- background: var(--guide-bg);
526
- backdrop-filter: blur(10px);
527
- padding: 20px; /* Increased Padding */
528
- border-radius: var(--radius-lg-guide);
529
- box-shadow: var(--shadow-xl);
530
- border: 1px solid var(--guide-border);
531
- animation: slideInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1) both;
532
  width: 90%;
533
- max-width: 420px; /* Increased width slightly */
534
- max-height: 90vh; /* Prevent overflow on small screens */
535
- overflow-y: auto; /* Enable scroll if needed */
536
- position: relative;
537
- box-sizing: border-box;
538
  font-family: 'Vazirmatn', sans-serif !important;
539
  }
540
- .ip-reset-guide-container::before {
541
- content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; background: var(--primary-gradient-guide);
542
- }
543
- .guide-header { display: flex; align-items: center; margin-bottom: 15px; }
544
- .guide-header-icon { width: 45px; height: 45px; margin-left: 15px; animation: float 3s ease-in-out infinite; flex-shrink: 0; }
545
- .guide-header h2 { font-size: 1.2rem; color: var(--guide-text-title); font-weight: 700; margin: 0; }
546
- .guide-header p { color: var(--guide-text-body); font-size: 0.8rem; margin-top: 3px; margin-bottom: 0; }
547
-
548
- .guide-content { font-size: 0.9rem; color: var(--guide-text-body); line-height: 1.6; }
549
-
550
- .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; }
551
- .info-card p { font-size: 0.85rem; line-height: 1.6; margin: 0; }
552
- .info-card::before { content: ''; position: absolute; top: 0; right: 0; width: 3px; height: 100%; background: var(--primary-gradient-guide); }
553
- .info-card-header { display: flex; align-items: center; margin-bottom: 8px; }
554
- .info-card-icon { width: 18px; height: 18px; margin-left: 8px; }
555
- .info-card-title { font-weight: 600; color: var(--guide-text-title); font-size: 0.95rem; }
556
-
557
- .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; }
558
- .summary-section::before { content: ''; position: absolute; top: 0; right: 0; width: 3px; height: 100%; background: var(--success-gradient-guide); }
559
- .summary-header { display: flex; align-items: center; margin-bottom: 8px; }
560
- .summary-icon { width: 18px; height: 18px; margin-left: 8px; }
561
- .summary-title { font-weight: 600; color: #2f5a33; font-size: 0.95rem; }
562
- .summary-text { color: #2f5a33; font-size: 0.85rem; line-height: 1.6; }
563
-
564
- /* Tutorial Button */
565
- .video-button-container { text-align: center; margin: 20px 0 15px 0; width: 100%; }
566
  .elegant-video-button {
567
- display: inline-flex !important;
568
- align-items: center;
569
- justify-content: center;
570
- padding: 10px 20px !important;
571
- background-color: #fff !important;
572
- color: var(--guide-accent) !important;
573
- border: 1px solid #e2e8f0 !important;
574
- text-decoration: none;
575
- border-radius: 50px !important;
576
- font-weight: 600 !important;
577
- font-size: 0.9rem !important;
578
- cursor: pointer !important;
579
- font-family: inherit;
580
- transition: all 0.3s ease !important;
581
- box-shadow: 0 2px 10px rgba(0,0,0,0.05) !important;
582
- width: auto !important;
583
- }
584
- .elegant-video-button:hover {
585
- background: var(--primary-gradient-guide) !important;
586
- color: white !important;
587
- border-color: transparent !important;
588
- transform: translateY(-2px);
589
- box-shadow: 0 6px 16px rgba(102, 126, 234, 0.3) !important;
590
- }
591
- .elegant-video-button-icon { width: 18px; height: 18px; margin-left: 8px; fill: currentColor; }
592
-
593
- /* Action Buttons */
594
- .guide-actions {
595
- display: flex !important;
596
- gap: 12px !important;
597
- margin-top: 20px;
598
- padding-top: 20px;
599
- border-top: 1px solid #e2e8f0;
600
- width: 100% !important;
601
- }
602
- .action-button {
603
- padding: 12px 15px !important;
604
- border: none !important;
605
- border-radius: 12px !important;
606
- font-size: 0.95rem !important;
607
- font-weight: 600 !important;
608
- cursor: pointer !important;
609
- flex: 1 !important;
610
- transition: all 0.3s ease !important;
611
- display: flex !important;
612
- align-items: center;
613
- justify-content: center;
614
- font-family: inherit;
615
- height: 48px !important;
616
- }
617
- .action-button-icon { width: 20px; height: 20px; margin-right: 0; margin-left: 8px; }
618
-
619
- .back-button {
620
- background: white !important;
621
- color: var(--guide-text-body) !important;
622
- border: 2px solid #e2e8f0 !important;
623
- }
624
- .back-button:hover {
625
- background: #f7fafc !important;
626
- border-color: var(--guide-accent) !important;
627
- transform: translateY(-2px);
628
- box-shadow: var(--shadow-md) !important;
629
- }
630
-
631
- .retry-button {
632
- background: var(--primary-gradient-guide) !important;
633
- color: white !important;
634
- box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3) !important;
635
- }
636
- .retry-button:hover {
637
- transform: translateY(-2px);
638
- box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4) !important;
639
- }
640
-
641
- /* --- Main App CSS --- */
642
- body {
643
- font-family: 'Vazirmatn', sans-serif !important;
644
- background-color: #f5f7fa !important;
645
- margin: 0;
646
- padding: 10px;
647
  }
 
 
648
 
 
649
  #col-container {
650
  margin: 0 auto;
651
  max-width: 980px;
@@ -657,178 +571,57 @@ body {
657
  box-shadow: 0 10px 40px -10px rgba(0,0,0,0.08);
658
  border: 1px solid rgba(255,255,255,0.8);
659
  }
660
-
661
  #main-title h1 {
662
  font-size: 2.4em !important;
663
  text-align: center;
664
  color: #1a202c !important;
665
- margin-bottom: 15px;
666
  font-weight: 800;
667
  background: -webkit-linear-gradient(45deg, #2563eb, #1e40af);
668
  -webkit-background-clip: text;
669
  -webkit-text-fill-color: transparent;
670
  }
671
-
672
  #main-description {
673
  text-align: center;
674
  font-size: 1.15em;
675
  color: #4b5563 !important;
676
- margin-bottom: 40px;
677
- line-height: 1.6;
678
- }
679
-
680
- .gr-input-label, span.label-wrap, label span {
681
- font-weight: 700 !important;
682
- color: #374151 !important;
683
- font-size: 0.95em !important;
684
- margin-bottom: 8px !important;
685
- }
686
-
687
- textarea, input[type="text"] {
688
- border: 2px solid #e2e8f0 !important;
689
- border-radius: 12px !important;
690
- background-color: #ffffff !important;
691
- color: #111827 !important;
692
- padding: 12px !important;
693
- transition: all 0.3s ease;
694
- font-family: 'Vazirmatn', sans-serif !important;
695
- }
696
-
697
- textarea:focus, input[type="text"]:focus {
698
- border-color: #3b82f6 !important;
699
- box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1) !important;
700
- outline: none;
701
  }
702
-
703
- .gr-dropdown {
704
- background: #ffffff !important;
705
- border-radius: 12px !important;
706
  }
707
-
708
- .primary-btn, button.primary {
709
  background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
710
  border: none !important;
711
  color: white !important;
712
  font-weight: 700 !important;
713
- font-size: 1.1em !important;
714
  padding: 14px 28px !important;
715
  border-radius: 14px !important;
716
- box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3) !important;
717
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
718
- cursor: pointer !important;
719
- width: 100%;
720
- margin-top: 15px;
721
- }
722
-
723
- .primary-btn:hover, button.primary:hover {
724
- transform: translateY(-2px);
725
- box-shadow: 0 8px 25px rgba(16, 185, 129, 0.45) !important;
726
- }
727
-
728
- .primary-btn:active, button.primary:active {
729
- transform: translateY(1px);
730
- }
731
-
732
- #download-btn {
733
- background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
734
- box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3) !important;
735
- }
736
- #download-btn:hover {
737
- box-shadow: 0 8px 25px rgba(59, 130, 246, 0.45) !important;
738
- }
739
-
740
- .gradio-container .prose table,
741
- .gradio-container table {
742
- background-color: #ffffff !important;
743
- color: #111827 !important;
744
- border: 1px solid #e5e7eb !important;
745
- border-radius: 12px !important;
746
- overflow: hidden !important;
747
- width: 100% !important;
748
- margin-top: 20px !important;
749
- }
750
-
751
- .gradio-container thead th {
752
- background-color: #f3f4f6 !important;
753
- color: #374151 !important;
754
- font-weight: 700 !important;
755
- border-bottom: 2px solid #e5e7eb !important;
756
- padding: 12px !important;
757
- text-align: right !important;
758
- }
759
-
760
- .gradio-container tbody tr {
761
- background-color: #ffffff !important;
762
- border-bottom: 1px solid #f3f4f6 !important;
763
- }
764
-
765
- .gradio-container tbody tr:hover {
766
- background-color: #f9fafb !important;
767
- }
768
-
769
- .gradio-container tbody td {
770
- background-color: #ffffff !important;
771
- color: #374151 !important;
772
- padding: 10px !important;
773
- }
774
-
775
- .gradio-container tbody td span,
776
- .gradio-container tbody td p {
777
- color: #374151 !important;
778
- }
779
-
780
- footer { display: none !important; }
781
- .flagging { display: none !important; }
782
-
783
- /* Force toast transparency */
784
- .toast-body {
785
- direction: rtl !important;
786
- text-align: right !important;
787
- background: transparent !important;
788
- box-shadow: none !important;
789
- border: none !important;
790
- padding: 0 !important;
791
- max-width: 100% !important;
792
- width: auto !important;
793
- }
794
- .toast-wrap {
795
- background: transparent !important;
796
- border: none !important;
797
- box-shadow: none !important;
798
- }
799
-
800
- @media (prefers-color-scheme: dark) {
801
- body, .gradio-container, .prose, table, tr, td, th {
802
- background-color: #ffffff !important;
803
- color: #333333 !important;
804
- }
805
  }
806
  </style>
807
  """
808
 
809
- # ادغام CSS و JS
810
  combined_html = css_code + js_global_content
811
 
812
- # استفاده از gr.Blocks
813
  with gr.Blocks() as demo:
814
- # تزریق کدها به عنوان HTML
815
  gr.HTML(combined_html)
816
 
 
 
 
 
 
817
  with gr.Column(elem_id="col-container"):
818
  gr.Markdown("# **ویرایشگر هوشمند آلفا**", elem_id="main-title")
819
  gr.Markdown(
820
  "با هوش مصنوعی آلفا تصاویر تونو به مدل های مختلف ویرایش کنید.",
821
  elem_id="main-description"
822
  )
823
- # --- متن اضافه شده برای مشخصات کاربران ---
824
- gr.HTML(
825
- """
826
- <div style="text-align: center; margin-bottom: 20px; font-size: 0.9em; font-family: 'Vazirmatn', sans-serif;">
827
- <span style="color: #666; margin: 0 10px; background: #f0f0f0; padding: 4px 10px; border-radius: 12px;">👤 نسخه رایگان: ۵ تصویر در روز</span>
828
- <span style="color: #059669; margin: 0 10px; font-weight: bold; background: #d1fae5; padding: 4px 10px; border-radius: 12px;">⭐️ نسخه نامحدود: دسترسی کامل</span>
829
- </div>
830
- """
831
- )
832
 
833
  with gr.Row(equal_height=True):
834
  with gr.Column():
@@ -844,7 +637,6 @@ with gr.Blocks() as demo:
844
 
845
  status_box = gr.HTML(label="وضعیت")
846
 
847
- # IMPORTANT: Added elem_id="run-btn" here for JS targeting
848
  run_button = gr.Button("✨ شروع پردازش و ساخت تصویر", variant="primary", elem_classes="primary-btn", elem_id="run-btn")
849
 
850
  with gr.Column():
@@ -882,11 +674,6 @@ with gr.Blocks() as demo:
882
  guidance_scale = gr.Slider(label="میزان وفاداری به متن (Guidance Scale)", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
883
  steps = gr.Slider(label="تعداد مراحل پردازش (Steps)", minimum=1, maximum=50, step=1, value=4)
884
 
885
- # --- کامپوننت‌های مخفی برای انتقال اطلاعات کاربر ---
886
- hidden_fingerprint = gr.Textbox(visible=False)
887
- hidden_status = gr.Textbox(visible=False)
888
-
889
- # اصلاح تابع نمایش ردیف اسلایدرها
890
  def toggle_row(choice):
891
  if choice == "شخصی‌سازی (Custom)":
892
  return gr.update(visible=True)
@@ -919,23 +706,16 @@ with gr.Blocks() as demo:
919
  label="نمونه‌ها (برای تست کلیک کنید)"
920
  )
921
 
922
- # جاوا اسکریپت برای تزریق فینگرپرینت و وضعیت به ورودی‌های تابع پایتون
923
- # آرگومان‌های ورودی تابع js باید با inputs همخوانی داشته باشند (به جز دو مورد آخر که اضافه می‌کنیم)
924
- js_click_handler = """
925
- (img, p, lora, s, rs, gs, steps, ar, cw, ch, fp, stat) => {
926
- return [
927
- img, p, lora, s, rs, gs, steps, ar, cw, ch,
928
- window.userFingerprint,
929
- window.userSubscriptionStatus
930
- ];
931
- }
932
- """
933
-
934
  run_button.click(
935
  fn=infer,
936
- inputs=[input_image, prompt, lora_adapter_persian, seed, randomize_seed, guidance_scale, steps, aspect_ratio_selection, custom_width, custom_height, hidden_fingerprint, hidden_status],
 
 
 
 
 
937
  outputs=[output_image, seed, status_box],
938
- js=js_click_handler,
939
  api_name="predict"
940
  )
941
 
 
33
 
34
  # --- تنظیمات سیستم اعتبار ---
35
  USAGE_LIMIT = 5
36
+ usage_data_cache = {} # {fingerprint: {'count': 0, 'last_reset': 'YYYY-MM-DD'}}
37
+ PREMIUM_PAGE_ID = '1149636' # شناسه محصول برای بررسی خرید
38
 
39
  # --- بارگذاری مدل تشخیص محتوای نامناسب (NSFW) ---
40
  print("Loading Safety Checker...")
 
175
  </div>
176
  """
177
 
178
+ def check_quota(fingerprint, subscription_status):
179
+ """
180
+ بررسی اعتبار کاربر.
181
+ اگر پولی باشد -> True
182
+ اگر رایگان باشد:
183
+ - بررسی تاریخ
184
+ - بررسی تعداد استفاده
185
+ """
186
+ if subscription_status == 'paid':
187
+ return True, "unlimited"
188
 
 
189
  if not fingerprint:
190
+ # اگر فینگرپرینت نرسید (مثلا جاوا اسکریپت لود نشد)، به عنوان مهمان یک بار اجازه دهیم یا خطا دهیم.
191
+ # اینجا فرض می‌کنیم باید خطا دهیم یا اجازه محدود دهیم.
192
+ return True, 0 # برای جلوگیری از خطا در اولین اجرا
193
+
194
+ today_str = date.today().isoformat()
195
+ user_record = usage_data_cache.get(fingerprint)
196
+
197
+ # اگر کاربر جدید است یا روز عوض شده
198
+ if not user_record or user_record.get("last_reset") != today_str:
199
+ user_record = {"count": 0, "last_reset": today_str}
200
+ usage_data_cache[fingerprint] = user_record
201
 
 
 
 
 
 
 
202
  # بررسی محدودیت
203
+ if user_record["count"] >= USAGE_LIMIT:
204
+ return False, user_record["count"]
205
 
206
+ # کسر اعتبار (در واقع افزایش استفاده)
207
+ user_record["count"] += 1
208
+ return True, user_record["count"]
209
 
 
 
 
 
 
 
210
 
211
  @spaces.GPU(duration=30)
212
  def infer(
 
220
  aspect_ratio_selection,
221
  custom_width,
222
  custom_height,
223
+ fingerprint,
224
+ subscription_status,
225
  progress=gr.Progress(track_tqdm=True)
226
  ):
227
+ # --- بررسی اعتبار ---
228
+ is_allowed, usage_count = check_quota(fingerprint, subscription_status)
229
+
230
  if not is_allowed:
231
+ msg = "شما به محدودیت ۵ تصویر رایگان روزانه خود رسیده‌اید. لطفاً برای استفاده نامحدود حساب خود را ارتقا دهید."
232
+ # این پیام باعث نمایش مودال ارتقا می‌شود (به خاطر کلمه Quota یا Limited در JS)
233
  return None, seed, get_error_html(msg)
234
 
235
  if input_image is None:
 
282
  if is_image_nsfw(result):
283
  return None, seed, get_error_html("تصویر تولید شده حاوی محتوای نامناسب بود و حذف شد.")
284
 
285
+ success_msg = f"تصویر با موفقیت ویرایش شد."
286
+ if subscription_status != 'paid':
287
+ success_msg += f" (اعتبار باقی‌مانده امروز: {USAGE_LIMIT - usage_count})"
288
+
289
+ return result, seed, get_success_html(success_msg)
290
 
291
  except Exception as e:
292
  error_str = str(e)
 
296
 
297
  @spaces.GPU(duration=30)
298
  def infer_example(input_image, prompt, lora_adapter):
299
+ # برای مثال‌ها، فینگرپرینت ساختگی و وضعیت رایگان در نظر می‌گیریم اما معمولا مثال‌ها اعتبار کم نمی‌کنند
300
+ # یا می‌توانید برای مثال‌ها سختگیری نکنید. اینجا سختگیری نمیکنیم.
301
+ res, s, status = infer(input_image, prompt, lora_adapter, 0, True, 1.0, 4, "خودکار (پیش‌فرض)", 1024, 1024, "example_user", "paid")
302
  return res, s, status
303
 
304
+ # --- جاوااسکریپت برای دانلود ---
305
  js_download_func = """
306
  async (image) => {
307
  if (!image) {
 
321
  }
322
  """
323
 
324
+ # --- جاوااسکریپت جامع (Fingerprint, Subscription, UI, Errors) ---
325
  js_global_content = """
326
  <script>
327
+ document.addEventListener('DOMContentLoaded', () => {
328
+ // ---------------------------------------------
329
+ // 1. Fingerprint & Subscription Logic
330
+ // ---------------------------------------------
331
+
332
+ // تابع تولید Fingerprint
333
+ async function getBrowserFingerprint() {
334
+ const components = [
335
+ navigator.userAgent,
336
+ navigator.language,
337
+ screen.width + 'x' + screen.height,
338
+ new Date().getTimezoneOffset()
339
+ ];
340
+ try {
341
+ const canvas = document.createElement('canvas');
342
+ const ctx = canvas.getContext('2d');
343
+ ctx.textBaseline = "top";
344
+ ctx.font = "14px 'Arial'";
345
+ ctx.textBaseline = "alphabetic";
346
+ ctx.fillStyle = "#f60";
347
+ ctx.fillRect(125, 1, 62, 20);
348
+ ctx.fillStyle = "#069";
349
+ ctx.fillText("a1b2c3d4e5f6g7h8i9j0_!@#$%^&*()", 2, 15);
350
+ components.push(canvas.toDataURL());
351
+ } catch (e) {
352
+ components.push("canvas-error");
353
+ }
354
+ const fingerprintString = components.join('~~~');
355
+ let hash = 0;
356
+ for (let i = 0; i < fingerprintString.length; i++) {
357
+ const char = fingerprintString.charCodeAt(i);
358
+ hash = ((hash << 5) - hash) + char;
359
+ hash |= 0;
360
+ }
361
+ return 'fp_' + Math.abs(hash).toString(16);
362
+ }
363
 
364
+ // بررسی کاربر پولی بودن
365
+ function isUserPaid(userObject) {
366
+ const PREMIUM_PAGE_ID = '1149636';
367
+ if (userObject && userObject.isLogin && userObject.accessible_pages) {
368
+ // بررسی می‌کنیم آیا ID صفحه پرمیوم در لیست دسترسی‌ها هست یا نه
369
+ if (Array.isArray(userObject.accessible_pages)) {
370
+ return userObject.accessible_pages.some(page =>
371
+ String(page) === String(PREMIUM_PAGE_ID)
372
+ );
373
+ }
374
+ }
375
+ return false;
376
+ }
377
+
378
+ // به‌روزرسانی UI badge
379
+ function updateSubscriptionBadge(status) {
380
+ const badge = document.getElementById('user-sub-badge');
381
+ const fpInput = document.querySelector('#fingerprint_storage textarea');
382
+ const stInput = document.querySelector('#status_storage textarea');
383
+
384
+ // مقداردهی اینپوت‌های مخفی برای ارسال به پایتون
385
+ if(fpInput) {
386
+ fpInput.value = window.userFingerprint || 'unknown';
387
+ fpInput.dispatchEvent(new Event('input', { bubbles: true }));
388
+ }
389
+ if(stInput) {
390
+ stInput.value = status;
391
+ stInput.dispatchEvent(new Event('input', { bubbles: true }));
392
+ }
393
 
394
+ if (!badge) return;
395
+
396
+ if (status === 'paid') {
397
+ badge.innerHTML = '✨ اشتراک: <span style="color: #FFD700; font-weight: bold;">نامحدود (PRO)</span>';
398
+ badge.style.background = 'linear-gradient(45deg, #1e3a8a, #3b82f6)';
399
+ } else {
400
+ badge.innerHTML = '👤 اشتراک: <span style="color: #fff; font-weight: bold;">رایگان (۵ اعتبار روزانه)</span>';
401
+ badge.style.background = 'linear-gradient(45deg, #4b5563, #6b7280)';
402
+ }
403
+ badge.style.display = 'inline-block';
404
+ }
405
+
406
+ // شروع فرآیند شناسایی
407
+ async function initUserIdentity() {
408
+ window.userFingerprint = await getBrowserFingerprint();
409
+
410
+ // درخواست وضعیت از Parent
411
+ window.parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
412
+
413
+ // پیش‌فرض: رایگان
414
+ updateSubscriptionBadge('free');
415
+ }
416
 
417
+ // شنیدن پیام از Parent
418
  window.addEventListener('message', (event) => {
419
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
420
  try {
421
+ const userObject = typeof event.data.payload === 'string'
422
+ ? JSON.parse(event.data.payload)
423
+ : event.data.payload;
424
+
425
+ const status = isUserPaid(userObject) ? 'paid' : 'free';
426
+ updateSubscriptionBadge(status);
 
 
 
 
427
  } catch (e) {
428
  console.error("Error parsing user status:", e);
429
+ updateSubscriptionBadge('free');
430
  }
431
  }
432
  });
433
+
434
+ initUserIdentity();
435
+
436
+ // ---------------------------------------------
437
+ // 2. UI Helper & Error Handling
438
+ // ---------------------------------------------
 
439
  const forceLight = () => {
440
  const body = document.querySelector('body');
441
  if (body) {
 
448
  forceLight();
449
  setInterval(forceLight, 1000);
450
 
 
451
  window.retryGeneration = function() {
452
  const modal = document.getElementById('custom-quota-modal');
453
  if (modal) modal.remove();
 
454
  const runBtn = document.getElementById('run-btn');
455
  if(runBtn) runBtn.click();
456
  };
457
 
 
458
  window.closeErrorModal = function() {
459
  const modal = document.getElementById('custom-quota-modal');
460
  if (modal) modal.remove();
461
  };
462
 
 
463
  const showQuotaModal = () => {
464
  if (document.getElementById('custom-quota-modal')) return;
 
465
  const modalHtml = `
466
  <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;">
467
  <div class="ip-reset-guide-container">
468
  <div class="guide-header">
469
+ <div style="font-size: 2rem;">💎</div>
 
 
 
 
 
 
 
470
  <div>
471
+ <h2>پایان اعتبار رایگان امروز</h2>
472
+ <p>برای ادامه، حساب خود را ارتقا دهید یا فردا برگردید.</p>
473
  </div>
474
  </div>
475
 
476
  <div class="guide-content">
477
+ <div class="info-card" style="border-color: #fbbf24; background: #fffbeb;">
478
+ <p>شما از ۵ اعتبار رایگان امروز خود استفاده کرده‌اید. با خرید نسخه نامحدود، بدون هیچ محدودیتی تصویر بسازید.</p>
 
 
 
 
 
 
 
 
 
 
 
 
479
  </div>
480
 
481
  <div class="video-button-container">
482
+ <button onclick="parent.postMessage({ type: 'NAVIGATE_TO_URL', url: '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTc1NDIyNGVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991' }, '*')" class="elegant-video-button" style="background: linear-gradient(135deg, #f59e0b, #d97706) !important; color: white !important;">
483
+ <span>⭐️ خرید نسخه نامحدود</span>
 
484
  </button>
485
  </div>
486
  </div>
487
 
488
  <div class="guide-actions">
489
  <button class="action-button back-button" onclick="window.closeErrorModal()">
 
490
  <span>بازگشت</span>
491
  </button>
 
 
 
 
492
  </div>
493
  </div>
494
  </div>
495
  `;
 
496
  document.body.insertAdjacentHTML('beforeend', modalHtml);
 
 
 
 
 
497
  };
498
 
 
499
  setInterval(() => {
500
  const potentialErrors = document.querySelectorAll('.toast-body, .error, .toast-wrap, .eta-bar, div[class*="error"]');
 
501
  potentialErrors.forEach(el => {
502
  const text = el.innerText || "";
503
+ // اگر ارور مربوط به Quota بود (که از سمت پایتون میاد)
504
+ if (text.includes('محدودیت') && text.includes('رایگان')) {
505
  showQuotaModal();
506
  el.style.display = 'none';
 
 
507
  const parentWrap = el.closest('.toast-wrap');
508
  if(parentWrap) parentWrap.style.display = 'none';
509
  }
510
  });
511
+ }, 500);
512
  });
513
  </script>
514
  """
515
 
516
+ # --- CSS Updated ---
517
  css_code = """
518
  <style>
519
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
 
521
  :root, .dark, body, .gradio-container {
522
  --body-background-fill: #f5f7fa !important;
523
  --body-text-color: #1f2937 !important;
524
+ font-family: 'Vazirmatn', sans-serif !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  }
526
 
527
+ /* Badge Style */
528
+ #user-sub-badge {
529
+ padding: 6px 16px;
530
+ border-radius: 20px;
531
+ font-size: 0.9em;
532
+ color: white;
533
+ margin-top: 10px;
534
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
535
+ display: none; /* JS will show it */
536
+ transition: all 0.3s ease;
537
  }
538
 
539
+ /* Modal CSS (Reused from previous request mostly) */
540
  .ip-reset-guide-container {
541
  text-align: right;
542
  direction: rtl;
543
+ background: #fff;
544
+ padding: 25px;
545
+ border-radius: 16px;
546
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
 
 
 
547
  width: 90%;
548
+ max-width: 400px;
 
 
 
 
549
  font-family: 'Vazirmatn', sans-serif !important;
550
  }
551
+ .guide-header { display: flex; align-items: center; margin-bottom: 15px; gap: 15px; }
552
+ .guide-header h2 { margin: 0; font-size: 1.2rem; color: #1f2937; }
553
+ .guide-content { font-size: 0.95rem; color: #4b5563; line-height: 1.6; }
554
+ .info-card { padding: 12px; border-radius: 8px; border: 1px solid #e5e7eb; margin-bottom: 15px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
555
  .elegant-video-button {
556
+ display: inline-flex; width: 100%; justify-content: center; padding: 12px;
557
+ border-radius: 12px; border: none; font-weight: bold; cursor: pointer; transition: transform 0.2s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558
  }
559
+ .elegant-video-button:hover { transform: translateY(-2px); }
560
+ .action-button { width: 100%; padding: 10px; border-radius: 8px; border: 1px solid #d1d5db; background: white; cursor: pointer; }
561
 
562
+ /* Main App Styles */
563
  #col-container {
564
  margin: 0 auto;
565
  max-width: 980px;
 
571
  box-shadow: 0 10px 40px -10px rgba(0,0,0,0.08);
572
  border: 1px solid rgba(255,255,255,0.8);
573
  }
 
574
  #main-title h1 {
575
  font-size: 2.4em !important;
576
  text-align: center;
577
  color: #1a202c !important;
578
+ margin-bottom: 5px;
579
  font-weight: 800;
580
  background: -webkit-linear-gradient(45deg, #2563eb, #1e40af);
581
  -webkit-background-clip: text;
582
  -webkit-text-fill-color: transparent;
583
  }
 
584
  #main-description {
585
  text-align: center;
586
  font-size: 1.15em;
587
  color: #4b5563 !important;
588
+ margin-bottom: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
  }
590
+ #badge-container {
591
+ text-align: center;
592
+ margin-bottom: 30px;
593
+ height: 30px;
594
  }
595
+ .primary-btn {
 
596
  background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
597
  border: none !important;
598
  color: white !important;
599
  font-weight: 700 !important;
 
600
  padding: 14px 28px !important;
601
  border-radius: 14px !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  }
603
  </style>
604
  """
605
 
 
606
  combined_html = css_code + js_global_content
607
 
 
608
  with gr.Blocks() as demo:
 
609
  gr.HTML(combined_html)
610
 
611
+ # کامپوننت‌های مخفی برای انتقال اطلاعات JS به Python
612
+ # مقدار این‌ها توسط JS پر می‌شود
613
+ fingerprint_box = gr.Textbox(elem_id="fingerprint_storage", visible=False)
614
+ status_box_input = gr.Textbox(elem_id="status_storage", visible=False)
615
+
616
  with gr.Column(elem_id="col-container"):
617
  gr.Markdown("# **ویرایشگر هوشمند آلفا**", elem_id="main-title")
618
  gr.Markdown(
619
  "با هوش مصنوعی آلفا تصاویر تونو به مدل های مختلف ویرایش کنید.",
620
  elem_id="main-description"
621
  )
622
+
623
+ # محل قرارگیری بج وضعیت اشتراک
624
+ gr.HTML('<div id="badge-container"><span id="user-sub-badge"></span></div>')
 
 
 
 
 
 
625
 
626
  with gr.Row(equal_height=True):
627
  with gr.Column():
 
637
 
638
  status_box = gr.HTML(label="وضعیت")
639
 
 
640
  run_button = gr.Button("✨ شروع پردازش و ساخت تصویر", variant="primary", elem_classes="primary-btn", elem_id="run-btn")
641
 
642
  with gr.Column():
 
674
  guidance_scale = gr.Slider(label="میزان وفاداری به متن (Guidance Scale)", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
675
  steps = gr.Slider(label="تعداد مراحل پردازش (Steps)", minimum=1, maximum=50, step=1, value=4)
676
 
 
 
 
 
 
677
  def toggle_row(choice):
678
  if choice == "شخصی‌سازی (Custom)":
679
  return gr.update(visible=True)
 
706
  label="نمونه‌ها (برای تست کلیک کنید)"
707
  )
708
 
709
+ # اتصال دکمه اجرا با ورودی‌های مخفی Fingerprint و Status
 
 
 
 
 
 
 
 
 
 
 
710
  run_button.click(
711
  fn=infer,
712
+ inputs=[
713
+ input_image, prompt, lora_adapter, seed, randomize_seed,
714
+ guidance_scale, steps, aspect_ratio_selection,
715
+ custom_width, custom_height,
716
+ fingerprint_box, status_box_input # ارسال اطلاعات کاربر به تابع پایتون
717
+ ],
718
  outputs=[output_image, seed, status_box],
 
719
  api_name="predict"
720
  )
721