jejunepixels commited on
Commit
21b456a
·
verified ·
1 Parent(s): 35d4a06

Upload 2 files

Browse files
Files changed (2) hide show
  1. README.md +1 -1
  2. index.html +68 -5
README.md CHANGED
@@ -43,7 +43,7 @@ Type freely to converse with two characters whose names, backstories, and person
43
 
44
  ### AI Pipeline
45
 
46
- All AI calls route through Hugging Face Spaces. Players sign in with their Hugging Face account on the start page, and dialogue and character generation run on their own Inference Providers credits — the Spaces hold no API keys.
47
 
48
  #### Dialogue Model
49
  **Endpoint:** `jejunepixels-noexit-proxy.hf.space/chat`
 
43
 
44
  ### AI Pipeline
45
 
46
+ All AI calls route through Hugging Face Spaces. Visitors can play free up to a per-visitor token allowance; after that, or from the start if they prefer, they sign in with their Hugging Face account and dialogue and character generation run on their own Inference Providers credits. No API keys are exposed to the client.
47
 
48
  #### Dialogue Model
49
  **Endpoint:** `jejunepixels-noexit-proxy.hf.space/chat`
index.html CHANGED
@@ -360,12 +360,13 @@
360
  <button type="button" class="signin-btn" id="signin-btn">
361
  <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-xl-dark.svg" alt="Sign in with Hugging Face">
362
  </button>
363
- <p class="auth-note">The characters run on your own Hugging Face inference credits. Sign in to play — no API key needed.</p>
364
- <p class="auth-note" id="auth-newtab-note" hidden>Sign-in opens the game in a new tab.</p>
365
  </div>
366
  <div id="auth-signed-in" hidden>
367
  <p class="auth-user">Signed in as <strong id="auth-username"></strong> · <a href="#" id="signout-link">Sign out</a></p>
368
  <p class="auth-note">Dialogue is billed to your Hugging Face inference credits.</p>
 
369
  </div>
370
  <p class="auth-error" id="auth-error" hidden></p>
371
  </div>
@@ -400,18 +401,29 @@
400
  (state === 'ready' ? ' ready' : state === 'error' ? ' error' : '');
401
  }
402
 
403
- // Set by the sign-in script below once a valid Hugging Face token is stored.
 
404
  var signedIn = false;
 
405
 
406
  function checkAllReady(enterBtn, titleEl) {
407
  var systemsReady = SPACES.every(s => s.ready);
408
  if (systemsReady) titleEl.textContent = 'Systems Ready';
409
- enterBtn.classList.toggle('disabled', !(systemsReady && signedIn));
 
 
 
 
410
  }
411
 
412
  window.setSignedIn = function(value) {
413
  signedIn = value;
414
- checkAllReady(document.getElementById('enter-btn'), document.querySelector('.status-panel-title'));
 
 
 
 
 
415
  };
416
 
417
  async function pollSpace(space, enterBtn, titleEl) {
@@ -483,9 +495,16 @@
483
  // inference is billed to the signed-in player.
484
  const STORAGE_KEY = 'noexit_hf_oauth';
485
  const HUB_URL = 'https://cdn.jsdelivr.net/npm/@huggingface/hub@2.17.5/+esm';
 
 
 
 
486
 
487
  // The Hugging Face login page cannot load inside the Space's frame on
488
  // huggingface.co, so when framed the flow moves to a new tab on this URL.
 
 
 
489
  const framed = window.self !== window.top;
490
  const pageUrl = window.location.origin + window.location.pathname;
491
  const params = new URLSearchParams(window.location.search);
@@ -518,6 +537,31 @@
518
  window.setSignedIn(!!auth);
519
  }
520
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
521
  let hub = null;
522
  async function loadHub() {
523
  if (!hub) hub = await import(HUB_URL);
@@ -530,6 +574,9 @@
530
  window.open(pageUrl + '?signin=1', '_blank', 'noopener');
531
  return;
532
  }
 
 
 
533
  const vars = window.huggingface && window.huggingface.variables;
534
  if (!vars || !vars.OAUTH_CLIENT_ID) {
535
  showError('Sign-in is not set up for this Space yet.');
@@ -569,6 +616,22 @@
569
  }
570
 
571
  document.getElementById('auth-newtab-note').hidden = !framed;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
  document.getElementById('signin-btn').addEventListener('click', startSignIn);
573
  document.getElementById('signout-link').addEventListener('click', function(event) {
574
  event.preventDefault();
 
360
  <button type="button" class="signin-btn" id="signin-btn">
361
  <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-xl-dark.svg" alt="Sign in with Hugging Face">
362
  </button>
363
+ <p class="auth-note" id="auth-free-note">Play free for a while, or sign in with Hugging Face to use your own inference credits — no API key needed.</p>
364
+ <p class="auth-note" id="auth-newtab-note" hidden>The game and sign-in open in a new tab.</p>
365
  </div>
366
  <div id="auth-signed-in" hidden>
367
  <p class="auth-user">Signed in as <strong id="auth-username"></strong> · <a href="#" id="signout-link">Sign out</a></p>
368
  <p class="auth-note">Dialogue is billed to your Hugging Face inference credits.</p>
369
+ <p class="auth-note" id="auth-return-note" hidden>You're signed in — go back to the game tab to continue.</p>
370
  </div>
371
  <p class="auth-error" id="auth-error" hidden></p>
372
  </div>
 
401
  (state === 'ready' ? ' ready' : state === 'error' ? ' error' : '');
402
  }
403
 
404
+ // Set by the sign-in script below: a visitor can play when they are signed in
405
+ // with Hugging Face, or while they still have free play left on the proxy.
406
  var signedIn = false;
407
+ var freePlay = false;
408
 
409
  function checkAllReady(enterBtn, titleEl) {
410
  var systemsReady = SPACES.every(s => s.ready);
411
  if (systemsReady) titleEl.textContent = 'Systems Ready';
412
+ enterBtn.classList.toggle('disabled', !(systemsReady && (signedIn || freePlay)));
413
+ }
414
+
415
+ function refreshEnter() {
416
+ checkAllReady(document.getElementById('enter-btn'), document.querySelector('.status-panel-title'));
417
  }
418
 
419
  window.setSignedIn = function(value) {
420
  signedIn = value;
421
+ refreshEnter();
422
+ };
423
+
424
+ window.setFreePlay = function(value) {
425
+ freePlay = value;
426
+ refreshEnter();
427
  };
428
 
429
  async function pollSpace(space, enterBtn, titleEl) {
 
495
  // inference is billed to the signed-in player.
496
  const STORAGE_KEY = 'noexit_hf_oauth';
497
  const HUB_URL = 'https://cdn.jsdelivr.net/npm/@huggingface/hub@2.17.5/+esm';
498
+ const FREE_STATUS_URL = 'https://jejunepixels-noexit-proxy.hf.space/free/status';
499
+ // Set when this page was opened by the game's own sign-in prompt, and kept
500
+ // across the trip to the Hugging Face login page.
501
+ const FROM_GAME_KEY = 'noexit_signin_from_game';
502
 
503
  // The Hugging Face login page cannot load inside the Space's frame on
504
  // huggingface.co, so when framed the flow moves to a new tab on this URL.
505
+ // The game opens in a new tab too: a framed page gets separate browser
506
+ // storage, and the game must share storage with the sign-in page to pick
507
+ // up a sign-in made while playing.
508
  const framed = window.self !== window.top;
509
  const pageUrl = window.location.origin + window.location.pathname;
510
  const params = new URLSearchParams(window.location.search);
 
537
  window.setSignedIn(!!auth);
538
  }
539
 
540
+ // Ask the proxy whether this visitor still has free play. The proxy may be
541
+ // waking up, so keep trying for a while; until it answers, Enter needs sign-in.
542
+ async function checkFreePlay() {
543
+ const noteEl = document.getElementById('auth-free-note');
544
+ for (let attempt = 0; attempt < 24; attempt++) {
545
+ try {
546
+ const res = await fetch(FREE_STATUS_URL);
547
+ if (res.ok) {
548
+ const status = await res.json();
549
+ const available = !!status.enabled && status.remaining > 0;
550
+ window.setFreePlay(available);
551
+ if (!available) {
552
+ noteEl.textContent = status.enabled
553
+ ? 'Your free play is used up — sign in with Hugging Face to continue on your own inference credits.'
554
+ : 'Sign in with Hugging Face to play on your own inference credits — no API key needed.';
555
+ }
556
+ return;
557
+ }
558
+ if (res.status === 404) break; // proxy without free play
559
+ } catch (e) {}
560
+ await new Promise(function(r) { setTimeout(r, 5000); });
561
+ }
562
+ noteEl.textContent = 'Sign in with Hugging Face to play on your own inference credits — no API key needed.';
563
+ }
564
+
565
  let hub = null;
566
  async function loadHub() {
567
  if (!hub) hub = await import(HUB_URL);
 
574
  window.open(pageUrl + '?signin=1', '_blank', 'noopener');
575
  return;
576
  }
577
+ if (params.get('from') === 'game') {
578
+ try { sessionStorage.setItem(FROM_GAME_KEY, '1'); } catch (e) {}
579
+ }
580
  const vars = window.huggingface && window.huggingface.variables;
581
  if (!vars || !vars.OAUTH_CLIENT_ID) {
582
  showError('Sign-in is not set up for this Space yet.');
 
616
  }
617
 
618
  document.getElementById('auth-newtab-note').hidden = !framed;
619
+
620
+ if (framed) {
621
+ // Whether the visitor may play is settled in the game's own tab.
622
+ document.getElementById('enter-btn').target = '_blank';
623
+ window.setFreePlay(true);
624
+ } else {
625
+ checkFreePlay();
626
+ }
627
+
628
+ // Sign-in started from the game's prompt: point the player back to that tab.
629
+ try {
630
+ if (auth && (params.get('from') === 'game' || sessionStorage.getItem(FROM_GAME_KEY))) {
631
+ document.getElementById('auth-return-note').hidden = false;
632
+ sessionStorage.removeItem(FROM_GAME_KEY);
633
+ }
634
+ } catch (e) {}
635
  document.getElementById('signin-btn').addEventListener('click', startSignIn);
636
  document.getElementById('signout-link').addEventListener('click', function(event) {
637
  event.preventDefault();