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>
  );
}