Spaces:
Running
Running
lllll
Browse files- index(2).html +57 -187
index(2).html
CHANGED
|
@@ -42,7 +42,6 @@
|
|
| 42 |
border-radius:14px;outline:none;font:inherit;transition:.18s ease
|
| 43 |
}
|
| 44 |
textarea{min-height:168px;resize:vertical;padding:14px;line-height:1.52}
|
| 45 |
-
textarea.token-pool{min-height:108px;font:12px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace}
|
| 46 |
select,input{height:48px;padding:0 13px}
|
| 47 |
textarea:focus,select:focus,input:focus{border-color:rgba(139,108,255,.9);box-shadow:0 0 0 4px rgba(139,108,255,.12)}
|
| 48 |
.row{display:grid;grid-template-columns:1fr 1fr;gap:11px}
|
|
@@ -107,11 +106,6 @@
|
|
| 107 |
</select>
|
| 108 |
</div>
|
| 109 |
|
| 110 |
-
<div class="field">
|
| 111 |
-
<label for="hfTokens">Hugging Face tokens · one per line · max 50</label>
|
| 112 |
-
<textarea id="hfTokens" class="token-pool" autocomplete="off" autocapitalize="off" spellcheck="false" placeholder="hf_token_1 hf_token_2 hf_token_3"></textarea>
|
| 113 |
-
</div>
|
| 114 |
-
|
| 115 |
<div class="field">
|
| 116 |
<label for="prompt">Prompt</label>
|
| 117 |
<textarea id="prompt" maxlength="5000" placeholder="Describe the subject, environment, camera, lighting, mood and composition..."></textarea>
|
|
@@ -156,12 +150,11 @@
|
|
| 156 |
<div class="queuebox">
|
| 157 |
<span class="qchip" id="queuedChip">Queued: 0</span>
|
| 158 |
<span class="qchip" id="activeChip">Active: none</span>
|
| 159 |
-
<span class="qchip" id="tokenChip">Token: anonymous</span>
|
| 160 |
<span class="qchip" id="engineChip">Engines: 20</span>
|
| 161 |
</div>
|
| 162 |
|
| 163 |
<div class="fine">
|
| 164 |
-
This Static Space keeps a private FIFO queue in your browser
|
| 165 |
</div>
|
| 166 |
</div>
|
| 167 |
|
|
@@ -320,11 +313,6 @@ let api = null;
|
|
| 320 |
let activeBackend = null;
|
| 321 |
let preferredBackendIndex = 0;
|
| 322 |
let jobCounter = 0;
|
| 323 |
-
const MAX_HF_TOKENS = 50;
|
| 324 |
-
let hfTokens = [];
|
| 325 |
-
let activeTokenIndex = 0;
|
| 326 |
-
let connectedTokenIndex = -1;
|
| 327 |
-
let tokenPoolSignature = "";
|
| 328 |
const backendFailures = new Map();
|
| 329 |
const BACKEND_FAILURE_COOLDOWN_MS = 10 * 60 * 1000;
|
| 330 |
const BACKEND_CONNECT_TIMEOUT_MS = 18 * 1000;
|
|
@@ -451,42 +439,8 @@ function normalise(value) {
|
|
| 451 |
return String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, "");
|
| 452 |
}
|
| 453 |
|
| 454 |
-
function parseHfTokens(value) {
|
| 455 |
-
const unique = new Set();
|
| 456 |
-
for (const part of String(value || "").split(/[\s,;]+/)) {
|
| 457 |
-
const token = part.trim();
|
| 458 |
-
if (!token || !token.startsWith("hf_") || unique.has(token)) continue;
|
| 459 |
-
unique.add(token);
|
| 460 |
-
if (unique.size >= MAX_HF_TOKENS) break;
|
| 461 |
-
}
|
| 462 |
-
return [...unique];
|
| 463 |
-
}
|
| 464 |
-
|
| 465 |
-
function syncTokenPool() {
|
| 466 |
-
const nextTokens = parseHfTokens($("hfTokens").value);
|
| 467 |
-
const nextSignature = nextTokens.join("\n");
|
| 468 |
-
|
| 469 |
-
if (nextSignature !== tokenPoolSignature) {
|
| 470 |
-
hfTokens = nextTokens;
|
| 471 |
-
tokenPoolSignature = nextSignature;
|
| 472 |
-
activeTokenIndex = 0;
|
| 473 |
-
resetBackendConnection();
|
| 474 |
-
}
|
| 475 |
-
|
| 476 |
-
updateQueueUI();
|
| 477 |
-
return hfTokens;
|
| 478 |
-
}
|
| 479 |
-
|
| 480 |
-
function activeToken() {
|
| 481 |
-
return hfTokens[activeTokenIndex] || "";
|
| 482 |
-
}
|
| 483 |
-
|
| 484 |
function updateQueueUI() {
|
| 485 |
$("queuedChip").textContent = `Queued: ${localQueue.length}`;
|
| 486 |
-
$("tokenChip").textContent = hfTokens.length
|
| 487 |
-
? `Token: ${activeTokenIndex + 1}/${hfTokens.length}`
|
| 488 |
-
: "Token: anonymous";
|
| 489 |
-
|
| 490 |
if (!processing) {
|
| 491 |
$("activeChip").textContent = "Active: none";
|
| 492 |
return;
|
|
@@ -567,7 +521,6 @@ function resetBackendConnection() {
|
|
| 567 |
client = null;
|
| 568 |
api = null;
|
| 569 |
activeBackend = null;
|
| 570 |
-
connectedTokenIndex = -1;
|
| 571 |
updateQueueUI();
|
| 572 |
}
|
| 573 |
|
|
@@ -575,11 +528,7 @@ async function connectBackend(index) {
|
|
| 575 |
const backend = BACKEND_SPACES[index];
|
| 576 |
if (!backend) throw new Error("Unknown backend index.");
|
| 577 |
|
| 578 |
-
if (
|
| 579 |
-
client &&
|
| 580 |
-
activeBackend?.id === backend.id &&
|
| 581 |
-
connectedTokenIndex === activeTokenIndex
|
| 582 |
-
) return client;
|
| 583 |
|
| 584 |
resetBackendConnection();
|
| 585 |
activeBackend = backend;
|
|
@@ -588,22 +537,15 @@ async function connectBackend(index) {
|
|
| 588 |
$("loadingTitle").textContent = `Connecting to ${backend.label}…`;
|
| 589 |
$("queueStatus").textContent = backend.id;
|
| 590 |
|
| 591 |
-
const handleSpaceStatus = status => {
|
| 592 |
-
if (status?.status && status.status !== "running") {
|
| 593 |
-
$("queueStatus").textContent = `${backend.label}: ${status.status}`;
|
| 594 |
-
}
|
| 595 |
-
};
|
| 596 |
-
|
| 597 |
-
const connectOptions = {
|
| 598 |
-
events: ["data", "status"],
|
| 599 |
-
space_status: handleSpaceStatus,
|
| 600 |
-
status_callback: handleSpaceStatus
|
| 601 |
-
};
|
| 602 |
-
const token = activeToken();
|
| 603 |
-
if (token) connectOptions.token = token;
|
| 604 |
-
|
| 605 |
const newClient = await withTimeout(
|
| 606 |
-
Client.connect(backend.id,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 607 |
BACKEND_CONNECT_TIMEOUT_MS,
|
| 608 |
`${backend.label} connection timed out`
|
| 609 |
);
|
|
@@ -620,7 +562,6 @@ async function connectBackend(index) {
|
|
| 620 |
|
| 621 |
client = newClient;
|
| 622 |
api = newApi;
|
| 623 |
-
connectedTokenIndex = activeTokenIndex;
|
| 624 |
return client;
|
| 625 |
}
|
| 626 |
|
|
@@ -634,138 +575,74 @@ function isCallerZeroGpuQuotaError(error) {
|
|
| 634 |
text.includes("exceeded your free zerogpu quota") ||
|
| 635 |
text.includes("zerogpu quota") && text.includes("left") ||
|
| 636 |
text.includes("daily gpu quota") ||
|
| 637 |
-
text.includes("gpu quota") && text.includes("exceeded") ||
|
| 638 |
text.includes("try again in") && text.includes("quota")
|
| 639 |
);
|
| 640 |
}
|
| 641 |
|
| 642 |
-
function isTokenRateLimitError(error) {
|
| 643 |
-
const text = errorText(error).toLowerCase();
|
| 644 |
-
return (
|
| 645 |
-
isCallerZeroGpuQuotaError(error) ||
|
| 646 |
-
text.includes("too many requests") ||
|
| 647 |
-
text.includes("rate limit") ||
|
| 648 |
-
text.includes("rate-limit") ||
|
| 649 |
-
/(^|\D)429(\D|$)/.test(text)
|
| 650 |
-
);
|
| 651 |
-
}
|
| 652 |
-
|
| 653 |
-
function isTokenAuthenticationError(error) {
|
| 654 |
-
const text = errorText(error).toLowerCase();
|
| 655 |
-
return (
|
| 656 |
-
text.includes("invalid token") ||
|
| 657 |
-
text.includes("token is invalid") ||
|
| 658 |
-
text.includes("unauthorized") ||
|
| 659 |
-
text.includes("authentication failed") ||
|
| 660 |
-
text.includes("invalid credentials") ||
|
| 661 |
-
/(^|\D)401(\D|$)/.test(text)
|
| 662 |
-
);
|
| 663 |
-
}
|
| 664 |
-
|
| 665 |
-
function rotateToNextToken(reason) {
|
| 666 |
-
if (!hfTokens.length || activeTokenIndex + 1 >= hfTokens.length) return false;
|
| 667 |
-
|
| 668 |
-
const previousNumber = activeTokenIndex + 1;
|
| 669 |
-
activeTokenIndex += 1;
|
| 670 |
-
resetBackendConnection();
|
| 671 |
-
$("queueStatus").textContent =
|
| 672 |
-
`Token ${previousNumber} ${reason}. Switching to token ${activeTokenIndex + 1}/${hfTokens.length}…`;
|
| 673 |
-
updateQueueUI();
|
| 674 |
-
return true;
|
| 675 |
-
}
|
| 676 |
-
|
| 677 |
async function generateWithBackendFailover(job) {
|
| 678 |
const errors = [];
|
| 679 |
-
const remainingTokenAttempts = hfTokens.length
|
| 680 |
-
? hfTokens.length - activeTokenIndex
|
| 681 |
-
: 1;
|
| 682 |
-
let attemptedTokens = 0;
|
| 683 |
-
|
| 684 |
-
while (attemptedTokens < remainingTokenAttempts) {
|
| 685 |
-
let switchedToken = false;
|
| 686 |
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
try {
|
| 696 |
-
await connectBackend(index);
|
| 697 |
-
$("queueStatus").textContent = `${backend.label}: loading AIDMA LoRA`;
|
| 698 |
-
const loaded = await loadSelectedLora(job);
|
| 699 |
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
|
|
|
| 703 |
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
} catch (error) {
|
| 708 |
-
const message = errorText(error);
|
| 709 |
-
console.error(`${backend.label} failed:`, error);
|
| 710 |
|
| 711 |
-
|
| 712 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 713 |
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
const reason = authenticationFailure ? "was rejected" : "reached its quota/rate limit";
|
| 718 |
|
| 719 |
-
|
| 720 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
break;
|
| 722 |
}
|
| 723 |
-
|
| 724 |
-
resetBackendConnection();
|
| 725 |
-
activeTokenIndex = 0;
|
| 726 |
-
updateQueueUI();
|
| 727 |
-
throw new Error(
|
| 728 |
-
"All configured Hugging Face tokens reached quota/rate-limit or were rejected. " +
|
| 729 |
-
errors.join(" | ")
|
| 730 |
-
);
|
| 731 |
-
}
|
| 732 |
-
|
| 733 |
-
if (!hfTokens.length && quotaOrRateLimit) {
|
| 734 |
-
resetBackendConnection();
|
| 735 |
-
throw new Error(message);
|
| 736 |
-
}
|
| 737 |
-
|
| 738 |
-
errors.push(`${backend.label}: ${message || "unknown error"}`);
|
| 739 |
-
markBackendFailed(backend.id);
|
| 740 |
-
resetBackendConnection();
|
| 741 |
-
|
| 742 |
-
if (offset + 1 < BACKEND_SPACES.length) {
|
| 743 |
-
let nextBackend = null;
|
| 744 |
-
for (let ahead = offset + 1; ahead < BACKEND_SPACES.length; ahead++) {
|
| 745 |
-
const nextIndex = (preferredBackendIndex + ahead) % BACKEND_SPACES.length;
|
| 746 |
-
const candidate = BACKEND_SPACES[nextIndex];
|
| 747 |
-
if (!backendIsCoolingDown(candidate.id)) {
|
| 748 |
-
nextBackend = candidate;
|
| 749 |
-
break;
|
| 750 |
-
}
|
| 751 |
-
}
|
| 752 |
-
$("queueStatus").textContent = nextBackend
|
| 753 |
-
? `${backend.label} failed. Switching to ${nextBackend.label}…`
|
| 754 |
-
: `${backend.label} failed. No healthy backend remains.`;
|
| 755 |
}
|
|
|
|
|
|
|
|
|
|
| 756 |
}
|
| 757 |
}
|
| 758 |
-
|
| 759 |
-
if (switchedToken) continue;
|
| 760 |
-
|
| 761 |
-
throw new Error(
|
| 762 |
-
"All configured AIDMA Spaces failed. " + errors.join(" | ")
|
| 763 |
-
);
|
| 764 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 765 |
}
|
| 766 |
|
| 767 |
function snapshotJob() {
|
| 768 |
-
syncTokenPool();
|
| 769 |
const prompt = $("prompt").value.trim();
|
| 770 |
if (!prompt) throw new Error("Write a prompt first.");
|
| 771 |
|
|
@@ -1015,13 +892,6 @@ $("generate").addEventListener("click", () => {
|
|
| 1015 |
}
|
| 1016 |
});
|
| 1017 |
|
| 1018 |
-
$("hfTokens").addEventListener("input", () => {
|
| 1019 |
-
const preview = parseHfTokens($("hfTokens").value);
|
| 1020 |
-
$("tokenChip").textContent = preview.length
|
| 1021 |
-
? `Tokens entered: ${preview.length}/${MAX_HF_TOKENS}`
|
| 1022 |
-
: "Token: anonymous";
|
| 1023 |
-
});
|
| 1024 |
-
|
| 1025 |
$("prompt").addEventListener("keydown", event => {
|
| 1026 |
if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
|
| 1027 |
event.preventDefault();
|
|
@@ -1032,4 +902,4 @@ $("prompt").addEventListener("keydown", event => {
|
|
| 1032 |
updateQueueUI();
|
| 1033 |
</script>
|
| 1034 |
</body>
|
| 1035 |
-
</html>
|
|
|
|
| 42 |
border-radius:14px;outline:none;font:inherit;transition:.18s ease
|
| 43 |
}
|
| 44 |
textarea{min-height:168px;resize:vertical;padding:14px;line-height:1.52}
|
|
|
|
| 45 |
select,input{height:48px;padding:0 13px}
|
| 46 |
textarea:focus,select:focus,input:focus{border-color:rgba(139,108,255,.9);box-shadow:0 0 0 4px rgba(139,108,255,.12)}
|
| 47 |
.row{display:grid;grid-template-columns:1fr 1fr;gap:11px}
|
|
|
|
| 106 |
</select>
|
| 107 |
</div>
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
<div class="field">
|
| 110 |
<label for="prompt">Prompt</label>
|
| 111 |
<textarea id="prompt" maxlength="5000" placeholder="Describe the subject, environment, camera, lighting, mood and composition..."></textarea>
|
|
|
|
| 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 |
|
|
|
|
| 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;
|
|
|
|
| 439 |
return String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, "");
|
| 440 |
}
|
| 441 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 442 |
function updateQueueUI() {
|
| 443 |
$("queuedChip").textContent = `Queued: ${localQueue.length}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
if (!processing) {
|
| 445 |
$("activeChip").textContent = "Active: none";
|
| 446 |
return;
|
|
|
|
| 521 |
client = null;
|
| 522 |
api = null;
|
| 523 |
activeBackend = null;
|
|
|
|
| 524 |
updateQueueUI();
|
| 525 |
}
|
| 526 |
|
|
|
|
| 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;
|
|
|
|
| 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 |
);
|
|
|
|
| 562 |
|
| 563 |
client = newClient;
|
| 564 |
api = newApi;
|
|
|
|
| 565 |
return client;
|
| 566 |
}
|
| 567 |
|
|
|
|
| 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() {
|
|
|
|
| 646 |
const prompt = $("prompt").value.trim();
|
| 647 |
if (!prompt) throw new Error("Write a prompt first.");
|
| 648 |
|
|
|
|
| 892 |
}
|
| 893 |
});
|
| 894 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 895 |
$("prompt").addEventListener("keydown", event => {
|
| 896 |
if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
|
| 897 |
event.preventDefault();
|
|
|
|
| 902 |
updateQueueUI();
|
| 903 |
</script>
|
| 904 |
</body>
|
| 905 |
+
</html>
|