Files
2026-01-14 18:00:49 +01:00

275 lines
8.3 KiB
JavaScript

import { api } from "/scripts/api.js";
import { LOG_PREFIX, CACHE_KEY, API_ENDPOINTS } from "./Constants.js";
const MAX_CONCURRENT_REQUESTS = 4;
// IndexedDB Configuration
const DB_NAME = "MadNodesDB";
const STORE_NAME = "cache";
const DB_VERSION = 1;
class IDBAdapter {
constructor() {
this.db = null;
this.readyPromise = this.init();
}
init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = (e) => {
const db = e.target.result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME);
}
};
request.onsuccess = (e) => {
this.db = e.target.result;
resolve();
};
request.onerror = (e) => reject(e);
});
}
async getAll() {
await this.readyPromise;
return new Promise((resolve, reject) => {
const tx = this.db.transaction(STORE_NAME, "readonly");
const store = tx.objectStore(STORE_NAME);
const request = store.openCursor();
const result = {};
request.onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) {
result[cursor.key] = cursor.value;
cursor.continue();
} else {
resolve(result);
}
};
request.onerror = () => reject(request.error);
});
}
async set(key, value) {
await this.readyPromise;
return new Promise((resolve, reject) => {
const tx = this.db.transaction(STORE_NAME, "readwrite");
const store = tx.objectStore(STORE_NAME);
const request = store.put(value, key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
async delete(key) {
await this.readyPromise;
return new Promise((resolve, reject) => {
const tx = this.db.transaction(STORE_NAME, "readwrite");
const store = tx.objectStore(STORE_NAME);
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
async clear() {
await this.readyPromise;
return new Promise((resolve, reject) => {
const tx = this.db.transaction(STORE_NAME, "readwrite");
const store = tx.objectStore(STORE_NAME);
const request = store.clear();
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
}
export class RequestManager {
constructor() {
this.queue = [];
this.activeRequests = 0;
this.storageCache = {};
this.onQueueChange = null;
this.db = new IDBAdapter();
this.hydrate();
}
async hydrate() {
try {
const data = await this.db.getAll();
if (data && Object.keys(data).length > 0) {
this.storageCache = data;
console.log(`${LOG_PREFIX} Cache hydrated from IndexedDB.`);
} else {
const rawLegacy = localStorage.getItem(CACHE_KEY);
if (rawLegacy) {
try {
console.log(`${LOG_PREFIX} Migrating legacy localStorage cache to IndexedDB...`);
const legacyData = JSON.parse(rawLegacy);
this.storageCache = legacyData;
for (const [k, v] of Object.entries(legacyData)) {
this.db.set(k, v);
}
localStorage.removeItem(CACHE_KEY);
} catch (e) {
console.warn(`${LOG_PREFIX} Legacy migration failed`, e);
}
}
}
} catch (e) {
console.error(`${LOG_PREFIX} Failed to hydrate cache`, e);
}
}
getCached(key) {
return this.storageCache[key] || null;
}
setCached(key, data) {
this.storageCache[key] = data;
this.db.set(key, data).catch((e) => console.warn(`${LOG_PREFIX} Failed to write to IDB`, e));
}
clearStorage(specificKey = null) {
if (specificKey) {
delete this.storageCache[specificKey];
this.db.delete(specificKey);
} else {
this.storageCache = {};
this.db.clear();
}
}
schedule(id, fetchFn, highPriority = false) {
return new Promise((resolve, reject) => {
const task = { id, fn: fetchFn, resolve, reject };
if (highPriority) {
this.queue.unshift(task);
} else {
this.queue.push(task);
}
this._processQueue();
});
}
async _processQueue() {
if (this.activeRequests >= MAX_CONCURRENT_REQUESTS || this.queue.length === 0) return;
this.activeRequests++;
const task = this.queue.shift();
try {
const result = await task.fn();
task.resolve(result);
} catch (error) {
console.error(`${LOG_PREFIX} Request failed: ${task.id}`, error);
task.reject(error);
} finally {
this.activeRequests--;
this._processQueue();
}
}
async fetchConfig() {
try {
const res = await api.fetchApi(API_ENDPOINTS.CONFIG);
if (!res.ok) throw new Error("Config fetch failed");
return await res.json();
} catch (e) {
console.error(`${LOG_PREFIX} Failed to fetch UI config`, e);
return {};
}
}
async fetchLoraList() {
try {
const response = await api.fetchApi(API_ENDPOINTS.LORA_LIST);
const data = await response.json();
return data?.LoraLoader?.input?.required?.lora_name?.[0] || [];
} catch (e) {
console.error(`${LOG_PREFIX} Failed to fetch LoRA list`, e);
throw e;
}
}
async clearBackendCache() {
try {
await api.fetchApi(`${API_ENDPOINTS.INSPECT}?clear_cache_all=true`);
} catch (e) {
console.error(`${LOG_PREFIX} Failed to clear backend cache`, e);
}
}
async inspectLora(loraName, forceRefresh = false, isHighPriority = false) {
if (!loraName) return { arch: "UNKNOWN" };
if (!forceRefresh) {
const cached = this.getCached(`inspect:${loraName}`);
if (cached && cached.stats) return cached;
}
const priority = isHighPriority || forceRefresh;
return this.schedule(
`inspect:${loraName}`,
async () => {
const url = `${API_ENDPOINTS.INSPECT}?lora_name=${encodeURIComponent(loraName)}&refresh=${forceRefresh}`;
const res = await api.fetchApi(url);
if (!res.ok) throw new Error(res.statusText);
const data = await res.json();
this.setCached(`inspect:${loraName}`, data);
return data;
},
priority,
);
}
primeInspectCache(loraName, arch) {
if (!loraName || !arch || arch === "UNKNOWN") return;
const key = `inspect:${loraName}`;
const existing = this.getCached(key);
if (!existing) {
this.setCached(key, { arch: arch, stats: null });
} else if (existing.arch === "UNKNOWN") {
existing.arch = arch;
this.setCached(key, existing);
}
}
async checkCompatibility(ckptName, loraName) {
const key = `compat:${ckptName}:${loraName}`;
const cached = this.getCached(key);
if (cached) return cached;
return this.schedule(
key,
async () => {
const url = `${API_ENDPOINTS.COMPAT}?ckpt_name=${encodeURIComponent(ckptName)}&lora_name=${encodeURIComponent(loraName)}`;
const res = await api.fetchApi(url);
const data = await res.json();
this.setCached(key, data);
return data;
},
false,
);
}
}
export const requestManager = new RequestManager();