nice-bill commited on
Commit
03cd558
·
1 Parent(s): 27baee9

Add React dashboard frontend

Browse files
.gitignore CHANGED
@@ -7,3 +7,7 @@ dist/
7
  build/
8
  .vscode/
9
  .idea/
 
 
 
 
 
7
  build/
8
  .vscode/
9
  .idea/
10
+
11
+ # Frontend
12
+ frontend/node_modules/
13
+ frontend/dist/
core/__init__.py CHANGED
@@ -3,5 +3,6 @@
3
  from .agent import Agent
4
  from .defi_mechanics import Pool
5
  from .simulation import Simulation
 
6
 
7
- __all__ = ["Agent", "Pool", "Simulation"]
 
3
  from .agent import Agent
4
  from .defi_mechanics import Pool
5
  from .simulation import Simulation
6
+ from .analyzer import Analyzer
7
 
8
+ __all__ = ["Agent", "Pool", "Simulation", "Analyzer"]
frontend/index.html ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>DeFi Agent Arena</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.jsx"></script>
11
+ </body>
12
+ </html>
frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
frontend/package.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "defi-agents-frontend",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "^18.2.0",
13
+ "react-dom": "^18.2.0",
14
+ "recharts": "^2.10.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/react": "^18.2.43",
18
+ "@types/react-dom": "^18.2.17",
19
+ "@vitejs/plugin-react": "^4.2.1",
20
+ "autoprefixer": "^10.4.16",
21
+ "postcss": "^8.4.32",
22
+ "tailwindcss": "^3.4.0",
23
+ "vite": "^5.0.8"
24
+ }
25
+ }
frontend/postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
frontend/src/App.jsx ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react'
2
+ import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar } from 'recharts'
3
+
4
+ const API_BASE = '/api'
5
+
6
+ function App() {
7
+ const [runs, setRuns] = useState([])
8
+ const [selectedRun, setSelectedRun] = useState(null)
9
+ const [trends, setTrends] = useState(null)
10
+ const [loading, setLoading] = useState(false)
11
+ const [newRunConfig, setNewRunConfig] = useState({ num_agents: 5, turns_per_run: 10 })
12
+
13
+ useEffect(() => {
14
+ fetchRuns()
15
+ fetchTrends()
16
+ }, [])
17
+
18
+ const fetchRuns = async () => {
19
+ try {
20
+ const res = await fetch(`${API_BASE}/runs`)
21
+ if (res.ok) {
22
+ const data = await res.json()
23
+ setRuns(data.runs || [])
24
+ }
25
+ } catch (e) {
26
+ console.error('Failed to fetch runs:', e)
27
+ }
28
+ }
29
+
30
+ const fetchTrends = async () => {
31
+ try {
32
+ const res = await fetch(`${API_BASE}/analysis/trends`)
33
+ if (res.ok) {
34
+ const data = await res.json()
35
+ setTrends(data)
36
+ }
37
+ } catch (e) {
38
+ console.error('Failed to fetch trends:', e)
39
+ }
40
+ }
41
+
42
+ const startRun = async () => {
43
+ setLoading(true)
44
+ try {
45
+ const res = await fetch(`${API_BASE}/runs`, {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify(newRunConfig)
49
+ })
50
+ if (res.ok) {
51
+ await fetchRuns()
52
+ await fetchTrends()
53
+ }
54
+ } catch (e) {
55
+ console.error('Failed to start run:', e)
56
+ }
57
+ setLoading(false)
58
+ }
59
+
60
+ const selectRun = async (runId) => {
61
+ try {
62
+ const res = await fetch(`${API_BASE}/runs/${runId}`)
63
+ if (res.ok) {
64
+ const data = await res.json()
65
+ setSelectedRun(data)
66
+ }
67
+ } catch (e) {
68
+ console.error('Failed to fetch run details:', e)
69
+ }
70
+ }
71
+
72
+ return (
73
+ <div className="min-h-screen bg-slate-50">
74
+ {/* Header */}
75
+ <header className="bg-white border-b border-slate-200 px-6 py-4">
76
+ <div className="max-w-6xl mx-auto flex justify-between items-center">
77
+ <h1 className="text-xl font-semibold text-slate-800">DeFi Agent Arena</h1>
78
+ <div className="flex gap-4 text-sm text-slate-600">
79
+ <span>Backend: <span className={trends ? 'text-green-600' : 'text-red-600'}>{trends ? 'Connected' : 'Disconnected'}</span></span>
80
+ </div>
81
+ </div>
82
+ </header>
83
+
84
+ <main className="max-w-6xl mx-auto px-6 py-8">
85
+ {/* New Run Section */}
86
+ <section className="bg-white rounded-lg border border-slate-200 p-6 mb-8">
87
+ <h2 className="text-lg font-medium text-slate-800 mb-4">Start New Run</h2>
88
+ <div className="flex gap-4 items-end">
89
+ <div>
90
+ <label className="block text-sm text-slate-600 mb-1">Agents</label>
91
+ <input
92
+ type="number"
93
+ value={newRunConfig.num_agents}
94
+ onChange={(e) => setNewRunConfig({ ...newRunConfig, num_agents: parseInt(e.target.value) })}
95
+ className="border border-slate-300 rounded px-3 py-2 w-24 text-slate-800"
96
+ />
97
+ </div>
98
+ <div>
99
+ <label className="block text-sm text-slate-600 mb-1">Turns</label>
100
+ <input
101
+ type="number"
102
+ value={newRunConfig.turns_per_run}
103
+ onChange={(e) => setNewRunConfig({ ...newRunConfig, turns_per_run: parseInt(e.target.value) })}
104
+ className="border border-slate-300 rounded px-3 py-2 w-24 text-slate-800"
105
+ />
106
+ </div>
107
+ <button
108
+ onClick={startRun}
109
+ disabled={loading}
110
+ className="bg-slate-800 text-white px-6 py-2 rounded hover:bg-slate-700 disabled:opacity-50"
111
+ >
112
+ {loading ? 'Running...' : 'Start Run'}
113
+ </button>
114
+ </div>
115
+ </section>
116
+
117
+ {/* Trends Section */}
118
+ {trends && (
119
+ <section className="bg-white rounded-lg border border-slate-200 p-6 mb-8">
120
+ <h2 className="text-lg font-medium text-slate-800 mb-4">Trends</h2>
121
+ <div className="grid grid-cols-4 gap-4">
122
+ <div className="bg-slate-50 rounded p-4">
123
+ <div className="text-sm text-slate-600">Total Runs</div>
124
+ <div className="text-2xl font-semibold text-slate-800">{trends.run_count}</div>
125
+ </div>
126
+ <div className="bg-slate-50 rounded p-4">
127
+ <div className="text-sm text-slate-600">Avg Profit</div>
128
+ <div className="text-2xl font-semibold text-slate-800">{trends.avg_profit?.toFixed(2) || 0}</div>
129
+ </div>
130
+ <div className="bg-slate-50 rounded p-4">
131
+ <div className="text-sm text-slate-600">Avg Inequality</div>
132
+ <div className="text-2xl font-semibold text-slate-800">{(trends.avg_gini * 100).toFixed(1)}%</div>
133
+ </div>
134
+ <div className="bg-slate-50 rounded p-4">
135
+ <div className="text-sm text-slate-600">Profit Trend</div>
136
+ <div className="text-2xl font-semibold text-slate-800 capitalize">{trends.profit_trend}</div>
137
+ </div>
138
+ </div>
139
+ </section>
140
+ )}
141
+
142
+ {/* Runs List */}
143
+ <section className="bg-white rounded-lg border border-slate-200 p-6 mb-8">
144
+ <h2 className="text-lg font-medium text-slate-800 mb-4">Runs</h2>
145
+ {runs.length === 0 ? (
146
+ <p className="text-slate-500">No runs yet. Start one above!</p>
147
+ ) : (
148
+ <div className="overflow-x-auto">
149
+ <table className="w-full">
150
+ <thead>
151
+ <tr className="border-b border-slate-200 text-left">
152
+ <th className="py-2 px-3 text-sm font-medium text-slate-600">Run</th>
153
+ <th className="py-2 px-3 text-sm font-medium text-slate-600">Status</th>
154
+ <th className="py-2 px-3 text-sm font-medium text-slate-600">Started</th>
155
+ <th className="py-2 px-3 text-sm font-medium text-slate-600">Action</th>
156
+ </tr>
157
+ </thead>
158
+ <tbody>
159
+ {runs.map((run) => (
160
+ <tr key={run.id} className="border-b border-slate-100">
161
+ <td className="py-2 px-3 text-slate-800">#{run.run_number}</td>
162
+ <td className="py-2 px-3">
163
+ <span className={`px-2 py-1 rounded text-xs ${
164
+ run.status === 'completed' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'
165
+ }`}>
166
+ {run.status}
167
+ </span>
168
+ </td>
169
+ <td className="py-2 px-3 text-slate-600 text-sm">
170
+ {new Date(run.start_time).toLocaleString()}
171
+ </td>
172
+ <td className="py-2 px-3">
173
+ <button
174
+ onClick={() => selectRun(run.id)}
175
+ className="text-blue-600 hover:text-blue-800 text-sm"
176
+ >
177
+ View
178
+ </button>
179
+ </td>
180
+ </tr>
181
+ ))}
182
+ </tbody>
183
+ </table>
184
+ </div>
185
+ )}
186
+ </section>
187
+
188
+ {/* Run Detail */}
189
+ {selectedRun && (
190
+ <section className="bg-white rounded-lg border border-slate-200 p-6">
191
+ <div className="flex justify-between items-center mb-4">
192
+ <h2 className="text-lg font-medium text-slate-800">Run Details</h2>
193
+ <button onClick={() => setSelectedRun(null)} className="text-slate-500 hover:text-slate-700">
194
+ Close
195
+ </button>
196
+ </div>
197
+
198
+ {/* Metrics */}
199
+ {selectedRun.metrics && (
200
+ <div className="grid grid-cols-3 gap-4 mb-6">
201
+ <div className="bg-slate-50 rounded p-4">
202
+ <div className="text-sm text-slate-600">Gini Coefficient</div>
203
+ <div className="text-xl font-semibold text-slate-800">{selectedRun.metrics.gini_coefficient?.toFixed(4)}</div>
204
+ </div>
205
+ <div className="bg-slate-50 rounded p-4">
206
+ <div className="text-sm text-slate-600">Avg Profit</div>
207
+ <div className="text-xl font-semibold text-slate-800">{selectedRun.metrics.avg_agent_profit?.toFixed(2)}</div>
208
+ </div>
209
+ <div className="bg-slate-50 rounded p-4">
210
+ <div className="text-sm text-slate-600">Cooperation Rate</div>
211
+ <div className="text-xl font-semibold text-slate-800">{selectedRun.metrics.cooperation_rate?.toFixed(2)}</div>
212
+ </div>
213
+ </div>
214
+ )}
215
+
216
+ {/* Agents */}
217
+ <h3 className="font-medium text-slate-800 mb-3">Agents</h3>
218
+ <div className="overflow-x-auto">
219
+ <table className="w-full">
220
+ <thead>
221
+ <tr className="border-b border-slate-200 text-left">
222
+ <th className="py-2 px-3 text-sm font-medium text-slate-600">Agent</th>
223
+ <th className="py-2 px-3 text-sm font-medium text-slate-600">Token A</th>
224
+ <th className="py-2 px-3 text-sm font-medium text-slate-600">Token B</th>
225
+ <th className="py-2 px-3 text-sm font-medium text-slate-600">Profit</th>
226
+ <th className="py-2 px-3 text-sm font-medium text-slate-600">Strategy</th>
227
+ </tr>
228
+ </thead>
229
+ <tbody>
230
+ {(selectedRun.agents || []).map((agent, i) => (
231
+ <tr key={i} className="border-b border-slate-100">
232
+ <td className="py-2 px-3 text-slate-800">{agent.name}</td>
233
+ <td className="py-2 px-3 text-slate-600">{agent.token_a?.toFixed(2)}</td>
234
+ <td className="py-2 px-3 text-slate-600">{agent.token_b?.toFixed(2)}</td>
235
+ <td className={`py-2 px-3 ${agent.profit >= 0 ? 'text-green-600' : 'text-red-600'}`}>
236
+ {agent.profit?.toFixed(2)}
237
+ </td>
238
+ <td className="py-2 px-3 text-slate-600">{agent.strategy}</td>
239
+ </tr>
240
+ ))}
241
+ </tbody>
242
+ </table>
243
+ </div>
244
+
245
+ {/* Actions */}
246
+ {(selectedRun.actions || []).length > 0 && (
247
+ <>
248
+ <h3 className="font-medium text-slate-800 mt-6 mb-3">Actions Log</h3>
249
+ <div className="space-y-2 max-h-64 overflow-y-auto">
250
+ {selectedRun.actions.slice(0, 20).map((action, i) => (
251
+ <div key={i} className="bg-slate-50 rounded p-3 text-sm">
252
+ <div className="flex gap-2 mb-1">
253
+ <span className="font-medium text-slate-800">{action.agent_name}</span>
254
+ <span className="text-slate-500">Turn {action.turn}</span>
255
+ </div>
256
+ <div className="text-slate-600">
257
+ <span className="font-medium">{action.action_type}</span>
258
+ {action.reasoning && (
259
+ <p className="text-slate-500 mt-1 text-xs">{action.reasoning.substring(0, 150)}...</p>
260
+ )}
261
+ </div>
262
+ </div>
263
+ ))}
264
+ </div>
265
+ </>
266
+ )}
267
+ </section>
268
+ )}
269
+ </main>
270
+ </div>
271
+ )
272
+ }
273
+
274
+ export default App
frontend/src/index.css ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ body {
6
+ background-color: #f8fafc;
7
+ color: #1e293b;
8
+ }
frontend/src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App.jsx'
4
+ import './index.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>,
10
+ )
frontend/tailwind.config.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {
9
+ colors: {
10
+ slate: {
11
+ 50: '#f8fafc',
12
+ 100: '#f1f5f9',
13
+ 200: '#e2e8f0',
14
+ 300: '#cbd5e1',
15
+ 400: '#94a3b8',
16
+ 500: '#64748b',
17
+ 600: '#475569',
18
+ 700: '#334155',
19
+ 800: '#1e293b',
20
+ 900: '#0f172a',
21
+ }
22
+ }
23
+ },
24
+ },
25
+ plugins: [],
26
+ }
frontend/vite.config.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ port: 3000,
8
+ proxy: {
9
+ '/api': 'http://localhost:8000'
10
+ }
11
+ }
12
+ })