Opera8 commited on
Commit
f91438e
·
verified ·
1 Parent(s): 582db3a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +341 -389
app.py CHANGED
@@ -4,14 +4,14 @@ import numpy as np
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
10
  from gradio.themes.utils import colors, fonts, sizes
11
  from deep_translator import GoogleTranslator
12
  from transformers import pipeline
13
- import json
14
- from datetime import datetime
15
 
16
  # --- تعریف تم ---
17
  colors.steel_blue = colors.Color(
@@ -31,6 +31,58 @@ 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)
@@ -170,6 +222,7 @@ def get_success_html(message):
170
  </div>
171
  """
172
 
 
173
  @spaces.GPU(duration=30)
174
  def infer(
175
  input_image,
@@ -182,8 +235,19 @@ def infer(
182
  aspect_ratio_selection,
183
  custom_width,
184
  custom_height,
 
 
185
  progress=gr.Progress(track_tqdm=True)
186
  ):
 
 
 
 
 
 
 
 
 
187
  if input_image is None:
188
  return None, seed, get_error_html("لطفاً ابتدا یک تصویر بارگذاری کنید.")
189
 
@@ -234,6 +298,9 @@ def infer(
234
  if is_image_nsfw(result):
235
  return None, seed, get_error_html("تصویر تولید شده حاوی محتوای نامناسب بود و حذف شد.")
236
 
 
 
 
237
  return result, seed, get_success_html("تصویر با موفقیت ویرایش شد.")
238
 
239
  except Exception as e:
@@ -244,385 +311,249 @@ def infer(
244
 
245
  @spaces.GPU(duration=30)
246
  def infer_example(input_image, prompt, lora_adapter):
247
- res, s, status = infer(input_image, prompt, lora_adapter, 0, True, 1.0, 4, "خودکار (پیش‌فرض)", 1024, 1024)
 
248
  return res, s, status
249
 
250
- # --- جاوااسکریپت برای دکمه دانلود ---
251
- js_download_func = """
252
- async (image) => {
253
- if (!image) {
254
- alert("لطفاً ابتدا تصویر را تولید کنید.");
255
- return;
256
- }
257
- let fileUrl = image.url;
258
- if (fileUrl && !fileUrl.startsWith('http')) {
259
- fileUrl = window.location.origin + fileUrl;
260
- } else if (!fileUrl && image.path) {
261
- fileUrl = window.location.origin + "/file=" + image.path;
262
- }
263
- window.parent.postMessage({
264
- type: 'DOWNLOAD_REQUEST',
265
- url: fileUrl
266
- }, '*');
267
- }
268
- """
269
-
270
- # --- جاوااسکریپت مدیریت کاربر (رایگان/پولی) و محدودیت‌ها ---
271
- js_user_auth_logic = """
272
- <script>
273
- // --- تنظیمات پریمیوم ---
274
- const PREMIUM_PAGE_ID = '1149636';
275
- const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTc1NDIyNGVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
276
- const DAILY_LIMIT = 5;
277
-
278
- // --- متغیرهای وضعیت ---
279
- window.userSubscriptionStatus = 'free'; // پیش‌فرض رایگان
280
- window.userFingerprint = null;
281
-
282
- // --- ایجاد فینگرپرینت ---
283
- async function getBrowserFingerprint() {
284
- const components = [navigator.userAgent, navigator.language, screen.width + 'x' + screen.height, new Date().getTimezoneOffset()];
285
- try {
286
- const canvas = document.createElement('canvas');
287
- const ctx = canvas.getContext('2d');
288
- ctx.textBaseline = "top";
289
- ctx.font = "14px 'Arial'";
290
- ctx.textBaseline = "alphabetic";
291
- ctx.fillStyle = "#f60";
292
- ctx.fillRect(125, 1, 62, 20);
293
- ctx.fillStyle = "#069";
294
- ctx.fillText("a1b2c3d4e5f6g7h8i9j0_!@#$%^&*()", 2, 15);
295
- components.push(canvas.toDataURL());
296
- } catch (e) {
297
- components.push("canvas-error");
298
- }
299
- const fingerprintString = components.join('~~~');
300
- let hash = 0;
301
- for (let i = 0; i < fingerprintString.length; i++) {
302
- const char = fingerprintString.charCodeAt(i);
303
- hash = ((hash << 5) - hash) + char;
304
- hash |= 0;
305
- }
306
- return 'fp_' + Math.abs(hash).toString(16);
307
- }
308
-
309
- // --- بررسی اینکه آیا کاربر پولی است ---
310
- function isUserPaid(userObject) {
311
- return userObject && userObject.isLogin && userObject.accessible_pages &&
312
- (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) || userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID)));
313
- }
314
-
315
- // --- به‌روزرسانی UI بر اساس وضعیت کاربر ---
316
- function updateUIForSubscriptionStatus(status) {
317
- window.userSubscriptionStatus = status;
318
-
319
- const badge = document.getElementById('subscription-status-badge');
320
- const creditInfo = document.getElementById('credit-info');
321
- const runBtn = document.getElementById('run-btn');
322
- const upgradeBtn = document.getElementById('upgrade-premium-btn');
323
-
324
- if (status === 'paid') {
325
- if(badge) {
326
- badge.textContent = '⭐️ نسخه نامحدود';
327
- badge.className = 'paid-badge';
328
- badge.style.display = 'inline-block';
329
- }
330
- if(creditInfo) creditInfo.style.display = 'none';
331
-
332
- // کاربر پولی همیشه دکمه فعال دارد
333
- if(runBtn) runBtn.disabled = false;
334
- if(upgradeBtn) upgradeBtn.style.display = 'none';
335
-
336
- } else {
337
- if(badge) {
338
- badge.textContent = 'نسخه رایگان';
339
- badge.className = 'free-badge';
340
- badge.style.display = 'inline-block';
341
- }
342
- // بررسی اعتبار برای کاربر رایگان
343
- checkFreeUserCredit();
344
- }
345
- }
346
-
347
- // --- مدیریت اعتبار کاربر رایگان (ذخیره در LocalStorage) ---
348
- function checkFreeUserCredit() {
349
- if (window.userSubscriptionStatus === 'paid') return;
350
-
351
- const today = new Date().toLocaleDateString();
352
- const storageKey = 'alpha_edit_usage';
353
- let data = JSON.parse(localStorage.getItem(storageKey) || '{}');
354
-
355
- // اگر تاریخ عوض شده، ریست کن
356
- if (data.date !== today) {
357
- data = { date: today, count: 0 };
358
- localStorage.setItem(storageKey, JSON.stringify(data));
359
- }
360
-
361
- const used = data.count;
362
- const remaining = DAILY_LIMIT - used;
363
-
364
- const creditInfo = document.getElementById('credit-info');
365
- if(creditInfo) {
366
- creditInfo.style.display = 'block';
367
- if (remaining > 0) {
368
- creditInfo.innerHTML = `اعتبار امروز: <b>${remaining}</b> تصویر دیگر`;
369
- creditInfo.style.color = '#4b5563';
370
- } else {
371
- creditInfo.innerHTML = `<span style="color:#ef4444">اعتبار امروز شما تمام شده است.</span>`;
372
- }
373
- }
374
-
375
- const runBtn = document.getElementById('run-btn');
376
- const upgradeBtn = document.getElementById('upgrade-premium-btn');
377
-
378
- if (used >= DAILY_LIMIT) {
379
- if(runBtn) {
380
- runBtn.disabled = true;
381
- runBtn.innerText = "⛔ اعتبار روزانه تمام شد";
382
- runBtn.style.opacity = "0.6";
383
- runBtn.style.cursor = "not-allowed";
384
- }
385
- if(upgradeBtn) {
386
- upgradeBtn.style.display = 'block'; // نمایش دکمه ارتقا
387
- }
388
- } else {
389
- if(runBtn) {
390
- runBtn.disabled = false;
391
- runBtn.innerText = "✨ شروع پردازش و ساخت تصویر";
392
- runBtn.style.opacity = "1";
393
- runBtn.style.cursor = "pointer";
394
- }
395
- if(upgradeBtn) upgradeBtn.style.display = 'none';
396
- }
397
- }
398
-
399
- // --- تابع کسر اعتبار (قبل از اجرای پایتون صدا زده می‌شود) ---
400
- async function deductCreditAndCheck() {
401
- if (window.userSubscriptionStatus === 'paid') return true;
402
-
403
- const today = new Date().toLocaleDateString();
404
- const storageKey = 'alpha_edit_usage';
405
- let data = JSON.parse(localStorage.getItem(storageKey) || '{}');
406
-
407
- if (data.date !== today) {
408
- data = { date: today, count: 0 };
409
- }
410
-
411
- if (data.count >= DAILY_LIMIT) {
412
- alert("اعتبار روزانه شما تمام شده است. لطفاً نسخه نامحدود را تهیه کنید.");
413
- throw new Error("Credit limit reached"); // توقف اجرا
414
- }
415
-
416
- // افزایش کنتور
417
- data.count++;
418
- localStorage.setItem(storageKey, JSON.stringify(data));
419
- checkFreeUserCredit(); // بروزرسانی UI
420
- return true;
421
- }
422
 
423
- // --- هندلر ارتقا ---
424
- window.navigateToPremium = function() {
425
- parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*');
426
- }
427
-
428
- // --- شروع و لیسنرها ---
429
- document.addEventListener('DOMContentLoaded', async () => {
430
- window.userFingerprint = await getBrowserFingerprint();
431
-
432
- // درخواست وضعیت از اندروید
433
- parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
434
-
435
- window.addEventListener('message', (event) => {
436
- if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
437
- if (event.data.error || !event.data.payload) {
438
- updateUIForSubscriptionStatus('free');
439
- return;
440
- }
441
- try {
442
- const userObject = JSON.parse(event.data.payload);
443
- const status = isUserPaid(userObject) ? 'paid' : 'free';
444
- updateUIForSubscriptionStatus(status);
445
- } catch (e) {
446
- updateUIForSubscriptionStatus('free');
447
- }
448
- }
449
- });
450
-
451
- // بررسی اولیه
452
- setTimeout(() => {
453
- if(window.userSubscriptionStatus === 'free') checkFreeUserCredit();
454
- }, 1000);
455
- });
456
- </script>
457
- """
458
-
459
- # --- CSS Updated (With Badges & Upgrade Button) ---
460
- css_code = """
461
  <style>
462
- @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');
463
 
464
  :root, .dark, body, .gradio-container {
465
  --body-background-fill: #f5f7fa !important;
466
  --body-text-color: #1f2937 !important;
467
- font-family: 'Vazirmatn', sans-serif !important;
 
 
 
 
 
 
468
  }
469
 
470
- /* Badges */
 
 
 
 
471
  #subscription-status-badge {
472
- display: none; /* توسط JS کنترل می‌شود */
473
  padding: 6px 16px;
474
  border-radius: 20px;
475
  font-size: 0.9em;
476
  font-weight: 700;
477
- margin-top: 10px;
 
478
  letter-spacing: 0.5px;
479
  text-shadow: 0 1px 2px rgba(0,0,0,0.1);
480
- animation: fadeIn 0.5s ease;
 
 
481
  }
482
-
483
- .free-badge {
484
  background: linear-gradient(45deg, #6c757d, #495057);
485
  color: white;
486
  box-shadow: 0 4px 10px rgba(108, 117, 125, 0.3);
487
  }
488
-
489
- .paid-badge {
490
- background: linear-gradient(45deg, #FFC107, #ffca2c);
491
  color: #333;
492
- box-shadow: 0 4px 10px rgba(255, 193, 7, 0.4);
493
  }
494
 
495
- @keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
496
-
497
- /* Upgrade Button */
498
- @keyframes upgrade-pulse {
499
- 0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); }
500
- 70% { transform: scale(1.02); box-shadow: 0 0 0 10px rgba(255, 193, 7, 0); }
501
- 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); }
502
  }
503
-
504
  #upgrade-premium-btn {
505
- background: linear-gradient(95deg, #FFD54F, #FFC107 100%) !important;
506
- color: #212529 !important;
507
- font-weight: 800 !important;
508
- border: none !important;
509
- font-size: 1.1em !important;
510
- padding: 12px !important;
511
- border-radius: 12px !important;
512
- margin-top: 15px !important;
513
  width: 100%;
514
- animation: upgrade-pulse 2s infinite;
515
- cursor: pointer !important;
516
- display: none; /* پیش‌فرض مخفی */
 
 
 
 
 
 
 
 
 
 
 
517
  }
518
  #upgrade-premium-btn:hover {
519
- transform: translateY(-2px);
520
- box-shadow: 0 10px 20px -5px rgba(255, 193, 7, 0.5);
521
- animation: none;
522
  }
523
 
524
- /* Other Styles */
525
- #col-container {
526
- margin: 0 auto;
527
- max-width: 980px;
528
- direction: rtl;
529
- text-align: right;
530
- padding: 30px;
531
- background: #ffffff !important;
532
- border-radius: 24px;
533
- box-shadow: 0 10px 40px -10px rgba(0,0,0,0.08);
534
- border: 1px solid rgba(255,255,255,0.8);
535
  }
536
 
537
- #main-title h1 {
538
- font-size: 2.4em !important;
539
- text-align: center;
540
- color: #1a202c !important;
541
- margin-bottom: 5px;
542
- font-weight: 800;
543
- background: -webkit-linear-gradient(45deg, #2563eb, #1e40af);
544
- -webkit-background-clip: text;
545
- -webkit-text-fill-color: transparent;
 
 
 
546
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
 
548
- #main-description {
549
- text-align: center;
550
- font-size: 1.15em;
551
- color: #4b5563 !important;
552
- margin-bottom: 10px;
553
- line-height: 1.6;
554
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
555
 
556
- /* Center badge container */
557
- .badge-container {
558
- display: flex;
559
- flex-direction: column;
560
- align-items: center;
561
- justify-content: center;
562
- margin-bottom: 30px;
563
- }
564
 
565
- textarea, input[type="text"] {
566
- border: 2px solid #e2e8f0 !important;
567
- border-radius: 12px !important;
568
- background-color: #ffffff !important;
569
- color: #111827 !important;
570
- padding: 12px !important;
571
- font-family: 'Vazirmatn', sans-serif !important;
572
- }
573
 
574
- .primary-btn, button.primary {
575
- background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
576
- border: none !important;
577
- color: white !important;
578
- font-weight: 700 !important;
579
- font-size: 1.1em !important;
580
- padding: 14px 28px !important;
581
- border-radius: 14px !important;
582
- box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3) !important;
583
- width: 100%;
584
- }
585
 
586
- .primary-btn:disabled {
587
- background: #9ca3af !important;
588
- box-shadow: none !important;
589
- cursor: not-allowed !important;
590
- }
 
 
 
 
 
 
 
 
 
591
 
592
- #download-btn {
593
- background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
594
- }
 
 
 
 
 
 
 
 
 
 
 
595
 
596
- footer { display: none !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
 
598
- /* IP Guide Styles (Keep existing) */
599
- .ip-reset-guide-container {
600
- text-align: right; direction: rtl; background: rgba(255, 255, 255, 0.98);
601
- backdrop-filter: blur(10px); padding: 20px; border-radius: 16px;
602
- box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1); border: 1px solid rgba(100,100,200,0.2);
603
- width: 90%; max-width: 420px; font-family: 'Vazirmatn', sans-serif;
604
- }
605
- .guide-header { display: flex; align-items: center; margin-bottom: 15px; }
606
- .guide-header h2 { font-size: 1.2rem; margin: 0; color: #2d3748; }
607
- .elegant-video-button {
608
- display: inline-flex; align-items: center; justify-content: center; padding: 10px 20px;
609
- background: white; color: #667eea; border: 1px solid #e2e8f0; border-radius: 50px; cursor: pointer;
610
- font-family: inherit; font-weight: 600; margin-top: 10px;
611
- }
612
- .action-button {
613
- padding: 10px; border-radius: 12px; border: none; cursor: pointer; flex: 1; font-family: inherit; font-weight: 600;
614
- }
615
- .retry-button { background: linear-gradient(135deg, #667eea, #764ba2); color: white; }
616
- .back-button { background: white; border: 1px solid #e2e8f0; }
617
- </style>
618
- """
619
 
620
- # جاوااسکریپت سراسری (شامل راهنمای IP و استایل‌ها)
621
- # توجه: ما کدهای جدید Auth را هم به این بخش اضافه می‌کنیم تا یکجا اینجکت شود
622
- js_global_combined = js_user_auth_logic + """
623
- <script>
624
- document.addEventListener('DOMContentLoaded', () => {
625
- // 1. Force Light Mode
626
  const forceLight = () => {
627
  const body = document.querySelector('body');
628
  if (body) {
@@ -630,51 +561,72 @@ document.addEventListener('DOMContentLoaded', () => {
630
  body.style.backgroundColor = '#f5f7fa';
631
  body.style.color = '#333333';
632
  }
 
633
  };
634
  forceLight();
635
  setInterval(forceLight, 1000);
636
 
637
- // 2. IP Reset Guide Functions
 
 
 
 
638
  window.retryGeneration = function() {
639
- const modal = document.getElementById('custom-quota-modal');
640
- if (modal) modal.remove();
641
  const runBtn = document.getElementById('run-btn');
642
  if(runBtn) runBtn.click();
643
  };
644
 
645
- window.closeErrorModal = function() {
646
- const modal = document.getElementById('custom-quota-modal');
647
- if (modal) modal.remove();
648
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
 
 
650
  const showQuotaModal = () => {
651
  if (document.getElementById('custom-quota-modal')) return;
652
  const modalHtml = `
653
- <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;">
654
  <div class="ip-reset-guide-container">
655
  <div class="guide-header">
656
- <div>
657
- <h2>یک قدم تا ساخت تصاویر جدید</h2>
658
- <p>نیازمند تغییر نقطه دستیابی</p>
659
- </div>
660
  </div>
661
  <div class="guide-content">
662
- <p>برای استفاده نامحدود، اینترنت را خاموش/روشن کنید یا IP را تغییر دهید.</p>
663
- <div style="display:flex; gap:10px; margin-top:20px;">
664
- <button class="action-button back-button" onclick="window.closeErrorModal()">بازگشت</button>
665
- <button class="action-button retry-button" onclick="window.retryGeneration()">تلاش مجدد</button>
 
 
666
  </div>
667
  </div>
 
 
 
 
668
  </div>
669
- </div>
670
- `;
671
  document.body.insertAdjacentHTML('beforeend', modalHtml);
672
  setTimeout(() => window.closeErrorModal(), 10000);
673
  };
674
 
675
- // 3. Scanner for Quota Errors
676
  setInterval(() => {
677
- const potentialErrors = document.querySelectorAll('.toast-body, .error, div[class*="error"]');
678
  potentialErrors.forEach(el => {
679
  const text = el.innerText || "";
680
  if (text.toLowerCase().includes('quota') || text.toLowerCase().includes('exceeded')) {
@@ -682,18 +634,24 @@ document.addEventListener('DOMContentLoaded', () => {
682
  el.style.display = 'none';
683
  }
684
  });
685
- }, 100);
686
  });
 
 
 
 
 
 
 
 
 
687
  </script>
688
  """
689
 
690
- # ادغام نهایی
691
- combined_html = css_code + js_global_combined
692
-
693
  # استفاده از gr.Blocks
694
  with gr.Blocks() as demo:
695
  # تزریق کدها به عنوان HTML
696
- gr.HTML(combined_html)
697
 
698
  with gr.Column(elem_id="col-container"):
699
  gr.Markdown("# **ویرایشگر هوشمند آلفا**", elem_id="main-title")
@@ -702,13 +660,13 @@ with gr.Blocks() as demo:
702
  elem_id="main-description"
703
  )
704
 
705
- # --- بخش جدید: بج و اطلاعات اعتبار ---
706
- gr.HTML("""
707
- <div class="badge-container">
708
- <div id="subscription-status-badge"></div>
709
- <div id="credit-info" style="font-size: 0.9em; margin-top: 8px; color: #666; display:none;"></div>
710
- </div>
711
- """)
712
 
713
  with gr.Row(equal_height=True):
714
  with gr.Column():
@@ -724,11 +682,7 @@ with gr.Blocks() as demo:
724
 
725
  status_box = gr.HTML(label="وضعیت")
726
 
727
- # دکمه اجرا (با شناسه برای JS)
728
  run_button = gr.Button("✨ شروع پردازش و ساخت تصویر", variant="primary", elem_classes="primary-btn", elem_id="run-btn")
729
-
730
- # دکمه ارتقا (مخفی، توسط JS کنترل می‌شود)
731
- upgrade_button = gr.Button("⭐️ ارتقا به نسخه کامل و نامحدود", visible=True, elem_id="upgrade-premium-btn")
732
 
733
  with gr.Column():
734
  output_image = gr.Image(label="تصویر نهایی", interactive=False, format="png", height=380)
@@ -765,6 +719,7 @@ with gr.Blocks() as demo:
765
  guidance_scale = gr.Slider(label="میزان وفاداری به متن (Guidance Scale)", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
766
  steps = gr.Slider(label="تعداد مراحل پردازش (Steps)", minimum=1, maximum=50, step=1, value=4)
767
 
 
768
  def toggle_row(choice):
769
  if choice == "شخصی‌سازی (Custom)":
770
  return gr.update(visible=True)
@@ -781,38 +736,35 @@ with gr.Blocks() as demo:
781
  ["examples/1.jpg", "تبدیل به انیمه کن.", "تبدیل عکس به انیمه"],
782
  ["examples/5.jpg", "سایه‌ها را حذف کن و نورپردازی نرم به تصویر بده.", "اصلاح نور و سایه"],
783
  ["examples/4.jpg", "از فیلتر ساعت طلایی با پخش نور ملایم استفاده کن.", "نورپردازی مجدد (Relight)"],
 
 
 
784
  ],
785
  inputs=[input_image, prompt, lora_adapter],
786
  outputs=[output_image, seed, status_box],
787
  fn=infer_example,
788
  cache_examples=False,
789
- label="نمونه‌ها"
790
  )
791
 
792
- # --- اتصال رویدادها ---
793
-
794
- # دکمه اجرا: اول JS اجرا می‌شود (کسر اعتبار)، اگر موفق بود پایتون اجرا می‌شود
795
  run_button.click(
796
  fn=infer,
797
- inputs=[input_image, prompt, lora_adapter, seed, randomize_seed, guidance_scale, steps, aspect_ratio_selection, custom_width, custom_height],
 
 
 
 
 
798
  outputs=[output_image, seed, status_box],
799
- api_name="predict",
800
- js="deductCreditAndCheck" # این تابع JS قبل از پایتون اجرا می‌شود
801
- )
802
-
803
- # دکمه ارتقا: فقط JS اجرا می‌شود (پیام به اندروید)
804
- upgrade_button.click(
805
- fn=None,
806
- inputs=None,
807
- outputs=None,
808
- js="navigateToPremium"
809
  )
810
 
811
  download_button.click(
812
  fn=None,
813
  inputs=[output_image],
814
  outputs=None,
815
- js=js_download_func
816
  )
817
 
818
  if __name__ == "__main__":
 
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
12
  from gradio.themes.utils import colors, fonts, sizes
13
  from deep_translator import GoogleTranslator
14
  from transformers import pipeline
 
 
15
 
16
  # --- تعریف تم ---
17
  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
  </div>
223
  """
224
 
225
+ # --- تابع اصلی پردازش (به‌روز شده با سیستم Quota) ---
226
  @spaces.GPU(duration=30)
227
  def infer(
228
  input_image,
 
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
  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
 
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) {
 
561
  body.style.backgroundColor = '#f5f7fa';
562
  body.style.color = '#333333';
563
  }
564
+ document.querySelectorAll('.dark').forEach(el => el.classList.remove('dark'));
565
  };
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
  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")
 
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
 
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():
688
  output_image = gr.Image(label="تصویر نهایی", interactive=False, format="png", height=380)
 
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
  ["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],
745
  fn=infer_example,
746
  cache_examples=False,
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__":