/** * LLM Monitor - Main App * Gestisce il Web Worker e aggiorna il DOM in modo efficiente */ class LLMMonitorApp { constructor() { this.worker = null; this.activeServer = getActiveServer(); this.selectedModelName = null; this.isModalOpen = false; this.hoverOpenDelayMs = 180; this.hoverOpenTimer = null; this.lastData = { health: null, models: null }; this.init(); } init() { if (!this.activeServer) { this.renderNoServerState(); return; } this.updateServerContextUI(); // Inizializzare il Web Worker if (typeof Worker !== 'undefined') { this.worker = new Worker('/static/js/data-sync.worker.js'); this.worker.onmessage = (event) => this.handleWorkerMessage(event); this.worker.onerror = (error) => { console.error("Worker error:", error); // Fallback: sincronizzazione nel main thread this.syncDataInMainThread(); }; this.worker.postMessage({ type: "SET_SERVER", serverId: this.activeServer.id, host: this.activeServer.host }); } else { console.warn("Web Workers not supported, using main thread"); this.syncDataInMainThread(); } // Caricare dati da localStorage this.loadFromLocalStorage(); // Listener al pulsante manuale document.getElementById("refresh-btn")?.addEventListener("click", () => { this.requestSync(); }); // Chiusura modal con pulsante X document.getElementById("model-details-close")?.addEventListener("click", () => { this.hideModelDetails(); }); // Chiusura modal con click su overlay document.getElementById("model-details-backdrop")?.addEventListener("click", () => { this.hideModelDetails(); }); // Chiusura modal con tasto Esc document.addEventListener("keydown", (event) => { if (event.key === "Escape") { this.hideModelDetails(); } }); } // Caricare dati da localStorage loadFromLocalStorage() { const healthStr = localStorage.getItem(this.getStorageKey("health")); const modelsStr = localStorage.getItem(this.getStorageKey("models")); if (healthStr) { try { this.lastData.health = JSON.parse(healthStr); this.renderHealth(this.lastData.health); } catch (e) { console.error("Error parsing health data:", e); } } if (modelsStr) { try { this.lastData.models = JSON.parse(modelsStr); this.renderModels(this.lastData.models); } catch (e) { console.error("Error parsing models data:", e); } } } // Gestire messaggi dal Worker handleWorkerMessage(event) { const { type, health, modelsData, serverId } = event.data; if (serverId && this.activeServer && serverId !== this.activeServer.id) { return; } if (type === "DATA_UPDATED") { if (health && JSON.stringify(this.lastData.health) !== JSON.stringify(health)) { this.lastData.health = health; try { localStorage.setItem(this.getStorageKey("health"), JSON.stringify(health)); } catch (error) { console.warn("Cannot persist health in localStorage:", error); } this.renderHealth(health); } if (modelsData && JSON.stringify(this.lastData.models) !== JSON.stringify(modelsData)) { this.lastData.models = modelsData; try { localStorage.setItem(this.getStorageKey("models"), JSON.stringify(modelsData)); } catch (error) { console.warn("Cannot persist models in localStorage:", error); } this.renderModels(modelsData); if (this.selectedModelName) { this.showModelDetails(this.selectedModelName); } } } } // Renderizzare Health (aggiornamento granulare) renderHealth(health) { if (!health) return; const ollamaStatus = health.ollama_status; const statusEl = document.getElementById("status-indicator"); const statusText = document.getElementById("status-text"); const ollamaStatusEl = document.getElementById("ollama-status"); if (ollamaStatus === "online") { // Aggiornare solo se cambiato if (!statusEl.classList.contains("bg-green-500")) { statusEl.className = "w-3 h-3 bg-green-500 rounded-full"; statusText.className = "text-sm text-green-400"; statusText.textContent = "Ollama Online"; ollamaStatusEl.innerHTML = "๐ข Online"; } } else { if (!statusEl.classList.contains("bg-red-500")) { statusEl.className = "w-3 h-3 bg-red-500 rounded-full"; statusText.className = "text-sm text-red-400"; statusText.textContent = "Ollama Offline"; ollamaStatusEl.innerHTML = "๐ด Offline"; } } } // Renderizzare Modelli (aggiornamento granulare) renderModels(modelsData) { if (!modelsData) return; // Aggiornare conteggio document.getElementById("models-count").textContent = modelsData.total; // Aggiornare spazio totale document.getElementById("total-size").textContent = modelsData.totalSize; // Aggiornare lista modelli const container = document.getElementById("models-container"); const { models } = modelsData; if (models.length === 0) { container.innerHTML = `
No models loaded
Size
${this.formatBytes(model.size)}
Last Updated
${formattedDate}
Digest
${this.escapeHtml(model.digest.substring(0, 64))}...
Hover or click to view show details