Spaces:
Running
Running
Gate first model download behind start button
Browse files- static-app.js +222 -78
- static.html +45 -16
static-app.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
const $ = (id) => document.getElementById(id);
|
| 4 |
|
| 5 |
const statusEl = $("status");
|
| 6 |
-
const imageEl = $("image");
|
| 7 |
-
const canvasEl = $("image-canvas");
|
| 8 |
-
const placeholderEl = $("placeholder");
|
| 9 |
-
const
|
| 10 |
-
const
|
| 11 |
-
const
|
|
|
|
|
|
|
|
|
|
| 12 |
const elapsedEl = $("elapsed");
|
| 13 |
const initImageInput = $("init-image");
|
| 14 |
const initImageLabelEl = $("init-image-label");
|
|
@@ -53,10 +54,15 @@ const THEME_STORAGE_KEY = "flux2-static-theme";
|
|
| 53 |
const GALLERY_LIMIT = 24;
|
| 54 |
const DEFAULT_LOCAL_MODEL_BASE_URL = "/models";
|
| 55 |
const DEFAULT_LOCAL_RUNTIME_BASE_URL = "/runtime";
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
let
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
let currentSourceUrl = null;
|
| 61 |
let backgroundWarmPromise = null;
|
| 62 |
let backgroundWarmKey = "";
|
|
@@ -82,7 +88,9 @@ let backgroundVaePrepareKey = "";
|
|
| 82 |
let backgroundVaeEncoderPreparePromise = null;
|
| 83 |
let backgroundVaeEncoderPrepareKey = "";
|
| 84 |
let backgroundDeepPrepareTimer = 0;
|
| 85 |
-
let backgroundDeepPrepareToken = 0;
|
|
|
|
|
|
|
| 86 |
const preparedInitImageCache = new WeakMap();
|
| 87 |
const generationResultCache = new Map();
|
| 88 |
const tokenizedPromptCache = new Map();
|
|
@@ -422,19 +430,72 @@ function setStatus(lines, isError = false) {
|
|
| 422 |
statusEl.classList.toggle("error", Boolean(isError));
|
| 423 |
}
|
| 424 |
|
| 425 |
-
function setButtonDisabled(button, disabled) {
|
| 426 |
-
if (button) button.disabled = disabled;
|
| 427 |
-
}
|
| 428 |
-
|
| 429 |
-
function setElapsed(text, active = false) {
|
| 430 |
-
if (!elapsedEl) return;
|
| 431 |
-
elapsedEl.textContent = text;
|
| 432 |
-
elapsedEl.classList.toggle("active", active);
|
| 433 |
-
}
|
| 434 |
-
|
| 435 |
-
function
|
| 436 |
-
if (
|
| 437 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
return 0;
|
| 439 |
}
|
| 440 |
const seconds = (performance.now() - runTimerStarted) / 1000;
|
|
@@ -641,12 +702,60 @@ function initTheme() {
|
|
| 641 |
applyTheme(requestedTheme || "dark", { persist: false });
|
| 642 |
}
|
| 643 |
|
| 644 |
-
function isAbortError(err) {
|
| 645 |
-
return String(err?.stack || err?.message || err || "").includes("ABORTED_BY_CLIENT");
|
| 646 |
-
}
|
| 647 |
-
|
| 648 |
-
function
|
| 649 |
-
return new Promise((resolve, reject) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 650 |
const deadline = performance.now() + 30000;
|
| 651 |
const tick = () => {
|
| 652 |
if (window.flux2Engine && typeof window.flux2Engine.init === "function") {
|
|
@@ -724,6 +833,7 @@ function maskFromLength(length, seqLen) {
|
|
| 724 |
async function loadTokenizer() {
|
| 725 |
if (tokenizerPromise) return tokenizerPromise;
|
| 726 |
setLoadPhase("Loading tokenizer");
|
|
|
|
| 727 |
const modelBaseUrl = configuredModelBaseUrl();
|
| 728 |
const tokenizerSpec = tokenizerLoadSpec(modelBaseUrl);
|
| 729 |
env.allowLocalModels = !tokenizerSpec.remote;
|
|
@@ -740,6 +850,14 @@ async function loadTokenizer() {
|
|
| 740 |
return tokenizerPromise;
|
| 741 |
}
|
| 742 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 743 |
async function tokenizePrompt(prompt, seqLen) {
|
| 744 |
const cachePayload = JSON.stringify({ model: "flux.2-klein-4b", text_seq_len: seqLen, prompt });
|
| 745 |
const promptCacheKey = await sha256Hex(cachePayload);
|
|
@@ -779,6 +897,7 @@ async function tokenizePrompt(prompt, seqLen) {
|
|
| 779 |
async function initEngine() {
|
| 780 |
if (engineReadyPromise) return engineReadyPromise;
|
| 781 |
engineReadyPromise = (async () => {
|
|
|
|
| 782 |
const engine = await waitForEngine();
|
| 783 |
const modelBaseUrl = configuredModelBaseUrl();
|
| 784 |
const runtimeBaseUrl = configuredRuntimeBaseUrl(modelBaseUrl);
|
|
@@ -1418,9 +1537,10 @@ function shouldTextContextPrepareRequest(request) {
|
|
| 1418 |
return request.transformerBackend === "custom-lowbit-webgpu";
|
| 1419 |
}
|
| 1420 |
|
| 1421 |
-
function startBackgroundTextContextPrepareForCurrentSettings(request = readRequest()) {
|
| 1422 |
-
|
| 1423 |
-
|
|
|
|
| 1424 |
const key = textContextPrepareKeyForRequest(prepareRequest);
|
| 1425 |
if (backgroundTextContextPreparePromise && backgroundTextContextPrepareKey === key) return true;
|
| 1426 |
backgroundTextContextPrepareKey = key;
|
|
@@ -1483,9 +1603,10 @@ function shouldTransformerDispatchWarmupRequest(request) {
|
|
| 1483 |
return plannedMaxDimension <= 512;
|
| 1484 |
}
|
| 1485 |
|
| 1486 |
-
function startBackgroundTransformerSetupPrepareForCurrentSettings(request = readRequest()) {
|
| 1487 |
-
|
| 1488 |
-
|
|
|
|
| 1489 |
const dispatchWarmup = shouldTransformerDispatchWarmupRequest(prepareRequest);
|
| 1490 |
const key = `${transformerSetupPrepareKeyForRequest(prepareRequest)}|dispatch=${dispatchWarmup ? 1 : 0}`;
|
| 1491 |
if (backgroundTransformerSetupPreparePromise && backgroundTransformerSetupPrepareKey === key) return true;
|
|
@@ -1569,8 +1690,9 @@ function shouldAutoWarmRequest(request) {
|
|
| 1569 |
maxDimension <= 256;
|
| 1570 |
}
|
| 1571 |
|
| 1572 |
-
function startBackgroundAssetPrepareForCurrentSettings(request = readRequest()) {
|
| 1573 |
-
if (!
|
|
|
|
| 1574 |
const key = assetPrepareKeyForRequest(request);
|
| 1575 |
if (backgroundAssetPreparePromise && backgroundAssetPrepareKey === key) return true;
|
| 1576 |
backgroundAssetPrepareKey = key;
|
|
@@ -1631,8 +1753,9 @@ function shouldVaeEncoderPrepareRequest(request) {
|
|
| 1631 |
return true;
|
| 1632 |
}
|
| 1633 |
|
| 1634 |
-
function startBackgroundVaeEncoderPrepareForCurrentSettings(request = readRequest()) {
|
| 1635 |
-
if (!
|
|
|
|
| 1636 |
const key = vaeEncoderPrepareKeyForRequest(request);
|
| 1637 |
if (backgroundVaeEncoderPreparePromise && backgroundVaeEncoderPrepareKey === key) return true;
|
| 1638 |
backgroundVaeEncoderPrepareKey = key;
|
|
@@ -1695,8 +1818,9 @@ async function waitForStartedPreparePromises() {
|
|
| 1695 |
}));
|
| 1696 |
}
|
| 1697 |
|
| 1698 |
-
function startStageAndVaePrepareForRequest(request) {
|
| 1699 |
-
|
|
|
|
| 1700 |
const startedTransformer = startBackgroundTransformerSetupPrepareForCurrentSettings(request);
|
| 1701 |
if (request.hasInitImage && shouldVaeEncoderPrepareRequest(request)) {
|
| 1702 |
const startedVaeEncoder = startBackgroundVaeEncoderPrepareForCurrentSettings(request);
|
|
@@ -1706,8 +1830,9 @@ function startStageAndVaePrepareForRequest(request) {
|
|
| 1706 |
return startedText || startedTransformer || startedVae;
|
| 1707 |
}
|
| 1708 |
|
| 1709 |
-
function startBackgroundVaePrepareForCurrentSettings(request = readRequest()) {
|
| 1710 |
-
|
|
|
|
| 1711 |
if (!shouldVaePrepareRequest(prepareRequest)) return false;
|
| 1712 |
const dispatchWarmup = shouldVaeDispatchWarmupRequest(prepareRequest);
|
| 1713 |
const key = `${vaePrepareKeyForRequest(prepareRequest)}|dispatch=${dispatchWarmup ? 1 : 0}`;
|
|
@@ -1770,8 +1895,9 @@ function cancelBackgroundWarm(message = "") {
|
|
| 1770 |
return true;
|
| 1771 |
}
|
| 1772 |
|
| 1773 |
-
function startBackgroundWarmForCurrentSettings() {
|
| 1774 |
-
if (
|
|
|
|
| 1775 |
const request = readRequest();
|
| 1776 |
if (!shouldAutoWarmRequest(request)) return false;
|
| 1777 |
const key = warmKeyForRequest(request);
|
|
@@ -1809,8 +1935,9 @@ function startBackgroundWarmForCurrentSettings() {
|
|
| 1809 |
return true;
|
| 1810 |
}
|
| 1811 |
|
| 1812 |
-
function scheduleBackgroundStageAndVaePrepareForCurrentSettings(request = readRequest(), delayMs = 750) {
|
| 1813 |
-
if (
|
|
|
|
| 1814 |
if (backgroundDeepPrepareTimer) {
|
| 1815 |
clearTimeout(backgroundDeepPrepareTimer);
|
| 1816 |
backgroundDeepPrepareTimer = 0;
|
|
@@ -1847,9 +1974,10 @@ function scheduleBackgroundStageAndVaePrepareForCurrentSettings(request = readRe
|
|
| 1847 |
return true;
|
| 1848 |
}
|
| 1849 |
|
| 1850 |
-
function scheduleAutoWarmForCurrentSettings(delayMs = 350) {
|
| 1851 |
-
clearWarmSchedule();
|
| 1852 |
-
if (
|
|
|
|
| 1853 |
const request = readRequest();
|
| 1854 |
startBackgroundAssetPrepareForCurrentSettings(request);
|
| 1855 |
scheduleBackgroundStageAndVaePrepareForCurrentSettings(request, request.hasInitImage ? delayMs : 750);
|
|
@@ -2344,9 +2472,10 @@ function displayGenerationResult(result, elapsed, seqLen, heading = "Elapsed", e
|
|
| 2344 |
}
|
| 2345 |
}
|
| 2346 |
|
| 2347 |
-
async function generate() {
|
| 2348 |
-
|
| 2349 |
-
const
|
|
|
|
| 2350 |
runButton.disabled = true;
|
| 2351 |
setButtonDisabled(warmButton, true);
|
| 2352 |
abortButton.disabled = false;
|
|
@@ -2621,8 +2750,9 @@ async function waitForBackgroundVaePrepare(request) {
|
|
| 2621 |
return (performance.now() - started) / 1000;
|
| 2622 |
}
|
| 2623 |
|
| 2624 |
-
async function warmCurrentSettings({ background = false } = {}) {
|
| 2625 |
-
|
|
|
|
| 2626 |
const { warmRequest, warmProfile } = warmPlanForRequest(request);
|
| 2627 |
const { engine, init } = await initEngine();
|
| 2628 |
const started = performance.now();
|
|
@@ -2730,19 +2860,20 @@ async function prepareInitialFastPath(request) {
|
|
| 2730 |
}
|
| 2731 |
|
| 2732 |
async function boot() {
|
| 2733 |
-
|
|
|
|
|
|
|
| 2734 |
try {
|
| 2735 |
installLoadProgressFetch();
|
| 2736 |
setStatus("Loading browser runtime");
|
| 2737 |
setLoadPhase("Loading browser runtime");
|
| 2738 |
const [{ init }, tokenizer] = await Promise.all([initEngine(), loadTokenizer()]);
|
| 2739 |
const bootRequest = readRequest();
|
| 2740 |
-
const fastPathPrepared = await prepareInitialFastPath(bootRequest);
|
| 2741 |
-
|
| 2742 |
-
|
| 2743 |
-
|
| 2744 |
-
"
|
| 2745 |
-
fastPathPrepared ? "Fast path prepared" : "",
|
| 2746 |
`Model: ${init.config.model_variant || init.config.model_name}`,
|
| 2747 |
seedInputLine(),
|
| 2748 |
`Tokenizer: ${tokenizer.constructor?.name || "loaded"}`,
|
|
@@ -2754,18 +2885,31 @@ async function boot() {
|
|
| 2754 |
}
|
| 2755 |
if (shouldAutoWarmRequest(bootRequest)) startBackgroundWarmForCurrentSettings();
|
| 2756 |
finishLoadProgress(fastPathPrepared ? "Fast path prepared" : "Ready");
|
|
|
|
| 2757 |
} catch (err) {
|
|
|
|
|
|
|
|
|
|
| 2758 |
runButton.disabled = true;
|
| 2759 |
setButtonDisabled(warmButton, true);
|
|
|
|
| 2760 |
setStatus(err?.stack || err?.message || String(err), true);
|
| 2761 |
finishLoadProgress("Load failed", false);
|
|
|
|
|
|
|
| 2762 |
}
|
| 2763 |
}
|
| 2764 |
-
|
| 2765 |
-
|
| 2766 |
-
if (
|
| 2767 |
-
|
| 2768 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2769 |
warmCurrentSettings().catch((err) => {
|
| 2770 |
if (isAbortError(err)) return;
|
| 2771 |
placeholderEl.hidden = false;
|
|
@@ -2779,9 +2923,9 @@ warmButton?.addEventListener("click", () => {
|
|
| 2779 |
});
|
| 2780 |
});
|
| 2781 |
|
| 2782 |
-
runButton.addEventListener("click", () => {
|
| 2783 |
-
if (foregroundBusy) return;
|
| 2784 |
-
clearWarmSchedule();
|
| 2785 |
foregroundBusy = true;
|
| 2786 |
startRunTimer();
|
| 2787 |
generate().catch((err) => {
|
|
@@ -2961,7 +3105,7 @@ galleryGridEl?.addEventListener("click", (event) => {
|
|
| 2961 |
});
|
| 2962 |
});
|
| 2963 |
|
| 2964 |
-
initTheme();
|
| 2965 |
-
applyUrlDefaults();
|
| 2966 |
-
updateStrengthDisplay();
|
| 2967 |
-
|
|
|
|
| 1 |
+
const $ = (id) => document.getElementById(id);
|
|
|
|
|
|
|
| 2 |
|
| 3 |
const statusEl = $("status");
|
| 4 |
+
const imageEl = $("image");
|
| 5 |
+
const canvasEl = $("image-canvas");
|
| 6 |
+
const placeholderEl = $("placeholder");
|
| 7 |
+
const downloadGateEl = $("download-gate");
|
| 8 |
+
const startDownloadButton = $("start-download");
|
| 9 |
+
const actionsEl = $("actions");
|
| 10 |
+
const runButton = $("run");
|
| 11 |
+
const warmButton = $("warm");
|
| 12 |
+
const abortButton = $("abort");
|
| 13 |
const elapsedEl = $("elapsed");
|
| 14 |
const initImageInput = $("init-image");
|
| 15 |
const initImageLabelEl = $("init-image-label");
|
|
|
|
| 54 |
const GALLERY_LIMIT = 24;
|
| 55 |
const DEFAULT_LOCAL_MODEL_BASE_URL = "/models";
|
| 56 |
const DEFAULT_LOCAL_RUNTIME_BASE_URL = "/runtime";
|
| 57 |
+
const DEFAULT_FIRST_LOAD_DOWNLOAD_LABEL = "about 3.8 GB / 3.5 GiB";
|
| 58 |
+
const FULL_MODEL_REPO_DOWNLOAD_LABEL = "12.6 GB";
|
| 59 |
+
|
| 60 |
+
let tokenizerPromise = null;
|
| 61 |
+
let transformersModulePromise = null;
|
| 62 |
+
let engineReadyPromise = null;
|
| 63 |
+
let engineModulePromise = null;
|
| 64 |
+
let customRuntimeScriptsPromise = null;
|
| 65 |
+
let currentImageUrl = null;
|
| 66 |
let currentSourceUrl = null;
|
| 67 |
let backgroundWarmPromise = null;
|
| 68 |
let backgroundWarmKey = "";
|
|
|
|
| 88 |
let backgroundVaeEncoderPreparePromise = null;
|
| 89 |
let backgroundVaeEncoderPrepareKey = "";
|
| 90 |
let backgroundDeepPrepareTimer = 0;
|
| 91 |
+
let backgroundDeepPrepareToken = 0;
|
| 92 |
+
let bootStarted = false;
|
| 93 |
+
let bootReady = false;
|
| 94 |
const preparedInitImageCache = new WeakMap();
|
| 95 |
const generationResultCache = new Map();
|
| 96 |
const tokenizedPromptCache = new Map();
|
|
|
|
| 430 |
statusEl.classList.toggle("error", Boolean(isError));
|
| 431 |
}
|
| 432 |
|
| 433 |
+
function setButtonDisabled(button, disabled) {
|
| 434 |
+
if (button) button.disabled = disabled;
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
function setElapsed(text, active = false) {
|
| 438 |
+
if (!elapsedEl) return;
|
| 439 |
+
elapsedEl.textContent = text;
|
| 440 |
+
elapsedEl.classList.toggle("active", active);
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
function setActionsVisible(visible) {
|
| 444 |
+
if (actionsEl) actionsEl.hidden = !visible;
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
function setDownloadGateVisible(visible) {
|
| 448 |
+
if (downloadGateEl) downloadGateEl.hidden = !visible;
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
function canRunRuntimeWork() {
|
| 452 |
+
return bootStarted;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
function showDownloadGate({ retry = false, preserveStatus = false } = {}) {
|
| 456 |
+
bootReady = false;
|
| 457 |
+
setDownloadGateVisible(true);
|
| 458 |
+
setActionsVisible(false);
|
| 459 |
+
if (startDownloadButton) startDownloadButton.disabled = false;
|
| 460 |
+
runButton.disabled = true;
|
| 461 |
+
setButtonDisabled(warmButton, true);
|
| 462 |
+
abortButton.disabled = true;
|
| 463 |
+
setElapsed("0.0s");
|
| 464 |
+
placeholderEl.hidden = false;
|
| 465 |
+
placeholderEl.textContent = "Model not loaded";
|
| 466 |
+
if (!preserveStatus) {
|
| 467 |
+
setStatus([
|
| 468 |
+
retry ? "Download failed" : "Ready to download",
|
| 469 |
+
`Default fast path: ${DEFAULT_FIRST_LOAD_DOWNLOAD_LABEL}`,
|
| 470 |
+
`Full model repo: ${FULL_MODEL_REPO_DOWNLOAD_LABEL}`,
|
| 471 |
+
], retry);
|
| 472 |
+
}
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
function showRuntimeLoading() {
|
| 476 |
+
setDownloadGateVisible(false);
|
| 477 |
+
setActionsVisible(false);
|
| 478 |
+
if (startDownloadButton) startDownloadButton.disabled = true;
|
| 479 |
+
runButton.disabled = true;
|
| 480 |
+
setButtonDisabled(warmButton, true);
|
| 481 |
+
abortButton.disabled = true;
|
| 482 |
+
setElapsed("0.0s");
|
| 483 |
+
placeholderEl.hidden = false;
|
| 484 |
+
placeholderEl.textContent = "Loading";
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
function showRuntimeReady() {
|
| 488 |
+
bootReady = true;
|
| 489 |
+
setDownloadGateVisible(false);
|
| 490 |
+
setActionsVisible(true);
|
| 491 |
+
runButton.disabled = false;
|
| 492 |
+
setButtonDisabled(warmButton, false);
|
| 493 |
+
abortButton.disabled = true;
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
function updateRunTimer(prefix = "Generating") {
|
| 497 |
+
if (!runTimerStarted) {
|
| 498 |
+
setElapsed("0.0s");
|
| 499 |
return 0;
|
| 500 |
}
|
| 501 |
const seconds = (performance.now() - runTimerStarted) / 1000;
|
|
|
|
| 702 |
applyTheme(requestedTheme || "dark", { persist: false });
|
| 703 |
}
|
| 704 |
|
| 705 |
+
function isAbortError(err) {
|
| 706 |
+
return String(err?.stack || err?.message || err || "").includes("ABORTED_BY_CLIENT");
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
function loadClassicScript(src) {
|
| 710 |
+
return new Promise((resolve, reject) => {
|
| 711 |
+
const existing = Array.from(document.scripts).find((script) => script.dataset.flux2Src === src);
|
| 712 |
+
if (existing?.dataset.loaded === "true") {
|
| 713 |
+
resolve();
|
| 714 |
+
return;
|
| 715 |
+
}
|
| 716 |
+
if (existing) existing.remove();
|
| 717 |
+
const script = document.createElement("script");
|
| 718 |
+
script.src = src;
|
| 719 |
+
script.dataset.flux2Src = src;
|
| 720 |
+
script.async = false;
|
| 721 |
+
script.onload = () => {
|
| 722 |
+
script.dataset.loaded = "true";
|
| 723 |
+
resolve();
|
| 724 |
+
};
|
| 725 |
+
script.onerror = () => reject(new Error(`Failed to load ${src}`));
|
| 726 |
+
if (!existing) document.body.appendChild(script);
|
| 727 |
+
});
|
| 728 |
+
}
|
| 729 |
+
|
| 730 |
+
async function loadCustomRuntimeScripts() {
|
| 731 |
+
if (!customRuntimeScriptsPromise) {
|
| 732 |
+
customRuntimeScriptsPromise = (async () => {
|
| 733 |
+
setLoadPhase("Loading custom WebGPU kernels");
|
| 734 |
+
await loadClassicScript("tools/custom_lowbit_webgpu/custom_lowbit_linear.js");
|
| 735 |
+
await loadClassicScript("tools/custom_lowbit_webgpu/custom_transformer_runtime.js");
|
| 736 |
+
})();
|
| 737 |
+
}
|
| 738 |
+
return customRuntimeScriptsPromise;
|
| 739 |
+
}
|
| 740 |
+
|
| 741 |
+
async function loadEngineModule() {
|
| 742 |
+
if (!engineModulePromise) {
|
| 743 |
+
setLoadPhase("Loading browser engine");
|
| 744 |
+
engineModulePromise = import("./flux2-engine.js");
|
| 745 |
+
}
|
| 746 |
+
return engineModulePromise;
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
function resetRuntimeLoadPromises() {
|
| 750 |
+
engineReadyPromise = null;
|
| 751 |
+
tokenizerPromise = null;
|
| 752 |
+
if (!window.flux2Engine) engineModulePromise = null;
|
| 753 |
+
if (typeof globalThis.createCustomFluxTransformerRuntime !== "function") customRuntimeScriptsPromise = null;
|
| 754 |
+
transformersModulePromise = null;
|
| 755 |
+
}
|
| 756 |
+
|
| 757 |
+
function waitForEngine() {
|
| 758 |
+
return new Promise((resolve, reject) => {
|
| 759 |
const deadline = performance.now() + 30000;
|
| 760 |
const tick = () => {
|
| 761 |
if (window.flux2Engine && typeof window.flux2Engine.init === "function") {
|
|
|
|
| 833 |
async function loadTokenizer() {
|
| 834 |
if (tokenizerPromise) return tokenizerPromise;
|
| 835 |
setLoadPhase("Loading tokenizer");
|
| 836 |
+
const { AutoTokenizer, env } = await loadTransformersModule();
|
| 837 |
const modelBaseUrl = configuredModelBaseUrl();
|
| 838 |
const tokenizerSpec = tokenizerLoadSpec(modelBaseUrl);
|
| 839 |
env.allowLocalModels = !tokenizerSpec.remote;
|
|
|
|
| 850 |
return tokenizerPromise;
|
| 851 |
}
|
| 852 |
|
| 853 |
+
async function loadTransformersModule() {
|
| 854 |
+
if (!transformersModulePromise) {
|
| 855 |
+
setLoadPhase("Loading tokenizer runtime");
|
| 856 |
+
transformersModulePromise = import("https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1");
|
| 857 |
+
}
|
| 858 |
+
return transformersModulePromise;
|
| 859 |
+
}
|
| 860 |
+
|
| 861 |
async function tokenizePrompt(prompt, seqLen) {
|
| 862 |
const cachePayload = JSON.stringify({ model: "flux.2-klein-4b", text_seq_len: seqLen, prompt });
|
| 863 |
const promptCacheKey = await sha256Hex(cachePayload);
|
|
|
|
| 897 |
async function initEngine() {
|
| 898 |
if (engineReadyPromise) return engineReadyPromise;
|
| 899 |
engineReadyPromise = (async () => {
|
| 900 |
+
await Promise.all([loadCustomRuntimeScripts(), loadEngineModule()]);
|
| 901 |
const engine = await waitForEngine();
|
| 902 |
const modelBaseUrl = configuredModelBaseUrl();
|
| 903 |
const runtimeBaseUrl = configuredRuntimeBaseUrl(modelBaseUrl);
|
|
|
|
| 1537 |
return request.transformerBackend === "custom-lowbit-webgpu";
|
| 1538 |
}
|
| 1539 |
|
| 1540 |
+
function startBackgroundTextContextPrepareForCurrentSettings(request = readRequest()) {
|
| 1541 |
+
if (!canRunRuntimeWork()) return false;
|
| 1542 |
+
const prepareRequest = backgroundPrepareRequestForRequest(request);
|
| 1543 |
+
if (!shouldTextContextPrepareRequest(prepareRequest)) return false;
|
| 1544 |
const key = textContextPrepareKeyForRequest(prepareRequest);
|
| 1545 |
if (backgroundTextContextPreparePromise && backgroundTextContextPrepareKey === key) return true;
|
| 1546 |
backgroundTextContextPrepareKey = key;
|
|
|
|
| 1603 |
return plannedMaxDimension <= 512;
|
| 1604 |
}
|
| 1605 |
|
| 1606 |
+
function startBackgroundTransformerSetupPrepareForCurrentSettings(request = readRequest()) {
|
| 1607 |
+
if (!canRunRuntimeWork()) return false;
|
| 1608 |
+
const prepareRequest = backgroundPrepareRequestForRequest(request);
|
| 1609 |
+
if (!shouldTransformerSetupPrepareRequest(prepareRequest)) return false;
|
| 1610 |
const dispatchWarmup = shouldTransformerDispatchWarmupRequest(prepareRequest);
|
| 1611 |
const key = `${transformerSetupPrepareKeyForRequest(prepareRequest)}|dispatch=${dispatchWarmup ? 1 : 0}`;
|
| 1612 |
if (backgroundTransformerSetupPreparePromise && backgroundTransformerSetupPrepareKey === key) return true;
|
|
|
|
| 1690 |
maxDimension <= 256;
|
| 1691 |
}
|
| 1692 |
|
| 1693 |
+
function startBackgroundAssetPrepareForCurrentSettings(request = readRequest()) {
|
| 1694 |
+
if (!canRunRuntimeWork()) return false;
|
| 1695 |
+
if (!shouldAssetPrepareRequest(request)) return false;
|
| 1696 |
const key = assetPrepareKeyForRequest(request);
|
| 1697 |
if (backgroundAssetPreparePromise && backgroundAssetPrepareKey === key) return true;
|
| 1698 |
backgroundAssetPrepareKey = key;
|
|
|
|
| 1753 |
return true;
|
| 1754 |
}
|
| 1755 |
|
| 1756 |
+
function startBackgroundVaeEncoderPrepareForCurrentSettings(request = readRequest()) {
|
| 1757 |
+
if (!canRunRuntimeWork()) return false;
|
| 1758 |
+
if (!shouldVaeEncoderPrepareRequest(request)) return false;
|
| 1759 |
const key = vaeEncoderPrepareKeyForRequest(request);
|
| 1760 |
if (backgroundVaeEncoderPreparePromise && backgroundVaeEncoderPrepareKey === key) return true;
|
| 1761 |
backgroundVaeEncoderPrepareKey = key;
|
|
|
|
| 1818 |
}));
|
| 1819 |
}
|
| 1820 |
|
| 1821 |
+
function startStageAndVaePrepareForRequest(request) {
|
| 1822 |
+
if (!canRunRuntimeWork()) return false;
|
| 1823 |
+
const startedText = startBackgroundTextContextPrepareForCurrentSettings(request);
|
| 1824 |
const startedTransformer = startBackgroundTransformerSetupPrepareForCurrentSettings(request);
|
| 1825 |
if (request.hasInitImage && shouldVaeEncoderPrepareRequest(request)) {
|
| 1826 |
const startedVaeEncoder = startBackgroundVaeEncoderPrepareForCurrentSettings(request);
|
|
|
|
| 1830 |
return startedText || startedTransformer || startedVae;
|
| 1831 |
}
|
| 1832 |
|
| 1833 |
+
function startBackgroundVaePrepareForCurrentSettings(request = readRequest()) {
|
| 1834 |
+
if (!canRunRuntimeWork()) return false;
|
| 1835 |
+
const prepareRequest = backgroundPrepareRequestForRequest(request);
|
| 1836 |
if (!shouldVaePrepareRequest(prepareRequest)) return false;
|
| 1837 |
const dispatchWarmup = shouldVaeDispatchWarmupRequest(prepareRequest);
|
| 1838 |
const key = `${vaePrepareKeyForRequest(prepareRequest)}|dispatch=${dispatchWarmup ? 1 : 0}`;
|
|
|
|
| 1895 |
return true;
|
| 1896 |
}
|
| 1897 |
|
| 1898 |
+
function startBackgroundWarmForCurrentSettings() {
|
| 1899 |
+
if (!canRunRuntimeWork()) return false;
|
| 1900 |
+
if (foregroundBusy) return false;
|
| 1901 |
const request = readRequest();
|
| 1902 |
if (!shouldAutoWarmRequest(request)) return false;
|
| 1903 |
const key = warmKeyForRequest(request);
|
|
|
|
| 1935 |
return true;
|
| 1936 |
}
|
| 1937 |
|
| 1938 |
+
function scheduleBackgroundStageAndVaePrepareForCurrentSettings(request = readRequest(), delayMs = 750) {
|
| 1939 |
+
if (!canRunRuntimeWork()) return false;
|
| 1940 |
+
if (foregroundBusy || urlFlag("noAutowarm")) return false;
|
| 1941 |
if (backgroundDeepPrepareTimer) {
|
| 1942 |
clearTimeout(backgroundDeepPrepareTimer);
|
| 1943 |
backgroundDeepPrepareTimer = 0;
|
|
|
|
| 1974 |
return true;
|
| 1975 |
}
|
| 1976 |
|
| 1977 |
+
function scheduleAutoWarmForCurrentSettings(delayMs = 350) {
|
| 1978 |
+
clearWarmSchedule();
|
| 1979 |
+
if (!canRunRuntimeWork()) return;
|
| 1980 |
+
if (foregroundBusy || urlFlag("noAutowarm")) return;
|
| 1981 |
const request = readRequest();
|
| 1982 |
startBackgroundAssetPrepareForCurrentSettings(request);
|
| 1983 |
scheduleBackgroundStageAndVaePrepareForCurrentSettings(request, request.hasInitImage ? delayMs : 750);
|
|
|
|
| 2472 |
}
|
| 2473 |
}
|
| 2474 |
|
| 2475 |
+
async function generate() {
|
| 2476 |
+
if (!bootReady) throw new Error("Model assets are not loaded yet.");
|
| 2477 |
+
const requestedStarted = performance.now();
|
| 2478 |
+
const request = readRequest();
|
| 2479 |
runButton.disabled = true;
|
| 2480 |
setButtonDisabled(warmButton, true);
|
| 2481 |
abortButton.disabled = false;
|
|
|
|
| 2750 |
return (performance.now() - started) / 1000;
|
| 2751 |
}
|
| 2752 |
|
| 2753 |
+
async function warmCurrentSettings({ background = false } = {}) {
|
| 2754 |
+
if (!canRunRuntimeWork()) throw new Error("Model assets are not loaded yet.");
|
| 2755 |
+
const request = readRequest();
|
| 2756 |
const { warmRequest, warmProfile } = warmPlanForRequest(request);
|
| 2757 |
const { engine, init } = await initEngine();
|
| 2758 |
const started = performance.now();
|
|
|
|
| 2860 |
}
|
| 2861 |
|
| 2862 |
async function boot() {
|
| 2863 |
+
if (!bootStarted) bootStarted = true;
|
| 2864 |
+
showRuntimeLoading();
|
| 2865 |
+
beginLoadProgress("Downloading assets");
|
| 2866 |
try {
|
| 2867 |
installLoadProgressFetch();
|
| 2868 |
setStatus("Loading browser runtime");
|
| 2869 |
setLoadPhase("Loading browser runtime");
|
| 2870 |
const [{ init }, tokenizer] = await Promise.all([initEngine(), loadTokenizer()]);
|
| 2871 |
const bootRequest = readRequest();
|
| 2872 |
+
const fastPathPrepared = await prepareInitialFastPath(bootRequest);
|
| 2873 |
+
showRuntimeReady();
|
| 2874 |
+
setStatus([
|
| 2875 |
+
"Ready",
|
| 2876 |
+
fastPathPrepared ? "Fast path prepared" : "",
|
|
|
|
| 2877 |
`Model: ${init.config.model_variant || init.config.model_name}`,
|
| 2878 |
seedInputLine(),
|
| 2879 |
`Tokenizer: ${tokenizer.constructor?.name || "loaded"}`,
|
|
|
|
| 2885 |
}
|
| 2886 |
if (shouldAutoWarmRequest(bootRequest)) startBackgroundWarmForCurrentSettings();
|
| 2887 |
finishLoadProgress(fastPathPrepared ? "Fast path prepared" : "Ready");
|
| 2888 |
+
return true;
|
| 2889 |
} catch (err) {
|
| 2890 |
+
bootStarted = false;
|
| 2891 |
+
bootReady = false;
|
| 2892 |
+
resetRuntimeLoadPromises();
|
| 2893 |
runButton.disabled = true;
|
| 2894 |
setButtonDisabled(warmButton, true);
|
| 2895 |
+
abortButton.disabled = true;
|
| 2896 |
setStatus(err?.stack || err?.message || String(err), true);
|
| 2897 |
finishLoadProgress("Load failed", false);
|
| 2898 |
+
showDownloadGate({ retry: true, preserveStatus: true });
|
| 2899 |
+
return false;
|
| 2900 |
}
|
| 2901 |
}
|
| 2902 |
+
|
| 2903 |
+
startDownloadButton?.addEventListener("click", () => {
|
| 2904 |
+
if (bootStarted) return;
|
| 2905 |
+
bootStarted = true;
|
| 2906 |
+
void boot();
|
| 2907 |
+
});
|
| 2908 |
+
|
| 2909 |
+
warmButton?.addEventListener("click", () => {
|
| 2910 |
+
if (!bootReady || foregroundBusy) return;
|
| 2911 |
+
clearWarmSchedule();
|
| 2912 |
+
foregroundBusy = true;
|
| 2913 |
warmCurrentSettings().catch((err) => {
|
| 2914 |
if (isAbortError(err)) return;
|
| 2915 |
placeholderEl.hidden = false;
|
|
|
|
| 2923 |
});
|
| 2924 |
});
|
| 2925 |
|
| 2926 |
+
runButton.addEventListener("click", () => {
|
| 2927 |
+
if (!bootReady || foregroundBusy) return;
|
| 2928 |
+
clearWarmSchedule();
|
| 2929 |
foregroundBusy = true;
|
| 2930 |
startRunTimer();
|
| 2931 |
generate().catch((err) => {
|
|
|
|
| 3105 |
});
|
| 3106 |
});
|
| 3107 |
|
| 3108 |
+
initTheme();
|
| 3109 |
+
applyUrlDefaults();
|
| 3110 |
+
updateStrengthDisplay();
|
| 3111 |
+
showDownloadGate();
|
static.html
CHANGED
|
@@ -337,17 +337,44 @@
|
|
| 337 |
gap: 10px;
|
| 338 |
}
|
| 339 |
.wide { grid-column: 1 / -1; }
|
| 340 |
-
.actions {
|
| 341 |
-
display: flex;
|
| 342 |
-
gap: 10px;
|
| 343 |
-
margin-top: 18px;
|
| 344 |
-
align-items: center;
|
| 345 |
-
flex-wrap: wrap;
|
| 346 |
-
}
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
padding: 0 14px;
|
| 352 |
background: var(--accent);
|
| 353 |
color: #061715;
|
|
@@ -698,7 +725,12 @@
|
|
| 698 |
<input id="decode-max" type="hidden" value="0">
|
| 699 |
<input id="render-max" type="hidden" value="0">
|
| 700 |
</div>
|
| 701 |
-
<div class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 702 |
<button id="run" type="button" disabled><span class="icon">auto_awesome</span>Generate</button>
|
| 703 |
<button id="abort" class="secondary" type="button" disabled><span class="icon">stop</span>Stop</button>
|
| 704 |
<div id="elapsed" class="elapsed-meter" aria-live="polite">0.0s</div>
|
|
@@ -732,11 +764,8 @@
|
|
| 732 |
<div id="gallery-grid" class="gallery-grid"></div>
|
| 733 |
</div>
|
| 734 |
</section>
|
| 735 |
-
</main>
|
| 736 |
<script src="space-config.js"></script>
|
| 737 |
-
<script src="tools/custom_lowbit_webgpu/custom_lowbit_linear.js"></script>
|
| 738 |
-
<script src="tools/custom_lowbit_webgpu/custom_transformer_runtime.js"></script>
|
| 739 |
-
<script type="module" src="flux2-engine.js"></script>
|
| 740 |
<script type="module" src="static-app.js"></script>
|
| 741 |
</body>
|
| 742 |
</html>
|
|
|
|
| 337 |
gap: 10px;
|
| 338 |
}
|
| 339 |
.wide { grid-column: 1 / -1; }
|
| 340 |
+
.actions {
|
| 341 |
+
display: flex;
|
| 342 |
+
gap: 10px;
|
| 343 |
+
margin-top: 18px;
|
| 344 |
+
align-items: center;
|
| 345 |
+
flex-wrap: wrap;
|
| 346 |
+
}
|
| 347 |
+
.actions[hidden] {
|
| 348 |
+
display: none;
|
| 349 |
+
}
|
| 350 |
+
.download-gate {
|
| 351 |
+
margin-top: 18px;
|
| 352 |
+
padding: 14px;
|
| 353 |
+
border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--line));
|
| 354 |
+
border-radius: var(--radius);
|
| 355 |
+
background: color-mix(in srgb, var(--accent-soft) 74%, var(--panel-2));
|
| 356 |
+
}
|
| 357 |
+
.download-gate[hidden] {
|
| 358 |
+
display: none;
|
| 359 |
+
}
|
| 360 |
+
.download-gate-title {
|
| 361 |
+
font-size: 13px;
|
| 362 |
+
font-weight: 850;
|
| 363 |
+
color: var(--text);
|
| 364 |
+
}
|
| 365 |
+
.download-gate-copy {
|
| 366 |
+
margin: 7px 0 12px;
|
| 367 |
+
color: var(--muted);
|
| 368 |
+
font-size: 12px;
|
| 369 |
+
line-height: 1.45;
|
| 370 |
+
}
|
| 371 |
+
.download-gate button {
|
| 372 |
+
width: 100%;
|
| 373 |
+
}
|
| 374 |
+
button {
|
| 375 |
+
min-height: 42px;
|
| 376 |
+
border: 0;
|
| 377 |
+
border-radius: var(--radius);
|
| 378 |
padding: 0 14px;
|
| 379 |
background: var(--accent);
|
| 380 |
color: #061715;
|
|
|
|
| 725 |
<input id="decode-max" type="hidden" value="0">
|
| 726 |
<input id="render-max" type="hidden" value="0">
|
| 727 |
</div>
|
| 728 |
+
<div id="download-gate" class="download-gate">
|
| 729 |
+
<div class="download-gate-title">Download model assets</div>
|
| 730 |
+
<div class="download-gate-copy">Default fast path downloads about 3.8 GB / 3.5 GiB before the first generation. The full public model repo is 12.6 GB; ONNX fallback modes can fetch more later.</div>
|
| 731 |
+
<button id="start-download" type="button"><span class="icon">download</span>Start download</button>
|
| 732 |
+
</div>
|
| 733 |
+
<div id="actions" class="actions" hidden>
|
| 734 |
<button id="run" type="button" disabled><span class="icon">auto_awesome</span>Generate</button>
|
| 735 |
<button id="abort" class="secondary" type="button" disabled><span class="icon">stop</span>Stop</button>
|
| 736 |
<div id="elapsed" class="elapsed-meter" aria-live="polite">0.0s</div>
|
|
|
|
| 764 |
<div id="gallery-grid" class="gallery-grid"></div>
|
| 765 |
</div>
|
| 766 |
</section>
|
| 767 |
+
</main>
|
| 768 |
<script src="space-config.js"></script>
|
|
|
|
|
|
|
|
|
|
| 769 |
<script type="module" src="static-app.js"></script>
|
| 770 |
</body>
|
| 771 |
</html>
|