Opera8 commited on
Commit
5b950ab
·
verified ·
1 Parent(s): f91438e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +245 -344
app.py CHANGED
@@ -4,8 +4,6 @@ import numpy as np
4
  import spaces
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
@@ -31,58 +29,6 @@ colors.steel_blue = colors.Color(
31
 
32
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
33
 
34
- # --- سیستم مدیریت کاربران و سهمیه (Quota System) ---
35
- # ساختار: { 'fingerprint': { 'count': 0, 'date': 'YYYY-MM-DD' } }
36
- USER_QUOTA_DB = {}
37
- DAILY_LIMIT = 5
38
- PREMIUM_PAGE_ID = '1149636'
39
- PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTc1NDIyNGVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991'
40
-
41
- def check_and_update_quota(fingerprint, is_paid_user):
42
- """
43
- بررسی می‌کند آیا کاربر مجاز به تولید تصویر است یا خیر.
44
- اگر مجاز بود، کنتور را می‌اندازد.
45
- """
46
- if is_paid_user:
47
- return True, "Unlimited"
48
-
49
- if not fingerprint:
50
- # اگر فینگرپرینت نرسید، سخت‌گیری نمی‌کنیم یا دیفالت در نظر می‌گیریم
51
- return True, "Unknown"
52
-
53
- today = datetime.now().strftime("%Y-%m-%d")
54
-
55
- # دریافت رکورد کاربر یا ایجاد رکورد جدید
56
- user_data = USER_QUOTA_DB.get(fingerprint, {'count': 0, 'date': today})
57
-
58
- # اگر روز عوض شده، کنتور را صفر کن
59
- if user_data['date'] != today:
60
- user_data = {'count': 0, 'date': today}
61
-
62
- # بررسی محدودیت
63
- if user_data['count'] >= DAILY_LIMIT:
64
- return False, f"تعداد مجاز روزانه ({DAILY_LIMIT} تصویر) به پایان رسیده است."
65
-
66
- # افزایش کنتور (این تابع باید قبل از شروع پردازش قطعی فراخوانی شود، یا بعد از موفقیت. اینجا فرض بر شروع است)
67
- # اما چون ممکن است خطا رخ دهد، بهتر است اینجا فقط چک کنیم و در تابع infer اضافه کنیم.
68
- # فعلا برای سادگی اینجا اضافه نمی‌کنیم، فقط وضعیت را برمی‌گردانیم.
69
- return True, f"{DAILY_LIMIT - user_data['count']} اعتبار باقی‌مانده"
70
-
71
- def consume_quota(fingerprint, is_paid_user):
72
- """یکی از اعتبار کاربر کم می‌کند"""
73
- if is_paid_user or not fingerprint:
74
- return
75
-
76
- today = datetime.now().strftime("%Y-%m-%d")
77
- user_data = USER_QUOTA_DB.get(fingerprint, {'count': 0, 'date': today})
78
-
79
- if user_data['date'] != today:
80
- user_data = {'count': 0, 'date': today}
81
-
82
- user_data['count'] += 1
83
- USER_QUOTA_DB[fingerprint] = user_data
84
-
85
-
86
  # --- بارگذاری مدل تشخیص محتوای نامناسب (NSFW) ---
87
  print("Loading Safety Checker...")
88
  safety_classifier = pipeline("image-classification", model="Falconsai/nsfw_image_detection", device=-1)
@@ -222,7 +168,6 @@ def get_success_html(message):
222
  </div>
223
  """
224
 
225
- # --- تابع اصلی پردازش (به‌روز شده با سیستم Quota) ---
226
  @spaces.GPU(duration=30)
227
  def infer(
228
  input_image,
@@ -235,19 +180,8 @@ def infer(
235
  aspect_ratio_selection,
236
  custom_width,
237
  custom_height,
238
- user_fingerprint, # ورودی جدید
239
- user_status, # ورودی جدید
240
  progress=gr.Progress(track_tqdm=True)
241
  ):
242
- # 1. بررسی وضعیت کاربر و اعتبار
243
- is_paid = (user_status == 'paid')
244
- allowed, message = check_and_update_quota(user_fingerprint, is_paid)
245
-
246
- if not allowed:
247
- # نمایش خطا و دکمه ارتقا (از طریق HTML خطا و JS که دکمه را نمایان میکند)
248
- # ما یک سیگنال خاص در متن خطا می‌گذاریم که JS تشخیص دهد
249
- return None, seed, get_error_html(f"{message} <br> لطفا برای استفاده نامحدود حساب خود را ارتقا دهید.")
250
-
251
  if input_image is None:
252
  return None, seed, get_error_html("لطفاً ابتدا یک تصویر بارگذاری کنید.")
253
 
@@ -298,9 +232,6 @@ def infer(
298
  if is_image_nsfw(result):
299
  return None, seed, get_error_html("تصویر تولید شده حاوی محتوای نامناسب بود و حذف شد.")
300
 
301
- # اگر موفق بود، اعتبار کسر شود
302
- consume_quota(user_fingerprint, is_paid)
303
-
304
  return result, seed, get_success_html("تصویر با موفقیت ویرایش شد.")
305
 
306
  except Exception as e:
@@ -311,249 +242,155 @@ def infer(
311
 
312
  @spaces.GPU(duration=30)
313
  def infer_example(input_image, prompt, lora_adapter):
314
- # مثال‌ها رایگان هستند، پس فینگرپرینت را 'demo' و وضعیت را 'paid' رد می‌کنیم تا محدودیت نداشته باشد
315
- res, s, status = infer(input_image, prompt, lora_adapter, 0, True, 1.0, 4, "خودکار (پیش‌فرض)", 1024, 1024, "demo_user", "paid")
316
  return res, s, status
317
 
318
- # --- جاوااسکریپت و CSS ترکیبی ---
319
- # این بخش شامل کد تشخیص کاربر، استایل دکمه‌های جدید و لاجیک قبلی است
320
-
321
- combined_html_js = """
322
- <style>
323
- @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
324
-
325
- :root, .dark, body, .gradio-container {
326
- --body-background-fill: #f5f7fa !important;
327
- --body-text-color: #1f2937 !important;
328
- --background-fill-primary: #ffffff !important;
329
- --background-fill-secondary: #f3f4f6 !important;
330
- --border-color-primary: #e5e7eb !important;
331
- --app-font: 'Vazirmatn', sans-serif;
332
- --accent-premium: #FFC107;
333
- --accent-premium-glow: rgba(255, 193, 7, 0.3);
334
- color-scheme: light !important;
335
- }
336
-
337
- /* Badge Styles */
338
- @keyframes badge-fade-in {
339
- from { opacity: 0; transform: translateY(-10px) scale(0.95); }
340
- to { opacity: 1; transform: translateY(0) scale(1); }
341
- }
342
- #subscription-status-badge {
343
- display: inline-block;
344
- padding: 6px 16px;
345
- border-radius: 20px;
346
- font-size: 0.9em;
347
- font-weight: 700;
348
- margin-top: 1rem;
349
- margin-bottom: 1rem;
350
- letter-spacing: 0.5px;
351
- text-shadow: 0 1px 2px rgba(0,0,0,0.1);
352
- animation: badge-fade-in 0.6s 0.5s ease-out backwards;
353
- display: none; /* Hidden by default until JS checks status */
354
- font-family: var(--app-font);
355
- }
356
- #subscription-status-badge.free-badge {
357
- background: linear-gradient(45deg, #6c757d, #495057);
358
- color: white;
359
- box-shadow: 0 4px 10px rgba(108, 117, 125, 0.3);
360
- }
361
- #subscription-status-badge.paid-badge {
362
- background: linear-gradient(45deg, var(--accent-premium), #ffca2c);
363
- color: #333;
364
- box-shadow: 0 4px 10px var(--accent-premium-glow);
365
- }
366
-
367
- /* Upgrade Button Styles */
368
- @keyframes upgrade-btn-pulse {
369
- 0% { transform: scale(1); box-shadow: 0 8px 20px -5px var(--accent-premium-glow); }
370
- 50% { transform: scale(1.02); box-shadow: 0 12px 25px -5px var(--accent-premium-glow); }
371
- 100% { transform: scale(1); box-shadow: 0 8px 20px -5px var(--accent-premium-glow); }
372
- }
373
- #upgrade-premium-btn {
374
- display: none;
375
- width: 100%;
376
- margin-top: 1.5rem;
377
- padding: 1rem;
378
- font-family: var(--app-font);
379
- font-size: 1.1em;
380
- font-weight: 800;
381
- color: #212529;
382
- background: linear-gradient(95deg, #FFD54F, var(--accent-premium) 100%);
383
- border: none;
384
- border-radius: 14px;
385
- cursor: pointer;
386
- transition: transform 0.2s ease, box-shadow 0.2s ease;
387
- box-shadow: 0 8px 20px -5px var(--accent-premium-glow);
388
- animation: upgrade-btn-pulse 2.5s infinite;
389
- text-align: center;
390
- }
391
- #upgrade-premium-btn:hover {
392
- transform: translateY(-3px);
393
- box-shadow: 0 12px 25px -5px rgba(255, 193, 7, 0.4);
394
- animation-play-state: paused;
395
- }
396
-
397
- /* --- IP Reset Guide CSS (from previous code) --- */
398
- :root {
399
- --guide-bg: rgba(255, 255, 255, 0.98);
400
- --guide-border: rgba(102, 126, 234, 0.2);
401
- --primary-gradient-guide: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
402
- --success-gradient-guide: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
403
- }
404
-
405
- .ip-reset-guide-container {
406
- text-align: right;
407
- direction: rtl;
408
- background: var(--guide-bg);
409
- backdrop-filter: blur(10px);
410
- padding: 20px;
411
- border-radius: 16px;
412
- box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);
413
- border: 1px solid var(--guide-border);
414
- width: 90%;
415
- max-width: 420px;
416
- font-family: 'Vazirmatn', sans-serif !important;
417
  }
418
- .guide-header { display: flex; align-items: center; margin-bottom: 15px; }
419
- .guide-content { font-size: 0.9rem; color: #4a5568; line-height: 1.6; }
420
- .info-card { background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); border: 1px solid rgba(102, 126, 234, 0.2); border-radius: 12px; padding: 12px; margin: 12px 0; }
421
- .summary-section { margin-top: 12px; padding: 12px; border-radius: 12px; background: linear-gradient(135deg, #56ab2f15 0%, #a8e06315 100%); border: 1px solid rgba(86, 171, 47, 0.2); }
422
- .elegant-video-button { display: inline-flex; align-items: center; justify-content: center; padding: 10px 20px; background-color: #fff; color: #667eea; border: 1px solid #e2e8f0; border-radius: 50px; font-weight: 600; cursor: pointer; width: auto; margin-top:15px; }
423
- .guide-actions { display: flex; gap: 12px; margin-top: 20px; padding-top: 20px; border-top: 1px solid #e2e8f0; }
424
- .action-button { padding: 12px 15px; border: none; border-radius: 12px; font-weight: 600; cursor: pointer; flex: 1; }
425
- .back-button { background: white; color: #4a5568; border: 2px solid #e2e8f0; }
426
- .retry-button { background: var(--primary-gradient-guide); color: white; }
427
-
428
- /* Main App CSS */
429
- body { font-family: 'Vazirmatn', sans-serif !important; background-color: #f5f7fa !important; margin: 0; padding: 10px; }
430
- #col-container { margin: 0 auto; max-width: 980px; direction: rtl; text-align: right; padding: 30px; background: #ffffff !important; border-radius: 24px; box-shadow: 0 10px 40px -10px rgba(0,0,0,0.08); border: 1px solid rgba(255,255,255,0.8); }
431
- #main-title h1 { font-size: 2.4em !important; text-align: center; color: #1a202c !important; margin-bottom: 15px; font-weight: 800; background: -webkit-linear-gradient(45deg, #2563eb, #1e40af); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
432
- #main-description { text-align: center; font-size: 1.15em; color: #4b5563 !important; margin-bottom: 10px; line-height: 1.6; }
433
- .primary-btn { background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important; border: none !important; color: white !important; font-weight: 700 !important; font-size: 1.1em !important; padding: 14px 28px !important; border-radius: 14px !important; box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3) !important; width: 100%; margin-top: 15px; }
434
- #download-btn { background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important; box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3) !important; }
435
- textarea, input[type="text"] { border: 2px solid #e2e8f0 !important; border-radius: 12px !important; padding: 12px !important; font-family: 'Vazirmatn', sans-serif !important; text-align: right; }
436
- footer { display: none !important; }
437
- </style>
438
 
 
 
439
  <script>
440
  document.addEventListener('DOMContentLoaded', () => {
441
- // ---------------------------------------------
442
- // 1. User Identification & Fingerprinting Logic
443
- // ---------------------------------------------
444
-
445
  const PREMIUM_PAGE_ID = '1149636';
446
  const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTc1NDIyNGVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
447
 
448
- let userFingerprint = null;
449
- let userSubscriptionStatus = 'free';
450
-
451
- // Simple Fingerprint Generator
452
- async function getBrowserFingerprint() {
453
- const components = [
454
- navigator.userAgent,
455
- navigator.language,
456
- screen.width + 'x' + screen.height,
457
- new Date().getTimezoneOffset()
458
- ];
459
- try {
460
- const canvas = document.createElement('canvas');
461
- const ctx = canvas.getContext('2d');
462
- ctx.textBaseline = "top";
463
- ctx.font = "14px 'Arial'";
464
- ctx.textBaseline = "alphabetic";
465
- ctx.fillStyle = "#f60";
466
- ctx.fillRect(125, 1, 62, 20);
467
- ctx.fillStyle = "#069";
468
- ctx.fillText("a1b2c3d4e5f6g7h8i9j0_!@#$%^&*()", 2, 15);
469
- components.push(canvas.toDataURL());
470
- } catch (e) { components.push("canvas-error"); }
471
-
472
- const fingerprintString = components.join('~~~');
473
  let hash = 0;
474
- for (let i = 0; i < fingerprintString.length; i++) {
475
- const char = fingerprintString.charCodeAt(i);
476
- hash = ((hash << 5) - hash) + char;
477
  hash |= 0;
478
  }
479
  return 'fp_' + Math.abs(hash).toString(16);
480
  }
481
 
482
- function isUserPaid(userObject) {
483
- return userObject && userObject.isLogin && userObject.accessible_pages &&
484
- (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) ||
485
- userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID)));
486
- }
487
-
488
- function updateBadge(status) {
489
- const badge = document.getElementById('subscription-status-badge');
490
- const upgradeBtn = document.getElementById('upgrade-premium-btn');
491
- if (!badge) return;
492
 
493
- if (status === 'paid') {
494
- badge.textContent = '⭐️ نسخه نامحدود';
495
- badge.className = 'paid-badge';
496
- if(upgradeBtn) upgradeBtn.style.display = 'none';
 
497
  } else {
498
- badge.textContent = 'نسخه رایگان (۵ تصویر روزانه)';
499
- badge.className = 'free-badge';
500
  }
501
- badge.style.display = 'inline-block';
502
  }
503
 
504
- // Function to populate hidden inputs for Gradio
505
- function updateGradioInputs() {
506
- const fpInput = document.querySelector('#user_fingerprint_input textarea');
507
- const statusInput = document.querySelector('#user_status_input textarea');
 
508
 
509
- if (fpInput) {
510
- fpInput.value = userFingerprint;
511
- fpInput.dispatchEvent(new Event('input', { bubbles: true }));
512
- }
513
- if (statusInput) {
514
- statusInput.value = userSubscriptionStatus;
515
- statusInput.dispatchEvent(new Event('input', { bubbles: true }));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
  }
517
  }
518
 
519
- // Listen for messages from Parent App
520
  window.addEventListener('message', (event) => {
521
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
522
  try {
523
  const userObject = JSON.parse(event.data.payload);
524
- userSubscriptionStatus = isUserPaid(userObject) ? 'paid' : 'free';
525
- updateBadge(userSubscriptionStatus);
526
- updateGradioInputs();
 
 
 
 
 
527
  } catch (e) {
528
  console.error("Error parsing user status", e);
529
- updateBadge('free');
 
530
  }
531
  }
532
  });
533
 
534
- // Initialize
535
- (async () => {
536
- userFingerprint = await getBrowserFingerprint();
537
- // Request User Status
538
- window.parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
539
- updateGradioInputs();
540
-
541
- // Setup Upgrade Button Click
542
- const upgradeBtn = document.getElementById('upgrade-premium-btn');
543
- if(upgradeBtn) {
544
- upgradeBtn.addEventListener('click', () => {
545
- window.parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*');
546
- });
 
 
 
 
 
 
547
  }
548
- })();
549
 
550
- // Constant check to ensure hidden inputs are populated before send
551
- setInterval(updateGradioInputs, 1000);
 
 
552
 
553
- // ---------------------------------------------
554
- // 2. Global Utilities (Force Light, Modals, Download)
555
- // ---------------------------------------------
 
 
556
 
 
 
 
 
 
557
  const forceLight = () => {
558
  const body = document.querySelector('body');
559
  if (body) {
@@ -566,67 +403,49 @@ document.addEventListener('DOMContentLoaded', () => {
566
  forceLight();
567
  setInterval(forceLight, 1000);
568
 
569
- window.closeErrorModal = function() {
570
- const modal = document.getElementById('custom-quota-modal');
571
- if (modal) modal.remove();
572
- };
573
-
574
  window.retryGeneration = function() {
575
- window.closeErrorModal();
 
 
 
576
  const runBtn = document.getElementById('run-btn');
577
  if(runBtn) runBtn.click();
578
  };
579
 
580
- // Check for "Limit Reached" error text in output to show Upgrade Button
581
- setInterval(() => {
582
- const errorBoxes = document.querySelectorAll('.prose'); // Where HTML output usually goes
583
- let limitReached = false;
584
- errorBoxes.forEach(box => {
585
- if (box.innerHTML.includes('تعداد مجاز روزانه')) {
586
- limitReached = true;
587
- }
588
- });
589
-
590
- const upgradeBtn = document.getElementById('upgrade-premium-btn');
591
- if (upgradeBtn) {
592
- if (limitReached && userSubscriptionStatus !== 'paid') {
593
- upgradeBtn.style.display = 'block';
594
- } else if (!limitReached && userSubscriptionStatus === 'paid') {
595
- upgradeBtn.style.display = 'none';
596
- }
597
- }
598
- }, 1000);
599
 
600
- // Original Quota/IP Modal Logic (Preserved)
601
  const showQuotaModal = () => {
602
  if (document.getElementById('custom-quota-modal')) return;
 
603
  const modalHtml = `
604
- <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;">
605
  <div class="ip-reset-guide-container">
606
  <div class="guide-header">
607
  <h2>یک قدم تا ساخت تصاویر جدید</h2>
 
608
  </div>
609
  <div class="guide-content">
610
- <div class="info-card"><p>طبق ویدیو آموزشی پایین بین نقطه دستیابی جابجا شوید تلاش مجدد بزنید تا تصاویر مجدداً تولید بشه.</p></div>
611
- <div class="summary-section"><div class="summary-text">از اینترنت سیم‌کارت استفاده کنید، VPN را خاموش کرده و طبق ویدیو آموزشی پایین نقطه دستیابی رو تغییر دهید.</div></div>
612
- <div class="video-button-container">
613
- <button onclick="parent.postMessage({ type: 'NAVIGATE_TO_URL', url: '#/nav/online/news/getSingle/1149635/eyJpdiI6IjhHVGhPQWJwb3E0cjRXbnFWTW5BaUE9PSIsInZhbHVlIjoiS1V0dTdvT21wbXAwSXZaK1RCTG1pVXZqdlFJa1hXV1RKa2FLem9zU3pXMjd5MmlVOGc2YWY0NVdNR3h3Smp1aSIsIm1hYyI6IjY1NTA5ZDYzMjAzMTJhMGQyMWQ4NjA4ZDgyNGZjZDVlY2MyNjdiMjA2NWYzOWRjY2M4ZmVjYWRlMWNlMWQ3ODEiLCJ0YWciOiIifQ==/21135210' }, '*')" class="elegant-video-button">
614
- <span>دیدن ویدیو آموزشی استفاده نامحدود</span>
615
- </button>
616
  </div>
617
  </div>
618
- <div class="guide-actions">
619
- <button class="action-button back-button" onclick="window.closeErrorModal()">بازگشت</button>
620
- <button class="action-button retry-button" onclick="window.retryGeneration()">تلاش مجدد</button>
621
- </div>
622
  </div>
623
- </div>`;
 
624
  document.body.insertAdjacentHTML('beforeend', modalHtml);
625
- setTimeout(() => window.closeErrorModal(), 10000);
626
  };
627
 
 
628
  setInterval(() => {
629
- const potentialErrors = document.querySelectorAll('.toast-body, .error');
630
  potentialErrors.forEach(el => {
631
  const text = el.innerText || "";
632
  if (text.toLowerCase().includes('quota') || text.toLowerCase().includes('exceeded')) {
@@ -634,24 +453,111 @@ document.addEventListener('DOMContentLoaded', () => {
634
  el.style.display = 'none';
635
  }
636
  });
637
- }, 500);
638
  });
 
 
639
 
640
- // Download Function
641
- async function handleDownload(image) {
642
- if (!image) { alert("لطفاً ابتدا تصویر را تولید کنید."); return; }
643
- let fileUrl = image.url;
644
- if (fileUrl && !fileUrl.startsWith('http')) { fileUrl = window.location.origin + fileUrl; }
645
- else if (!fileUrl && image.path) { fileUrl = window.location.origin + "/file=" + image.path; }
646
- window.parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: fileUrl }, '*');
 
 
647
  }
648
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
  """
650
 
 
 
 
651
  # استفاده از gr.Blocks
652
  with gr.Blocks() as demo:
653
  # تزریق کدها به عنوان HTML
654
- gr.HTML(combined_html_js)
655
 
656
  with gr.Column(elem_id="col-container"):
657
  gr.Markdown("# **ویرایشگر هوشمند آلفا**", elem_id="main-title")
@@ -659,14 +565,16 @@ with gr.Blocks() as demo:
659
  "با هوش مصنوعی آلفا تصاویر تونو به مدل های مختلف ویرایش کنید.",
660
  elem_id="main-description"
661
  )
662
-
663
- # --- المان‌های جدید برای نمایش Badge و دکمه ارتقا ---
664
- gr.HTML('<div style="text-align: center;"><div id="subscription-status-badge"></div></div>')
665
- gr.HTML('<button type="button" id="upgrade-premium-btn">⭐️ ارتقا به نسخه کامل و نامحدود</button>')
666
 
667
- # --- کامپوننت‌های مخفی برای انتقال داده JS به Python ---
668
- user_fingerprint = gr.Textbox(elem_id="user_fingerprint_input", visible=False)
669
- user_status = gr.Textbox(elem_id="user_status_input", visible=False, value="free")
 
 
 
 
 
 
670
 
671
  with gr.Row(equal_height=True):
672
  with gr.Column():
@@ -682,6 +590,7 @@ with gr.Blocks() as demo:
682
 
683
  status_box = gr.HTML(label="وضعیت")
684
 
 
685
  run_button = gr.Button("✨ شروع پردازش و ساخت تصویر", variant="primary", elem_classes="primary-btn", elem_id="run-btn")
686
 
687
  with gr.Column():
@@ -719,7 +628,6 @@ with gr.Blocks() as demo:
719
  guidance_scale = gr.Slider(label="میزان وفاداری به متن (Guidance Scale)", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
720
  steps = gr.Slider(label="تعداد مراحل پردازش (Steps)", minimum=1, maximum=50, step=1, value=4)
721
 
722
- # اصلاح تابع نمایش ردیف اسلایدرها
723
  def toggle_row(choice):
724
  if choice == "شخصی‌سازی (Custom)":
725
  return gr.update(visible=True)
@@ -736,9 +644,6 @@ with gr.Blocks() as demo:
736
  ["examples/1.jpg", "تبدیل به انیمه کن.", "تبدیل عکس به انیمه"],
737
  ["examples/5.jpg", "سایه‌ها را حذف کن و نورپردازی نرم به تصویر بده.", "اصلاح نور و سایه"],
738
  ["examples/4.jpg", "از فیلتر ساعت طلایی با پخش نور ملایم استفاده کن.", "نورپردازی مجدد (Relight)"],
739
- ["examples/2.jpeg", "دوربین را ۴۵ درجه به سمت چپ بچرخان.", "تغییر زاویه دید"],
740
- ["examples/7.jpg", "منبع نور را از سمت راست عقب قرار بده.", "نورپردازی چند زاویه‌ای"],
741
- ["examples/10.jpeg", "کیفیت تصویر را افزایش بده (Upscale).", "افزایش کیفیت (Upscale)"],
742
  ],
743
  inputs=[input_image, prompt, lora_adapter],
744
  outputs=[output_image, seed, status_box],
@@ -747,24 +652,20 @@ with gr.Blocks() as demo:
747
  label="نمونه‌ها (برای تست کلیک کنید)"
748
  )
749
 
750
- # اتصال دکمه اجرا با ورودی‌های جدید (fingerprint و status)
751
  run_button.click(
752
  fn=infer,
753
- inputs=[
754
- input_image, prompt, lora_adapter, seed, randomize_seed,
755
- guidance_scale, steps, aspect_ratio_selection,
756
- custom_width, custom_height,
757
- user_fingerprint, user_status # Added these inputs
758
- ],
759
  outputs=[output_image, seed, status_box],
760
- api_name="predict"
 
761
  )
762
 
763
  download_button.click(
764
  fn=None,
765
  inputs=[output_image],
766
  outputs=None,
767
- js="handleDownload"
768
  )
769
 
770
  if __name__ == "__main__":
 
4
  import spaces
5
  import torch
6
  import random
 
 
7
  from PIL import Image, ImageFilter
8
  from typing import Iterable
9
  from gradio.themes import Soft
 
29
 
30
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  # --- بارگذاری مدل تشخیص محتوای نامناسب (NSFW) ---
33
  print("Loading Safety Checker...")
34
  safety_classifier = pipeline("image-classification", model="Falconsai/nsfw_image_detection", device=-1)
 
168
  </div>
169
  """
170
 
 
171
  @spaces.GPU(duration=30)
172
  def infer(
173
  input_image,
 
180
  aspect_ratio_selection,
181
  custom_width,
182
  custom_height,
 
 
183
  progress=gr.Progress(track_tqdm=True)
184
  ):
 
 
 
 
 
 
 
 
 
185
  if input_image is None:
186
  return None, seed, get_error_html("لطفاً ابتدا یک تصویر بارگذاری کنید.")
187
 
 
232
  if is_image_nsfw(result):
233
  return None, seed, get_error_html("تصویر تولید شده حاوی محتوای نامناسب بود و حذف شد.")
234
 
 
 
 
235
  return result, seed, get_success_html("تصویر با موفقیت ویرایش شد.")
236
 
237
  except Exception as e:
 
242
 
243
  @spaces.GPU(duration=30)
244
  def infer_example(input_image, prompt, lora_adapter):
245
+ res, s, status = infer(input_image, prompt, lora_adapter, 0, True, 1.0, 4, "خودکار (پیش‌فرض)", 1024, 1024)
 
246
  return res, s, status
247
 
248
+ # --- جاوااسکریپت برای دکمه دانلود ---
249
+ js_download_func = """
250
+ async (image) => {
251
+ if (!image) {
252
+ alert("لطفاً ابتدا تصویر را تولید کنید.");
253
+ return;
254
+ }
255
+ let fileUrl = image.url;
256
+ if (fileUrl && !fileUrl.startsWith('http')) {
257
+ fileUrl = window.location.origin + fileUrl;
258
+ } else if (!fileUrl && image.path) {
259
+ fileUrl = window.location.origin + "/file=" + image.path;
260
+ }
261
+ window.parent.postMessage({
262
+ type: 'DOWNLOAD_REQUEST',
263
+ url: fileUrl
264
+ }, '*');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  }
266
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
+ # --- منطق جاوااسکریپت برای مدیریت اعتبار و اشتراک ---
269
+ js_credit_logic = """
270
  <script>
271
  document.addEventListener('DOMContentLoaded', () => {
 
 
 
 
272
  const PREMIUM_PAGE_ID = '1149636';
273
  const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTc1NDIyNGVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
274
 
275
+ let isUserPaid = false;
276
+ let dailyCredits = 5;
277
+
278
+ // تابع فینگرپرینت ساده برای شناسایی مرورگر
279
+ function getFingerprint() {
280
+ const str = navigator.userAgent + navigator.language + screen.width + 'x' + screen.height;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  let hash = 0;
282
+ for (let i = 0; i < str.length; i++) {
283
+ hash = ((hash << 5) - hash) + str.charCodeAt(i);
 
284
  hash |= 0;
285
  }
286
  return 'fp_' + Math.abs(hash).toString(16);
287
  }
288
 
289
+ const fingerprint = getFingerprint();
290
+ const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
291
+ const storageKey = `app_credits_${fingerprint}_${today}`;
 
 
 
 
 
 
 
292
 
293
+ function checkCredits() {
294
+ const stored = localStorage.getItem(storageKey);
295
+ if (stored === null) {
296
+ dailyCredits = 5;
297
+ localStorage.setItem(storageKey, 5);
298
  } else {
299
+ dailyCredits = parseInt(stored);
 
300
  }
301
+ updateUI();
302
  }
303
 
304
+ function updateUI() {
305
+ const badgeEl = document.getElementById('sub-badge');
306
+ const creditTextEl = document.getElementById('credit-text');
307
+ const runBtn = document.getElementById('run-btn');
308
+ const upgradeBtn = document.getElementById('upgrade-btn');
309
 
310
+ if (!badgeEl || !runBtn) return;
311
+
312
+ if (isUserPaid) {
313
+ // حالت پولی
314
+ badgeEl.innerHTML = '<span class="paid-badge">✨ نسخه نامحدود</span>';
315
+ creditTextEl.style.display = 'none';
316
+ runBtn.style.display = 'block';
317
+ if(upgradeBtn) upgradeBtn.style.display = 'none';
318
+ } else {
319
+ // حالت رایگان
320
+ badgeEl.innerHTML = '<span class="free-badge">نسخه رایگان</span>';
321
+ creditTextEl.style.display = 'block';
322
+ creditTextEl.innerHTML = `اعتبار باقی‌مانده امروز: <b>${dailyCredits}</b>`;
323
+
324
+ if (dailyCredits > 0) {
325
+ runBtn.style.display = 'block';
326
+ if(upgradeBtn) upgradeBtn.style.display = 'none';
327
+ } else {
328
+ runBtn.style.display = 'none';
329
+ if(upgradeBtn) upgradeBtn.style.display = 'flex';
330
+ creditTextEl.innerHTML = `<span style="color:#ef4444">اعتبار امروز شما تمام شده است!</span>`;
331
+ }
332
  }
333
  }
334
 
335
+ // بررسی وضعیت کاربر از اپلیکیشن
336
  window.addEventListener('message', (event) => {
337
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
338
  try {
339
  const userObject = JSON.parse(event.data.payload);
340
+ if (userObject && userObject.isLogin && userObject.accessible_pages &&
341
+ (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) ||
342
+ userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID)))) {
343
+ isUserPaid = true;
344
+ } else {
345
+ isUserPaid = false;
346
+ }
347
+ updateUI();
348
  } catch (e) {
349
  console.error("Error parsing user status", e);
350
+ isUserPaid = false;
351
+ updateUI();
352
  }
353
  }
354
  });
355
 
356
+ // درخواست وضعیت کاربر هنگام لود
357
+ parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
358
+
359
+ // لود اولیه کردیت
360
+ checkCredits();
361
+
362
+ // تابع سراسری برای کسر اعتبار (قبل از اجرای پایتون)
363
+ window.checkAndDeductCredit = async function() {
364
+ if (isUserPaid) return []; // اگر پولی بود، اجازه بده
365
+
366
+ checkCredits(); // رفرش کردن مقدار
367
+ if (dailyCredits > 0) {
368
+ dailyCredits--;
369
+ localStorage.setItem(storageKey, dailyCredits);
370
+ updateUI();
371
+ return []; // اجازه اجرا
372
+ } else {
373
+ alert("اعتبار امروز شما به پایان رسیده است. لطفاً برای استفاده نامحدود ارتقا دهید.");
374
+ throw new Error("Credit Exceeded"); // جلوگیری از اجرا
375
  }
376
+ };
377
 
378
+ // تابع برای دکمه ارتقا
379
+ window.navigateToPremium = function() {
380
+ parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*');
381
+ };
382
 
383
+ // Force UI Refresh periodically
384
+ setInterval(updateUI, 2000);
385
+ });
386
+ </script>
387
+ """
388
 
389
+ # --- جاوااسکریپت برای مدیریت خطا و تم (کد قبلی شما با کمی اصلاح) ---
390
+ js_global_content = """
391
+ <script>
392
+ document.addEventListener('DOMContentLoaded', () => {
393
+ // 1. Force Light Mode
394
  const forceLight = () => {
395
  const body = document.querySelector('body');
396
  if (body) {
 
403
  forceLight();
404
  setInterval(forceLight, 1000);
405
 
406
+ // 2. RETRY FUNCTION
 
 
 
 
407
  window.retryGeneration = function() {
408
+ const modal = document.getElementById('custom-quota-modal');
409
+ if (modal) modal.remove();
410
+ // تلاش مجدد نباید اعتبار کسر کند یا باید مدیریت شود، اما اینجا دکمه اصلی را کلیک می‌کنیم
411
+ // چون تابع checkAndDeductCredit روی دکمه است، دوباره اعتبار چک می‌شود.
412
  const runBtn = document.getElementById('run-btn');
413
  if(runBtn) runBtn.click();
414
  };
415
 
416
+ // Close function
417
+ window.closeErrorModal = function() {
418
+ const modal = document.getElementById('custom-quota-modal');
419
+ if (modal) modal.remove();
420
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
 
422
+ // 3. SHOW MODAL FUNCTION (Quota Exceeded)
423
  const showQuotaModal = () => {
424
  if (document.getElementById('custom-quota-modal')) return;
425
+
426
  const modalHtml = `
427
+ <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;">
428
  <div class="ip-reset-guide-container">
429
  <div class="guide-header">
430
  <h2>یک قدم تا ساخت تصاویر جدید</h2>
431
+ <p>نیازمند تغییر نقطه دستیابی</p>
432
  </div>
433
  <div class="guide-content">
434
+ <p>لطفاً IP خود را تغییر دهید (خاموش/روشن کردن حالت پرواز) و دوباره تلاش کنید.</p>
435
+ <div class="guide-actions">
436
+ <button class="action-button back-button" onclick="window.closeErrorModal()">بازگشت</button>
437
+ <button class="action-button retry-button" onclick="window.retryGeneration()">تلاش مجدد</button>
 
 
438
  </div>
439
  </div>
 
 
 
 
440
  </div>
441
+ </div>
442
+ `;
443
  document.body.insertAdjacentHTML('beforeend', modalHtml);
 
444
  };
445
 
446
+ // 4. SCANNER
447
  setInterval(() => {
448
+ const potentialErrors = document.querySelectorAll('.toast-body, .error, .toast-wrap, .eta-bar, div[class*="error"]');
449
  potentialErrors.forEach(el => {
450
  const text = el.innerText || "";
451
  if (text.toLowerCase().includes('quota') || text.toLowerCase().includes('exceeded')) {
 
453
  el.style.display = 'none';
454
  }
455
  });
456
+ }, 1000);
457
  });
458
+ </script>
459
+ """
460
 
461
+ # --- CSS Updated ---
462
+ css_code = """
463
+ <style>
464
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
465
+
466
+ :root, .dark, body, .gradio-container {
467
+ --body-background-fill: #f5f7fa !important;
468
+ --body-text-color: #1f2937 !important;
469
+ font-family: 'Vazirmatn', sans-serif !important;
470
  }
471
+
472
+ /* Badge Styles */
473
+ .paid-badge {
474
+ background: linear-gradient(45deg, #FFC107, #ffca2c);
475
+ color: #333;
476
+ padding: 6px 12px;
477
+ border-radius: 20px;
478
+ font-size: 0.9em;
479
+ font-weight: 800;
480
+ box-shadow: 0 4px 10px rgba(255, 193, 7, 0.3);
481
+ display: inline-block;
482
+ }
483
+
484
+ .free-badge {
485
+ background: linear-gradient(45deg, #6c757d, #495057);
486
+ color: white;
487
+ padding: 6px 12px;
488
+ border-radius: 20px;
489
+ font-size: 0.9em;
490
+ font-weight: 700;
491
+ box-shadow: 0 4px 10px rgba(108, 117, 125, 0.3);
492
+ display: inline-block;
493
+ }
494
+
495
+ #subscription-status-container {
496
+ text-align: center;
497
+ margin-bottom: 20px;
498
+ padding: 10px;
499
+ background: #fff;
500
+ border-radius: 12px;
501
+ border: 1px solid #e5e7eb;
502
+ }
503
+
504
+ #credit-text {
505
+ margin-top: 8px;
506
+ color: #4b5563;
507
+ font-size: 0.95em;
508
+ }
509
+
510
+ #upgrade-btn {
511
+ display: none; /* Controlled by JS */
512
+ width: 100%;
513
+ margin-top: 10px;
514
+ padding: 12px;
515
+ font-family: 'Vazirmatn', sans-serif;
516
+ font-size: 1.1em;
517
+ font-weight: 800;
518
+ color: #212529;
519
+ background: linear-gradient(95deg, #FFD54F, #FFC107 100%);
520
+ border: none;
521
+ border-radius: 14px;
522
+ cursor: pointer;
523
+ box-shadow: 0 8px 20px -5px rgba(255, 193, 7, 0.4);
524
+ animation: upgrade-pulse 2.5s infinite;
525
+ justify-content: center;
526
+ align-items: center;
527
+ }
528
+
529
+ @keyframes upgrade-pulse {
530
+ 0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); }
531
+ 70% { transform: scale(1.02); box-shadow: 0 0 0 10px rgba(255, 193, 7, 0); }
532
+ 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); }
533
+ }
534
+
535
+ /* Modal and other existing styles... */
536
+ .ip-reset-guide-container {
537
+ background: white; padding: 20px; border-radius: 16px; width: 90%; max-width: 400px; text-align: right; direction: rtl;
538
+ }
539
+ .action-button { margin: 5px; padding: 10px 20px; border-radius: 8px; cursor: pointer; border: none; }
540
+ .retry-button { background: #667eea; color: white; }
541
+ .back-button { background: #f0f0f0; }
542
+
543
+ #main-title h1 {
544
+ font-size: 2.2em !important;
545
+ text-align: center;
546
+ color: #1a202c !important;
547
+ background: -webkit-linear-gradient(45deg, #2563eb, #1e40af);
548
+ -webkit-background-clip: text;
549
+ -webkit-text-fill-color: transparent;
550
+ }
551
+ </style>
552
  """
553
 
554
+ # ادغام CSS و JS ها
555
+ combined_html = css_code + js_global_content + js_credit_logic
556
+
557
  # استفاده از gr.Blocks
558
  with gr.Blocks() as demo:
559
  # تزریق کدها به عنوان HTML
560
+ gr.HTML(combined_html)
561
 
562
  with gr.Column(elem_id="col-container"):
563
  gr.Markdown("# **ویرایشگر هوشمند آلفا**", elem_id="main-title")
 
565
  "با هوش مصنوعی آلفا تصاویر تونو به مدل های مختلف ویرایش کنید.",
566
  elem_id="main-description"
567
  )
 
 
 
 
568
 
569
+ # --- بخش وضعیت اشتراک و اعتبار ---
570
+ gr.HTML("""
571
+ <div id="subscription-status-container">
572
+ <div id="sub-badge"></div>
573
+ <div id="credit-text">در حال بررسی اعتبار...</div>
574
+ <button id="upgrade-btn" onclick="window.navigateToPremium()">⭐️ ارتقا به نسخه کامل و نامحدود</button>
575
+ </div>
576
+ """)
577
+ # ------------------------------------
578
 
579
  with gr.Row(equal_height=True):
580
  with gr.Column():
 
590
 
591
  status_box = gr.HTML(label="وضعیت")
592
 
593
+ # دکمه اصلی پردازش (elem_id="run-btn" مهم است)
594
  run_button = gr.Button("✨ شروع پردازش و ساخت تصویر", variant="primary", elem_classes="primary-btn", elem_id="run-btn")
595
 
596
  with gr.Column():
 
628
  guidance_scale = gr.Slider(label="میزان وفاداری به متن (Guidance Scale)", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
629
  steps = gr.Slider(label="تعداد مراحل پردازش (Steps)", minimum=1, maximum=50, step=1, value=4)
630
 
 
631
  def toggle_row(choice):
632
  if choice == "شخصی‌سازی (Custom)":
633
  return gr.update(visible=True)
 
644
  ["examples/1.jpg", "تبدیل به انیمه کن.", "تبدیل عکس به انیمه"],
645
  ["examples/5.jpg", "سایه‌ها را حذف کن و نورپردازی نرم به تصویر بده.", "اصلاح نور و سایه"],
646
  ["examples/4.jpg", "از فیلتر ساعت طلایی با پخش نور ملایم استفاده کن.", "نورپردازی مجدد (Relight)"],
 
 
 
647
  ],
648
  inputs=[input_image, prompt, lora_adapter],
649
  outputs=[output_image, seed, status_box],
 
652
  label="نمونه‌ها (برای تست کلیک کنید)"
653
  )
654
 
655
+ # اتصال دکمه با تابع JS برای بررسی اعتبار قبل از اجرا
656
  run_button.click(
657
  fn=infer,
658
+ inputs=[input_image, prompt, lora_adapter, seed, randomize_seed, guidance_scale, steps, aspect_ratio_selection, custom_width, custom_height],
 
 
 
 
 
659
  outputs=[output_image, seed, status_box],
660
+ api_name="predict",
661
+ js="window.checkAndDeductCredit" # این خط مهم است: ابتدا JS اجرا می‌شود
662
  )
663
 
664
  download_button.click(
665
  fn=None,
666
  inputs=[output_image],
667
  outputs=None,
668
+ js=js_download_func
669
  )
670
 
671
  if __name__ == "__main__":