diff --git a/backend/services/history_manager.py b/backend/services/history_manager.py index a2be196..7996c2c 100644 --- a/backend/services/history_manager.py +++ b/backend/services/history_manager.py @@ -12,36 +12,68 @@ SUMMARY_TRIGGER_TURNS = 20 # STATISTICHE LM STUDIO # ------------------------- +# services/history_manager.py + def track_lm_call(model_name: str, elapsed_seconds: float): """ - Aggiorna le statistiche di utilizzo LM Studio in Redis. + Aggiorna le statistiche di utilizzo LM Studio in Redis, sia globali che per modello. """ + model_key = model_name.replace(" ", "_") # per sicurezza nelle chiavi Redis pipe = redis_service.r.pipeline() - # Contatori globali + + # --- Globali --- pipe.incr("lm:calls:total") pipe.incr("lm:calls:last_hour") pipe.expire("lm:calls:last_hour", 3600) pipe.incr("lm:calls:last_24h") pipe.expire("lm:calls:last_24h", 86400) pipe.incrbyfloat("lm:processing_time:total", elapsed_seconds) - # Modelli caricati - if model_name: - pipe.sadd("lm:models:loaded", model_name) - pipe.execute() + # --- Per modello --- + pipe.sadd("lm:models:loaded", model_name) + + # Chiamate + pipe.incr(f"lm:model:{model_key}:calls:last_hour") + pipe.expire(f"lm:model:{model_key}:calls:last_hour", 3600) + pipe.incr(f"lm:model:{model_key}:calls:last_24h") + pipe.expire(f"lm:model:{model_key}:calls:last_24h", 86400) + + # Tempo totale + pipe.incrbyfloat(f"lm:model:{model_key}:time:total", elapsed_seconds) + + pipe.execute() def get_lm_stats(): """ - Restituisce le statistiche aggregate da Redis. + Restituisce statistiche globali e per modello. """ - return { - "models_loaded": list(redis_service.r.smembers("lm:models:loaded")), - "calls_total": int(redis_service.r.get("lm:calls:total") or 0), - "calls_last_hour": int(redis_service.r.get("lm:calls:last_hour") or 0), - "calls_last_24h": int(redis_service.r.get("lm:calls:last_24h") or 0), - "total_processing_time_sec": float(redis_service.r.get("lm:processing_time:total") or 0.0) - } + models = list(redis_service.r.smembers("lm:models:loaded")) + stats_per_model = [] + for m in models: + model_key = m.replace(" ", "_") + calls_last_hour = int(redis_service.r.get(f"lm:model:{model_key}:calls:last_hour") or 0) + calls_last_24h = int(redis_service.r.get(f"lm:model:{model_key}:calls:last_24h") or 0) + total_time = float(redis_service.r.get(f"lm:model:{model_key}:time:total") or 0.0) + avg_time = (total_time / calls_last_24h) if calls_last_24h > 0 else 0.0 + + stats_per_model.append({ + "model": m, + "calls_last_hour": calls_last_hour, + "calls_last_24h": calls_last_24h, + "total_time_sec": total_time, + "avg_time_sec": avg_time + }) + + return { + "global": { + "calls_total": int(redis_service.r.get("lm:calls:total") or 0), + "calls_last_hour": int(redis_service.r.get("lm:calls:last_hour") or 0), + "calls_last_24h": int(redis_service.r.get("lm:calls:last_24h") or 0), + "total_processing_time_sec": float(redis_service.r.get("lm:processing_time:total") or 0.0) + }, + "per_model": stats_per_model + } # ------------------------- # HISTORY diff --git a/frontend/src/ChatLayout.jsx b/frontend/src/ChatLayout.jsx index f6193a3..60a271b 100644 --- a/frontend/src/ChatLayout.jsx +++ b/frontend/src/ChatLayout.jsx @@ -6,6 +6,7 @@ import ChatInput from "./ChatInput"; import SessionTable from "./SessionTable"; import NoSessionBox from "./NoSessionBox"; import ModelOverview from "./ModelOverview"; +import LmStats from "./LmStats"; export default function ChatLayout({ theme, @@ -70,56 +71,59 @@ export default function ChatLayout({ onToggleModels={toggleModelsDetail} sessionId={sessionId} sessionName={sessionName} - defaultModelName={defaultModelName} + defaultModelName={defaultModelName} /> - {showModelPage ? ( - setShowModelPage(false)} /> - ) : ( - /* AREA SCROLLABILE */ -
- +
+ {showModelPage ? ( +
+ + setShowModelPage(false)} />
- )} - - {/* INPUT SEMPRE IN BASSO */} - {!showModelPage && ( - sessionId ? ( - onSend(message, modelName)} - onStop={onStop} - loading={loading} - sessionModelName={sessionModelName || ""} - /> - ) : ( - - ) - )} + ) : ( + /* AREA SCROLLABILE */ + + )}
- {/* PANEL SESSIONI */} -
-
-
Manage Sessions
- -
-
- -
+ {/* INPUT SEMPRE IN BASSO */} + {!showModelPage && ( + sessionId ? ( + onSend(message, modelName)} + onStop={onStop} + loading={loading} + sessionModelName={sessionModelName || ""} + /> + ) : ( + + ) + )} +
+ + {/* PANEL SESSIONI */} +
+
+
Manage Sessions
+
- +
+ +
+
+ ); } diff --git a/frontend/src/LmStats.jsx b/frontend/src/LmStats.jsx new file mode 100644 index 0000000..bfd901b --- /dev/null +++ b/frontend/src/LmStats.jsx @@ -0,0 +1,101 @@ +// src/LmStats.jsx +import React, { useEffect, useState } from "react"; + +export default function LmStats() { + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function loadStats() { + try { + const res = await fetch("/v1/lm-stats"); // Adatta se API su dominio diverso + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const data = await res.json(); + setStats(data); + } catch (err) { + console.error("Errore caricamento stats:", err); + setError(err.message); + } finally { + setLoading(false); + } + } + loadStats(); + }, []); + + if (loading) { + return
Caricamento statistiche...
; + } + + if (error) { + return
Errore: {error}
; + } + + if (!stats) { + return
Nessun dato disponibile
; + } + + return ( +
+

📊 Statistiche LM Studio

+ + {/* Statistiche globali */} +
+
+ 🌍 Globali +
+
+ + + + + + + + + + + + + + + + + +
Chiamate totaliUltima oraUltime 24hTempo totale (s)
{stats.global.calls_total}{stats.global.calls_last_hour}{stats.global.calls_last_24h}{stats.global.total_processing_time_sec.toFixed(2)}
+
+
+ + {/* Statistiche per modello */} +
+
+ 🧠 Per Modello +
+
+ + + + + + + + + + + + {stats.per_model.map((m) => ( + + + + + + + + ))} + +
ModelloUltima oraUltime 24hTempo totale (s)Tempo medio (s)
{m.model}{m.calls_last_hour}{m.calls_last_24h}{m.total_time_sec.toFixed(2)}{m.avg_time_sec.toFixed(2)}
+
+
+
+ ); +}