151 lines
5.1 KiB
TypeScript
151 lines
5.1 KiB
TypeScript
/**
|
|
* Bootstrap - Auto-scan and initialization
|
|
*/
|
|
|
|
import { get, post } from "../api/client.js";
|
|
import { ENDPOINTS } from "../api/endpoints.js";
|
|
import { APP_CONFIG } from "./config.js";
|
|
import { mjrDbg } from "../utils/logging.js";
|
|
|
|
// Auto-scan state (once per session)
|
|
let startupScanDone = false;
|
|
let startupScanTimer: ReturnType<typeof setTimeout> | null = null;
|
|
let startupWarmupDone = false;
|
|
let startupWarmupPromise: Promise<void> | null = null;
|
|
|
|
function isExecutionIdle(): boolean {
|
|
try {
|
|
const runtime = (window as any)?.__MJR_EXECUTION_RUNTIME__;
|
|
return !String(runtime?.active_prompt_id || "").trim();
|
|
} catch (error) {
|
|
console.debug?.(error);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Trigger background scan as soon as the ComfyUI frontend loads (if enabled).
|
|
* This helps warming the DB before the Assets Manager panel is opened.
|
|
*/
|
|
export async function triggerStartupScan(options: { delayMs?: number; idleOnly?: boolean } = {}): Promise<void> {
|
|
if (!APP_CONFIG || typeof APP_CONFIG !== "object") return;
|
|
if (!APP_CONFIG.AUTO_SCAN_ON_STARTUP) return;
|
|
if (startupScanDone) return;
|
|
const delayMs = Math.max(0, Number(options?.delayMs) || 0);
|
|
const idleOnly = options?.idleOnly !== false;
|
|
if (delayMs > 0) {
|
|
if (startupScanTimer) clearTimeout(startupScanTimer);
|
|
startupScanTimer = setTimeout(() => {
|
|
startupScanTimer = null;
|
|
void triggerStartupScan({ ...options, delayMs: 0 });
|
|
}, delayMs);
|
|
return;
|
|
}
|
|
if (idleOnly && !isExecutionIdle()) {
|
|
if (startupScanTimer) clearTimeout(startupScanTimer);
|
|
startupScanTimer = setTimeout(() => {
|
|
startupScanTimer = null;
|
|
void triggerStartupScan({ ...options, delayMs: 0 });
|
|
}, 5000);
|
|
return;
|
|
}
|
|
startupScanDone = true;
|
|
|
|
try {
|
|
mjrDbg("[Majoor] Starting startup scan of output directory...");
|
|
|
|
const result = await post(ENDPOINTS.SCAN, {
|
|
recursive: true,
|
|
incremental: true,
|
|
fast: true,
|
|
background_metadata: true,
|
|
});
|
|
|
|
if (result.ok) {
|
|
const stats = result.data;
|
|
mjrDbg(
|
|
`[Majoor] Startup scan complete - added: ${stats.added}, updated: ${stats.updated}, skipped: ${stats.skipped}`,
|
|
);
|
|
} else {
|
|
console.warn("[Majoor] Majoor [WARN]: Startup scan failed:", result.error);
|
|
}
|
|
} catch (error) {
|
|
console.error("[Majoor] ERROR: Startup scan error:", error);
|
|
}
|
|
}
|
|
|
|
async function warmDbAndStatus(): Promise<void> {
|
|
try {
|
|
await Promise.allSettled([
|
|
get(ENDPOINTS.HEALTH),
|
|
get(`${ENDPOINTS.HEALTH_COUNTERS}?scope=output`),
|
|
get(ENDPOINTS.HEALTH_DB),
|
|
]);
|
|
} catch (error) {
|
|
console.debug?.(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run a single explicit startup warmup sequence:
|
|
* 1. warm API/DB/status endpoints
|
|
* 2. schedule a background incremental scan if enabled and runtime is idle
|
|
*
|
|
* The warmup is yielded to the next idle/RAF tick so it does NOT contend with
|
|
* the early /list fetch on the single-threaded aiohttp backend during cold
|
|
* start. Three GETs (health, counters, db) running in parallel ahead of /list
|
|
* was adding 200-600ms of perceived latency before the first card paint.
|
|
*/
|
|
export async function runStartupWarmup(options: { delayMs?: number; idleOnly?: boolean } = {}): Promise<void> {
|
|
if (startupWarmupDone) return startupWarmupPromise || Promise.resolve();
|
|
startupWarmupDone = true;
|
|
startupWarmupPromise = (async () => {
|
|
await _yieldUntilIdleOrFirstPaint();
|
|
await warmDbAndStatus();
|
|
await triggerStartupScan(options);
|
|
})();
|
|
return startupWarmupPromise;
|
|
}
|
|
|
|
function _yieldUntilIdleOrFirstPaint(): Promise<void> {
|
|
return new Promise<void>((resolve) => {
|
|
try {
|
|
if (typeof window === "undefined") {
|
|
resolve();
|
|
return;
|
|
}
|
|
// Prefer requestIdleCallback so we yield until the browser is idle
|
|
// (after the first /list response has been parsed and the panel
|
|
// has had a chance to render its first cards). Fall back to a
|
|
// double-rAF + small timeout for environments without rIC.
|
|
const ric = window.requestIdleCallback;
|
|
if (typeof ric === "function") {
|
|
ric(() => resolve(), { timeout: 1500 });
|
|
return;
|
|
}
|
|
const raf = window.requestAnimationFrame || ((cb) => setTimeout(cb, 16));
|
|
raf(() => raf(() => setTimeout(resolve, 250)));
|
|
} catch {
|
|
resolve();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Test API connection
|
|
*/
|
|
export async function testAPI(): Promise<void> {
|
|
try {
|
|
mjrDbg("[Majoor] Testing API connection...");
|
|
const data = await get(ENDPOINTS.HEALTH);
|
|
|
|
if (data?.ok) {
|
|
mjrDbg("[Majoor] API connection successful, health:", data.data.overall);
|
|
} else {
|
|
console.error("[Majoor] ERROR: API health check failed:", data?.error);
|
|
}
|
|
} catch (error) {
|
|
console.error("[Majoor] ERROR: Failed to connect to API:", error);
|
|
}
|
|
}
|