Fix visualizzazione statistiche
This commit is contained in:
@@ -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
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user