Spaces:
Sleeping
Sleeping
File size: 4,733 Bytes
aa63765 345b8ff aa63765 d25df55 aa63765 d25df55 aa63765 345b8ff aa63765 d25df55 aa63765 d25df55 aa63765 d25df55 aa63765 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
"use client";
import { useState } from "react";
import { useTenant } from "@/contexts/TenantContext";
type ToolUsageStats = {
count: number;
avg_latency_ms: number;
total_tokens: number;
success_count: number;
error_count: number;
};
type AnalyticsOverview = {
overview: {
total_queries: number;
tool_usage: Record<string, ToolUsageStats>;
redflag_count: number;
active_users: number;
};
};
const API_BASE =
process.env.NEXT_PUBLIC_API_URL?.replace(/\/$/, "") || "http://localhost:8000";
export function AnalyticsPanel() {
const { tenantId } = useTenant();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState<AnalyticsOverview["overview"] | null>(null);
async function fetchAnalytics() {
setLoading(true);
setError(null);
try {
const res = await fetch(`${API_BASE}/analytics/overview`, {
headers: {
"x-tenant-id": tenantId,
},
});
if (!res.ok) {
throw new Error(`Analytics endpoint returned ${res.status}`);
}
const payload: AnalyticsOverview = await res.json();
setData(payload.overview);
} catch (err) {
console.error(err);
setError(
err instanceof Error
? err.message
: "Unable to reach analytics API. Is the FastAPI service running?",
);
} finally {
setLoading(false);
}
}
return (
<section
id="analytics"
className="glass-panel border border-white/10 p-6 text-white"
>
<div className="flex flex-wrap items-center justify-between gap-4">
<div>
<p className="text-sm uppercase tracking-[0.5em] text-cyan-200/70">
Compliance Pulse
</p>
<h2 className="mt-2 text-3xl font-semibold">Analytics snapshot</h2>
</div>
<button
onClick={fetchAnalytics}
disabled={loading}
className="rounded-full bg-white/90 px-5 py-2.5 text-sm font-semibold text-slate-900 shadow-lg shadow-cyan-500/30 transition hover:-translate-y-0.5 disabled:opacity-60"
>
{loading ? "Loading…" : "Refresh metrics"}
</button>
</div>
<div className="mt-6 grid gap-4 md:grid-cols-4">
{["total_queries", "active_users", "redflag_count"].map((key) => (
<div
key={key}
className="rounded-2xl border border-white/5 bg-slate-900/40 p-4 text-center"
>
<p className="text-sm uppercase tracking-widest text-slate-400">
{key.replace("_", " ")}
</p>
<p className="mt-2 text-3xl font-semibold">
{data ? data[key as keyof typeof data] : "—"}
</p>
</div>
))}
<div className="rounded-2xl border border-white/5 bg-slate-900/40 p-4 text-center">
<p className="text-sm uppercase tracking-widest text-slate-400">
Tool usage (top)
</p>
<p className="mt-2 text-3xl font-semibold">
{data
? Object.entries(data.tool_usage)
.sort((a, b) => b[1].count - a[1].count)[0]?.[0] ?? "—"
: "—"}
</p>
</div>
</div>
<div className="mt-6 rounded-2xl border border-white/5 bg-slate-950/50 p-4">
<p className="text-sm uppercase tracking-[0.5em] text-slate-400">
Raw tool usage
</p>
<div className="mt-4 grid gap-3 sm:grid-cols-3">
{data
? Object.entries(data.tool_usage).map(([tool, stats]) => (
<div
key={tool}
className="rounded-xl border border-white/10 bg-white/5 px-4 py-3"
>
<p className="text-sm uppercase tracking-widest text-slate-400">
{tool}
</p>
<p className="text-2xl font-semibold text-white">{stats.count}</p>
</div>
))
: Array.from({ length: 3 }).map((_, idx) => (
<div
key={idx}
className="rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-slate-500"
>
<p className="text-sm uppercase tracking-widest text-slate-500">
Tool {idx + 1}
</p>
<p className="text-2xl font-semibold text-slate-500">—</p>
</div>
))}
</div>
</div>
{error && (
<p className="mt-4 rounded-2xl border border-red-500/40 bg-red-500/10 px-4 py-3 text-sm text-red-200">
{error}
</p>
)}
</section>
);
}
|