Beepeen24 commited on
Commit
04b8216
·
verified ·
1 Parent(s): 319ea49

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +222 -51
app.py CHANGED
@@ -6,11 +6,13 @@ import os
6
  import time
7
  import secrets
8
  import json
 
 
9
 
10
  import spaces
11
 
12
  import PIL
13
- from PIL import Image
14
  from typing import Tuple
15
 
16
  import diffusers
@@ -91,6 +93,109 @@ def verify_license(license_key):
91
  valid_licenses = load_licenses()
92
  return license_upper in valid_licenses
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  # Initialize licenses on first run
95
  VALID_LICENSES = load_licenses()
96
  print(f"✅ License system initialized. Admin keys: {ADMIN_KEYS}")
@@ -278,7 +383,7 @@ def save_as_png(image, filename="professional_headshot"):
278
  @spaces.GPU
279
  def generate_image(
280
  face_image_path,
281
- license_key, # NEW: License key parameter
282
  prompt,
283
  negative_prompt,
284
  style_name,
@@ -296,15 +401,27 @@ def generate_image(
296
  progress=gr.Progress(track_tqdm=True),
297
  ):
298
 
299
- # ===== LICENSE VERIFICATION =====
300
- if not verify_license(license_key):
301
- raise gr.Error("""❌ Invalid or missing license key.
302
-
303
- Please check your email for the license key sent after purchase.
304
- If you haven't purchased yet, visit:
305
- https://canadianheadshotpro.carrd.co
306
-
307
- Need help? Email: bee.tools@zohomailcloud.ca""")
 
 
 
 
 
 
 
 
 
 
 
 
308
 
309
  if enable_LCM:
310
  pipe.scheduler = diffusers.LCMScheduler.from_config(pipe.scheduler.config)
@@ -393,7 +510,21 @@ Need help? Email: bee.tools@zohomailcloud.ca""")
393
  generator=generator,
394
  ).images
395
 
396
- png_filepath = save_as_png(images[0])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  return png_filepath, gr.update(visible=True)
398
 
399
  # MODERN PROFESSIONAL UI CSS
@@ -413,13 +544,11 @@ css = """
413
  --border: #e2e8f0;
414
  --shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
415
  }
416
-
417
  .gradio-container {
418
  max-width: 1400px !important;
419
  font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
420
  background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%) !important;
421
  }
422
-
423
  .main-container {
424
  background: white;
425
  border-radius: 24px;
@@ -428,7 +557,6 @@ css = """
428
  overflow: hidden;
429
  border: 1px solid var(--border);
430
  }
431
-
432
  .hero-section {
433
  background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
434
  color: white;
@@ -437,7 +565,6 @@ css = """
437
  position: relative;
438
  overflow: hidden;
439
  }
440
-
441
  .hero-section::before {
442
  content: '';
443
  position: absolute;
@@ -448,7 +575,6 @@ css = """
448
  background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 100" fill="rgba(255,255,255,0.1)"><polygon points="0,0 1000,100 0,100"/></svg>');
449
  background-size: cover;
450
  }
451
-
452
  .hero-title {
453
  font-size: 3em;
454
  font-weight: 800;
@@ -458,7 +584,6 @@ css = """
458
  -webkit-text-fill-color: transparent;
459
  background-clip: text;
460
  }
461
-
462
  .hero-subtitle {
463
  font-size: 1.3em;
464
  font-weight: 400;
@@ -466,7 +591,6 @@ css = """
466
  max-width: 600px;
467
  margin: 0 auto;
468
  }
469
-
470
  .upload-area {
471
  border: 3px dashed var(--border);
472
  border-radius: 20px;
@@ -476,19 +600,16 @@ css = """
476
  transition: all 0.3s ease;
477
  cursor: pointer;
478
  }
479
-
480
  .upload-area:hover {
481
  border-color: var(--primary);
482
  background: #f0f9ff;
483
  transform: translateY(-2px);
484
  }
485
-
486
  .upload-icon {
487
  font-size: 3em;
488
  margin-bottom: 16px;
489
  color: var(--primary);
490
  }
491
-
492
  .control-card {
493
  background: var(--surface);
494
  border-radius: 16px;
@@ -498,18 +619,15 @@ css = """
498
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
499
  transition: all 0.3s ease;
500
  }
501
-
502
  .control-card:hover {
503
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
504
  transform: translateY(-1px);
505
  }
506
-
507
  .control-header {
508
  display: flex;
509
  align-items: center;
510
  margin-bottom: 16px;
511
  }
512
-
513
  .control-icon {
514
  width: 40px;
515
  height: 40px;
@@ -522,14 +640,12 @@ css = """
522
  color: white;
523
  font-weight: 600;
524
  }
525
-
526
  .control-title {
527
  font-size: 1.2em;
528
  font-weight: 600;
529
  color: var(--text-primary);
530
  margin: 0;
531
  }
532
-
533
  .result-card {
534
  background: var(--surface);
535
  border-radius: 20px;
@@ -538,24 +654,20 @@ css = """
538
  box-shadow: var(--shadow);
539
  height: 100%;
540
  }
541
-
542
  .result-header {
543
  text-align: center;
544
  margin-bottom: 24px;
545
  }
546
-
547
  .result-title {
548
  font-size: 1.5em;
549
  font-weight: 700;
550
  color: var(--text-primary);
551
  margin-bottom: 8px;
552
  }
553
-
554
  .result-subtitle {
555
  color: var(--text-secondary);
556
  font-size: 0.95em;
557
  }
558
-
559
  .image-container {
560
  border-radius: 16px;
561
  overflow: hidden;
@@ -563,7 +675,6 @@ css = """
563
  border: 1px solid var(--border);
564
  margin-bottom: 20px;
565
  }
566
-
567
  .success-banner {
568
  background: linear-gradient(135deg, var(--success), #059669);
569
  color: white;
@@ -572,7 +683,6 @@ css = """
572
  margin-top: 20px;
573
  text-align: center;
574
  }
575
-
576
  .btn-primary {
577
  background: linear-gradient(135deg, var(--primary), var(--primary-dark)) !important;
578
  color: white !important;
@@ -584,23 +694,19 @@ css = """
584
  transition: all 0.3s ease !important;
585
  box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3) !important;
586
  }
587
-
588
  .btn-primary:hover {
589
  transform: translateY(-2px) !important;
590
  box-shadow: 0 6px 20px rgba(37, 99, 235, 0.4) !important;
591
  }
592
-
593
  .slider-container {
594
  padding: 8px 0;
595
  }
596
-
597
  .slider-label {
598
  display: flex;
599
  justify-content: space-between;
600
  align-items: center;
601
  margin-bottom: 8px;
602
  }
603
-
604
  .slider-value {
605
  background: var(--primary);
606
  color: white;
@@ -609,7 +715,6 @@ css = """
609
  font-size: 0.85em;
610
  font-weight: 600;
611
  }
612
-
613
  .tips-card {
614
  background: linear-gradient(135deg, #fef3c7, #f59e0b);
615
  border: none;
@@ -617,29 +722,48 @@ css = """
617
  padding: 20px;
618
  margin-bottom: 20px;
619
  }
620
-
621
  .tips-header {
622
  display: flex;
623
  align-items: center;
624
  margin-bottom: 12px;
625
  }
626
-
627
  .tips-icon {
628
  font-size: 1.5em;
629
  margin-right: 12px;
630
  }
631
-
632
  .progress-container {
633
  margin: 20px 0;
634
  text-align: center;
635
  }
636
-
637
  .progress-text {
638
  font-size: 0.9em;
639
  color: var(--text-secondary);
640
  margin-top: 8px;
641
  }
642
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
643
  /* Responsive design */
644
  @media (max-width: 768px) {
645
  .hero-title {
@@ -697,24 +821,36 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
697
  elem_classes="upload-area"
698
  )
699
 
700
- # License Key Section - NEW
701
  with gr.Column(elem_classes="control-card"):
702
  gr.HTML("""
703
  <div class="control-header">
704
  <div class="control-icon">🔑</div>
705
- <h3 class="control-title">License Verification</h3>
 
 
 
 
 
 
 
 
706
  </div>
707
  """)
 
708
  license_input = gr.Textbox(
709
  label="",
710
- placeholder="Enter your license key (e.g., HEADSHOT-A1B2C3D4E5F6)",
711
  show_label=False,
712
- info="💡 Your license key was emailed to you after purchase"
713
  )
714
- gr.HTML("""
 
715
  <div style="font-size: 0.85em; color: var(--text-secondary); margin-top: 8px;">
716
- <strong>Test Keys:</strong> HEADSHOT-TEST123456, HEADSHOT-OWNERACCESS
717
- <br>Don't have a license? <a href="https://canadianheadshotpro.carrd.co" target="_blank">Purchase access here</a>
 
 
718
  </div>
719
  """)
720
 
@@ -917,11 +1053,39 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
917
  </div>
918
  """
919
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
920
  submit.click(
921
  fn=generate_image,
922
  inputs=[
923
  face_file,
924
- license_input, # NEW: Added license input
925
  prompt,
926
  negative_prompt,
927
  style,
@@ -943,6 +1107,13 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
943
  outputs=success_msg
944
  )
945
 
 
 
 
 
 
 
 
946
  enable_LCM.input(
947
  fn=toggle_lcm_ui,
948
  inputs=[enable_LCM],
 
6
  import time
7
  import secrets
8
  import json
9
+ import hashlib
10
+ from datetime import datetime, timedelta
11
 
12
  import spaces
13
 
14
  import PIL
15
+ from PIL import Image, ImageDraw, ImageFont
16
  from typing import Tuple
17
 
18
  import diffusers
 
93
  valid_licenses = load_licenses()
94
  return license_upper in valid_licenses
95
 
96
+ # ===== TRIAL SYSTEM =====
97
+ TRIAL_FILE = "user_trials.json"
98
+ MAX_FREE_TRIALS = 3
99
+
100
+ def load_trials():
101
+ """Load user trial data"""
102
+ try:
103
+ with open(TRIAL_FILE, 'r') as f:
104
+ return json.load(f)
105
+ except FileNotFoundError:
106
+ return {}
107
+
108
+ def save_trials(trials_data):
109
+ """Save user trial data"""
110
+ with open(TRIAL_FILE, 'w') as f:
111
+ json.dump(trials_data, f)
112
+
113
+ def get_user_identifier():
114
+ """Create a unique but anonymous user identifier"""
115
+ # For Hugging Face Spaces, we'll use a simple approach
116
+ # In production, you might want to use session-based tracking
117
+ return "gradio_user"
118
+
119
+ def can_use_free_trial(user_id):
120
+ """Check if user can use free trial"""
121
+ trials_data = load_trials()
122
+
123
+ if user_id not in trials_data:
124
+ return True, MAX_FREE_TRIALS
125
+
126
+ user_data = trials_data[user_id]
127
+ trials_used = user_data.get('trials_used', 0)
128
+ first_trial_date = user_data.get('first_trial_date')
129
+
130
+ # Reset trials after 30 days
131
+ if first_trial_date:
132
+ first_date = datetime.fromisoformat(first_trial_date)
133
+ if datetime.now() - first_date > timedelta(days=30):
134
+ trials_used = 0
135
+ user_data['trials_used'] = 0
136
+ user_data['first_trial_date'] = datetime.now().isoformat()
137
+ save_trials(trials_data)
138
+
139
+ trials_left = MAX_FREE_TRIALS - trials_used
140
+ return trials_left > 0, trials_left
141
+
142
+ def record_trial_usage(user_id):
143
+ """Record that user used a trial"""
144
+ trials_data = load_trials()
145
+
146
+ if user_id not in trials_data:
147
+ trials_data[user_id] = {
148
+ 'trials_used': 1,
149
+ 'first_trial_date': datetime.now().isoformat(),
150
+ 'last_used': datetime.now().isoformat()
151
+ }
152
+ else:
153
+ trials_data[user_id]['trials_used'] += 1
154
+ trials_data[user_id]['last_used'] = datetime.now().isoformat()
155
+
156
+ save_trials(trials_data)
157
+
158
+ def apply_watermark(image):
159
+ """Apply watermark to free trial images"""
160
+ # Convert to PIL if needed
161
+ if hasattr(image, 'mode'):
162
+ pil_image = image
163
+ else:
164
+ pil_image = Image.fromarray(image)
165
+
166
+ draw = ImageDraw.Draw(pil_image, 'RGBA')
167
+ width, height = pil_image.size
168
+
169
+ # Watermark text
170
+ watermark_text = "PREVIEW - UPGRADE TO DOWNLOAD"
171
+
172
+ # Use default font
173
+ try:
174
+ font = ImageFont.truetype("arial.ttf", min(width, height) // 20)
175
+ except:
176
+ try:
177
+ font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", min(width, height) // 20)
178
+ except:
179
+ font = ImageFont.load_default()
180
+
181
+ # Get text size
182
+ bbox = draw.textbbox((0, 0), watermark_text, font=font)
183
+ text_width = bbox[2] - bbox[0]
184
+ text_height = bbox[3] - bbox[1]
185
+
186
+ # Position watermark (center bottom)
187
+ x = (width - text_width) // 2
188
+ y = height - text_height - 50
189
+
190
+ # Draw semi-transparent background
191
+ draw.rectangle([x-10, y-10, x+text_width+10, y+text_height+10],
192
+ fill=(0, 0, 0, 128))
193
+
194
+ # Draw text
195
+ draw.text((x, y), watermark_text, fill=(255, 255, 255, 255), font=font)
196
+
197
+ return pil_image
198
+
199
  # Initialize licenses on first run
200
  VALID_LICENSES = load_licenses()
201
  print(f"✅ License system initialized. Admin keys: {ADMIN_KEYS}")
 
383
  @spaces.GPU
384
  def generate_image(
385
  face_image_path,
386
+ license_key,
387
  prompt,
388
  negative_prompt,
389
  style_name,
 
401
  progress=gr.Progress(track_tqdm=True),
402
  ):
403
 
404
+ # ===== LICENSE & TRIAL VERIFICATION =====
405
+ user_id = get_user_identifier()
406
+ has_valid_license = verify_license(license_key)
407
+
408
+ # If no valid license, check free trials
409
+ if not has_valid_license:
410
+ can_use_trial, trials_left = can_use_free_trial(user_id)
411
+
412
+ if not can_use_trial:
413
+ raise gr.Error(f"""
414
+ ❌ No free trials remaining!
415
+
416
+ You've used all {MAX_FREE_TRIALS} free generations.
417
+
418
+ 🔑 **Options:**
419
+ 1. **Purchase a license** for unlimited HD downloads
420
+ 2. **Enter your existing license key** if you already purchased
421
+
422
+ 💡 Visit: https://canadianheadshotpro.carrd.co to purchase
423
+ 📧 Support: bee.tools@zohomailcloud.ca
424
+ """)
425
 
426
  if enable_LCM:
427
  pipe.scheduler = diffusers.LCMScheduler.from_config(pipe.scheduler.config)
 
510
  generator=generator,
511
  ).images
512
 
513
+ # ===== APPLY WATERMARK IF USING FREE TRIAL =====
514
+ final_image = images[0]
515
+ if not has_valid_license:
516
+ # Record trial usage
517
+ record_trial_usage(user_id)
518
+ # Apply watermark
519
+ final_image = apply_watermark(final_image)
520
+
521
+ # Update trials left
522
+ _, trials_left = can_use_free_trial(user_id)
523
+
524
+ # Show trial usage message
525
+ gr.Info(f"Free trial used! {trials_left} generations remaining. Upgrade for watermark-free HD downloads.")
526
+
527
+ png_filepath = save_as_png(final_image)
528
  return png_filepath, gr.update(visible=True)
529
 
530
  # MODERN PROFESSIONAL UI CSS
 
544
  --border: #e2e8f0;
545
  --shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
546
  }
 
547
  .gradio-container {
548
  max-width: 1400px !important;
549
  font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
550
  background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%) !important;
551
  }
 
552
  .main-container {
553
  background: white;
554
  border-radius: 24px;
 
557
  overflow: hidden;
558
  border: 1px solid var(--border);
559
  }
 
560
  .hero-section {
561
  background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
562
  color: white;
 
565
  position: relative;
566
  overflow: hidden;
567
  }
 
568
  .hero-section::before {
569
  content: '';
570
  position: absolute;
 
575
  background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 100" fill="rgba(255,255,255,0.1)"><polygon points="0,0 1000,100 0,100"/></svg>');
576
  background-size: cover;
577
  }
 
578
  .hero-title {
579
  font-size: 3em;
580
  font-weight: 800;
 
584
  -webkit-text-fill-color: transparent;
585
  background-clip: text;
586
  }
 
587
  .hero-subtitle {
588
  font-size: 1.3em;
589
  font-weight: 400;
 
591
  max-width: 600px;
592
  margin: 0 auto;
593
  }
 
594
  .upload-area {
595
  border: 3px dashed var(--border);
596
  border-radius: 20px;
 
600
  transition: all 0.3s ease;
601
  cursor: pointer;
602
  }
 
603
  .upload-area:hover {
604
  border-color: var(--primary);
605
  background: #f0f9ff;
606
  transform: translateY(-2px);
607
  }
 
608
  .upload-icon {
609
  font-size: 3em;
610
  margin-bottom: 16px;
611
  color: var(--primary);
612
  }
 
613
  .control-card {
614
  background: var(--surface);
615
  border-radius: 16px;
 
619
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
620
  transition: all 0.3s ease;
621
  }
 
622
  .control-card:hover {
623
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
624
  transform: translateY(-1px);
625
  }
 
626
  .control-header {
627
  display: flex;
628
  align-items: center;
629
  margin-bottom: 16px;
630
  }
 
631
  .control-icon {
632
  width: 40px;
633
  height: 40px;
 
640
  color: white;
641
  font-weight: 600;
642
  }
 
643
  .control-title {
644
  font-size: 1.2em;
645
  font-weight: 600;
646
  color: var(--text-primary);
647
  margin: 0;
648
  }
 
649
  .result-card {
650
  background: var(--surface);
651
  border-radius: 20px;
 
654
  box-shadow: var(--shadow);
655
  height: 100%;
656
  }
 
657
  .result-header {
658
  text-align: center;
659
  margin-bottom: 24px;
660
  }
 
661
  .result-title {
662
  font-size: 1.5em;
663
  font-weight: 700;
664
  color: var(--text-primary);
665
  margin-bottom: 8px;
666
  }
 
667
  .result-subtitle {
668
  color: var(--text-secondary);
669
  font-size: 0.95em;
670
  }
 
671
  .image-container {
672
  border-radius: 16px;
673
  overflow: hidden;
 
675
  border: 1px solid var(--border);
676
  margin-bottom: 20px;
677
  }
 
678
  .success-banner {
679
  background: linear-gradient(135deg, var(--success), #059669);
680
  color: white;
 
683
  margin-top: 20px;
684
  text-align: center;
685
  }
 
686
  .btn-primary {
687
  background: linear-gradient(135deg, var(--primary), var(--primary-dark)) !important;
688
  color: white !important;
 
694
  transition: all 0.3s ease !important;
695
  box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3) !important;
696
  }
 
697
  .btn-primary:hover {
698
  transform: translateY(-2px) !important;
699
  box-shadow: 0 6px 20px rgba(37, 99, 235, 0.4) !important;
700
  }
 
701
  .slider-container {
702
  padding: 8px 0;
703
  }
 
704
  .slider-label {
705
  display: flex;
706
  justify-content: space-between;
707
  align-items: center;
708
  margin-bottom: 8px;
709
  }
 
710
  .slider-value {
711
  background: var(--primary);
712
  color: white;
 
715
  font-size: 0.85em;
716
  font-weight: 600;
717
  }
 
718
  .tips-card {
719
  background: linear-gradient(135deg, #fef3c7, #f59e0b);
720
  border: none;
 
722
  padding: 20px;
723
  margin-bottom: 20px;
724
  }
 
725
  .tips-header {
726
  display: flex;
727
  align-items: center;
728
  margin-bottom: 12px;
729
  }
 
730
  .tips-icon {
731
  font-size: 1.5em;
732
  margin-right: 12px;
733
  }
 
734
  .progress-container {
735
  margin: 20px 0;
736
  text-align: center;
737
  }
 
738
  .progress-text {
739
  font-size: 0.9em;
740
  color: var(--text-secondary);
741
  margin-top: 8px;
742
  }
743
+ .trial-banner {
744
+ background: linear-gradient(135deg, #10b981, #059669);
745
+ color: white;
746
+ padding: 15px;
747
+ border-radius: 12px;
748
+ text-align: center;
749
+ margin-bottom: 15px;
750
+ }
751
+ .trial-banner-warning {
752
+ background: linear-gradient(135deg, #f59e0b, #d97706);
753
+ color: white;
754
+ padding: 15px;
755
+ border-radius: 12px;
756
+ text-align: center;
757
+ margin-bottom: 15px;
758
+ }
759
+ .trial-banner-error {
760
+ background: linear-gradient(135deg, #ef4444, #dc2626);
761
+ color: white;
762
+ padding: 15px;
763
+ border-radius: 12px;
764
+ text-align: center;
765
+ margin-bottom: 15px;
766
+ }
767
  /* Responsive design */
768
  @media (max-width: 768px) {
769
  .hero-title {
 
821
  elem_classes="upload-area"
822
  )
823
 
824
+ # Access Options Section - UPDATED WITH TRIAL SYSTEM
825
  with gr.Column(elem_classes="control-card"):
826
  gr.HTML("""
827
  <div class="control-header">
828
  <div class="control-icon">🔑</div>
829
+ <h3 class="control-title">Access Options</h3>
830
+ </div>
831
+ """)
832
+
833
+ # Trial Status Display
834
+ trial_status = gr.HTML(f"""
835
+ <div class="trial-banner">
836
+ <h4 style="margin: 0 0 8px 0;">🎉 {MAX_FREE_TRIALS} FREE Trials Available!</h4>
837
+ <p style="margin: 0; font-size: 0.9em;">Try our AI headshot generator - no credit card required</p>
838
  </div>
839
  """)
840
+
841
  license_input = gr.Textbox(
842
  label="",
843
+ placeholder="Enter license key (or leave blank for free trial)",
844
  show_label=False,
845
+ info=f"💡 You get {MAX_FREE_TRIALS} free generations. Purchase license for HD downloads without watermark."
846
  )
847
+
848
+ gr.HTML(f"""
849
  <div style="font-size: 0.85em; color: var(--text-secondary); margin-top: 8px;">
850
+ <strong>Free Trial:</strong> {MAX_FREE_TRIALS} watermarked previews<br>
851
+ <strong>Premium License:</strong> Unlimited HD downloads, no watermark<br>
852
+ <strong>Professional Use:</strong> Commercial rights included<br>
853
+ <a href="https://canadianheadshotpro.carrd.co" target="_blank" style="color: var(--primary); font-weight: 600;">👉 Click here to purchase license</a>
854
  </div>
855
  """)
856
 
 
1053
  </div>
1054
  """
1055
 
1056
+ def update_trial_display(license_key):
1057
+ """Update trial counter based on license status"""
1058
+ if verify_license(license_key):
1059
+ return """
1060
+ <div class="trial-banner">
1061
+ <h4 style="margin: 0 0 8px 0;">✅ Premium License Active</h4>
1062
+ <p style="margin: 0; font-size: 0.9em;">Unlimited HD downloads - no watermark</p>
1063
+ </div>
1064
+ """
1065
+
1066
+ user_id = get_user_identifier()
1067
+ can_use, trials_left = can_use_free_trial(user_id)
1068
+
1069
+ if not can_use:
1070
+ return f"""
1071
+ <div class="trial-banner-error">
1072
+ <h4 style="margin: 0 0 8px 0;">❌ No Free Trials Left</h4>
1073
+ <p style="margin: 0; font-size: 0.9em;">Please purchase a license to continue</p>
1074
+ </div>
1075
+ """
1076
+
1077
+ return f"""
1078
+ <div class="trial-banner">
1079
+ <h4 style="margin: 0 0 8px 0;">🎉 {trials_left} Free Generations Left!</h4>
1080
+ <p style="margin: 0; font-size: 0.9em;">Watermarked previews - upgrade for HD downloads</p>
1081
+ </div>
1082
+ """
1083
+
1084
  submit.click(
1085
  fn=generate_image,
1086
  inputs=[
1087
  face_file,
1088
+ license_input,
1089
  prompt,
1090
  negative_prompt,
1091
  style,
 
1107
  outputs=success_msg
1108
  )
1109
 
1110
+ # Update trial display when license input changes
1111
+ license_input.change(
1112
+ fn=update_trial_display,
1113
+ inputs=[license_input],
1114
+ outputs=[trial_status]
1115
+ )
1116
+
1117
  enable_LCM.input(
1118
  fn=toggle_lcm_ui,
1119
  inputs=[enable_LCM],