Fix visualizzazione statistiche

This commit is contained in:
Samuele E. Locatelli
2025-09-05 17:50:16 +00:00
parent 4c04306d1d
commit 776030832e
3 changed files with 195 additions and 58 deletions
+46 -14
View File
@@ -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
+48 -44
View File
@@ -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 ? (
<ModelOverview onBackToChat={() => setShowModelPage(false)} />
) : (
/* AREA SCROLLABILE */
<div className="flex-grow-1 overflow-auto" style={{ minHeight: 0 }}>
<ChatWindow messages={messages} loading={loading} theme={theme} />
<div className="flex-grow-1 overflow-auto" style={{ minHeight: 0 }}>
{showModelPage ? (
<div>
<LmStats />
<ModelOverview onBackToChat={() => setShowModelPage(false)} />
</div>
)}
{/* INPUT SEMPRE IN BASSO */}
{!showModelPage && (
sessionId ? (
<ChatInput
onSend={(message, modelName) => onSend(message, modelName)}
onStop={onStop}
loading={loading}
sessionModelName={sessionModelName || ""}
/>
) : (
<NoSessionBox onCreateSession={onCreateSession} />
)
)}
) : (
/* AREA SCROLLABILE */
<ChatWindow messages={messages} loading={loading} theme={theme} />
)}
</div>
{/* PANEL SESSIONI */}
<div
className={`offcanvas offcanvas-start ${showSessionsPanel ? "show" : ""}`}
tabIndex="-1"
style={{ visibility: showSessionsPanel ? "visible" : "hidden" }}
>
<div className="offcanvas-header">
<h5 className="offcanvas-title">Manage Sessions</h5>
<button
type="button"
className="btn-close text-reset"
onClick={closeSessionManager}
></button>
</div>
<div className="offcanvas-body">
<SessionTable
userId={userId}
onSelectSession={onSelectSession}
onClosePanel={closeSessionManager}
/>
</div>
{/* INPUT SEMPRE IN BASSO */}
{!showModelPage && (
sessionId ? (
<ChatInput
onSend={(message, modelName) => onSend(message, modelName)}
onStop={onStop}
loading={loading}
sessionModelName={sessionModelName || ""}
/>
) : (
<NoSessionBox onCreateSession={onCreateSession} />
)
)}
</div>
{/* PANEL SESSIONI */}
<div
className={`offcanvas offcanvas-start ${showSessionsPanel ? "show" : ""}`}
tabIndex="-1"
style={{ visibility: showSessionsPanel ? "visible" : "hidden" }}
>
<div className="offcanvas-header">
<h5 className="offcanvas-title">Manage Sessions</h5>
<button
type="button"
className="btn-close text-reset"
onClick={closeSessionManager}
></button>
</div>
</>
<div className="offcanvas-body">
<SessionTable
userId={userId}
onSelectSession={onSelectSession}
onClosePanel={closeSessionManager}
/>
</div>
</div>
</>
);
}
+101
View File
@@ -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 <div className="alert alert-info">Caricamento statistiche...</div>;
}
if (error) {
return <div className="alert alert-danger">Errore: {error}</div>;
}
if (!stats) {
return <div className="alert alert-warning">Nessun dato disponibile</div>;
}
return (
<div className="container my-4">
<h2 className="mb-4">📊 Statistiche LM Studio</h2>
{/* Statistiche globali */}
<div className="card mb-4">
<div className="card-header bg-primary text-white">
🌍 Globali
</div>
<div className="card-body p-0">
<table className="table table-striped mb-0">
<thead className="table-light">
<tr>
<th>Chiamate totali</th>
<th>Ultima ora</th>
<th>Ultime 24h</th>
<th>Tempo totale (s)</th>
</tr>
</thead>
<tbody>
<tr>
<td>{stats.global.calls_total}</td>
<td>{stats.global.calls_last_hour}</td>
<td>{stats.global.calls_last_24h}</td>
<td>{stats.global.total_processing_time_sec.toFixed(2)}</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* Statistiche per modello */}
<div className="card">
<div className="card-header bg-success text-white">
🧠 Per Modello
</div>
<div className="card-body p-0">
<table className="table table-hover mb-0">
<thead className="table-light">
<tr>
<th>Modello</th>
<th>Ultima ora</th>
<th>Ultime 24h</th>
<th>Tempo totale (s)</th>
<th>Tempo medio (s)</th>
</tr>
</thead>
<tbody>
{stats.per_model.map((m) => (
<tr key={m.model}>
<td>{m.model}</td>
<td>{m.calls_last_hour}</td>
<td>{m.calls_last_24h}</td>
<td>{m.total_time_sec.toFixed(2)}</td>
<td>{m.avg_time_sec.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}