create-stylus-ide 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/Readme.MD +1515 -0
  2. package/cli.js +28 -0
  3. package/frontend/.vscode/settings.json +9 -0
  4. package/frontend/app/api/chat/route.ts +101 -0
  5. package/frontend/app/api/check-setup/route.ts +93 -0
  6. package/frontend/app/api/cleanup/route.ts +14 -0
  7. package/frontend/app/api/compile/route.ts +95 -0
  8. package/frontend/app/api/compile-stream/route.ts +98 -0
  9. package/frontend/app/api/complete/route.ts +86 -0
  10. package/frontend/app/api/deploy/route.ts +118 -0
  11. package/frontend/app/api/export-abi/route.ts +58 -0
  12. package/frontend/app/favicon.ico +0 -0
  13. package/frontend/app/globals.css +177 -0
  14. package/frontend/app/layout.tsx +29 -0
  15. package/frontend/app/ml/page.tsx +694 -0
  16. package/frontend/app/page.tsx +1132 -0
  17. package/frontend/app/providers.tsx +18 -0
  18. package/frontend/app/qlearning/page.tsx +188 -0
  19. package/frontend/app/raytracing/page.tsx +268 -0
  20. package/frontend/components/abi/ABIDialog.tsx +132 -0
  21. package/frontend/components/ai/AICompletionPopup.tsx +76 -0
  22. package/frontend/components/ai/ChatPanel.tsx +292 -0
  23. package/frontend/components/ai/QuickActions.tsx +128 -0
  24. package/frontend/components/blockchain/BlockchainContractBanner.tsx +64 -0
  25. package/frontend/components/blockchain/BlockchainLoadingDialog.tsx +188 -0
  26. package/frontend/components/deploy/DeployDialog.tsx +334 -0
  27. package/frontend/components/editor/FileTabs.tsx +181 -0
  28. package/frontend/components/editor/MonacoEditor.tsx +306 -0
  29. package/frontend/components/file-tree/ContextMenu.tsx +110 -0
  30. package/frontend/components/file-tree/DeleteConfirmDialog.tsx +61 -0
  31. package/frontend/components/file-tree/FileInputDialog.tsx +97 -0
  32. package/frontend/components/file-tree/FileNode.tsx +60 -0
  33. package/frontend/components/file-tree/FileTree.tsx +259 -0
  34. package/frontend/components/file-tree/FileTreeSkeleton.tsx +26 -0
  35. package/frontend/components/file-tree/FolderNode.tsx +105 -0
  36. package/frontend/components/github/GitHubLoadingDialog.tsx +201 -0
  37. package/frontend/components/github/GitHubMetadataBanner.tsx +61 -0
  38. package/frontend/components/github/LoadFromGitHubDialog.tsx +125 -0
  39. package/frontend/components/github/URLCopyButton.tsx +60 -0
  40. package/frontend/components/interact/ContractInteraction.tsx +323 -0
  41. package/frontend/components/interact/ContractPlaceholder.tsx +41 -0
  42. package/frontend/components/orbit/BenchmarkDialog.tsx +342 -0
  43. package/frontend/components/orbit/OrbitExplorer.tsx +273 -0
  44. package/frontend/components/project/ProjectActions.tsx +176 -0
  45. package/frontend/components/q-learning/ContractConfig.tsx +172 -0
  46. package/frontend/components/q-learning/MazeGrid.tsx +346 -0
  47. package/frontend/components/q-learning/PathAnimation.tsx +384 -0
  48. package/frontend/components/q-learning/QTableHeatmap.tsx +300 -0
  49. package/frontend/components/q-learning/TrainingForm.tsx +349 -0
  50. package/frontend/components/ray-tracing/ContractConfig.tsx +245 -0
  51. package/frontend/components/ray-tracing/MintingForm.tsx +280 -0
  52. package/frontend/components/ray-tracing/RenderCanvas.tsx +228 -0
  53. package/frontend/components/ray-tracing/RenderingPanel.tsx +259 -0
  54. package/frontend/components/ray-tracing/StyleControls.tsx +217 -0
  55. package/frontend/components/setup/SetupGuide.tsx +290 -0
  56. package/frontend/components/ui/KeyboardShortcutHint.tsx +74 -0
  57. package/frontend/components/ui/alert-dialog.tsx +157 -0
  58. package/frontend/components/ui/alert.tsx +66 -0
  59. package/frontend/components/ui/badge.tsx +46 -0
  60. package/frontend/components/ui/button.tsx +62 -0
  61. package/frontend/components/ui/card.tsx +92 -0
  62. package/frontend/components/ui/context-menu.tsx +252 -0
  63. package/frontend/components/ui/dialog.tsx +143 -0
  64. package/frontend/components/ui/dropdown-menu.tsx +257 -0
  65. package/frontend/components/ui/input.tsx +21 -0
  66. package/frontend/components/ui/label.tsx +24 -0
  67. package/frontend/components/ui/progress.tsx +31 -0
  68. package/frontend/components/ui/scroll-area.tsx +58 -0
  69. package/frontend/components/ui/select.tsx +190 -0
  70. package/frontend/components/ui/separator.tsx +28 -0
  71. package/frontend/components/ui/sheet.tsx +139 -0
  72. package/frontend/components/ui/skeleton.tsx +13 -0
  73. package/frontend/components/ui/slider.tsx +63 -0
  74. package/frontend/components/ui/sonner.tsx +40 -0
  75. package/frontend/components/ui/tabs.tsx +66 -0
  76. package/frontend/components/ui/textarea.tsx +18 -0
  77. package/frontend/components/wallet/ConnectButton.tsx +167 -0
  78. package/frontend/components/wallet/FaucetButton.tsx +256 -0
  79. package/frontend/components.json +22 -0
  80. package/frontend/eslint.config.mjs +18 -0
  81. package/frontend/hooks/useAICompletion.ts +75 -0
  82. package/frontend/hooks/useBlockchainLoader.ts +58 -0
  83. package/frontend/hooks/useChats.ts +137 -0
  84. package/frontend/hooks/useCompilation.ts +173 -0
  85. package/frontend/hooks/useFileTabs.ts +178 -0
  86. package/frontend/hooks/useGitHubLoader.ts +50 -0
  87. package/frontend/hooks/useKeyboardShortcuts.ts +47 -0
  88. package/frontend/hooks/usePanelState.ts +115 -0
  89. package/frontend/hooks/useProjectState.ts +276 -0
  90. package/frontend/hooks/useResponsive.ts +29 -0
  91. package/frontend/lib/abi-parser.ts +58 -0
  92. package/frontend/lib/blockchain-api.ts +374 -0
  93. package/frontend/lib/blockchain-explorers.ts +75 -0
  94. package/frontend/lib/blockchain-loader.ts +112 -0
  95. package/frontend/lib/cargo-template.ts +64 -0
  96. package/frontend/lib/compilation.ts +529 -0
  97. package/frontend/lib/constants.ts +31 -0
  98. package/frontend/lib/deployment.ts +176 -0
  99. package/frontend/lib/file-utils.ts +83 -0
  100. package/frontend/lib/github-api.ts +246 -0
  101. package/frontend/lib/github-loader.ts +369 -0
  102. package/frontend/lib/ml-contract-template.txt +900 -0
  103. package/frontend/lib/orbit-chains.ts +181 -0
  104. package/frontend/lib/output-formatter.ts +68 -0
  105. package/frontend/lib/project-manager.ts +632 -0
  106. package/frontend/lib/ray-tracing-abi.ts +206 -0
  107. package/frontend/lib/storage.ts +189 -0
  108. package/frontend/lib/templates.ts +1662 -0
  109. package/frontend/lib/url-parser.ts +188 -0
  110. package/frontend/lib/utils.ts +6 -0
  111. package/frontend/lib/wagmi-config.ts +24 -0
  112. package/frontend/next.config.ts +7 -0
  113. package/frontend/package-lock.json +16259 -0
  114. package/frontend/package.json +60 -0
  115. package/frontend/postcss.config.mjs +7 -0
  116. package/frontend/public/file.svg +1 -0
  117. package/frontend/public/globe.svg +1 -0
  118. package/frontend/public/ml-weights/.gitkeep +0 -0
  119. package/frontend/public/ml-weights/model.pkl +0 -0
  120. package/frontend/public/ml-weights/model_weights.json +27102 -0
  121. package/frontend/public/ml-weights/test_samples.json +7888 -0
  122. package/frontend/public/next.svg +1 -0
  123. package/frontend/public/vercel.svg +1 -0
  124. package/frontend/public/window.svg +1 -0
  125. package/frontend/scripts/check-env.js +52 -0
  126. package/frontend/scripts/setup.js +285 -0
  127. package/frontend/tailwind.config.ts +64 -0
  128. package/frontend/tsconfig.json +34 -0
  129. package/frontend/types/blockchain.ts +63 -0
  130. package/frontend/types/github.ts +54 -0
  131. package/frontend/types/project.ts +106 -0
  132. package/ml-training/README.md +56 -0
  133. package/ml-training/train_tiny_model.py +325 -0
  134. package/ml-training/update_template.py +59 -0
  135. package/package.json +30 -0
@@ -0,0 +1,384 @@
1
+ //pathanimation.tsx
2
+ 'use client';
3
+
4
+ import { useEffect, useState } from 'react';
5
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
6
+ import { Button } from '@/components/ui/button';
7
+ import { usePublicClient } from 'wagmi';
8
+ import { arbitrumSepolia } from 'wagmi/chains';
9
+ import { parseAbi } from 'viem';
10
+ import { Play, Pause, RotateCcw, Loader2 } from 'lucide-react';
11
+
12
+ const QLEARNING_ABI = parseAbi([
13
+ 'function getPolicy(uint256 row, uint256 col) external view returns (uint256)',
14
+ ]);
15
+
16
+ const MAZE: number[][] = [
17
+ [0, 0, 0, 0, 0],
18
+ [0, 1, 1, 1, 0],
19
+ [0, 0, 0, 0, 0],
20
+ [0, 1, 1, 1, 0],
21
+ [0, 0, 0, 0, 0],
22
+ ];
23
+
24
+ interface PathStep {
25
+ row: number;
26
+ col: number;
27
+ action: number;
28
+ }
29
+
30
+ interface PathAnimationProps {
31
+ contractAddress: string;
32
+ size: number;
33
+ startPos: [number, number];
34
+ goalPos: [number, number];
35
+ isConnected: boolean;
36
+ isTrained: boolean;
37
+ }
38
+
39
+ export function PathAnimation({
40
+ contractAddress,
41
+ size,
42
+ startPos,
43
+ goalPos,
44
+ isConnected,
45
+ isTrained,
46
+ }: PathAnimationProps) {
47
+ const [path, setPath] = useState<PathStep[]>([]);
48
+ const [currentStep, setCurrentStep] = useState(0);
49
+ const [isAnimating, setIsAnimating] = useState(false);
50
+ const [isLoading, setIsLoading] = useState(false);
51
+ const [totalReward, setTotalReward] = useState(0);
52
+ const [endedWithGoal, setEndedWithGoal] = useState(false);
53
+ const [endReason, setEndReason] = useState<string | null>(null);
54
+
55
+ const publicClient = usePublicClient({ chainId: arbitrumSepolia.id });
56
+
57
+ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
58
+
59
+ const isWall = (r: number, c: number) => {
60
+ const rr = Math.max(0, Math.min(size - 1, r));
61
+ const cc = Math.max(0, Math.min(size - 1, c));
62
+ return MAZE[rr]?.[cc] === 1;
63
+ };
64
+
65
+ const executeAction = (row: number, col: number, action: number): [number, number] => {
66
+ let nextRow = row;
67
+ let nextCol = col;
68
+
69
+ switch (action) {
70
+ case 0: nextRow = Math.max(0, row - 1); break; // Up
71
+ case 1: nextRow = Math.min(size - 1, row + 1); break; // Down
72
+ case 2: nextCol = Math.max(0, col - 1); break; // Left
73
+ case 3: nextCol = Math.min(size - 1, col + 1); break; // Right
74
+ default: break;
75
+ }
76
+
77
+ return [nextRow, nextCol];
78
+ };
79
+
80
+ const getActionName = (action: number) => ['Up', 'Down', 'Left', 'Right'][action] || 'Unknown';
81
+ const getActionSymbol = (action: number) => ['↑', '↓', '←', '→'][action] || '?';
82
+
83
+ const fetchPolicyGrid = async () => {
84
+ if (!publicClient) throw new Error('publicClient not ready');
85
+ const addr = contractAddress.trim().toLowerCase();
86
+ if (!addr) throw new Error('Missing contract address');
87
+
88
+ const cells: { row: number; col: number }[] = [];
89
+ for (let r = 0; r < size; r++) {
90
+ for (let c = 0; c < size; c++) {
91
+ if (MAZE[r]?.[c] !== 1) cells.push({ row: r, col: c });
92
+ }
93
+ }
94
+
95
+ const contracts = cells.map(({ row, col }) => ({
96
+ address: addr as `0x${string}`,
97
+ abi: QLEARNING_ABI,
98
+ functionName: 'getPolicy' as const,
99
+ args: [BigInt(row), BigInt(col)] as const,
100
+ }));
101
+
102
+ const res = await publicClient.multicall({ contracts, allowFailure: true });
103
+
104
+ const grid: number[][] = Array.from({ length: size }, () => Array.from({ length: size }, () => 0));
105
+ for (let i = 0; i < cells.length; i++) {
106
+ const { row, col } = cells[i];
107
+ const r: any = res[i];
108
+ if (r?.status === 'success') grid[row][col] = Number(r.result);
109
+ }
110
+
111
+ return grid;
112
+ };
113
+
114
+ const simulatePath = async () => {
115
+ if (!publicClient || !contractAddress) return;
116
+
117
+ setIsLoading(true);
118
+ setIsAnimating(false);
119
+ setPath([]);
120
+ setCurrentStep(0);
121
+ setTotalReward(0);
122
+ setEndedWithGoal(false);
123
+ setEndReason(null);
124
+
125
+ try {
126
+ const policyGrid = await fetchPolicyGrid();
127
+
128
+ const simulatedPath: PathStep[] = [];
129
+ let row = startPos[0];
130
+ let col = startPos[1];
131
+ let reward = 0;
132
+
133
+ let goalReached = false;
134
+ let reason: string | null = null;
135
+
136
+ const maxSteps = 50;
137
+
138
+ // loop detection by revisiting same cell too many times
139
+ const visits = new Map<string, number>();
140
+ let consecutiveWallMoves = 0;
141
+
142
+ for (let step = 0; step < maxSteps; step++) {
143
+ const action = policyGrid[row]?.[col] ?? 0;
144
+ simulatedPath.push({ row, col, action });
145
+
146
+ const key = `${row},${col}`;
147
+ visits.set(key, (visits.get(key) ?? 0) + 1);
148
+ if ((visits.get(key) ?? 0) >= 8) {
149
+ reason = 'Loop detected (revisiting the same cell repeatedly).';
150
+ break;
151
+ }
152
+
153
+ const [candidateRow, candidateCol] = executeAction(row, col, action);
154
+
155
+ // WALL: stay in place, -10 (matches your contract semantics)
156
+ if (isWall(candidateRow, candidateCol)) {
157
+ reward -= 10;
158
+ consecutiveWallMoves++;
159
+ if (consecutiveWallMoves >= 8) {
160
+ reason = 'Agent kept choosing wall moves.';
161
+ break;
162
+ }
163
+ // stay (row/col unchanged)
164
+ } else {
165
+ // NORMAL step (including boundary clamp where you might not move): -1
166
+ reward -= 1;
167
+ consecutiveWallMoves = 0;
168
+ row = candidateRow;
169
+ col = candidateCol;
170
+ }
171
+
172
+ // GOAL: +100 then end
173
+ if (row === goalPos[0] && col === goalPos[1]) {
174
+ reward += 100;
175
+ goalReached = true;
176
+ reason = null;
177
+ break;
178
+ }
179
+
180
+ if (step % 6 === 0) await sleep(20);
181
+ }
182
+
183
+ if (!goalReached && !reason) reason = 'Stopped (max steps reached).';
184
+
185
+ setPath(simulatedPath);
186
+ setTotalReward(reward);
187
+ setEndedWithGoal(goalReached);
188
+ setEndReason(reason);
189
+ } catch (err) {
190
+ console.error('Failed to simulate path:', err);
191
+ setEndReason('RPC error while generating policy/path.');
192
+ } finally {
193
+ setIsLoading(false);
194
+ }
195
+ };
196
+
197
+ const handlePlayPause = () => setIsAnimating((v) => !v);
198
+ const handleReset = () => {
199
+ setIsAnimating(false);
200
+ setCurrentStep(0);
201
+ };
202
+
203
+ useEffect(() => {
204
+ if (!isAnimating || currentStep >= path.length - 1) {
205
+ if (currentStep >= path.length - 1 && path.length > 0) setIsAnimating(false);
206
+ return;
207
+ }
208
+ const t = setTimeout(() => setCurrentStep((p) => p + 1), 500);
209
+ return () => clearTimeout(t);
210
+ }, [isAnimating, currentStep, path.length]);
211
+
212
+ const isCurrentPosition = (row: number, col: number) => {
213
+ if (path.length === 0 || currentStep >= path.length) return false;
214
+ const cur = path[currentStep];
215
+ return cur.row === row && cur.col === col;
216
+ };
217
+
218
+ const isVisitedPosition = (row: number, col: number) =>
219
+ path.slice(0, currentStep).some((s) => s.row === row && s.col === col);
220
+
221
+ if (!isTrained) {
222
+ return (
223
+ <Card>
224
+ <CardHeader>
225
+ <CardTitle>Path Simulation</CardTitle>
226
+ <CardDescription>Visualize the agent following the learned policy</CardDescription>
227
+ </CardHeader>
228
+ <CardContent>
229
+ <div className="flex items-center justify-center h-64 text-muted-foreground">
230
+ <div className="text-center">
231
+ <p>Train the agent first to simulate the path</p>
232
+ </div>
233
+ </div>
234
+ </CardContent>
235
+ </Card>
236
+ );
237
+ }
238
+
239
+ return (
240
+ <Card>
241
+ <CardHeader>
242
+ <CardTitle>Path Simulation</CardTitle>
243
+ <CardDescription>Watch the agent navigate from start to goal using learned policy</CardDescription>
244
+ </CardHeader>
245
+
246
+ <CardContent className="space-y-4">
247
+ <div className="flex items-center gap-2">
248
+ <Button onClick={simulatePath} disabled={isLoading || isAnimating || !isConnected} size="sm">
249
+ {isLoading ? (
250
+ <>
251
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
252
+ Loading Path...
253
+ </>
254
+ ) : (
255
+ <>
256
+ <RotateCcw className="h-4 w-4 mr-2" />
257
+ Generate Path
258
+ </>
259
+ )}
260
+ </Button>
261
+
262
+ {path.length > 0 && (
263
+ <>
264
+ <Button onClick={handlePlayPause} disabled={isLoading} size="sm" variant="outline">
265
+ {isAnimating ? (
266
+ <>
267
+ <Pause className="h-4 w-4 mr-2" />
268
+ Pause
269
+ </>
270
+ ) : (
271
+ <>
272
+ <Play className="h-4 w-4 mr-2" />
273
+ Play
274
+ </>
275
+ )}
276
+ </Button>
277
+
278
+ <Button onClick={handleReset} disabled={isLoading || isAnimating} size="sm" variant="outline">
279
+ <RotateCcw className="h-4 w-4 mr-2" />
280
+ Reset
281
+ </Button>
282
+ </>
283
+ )}
284
+ </div>
285
+
286
+ {path.length > 0 && (
287
+ <div className="grid grid-cols-3 gap-3">
288
+ <div className="bg-muted p-3 rounded-md">
289
+ <div className="text-xs text-muted-foreground">Steps</div>
290
+ <div className="text-xl font-bold">
291
+ {Math.min(currentStep + 1, path.length)} / {path.length}
292
+ </div>
293
+ </div>
294
+ <div className="bg-muted p-3 rounded-md">
295
+ <div className="text-xs text-muted-foreground">Total Reward</div>
296
+ <div className="text-xl font-bold">{totalReward}</div>
297
+ </div>
298
+ <div className="bg-muted p-3 rounded-md">
299
+ <div className="text-xs text-muted-foreground">Current Action</div>
300
+ <div className="text-xl font-bold">
301
+ {currentStep < path.length ? getActionName(path[currentStep].action) : 'Done'}
302
+ </div>
303
+ </div>
304
+ </div>
305
+ )}
306
+
307
+ {path.length > 0 && (
308
+ <div>
309
+ <h4 className="text-sm font-semibold mb-2">Agent Position</h4>
310
+ <div className="grid gap-1" style={{ gridTemplateColumns: `repeat(${size}, 1fr)` }}>
311
+ {Array.from({ length: size }).map((_, row) =>
312
+ Array.from({ length: size }).map((_, col) => {
313
+ const isCurrent = isCurrentPosition(row, col);
314
+ const isVisited = isVisitedPosition(row, col);
315
+ const isStart = row === startPos[0] && col === startPos[1];
316
+ const isGoal = row === goalPos[0] && col === goalPos[1];
317
+ const wall = isWall(row, col);
318
+
319
+ return (
320
+ <div
321
+ key={`${row}-${col}`}
322
+ className={`
323
+ w-12 h-12 rounded-md flex items-center justify-center
324
+ border transition-all
325
+ ${wall ? 'bg-slate-600/40 border-slate-600' : ''}
326
+ ${isCurrent ? 'bg-blue-500 border-blue-600 scale-110' : ''}
327
+ ${isVisited && !isCurrent ? 'bg-blue-500/30 border-blue-500/50' : ''}
328
+ ${!wall && !isCurrent && !isVisited && !isStart && !isGoal ? 'bg-muted border-border' : ''}
329
+ ${isStart && !isCurrent ? 'bg-green-500/50 border-green-500' : ''}
330
+ ${isGoal ? 'bg-red-500/50 border-red-500' : ''}
331
+ `}
332
+ >
333
+ {isCurrent && <span className="text-2xl">🤖</span>}
334
+ {isStart && !isCurrent && <span className="text-xs font-semibold text-white">S</span>}
335
+ {isGoal && !isCurrent && <span className="text-xs font-semibold text-white">G</span>}
336
+ </div>
337
+ );
338
+ })
339
+ )}
340
+ </div>
341
+ </div>
342
+ )}
343
+
344
+ {path.length > 0 && (
345
+ <div>
346
+ <h4 className="text-sm font-semibold mb-2">Path Sequence</h4>
347
+ <div className="bg-muted p-3 rounded-md max-h-32 overflow-y-auto">
348
+ <div className="flex flex-wrap gap-2">
349
+ {path.map((step, idx) => (
350
+ <div
351
+ key={idx}
352
+ className={`
353
+ px-2 py-1 rounded text-xs font-mono
354
+ ${idx === currentStep ? 'bg-blue-500 text-white' : 'bg-background'}
355
+ ${idx < currentStep ? 'opacity-50' : ''}
356
+ `}
357
+ >
358
+ ({step.row},{step.col}) {getActionSymbol(step.action)}
359
+ </div>
360
+ ))}
361
+ </div>
362
+ </div>
363
+ </div>
364
+ )}
365
+
366
+ {path.length > 0 && currentStep >= path.length - 1 && !isAnimating && (
367
+ endedWithGoal ? (
368
+ <div className="bg-green-500/10 border border-green-500/20 p-3 rounded-md text-sm text-green-400">
369
+ <p className="font-medium">🎉 Goal Reached!</p>
370
+ <p className="text-xs mt-1">
371
+ Agent reached the goal in {path.length} steps with total reward: {totalReward}
372
+ </p>
373
+ </div>
374
+ ) : (
375
+ <div className="bg-yellow-500/10 border border-yellow-500/20 p-3 rounded-md text-sm text-yellow-300">
376
+ <p className="font-medium">Stopped (did not reach goal)</p>
377
+ <p className="text-xs mt-1">{endReason ?? 'Unknown reason'}</p>
378
+ </div>
379
+ )
380
+ )}
381
+ </CardContent>
382
+ </Card>
383
+ );
384
+ }
@@ -0,0 +1,300 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
5
+ import { Button } from '@/components/ui/button';
6
+ import { usePublicClient } from 'wagmi';
7
+ import { arbitrumSepolia } from 'wagmi/chains';
8
+ import { parseAbi } from 'viem';
9
+ import { Loader2, RefreshCw } from 'lucide-react';
10
+
11
+ const QLEARNING_ABI = parseAbi([
12
+ 'function getQValue(uint256 row, uint256 col, uint256 action) external view returns (int256)',
13
+ ]);
14
+
15
+ const ACTIONS = ['Up ↑', 'Down ↓', 'Left ←', 'Right →'];
16
+ const ACTION_SYMBOLS = ['↑', '↓', '←', '→'];
17
+
18
+ interface QTableHeatmapProps {
19
+ contractAddress: string;
20
+ size: number;
21
+ isConnected: boolean;
22
+ isTrained: boolean;
23
+ }
24
+
25
+ export function QTableHeatmap({ contractAddress, size, isConnected, isTrained }: QTableHeatmapProps) {
26
+ const [qValues, setQValues] = useState<number[][][]>([]); // [row][col][action]
27
+ const [isLoading, setIsLoading] = useState(false);
28
+ const [selectedCell, setSelectedCell] = useState<[number, number] | null>(null);
29
+ const publicClient = usePublicClient({ chainId: arbitrumSepolia.id });
30
+
31
+ useEffect(() => {
32
+ if (isTrained && isConnected) {
33
+ loadQTable();
34
+ }
35
+ }, [isTrained, isConnected]);
36
+
37
+ const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
38
+
39
+ const loadQTable = async () => {
40
+ if (!publicClient) return;
41
+
42
+ setIsLoading(true);
43
+
44
+ try {
45
+ const qTable: number[][][] = [];
46
+
47
+ for (let row = 0; row < size; row++) {
48
+ const rowData: number[][] = [];
49
+ for (let col = 0; col < size; col++) {
50
+ const cellData: number[] = [];
51
+ for (let action = 0; action < 4; action++) {
52
+ try {
53
+ const qValue = await publicClient.readContract({
54
+ address: contractAddress as `0x${string}`,
55
+ abi: QLEARNING_ABI,
56
+ functionName: 'getQValue',
57
+ args: [BigInt(row), BigInt(col), BigInt(action)],
58
+ });
59
+
60
+ // Convert from scaled integer (divide by 10000)
61
+ const scaledValue = Number(qValue) / 10000;
62
+ cellData.push(scaledValue);
63
+
64
+ // Delay every 10 calls to avoid rate limiting
65
+ if ((row * size * 4 + col * 4 + action) % 10 === 0) {
66
+ await sleep(50);
67
+ }
68
+ } catch (err) {
69
+ console.error(`Failed to get Q-value for (${row}, ${col}, ${action}):`, err);
70
+ cellData.push(0);
71
+ }
72
+ }
73
+ rowData.push(cellData);
74
+ }
75
+ qTable.push(rowData);
76
+ }
77
+
78
+ setQValues(qTable);
79
+ } catch (err) {
80
+ console.error('Failed to load Q-table:', err);
81
+ } finally {
82
+ setIsLoading(false);
83
+ }
84
+ };
85
+
86
+ const getColorForQValue = (qValue: number, maxQ: number, minQ: number): string => {
87
+ if (maxQ === minQ) return 'bg-muted';
88
+
89
+ const normalized = (qValue - minQ) / (maxQ - minQ);
90
+
91
+ if (normalized > 0.8) return 'bg-green-500';
92
+ if (normalized > 0.6) return 'bg-green-400';
93
+ if (normalized > 0.4) return 'bg-yellow-500';
94
+ if (normalized > 0.2) return 'bg-orange-400';
95
+ return 'bg-red-400';
96
+ };
97
+
98
+ const getBestAction = (row: number, col: number): number => {
99
+ if (!qValues[row]?.[col]) return 0;
100
+
101
+ const actions = qValues[row][col];
102
+ let bestAction = 0;
103
+ let maxQ = actions[0];
104
+
105
+ for (let i = 1; i < actions.length; i++) {
106
+ if (actions[i] > maxQ) {
107
+ maxQ = actions[i];
108
+ bestAction = i;
109
+ }
110
+ }
111
+
112
+ return bestAction;
113
+ };
114
+
115
+ // Find global min/max for color scaling
116
+ const getGlobalMinMax = () => {
117
+ let min = 0;
118
+ let max = 0;
119
+
120
+ qValues.forEach(row => {
121
+ row.forEach(cell => {
122
+ cell.forEach(q => {
123
+ min = Math.min(min, q);
124
+ max = Math.max(max, q);
125
+ });
126
+ });
127
+ });
128
+
129
+ return { min, max };
130
+ };
131
+
132
+ const { min: globalMin, max: globalMax } = qValues.length > 0 ? getGlobalMinMax() : { min: 0, max: 0 };
133
+
134
+ if (!isTrained) {
135
+ return (
136
+ <Card>
137
+ <CardHeader>
138
+ <CardTitle>Q-Table Heatmap</CardTitle>
139
+ <CardDescription>
140
+ Visualize learned Q-values after training
141
+ </CardDescription>
142
+ </CardHeader>
143
+ <CardContent>
144
+ <div className="flex items-center justify-center h-64 text-muted-foreground">
145
+ <div className="text-center">
146
+ <p>Train the agent first to see Q-values</p>
147
+ </div>
148
+ </div>
149
+ </CardContent>
150
+ </Card>
151
+ );
152
+ }
153
+
154
+ return (
155
+ <Card>
156
+ <CardHeader>
157
+ <div className="flex items-center justify-between">
158
+ <div>
159
+ <CardTitle>Q-Table Heatmap</CardTitle>
160
+ <CardDescription>
161
+ Click a cell to see Q-values for all actions
162
+ </CardDescription>
163
+ </div>
164
+ <Button
165
+ onClick={loadQTable}
166
+ disabled={isLoading}
167
+ variant="outline"
168
+ size="sm"
169
+ >
170
+ {isLoading ? (
171
+ <>
172
+ <Loader2 className="h-3 w-3 mr-2 animate-spin" />
173
+ Loading...
174
+ </>
175
+ ) : (
176
+ <>
177
+ <RefreshCw className="h-3 w-3 mr-2" />
178
+ Refresh
179
+ </>
180
+ )}
181
+ </Button>
182
+ </div>
183
+ </CardHeader>
184
+ <CardContent className="space-y-4">
185
+ {isLoading && qValues.length === 0 ? (
186
+ <div className="flex items-center justify-center h-64">
187
+ <div className="text-center">
188
+ <Loader2 className="h-8 w-8 mx-auto mb-2 animate-spin text-primary" />
189
+ <p className="text-sm text-muted-foreground">Loading Q-values...</p>
190
+ <p className="text-xs text-muted-foreground mt-1">This may take 10-20 seconds</p>
191
+ </div>
192
+ </div>
193
+ ) : qValues.length > 0 ? (
194
+ <>
195
+ {/* Q-Table Grid */}
196
+ <div className="grid gap-1" style={{ gridTemplateColumns: `repeat(${size}, 1fr)` }}>
197
+ {qValues.map((row, rowIdx) =>
198
+ row.map((cell, colIdx) => {
199
+ const bestAction = getBestAction(rowIdx, colIdx);
200
+ const maxQValue = Math.max(...cell);
201
+ const isSelected = selectedCell?.[0] === rowIdx && selectedCell?.[1] === colIdx;
202
+
203
+ return (
204
+ <button
205
+ key={`${rowIdx}-${colIdx}`}
206
+ onClick={() => setSelectedCell([rowIdx, colIdx])}
207
+ className={`
208
+ w-16 h-16 rounded-md flex flex-col items-center justify-center
209
+ border-2 transition-all hover:scale-105
210
+ ${isSelected ? 'border-primary' : 'border-border'}
211
+ ${getColorForQValue(maxQValue, globalMax, globalMin)}
212
+ relative
213
+ `}
214
+ >
215
+ <span className="text-xs text-white/70 absolute top-1 left-1">
216
+ {rowIdx},{colIdx}
217
+ </span>
218
+ <span className="text-2xl font-bold text-white">
219
+ {ACTION_SYMBOLS[bestAction]}
220
+ </span>
221
+ <span className="text-xs text-white/90 mt-1">
222
+ {maxQValue.toFixed(0)}
223
+ </span>
224
+ </button>
225
+ );
226
+ })
227
+ )}
228
+ </div>
229
+
230
+ {/* Legend */}
231
+ <div className="flex items-center gap-4 text-xs">
232
+ <span className="text-muted-foreground">Q-Value Range:</span>
233
+ <div className="flex items-center gap-2">
234
+ <div className="w-4 h-4 bg-red-400 rounded" />
235
+ <span>Low</span>
236
+ </div>
237
+ <div className="flex items-center gap-2">
238
+ <div className="w-4 h-4 bg-yellow-500 rounded" />
239
+ <span>Medium</span>
240
+ </div>
241
+ <div className="flex items-center gap-2">
242
+ <div className="w-4 h-4 bg-green-500 rounded" />
243
+ <span>High</span>
244
+ </div>
245
+ <span className="ml-auto text-muted-foreground">
246
+ Range: {globalMin.toFixed(0)} to {globalMax.toFixed(0)}
247
+ </span>
248
+ </div>
249
+
250
+ {/* Selected Cell Details */}
251
+ {selectedCell && qValues[selectedCell[0]]?.[selectedCell[1]] && (
252
+ <div className="bg-muted p-4 rounded-lg">
253
+ <h4 className="font-semibold mb-3">
254
+ State ({selectedCell[0]}, {selectedCell[1]}) - Q-Values
255
+ </h4>
256
+ <div className="grid grid-cols-2 gap-3">
257
+ {ACTIONS.map((action, idx) => {
258
+ const qValue = qValues[selectedCell[0]][selectedCell[1]][idx];
259
+ const isBest = idx === getBestAction(selectedCell[0], selectedCell[1]);
260
+
261
+ return (
262
+ <div
263
+ key={idx}
264
+ className={`
265
+ p-2 rounded-md border
266
+ ${isBest ? 'border-primary bg-primary/10' : 'border-border'}
267
+ `}
268
+ >
269
+ <div className="flex items-center justify-between">
270
+ <span className="text-sm font-medium">{action}</span>
271
+ {isBest && (
272
+ <span className="text-xs bg-primary text-primary-foreground px-1.5 py-0.5 rounded">
273
+ Best
274
+ </span>
275
+ )}
276
+ </div>
277
+ <div className="text-lg font-bold mt-1">
278
+ {qValue.toFixed(2)}
279
+ </div>
280
+ </div>
281
+ );
282
+ })}
283
+ </div>
284
+ <p className="text-xs text-muted-foreground mt-3">
285
+ Best action: <strong>{ACTIONS[getBestAction(selectedCell[0], selectedCell[1])]}</strong>
286
+ </p>
287
+ </div>
288
+ )}
289
+ </>
290
+ ) : (
291
+ <div className="flex items-center justify-center h-64 text-muted-foreground">
292
+ <div className="text-center">
293
+ <p>Click "Refresh" to load Q-values</p>
294
+ </div>
295
+ </div>
296
+ )}
297
+ </CardContent>
298
+ </Card>
299
+ );
300
+ }