apolinario commited on
Commit
750d70f
·
1 Parent(s): 35e5813

new limit popup stuff

Browse files
Files changed (2) hide show
  1. app.py +23 -16
  2. 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
- # Check if user can start session
447
- can_start, used, limit = can_start_generation(user_info["username"], user_info["is_pro"])
448
- if not can_start:
449
- await websocket.close(code=1008, reason=f"Daily limit reached ({used}/{limit})")
450
- return
451
-
452
- if not FAL_API_KEY:
453
- await websocket.close(code=1011, reason="FAL API key not configured")
454
- return
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 {FAL_API_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
- // Show/hide limit warning
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
- // Record session
1168
- try {
1169
- const response = await fetch('/api/start-session', {
1170
- method: 'POST',
1171
- headers: auth.getAuthHeaders()
1172
- });
1173
- if (!response.ok) {
1174
- const data = await response.json();
1175
- this.showError(data.detail || 'Failed to start session');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1176
  return;
1177
  }
1178
- // Update session count
1179
- const sessionData = await response.json();
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
- const wsUrl = `${protocol}//${window.location.host}/ws/video-gen`;
 
 
 
 
 
 
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);