Spaces:
Running
Running
Add first-load download progress indicator
Browse files- static-app.js +440 -80
- static.html +99 -16
static-app.js
CHANGED
|
@@ -27,14 +27,21 @@ const aspectLockIconEl = $("aspect-lock-icon");
|
|
| 27 |
const aspectRatioEl = $("aspect-ratio");
|
| 28 |
const settingsToggleButton = $("settings-toggle");
|
| 29 |
const settingsPopoverEl = $("settings-popover");
|
| 30 |
-
const themeToggleButton = $("theme-toggle");
|
| 31 |
-
const themeIconEl = $("theme-icon");
|
| 32 |
-
const galleryPanelEl = $("gallery-panel");
|
| 33 |
-
const galleryGridEl = $("gallery-grid");
|
| 34 |
-
const clearGalleryButton = $("clear-gallery");
|
| 35 |
-
const
|
| 36 |
-
const
|
| 37 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
const ASPECT_RATIOS = {
|
| 39 |
"1:1": 1,
|
| 40 |
"4:3": 4 / 3,
|
|
@@ -76,15 +83,41 @@ let backgroundVaeEncoderPreparePromise = null;
|
|
| 76 |
let backgroundVaeEncoderPrepareKey = "";
|
| 77 |
let backgroundDeepPrepareTimer = 0;
|
| 78 |
let backgroundDeepPrepareToken = 0;
|
| 79 |
-
const preparedInitImageCache = new WeakMap();
|
| 80 |
-
const generationResultCache = new Map();
|
| 81 |
-
const tokenizedPromptCache = new Map();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
function formatMiB(bytes) {
|
| 84 |
const value = Number(bytes || 0);
|
| 85 |
return `${(value / (1024 * 1024)).toFixed(value >= 100 * 1024 * 1024 ? 0 : 1)} MiB`;
|
| 86 |
}
|
| 87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
function trimTrailingSlash(value) {
|
| 89 |
return String(value || "").replace(/\/+$/, "");
|
| 90 |
}
|
|
@@ -130,6 +163,219 @@ function resolveAssetUrl(baseUrl, path) {
|
|
| 130 |
return new URL(path, new URL(withTrailingSlash(baseUrl), window.location.href)).toString();
|
| 131 |
}
|
| 132 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
function huggingFaceResolveSpec(baseUrl) {
|
| 134 |
let url;
|
| 135 |
try {
|
|
@@ -477,16 +723,24 @@ function maskFromLength(length, seqLen) {
|
|
| 477 |
|
| 478 |
async function loadTokenizer() {
|
| 479 |
if (tokenizerPromise) return tokenizerPromise;
|
|
|
|
| 480 |
const modelBaseUrl = configuredModelBaseUrl();
|
| 481 |
const tokenizerSpec = tokenizerLoadSpec(modelBaseUrl);
|
| 482 |
env.allowLocalModels = !tokenizerSpec.remote;
|
| 483 |
env.allowRemoteModels = tokenizerSpec.remote;
|
| 484 |
env.localModelPath = "/";
|
| 485 |
-
tokenizerPromise = AutoTokenizer.from_pretrained(tokenizerSpec.source,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 486 |
return tokenizerPromise;
|
| 487 |
}
|
| 488 |
-
|
| 489 |
-
async function tokenizePrompt(prompt, seqLen) {
|
| 490 |
const cachePayload = JSON.stringify({ model: "flux.2-klein-4b", text_seq_len: seqLen, prompt });
|
| 491 |
const promptCacheKey = await sha256Hex(cachePayload);
|
| 492 |
const cacheKey = `${seqLen}|${promptCacheKey}`;
|
|
@@ -528,6 +782,7 @@ async function initEngine() {
|
|
| 528 |
const engine = await waitForEngine();
|
| 529 |
const modelBaseUrl = configuredModelBaseUrl();
|
| 530 |
const runtimeBaseUrl = configuredRuntimeBaseUrl(modelBaseUrl);
|
|
|
|
| 531 |
const result = await engine.init(modelBaseUrl, {
|
| 532 |
executionProvider: "webgpu",
|
| 533 |
customKernelBaseUrl: configuredCustomKernelBaseUrl(modelBaseUrl, runtimeBaseUrl),
|
|
@@ -1050,14 +1305,106 @@ function assetPrepareKeyForRequest(request) {
|
|
| 1050 |
});
|
| 1051 |
}
|
| 1052 |
|
| 1053 |
-
function shouldAssetPrepareRequest(request) {
|
| 1054 |
-
if (urlFlag("noAutowarm") || urlFlag("noAssetWarm")) return false;
|
| 1055 |
-
return request.transformerBackend === "custom-lowbit-webgpu";
|
| 1056 |
-
}
|
| 1057 |
-
|
| 1058 |
-
function
|
| 1059 |
-
|
| 1060 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1061 |
backend: request.transformerBackend,
|
| 1062 |
textLimit: request.speed.customTextTokenLimit,
|
| 1063 |
minText: request.speed.customMinTextTokens,
|
|
@@ -1077,10 +1424,11 @@ function startBackgroundTextContextPrepareForCurrentSettings(request = readReque
|
|
| 1077 |
const key = textContextPrepareKeyForRequest(prepareRequest);
|
| 1078 |
if (backgroundTextContextPreparePromise && backgroundTextContextPrepareKey === key) return true;
|
| 1079 |
backgroundTextContextPrepareKey = key;
|
| 1080 |
-
backgroundTextContextPreparePromise = initEngine()
|
| 1081 |
-
.then(async ({ engine, init }) => {
|
| 1082 |
-
if (typeof engine.encodeText !== "function") return null;
|
| 1083 |
-
|
|
|
|
| 1084 |
const tokenized = await tokenizePrompt(prepareRequest.prompt, seqLen);
|
| 1085 |
const params = buildGenerateParams(prepareRequest, tokenized, null);
|
| 1086 |
if (typeof engine.prepareCustomTransformerTextProjection === "function") {
|
|
@@ -1142,11 +1490,12 @@ function startBackgroundTransformerSetupPrepareForCurrentSettings(request = read
|
|
| 1142 |
const key = `${transformerSetupPrepareKeyForRequest(prepareRequest)}|dispatch=${dispatchWarmup ? 1 : 0}`;
|
| 1143 |
if (backgroundTransformerSetupPreparePromise && backgroundTransformerSetupPrepareKey === key) return true;
|
| 1144 |
backgroundTransformerSetupPrepareKey = key;
|
| 1145 |
-
backgroundTransformerSetupPreparePromise = initEngine()
|
| 1146 |
-
.then(async ({ engine, init }) => {
|
| 1147 |
-
if (typeof engine.prepareCustomTransformerStageSetup !== "function") return null;
|
| 1148 |
-
|
| 1149 |
-
const
|
|
|
|
| 1150 |
const plannedTextTokens = plannedCustomTextTokens(prepareRequest, tokenized.attentionMask, seqLen);
|
| 1151 |
return await engine.prepareCustomTransformerStageSetup({
|
| 1152 |
width: prepareRequest.width,
|
|
@@ -1225,12 +1574,13 @@ function startBackgroundAssetPrepareForCurrentSettings(request = readRequest())
|
|
| 1225 |
const key = assetPrepareKeyForRequest(request);
|
| 1226 |
if (backgroundAssetPreparePromise && backgroundAssetPrepareKey === key) return true;
|
| 1227 |
backgroundAssetPrepareKey = key;
|
| 1228 |
-
backgroundAssetPreparePromise = initEngine()
|
| 1229 |
-
.then(({ engine }) => {
|
| 1230 |
-
if (typeof engine.prepareCustomTransformerAssets !== "function") return null;
|
| 1231 |
-
|
| 1232 |
-
|
| 1233 |
-
|
|
|
|
| 1234 |
});
|
| 1235 |
})
|
| 1236 |
.then((result) => {
|
|
@@ -1286,10 +1636,11 @@ function startBackgroundVaeEncoderPrepareForCurrentSettings(request = readReques
|
|
| 1286 |
const key = vaeEncoderPrepareKeyForRequest(request);
|
| 1287 |
if (backgroundVaeEncoderPreparePromise && backgroundVaeEncoderPrepareKey === key) return true;
|
| 1288 |
backgroundVaeEncoderPrepareKey = key;
|
| 1289 |
-
backgroundVaeEncoderPreparePromise = initEngine()
|
| 1290 |
-
.then(async ({ engine }) => {
|
| 1291 |
-
if (typeof engine.prepareVaeEncoderLatent !== "function") return null;
|
| 1292 |
-
|
|
|
|
| 1293 |
const initImage = await prepareInitImage(renderSize.width, renderSize.height, request.fitMode);
|
| 1294 |
if (!initImage) return null;
|
| 1295 |
return await engine.prepareVaeEncoderLatent({
|
|
@@ -1363,10 +1714,11 @@ function startBackgroundVaePrepareForCurrentSettings(request = readRequest()) {
|
|
| 1363 |
if (backgroundVaePreparePromise && backgroundVaePrepareKey === key) return true;
|
| 1364 |
backgroundVaePrepareKey = key;
|
| 1365 |
const plannedDecodeMax = plannedDecodeMaxForRequest(prepareRequest);
|
| 1366 |
-
backgroundVaePreparePromise = initEngine()
|
| 1367 |
-
.then(({ engine }) => {
|
| 1368 |
-
if (typeof engine.prepareVaeSessions !== "function") return null;
|
| 1369 |
-
|
|
|
|
| 1370 |
width: prepareRequest.width,
|
| 1371 |
height: prepareRequest.height,
|
| 1372 |
hasInitImage: prepareRequest.hasInitImage,
|
|
@@ -2330,16 +2682,18 @@ async function prepareInitialFastPath(request) {
|
|
| 2330 |
const shouldPrepareVae = shouldVaePrepareRequest(prepareRequest);
|
| 2331 |
if (!shouldPrepareAssets && !shouldPrepareTransformer && !shouldPrepareVae) return false;
|
| 2332 |
|
| 2333 |
-
setStatus([
|
| 2334 |
-
"Preparing fast path",
|
| 2335 |
-
`Backend: ${prepareRequest.transformerBackend}`,
|
| 2336 |
-
`Speed Profile: ${speedProfileLabel(prepareRequest.speed)}`,
|
| 2337 |
-
prepareRequest.previewRenderMaxSize ? `Preview render: ${prepareRequest.previewRenderMaxSize}px max` : "",
|
| 2338 |
-
prepareRequest.previewDecodeMaxSize ? `Preview decode: ${prepareRequest.previewDecodeMaxSize}px max` : "",
|
| 2339 |
-
]);
|
| 2340 |
-
|
| 2341 |
-
|
| 2342 |
-
|
|
|
|
|
|
|
| 2343 |
if (assetPromise) {
|
| 2344 |
try {
|
| 2345 |
await assetPromise;
|
|
@@ -2360,25 +2714,29 @@ async function prepareInitialFastPath(request) {
|
|
| 2360 |
|
| 2361 |
if (foregroundBusy) return true;
|
| 2362 |
const finalPrepareRequest = progressiveFinalPrepareRequestForRequest(currentRequest);
|
| 2363 |
-
if (finalPrepareRequest && warmKeyForRequest(readRequest()) === warmKeyForRequest(request)) {
|
| 2364 |
-
setStatus([
|
| 2365 |
-
"Preparing refinement path",
|
| 2366 |
`Backend: ${finalPrepareRequest.transformerBackend}`,
|
| 2367 |
`Speed Profile: ${speedProfileLabel(finalPrepareRequest.speed)}`,
|
| 2368 |
finalPrepareRequest.previewRenderMaxSize ? `Preview render: ${finalPrepareRequest.previewRenderMaxSize}px max` : "",
|
| 2369 |
-
finalPrepareRequest.previewDecodeMaxSize ? `Preview decode: ${finalPrepareRequest.previewDecodeMaxSize}px max` : "",
|
| 2370 |
-
]);
|
| 2371 |
-
|
| 2372 |
-
|
| 2373 |
-
|
|
|
|
| 2374 |
return true;
|
| 2375 |
}
|
| 2376 |
|
| 2377 |
-
async function boot() {
|
| 2378 |
-
|
| 2379 |
-
|
| 2380 |
-
|
| 2381 |
-
|
|
|
|
|
|
|
|
|
|
| 2382 |
const fastPathPrepared = await prepareInitialFastPath(bootRequest);
|
| 2383 |
runButton.disabled = false;
|
| 2384 |
setButtonDisabled(warmButton, false);
|
|
@@ -2389,18 +2747,20 @@ async function boot() {
|
|
| 2389 |
seedInputLine(),
|
| 2390 |
`Tokenizer: ${tokenizer.constructor?.name || "loaded"}`,
|
| 2391 |
`Full image backend: ${init.customKernels?.fullImageBackend || "unknown"}`,
|
| 2392 |
-
]);
|
| 2393 |
-
if (!fastPathPrepared) {
|
| 2394 |
-
startBackgroundAssetPrepareForCurrentSettings(bootRequest);
|
| 2395 |
-
scheduleBackgroundStageAndVaePrepareForCurrentSettings(bootRequest);
|
| 2396 |
-
}
|
| 2397 |
-
if (shouldAutoWarmRequest(bootRequest)) startBackgroundWarmForCurrentSettings();
|
| 2398 |
-
|
| 2399 |
-
|
| 2400 |
-
|
| 2401 |
-
|
| 2402 |
-
|
| 2403 |
-
|
|
|
|
|
|
|
| 2404 |
|
| 2405 |
warmButton?.addEventListener("click", () => {
|
| 2406 |
if (foregroundBusy) return;
|
|
|
|
| 27 |
const aspectRatioEl = $("aspect-ratio");
|
| 28 |
const settingsToggleButton = $("settings-toggle");
|
| 29 |
const settingsPopoverEl = $("settings-popover");
|
| 30 |
+
const themeToggleButton = $("theme-toggle");
|
| 31 |
+
const themeIconEl = $("theme-icon");
|
| 32 |
+
const galleryPanelEl = $("gallery-panel");
|
| 33 |
+
const galleryGridEl = $("gallery-grid");
|
| 34 |
+
const clearGalleryButton = $("clear-gallery");
|
| 35 |
+
const loadProgressEl = $("load-progress");
|
| 36 |
+
const loadProgressTitleEl = $("load-progress-title");
|
| 37 |
+
const loadProgressSummaryEl = $("load-progress-summary");
|
| 38 |
+
const loadProgressTrackEl = $("load-progress-track");
|
| 39 |
+
const loadProgressFillEl = $("load-progress-fill");
|
| 40 |
+
const loadProgressDetailEl = $("load-progress-detail");
|
| 41 |
+
const loadProgressRateEl = $("load-progress-rate");
|
| 42 |
+
const urlDefaults = new URLSearchParams(window.location.search);
|
| 43 |
+
const DEFAULT_PROMPT = "A small ceramic robot on a workbench, product photo lighting";
|
| 44 |
+
const COMMON_RESOLUTIONS = [256, 512, 768, 1024];
|
| 45 |
const ASPECT_RATIOS = {
|
| 46 |
"1:1": 1,
|
| 47 |
"4:3": 4 / 3,
|
|
|
|
| 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();
|
| 89 |
+
const nativeFetch = globalThis.fetch.bind(globalThis);
|
| 90 |
+
|
| 91 |
+
const loadProgressState = {
|
| 92 |
+
visible: false,
|
| 93 |
+
tracking: false,
|
| 94 |
+
completed: false,
|
| 95 |
+
startedAt: 0,
|
| 96 |
+
title: "Loading",
|
| 97 |
+
detail: "Starting",
|
| 98 |
+
estimatedTotalBytes: 0,
|
| 99 |
+
estimatedFiles: 0,
|
| 100 |
+
nextRequestId: 1,
|
| 101 |
+
requests: new Map(),
|
| 102 |
+
renderQueued: false,
|
| 103 |
+
timerId: 0,
|
| 104 |
+
hideTimerId: 0,
|
| 105 |
+
};
|
| 106 |
|
| 107 |
function formatMiB(bytes) {
|
| 108 |
const value = Number(bytes || 0);
|
| 109 |
return `${(value / (1024 * 1024)).toFixed(value >= 100 * 1024 * 1024 ? 0 : 1)} MiB`;
|
| 110 |
}
|
| 111 |
|
| 112 |
+
function formatBytesCompact(bytes) {
|
| 113 |
+
const value = Number(bytes || 0);
|
| 114 |
+
if (!Number.isFinite(value) || value <= 0) return "0 B";
|
| 115 |
+
if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(value >= 10 * 1024 ** 3 ? 1 : 2)} GiB`;
|
| 116 |
+
if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(value >= 100 * 1024 ** 2 ? 0 : 1)} MiB`;
|
| 117 |
+
if (value >= 1024) return `${(value / 1024).toFixed(value >= 100 * 1024 ? 0 : 1)} KiB`;
|
| 118 |
+
return `${Math.round(value)} B`;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
function trimTrailingSlash(value) {
|
| 122 |
return String(value || "").replace(/\/+$/, "");
|
| 123 |
}
|
|
|
|
| 163 |
return new URL(path, new URL(withTrailingSlash(baseUrl), window.location.href)).toString();
|
| 164 |
}
|
| 165 |
|
| 166 |
+
function compactLoadUrlLabel(url) {
|
| 167 |
+
try {
|
| 168 |
+
const parsed = new URL(url, window.location.href);
|
| 169 |
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
| 170 |
+
const tail = parts.slice(-3).join("/");
|
| 171 |
+
return tail || parsed.hostname;
|
| 172 |
+
} catch {
|
| 173 |
+
return String(url || "download");
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
function beginLoadProgress(title = "Loading") {
|
| 178 |
+
if (!loadProgressEl) return;
|
| 179 |
+
if (loadProgressState.hideTimerId) {
|
| 180 |
+
clearTimeout(loadProgressState.hideTimerId);
|
| 181 |
+
loadProgressState.hideTimerId = 0;
|
| 182 |
+
}
|
| 183 |
+
loadProgressState.visible = true;
|
| 184 |
+
loadProgressState.tracking = true;
|
| 185 |
+
loadProgressState.completed = false;
|
| 186 |
+
loadProgressState.startedAt = performance.now();
|
| 187 |
+
loadProgressState.title = title;
|
| 188 |
+
loadProgressState.detail = "Starting";
|
| 189 |
+
loadProgressState.estimatedTotalBytes = 0;
|
| 190 |
+
loadProgressState.estimatedFiles = 0;
|
| 191 |
+
loadProgressState.nextRequestId = 1;
|
| 192 |
+
loadProgressState.requests.clear();
|
| 193 |
+
loadProgressEl.hidden = false;
|
| 194 |
+
if (loadProgressState.timerId) clearInterval(loadProgressState.timerId);
|
| 195 |
+
loadProgressState.timerId = setInterval(renderLoadProgress, 250);
|
| 196 |
+
renderLoadProgress();
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
function setLoadPhase(detail, title = "") {
|
| 200 |
+
if (!loadProgressState.visible) return;
|
| 201 |
+
if (title) loadProgressState.title = title;
|
| 202 |
+
if (detail) loadProgressState.detail = detail;
|
| 203 |
+
scheduleLoadProgressRender();
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
function setLoadEstimate(totalBytes, expectedFiles = 0) {
|
| 207 |
+
if (!loadProgressState.visible) return;
|
| 208 |
+
const bytes = Number(totalBytes || 0);
|
| 209 |
+
if (Number.isFinite(bytes) && bytes > loadProgressState.estimatedTotalBytes) {
|
| 210 |
+
loadProgressState.estimatedTotalBytes = bytes;
|
| 211 |
+
}
|
| 212 |
+
const files = Number(expectedFiles || 0);
|
| 213 |
+
if (Number.isFinite(files) && files > loadProgressState.estimatedFiles) {
|
| 214 |
+
loadProgressState.estimatedFiles = files;
|
| 215 |
+
}
|
| 216 |
+
scheduleLoadProgressRender();
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
function finishLoadProgress(detail = "Ready", success = true) {
|
| 220 |
+
if (!loadProgressState.visible) return;
|
| 221 |
+
loadProgressState.tracking = false;
|
| 222 |
+
loadProgressState.completed = success;
|
| 223 |
+
if (!success) loadProgressState.title = detail;
|
| 224 |
+
loadProgressState.detail = detail;
|
| 225 |
+
if (success) {
|
| 226 |
+
const loaded = loadProgressTotals().loaded;
|
| 227 |
+
loadProgressState.estimatedTotalBytes = Math.max(loadProgressState.estimatedTotalBytes, loaded);
|
| 228 |
+
}
|
| 229 |
+
if (loadProgressState.timerId) {
|
| 230 |
+
clearInterval(loadProgressState.timerId);
|
| 231 |
+
loadProgressState.timerId = 0;
|
| 232 |
+
}
|
| 233 |
+
renderLoadProgress();
|
| 234 |
+
if (success) {
|
| 235 |
+
loadProgressState.hideTimerId = setTimeout(() => {
|
| 236 |
+
loadProgressState.visible = false;
|
| 237 |
+
if (loadProgressEl) loadProgressEl.hidden = true;
|
| 238 |
+
}, 4500);
|
| 239 |
+
}
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
function loadProgressTotals() {
|
| 243 |
+
let loaded = 0;
|
| 244 |
+
let dynamicTotal = 0;
|
| 245 |
+
let active = 0;
|
| 246 |
+
let completed = 0;
|
| 247 |
+
let failed = 0;
|
| 248 |
+
let current = "";
|
| 249 |
+
for (const request of loadProgressState.requests.values()) {
|
| 250 |
+
loaded += request.loaded || 0;
|
| 251 |
+
if (request.total) dynamicTotal += request.total;
|
| 252 |
+
if (request.failed) failed += 1;
|
| 253 |
+
if (request.done || request.failed) {
|
| 254 |
+
completed += 1;
|
| 255 |
+
} else {
|
| 256 |
+
active += 1;
|
| 257 |
+
current = request.label || current;
|
| 258 |
+
}
|
| 259 |
+
}
|
| 260 |
+
return {
|
| 261 |
+
loaded,
|
| 262 |
+
total: Math.max(loadProgressState.estimatedTotalBytes, dynamicTotal, loaded),
|
| 263 |
+
active,
|
| 264 |
+
completed,
|
| 265 |
+
failed,
|
| 266 |
+
current,
|
| 267 |
+
};
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
function renderLoadProgress() {
|
| 271 |
+
if (!loadProgressEl || !loadProgressState.visible) return;
|
| 272 |
+
loadProgressState.renderQueued = false;
|
| 273 |
+
const { loaded, total, active, completed, failed, current } = loadProgressTotals();
|
| 274 |
+
const elapsed = Math.max(0.001, (performance.now() - loadProgressState.startedAt) / 1000);
|
| 275 |
+
const rate = loaded / elapsed;
|
| 276 |
+
const hasTotal = total > 0;
|
| 277 |
+
const percent = hasTotal ? Math.max(0, Math.min(100, (loaded / total) * 100)) : 0;
|
| 278 |
+
const displayPercent = hasTotal && loadProgressState.tracking && active > 0
|
| 279 |
+
? Math.min(percent, 99.5)
|
| 280 |
+
: percent;
|
| 281 |
+
const files = loadProgressState.estimatedFiles
|
| 282 |
+
? `${Math.min(completed, loadProgressState.estimatedFiles)}/${loadProgressState.estimatedFiles} files`
|
| 283 |
+
: `${completed}${active ? ` +${active}` : ""} files`;
|
| 284 |
+
const summary = !loadProgressState.tracking && !loadProgressState.completed
|
| 285 |
+
? "Error"
|
| 286 |
+
: loadProgressState.completed
|
| 287 |
+
? "Ready"
|
| 288 |
+
: hasTotal
|
| 289 |
+
? `${displayPercent.toFixed(displayPercent >= 10 ? 0 : 1)}%`
|
| 290 |
+
: "Loading";
|
| 291 |
+
const detailParts = [
|
| 292 |
+
hasTotal ? `${formatBytesCompact(loaded)} / ${formatBytesCompact(total)}` : `${formatBytesCompact(loaded)} downloaded`,
|
| 293 |
+
files,
|
| 294 |
+
failed ? `${failed} failed` : "",
|
| 295 |
+
current || loadProgressState.detail,
|
| 296 |
+
].filter(Boolean);
|
| 297 |
+
loadProgressTitleEl.textContent = loadProgressState.title;
|
| 298 |
+
loadProgressSummaryEl.textContent = summary;
|
| 299 |
+
loadProgressDetailEl.textContent = detailParts.join(" | ");
|
| 300 |
+
loadProgressRateEl.textContent = `${formatBytesCompact(rate)}/s | ${elapsed.toFixed(1)}s`;
|
| 301 |
+
loadProgressTrackEl.classList.toggle("indeterminate", !hasTotal && loadProgressState.tracking);
|
| 302 |
+
loadProgressFillEl.style.width = hasTotal ? `${loadProgressState.completed ? 100 : displayPercent}%` : "";
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
function scheduleLoadProgressRender() {
|
| 306 |
+
if (!loadProgressState.visible || loadProgressState.renderQueued) return;
|
| 307 |
+
loadProgressState.renderQueued = true;
|
| 308 |
+
requestAnimationFrame(renderLoadProgress);
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
function shouldTrackLoadFetch(input, init, response) {
|
| 312 |
+
if (!loadProgressState.tracking || !response || !response.body) return false;
|
| 313 |
+
const method = String(init?.method || (input && typeof input === "object" && input.method) || "GET").toUpperCase();
|
| 314 |
+
if (method !== "GET") return false;
|
| 315 |
+
return true;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
function trackLoadFetch(input, response) {
|
| 319 |
+
const url = response.url || (typeof input === "string" ? input : input?.url) || "";
|
| 320 |
+
const totalHeader = response.headers.get("content-length");
|
| 321 |
+
const total = totalHeader ? Number(totalHeader) : 0;
|
| 322 |
+
const id = loadProgressState.nextRequestId++;
|
| 323 |
+
const request = {
|
| 324 |
+
id,
|
| 325 |
+
url,
|
| 326 |
+
label: compactLoadUrlLabel(url),
|
| 327 |
+
total: Number.isFinite(total) && total > 0 ? total : 0,
|
| 328 |
+
loaded: 0,
|
| 329 |
+
done: false,
|
| 330 |
+
failed: false,
|
| 331 |
+
};
|
| 332 |
+
loadProgressState.requests.set(id, request);
|
| 333 |
+
scheduleLoadProgressRender();
|
| 334 |
+
|
| 335 |
+
const reader = response.body.getReader();
|
| 336 |
+
const stream = new ReadableStream({
|
| 337 |
+
async pull(controller) {
|
| 338 |
+
try {
|
| 339 |
+
const { done, value } = await reader.read();
|
| 340 |
+
if (done) {
|
| 341 |
+
request.done = true;
|
| 342 |
+
scheduleLoadProgressRender();
|
| 343 |
+
controller.close();
|
| 344 |
+
return;
|
| 345 |
+
}
|
| 346 |
+
request.loaded += value.byteLength || value.length || 0;
|
| 347 |
+
scheduleLoadProgressRender();
|
| 348 |
+
controller.enqueue(value);
|
| 349 |
+
} catch (err) {
|
| 350 |
+
request.failed = true;
|
| 351 |
+
scheduleLoadProgressRender();
|
| 352 |
+
controller.error(err);
|
| 353 |
+
}
|
| 354 |
+
},
|
| 355 |
+
cancel(reason) {
|
| 356 |
+
request.done = true;
|
| 357 |
+
scheduleLoadProgressRender();
|
| 358 |
+
return reader.cancel(reason);
|
| 359 |
+
},
|
| 360 |
+
});
|
| 361 |
+
|
| 362 |
+
return new Response(stream, {
|
| 363 |
+
status: response.status,
|
| 364 |
+
statusText: response.statusText,
|
| 365 |
+
headers: response.headers,
|
| 366 |
+
});
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
function installLoadProgressFetch() {
|
| 370 |
+
if (globalThis.__flux2LoadProgressFetchInstalled) return;
|
| 371 |
+
globalThis.__flux2LoadProgressFetchInstalled = true;
|
| 372 |
+
globalThis.fetch = async (input, init) => {
|
| 373 |
+
const response = await nativeFetch(input, init);
|
| 374 |
+
if (!shouldTrackLoadFetch(input, init, response)) return response;
|
| 375 |
+
return trackLoadFetch(input, response);
|
| 376 |
+
};
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
function huggingFaceResolveSpec(baseUrl) {
|
| 380 |
let url;
|
| 381 |
try {
|
|
|
|
| 723 |
|
| 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;
|
| 730 |
env.allowRemoteModels = tokenizerSpec.remote;
|
| 731 |
env.localModelPath = "/";
|
| 732 |
+
tokenizerPromise = AutoTokenizer.from_pretrained(tokenizerSpec.source, {
|
| 733 |
+
...tokenizerSpec.options,
|
| 734 |
+
progress_callback: (progress) => {
|
| 735 |
+
const file = progress?.file ? compactLoadUrlLabel(progress.file) : "tokenizer";
|
| 736 |
+
const status = progress?.status ? ` ${progress.status}` : "";
|
| 737 |
+
setLoadPhase(`Loading ${file}${status}`);
|
| 738 |
+
},
|
| 739 |
+
});
|
| 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);
|
| 746 |
const cacheKey = `${seqLen}|${promptCacheKey}`;
|
|
|
|
| 782 |
const engine = await waitForEngine();
|
| 783 |
const modelBaseUrl = configuredModelBaseUrl();
|
| 784 |
const runtimeBaseUrl = configuredRuntimeBaseUrl(modelBaseUrl);
|
| 785 |
+
setLoadPhase("Loading model config and runtime manifest");
|
| 786 |
const result = await engine.init(modelBaseUrl, {
|
| 787 |
executionProvider: "webgpu",
|
| 788 |
customKernelBaseUrl: configuredCustomKernelBaseUrl(modelBaseUrl, runtimeBaseUrl),
|
|
|
|
| 1305 |
});
|
| 1306 |
}
|
| 1307 |
|
| 1308 |
+
function shouldAssetPrepareRequest(request) {
|
| 1309 |
+
if (urlFlag("noAutowarm") || urlFlag("noAssetWarm")) return false;
|
| 1310 |
+
return request.transformerBackend === "custom-lowbit-webgpu";
|
| 1311 |
+
}
|
| 1312 |
+
|
| 1313 |
+
function fullManifestLinearById(manifest, id) {
|
| 1314 |
+
const linears = Array.isArray(manifest?.linears) ? manifest.linears : [];
|
| 1315 |
+
return linears.find((item) => item.id === id || item.name === id || item.source_node === id) || null;
|
| 1316 |
+
}
|
| 1317 |
+
|
| 1318 |
+
function fullManifestQkNormById(manifest, id) {
|
| 1319 |
+
const qkNorms = Array.isArray(manifest?.constants?.qk_norms) ? manifest.constants.qk_norms : [];
|
| 1320 |
+
return qkNorms.find((item) => item.id === id || item.name === id) || null;
|
| 1321 |
+
}
|
| 1322 |
+
|
| 1323 |
+
function estimateLinearAssetBytes(linear) {
|
| 1324 |
+
if (!linear) return { bytes: 0, files: 0 };
|
| 1325 |
+
if (linear.i8_dp4a) {
|
| 1326 |
+
const packed = Number(linear.i8_dp4a.packed_u32_count || 0) * 4;
|
| 1327 |
+
const scales = Number(linear.i8_dp4a.scale_count || 0) * 4;
|
| 1328 |
+
return { bytes: packed + scales, files: 2 };
|
| 1329 |
+
}
|
| 1330 |
+
if (linear.q4) {
|
| 1331 |
+
const qweight = Number(linear.q4.qweight_u32_count || linear.q4.packed_u32_count || 0) * 4;
|
| 1332 |
+
const scales = Number(linear.q4.scale_count || 0) * 2;
|
| 1333 |
+
const zeroPoints = Number(linear.q4.zero_point_count || linear.q4.zero_points_u32_count || 0) * 4;
|
| 1334 |
+
return { bytes: qweight + scales + zeroPoints, files: 3 };
|
| 1335 |
+
}
|
| 1336 |
+
return { bytes: 0, files: 0 };
|
| 1337 |
+
}
|
| 1338 |
+
|
| 1339 |
+
function estimateQkNormBytes(entry) {
|
| 1340 |
+
if (!entry) return { bytes: 0, files: 0 };
|
| 1341 |
+
const count = Array.isArray(entry.shape) && Number.isFinite(Number(entry.shape[0])) ? Number(entry.shape[0]) : 128;
|
| 1342 |
+
return { bytes: count * 2 * 2, files: 2 };
|
| 1343 |
+
}
|
| 1344 |
+
|
| 1345 |
+
function estimateCustomTransformerAssetSet(manifest, request) {
|
| 1346 |
+
const doubleBlockCount = Math.max(1, Math.min(5, Math.trunc(Number(request.speed.customMaxDoubleBlocks || 5))));
|
| 1347 |
+
const singleBlockCount = Math.max(0, Math.min(20, Math.trunc(Number(request.speed.customMaxSingleBlocks || 20))));
|
| 1348 |
+
const linearIds = ["img_in", "txt_in", "final_layer.linear"];
|
| 1349 |
+
const qkNormIds = [];
|
| 1350 |
+
for (let block = 0; block < doubleBlockCount; block += 1) {
|
| 1351 |
+
linearIds.push(
|
| 1352 |
+
`double_blocks.${block}.img_attn.qkv`,
|
| 1353 |
+
`double_blocks.${block}.txt_attn.qkv`,
|
| 1354 |
+
`double_blocks.${block}.img_attn.proj`,
|
| 1355 |
+
`double_blocks.${block}.txt_attn.proj`,
|
| 1356 |
+
`double_blocks.${block}.img_mlp.0`,
|
| 1357 |
+
`double_blocks.${block}.img_mlp.2`,
|
| 1358 |
+
`double_blocks.${block}.txt_mlp.0`,
|
| 1359 |
+
`double_blocks.${block}.txt_mlp.2`,
|
| 1360 |
+
);
|
| 1361 |
+
qkNormIds.push(`double_blocks.${block}.img_attn.norm`, `double_blocks.${block}.txt_attn.norm`);
|
| 1362 |
+
}
|
| 1363 |
+
for (let block = 0; block < singleBlockCount; block += 1) {
|
| 1364 |
+
linearIds.push(`single_blocks.${block}.linear1`, `single_blocks.${block}.linear2`);
|
| 1365 |
+
qkNormIds.push(`single_blocks.${block}.norm`);
|
| 1366 |
+
}
|
| 1367 |
+
let bytes = 0;
|
| 1368 |
+
let files = 0;
|
| 1369 |
+
for (const id of linearIds) {
|
| 1370 |
+
const estimate = estimateLinearAssetBytes(fullManifestLinearById(manifest, id));
|
| 1371 |
+
bytes += estimate.bytes;
|
| 1372 |
+
files += estimate.files;
|
| 1373 |
+
}
|
| 1374 |
+
for (const id of qkNormIds) {
|
| 1375 |
+
const estimate = estimateQkNormBytes(fullManifestQkNormById(manifest, id));
|
| 1376 |
+
bytes += estimate.bytes;
|
| 1377 |
+
files += estimate.files;
|
| 1378 |
+
}
|
| 1379 |
+
return { bytes, files, doubleBlockCount, singleBlockCount };
|
| 1380 |
+
}
|
| 1381 |
+
|
| 1382 |
+
async function prepareCustomAssetLoadEstimate(request) {
|
| 1383 |
+
if (!loadProgressState.visible || !shouldAssetPrepareRequest(request)) return null;
|
| 1384 |
+
try {
|
| 1385 |
+
const modelBaseUrl = configuredModelBaseUrl();
|
| 1386 |
+
const runtimeBaseUrl = configuredRuntimeBaseUrl(modelBaseUrl);
|
| 1387 |
+
const customKernelBaseUrl = configuredCustomKernelBaseUrl(modelBaseUrl, runtimeBaseUrl);
|
| 1388 |
+
const manifestUrl = resolveAssetUrl(customKernelBaseUrl, "full_transformer/manifest.json");
|
| 1389 |
+
setLoadPhase("Reading transformer asset manifest");
|
| 1390 |
+
const response = await fetch(manifestUrl);
|
| 1391 |
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
| 1392 |
+
const manifest = await response.json();
|
| 1393 |
+
const estimate = estimateCustomTransformerAssetSet(manifest, request);
|
| 1394 |
+
if (estimate.bytes > 0) {
|
| 1395 |
+
setLoadEstimate(estimate.bytes, estimate.files);
|
| 1396 |
+
setLoadPhase(`Downloading transformer assets (${formatBytesCompact(estimate.bytes)})`);
|
| 1397 |
+
}
|
| 1398 |
+
return estimate;
|
| 1399 |
+
} catch (err) {
|
| 1400 |
+
console.warn("[flux2] custom asset byte estimate failed", err);
|
| 1401 |
+
return null;
|
| 1402 |
+
}
|
| 1403 |
+
}
|
| 1404 |
+
|
| 1405 |
+
function textContextPrepareKeyForRequest(request) {
|
| 1406 |
+
return JSON.stringify({
|
| 1407 |
+
prompt: request.prompt,
|
| 1408 |
backend: request.transformerBackend,
|
| 1409 |
textLimit: request.speed.customTextTokenLimit,
|
| 1410 |
minText: request.speed.customMinTextTokens,
|
|
|
|
| 1424 |
const key = textContextPrepareKeyForRequest(prepareRequest);
|
| 1425 |
if (backgroundTextContextPreparePromise && backgroundTextContextPrepareKey === key) return true;
|
| 1426 |
backgroundTextContextPrepareKey = key;
|
| 1427 |
+
backgroundTextContextPreparePromise = initEngine()
|
| 1428 |
+
.then(async ({ engine, init }) => {
|
| 1429 |
+
if (typeof engine.encodeText !== "function") return null;
|
| 1430 |
+
setLoadPhase("Preparing text context");
|
| 1431 |
+
const seqLen = Number(init.config.text_seq_len || 512);
|
| 1432 |
const tokenized = await tokenizePrompt(prepareRequest.prompt, seqLen);
|
| 1433 |
const params = buildGenerateParams(prepareRequest, tokenized, null);
|
| 1434 |
if (typeof engine.prepareCustomTransformerTextProjection === "function") {
|
|
|
|
| 1490 |
const key = `${transformerSetupPrepareKeyForRequest(prepareRequest)}|dispatch=${dispatchWarmup ? 1 : 0}`;
|
| 1491 |
if (backgroundTransformerSetupPreparePromise && backgroundTransformerSetupPrepareKey === key) return true;
|
| 1492 |
backgroundTransformerSetupPrepareKey = key;
|
| 1493 |
+
backgroundTransformerSetupPreparePromise = initEngine()
|
| 1494 |
+
.then(async ({ engine, init }) => {
|
| 1495 |
+
if (typeof engine.prepareCustomTransformerStageSetup !== "function") return null;
|
| 1496 |
+
setLoadPhase(dispatchWarmup ? "Preparing transformer dispatch warmup" : "Preparing transformer setup");
|
| 1497 |
+
const seqLen = Number(init.config.text_seq_len || 512);
|
| 1498 |
+
const tokenized = await tokenizePrompt(prepareRequest.prompt, seqLen);
|
| 1499 |
const plannedTextTokens = plannedCustomTextTokens(prepareRequest, tokenized.attentionMask, seqLen);
|
| 1500 |
return await engine.prepareCustomTransformerStageSetup({
|
| 1501 |
width: prepareRequest.width,
|
|
|
|
| 1574 |
const key = assetPrepareKeyForRequest(request);
|
| 1575 |
if (backgroundAssetPreparePromise && backgroundAssetPrepareKey === key) return true;
|
| 1576 |
backgroundAssetPrepareKey = key;
|
| 1577 |
+
backgroundAssetPreparePromise = initEngine()
|
| 1578 |
+
.then(({ engine }) => {
|
| 1579 |
+
if (typeof engine.prepareCustomTransformerAssets !== "function") return null;
|
| 1580 |
+
setLoadPhase("Downloading transformer assets");
|
| 1581 |
+
return engine.prepareCustomTransformerAssets({
|
| 1582 |
+
maxDoubleBlocks: request.speed.customMaxDoubleBlocks || 5,
|
| 1583 |
+
maxSingleBlocks: request.speed.customMaxSingleBlocks || 20,
|
| 1584 |
});
|
| 1585 |
})
|
| 1586 |
.then((result) => {
|
|
|
|
| 1636 |
const key = vaeEncoderPrepareKeyForRequest(request);
|
| 1637 |
if (backgroundVaeEncoderPreparePromise && backgroundVaeEncoderPrepareKey === key) return true;
|
| 1638 |
backgroundVaeEncoderPrepareKey = key;
|
| 1639 |
+
backgroundVaeEncoderPreparePromise = initEngine()
|
| 1640 |
+
.then(async ({ engine }) => {
|
| 1641 |
+
if (typeof engine.prepareVaeEncoderLatent !== "function") return null;
|
| 1642 |
+
setLoadPhase("Preparing source image latent");
|
| 1643 |
+
const renderSize = plannedRenderSizeForRequest(request);
|
| 1644 |
const initImage = await prepareInitImage(renderSize.width, renderSize.height, request.fitMode);
|
| 1645 |
if (!initImage) return null;
|
| 1646 |
return await engine.prepareVaeEncoderLatent({
|
|
|
|
| 1714 |
if (backgroundVaePreparePromise && backgroundVaePrepareKey === key) return true;
|
| 1715 |
backgroundVaePrepareKey = key;
|
| 1716 |
const plannedDecodeMax = plannedDecodeMaxForRequest(prepareRequest);
|
| 1717 |
+
backgroundVaePreparePromise = initEngine()
|
| 1718 |
+
.then(({ engine }) => {
|
| 1719 |
+
if (typeof engine.prepareVaeSessions !== "function") return null;
|
| 1720 |
+
setLoadPhase(dispatchWarmup ? "Preparing VAE dispatch warmup" : "Preparing VAE sessions");
|
| 1721 |
+
return engine.prepareVaeSessions({
|
| 1722 |
width: prepareRequest.width,
|
| 1723 |
height: prepareRequest.height,
|
| 1724 |
hasInitImage: prepareRequest.hasInitImage,
|
|
|
|
| 2682 |
const shouldPrepareVae = shouldVaePrepareRequest(prepareRequest);
|
| 2683 |
if (!shouldPrepareAssets && !shouldPrepareTransformer && !shouldPrepareVae) return false;
|
| 2684 |
|
| 2685 |
+
setStatus([
|
| 2686 |
+
"Preparing fast path",
|
| 2687 |
+
`Backend: ${prepareRequest.transformerBackend}`,
|
| 2688 |
+
`Speed Profile: ${speedProfileLabel(prepareRequest.speed)}`,
|
| 2689 |
+
prepareRequest.previewRenderMaxSize ? `Preview render: ${prepareRequest.previewRenderMaxSize}px max` : "",
|
| 2690 |
+
prepareRequest.previewDecodeMaxSize ? `Preview decode: ${prepareRequest.previewDecodeMaxSize}px max` : "",
|
| 2691 |
+
]);
|
| 2692 |
+
setLoadPhase("Preparing fast path");
|
| 2693 |
+
|
| 2694 |
+
if (shouldPrepareAssets) await prepareCustomAssetLoadEstimate(prepareRequest);
|
| 2695 |
+
startBackgroundAssetPrepareForCurrentSettings(request);
|
| 2696 |
+
const assetPromise = backgroundAssetPreparePromise;
|
| 2697 |
if (assetPromise) {
|
| 2698 |
try {
|
| 2699 |
await assetPromise;
|
|
|
|
| 2714 |
|
| 2715 |
if (foregroundBusy) return true;
|
| 2716 |
const finalPrepareRequest = progressiveFinalPrepareRequestForRequest(currentRequest);
|
| 2717 |
+
if (finalPrepareRequest && warmKeyForRequest(readRequest()) === warmKeyForRequest(request)) {
|
| 2718 |
+
setStatus([
|
| 2719 |
+
"Preparing refinement path",
|
| 2720 |
`Backend: ${finalPrepareRequest.transformerBackend}`,
|
| 2721 |
`Speed Profile: ${speedProfileLabel(finalPrepareRequest.speed)}`,
|
| 2722 |
finalPrepareRequest.previewRenderMaxSize ? `Preview render: ${finalPrepareRequest.previewRenderMaxSize}px max` : "",
|
| 2723 |
+
finalPrepareRequest.previewDecodeMaxSize ? `Preview decode: ${finalPrepareRequest.previewDecodeMaxSize}px max` : "",
|
| 2724 |
+
]);
|
| 2725 |
+
setLoadPhase("Preparing refinement path");
|
| 2726 |
+
startStageAndVaePrepareForRequest(finalPrepareRequest);
|
| 2727 |
+
await waitForStartedPreparePromises();
|
| 2728 |
+
}
|
| 2729 |
return true;
|
| 2730 |
}
|
| 2731 |
|
| 2732 |
+
async function boot() {
|
| 2733 |
+
beginLoadProgress("First load");
|
| 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 |
runButton.disabled = false;
|
| 2742 |
setButtonDisabled(warmButton, false);
|
|
|
|
| 2747 |
seedInputLine(),
|
| 2748 |
`Tokenizer: ${tokenizer.constructor?.name || "loaded"}`,
|
| 2749 |
`Full image backend: ${init.customKernels?.fullImageBackend || "unknown"}`,
|
| 2750 |
+
]);
|
| 2751 |
+
if (!fastPathPrepared) {
|
| 2752 |
+
startBackgroundAssetPrepareForCurrentSettings(bootRequest);
|
| 2753 |
+
scheduleBackgroundStageAndVaePrepareForCurrentSettings(bootRequest);
|
| 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 |
warmButton?.addEventListener("click", () => {
|
| 2766 |
if (foregroundBusy) return;
|
static.html
CHANGED
|
@@ -387,15 +387,85 @@
|
|
| 387 |
font-weight: 750;
|
| 388 |
min-width: 96px;
|
| 389 |
}
|
| 390 |
-
.elapsed-meter.active {
|
| 391 |
-
border-color: var(--accent);
|
| 392 |
-
color: var(--accent-strong);
|
| 393 |
-
background: var(--accent-soft);
|
| 394 |
-
}
|
| 395 |
-
|
| 396 |
-
margin:
|
| 397 |
-
padding: 12px;
|
| 398 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
border: 1px solid var(--line);
|
| 400 |
border-radius: var(--radius);
|
| 401 |
background: var(--panel-2);
|
|
@@ -628,13 +698,26 @@
|
|
| 628 |
<input id="decode-max" type="hidden" value="0">
|
| 629 |
<input id="render-max" type="hidden" value="0">
|
| 630 |
</div>
|
| 631 |
-
<div class="actions">
|
| 632 |
-
<button id="run" type="button" disabled><span class="icon">auto_awesome</span>Generate</button>
|
| 633 |
-
<button id="abort" class="secondary" type="button" disabled><span class="icon">stop</span>Stop</button>
|
| 634 |
-
<div id="elapsed" class="elapsed-meter" aria-live="polite">0.0s</div>
|
| 635 |
-
</div>
|
| 636 |
-
<
|
| 637 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 638 |
<section>
|
| 639 |
<div class="stage">
|
| 640 |
<img id="image" alt="Generated image" hidden>
|
|
|
|
| 387 |
font-weight: 750;
|
| 388 |
min-width: 96px;
|
| 389 |
}
|
| 390 |
+
.elapsed-meter.active {
|
| 391 |
+
border-color: var(--accent);
|
| 392 |
+
color: var(--accent-strong);
|
| 393 |
+
background: var(--accent-soft);
|
| 394 |
+
}
|
| 395 |
+
.load-progress {
|
| 396 |
+
margin: 14px 0 0;
|
| 397 |
+
padding: 12px;
|
| 398 |
+
border: 1px solid var(--line);
|
| 399 |
+
border-radius: var(--radius);
|
| 400 |
+
background: var(--panel-2);
|
| 401 |
+
}
|
| 402 |
+
.load-progress[hidden] {
|
| 403 |
+
display: none;
|
| 404 |
+
}
|
| 405 |
+
.load-progress-head,
|
| 406 |
+
.load-progress-meta {
|
| 407 |
+
display: flex;
|
| 408 |
+
align-items: center;
|
| 409 |
+
justify-content: space-between;
|
| 410 |
+
gap: 12px;
|
| 411 |
+
}
|
| 412 |
+
.load-progress-title {
|
| 413 |
+
min-width: 0;
|
| 414 |
+
overflow: hidden;
|
| 415 |
+
text-overflow: ellipsis;
|
| 416 |
+
white-space: nowrap;
|
| 417 |
+
font-size: 12px;
|
| 418 |
+
font-weight: 800;
|
| 419 |
+
color: var(--text);
|
| 420 |
+
}
|
| 421 |
+
.load-progress-summary {
|
| 422 |
+
flex: 0 0 auto;
|
| 423 |
+
color: var(--accent-strong);
|
| 424 |
+
font-size: 12px;
|
| 425 |
+
font-weight: 800;
|
| 426 |
+
}
|
| 427 |
+
.load-progress-track {
|
| 428 |
+
position: relative;
|
| 429 |
+
height: 9px;
|
| 430 |
+
margin: 10px 0 8px;
|
| 431 |
+
overflow: hidden;
|
| 432 |
+
border-radius: 999px;
|
| 433 |
+
background: color-mix(in srgb, var(--line) 55%, transparent);
|
| 434 |
+
}
|
| 435 |
+
.load-progress-fill {
|
| 436 |
+
width: 0%;
|
| 437 |
+
height: 100%;
|
| 438 |
+
border-radius: inherit;
|
| 439 |
+
background: var(--accent);
|
| 440 |
+
transition: width 160ms ease;
|
| 441 |
+
}
|
| 442 |
+
.load-progress-track.indeterminate .load-progress-fill {
|
| 443 |
+
width: 42%;
|
| 444 |
+
animation: load-progress-sweep 1.15s ease-in-out infinite;
|
| 445 |
+
}
|
| 446 |
+
@keyframes load-progress-sweep {
|
| 447 |
+
0% { transform: translateX(-110%); }
|
| 448 |
+
100% { transform: translateX(245%); }
|
| 449 |
+
}
|
| 450 |
+
.load-progress-meta {
|
| 451 |
+
color: var(--muted);
|
| 452 |
+
font: 11px/1.35 ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
|
| 453 |
+
}
|
| 454 |
+
.load-progress-detail {
|
| 455 |
+
min-width: 0;
|
| 456 |
+
overflow: hidden;
|
| 457 |
+
text-overflow: ellipsis;
|
| 458 |
+
white-space: nowrap;
|
| 459 |
+
}
|
| 460 |
+
.load-progress-rate {
|
| 461 |
+
flex: 0 0 auto;
|
| 462 |
+
color: var(--muted);
|
| 463 |
+
white-space: nowrap;
|
| 464 |
+
}
|
| 465 |
+
pre {
|
| 466 |
+
margin: 16px 0 0;
|
| 467 |
+
padding: 12px;
|
| 468 |
+
min-height: 132px;
|
| 469 |
border: 1px solid var(--line);
|
| 470 |
border-radius: var(--radius);
|
| 471 |
background: var(--panel-2);
|
|
|
|
| 698 |
<input id="decode-max" type="hidden" value="0">
|
| 699 |
<input id="render-max" type="hidden" value="0">
|
| 700 |
</div>
|
| 701 |
+
<div class="actions">
|
| 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>
|
| 705 |
+
</div>
|
| 706 |
+
<div id="load-progress" class="load-progress" aria-live="polite" hidden>
|
| 707 |
+
<div class="load-progress-head">
|
| 708 |
+
<div id="load-progress-title" class="load-progress-title">Loading</div>
|
| 709 |
+
<div id="load-progress-summary" class="load-progress-summary">0%</div>
|
| 710 |
+
</div>
|
| 711 |
+
<div id="load-progress-track" class="load-progress-track">
|
| 712 |
+
<div id="load-progress-fill" class="load-progress-fill"></div>
|
| 713 |
+
</div>
|
| 714 |
+
<div class="load-progress-meta">
|
| 715 |
+
<div id="load-progress-detail" class="load-progress-detail">Starting</div>
|
| 716 |
+
<div id="load-progress-rate" class="load-progress-rate">0 MiB/s</div>
|
| 717 |
+
</div>
|
| 718 |
+
</div>
|
| 719 |
+
<pre id="status">Loading</pre>
|
| 720 |
+
</aside>
|
| 721 |
<section>
|
| 722 |
<div class="stage">
|
| 723 |
<img id="image" alt="Generated image" hidden>
|