rb125 commited on
Commit
d2e404a
·
1 Parent(s): 03c8703

added nextjs dashboard

Browse files
dashboard-next/app/globals.css ADDED
@@ -0,0 +1 @@
 
 
1
+ @import "tailwindcss";
dashboard-next/app/layout.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import "./globals.css";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "CGAE · Agent Economy",
6
+ description: "Comprehension-Gated Agent Economy — 0G Chain",
7
+ };
8
+
9
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
10
+ return (
11
+ <html lang="en">
12
+ <head>
13
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
14
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
15
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
16
+ </head>
17
+ <body className="antialiased min-h-screen" style={{ fontFamily: "'Inter', system-ui, sans-serif" }}>{children}</body>
18
+ </html>
19
+ );
20
+ }
dashboard-next/app/page.tsx ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import {
5
+ AreaChart, Area, BarChart, Bar, XAxis, YAxis, Tooltip,
6
+ ResponsiveContainer, CartesianGrid, Cell, PieChart, Pie,
7
+ } from "recharts";
8
+
9
+ const API = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
10
+ const POLL_MS = 2000;
11
+
12
+ const VIOLET = "#7c3aed";
13
+ const BLUE = "#2563eb";
14
+ const GREEN = "#059669";
15
+ const RED = "#dc2626";
16
+ const AMBER = "#d97706";
17
+ const TC: Record<number,string> = {0:"#94a3b8",1:"#6366f1",2:"#2563eb",3:"#7c3aed",4:"#d97706",5:"#dc2626"};
18
+
19
+ interface Economy { aggregate_safety:number; active_agents:number; total_balance:number; total_earned:number; contracts_completed:number; contracts_failed:number }
20
+ interface Agent { agent_id:string; model_name:string; strategy:string; current_tier:number; balance:number; total_earned:number; total_penalties:number; contracts_completed:number; contracts_failed:number; status:string; wallet_address?:string; robustness:{cc:number;er:number;as_:number;ih:number}|null }
21
+ interface Trade { round:number; agent:string; task_id:string; task_prompt:string; tier:string; domain:string; passed:boolean; reward:number; penalty:number; token_cost:number; latency_ms:number; output_preview:string; constraints_passed:string[]; constraints_failed:string[] }
22
+ interface Evt { timestamp:number; type:string; agent:string; message:string }
23
+
24
+ function usePoll<T>(url:string,ms:number):T|null{const[d,setD]=useState<T|null>(null);useEffect(()=>{let a=true;const p=()=>{fetch(url).then(r=>r.json()).then(v=>{if(a)setD(v)}).catch(()=>{})};p();const id=setInterval(p,ms);return()=>{a=false;clearInterval(id)}},[url,ms]);return d}
25
+
26
+ /* ---- Atoms ---- */
27
+ const Card=({children,className=""}:{children:React.ReactNode;className?:string})=>(
28
+ <div className={`bg-white/80 backdrop-blur-sm border border-white/60 rounded-2xl shadow-lg shadow-black/[0.03] ${className}`}>{children}</div>);
29
+
30
+ const Stat=({label,value,sub,pulse}:{label:string;value:string;sub?:string;pulse?:boolean})=>(
31
+ <Card className="p-5">
32
+ <div className="flex items-center justify-between mb-1"><p className="text-[11px] uppercase tracking-wider text-slate-400 font-semibold">{label}</p>{pulse&&<span className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse"/>}</div>
33
+ <p className="text-2xl font-extrabold text-slate-800">{value}</p>{sub&&<p className="text-[11px] text-slate-400 mt-0.5">{sub}</p>}
34
+ </Card>);
35
+
36
+ const TB=({t}:{t:number})=>{const c=TC[t]||"#94a3b8";return<span className="px-2 py-0.5 rounded-full text-[10px] font-bold" style={{background:c+"18",color:c,border:`1px solid ${c}35`}}>T{t}</span>};
37
+ const RB=({l,v}:{l:string;v:number})=>{const p=Math.round(v*100);const c=v>=.65?GREEN:v>=.4?AMBER:RED;return(<div className="flex items-center gap-1.5"><span className="w-5 text-[10px] text-slate-400 font-medium">{l}</span><div className="flex-1 h-1.5 bg-slate-100 rounded-full overflow-hidden"><div className="h-full rounded-full transition-all duration-500" style={{width:`${p}%`,backgroundColor:c}}/></div><span className="w-7 text-right text-[10px] text-slate-500 font-medium">{p}%</span></div>)};
38
+ const Addr=({id}:{id:string})=>(!id||id.length<8?<span className="text-slate-400 font-mono text-[10px]">{id}</span>:<span className="text-slate-400 font-mono text-[10px]">{id.slice(0,6)}…{id.slice(-4)}</span>);
39
+ const tt={backgroundColor:"#fff",border:"1px solid #e2e8f0",borderRadius:12,fontSize:11,color:"#1e293b",boxShadow:"0 4px 12px rgba(0,0,0,0.06)"};
40
+ const StatusDot=({s}:{s:string})=>{const c:Record<string,string>={idle:"#94a3b8",setup:AMBER,running:GREEN,done:BLUE,connecting:"#94a3b8"};return<span className="inline-flex items-center gap-1.5 text-xs text-slate-500 font-medium"><span className="h-2 w-2 rounded-full" style={{backgroundColor:c[s]||"#94a3b8"}}/>{s}</span>};
41
+
42
+ function EventFeed({events}:{events:Evt[]}){
43
+ if(!events.length) return null;
44
+ const last = events.slice(-6).reverse();
45
+ const styles:Record<string,{bg:string;border:string;icon:string;text:string}>={
46
+ BANKRUPTCY:{bg:"#fef2f2",border:"#fecaca",icon:"🚨",text:"text-red-700"},
47
+ DEMOTION:{bg:"#fffbeb",border:"#fde68a",icon:"⚠️",text:"text-amber-700"},
48
+ UPGRADE:{bg:"#ecfdf5",border:"#a7f3d0",icon:"🎉",text:"text-emerald-700"},
49
+ TEST_ETH_TOPUP:{bg:"#eef2ff",border:"#c7d2fe",icon:"💰",text:"text-violet-700"},
50
+ };
51
+ return(
52
+ <div className="space-y-1.5 mb-6">
53
+ <h3 className="text-[11px] font-bold text-slate-400 uppercase tracking-wider mb-2">Protocol Events</h3>
54
+ {last.map((e,i)=>{
55
+ const s=styles[e.type]||{bg:"#eff6ff",border:"#bfdbfe",icon:"📋",text:"text-blue-700"};
56
+ return(<div key={i} className="rounded-xl px-4 py-2.5 text-xs" style={{background:s.bg,border:`1px solid ${s.border}`}}>
57
+ <span className="mr-2">{s.icon}</span><span className={`font-bold mr-2 ${s.text}`}>{e.type}</span><span className="text-slate-600">{e.message}</span>
58
+ </div>);
59
+ })}
60
+ </div>);
61
+ }
62
+
63
+ function EconomyTab({eco,ts,events}:{eco:Economy|null;ts:any;events:Evt[]}){
64
+ if(!eco)return<div className="flex items-center justify-center h-64 text-slate-400 text-sm">Waiting for first round…</div>;
65
+ const sD=(ts?.safety||[]).map((v:number,i:number)=>({r:i+1,v}));
66
+ const bD=(ts?.balance||[]).map((v:number,i:number)=>({r:i+1,v}));
67
+ const fD=(ts?.rewards||[]).map((v:number,i:number)=>({r:i+1,rw:v,pn:ts?.penalties?.[i]||0}));
68
+ return(<div className="space-y-6">
69
+ <EventFeed events={events}/>
70
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
71
+ <Stat label="Safety S(P)" value={`${(eco.aggregate_safety*100).toFixed(1)}%`} pulse/>
72
+ <Stat label="Active Agents" value={String(eco.active_agents)}/>
73
+ <Stat label="Total Balance" value={`Ξ ${eco.total_balance.toFixed(4)}`} sub="ETH"/>
74
+ <Stat label="Total Earned" value={`Ξ ${eco.total_earned.toFixed(4)}`} sub="ETH"/>
75
+ <Stat label="Completed" value={String(eco.contracts_completed)} sub={`${eco.contracts_failed} failed`}/>
76
+ <Stat label="Chain" value="0G Galileo" sub="Testnet"/>
77
+ </div>
78
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-5">
79
+ <Card className="p-5"><h3 className="text-[11px] font-bold text-slate-400 uppercase tracking-wider mb-4">Aggregate Safety — Theorem 3</h3>
80
+ <ResponsiveContainer width="100%" height={200}><AreaChart data={sD}>
81
+ <defs><linearGradient id="gS" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor={VIOLET} stopOpacity={.2}/><stop offset="100%" stopColor={VIOLET} stopOpacity={0}/></linearGradient></defs>
82
+ <CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9"/><XAxis dataKey="r" tick={{fontSize:9,fill:"#94a3b8"}}/><YAxis domain={[0.3,1]} tick={{fontSize:9,fill:"#94a3b8"}}/><Tooltip contentStyle={tt}/>
83
+ <Area type="monotone" dataKey="v" stroke={VIOLET} fill="url(#gS)" strokeWidth={2.5} dot={false}/>
84
+ </AreaChart></ResponsiveContainer></Card>
85
+ <Card className="p-5"><h3 className="text-[11px] font-bold text-slate-400 uppercase tracking-wider mb-4">Circulating ETH</h3>
86
+ <ResponsiveContainer width="100%" height={200}><AreaChart data={bD}>
87
+ <defs><linearGradient id="gB" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor={BLUE} stopOpacity={.2}/><stop offset="100%" stopColor={BLUE} stopOpacity={0}/></linearGradient></defs>
88
+ <CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9"/><XAxis dataKey="r" tick={{fontSize:9,fill:"#94a3b8"}}/><YAxis tick={{fontSize:9,fill:"#94a3b8"}}/><Tooltip contentStyle={tt}/>
89
+ <Area type="monotone" dataKey="v" stroke={BLUE} fill="url(#gB)" strokeWidth={2.5} dot={false}/>
90
+ </AreaChart></ResponsiveContainer></Card>
91
+ <Card className="p-5"><h3 className="text-[11px] font-bold text-slate-400 uppercase tracking-wider mb-4">Rewards vs Penalties</h3>
92
+ <ResponsiveContainer width="100%" height={200}><AreaChart data={fD}>
93
+ <CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9"/><XAxis dataKey="r" tick={{fontSize:9,fill:"#94a3b8"}}/><YAxis tick={{fontSize:9,fill:"#94a3b8"}}/><Tooltip contentStyle={tt}/>
94
+ <Area type="monotone" dataKey="rw" stroke={GREEN} fill={GREEN} fillOpacity={.1} strokeWidth={2} dot={false} name="Reward"/>
95
+ <Area type="monotone" dataKey="pn" stroke={RED} fill={RED} fillOpacity={.1} strokeWidth={2} dot={false} name="Penalty"/>
96
+ </AreaChart></ResponsiveContainer></Card>
97
+ </div>
98
+ </div>);
99
+ }
100
+
101
+ function AgentsTab({agents}:{agents:Agent[]}){
102
+ const[sort,setSort]=useState<"earned"|"tier"|"balance">("earned");
103
+ if(!agents.length)return<div className="flex items-center justify-center h-64 text-slate-400">No agents yet…</div>;
104
+ const s=[...agents].sort((a,b)=>sort==="tier"?b.current_tier-a.current_tier||b.total_earned-a.total_earned:sort==="balance"?b.balance-a.balance:b.total_earned-a.total_earned);
105
+ const tierCounts:Record<number,number>={};agents.forEach(a=>{tierCounts[a.current_tier]=(tierCounts[a.current_tier]||0)+1});
106
+ const pieData=Object.entries(tierCounts).map(([t,c])=>({name:`T${t}`,value:c,fill:TC[Number(t)]||"#94a3b8"}));
107
+ const robData=agents.filter(a=>a.robustness).map(a=>({name:a.model_name.length>12?a.model_name.slice(0,12)+"…":a.model_name,CC:Math.round((a.robustness?.cc||0)*100),ER:Math.round((a.robustness?.er||0)*100),AS:Math.round((a.robustness?.as_||0)*100)}));
108
+ return(<div className="space-y-6">
109
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
110
+ <Card className="p-5"><h3 className="text-[11px] font-bold text-slate-400 uppercase tracking-wider mb-4">Tier Distribution</h3>
111
+ <ResponsiveContainer width="100%" height={200}><PieChart><Pie data={pieData} dataKey="value" nameKey="name" cx="50%" cy="50%" innerRadius={50} outerRadius={80} paddingAngle={3} label={({name,value})=>`${name}: ${value}`} labelLine={false} fontSize={11}>
112
+ {pieData.map((e,i)=><Cell key={i} fill={e.fill} stroke="none"/>)}</Pie><Tooltip contentStyle={tt}/></PieChart></ResponsiveContainer></Card>
113
+ <Card className="p-5"><h3 className="text-[11px] font-bold text-slate-400 uppercase tracking-wider mb-4">Robustness Profile</h3>
114
+ <ResponsiveContainer width="100%" height={200}><BarChart data={robData} barGap={1} barSize={8}>
115
+ <CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9"/><XAxis dataKey="name" tick={{fontSize:8,fill:"#94a3b8"}} interval={0} angle={-20} textAnchor="end" height={50}/><YAxis domain={[0,100]} tick={{fontSize:9,fill:"#94a3b8"}}/><Tooltip contentStyle={tt}/>
116
+ <Bar dataKey="CC" fill={VIOLET} radius={[2,2,0,0]}/><Bar dataKey="ER" fill={AMBER} radius={[2,2,0,0]}/><Bar dataKey="AS" fill={BLUE} radius={[2,2,0,0]}/>
117
+ </BarChart></ResponsiveContainer></Card>
118
+ </div>
119
+ <div className="flex items-center gap-2">
120
+ <span className="text-[10px] text-slate-400 uppercase tracking-wider font-semibold">Sort</span>
121
+ {(["earned","tier","balance"] as const).map(x=>(<button key={x} onClick={()=>setSort(x)} className={`text-[11px] px-3 py-1 rounded-full border font-semibold capitalize transition-all ${sort===x?"border-violet-300 text-violet-700 bg-violet-50 shadow-sm":"border-slate-200 text-slate-400 hover:text-slate-600 hover:border-slate-300"}`}>{x}</button>))}
122
+ </div>
123
+ <Card className="overflow-hidden">
124
+ <table className="w-full text-sm">
125
+ <thead><tr className="text-[10px] text-slate-400 uppercase tracking-wider border-b border-slate-100 bg-slate-50/60">
126
+ <th className="px-5 py-3 text-left">Agent</th><th className="px-3 py-3 text-left">Strategy</th><th className="px-3 py-3 text-center">Tier</th>
127
+ <th className="px-3 py-3 text-right">Balance</th><th className="px-3 py-3 text-right">Earned</th><th className="px-3 py-3 text-right">Penalties</th>
128
+ <th className="px-3 py-3 text-center">W/L</th><th className="px-3 py-3 text-left" style={{minWidth:200}}>Robustness</th><th className="px-3 py-3 text-center">⬤</th>
129
+ </tr></thead>
130
+ <tbody>{s.map(a=>(
131
+ <tr key={a.agent_id} className="border-b border-slate-50 hover:bg-violet-50/30 transition-colors">
132
+ <td className="px-5 py-3.5"><div className="font-bold text-slate-800">{a.model_name}</div><Addr id={a.wallet_address||a.agent_id}/></td>
133
+ <td className="px-3 py-3.5 text-slate-500 capitalize text-xs font-medium">{a.strategy}</td>
134
+ <td className="px-3 py-3.5 text-center"><TB t={a.current_tier}/></td>
135
+ <td className="px-3 py-3.5 text-right font-mono text-xs text-slate-700">Ξ {a.balance.toFixed(4)}</td>
136
+ <td className="px-3 py-3.5 text-right font-mono text-xs font-bold text-emerald-600">Ξ {a.total_earned.toFixed(4)}</td>
137
+ <td className="px-3 py-3.5 text-right font-mono text-xs text-red-500">{a.total_penalties.toFixed(4)}</td>
138
+ <td className="px-3 py-3.5 text-center text-xs"><span className="text-emerald-600 font-bold">{a.contracts_completed}</span><span className="text-slate-300 mx-0.5">/</span><span className="text-red-500">{a.contracts_failed}</span></td>
139
+ <td className="px-3 py-3.5">{a.robustness?<div className="space-y-0.5"><RB l="CC" v={a.robustness.cc}/><RB l="ER" v={a.robustness.er}/><RB l="AS" v={a.robustness.as_}/><RB l="IH" v={a.robustness.ih}/></div>:<span className="text-slate-300">—</span>}</td>
140
+ <td className="px-3 py-3.5 text-center"><span className={`inline-block w-2.5 h-2.5 rounded-full ${a.status==="active"?"bg-emerald-500":"bg-slate-300"}`}/></td>
141
+ </tr>))}</tbody>
142
+ </table>
143
+ </Card>
144
+ </div>);
145
+ }
146
+
147
+ function TradesTab({trades}:{trades:Trade[]}){
148
+ const[exp,setExp]=useState<Set<number>>(new Set());
149
+ if(!trades.length)return<div className="flex items-center justify-center h-64 text-slate-400">No trades yet…</div>;
150
+ const sorted=[...trades].reverse();
151
+ const tog=(i:number)=>{setExp(p=>{const n=new Set(p);n.has(i)?n.delete(i):n.add(i);return n})};
152
+ const passed=trades.filter(t=>t.passed).length;
153
+ return(<div className="space-y-4">
154
+ <div className="grid grid-cols-3 gap-4">
155
+ <Stat label="Total Trades" value={String(trades.length)}/>
156
+ <Stat label="Passed" value={String(passed)} sub={`${trades.length?(passed/trades.length*100).toFixed(0):"0"}% pass rate`}/>
157
+ <Stat label="Failed" value={String(trades.length-passed)}/>
158
+ </div>
159
+ <div className="space-y-2">
160
+ {sorted.map((t,i)=>{const o=exp.has(i);const tn=parseInt(t.tier.replace("T",""))||0;return(
161
+ <Card key={i} className={`overflow-hidden transition-all ${o?"ring-2 ring-violet-200":""}`}>
162
+ <button onClick={()=>tog(i)} className="w-full flex items-center gap-3 px-5 py-3.5 text-left hover:bg-violet-50/30 transition-colors">
163
+ <span className="text-[10px] text-slate-400 font-mono w-8">R{t.round+1}</span>
164
+ <span className={`text-[10px] font-extrabold w-10 ${t.passed?"text-emerald-600":"text-red-500"}`}>{t.passed?"PASS":"FAIL"}</span>
165
+ <span className="text-xs font-bold w-44 truncate text-slate-800">{t.agent}</span>
166
+ <TB t={tn}/>
167
+ <span className="text-[10px] text-slate-400 w-20 capitalize font-medium">{t.domain}</span>
168
+ <span className="text-[11px] text-slate-500 flex-1 truncate">{t.task_id}</span>
169
+ <span className="text-xs font-mono font-bold w-28 text-right" style={{color:t.passed?GREEN:RED}}>
170
+ {t.passed?`+Ξ ${t.reward.toFixed(4)}`:`-Ξ ${t.penalty.toFixed(4)}`}
171
+ </span>
172
+ <svg className={`w-4 h-4 text-slate-400 transition-transform ${o?"rotate-180":""}`} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7"/></svg>
173
+ </button>
174
+ {o&&(
175
+ <div className="border-t border-slate-100 px-5 py-4 bg-slate-50/40 space-y-4">
176
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-xs">
177
+ <div><p className="text-[10px] text-slate-400 font-semibold mb-0.5">Task</p><p className="text-slate-700">{t.task_id}</p></div>
178
+ <div><p className="text-[10px] text-slate-400 font-semibold mb-0.5">Domain / Tier</p><p className="text-slate-700 capitalize">{t.domain} · <TB t={tn}/></p></div>
179
+ <div><p className="text-[10px] text-slate-400 font-semibold mb-0.5">Token Cost</p><p className="font-mono text-slate-700">Ξ {t.token_cost.toFixed(6)}</p></div>
180
+ <div><p className="text-[10px] text-slate-400 font-semibold mb-0.5">Latency</p><p className="text-slate-700">{t.latency_ms.toFixed(0)} ms</p></div>
181
+ </div>
182
+ {(t.constraints_passed.length>0||t.constraints_failed.length>0)&&(
183
+ <div><p className="text-[10px] text-slate-400 font-semibold mb-1.5">Constraints</p>
184
+ <div className="flex flex-wrap gap-1.5">
185
+ {t.constraints_passed.map((c,j)=><span key={`p${j}`} className="px-2 py-0.5 rounded-full text-[10px] font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200">✓ {c}</span>)}
186
+ {t.constraints_failed.map((c,j)=><span key={`f${j}`} className="px-2 py-0.5 rounded-full text-[10px] font-semibold bg-red-50 text-red-600 border border-red-200">✗ {c}</span>)}
187
+ </div></div>)}
188
+ {t.task_prompt&&<div><p className="text-[10px] text-slate-400 font-semibold mb-1.5">Task Prompt</p><pre className="text-[11px] text-slate-600 bg-white rounded-xl p-3.5 overflow-x-auto max-h-48 whitespace-pre-wrap border border-slate-200 shadow-inner">{t.task_prompt}</pre></div>}
189
+ <div><p className="text-[10px] text-slate-400 font-semibold mb-1.5">Agent Output</p><pre className="text-[11px] text-slate-500 bg-white rounded-xl p-3.5 overflow-x-auto max-h-40 whitespace-pre-wrap border border-slate-200 shadow-inner">{t.output_preview}</pre></div>
190
+ </div>)}
191
+ </Card>);})}
192
+ </div>
193
+ </div>);
194
+ }
195
+
196
+ function OnChainTab(){
197
+ const contracts = usePoll<any>(`${API}/api/contracts`, 10000);
198
+ const explorer = contracts?.explorer || "https://chainscan-galileo.0g.ai";
199
+ const registry = contracts?.contracts?.CGAERegistry?.address || "";
200
+ const escrow = contracts?.contracts?.CGAEEscrow?.address || "";
201
+ return(<div className="space-y-5">
202
+ <Card className="p-6"><h3 className="text-sm font-bold text-slate-800 mb-4">0G Chain — Galileo Testnet</h3>
203
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-5 text-sm">
204
+ <div><p className="text-[10px] text-slate-400 font-semibold mb-1">CGAERegistry</p><code className="text-xs text-violet-600 break-all font-semibold">{registry||"Not deployed"}</code></div>
205
+ <div><p className="text-[10px] text-slate-400 font-semibold mb-1">CGAEEscrow</p><code className="text-xs text-violet-600 break-all font-semibold">{escrow||"Not deployed"}</code></div>
206
+ <div><p className="text-[10px] text-slate-400 font-semibold mb-1">Chain ID</p><p className="text-slate-700">16602</p></div>
207
+ <div><p className="text-[10px] text-slate-400 font-semibold mb-1">Architecture</p><p className="text-slate-500 text-xs">Agent wallets · ETH escrow · Weakest-link gate on-chain · Budget ceiling enforcement</p></div>
208
+ </div>
209
+ {registry&&<div className="mt-5"><a href={`${explorer}/address/${registry}`} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-2 text-xs px-4 py-2.5 rounded-xl font-bold text-white bg-gradient-to-r from-violet-600 to-blue-600 hover:from-violet-700 hover:to-blue-700 shadow-md shadow-violet-200 transition-all">View on 0G Explorer ↗</a></div>}
210
+ </Card>
211
+ <Card className="p-6"><h3 className="text-sm font-bold text-slate-800 mb-3">Verification Flow</h3>
212
+ <div className="flex items-center gap-2 text-xs text-slate-500 flex-wrap">
213
+ {["audit_live()","→","[CC, ER, AS, IH]","→","0G Storage","→","Merkle root hash","→","CGAERegistry.certify()","→","Tier assigned"].map((step,i)=>(
214
+ <span key={i} className={i%2===0?"text-slate-700 font-semibold bg-slate-100 px-2 py-0.5 rounded-md":"text-violet-400 font-bold"}>{step}</span>))}
215
+ </div>
216
+ <p className="text-xs text-slate-400 mt-3">Anyone can fetch the root hash from the registry, download from 0G Storage, verify the Merkle proof, and confirm scores match.</p>
217
+ </Card>
218
+ </div>);
219
+ }
220
+
221
+ export default function Dashboard(){
222
+ const[tab,setTab]=useState<"economy"|"agents"|"trades"|"onchain">("economy");
223
+ const st=usePoll<{status:string;round:number;total_rounds:number;economy:Economy|null}>(`${API}/api/state`,POLL_MS);
224
+ const ag=usePoll<{agents:Agent[]}>(`${API}/api/agents`,POLL_MS);
225
+ const tr=usePoll<{trades:Trade[]}>(`${API}/api/trades?limit=200`,POLL_MS);
226
+ const ts=usePoll<any>(`${API}/api/timeseries`,POLL_MS);
227
+ const ev=usePoll<{events:Evt[]}>(`${API}/api/events?limit=200`,POLL_MS);
228
+ const status=st?.status||"connecting";
229
+ const round=st?.round||0;
230
+ const totalR=st?.total_rounds||0;
231
+ const tabs=[{id:"economy" as const,l:"📈 Economy"},{id:"agents" as const,l:`🛡️ Agents${ag?.agents?` (${ag.agents.length})`:""}`},{id:"trades" as const,l:`⚡ Trades${tr?.trades?` (${tr.trades.length})`:""}`},{id:"onchain" as const,l:"🔗 On-Chain"}];
232
+
233
+ return(<div className="min-h-screen" style={{background:"linear-gradient(135deg, #ede9fe 0%, #e0f2fe 30%, #faf5ff 60%, #fce7f3 100%)"}}>
234
+ <header className="bg-white/70 backdrop-blur-md border-b border-white/50 px-6 py-3.5 sticky top-0 z-50 shadow-sm">
235
+ <div className="max-w-7xl mx-auto flex items-center justify-between">
236
+ <div className="flex items-center gap-5">
237
+ <div className="flex items-center gap-2.5">
238
+ <div className="w-8 h-8 rounded-xl bg-gradient-to-br from-violet-600 to-blue-600 flex items-center justify-center text-white text-sm font-black shadow-lg shadow-violet-200">◆</div>
239
+ <div><h1 className="text-sm font-extrabold text-slate-800">CGAE</h1><p className="text-[10px] text-slate-400 font-medium -mt-0.5">Agent Economy</p></div>
240
+ </div>
241
+ <div className="hidden sm:flex items-center gap-3 text-xs border-l border-slate-200 pl-5">
242
+ <StatusDot s={status}/>
243
+ {round>0&&<span className="text-slate-500">Round <span className="font-bold text-slate-800">{round}</span>{totalR>0?`/${totalR}`:""}</span>}
244
+ </div>
245
+ </div>
246
+ <a href="https://chainscan-galileo.0g.ai" target="_blank" rel="noopener noreferrer" className="text-[11px] px-4 py-2 rounded-xl font-bold text-white bg-gradient-to-r from-violet-600 to-blue-600 hover:from-violet-700 hover:to-blue-700 shadow-md shadow-violet-200 transition-all">0G Explorer ↗</a>
247
+ </div>
248
+ </header>
249
+ <nav className="bg-white/50 backdrop-blur-md border-b border-white/40 px-6 sticky top-[57px] z-40">
250
+ <div className="max-w-7xl mx-auto flex">
251
+ {tabs.map(t=>(<button key={t.id} onClick={()=>setTab(t.id)} className={`px-5 py-3 text-xs font-bold border-b-2 transition-all ${tab===t.id?"border-violet-500 text-violet-700":"border-transparent text-slate-400 hover:text-slate-600"}`}>{t.l}</button>))}
252
+ </div>
253
+ </nav>
254
+ <main className="max-w-7xl mx-auto px-6 py-6">
255
+ {tab==="economy"&&<EconomyTab eco={st?.economy||null} ts={ts} events={ev?.events||[]}/>}
256
+ {tab==="agents"&&<AgentsTab agents={ag?.agents||[]}/>}
257
+ {tab==="trades"&&<TradesTab trades={tr?.trades||[]}/>}
258
+ {tab==="onchain"&&<OnChainTab/>}
259
+ </main>
260
+ <footer className="text-center text-[10px] text-slate-400 font-medium py-5">CGAE · Comprehension-Gated Agent Economy · ETH OpenAgents 2026</footer>
261
+ </div>);
262
+ }
dashboard-next/eslint.config.mjs ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
dashboard-next/next-env.d.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ import "./.next/dev/types/routes.d.ts";
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
dashboard-next/next.config.ts ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ async rewrites() {
5
+ return [
6
+ {
7
+ source: "/api/:path*",
8
+ destination: "http://localhost:8000/api/:path*",
9
+ },
10
+ ];
11
+ },
12
+ };
13
+
14
+ export default nextConfig;
dashboard-next/package.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "cgae-dashboard",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev --port 3000",
7
+ "build": "next build",
8
+ "start": "next start"
9
+ },
10
+ "dependencies": {
11
+ "next": "16.2.4",
12
+ "react": "19.2.4",
13
+ "react-dom": "19.2.4",
14
+ "recharts": "^3.8.1"
15
+ },
16
+ "devDependencies": {
17
+ "@tailwindcss/postcss": "^4",
18
+ "@types/node": "^20",
19
+ "@types/react": "^19",
20
+ "@types/react-dom": "^19",
21
+ "eslint": "^9",
22
+ "eslint-config-next": "16.2.4",
23
+ "tailwindcss": "^4",
24
+ "typescript": "^5"
25
+ }
26
+ }
dashboard-next/postcss.config.mjs ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
dashboard-next/tsconfig.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
33
+ "exclude": ["node_modules"]
34
+ }
server/api.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CGAE Live Economy Server — ETH / 0G Chain
3
+
4
+ Runs the LiveSimulationRunner in a background thread and exposes
5
+ real-time state via REST endpoints for the dashboard.
6
+
7
+ Usage:
8
+ python -m server.api # default 20 rounds
9
+ python -m server.api --rounds 50
10
+ python -m server.api --rounds -1 # infinite
11
+ """
12
+
13
+ import argparse
14
+ import json
15
+ import logging
16
+ import threading
17
+ from pathlib import Path
18
+
19
+ from fastapi import FastAPI
20
+ from fastapi.middleware.cors import CORSMiddleware
21
+
22
+ app = FastAPI(title="CGAE Live Economy")
23
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
24
+
25
+ logger = logging.getLogger("cgae.api")
26
+
27
+ _state: dict = {
28
+ "status": "idle",
29
+ "round": 0,
30
+ "total_rounds": 0,
31
+ "economy": None,
32
+ "agents": {},
33
+ "trades": [],
34
+ "events": [],
35
+ "time_series": {"safety": [], "balance": [], "rewards": [], "penalties": []},
36
+ }
37
+ _state_lock = threading.Lock()
38
+ MAX_TRADES = 500
39
+
40
+ DEPLOYED = Path(__file__).resolve().parents[1] / "contracts" / "deployed.json"
41
+
42
+
43
+ def _run_economy(num_rounds: int, initial_balance: float):
44
+ import sys, os
45
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
46
+ os.environ.setdefault("PYTHONDONTWRITEBYTECODE", "1")
47
+
48
+ from dotenv import load_dotenv
49
+ load_dotenv(Path(__file__).resolve().parents[1] / ".env", override=True)
50
+
51
+ from server.live_runner import LiveSimulationRunner, LiveSimConfig
52
+
53
+ config = LiveSimConfig(
54
+ num_rounds=num_rounds,
55
+ initial_balance=initial_balance,
56
+ run_live_audit=False,
57
+ self_verify=True,
58
+ max_retries=1,
59
+ test_eth_top_up_threshold=0.05,
60
+ test_eth_top_up_amount=0.3,
61
+ )
62
+
63
+ runner = LiveSimulationRunner(config)
64
+
65
+ with _state_lock:
66
+ _state["status"] = "setup"
67
+ _state["total_rounds"] = num_rounds
68
+
69
+ runner.setup()
70
+
71
+ with _state_lock:
72
+ _state["status"] = "running"
73
+
74
+ round_num = 0
75
+ infinite = num_rounds == -1
76
+
77
+ try:
78
+ while infinite or round_num < num_rounds:
79
+ runner._reactivate_suspended_agents()
80
+ round_results = runner._run_round(round_num)
81
+ runner._round_summaries.append(round_results)
82
+ step_events = runner.economy.step()
83
+
84
+ safety = runner.economy.aggregate_safety()
85
+ agents_snapshot = {}
86
+ for aid, mname in runner.agent_model_map.items():
87
+ rec = runner.economy.registry.get_agent(aid)
88
+ if not rec:
89
+ continue
90
+ r = rec.current_robustness
91
+ agents_snapshot[aid] = {
92
+ "agent_id": aid,
93
+ "model_name": mname,
94
+ "strategy": _get_strategy(runner, mname),
95
+ "current_tier": rec.current_tier.value,
96
+ "balance": rec.balance,
97
+ "total_earned": rec.total_earned,
98
+ "total_penalties": rec.total_penalties,
99
+ "contracts_completed": rec.contracts_completed,
100
+ "contracts_failed": rec.contracts_failed,
101
+ "status": rec.status.value,
102
+ "wallet_address": rec.wallet_address,
103
+ "robustness": {
104
+ "cc": r.cc, "er": r.er, "as_": r.as_, "ih": r.ih,
105
+ } if r else None,
106
+ }
107
+
108
+ trades = []
109
+ for tr in round_results.get("task_results", []):
110
+ trades.append({
111
+ "round": round_num,
112
+ "agent": tr["agent"],
113
+ "task_id": tr["task_id"],
114
+ "task_prompt": tr.get("task_prompt", ""),
115
+ "tier": tr["tier"],
116
+ "domain": tr["domain"],
117
+ "passed": tr["verification"]["overall_pass"],
118
+ "reward": tr["settlement"].get("reward", 0) if tr["settlement"] else 0,
119
+ "penalty": tr["settlement"].get("penalty", 0) if tr["settlement"] else 0,
120
+ "token_cost": tr.get("token_cost_eth", 0),
121
+ "latency_ms": tr.get("latency_ms", 0),
122
+ "output_preview": tr.get("output_preview", ""),
123
+ "constraints_passed": tr["verification"].get("constraints_passed", []),
124
+ "constraints_failed": tr["verification"].get("constraints_failed", []),
125
+ })
126
+
127
+ # Capture protocol events from step
128
+ for aid in step_events.get("agents_demoted", []):
129
+ mname = runner.agent_model_map.get(aid, aid)
130
+ with _state_lock:
131
+ _state["events"].append({"timestamp": runner.economy.current_time, "type": "DEMOTION", "agent": mname, "message": f"{mname} demoted after spot-audit failure"})
132
+
133
+ with _state_lock:
134
+ _state["round"] = round_num + 1
135
+ _state["economy"] = {
136
+ "aggregate_safety": safety,
137
+ "active_agents": len(runner.economy.registry.active_agents),
138
+ "total_balance": sum(a["balance"] for a in agents_snapshot.values()),
139
+ "total_earned": sum(a["total_earned"] for a in agents_snapshot.values()),
140
+ "contracts_completed": sum(a["contracts_completed"] for a in agents_snapshot.values()),
141
+ "contracts_failed": sum(a["contracts_failed"] for a in agents_snapshot.values()),
142
+ }
143
+ _state["agents"] = agents_snapshot
144
+ _state["trades"] = (_state["trades"] + trades)[-MAX_TRADES:]
145
+ _state["time_series"]["safety"].append(safety)
146
+ _state["time_series"]["balance"].append(_state["economy"]["total_balance"])
147
+ _state["time_series"]["rewards"].append(round_results.get("total_reward", 0))
148
+ _state["time_series"]["penalties"].append(round_results.get("total_penalty", 0))
149
+
150
+ round_num += 1
151
+
152
+ except Exception as e:
153
+ logger.exception(f"Economy runner failed: {e}")
154
+ finally:
155
+ with _state_lock:
156
+ _state["status"] = "done"
157
+
158
+
159
+ def _get_strategy(runner, model_name: str) -> str:
160
+ auto = runner.autonomous_agents.get(model_name)
161
+ if auto is None:
162
+ return "unknown"
163
+ return type(auto.strategy).__name__.replace("Strategy", "").lower()
164
+
165
+
166
+ @app.get("/api/state")
167
+ def get_state():
168
+ with _state_lock:
169
+ return {"status": _state["status"], "round": _state["round"], "total_rounds": _state["total_rounds"], "economy": _state["economy"]}
170
+
171
+
172
+ @app.get("/api/agents")
173
+ def get_agents():
174
+ with _state_lock:
175
+ return {"agents": list(_state["agents"].values())}
176
+
177
+
178
+ @app.get("/api/trades")
179
+ def get_trades(limit: int = 100):
180
+ with _state_lock:
181
+ return {"trades": _state["trades"][-limit:]}
182
+
183
+
184
+ @app.get("/api/events")
185
+ def get_events(limit: int = 100):
186
+ with _state_lock:
187
+ return {"events": _state["events"][-limit:]}
188
+
189
+
190
+ @app.get("/api/timeseries")
191
+ def get_timeseries():
192
+ with _state_lock:
193
+ return _state["time_series"]
194
+
195
+
196
+ @app.get("/api/contracts")
197
+ def get_contracts():
198
+ if DEPLOYED.exists():
199
+ return json.loads(DEPLOYED.read_text())
200
+ return {}
201
+
202
+
203
+ _runner_thread = None
204
+
205
+
206
+ def start_economy(rounds: int = 20, balance: float = 0.5):
207
+ global _runner_thread
208
+ if _runner_thread and _runner_thread.is_alive():
209
+ return
210
+ _runner_thread = threading.Thread(target=_run_economy, args=(rounds, balance), daemon=True)
211
+ _runner_thread.start()
212
+
213
+
214
+ @app.on_event("startup")
215
+ async def on_startup():
216
+ import sys
217
+ rounds = 20
218
+ for i, arg in enumerate(sys.argv):
219
+ if arg == "--rounds" and i + 1 < len(sys.argv):
220
+ rounds = int(sys.argv[i + 1])
221
+ start_economy(rounds=rounds)
222
+
223
+
224
+ if __name__ == "__main__":
225
+ import uvicorn
226
+ parser = argparse.ArgumentParser()
227
+ parser.add_argument("--rounds", type=int, default=20)
228
+ parser.add_argument("--port", type=int, default=8000)
229
+ args = parser.parse_args()
230
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s")
231
+ uvicorn.run(app, host="0.0.0.0", port=args.port)