Files
agent-bounty-protocol/apps/web/src/app/explorer/page.tsx
2026-06-09 12:59:04 +08:00

212 lines
10 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { Activity, ShieldAlert, Cpu, Network, Zap, CheckCircle2, AlertCircle } from "lucide-react";
import { motion } from "framer-motion";
export default function ExplorerPage() {
const [data, setData] = useState<any>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch("/api/explorer/stats");
const json = await res.json();
if (json.success) {
setData(json.data);
}
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};
fetchData();
const interval = setInterval(fetchData, 10000); // refresh every 10s
return () => clearInterval(interval);
}, []);
if (loading || !data) {
return (
<div className="min-h-screen bg-neutral-950 flex items-center justify-center">
<div className="flex flex-col items-center gap-4">
<div className="w-12 h-12 border-4 border-emerald-500/30 border-t-emerald-500 rounded-full animate-spin" />
<p className="text-emerald-500 font-mono tracking-widest animate-pulse">SYNCING WITH A2A NETWORK...</p>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-neutral-950 text-white font-sans selection:bg-emerald-500/30 p-4 md:p-8">
{/* Background Glow */}
<div className="fixed inset-0 overflow-hidden pointer-events-none -z-10">
<div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] bg-emerald-600/20 blur-[120px] rounded-full mix-blend-screen" />
<div className="absolute bottom-[-10%] right-[-10%] w-[40%] h-[40%] bg-blue-600/20 blur-[120px] rounded-full mix-blend-screen" />
</div>
<header className="max-w-7xl mx-auto mb-12">
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
className="flex items-center gap-4 mb-2"
>
<Network className="w-10 h-10 text-emerald-400" />
<h1 className="text-4xl md:text-5xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 to-cyan-400 tracking-tight">
VibeWork Explorer
</h1>
</motion.div>
<p className="text-neutral-400 font-mono">Live A2A Network Telemetry & Swarm Visualizer</p>
</header>
<main className="max-w-7xl mx-auto space-y-8">
{/* STATS ROW */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{[
{ label: "Total Value Locked", value: `$${data.stats.total_value_locked_usd.toLocaleString()}`, icon: Zap, color: "text-amber-400", bg: "bg-amber-400/10" },
{ label: "Active Agent Nodes", value: data.stats.active_agents, icon: Cpu, color: "text-emerald-400", bg: "bg-emerald-400/10" },
{ label: "Tasks in Swarm", value: data.stats.total_tasks, icon: Activity, color: "text-cyan-400", bg: "bg-cyan-400/10" },
].map((stat, i) => (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: i * 0.1 }}
key={stat.label}
className="relative overflow-hidden bg-white/5 backdrop-blur-xl border border-white/10 rounded-2xl p-6 group hover:bg-white/10 transition-colors"
>
<div className="flex items-start justify-between relative z-10">
<div>
<p className="text-neutral-400 font-mono text-sm uppercase tracking-wider mb-2">{stat.label}</p>
<p className="text-3xl font-semibold tracking-tight">{stat.value}</p>
</div>
<div className={`p-3 rounded-xl ${stat.bg} ${stat.color}`}>
<stat.icon className="w-6 h-6" />
</div>
</div>
</motion.div>
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* SWARM TASKS */}
<div className="lg:col-span-2 space-y-4">
<h2 className="text-2xl font-semibold flex items-center gap-2">
<Activity className="w-5 h-5 text-emerald-500" />
Live Swarm Activity
</h2>
<div className="bg-white/5 backdrop-blur-xl border border-white/10 rounded-2xl overflow-hidden">
<div className="p-6 flex flex-col gap-4">
{data.tasks.map((task: any, i: number) => (
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: i * 0.05 }}
key={task.id}
className={`p-4 rounded-xl border border-white/5 flex flex-col md:flex-row md:items-center justify-between gap-4 transition-all hover:border-emerald-500/30 ${task.parent_task_id ? 'ml-8 border-l-2 border-l-emerald-500/50 bg-black/20' : 'bg-white/5'}`}
>
<div>
<div className="flex items-center gap-2 mb-1">
<span className={`text-xs px-2 py-0.5 rounded-full font-mono ${task.difficulty === 'EPIC' ? 'bg-purple-500/20 text-purple-400 border border-purple-500/30' : 'bg-blue-500/20 text-blue-400'}`}>
{task.difficulty}
</span>
<span className="text-xs text-neutral-500 font-mono">{task.id.split('-')[0]}</span>
</div>
<h3 className="font-medium text-lg">{task.title}</h3>
{task.builder_id && (
<p className="text-sm text-neutral-400 mt-1 flex items-center gap-1">
<Cpu className="w-3 h-3" />
Assigned to: <span className="text-emerald-400 font-mono">{task.builder_id}</span>
</p>
)}
</div>
<div className="text-right">
<p className="text-emerald-400 font-mono font-medium">${(task.reward_amount / 100).toLocaleString()}</p>
<p className="text-xs text-neutral-500 mt-1">{task.status}</p>
</div>
</motion.div>
))}
{data.tasks.length === 0 && (
<p className="text-neutral-500 text-center py-8">No tasks in network</p>
)}
</div>
</div>
</div>
{/* ARBITRATION & AGENTS */}
<div className="space-y-8">
{/* ARBITRATION FEED */}
<div className="space-y-4">
<h2 className="text-2xl font-semibold flex items-center gap-2">
<ShieldAlert className="w-5 h-5 text-rose-500" />
Live Disputes
</h2>
<div className="bg-white/5 backdrop-blur-xl border border-white/10 rounded-2xl p-6">
<div className="flex flex-col gap-4">
{data.arbitrations.map((arb: any, i: number) => (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: i * 0.1 }}
key={arb.id}
className="p-4 rounded-xl bg-black/40 border border-rose-500/30 relative overflow-hidden group"
>
<div className="absolute top-0 right-0 p-2">
{arb.status === "RESOLVED" ? (
<CheckCircle2 className="w-4 h-4 text-emerald-500" />
) : (
<div className="w-2 h-2 rounded-full bg-rose-500 animate-pulse" />
)}
</div>
<p className="text-xs text-rose-400 font-mono mb-2">ARBITRATION #{arb.id.split('-')[0]}</p>
<div className="flex justify-between items-center text-sm mb-3">
<span className="text-neutral-300">Builder: <span className="font-mono text-xs">{arb.builder.agent_id.slice(0,8)}</span></span>
<span className="text-neutral-500">vs</span>
<span className="text-neutral-300">Evaluator: <span className="font-mono text-xs">{arb.evaluator.agent_id.slice(0,8)}</span></span>
</div>
<div className="flex gap-2 text-xs">
{arb.votes.map((v: any, vi: number) => (
<span key={vi} className={`px-2 py-1 rounded border ${v.vote_for === 'BUILDER' ? 'border-emerald-500/30 text-emerald-400' : 'border-blue-500/30 text-blue-400'}`}>
Vote: {v.vote_for}
</span>
))}
</div>
</motion.div>
))}
{data.arbitrations.length === 0 && (
<div className="text-center py-6">
<CheckCircle2 className="w-8 h-8 text-neutral-600 mx-auto mb-2" />
<p className="text-neutral-500">No active disputes.</p>
</div>
)}
</div>
</div>
</div>
{/* GLOBAL AGENT RADAR */}
<div className="space-y-4">
<h2 className="text-2xl font-semibold flex items-center gap-2">
<Cpu className="w-5 h-5 text-cyan-500" />
Global Agent Radar
</h2>
<div className="bg-white/5 backdrop-blur-xl border border-white/10 rounded-2xl p-6">
<div className="flex flex-wrap gap-2">
{data.agents.map((agent: any) => (
<div key={agent.agent_id} className="flex items-center gap-2 p-2 rounded-lg bg-black/30 border border-white/5 hover:border-cyan-500/50 transition-colors">
<div className={`w-2 h-2 rounded-full ${agent.status === 'WHITELISTED' ? 'bg-emerald-500' : 'bg-neutral-600'}`} />
<span className="font-mono text-sm">{agent.agent_id}</span>
</div>
))}
{data.agents.length === 0 && (
<p className="text-neutral-500 text-sm">Waiting for Discovery Sync...</p>
)}
</div>
</div>
</div>
</div>
</div>
</main>
</div>
);
}