diff --git a/app/web/static/js/app.js b/app/web/static/js/app.js
index 0d04d86..70dbc56 100644
--- a/app/web/static/js/app.js
+++ b/app/web/static/js/app.js
@@ -156,7 +156,7 @@ class LLMMonitorApp {
if (models.length === 0) {
container.innerHTML = `
${modelName}
- Caricato
+ Loaded
-
Dimensione
+
Size
${this.formatBytes(model.size)}
-
Ultimo aggiornamento
+
Last Updated
${formattedDate}
@@ -245,7 +245,7 @@ class LLMMonitorApp {
Digest
${this.escapeHtml(model.digest.substring(0, 64))}...
-
-
Aggiornamento in corso...
+
Refreshing data...
`;
try {
const response = await fetch("/api/v1/models/running");
if (!response.ok) {
- throw new Error("Errore nel caricamento dei modelli residenti");
+ throw new Error("Failed to load running models");
}
const data = await response.json();
@@ -38,7 +38,7 @@ class RunningModelsPage {
} catch (error) {
container.innerHTML = `
-
Errore nel caricamento di ollama ps
+
Failed to load ollama ps output
`;
this.renderStats([]);
@@ -60,7 +60,7 @@ class RunningModelsPage {
vramTotalEl.textContent = this.formatBytes(totalVram);
}
if (lastRefreshEl) {
- lastRefreshEl.textContent = new Date().toLocaleString("it-IT");
+ lastRefreshEl.textContent = new Date().toLocaleString("en-US");
}
}
@@ -73,7 +73,7 @@ class RunningModelsPage {
if (models.length === 0) {
container.innerHTML = `
-
Nessun modello residente in memoria al momento.
+
No models are currently loaded in memory.
`;
return;
@@ -99,15 +99,15 @@ class RunningModelsPage {
-
Dimensione modello
+
Model Size
${size}
-
VRAM usata
+
VRAM Used
${sizeVram}
@@ -115,7 +115,7 @@ class RunningModelsPage {
${processor}
-
Scarico previsto
+
Unload Time
${expiresAt}
@@ -139,7 +139,7 @@ class RunningModelsPage {
return "-";
}
- return date.toLocaleString("it-IT", {
+ return date.toLocaleString("en-US", {
year: "numeric",
month: "short",
day: "2-digit",
diff --git a/app/web/static/js/pwa-register.js b/app/web/static/js/pwa-register.js
new file mode 100644
index 0000000..e46f76c
--- /dev/null
+++ b/app/web/static/js/pwa-register.js
@@ -0,0 +1,13 @@
+(() => {
+ if (!("serviceWorker" in navigator)) {
+ return;
+ }
+
+ window.addEventListener("load", async () => {
+ try {
+ await navigator.serviceWorker.register("/service-worker.js", { scope: "/" });
+ } catch (error) {
+ console.error("Service worker registration failed:", error);
+ }
+ });
+})();
diff --git a/app/web/static/js/service-worker.js b/app/web/static/js/service-worker.js
new file mode 100644
index 0000000..045ad62
--- /dev/null
+++ b/app/web/static/js/service-worker.js
@@ -0,0 +1,72 @@
+const CACHE_NAME = "llm-monitor-v1";
+const APP_SHELL = [
+ "/",
+ "/models-running",
+ "/models-available",
+ "/static/css/output.css",
+ "/static/js/app.js",
+ "/static/js/models-running.js",
+ "/static/js/data-sync.worker.js",
+ "/static/js/pwa-register.js",
+ "/manifest.webmanifest",
+ "/favicon.ico"
+];
+
+self.addEventListener("install", (event) => {
+ event.waitUntil(
+ caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL))
+ );
+ self.skipWaiting();
+});
+
+self.addEventListener("activate", (event) => {
+ event.waitUntil(
+ caches.keys().then((keys) =>
+ Promise.all(
+ keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))
+ )
+ )
+ );
+ self.clients.claim();
+});
+
+self.addEventListener("fetch", (event) => {
+ if (event.request.method !== "GET") {
+ return;
+ }
+
+ const requestUrl = new URL(event.request.url);
+ const isApiRequest = requestUrl.pathname.startsWith("/api/");
+
+ if (isApiRequest) {
+ event.respondWith(
+ fetch(event.request).catch(() =>
+ new Response(JSON.stringify({ detail: "Offline" }), {
+ status: 503,
+ headers: { "Content-Type": "application/json" }
+ })
+ )
+ );
+ return;
+ }
+
+ event.respondWith(
+ caches.match(event.request).then((cached) => {
+ if (cached) {
+ return cached;
+ }
+
+ return fetch(event.request)
+ .then((response) => {
+ if (!response || response.status !== 200 || response.type !== "basic") {
+ return response;
+ }
+
+ const responseClone = response.clone();
+ caches.open(CACHE_NAME).then((cache) => cache.put(event.request, responseClone));
+ return response;
+ })
+ .catch(() => caches.match("/models-running"));
+ })
+ );
+});
diff --git a/app/web/static/manifest.webmanifest b/app/web/static/manifest.webmanifest
new file mode 100644
index 0000000..75e42db
--- /dev/null
+++ b/app/web/static/manifest.webmanifest
@@ -0,0 +1,18 @@
+{
+ "name": "LLM Monitor",
+ "short_name": "LLM Monitor",
+ "description": "Monitor available and running Ollama models.",
+ "start_url": "/",
+ "scope": "/",
+ "display": "standalone",
+ "background_color": "#111827",
+ "theme_color": "#111827",
+ "lang": "en",
+ "icons": [
+ {
+ "src": "/favicon.ico",
+ "sizes": "any",
+ "type": "image/x-icon"
+ }
+ ]
+}
diff --git a/app/web/templates/index.html b/app/web/templates/index.html
index ac90806..88c3775 100644
--- a/app/web/templates/index.html
+++ b/app/web/templates/index.html
@@ -1,10 +1,14 @@
-
+