Spaces:
Sleeping
Sleeping
File size: 2,793 Bytes
3786a3f f14eb69 3786a3f f14eb69 3786a3f f14eb69 3786a3f f14eb69 3786a3f f14eb69 3786a3f f14eb69 3786a3f | 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 | 'use client';
import { useEffect, useRef, useState } from 'react';
import api from '@/lib/axios';
interface Stats {
nodes: number;
edges: number;
types: number;
documents: number;
}
function Counter({ value, suffix, decimals = 0 }: { value: number; suffix: string; decimals?: number }) {
const [n, setN] = useState(0);
const ref = useRef<HTMLSpanElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const obs = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
let start = 0;
const dur = 1200;
const t0 = performance.now();
const tick = (t: number) => {
const p = Math.min((t - t0) / dur, 1);
setN(value * (1 - Math.pow(1 - p, 3)));
if (p < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
obs.disconnect();
}
});
obs.observe(el);
return () => obs.disconnect();
}, [value]);
return (
<span ref={ref}>
{n.toFixed(decimals)}
{suffix}
</span>
);
}
export function StatsCounter() {
const [stats, setStats] = useState<Stats>({ nodes: 0, edges: 0, types: 0, documents: 0 });
useEffect(() => {
Promise.all([
api.get('/graph/stats/').catch(() => ({ data: { nodes_count: 0, edges_count: 0, type_distribution: [] } })),
api.get('/documents/').catch(() => ({ data: { count: 0 } })),
]).then(([graphRes, docsRes]) => {
setStats({
nodes: graphRes.data.nodes_count || 0,
edges: graphRes.data.edges_count || 0,
types: (graphRes.data.type_distribution || []).length,
documents: docsRes.data.count || (docsRes.data.results || []).length || 0,
});
});
}, []);
const STAT_ITEMS = [
{ label: 'Graph Nodes', value: stats.nodes, suffix: '' },
{ label: 'Relationships', value: stats.edges, suffix: '' },
{ label: 'Entity Types', value: stats.types, suffix: '' },
{ label: 'Documents', value: stats.documents, suffix: '' },
];
if (stats.nodes === 0 && stats.documents === 0) return null;
return (
<section className="mx-auto max-w-6xl px-6 py-16">
<p className="text-center text-sm uppercase tracking-wide text-text-muted">
Knowledge Graph Statistics
</p>
<div className="mt-8 grid grid-cols-2 gap-6 lg:grid-cols-4">
{STAT_ITEMS.map((s) => (
<div key={s.label} className="text-center">
<p className="bg-gradient-to-r from-accent-violet to-accent-cyan bg-clip-text text-3xl font-bold text-transparent sm:text-4xl">
<Counter value={s.value} suffix={s.suffix} />
</p>
<p className="mt-1 text-sm text-text-secondary">{s.label}</p>
</div>
))}
</div>
</section>
);
}
|