Spaces:
Running
on
Zero
Running
on
Zero
Update app.py
Browse files
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,
|
| 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 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 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">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 706 |
</div>
|
| 707 |
""")
|
|
|
|
| 708 |
license_input = gr.Textbox(
|
| 709 |
label="",
|
| 710 |
-
placeholder="Enter
|
| 711 |
show_label=False,
|
| 712 |
-
info="💡
|
| 713 |
)
|
| 714 |
-
|
|
|
|
| 715 |
<div style="font-size: 0.85em; color: var(--text-secondary); margin-top: 8px;">
|
| 716 |
-
<strong>
|
| 717 |
-
<
|
|
|
|
|
|
|
| 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,
|
| 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],
|