Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Commit
·
750d70f
1
Parent(s):
35e5813
new limit popup stuff
Browse files- app.py +23 -16
- index.html +218 -40
app.py
CHANGED
|
@@ -423,36 +423,43 @@ async def health():
|
|
| 423 |
}
|
| 424 |
|
| 425 |
@app.websocket("/ws/video-gen")
|
| 426 |
-
async def websocket_video_gen(websocket: WebSocket):
|
| 427 |
"""WebSocket proxy to FAL API - keeps API key secret"""
|
| 428 |
from fastapi import WebSocket
|
| 429 |
import websockets
|
| 430 |
import json
|
| 431 |
-
|
| 432 |
await websocket.accept()
|
| 433 |
-
|
| 434 |
# Get user from cookie
|
| 435 |
access_token = websocket.cookies.get("access_token")
|
| 436 |
if not access_token:
|
| 437 |
await websocket.close(code=1008, reason="Not authenticated")
|
| 438 |
return
|
| 439 |
-
|
| 440 |
try:
|
| 441 |
user_info = await get_user_info(access_token)
|
| 442 |
except:
|
| 443 |
await websocket.close(code=1008, reason="Invalid session")
|
| 444 |
return
|
| 445 |
-
|
| 446 |
-
#
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
# Fetch temporary FAL token
|
| 457 |
try:
|
| 458 |
async with httpx.AsyncClient() as client:
|
|
@@ -460,7 +467,7 @@ async def websocket_video_gen(websocket: WebSocket):
|
|
| 460 |
"https://rest.alpha.fal.ai/tokens/",
|
| 461 |
headers={
|
| 462 |
"Content-Type": "application/json",
|
| 463 |
-
"Authorization": f"Key {
|
| 464 |
},
|
| 465 |
json={
|
| 466 |
"allowed_apps": ["krea-wan-14b"],
|
|
|
|
| 423 |
}
|
| 424 |
|
| 425 |
@app.websocket("/ws/video-gen")
|
| 426 |
+
async def websocket_video_gen(websocket: WebSocket, user_fal_key: Optional[str] = None):
|
| 427 |
"""WebSocket proxy to FAL API - keeps API key secret"""
|
| 428 |
from fastapi import WebSocket
|
| 429 |
import websockets
|
| 430 |
import json
|
| 431 |
+
|
| 432 |
await websocket.accept()
|
| 433 |
+
|
| 434 |
# Get user from cookie
|
| 435 |
access_token = websocket.cookies.get("access_token")
|
| 436 |
if not access_token:
|
| 437 |
await websocket.close(code=1008, reason="Not authenticated")
|
| 438 |
return
|
| 439 |
+
|
| 440 |
try:
|
| 441 |
user_info = await get_user_info(access_token)
|
| 442 |
except:
|
| 443 |
await websocket.close(code=1008, reason="Invalid session")
|
| 444 |
return
|
| 445 |
+
|
| 446 |
+
# If user provided their own FAL key, use it (bypass limits)
|
| 447 |
+
if user_fal_key:
|
| 448 |
+
print(f"User {user_info['username']} using their own FAL key")
|
| 449 |
+
fal_key_to_use = user_fal_key
|
| 450 |
+
else:
|
| 451 |
+
# Check if user can start session with server FAL key
|
| 452 |
+
can_start, used, limit = can_start_generation(user_info["username"], user_info["is_pro"])
|
| 453 |
+
if not can_start:
|
| 454 |
+
await websocket.close(code=1008, reason=f"Daily limit reached ({used}/{limit})")
|
| 455 |
+
return
|
| 456 |
+
|
| 457 |
+
if not FAL_API_KEY:
|
| 458 |
+
await websocket.close(code=1011, reason="FAL API key not configured")
|
| 459 |
+
return
|
| 460 |
+
|
| 461 |
+
fal_key_to_use = FAL_API_KEY
|
| 462 |
+
|
| 463 |
# Fetch temporary FAL token
|
| 464 |
try:
|
| 465 |
async with httpx.AsyncClient() as client:
|
|
|
|
| 467 |
"https://rest.alpha.fal.ai/tokens/",
|
| 468 |
headers={
|
| 469 |
"Content-Type": "application/json",
|
| 470 |
+
"Authorization": f"Key {fal_key_to_use}"
|
| 471 |
},
|
| 472 |
json={
|
| 473 |
"allowed_apps": ["krea-wan-14b"],
|
index.html
CHANGED
|
@@ -533,6 +533,91 @@
|
|
| 533 |
padding: 15px 0;
|
| 534 |
}
|
| 535 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 536 |
@media (max-width: 1024px) {
|
| 537 |
.video-grid {
|
| 538 |
grid-template-columns: 1fr;
|
|
@@ -561,15 +646,6 @@
|
|
| 561 |
|
| 562 |
<!-- App Container (Grayed out when not authenticated) -->
|
| 563 |
<div id="appContainer">
|
| 564 |
-
<!-- Limit Warning (Hidden by default) -->
|
| 565 |
-
<div id="limitWarning" class="limit-warning hidden">
|
| 566 |
-
<h3 style="color: #991b1b; margin-bottom: 10px;">Daily Limit Reached</h3>
|
| 567 |
-
<p id="limitMessage" style="color: #78716c; font-size: 0.9rem;"></p>
|
| 568 |
-
<p id="upgradeMessage" style="margin-top: 10px; color: #78716c; font-size: 0.9rem;" class="hidden">
|
| 569 |
-
Upgrade to <a href="https://huggingface.co/pricing" target="_blank" class="upgrade-link">Hugging Face PRO</a> for 15 generations per day!
|
| 570 |
-
</p>
|
| 571 |
-
</div>
|
| 572 |
-
|
| 573 |
<header>
|
| 574 |
<h1>KREA Realtime Video</h1>
|
| 575 |
<div class="header-links">
|
|
@@ -711,6 +787,53 @@
|
|
| 711 |
<div id="infoBox" class="info-box hidden"></div>
|
| 712 |
</div>
|
| 713 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 714 |
<!-- User Bar (Hidden when not authenticated) -->
|
| 715 |
<div id="userBar" class="user-bar hidden">
|
| 716 |
<div class="user-info">
|
|
@@ -734,6 +857,53 @@
|
|
| 734 |
<script>
|
| 735 |
const OAUTH_CLIENT_ID = "{{ oauth_client_id }}";
|
| 736 |
const AUTH_STORAGE_KEY = 'HF_AUTH_STATE';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 737 |
|
| 738 |
// Authentication Manager
|
| 739 |
const auth = {
|
|
@@ -742,10 +912,14 @@
|
|
| 742 |
canStart: false,
|
| 743 |
sessionsUsed: 0,
|
| 744 |
sessionsLimit: 1,
|
|
|
|
| 745 |
|
| 746 |
async init() {
|
| 747 |
console.log('Auth init - checking for saved token');
|
| 748 |
|
|
|
|
|
|
|
|
|
|
| 749 |
// Try to restore saved auth from localStorage
|
| 750 |
const saved = localStorage.getItem(AUTH_STORAGE_KEY);
|
| 751 |
if (saved) {
|
|
@@ -838,19 +1012,7 @@
|
|
| 838 |
badge.textContent = this.user.is_pro ? 'PRO' : 'FREE';
|
| 839 |
badge.className = 'user-badge ' + (this.user.is_pro ? 'badge-pro' : 'badge-free');
|
| 840 |
|
| 841 |
-
//
|
| 842 |
-
if (!this.canStart) {
|
| 843 |
-
document.getElementById('limitWarning').classList.remove('hidden');
|
| 844 |
-
document.getElementById('limitMessage').textContent =
|
| 845 |
-
`You've used all ${this.sessionsLimit} generations for today. Come back tomorrow!`;
|
| 846 |
-
if (!this.user.is_pro) {
|
| 847 |
-
document.getElementById('upgradeMessage').classList.remove('hidden');
|
| 848 |
-
}
|
| 849 |
-
document.getElementById('startStopBtn').disabled = true;
|
| 850 |
-
document.getElementById('startStopBtn').textContent = 'Limit Reached';
|
| 851 |
-
} else {
|
| 852 |
-
document.getElementById('limitWarning').classList.add('hidden');
|
| 853 |
-
}
|
| 854 |
},
|
| 855 |
|
| 856 |
getAuthHeaders() {
|
|
@@ -1164,25 +1326,35 @@
|
|
| 1164 |
return;
|
| 1165 |
}
|
| 1166 |
|
| 1167 |
-
//
|
| 1168 |
-
|
| 1169 |
-
|
| 1170 |
-
|
| 1171 |
-
|
| 1172 |
-
|
| 1173 |
-
|
| 1174 |
-
|
| 1175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1176 |
return;
|
| 1177 |
}
|
| 1178 |
-
|
| 1179 |
-
|
| 1180 |
-
auth.sessionsUsed = sessionData.sessions_used;
|
| 1181 |
-
auth.sessionsLimit = sessionData.sessions_limit;
|
| 1182 |
-
auth.canStart = sessionData.sessions_used < sessionData.sessions_limit;
|
| 1183 |
-
} catch (error) {
|
| 1184 |
-
this.showError('Failed to start session');
|
| 1185 |
-
return;
|
| 1186 |
}
|
| 1187 |
|
| 1188 |
const prompt = document.getElementById('prompt').value.trim();
|
|
@@ -1225,7 +1397,13 @@
|
|
| 1225 |
|
| 1226 |
// Connect to backend WebSocket proxy
|
| 1227 |
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
| 1228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1229 |
|
| 1230 |
try {
|
| 1231 |
this.ws = new WebSocket(wsUrl);
|
|
|
|
| 533 |
padding: 15px 0;
|
| 534 |
}
|
| 535 |
|
| 536 |
+
/* Modal */
|
| 537 |
+
.modal-overlay {
|
| 538 |
+
position: fixed;
|
| 539 |
+
top: 0;
|
| 540 |
+
left: 0;
|
| 541 |
+
width: 100%;
|
| 542 |
+
height: 100%;
|
| 543 |
+
background: rgba(0, 0, 0, 0.7);
|
| 544 |
+
display: flex;
|
| 545 |
+
align-items: center;
|
| 546 |
+
justify-content: center;
|
| 547 |
+
z-index: 1000;
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
.modal {
|
| 551 |
+
background: #fafaf9;
|
| 552 |
+
border-radius: 12px;
|
| 553 |
+
border: 1px solid #d6d3d1;
|
| 554 |
+
padding: 30px;
|
| 555 |
+
max-width: 500px;
|
| 556 |
+
width: 90%;
|
| 557 |
+
box-shadow: 0px 10px 40px rgba(0, 0, 0, 0.3);
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
.modal h2 {
|
| 561 |
+
color: #1c1917;
|
| 562 |
+
margin-bottom: 15px;
|
| 563 |
+
font-size: 1.5rem;
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
.modal p {
|
| 567 |
+
color: #57534e;
|
| 568 |
+
margin-bottom: 20px;
|
| 569 |
+
line-height: 1.5;
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
.modal-options {
|
| 573 |
+
display: flex;
|
| 574 |
+
flex-direction: column;
|
| 575 |
+
gap: 15px;
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
.modal-option {
|
| 579 |
+
padding: 15px;
|
| 580 |
+
border: 2px solid #d6d3d1;
|
| 581 |
+
border-radius: 8px;
|
| 582 |
+
text-align: left;
|
| 583 |
+
background: #fafaf9;
|
| 584 |
+
cursor: pointer;
|
| 585 |
+
transition: all 0.2s;
|
| 586 |
+
}
|
| 587 |
+
|
| 588 |
+
.modal-option:hover {
|
| 589 |
+
border-color: #f59e0b;
|
| 590 |
+
background: #fef3c7;
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
.modal-option h3 {
|
| 594 |
+
color: #1c1917;
|
| 595 |
+
font-size: 1rem;
|
| 596 |
+
margin-bottom: 5px;
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
.modal-option p {
|
| 600 |
+
color: #78716c;
|
| 601 |
+
font-size: 0.85rem;
|
| 602 |
+
margin: 0;
|
| 603 |
+
}
|
| 604 |
+
|
| 605 |
+
.modal-fal-key {
|
| 606 |
+
margin-top: 20px;
|
| 607 |
+
padding-top: 20px;
|
| 608 |
+
border-top: 1px solid #d6d3d1;
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
.modal-fal-key h3 {
|
| 612 |
+
color: #1c1917;
|
| 613 |
+
font-size: 1rem;
|
| 614 |
+
margin-bottom: 10px;
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
.modal-fal-key .form-group {
|
| 618 |
+
margin-bottom: 10px;
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
@media (max-width: 1024px) {
|
| 622 |
.video-grid {
|
| 623 |
grid-template-columns: 1fr;
|
|
|
|
| 646 |
|
| 647 |
<!-- App Container (Grayed out when not authenticated) -->
|
| 648 |
<div id="appContainer">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 649 |
<header>
|
| 650 |
<h1>KREA Realtime Video</h1>
|
| 651 |
<div class="header-links">
|
|
|
|
| 787 |
<div id="infoBox" class="info-box hidden"></div>
|
| 788 |
</div>
|
| 789 |
|
| 790 |
+
<!-- Limit Reached Modal -->
|
| 791 |
+
<div id="limitModal" class="modal-overlay hidden">
|
| 792 |
+
<div class="modal">
|
| 793 |
+
<h2>Daily Limit Reached</h2>
|
| 794 |
+
<p>You've used all your generations for today. Here are your options:</p>
|
| 795 |
+
|
| 796 |
+
<div class="modal-options">
|
| 797 |
+
<!-- Subscribe to PRO (Free users only) -->
|
| 798 |
+
<a href="https://huggingface.co/subscribe/pro" target="_blank" class="modal-option" id="proOption">
|
| 799 |
+
<h3>Subscribe to PRO</h3>
|
| 800 |
+
<p>Get more generations per day with Hugging Face PRO</p>
|
| 801 |
+
</a>
|
| 802 |
+
|
| 803 |
+
<!-- Run Locally -->
|
| 804 |
+
<a href="https://huggingface.co/krea/krea-realtime-video" target="_blank" class="modal-option">
|
| 805 |
+
<h3>Run Locally</h3>
|
| 806 |
+
<p>Download and run the model on your own hardware</p>
|
| 807 |
+
</a>
|
| 808 |
+
|
| 809 |
+
<!-- Use API -->
|
| 810 |
+
<a href="https://fal.ai" target="_blank" class="modal-option">
|
| 811 |
+
<h3>Use with API</h3>
|
| 812 |
+
<p>Integrate into your own applications with FAL API</p>
|
| 813 |
+
</a>
|
| 814 |
+
</div>
|
| 815 |
+
|
| 816 |
+
<!-- FAL Key Input -->
|
| 817 |
+
<div class="modal-fal-key">
|
| 818 |
+
<h3>Or use your own FAL API Key</h3>
|
| 819 |
+
<div class="form-group">
|
| 820 |
+
<label>FAL API Key</label>
|
| 821 |
+
<input type="password" id="falKeyInput" placeholder="Enter your FAL API key">
|
| 822 |
+
</div>
|
| 823 |
+
<button class="btn btn-primary" style="width: 100%;" onclick="limitModal.saveFalKey()">
|
| 824 |
+
Save & Continue
|
| 825 |
+
</button>
|
| 826 |
+
<p style="font-size: 0.75rem; color: #78716c; margin-top: 10px;">
|
| 827 |
+
Your key is stored locally and never sent to our servers
|
| 828 |
+
</p>
|
| 829 |
+
</div>
|
| 830 |
+
|
| 831 |
+
<button onclick="limitModal.close()" style="margin-top: 20px; background: transparent; border: none; color: #78716c; cursor: pointer; width: 100%;">
|
| 832 |
+
Close
|
| 833 |
+
</button>
|
| 834 |
+
</div>
|
| 835 |
+
</div>
|
| 836 |
+
|
| 837 |
<!-- User Bar (Hidden when not authenticated) -->
|
| 838 |
<div id="userBar" class="user-bar hidden">
|
| 839 |
<div class="user-info">
|
|
|
|
| 857 |
<script>
|
| 858 |
const OAUTH_CLIENT_ID = "{{ oauth_client_id }}";
|
| 859 |
const AUTH_STORAGE_KEY = 'HF_AUTH_STATE';
|
| 860 |
+
const FAL_KEY_STORAGE = 'FAL_API_KEY';
|
| 861 |
+
|
| 862 |
+
// Limit Modal Manager
|
| 863 |
+
const limitModal = {
|
| 864 |
+
show(isPro) {
|
| 865 |
+
// Show/hide PRO option based on user tier
|
| 866 |
+
const proOption = document.getElementById('proOption');
|
| 867 |
+
if (isPro) {
|
| 868 |
+
proOption.style.display = 'none';
|
| 869 |
+
} else {
|
| 870 |
+
proOption.style.display = 'block';
|
| 871 |
+
}
|
| 872 |
+
|
| 873 |
+
// Check if FAL key already saved
|
| 874 |
+
const savedKey = localStorage.getItem(FAL_KEY_STORAGE);
|
| 875 |
+
if (savedKey) {
|
| 876 |
+
document.getElementById('falKeyInput').value = '••••••••••••';
|
| 877 |
+
}
|
| 878 |
+
|
| 879 |
+
document.getElementById('limitModal').classList.remove('hidden');
|
| 880 |
+
},
|
| 881 |
+
|
| 882 |
+
close() {
|
| 883 |
+
document.getElementById('limitModal').classList.add('hidden');
|
| 884 |
+
},
|
| 885 |
+
|
| 886 |
+
saveFalKey() {
|
| 887 |
+
const key = document.getElementById('falKeyInput').value.trim();
|
| 888 |
+
if (!key || key === '••••••••••••') {
|
| 889 |
+
app.showError('Please enter a valid FAL API key');
|
| 890 |
+
return;
|
| 891 |
+
}
|
| 892 |
+
|
| 893 |
+
localStorage.setItem(FAL_KEY_STORAGE, key);
|
| 894 |
+
app.showInfo('FAL API key saved! You can now use unlimited generations.');
|
| 895 |
+
auth.hasFalKey = true;
|
| 896 |
+
this.close();
|
| 897 |
+
},
|
| 898 |
+
|
| 899 |
+
hasFalKey() {
|
| 900 |
+
return !!localStorage.getItem(FAL_KEY_STORAGE);
|
| 901 |
+
},
|
| 902 |
+
|
| 903 |
+
getFalKey() {
|
| 904 |
+
return localStorage.getItem(FAL_KEY_STORAGE);
|
| 905 |
+
}
|
| 906 |
+
};
|
| 907 |
|
| 908 |
// Authentication Manager
|
| 909 |
const auth = {
|
|
|
|
| 912 |
canStart: false,
|
| 913 |
sessionsUsed: 0,
|
| 914 |
sessionsLimit: 1,
|
| 915 |
+
hasFalKey: false,
|
| 916 |
|
| 917 |
async init() {
|
| 918 |
console.log('Auth init - checking for saved token');
|
| 919 |
|
| 920 |
+
// Check if user has FAL key
|
| 921 |
+
this.hasFalKey = limitModal.hasFalKey();
|
| 922 |
+
|
| 923 |
// Try to restore saved auth from localStorage
|
| 924 |
const saved = localStorage.getItem(AUTH_STORAGE_KEY);
|
| 925 |
if (saved) {
|
|
|
|
| 1012 |
badge.textContent = this.user.is_pro ? 'PRO' : 'FREE';
|
| 1013 |
badge.className = 'user-badge ' + (this.user.is_pro ? 'badge-pro' : 'badge-free');
|
| 1014 |
|
| 1015 |
+
// No longer show banner - modal will appear when user tries to generate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1016 |
},
|
| 1017 |
|
| 1018 |
getAuthHeaders() {
|
|
|
|
| 1326 |
return;
|
| 1327 |
}
|
| 1328 |
|
| 1329 |
+
// Check if user has FAL key - if so, skip HF generation limits
|
| 1330 |
+
if (!auth.hasFalKey) {
|
| 1331 |
+
// Record session and check limits
|
| 1332 |
+
try {
|
| 1333 |
+
const response = await fetch('/api/start-session', {
|
| 1334 |
+
method: 'POST',
|
| 1335 |
+
headers: auth.getAuthHeaders()
|
| 1336 |
+
});
|
| 1337 |
+
if (!response.ok) {
|
| 1338 |
+
const data = await response.json();
|
| 1339 |
+
// If limit reached, show modal
|
| 1340 |
+
if (response.status === 429) {
|
| 1341 |
+
limitModal.show(auth.user.is_pro);
|
| 1342 |
+
return;
|
| 1343 |
+
}
|
| 1344 |
+
this.showError(data.detail || 'Failed to start session');
|
| 1345 |
+
return;
|
| 1346 |
+
}
|
| 1347 |
+
// Update session count
|
| 1348 |
+
const sessionData = await response.json();
|
| 1349 |
+
auth.sessionsUsed = sessionData.sessions_used;
|
| 1350 |
+
auth.sessionsLimit = sessionData.sessions_limit;
|
| 1351 |
+
auth.canStart = sessionData.sessions_used < sessionData.sessions_limit;
|
| 1352 |
+
} catch (error) {
|
| 1353 |
+
this.showError('Failed to start session');
|
| 1354 |
return;
|
| 1355 |
}
|
| 1356 |
+
} else {
|
| 1357 |
+
console.log('Using FAL API key - bypassing HF generation limits');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1358 |
}
|
| 1359 |
|
| 1360 |
const prompt = document.getElementById('prompt').value.trim();
|
|
|
|
| 1397 |
|
| 1398 |
// Connect to backend WebSocket proxy
|
| 1399 |
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
| 1400 |
+
let wsUrl = `${protocol}//${window.location.host}/ws/video-gen`;
|
| 1401 |
+
|
| 1402 |
+
// If user has their own FAL key, pass it as query param
|
| 1403 |
+
const userFalKey = limitModal.getFalKey();
|
| 1404 |
+
if (userFalKey) {
|
| 1405 |
+
wsUrl += `?user_fal_key=${encodeURIComponent(userFalKey)}`;
|
| 1406 |
+
}
|
| 1407 |
|
| 1408 |
try {
|
| 1409 |
this.ws = new WebSocket(wsUrl);
|