fix: handle local storage quota for model cache
This commit is contained in:
@@ -296,13 +296,43 @@ class LLMMonitorApp {
|
|||||||
detailsModal.setAttribute("aria-hidden", "false");
|
detailsModal.setAttribute("aria-hidden", "false");
|
||||||
|
|
||||||
if (!showData) {
|
if (!showData) {
|
||||||
detailsContent.textContent = "Show details are not available for this model.";
|
detailsContent.textContent = "Loading show details...";
|
||||||
|
this.loadModelShowDetails(modelName, detailsContent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
detailsContent.textContent = JSON.stringify(showData, null, 2);
|
detailsContent.textContent = JSON.stringify(showData, null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadModelShowDetails(modelName, detailsContent) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(this.buildApiUrl(`/api/v1/models/${encodeURIComponent(modelName)}/show`));
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to load show details for ${modelName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const showData = await response.json();
|
||||||
|
if (!this.lastData.models) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.lastData.models.showByModel) {
|
||||||
|
this.lastData.models.showByModel = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastData.models.showByModel[modelName] = showData;
|
||||||
|
|
||||||
|
if (this.selectedModelName === modelName) {
|
||||||
|
detailsContent.textContent = JSON.stringify(showData, null, 2);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
if (this.selectedModelName === modelName) {
|
||||||
|
detailsContent.textContent = "Show details are not available for this model.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hideModelDetails() {
|
hideModelDetails() {
|
||||||
const detailsModal = document.getElementById("model-details-modal");
|
const detailsModal = document.getElementById("model-details-modal");
|
||||||
const detailsDialog = document.getElementById("model-details-dialog");
|
const detailsDialog = document.getElementById("model-details-dialog");
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const SERVER_STORAGE_KEY = "llm_monitor_servers";
|
const SERVER_STORAGE_KEY = "llm_monitor_servers";
|
||||||
const ACTIVE_SERVER_KEY = "llm_monitor_active_server";
|
const ACTIVE_SERVER_KEY = "llm_monitor_active_server";
|
||||||
const DATA_REFRESH_INTERVAL_MS = 30000;
|
const DATA_REFRESH_INTERVAL_MS = 30000;
|
||||||
|
const SERVER_CACHE_SUFFIXES = ["health", "models", "running"];
|
||||||
|
|
||||||
function normalizeHost(host) {
|
function normalizeHost(host) {
|
||||||
if (!host) {
|
if (!host) {
|
||||||
@@ -41,6 +42,7 @@ function loadServers() {
|
|||||||
|
|
||||||
function saveServers(servers) {
|
function saveServers(servers) {
|
||||||
localStorage.setItem(SERVER_STORAGE_KEY, JSON.stringify(servers));
|
localStorage.setItem(SERVER_STORAGE_KEY, JSON.stringify(servers));
|
||||||
|
cleanupOrphanedServerCaches(servers);
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateServerId() {
|
function generateServerId() {
|
||||||
@@ -121,11 +123,108 @@ function readServerCache(serverId, suffix) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function writeServerCache(serverId, suffix, value) {
|
function writeServerCache(serverId, suffix, value) {
|
||||||
|
if (!serverId) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const storageKey = getServerStorageKey(serverId, suffix);
|
||||||
|
const candidates = [value];
|
||||||
|
|
||||||
|
cleanupOrphanedServerCaches();
|
||||||
|
|
||||||
|
if (suffix === "models") {
|
||||||
|
candidates.push(createSlimModelsCache(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(candidate));
|
||||||
|
return candidate;
|
||||||
|
} catch (error) {
|
||||||
|
if (!isQuotaExceededError(error)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last resort: free stale server caches and retry with the smallest payload.
|
||||||
|
cleanupOrphanedServerCaches(loadServers());
|
||||||
|
|
||||||
|
if (suffix === "models") {
|
||||||
|
const slimValue = createSlimModelsCache(value);
|
||||||
|
try {
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(slimValue));
|
||||||
|
return slimValue;
|
||||||
|
} catch (error) {
|
||||||
|
if (!isQuotaExceededError(error)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
console.warn(`Cache quota exceeded for ${storageKey}; using in-memory models data only.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn(`Cache quota exceeded for ${storageKey}; skipping persistence for this payload.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSlimModelsCache(value) {
|
||||||
|
if (!value || typeof value !== "object") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const slimValue = { ...value };
|
||||||
|
if (slimValue.showByModel) {
|
||||||
|
delete slimValue.showByModel;
|
||||||
|
slimValue.showDetailsDeferred = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return slimValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isQuotaExceededError(error) {
|
||||||
|
return error instanceof DOMException && (
|
||||||
|
error.code === 22 ||
|
||||||
|
error.code === 1014 ||
|
||||||
|
error.name === "QuotaExceededError" ||
|
||||||
|
error.name === "NS_ERROR_DOM_QUOTA_REACHED"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupOrphanedServerCaches(servers = loadServers()) {
|
||||||
|
const validServerIds = new Set(servers.map((server) => server.id));
|
||||||
|
const keysToRemove = [];
|
||||||
|
|
||||||
|
for (let index = 0; index < localStorage.length; index += 1) {
|
||||||
|
const key = localStorage.key(index);
|
||||||
|
if (!key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const suffix of SERVER_CACHE_SUFFIXES) {
|
||||||
|
const prefix = `llm_monitor_${suffix}_`;
|
||||||
|
if (!key.startsWith(prefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverId = key.slice(prefix.length);
|
||||||
|
if (!validServerIds.has(serverId)) {
|
||||||
|
keysToRemove.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearServerCaches(serverId) {
|
||||||
if (!serverId) {
|
if (!serverId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem(getServerStorageKey(serverId, suffix), JSON.stringify(value));
|
SERVER_CACHE_SUFFIXES.forEach((suffix) => {
|
||||||
|
localStorage.removeItem(getServerStorageKey(serverId, suffix));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCacheTimestamp(cacheValue) {
|
function getCacheTimestamp(cacheValue) {
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class ServersPage {
|
|||||||
deleteServer(serverId) {
|
deleteServer(serverId) {
|
||||||
const servers = loadServers().filter((server) => server.id !== serverId);
|
const servers = loadServers().filter((server) => server.id !== serverId);
|
||||||
saveServers(servers);
|
saveServers(servers);
|
||||||
|
clearServerCaches(serverId);
|
||||||
|
|
||||||
const activeServerId = getActiveServerId();
|
const activeServerId = getActiveServerId();
|
||||||
if (activeServerId === serverId) {
|
if (activeServerId === serverId) {
|
||||||
|
|||||||
Reference in New Issue
Block a user