mo7ammad2012 commited on
Commit
931a28d
·
verified ·
1 Parent(s): d2a4eca
Files changed (1) hide show
  1. index.html +24 -285
index.html CHANGED
@@ -150,11 +150,10 @@
150
  <div class="queuebox">
151
  <span class="qchip" id="queuedChip">Queued: 0</span>
152
  <span class="qchip" id="activeChip">Active: none</span>
153
- <span class="qchip" id="engineChip">Engines: 20</span>
154
  </div>
155
 
156
  <div class="fine">
157
- This Static Space keeps a private FIFO queue in your browser and checks up to 20 AIDMA backend candidates, automatically skipping unavailable or incompatible Spaces.
158
  </div>
159
  </div>
160
 
@@ -188,134 +187,15 @@ import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/+esm";
188
  Change this single constant if you later choose another compatible
189
  public AIDMA/FLUX LoRA generation Space.
190
  */
191
- /*
192
- Ordered AIDMA backend failover list.
193
- The current backend remains preferred until it fails, then the next one is used.
194
- Add more compatible Spaces here later using the same object format.
195
- */
196
- function spaceOrigin(spaceId) {
197
- return `https://${spaceId.replace("/", "-").toLowerCase()}.hf.space`;
198
- }
199
-
200
- /*
201
- Twenty-slot backend pool.
202
- Every candidate is API-checked at runtime. Paused, broken, sleeping,
203
- incompatible, or unreachable Spaces are skipped automatically.
204
- */
205
- const BACKEND_SPACES = [
206
- {
207
- id: "multimodalart/flux-lora-the-explorer",
208
- origin: spaceOrigin("multimodalart/flux-lora-the-explorer"),
209
- label: "AIDMA Engine 1"
210
- },
211
- {
212
- id: "John6666/flux-lora-the-explorer",
213
- origin: spaceOrigin("John6666/flux-lora-the-explorer"),
214
- label: "AIDMA Engine 2"
215
- },
216
- {
217
- id: "Svngoku/flux-lora-the-explorer",
218
- origin: spaceOrigin("Svngoku/flux-lora-the-explorer"),
219
- label: "AIDMA Engine 3"
220
- },
221
- {
222
- id: "Nymbo/flux-lora-the-explorer",
223
- origin: spaceOrigin("Nymbo/flux-lora-the-explorer"),
224
- label: "AIDMA Engine 4"
225
- },
226
- {
227
- id: "Emuixom/flux-lora-the-explorer",
228
- origin: spaceOrigin("Emuixom/flux-lora-the-explorer"),
229
- label: "AIDMA Engine 5"
230
- },
231
- {
232
- id: "codermert/flux-lora-the-explorer",
233
- origin: spaceOrigin("codermert/flux-lora-the-explorer"),
234
- label: "AIDMA Engine 6"
235
- },
236
- {
237
- id: "reza74ii/flux-lora-the-explorer",
238
- origin: spaceOrigin("reza74ii/flux-lora-the-explorer"),
239
- label: "AIDMA Engine 7"
240
- },
241
- {
242
- id: "x2778/flux-lora-the-explorer-a",
243
- origin: spaceOrigin("x2778/flux-lora-the-explorer-a"),
244
- label: "AIDMA Engine 8"
245
- },
246
- {
247
- id: "Ckjdjdjf/flux-lora-the-explorer",
248
- origin: spaceOrigin("Ckjdjdjf/flux-lora-the-explorer"),
249
- label: "AIDMA Engine 9"
250
- },
251
- {
252
- id: "ZENLLC/flux-lora-the-explorer",
253
- origin: spaceOrigin("ZENLLC/flux-lora-the-explorer"),
254
- label: "AIDMA Engine 10"
255
- },
256
- {
257
- id: "tenet/flux-lora-the-explorer",
258
- origin: spaceOrigin("tenet/flux-lora-the-explorer"),
259
- label: "AIDMA Engine 11"
260
- },
261
- {
262
- id: "seawolf2357/flxloraexp",
263
- origin: spaceOrigin("seawolf2357/flxloraexp"),
264
- label: "AIDMA Engine 12"
265
- },
266
- {
267
- id: "ginipick/flxloraexp",
268
- origin: spaceOrigin("ginipick/flxloraexp"),
269
- label: "AIDMA Engine 13"
270
- },
271
- {
272
- id: "John6666/flux-lora-the-explorer-crash-progress",
273
- origin: spaceOrigin("John6666/flux-lora-the-explorer-crash-progress"),
274
- label: "AIDMA Engine 14"
275
- },
276
- {
277
- id: "killwithabass/flux-gay-lora-explorer",
278
- origin: spaceOrigin("killwithabass/flux-gay-lora-explorer"),
279
- label: "AIDMA Engine 15"
280
- },
281
- {
282
- id: "multimodalart/flux-lora-lab",
283
- origin: spaceOrigin("multimodalart/flux-lora-lab"),
284
- label: "AIDMA Engine 16"
285
- },
286
- {
287
- id: "prithivMLmods/FLUX-LoRA-DLC2",
288
- origin: spaceOrigin("prithivMLmods/FLUX-LoRA-DLC2"),
289
- label: "AIDMA Engine 17"
290
- },
291
- {
292
- id: "enzostvs/lora-studio",
293
- origin: spaceOrigin("enzostvs/lora-studio"),
294
- label: "AIDMA Engine 18"
295
- },
296
- {
297
- id: "ovi054/FLUX.Dev-LoRA",
298
- origin: spaceOrigin("ovi054/FLUX.Dev-LoRA"),
299
- label: "AIDMA Engine 19"
300
- },
301
- {
302
- id: "waloneai/FLUX.Dev-LoRA-Serverless",
303
- origin: spaceOrigin("waloneai/FLUX.Dev-LoRA-Serverless"),
304
- label: "AIDMA Engine 20"
305
- }
306
- ];
307
 
308
  const $ = id => document.getElementById(id);
309
  const localQueue = [];
310
  let processing = false;
311
  let client = null;
312
  let api = null;
313
- let activeBackend = null;
314
- let preferredBackendIndex = 0;
315
  let jobCounter = 0;
316
- const backendFailures = new Map();
317
- const BACKEND_FAILURE_COOLDOWN_MS = 10 * 60 * 1000;
318
- const BACKEND_CONNECT_TIMEOUT_MS = 18 * 1000;
319
 
320
  const STYLES = [
321
  "No extra style","Photorealistic","Ultra realistic photography","Cinematic",
@@ -441,12 +321,7 @@ function normalise(value) {
441
 
442
  function updateQueueUI() {
443
  $("queuedChip").textContent = `Queued: ${localQueue.length}`;
444
- if (!processing) {
445
- $("activeChip").textContent = "Active: none";
446
- return;
447
- }
448
- const engine = activeBackend?.label || "connecting";
449
- $("activeChip").textContent = `Active: ${engine}`;
450
  }
451
 
452
  function setViewer(mode, message = "") {
@@ -487,159 +362,19 @@ function buildPayload(spec, values, fallback) {
487
  });
488
  }
489
 
490
- function withTimeout(promise, milliseconds, message) {
491
- let timer;
492
- const timeout = new Promise((_, reject) => {
493
- timer = setTimeout(() => reject(new Error(message)), milliseconds);
494
- });
495
- return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
496
- }
497
-
498
- function backendIsCoolingDown(spaceId) {
499
- const failedAt = backendFailures.get(spaceId);
500
- if (!failedAt) return false;
501
- if (Date.now() - failedAt >= BACKEND_FAILURE_COOLDOWN_MS) {
502
- backendFailures.delete(spaceId);
503
- return false;
504
- }
505
- return true;
506
- }
507
-
508
- function markBackendFailed(spaceId) {
509
- backendFailures.set(spaceId, Date.now());
510
- }
511
-
512
- function backendSupportsAidmaApi(apiDescription) {
513
- const named = apiDescription?.named_endpoints || {};
514
- const names = Object.keys(named).map(normalise);
515
- const hasAdd = names.some(name => name.includes(normalise("add_custom_lora")));
516
- const hasRun = names.some(name => name.includes(normalise("run_lora")));
517
- return hasAdd && hasRun;
518
- }
519
-
520
- function resetBackendConnection() {
521
- client = null;
522
- api = null;
523
- activeBackend = null;
524
- updateQueueUI();
525
- }
526
-
527
- async function connectBackend(index) {
528
- const backend = BACKEND_SPACES[index];
529
- if (!backend) throw new Error("Unknown backend index.");
530
-
531
- if (client && activeBackend?.id === backend.id) return client;
532
-
533
- resetBackendConnection();
534
- activeBackend = backend;
535
- updateQueueUI();
536
-
537
- $("loadingTitle").textContent = `Connecting to ${backend.label}…`;
538
- $("queueStatus").textContent = backend.id;
539
-
540
- const newClient = await withTimeout(
541
- Client.connect(backend.id, {
542
- events: ["data", "status"],
543
- space_status: status => {
544
- if (status?.status && status.status !== "running") {
545
- $("queueStatus").textContent = `${backend.label}: ${status.status}`;
546
- }
547
- }
548
- }),
549
- BACKEND_CONNECT_TIMEOUT_MS,
550
- `${backend.label} connection timed out`
551
- );
552
-
553
- const newApi = await withTimeout(
554
- newClient.view_api(),
555
- BACKEND_CONNECT_TIMEOUT_MS,
556
- `${backend.label} API check timed out`
557
- );
558
-
559
- if (!backendSupportsAidmaApi(newApi)) {
560
- throw new Error(`${backend.label} is not compatible with the required AIDMA API`);
561
- }
562
-
563
- client = newClient;
564
- api = newApi;
565
- return client;
566
- }
567
-
568
- function errorText(error) {
569
- return String(error?.message || error || "").replace(/\s+/g, " ").trim();
570
- }
571
-
572
- function isCallerZeroGpuQuotaError(error) {
573
- const text = errorText(error).toLowerCase();
574
- return (
575
- text.includes("exceeded your free zerogpu quota") ||
576
- text.includes("zerogpu quota") && text.includes("left") ||
577
- text.includes("daily gpu quota") ||
578
- text.includes("try again in") && text.includes("quota")
579
- );
580
- }
581
-
582
- async function generateWithBackendFailover(job) {
583
- const errors = [];
584
-
585
- for (let offset = 0; offset < BACKEND_SPACES.length; offset++) {
586
- const index = (preferredBackendIndex + offset) % BACKEND_SPACES.length;
587
- const backend = BACKEND_SPACES[index];
588
-
589
- if (backendIsCoolingDown(backend.id)) {
590
- continue;
591
- }
592
-
593
- try {
594
- await connectBackend(index);
595
- $("queueStatus").textContent = `${backend.label}: loading AIDMA LoRA`;
596
- const loaded = await loadSelectedLora(job);
597
-
598
- $("queueStatus").textContent =
599
- `${backend.label}: waiting for remote GPU and final image`;
600
- await runGeneration(job, loaded.selectedIndex, loaded.trigger);
601
-
602
- preferredBackendIndex = index;
603
- backendFailures.delete(backend.id);
604
- return;
605
- } catch (error) {
606
- const message = errorText(error);
607
- console.error(`${backend.label} failed:`, error);
608
-
609
- /*
610
- ZeroGPU usage quota belongs to the caller, not to this individual Space.
611
- Trying another ZeroGPU Space would return the same caller-quota failure,
612
- so this specific error is surfaced immediately.
613
- */
614
- if (isCallerZeroGpuQuotaError(error)) {
615
- resetBackendConnection();
616
- throw new Error(message);
617
- }
618
-
619
- errors.push(`${backend.label}: ${message || "unknown error"}`);
620
- markBackendFailed(backend.id);
621
- resetBackendConnection();
622
-
623
- if (offset + 1 < BACKEND_SPACES.length) {
624
- let nextBackend = null;
625
- for (let ahead = offset + 1; ahead < BACKEND_SPACES.length; ahead++) {
626
- const nextIndex = (preferredBackendIndex + ahead) % BACKEND_SPACES.length;
627
- const candidate = BACKEND_SPACES[nextIndex];
628
- if (!backendIsCoolingDown(candidate.id)) {
629
- nextBackend = candidate;
630
- break;
631
- }
632
- }
633
- $("queueStatus").textContent = nextBackend
634
- ? `${backend.label} failed. Switching to ${nextBackend.label}…`
635
- : `${backend.label} failed. No healthy backend remains.`;
636
  }
637
  }
638
- }
639
-
640
- throw new Error(
641
- "All configured AIDMA Spaces failed. " + errors.join(" | ")
642
- );
643
  }
644
 
645
  function snapshotJob() {
@@ -679,8 +414,8 @@ function remoteFileUrl(value) {
679
  if (/^(https?:\/\/|blob:|data:image\/)/i.test(text)) return text;
680
 
681
  // Modern Gradio may return a relative file route.
682
- if (text.startsWith("/gradio_api/")) return `${activeBackend?.origin || ""}${text}`;
683
- if (text.startsWith("gradio_api/")) return `${activeBackend?.origin || ""}/${text}`;
684
 
685
  // Some Gradio versions return only the server-side temporary path.
686
  if (
@@ -688,7 +423,7 @@ function remoteFileUrl(value) {
688
  text.includes("/gradio/") ||
689
  /\.(png|jpe?g|webp|gif|bmp)(\?|$)/i.test(text)
690
  ) {
691
- return `${activeBackend?.origin || ""}/gradio_api/file=${encodeURI(text)}`;
692
  }
693
 
694
  return null;
@@ -853,7 +588,10 @@ async function processQueue() {
853
  setViewer("loading", `Job #${job.id} is running.`);
854
 
855
  try {
856
- await generateWithBackendFailover(job);
 
 
 
857
  } catch (error) {
858
  console.error(error);
859
  $("status").className = "error";
@@ -863,7 +601,8 @@ async function processQueue() {
863
  '<div style="margin-top:8px">' + escapeHtml(error?.message || String(error)) + '</div>';
864
  setViewer("empty");
865
  // Force a clean connection for the next queued job.
866
- resetBackendConnection();
 
867
  }
868
  }
869
 
 
150
  <div class="queuebox">
151
  <span class="qchip" id="queuedChip">Queued: 0</span>
152
  <span class="qchip" id="activeChip">Active: none</span>
 
153
  </div>
154
 
155
  <div class="fine">
156
+ This Static Space keeps its own FIFO queue inside your browser tab. The remote GPU is provided by the connected public Hugging Face generation Space.
157
  </div>
158
  </div>
159
 
 
187
  Change this single constant if you later choose another compatible
188
  public AIDMA/FLUX LoRA generation Space.
189
  */
190
+ const BACKEND_SPACE = "multimodalart/flux-lora-the-explorer";
191
+ const BACKEND_ORIGIN = "https://multimodalart-flux-lora-the-explorer.hf.space";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
  const $ = id => document.getElementById(id);
194
  const localQueue = [];
195
  let processing = false;
196
  let client = null;
197
  let api = null;
 
 
198
  let jobCounter = 0;
 
 
 
199
 
200
  const STYLES = [
201
  "No extra style","Photorealistic","Ultra realistic photography","Cinematic",
 
321
 
322
  function updateQueueUI() {
323
  $("queuedChip").textContent = `Queued: ${localQueue.length}`;
324
+ $("activeChip").textContent = processing ? "Active: generating" : "Active: none";
 
 
 
 
 
325
  }
326
 
327
  function setViewer(mode, message = "") {
 
362
  });
363
  }
364
 
365
+ async function connectBackend() {
366
+ if (client) return client;
367
+ $("loadingTitle").textContent = "Connecting to the remote AIDMA engine…";
368
+ client = await Client.connect(BACKEND_SPACE, {
369
+ events: ["data", "status"],
370
+ space_status: status => {
371
+ if (status?.status && status.status !== "running") {
372
+ $("queueStatus").textContent = `Remote Space: ${status.status}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  }
374
  }
375
+ });
376
+ api = await client.view_api();
377
+ return client;
 
 
378
  }
379
 
380
  function snapshotJob() {
 
414
  if (/^(https?:\/\/|blob:|data:image\/)/i.test(text)) return text;
415
 
416
  // Modern Gradio may return a relative file route.
417
+ if (text.startsWith("/gradio_api/")) return `${BACKEND_ORIGIN}${text}`;
418
+ if (text.startsWith("gradio_api/")) return `${BACKEND_ORIGIN}/${text}`;
419
 
420
  // Some Gradio versions return only the server-side temporary path.
421
  if (
 
423
  text.includes("/gradio/") ||
424
  /\.(png|jpe?g|webp|gif|bmp)(\?|$)/i.test(text)
425
  ) {
426
+ return `${BACKEND_ORIGIN}/gradio_api/file=${encodeURI(text)}`;
427
  }
428
 
429
  return null;
 
588
  setViewer("loading", `Job #${job.id} is running.`);
589
 
590
  try {
591
+ await connectBackend();
592
+ const loaded = await loadSelectedLora(job);
593
+ $("queueStatus").textContent = "Submitting to remote GPU and waiting for final output";
594
+ await runGeneration(job, loaded.selectedIndex, loaded.trigger);
595
  } catch (error) {
596
  console.error(error);
597
  $("status").className = "error";
 
601
  '<div style="margin-top:8px">' + escapeHtml(error?.message || String(error)) + '</div>';
602
  setViewer("empty");
603
  // Force a clean connection for the next queued job.
604
+ client = null;
605
+ api = null;
606
  }
607
  }
608