275 lines
8.3 KiB
JavaScript
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();
|