nextmarte Claude Opus 4.8 commited on
Commit
250c08a
·
1 Parent(s): dc59186

fix: popup empty on cold load — lazy-load gradio client so UI never waits on the CDN

Browse files

The static esm.sh import blocked module execution until the CDN resolved, so on a
cold load the popup rendered with empty text. Load @gradio/client via dynamic import
(ensureClient, awaited in send/sendWatch) and seed the popup with default text in HTML.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files changed (2) hide show
  1. frontend/app.js +18 -6
  2. frontend/index.html +4 -4
frontend/app.js CHANGED
@@ -1,5 +1,3 @@
1
- import { Client, handle_file } from "https://esm.sh/@gradio/client";
2
-
3
  const $ = (id) => document.getElementById(id);
4
  const cam = $("cam"), canvas = $("canvas"), statusEl = $("status"),
5
  answerEl = $("answer"), hintEl = $("hint"), langBtn = $("lang"),
@@ -114,8 +112,19 @@ function stopRec() {
114
  });
115
  }
116
 
117
- // ---- backend ----
118
- let client = null;
 
 
 
 
 
 
 
 
 
 
 
119
 
120
  async function send(frame, audio, qtext = "") {
121
  if (busy) return;
@@ -123,6 +132,8 @@ async function send(frame, audio, qtext = "") {
123
  answerEl.textContent = "";
124
  setState("thinking", t("thinking"));
125
  try {
 
 
126
  const payload = { image: handle_file(frame), lang };
127
  if (audio) payload.audio = handle_file(audio);
128
  if (qtext) payload.qtext = qtext;
@@ -415,6 +426,8 @@ async function sendWatch(frame, hint = "") {
415
  if (busy) return;
416
  busy = true;
417
  try {
 
 
418
  const result = await client.predict("/watch", { image: handle_file(frame), prev: history.join(" · "), lang, hint });
419
  const out = Array.isArray(result.data) ? result.data[0] : result.data;
420
  if (out && out.speak && out.answer && !history.some((h) => tooSimilar(h, out.answer))) {
@@ -546,6 +559,5 @@ function updateLabels() {
546
  setState("", t("idle"));
547
  showIntro(); // welcome popup on every open (the camera starts when it closes)
548
  loadDetector(); // preload the object detector in the background (non-blocking)
549
- try { client = await Client.connect(window.location.origin); console.log("Iris connected"); }
550
- catch (e) { console.error("connect:", e); setState("", t("err")); }
551
  })();
 
 
 
1
  const $ = (id) => document.getElementById(id);
2
  const cam = $("cam"), canvas = $("canvas"), statusEl = $("status"),
3
  answerEl = $("answer"), hintEl = $("hint"), langBtn = $("lang"),
 
112
  });
113
  }
114
 
115
+ // ---- backend (gradio client loaded lazily, so the UI never waits on the CDN) ----
116
+ let Client, handle_file, client = null, clientReady = null;
117
+ function ensureClient() {
118
+ if (!clientReady) {
119
+ clientReady = (async () => {
120
+ const mod = await import("https://esm.sh/@gradio/client");
121
+ Client = mod.Client; handle_file = mod.handle_file;
122
+ client = await Client.connect(window.location.origin);
123
+ console.log("Iris connected");
124
+ })().catch((e) => { console.error("connect:", e); clientReady = null; });
125
+ }
126
+ return clientReady;
127
+ }
128
 
129
  async function send(frame, audio, qtext = "") {
130
  if (busy) return;
 
132
  answerEl.textContent = "";
133
  setState("thinking", t("thinking"));
134
  try {
135
+ await ensureClient();
136
+ if (!client) throw new Error("client not ready");
137
  const payload = { image: handle_file(frame), lang };
138
  if (audio) payload.audio = handle_file(audio);
139
  if (qtext) payload.qtext = qtext;
 
426
  if (busy) return;
427
  busy = true;
428
  try {
429
+ await ensureClient();
430
+ if (!client) return;
431
  const result = await client.predict("/watch", { image: handle_file(frame), prev: history.join(" · "), lang, hint });
432
  const out = Array.isArray(result.data) ? result.data[0] : result.data;
433
  if (out && out.speak && out.answer && !history.some((h) => tooSimilar(h, out.answer))) {
 
559
  setState("", t("idle"));
560
  showIntro(); // welcome popup on every open (the camera starts when it closes)
561
  loadDetector(); // preload the object detector in the background (non-blocking)
562
+ ensureClient(); // connect in the background; the UI never waits on it
 
563
  })();
frontend/index.html CHANGED
@@ -71,14 +71,14 @@
71
  </svg>
72
  </button>
73
  <h2 id="intro-title">Iris</h2>
74
- <p id="intro-body"></p>
75
  <div class="intro-langs" role="group" aria-label="Language">
76
  <button id="intro-en" class="intro-lang">EN</button>
77
  <button id="intro-pt" class="intro-lang">PT</button>
78
  </div>
79
- <a id="intro-video" class="intro-video" href="#" target="_blank" rel="noopener"></a>
80
- <button id="intro-close" class="intro-close"></button>
81
- <p id="intro-hint" class="intro-hint"></p>
82
  </div>
83
  </div>
84
 
 
71
  </svg>
72
  </button>
73
  <h2 id="intro-title">Iris</h2>
74
+ <p id="intro-body">This is Iris, your eyes by voice. Point your phone and it tells you what is in front of you, and reads your money, your bills, and your medicine, out loud.</p>
75
  <div class="intro-langs" role="group" aria-label="Language">
76
  <button id="intro-en" class="intro-lang">EN</button>
77
  <button id="intro-pt" class="intro-lang">PT</button>
78
  </div>
79
+ <a id="intro-video" class="intro-video" href="https://huggingface.co/spaces/build-small-hackathon/iris" target="_blank" rel="noopener">Watch the demo</a>
80
+ <button id="intro-close" class="intro-close">Close</button>
81
+ <p id="intro-hint" class="intro-hint">Say "close" or tap the button</p>
82
  </div>
83
  </div>
84