diff --git a/PRD.md b/PRD.md index 81daa41..8616ece 100644 --- a/PRD.md +++ b/PRD.md @@ -131,12 +131,15 @@ Attualmente, per verificare i modelli LLM in Ollama, รจ necessario: - Data ultimo aggiornamento - Digest (hash univoco) - Pulsante refresh manuale +- Pannello dettagli modello su click card **Behavior:** - Auto-refresh ogni 30 secondi - Aggiorna solo elementi cambiati (no full re-render) - Mostra loading state durante fetch - Error handling con messaggi chiari +- Durante il refresh lista, chiama `show` per ogni modello e salva i dettagli in cache locale +- Click su card modello apre i dettagli `show` senza page reload --- @@ -187,6 +190,9 @@ Dettagli di un modello specifico } ``` +#### `GET /api/v1/models/{model_name}/show` +Proxy dell'endpoint Ollama `POST /api/show` per ottenere informazioni estese sul modello + #### `POST /api/v1/models/{model_name}/pull` Scarica/carica un modello (**disabilitato di default**) @@ -221,7 +227,7 @@ Elimina un modello (**disabilitato di default**) **Dati Salvati:** - `llm_monitor_health` - Status health -- `llm_monitor_models` - Elenco modelli +- `llm_monitor_models` - Elenco modelli + mappa dettagli `showByModel` **Benefit:** - Offline support diff --git a/README.md b/README.md index 0177569..d7500aa 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Una dashboard web moderna e intuitiva per monitorare e gestire i modelli LLM car - โจ **Dashboard intuitiva** - Visualizza in tempo reale i modelli caricati in Ollama - ๐ **Monitoraggio modelli** - Dettagli completi di ogni modello (nome, dimensione, memoria, stato) +- ๐งฉ **Dettagli avanzati on click** - Clic su una card modello per visualizzare i dati Ollama `show` - ๐ **API REST documentata** - Documentazione interattiva con Swagger/OpenAPI - ๐จ **UI moderna** - Interfaccia elegante realizzata con TailwindCSS - ๐ณ **Docker ready** - Container sempre acceso (until stopped) @@ -147,6 +148,12 @@ GET /api/v1/models GET /api/v1/models/{model_name} ``` +#### Dettagli estesi da Ollama show + +```bash +GET /api/v1/models/{model_name}/show +``` + #### Health check API Ollama ```bash @@ -187,10 +194,19 @@ curl http://localhost:8000/api/v1/models # Ottenere info su un modello curl http://localhost:8000/api/v1/models/llama2 +# Ottenere dettagli estesi show +curl http://localhost:8000/api/v1/models/llama2/show + # Health check curl http://localhost:8000/api/v1/health ``` +### Comportamento dashboard + +- Al refresh della lista modelli, per ogni modello viene recuperato anche il dettaglio `show`. +- I dati vengono salvati in localStorage nella chiave `llm_monitor_models` (campo `showByModel`). +- Cliccando su una card modello, la dashboard mostra i dettagli `show` senza ricaricare la pagina. + ## ๐ณ Docker ### Build dell'immagine diff --git a/app/api/models.py b/app/api/models.py index 9bf8250..5991017 100644 --- a/app/api/models.py +++ b/app/api/models.py @@ -4,7 +4,7 @@ Models endpoints - Gestione dei modelli Ollama from fastapi import APIRouter, HTTPException from pydantic import BaseModel -from typing import List, Optional +from typing import Any, Dict, List, Optional from datetime import datetime import requests import logging @@ -174,6 +174,58 @@ async def get_model(model_name: str): detail="Errore nel recupero del modello" ) + +@router.get("/models/{model_name}/show") +async def get_model_show(model_name: str) -> Dict[str, Any]: + """ + Recupera le informazioni estese di un modello tramite endpoint Ollama /api/show. + + Args: + model_name: Nome del modello da interrogare + + Returns: + Dict[str, Any]: Dati estesi del modello + """ + try: + response = requests.post( + f"{settings.OLLAMA_HOST}/api/show", + json={"model": model_name}, + timeout=settings.OLLAMA_TIMEOUT + ) + + if response.status_code == 404: + raise HTTPException( + status_code=404, + detail=f"Modello '{model_name}' non trovato" + ) + + if response.status_code != 200: + raise HTTPException( + status_code=502, + detail="Errore durante il recupero dettagli modello" + ) + + return response.json() + + except requests.exceptions.Timeout: + raise HTTPException( + status_code=504, + detail="Timeout: Ollama non ha risposto in tempo" + ) + except requests.exceptions.ConnectionError: + raise HTTPException( + status_code=502, + detail="Impossible connettersi a Ollama" + ) + except HTTPException: + raise + except Exception as e: + logger.error(f"Error fetching model show data: {e}") + raise HTTPException( + status_code=500, + detail="Errore nel recupero dei dettagli modello" + ) + @router.post( "/models/{model_name}/pull", include_in_schema=settings.ENABLE_MODEL_RW_API diff --git a/app/web/static/js/app.js b/app/web/static/js/app.js index 304a68b..3c19fd9 100644 --- a/app/web/static/js/app.js +++ b/app/web/static/js/app.js @@ -6,6 +6,7 @@ class LLMMonitorApp { constructor() { this.worker = null; + this.selectedModelName = null; this.lastData = { health: null, models: null @@ -35,6 +36,15 @@ class LLMMonitorApp { document.getElementById("refresh-btn")?.addEventListener("click", () => { this.requestSync(); }); + + // Listener click card modello + document.getElementById("models-container")?.addEventListener("click", (event) => { + const card = event.target.closest("[data-model-name]"); + if (!card) { + return; + } + this.showModelDetails(card.getAttribute("data-model-name")); + }); } // Caricare dati da localStorage @@ -68,12 +78,17 @@ class LLMMonitorApp { if (type === "DATA_UPDATED") { if (health && JSON.stringify(this.lastData.health) !== JSON.stringify(health)) { this.lastData.health = health; + localStorage.setItem("llm_monitor_health", JSON.stringify(health)); this.renderHealth(health); } if (modelsData && JSON.stringify(this.lastData.models) !== JSON.stringify(modelsData)) { this.lastData.models = modelsData; + localStorage.setItem("llm_monitor_models", JSON.stringify(modelsData)); this.renderModels(modelsData); + if (this.selectedModelName) { + this.showModelDetails(this.selectedModelName); + } } } } @@ -140,10 +155,11 @@ class LLMMonitorApp { // Renderizzare singola card modello renderModelCard(model) { const formattedDate = this.formatDate(model.modified_at); + const modelName = this.escapeHtml(model.name); return ` -
Digest
${this.escapeHtml(model.digest.substring(0, 64))}...
Clicca per vedere dettagli show