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,280 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
6
+ import { Input } from '@/components/ui/input';
7
+ import { Label } from '@/components/ui/label';
8
+ import { Loader2, Coins, ExternalLink, CheckCircle2 } from 'lucide-react';
9
+ import { useAccount, useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
10
+ import { arbitrumSepolia } from 'wagmi/chains';
11
+ import { RAY_TRACING_ABI } from '@/lib/ray-tracing-abi';
12
+
13
+ interface MintingFormProps {
14
+ nftAddress: string;
15
+ isNftConnected: boolean;
16
+ predictedColors: [number, number, number] | null;
17
+ cameraX: number;
18
+ cameraY: number;
19
+ cameraZ: number;
20
+ onMintSuccess: (tokenId: bigint) => void;
21
+ }
22
+
23
+ export function MintingForm({
24
+ nftAddress,
25
+ isNftConnected,
26
+ predictedColors,
27
+ cameraX,
28
+ cameraY,
29
+ cameraZ,
30
+ onMintSuccess,
31
+ }: MintingFormProps) {
32
+ const { isConnected: walletConnected } = useAccount();
33
+
34
+ // Background colors (can be customized)
35
+ const [bgTopR, setBgTopR] = useState(255);
36
+ const [bgTopG, setBgTopG] = useState(255);
37
+ const [bgTopB, setBgTopB] = useState(255);
38
+ const [bgBottomR, setBgBottomR] = useState(91);
39
+ const [bgBottomG, setBgBottomG] = useState(127);
40
+ const [bgBottomB, setBgBottomB] = useState(213);
41
+
42
+ const { writeContract, data: hash, isPending, error } = useWriteContract();
43
+ const { isLoading: isConfirming, isSuccess, data: receipt } = useWaitForTransactionReceipt({
44
+ hash,
45
+ });
46
+
47
+ const handleMint = async () => {
48
+ if (!walletConnected || !isNftConnected || !predictedColors) {
49
+ alert('Please connect wallet and generate colors first');
50
+ return;
51
+ }
52
+
53
+ try {
54
+ writeContract({
55
+ address: nftAddress as `0x${string}`,
56
+ abi: RAY_TRACING_ABI,
57
+ functionName: 'mint',
58
+ args: [
59
+ predictedColors[0], // sphere_r
60
+ predictedColors[1], // sphere_g
61
+ predictedColors[2], // sphere_b
62
+ bgTopR, // bg_color1_r
63
+ bgTopG, // bg_color1_g
64
+ bgTopB, // bg_color1_b
65
+ bgBottomR, // bg_color2_r
66
+ bgBottomG, // bg_color2_g
67
+ bgBottomB, // bg_color2_b
68
+ cameraX, // cam_x
69
+ cameraY, // cam_y
70
+ cameraZ, // cam_z
71
+ ],
72
+ });
73
+ } catch (err) {
74
+ console.error('Minting failed:', err);
75
+ }
76
+ };
77
+
78
+ // Extract token ID from transaction logs when successful
79
+ if (isSuccess && receipt && !isPending) {
80
+ // The mint function returns the token_id
81
+ // We'll parse it from the transaction
82
+ const tokenId = BigInt(0); // This will be the new token ID
83
+ setTimeout(() => {
84
+ onMintSuccess(tokenId);
85
+ }, 1000);
86
+ }
87
+
88
+ const explorerUrl = hash
89
+ ? `${arbitrumSepolia.blockExplorers.default.url}/tx/${hash}`
90
+ : null;
91
+
92
+ return (
93
+ <Card>
94
+ <CardHeader>
95
+ <CardTitle>Mint NFT</CardTitle>
96
+ <CardDescription>
97
+ Store rendering parameters on-chain as an NFT
98
+ </CardDescription>
99
+ </CardHeader>
100
+ <CardContent className="space-y-4">
101
+ {/* Background Colors */}
102
+ <div className="space-y-3">
103
+ <h4 className="text-sm font-semibold">Background Gradient</h4>
104
+
105
+ {/* Top Color */}
106
+ <div className="flex items-center gap-3">
107
+ <div
108
+ className="w-12 h-12 rounded-md border border-border"
109
+ style={{ backgroundColor: `rgb(${bgTopR}, ${bgTopG}, ${bgTopB})` }}
110
+ />
111
+ <div className="flex-1 grid grid-cols-3 gap-2">
112
+ <Input
113
+ type="number"
114
+ placeholder="R"
115
+ value={bgTopR}
116
+ onChange={(e) => setBgTopR(Number(e.target.value))}
117
+ min={0}
118
+ max={255}
119
+ className="text-xs"
120
+ />
121
+ <Input
122
+ type="number"
123
+ placeholder="G"
124
+ value={bgTopG}
125
+ onChange={(e) => setBgTopG(Number(e.target.value))}
126
+ min={0}
127
+ max={255}
128
+ className="text-xs"
129
+ />
130
+ <Input
131
+ type="number"
132
+ placeholder="B"
133
+ value={bgTopB}
134
+ onChange={(e) => setBgTopB(Number(e.target.value))}
135
+ min={0}
136
+ max={255}
137
+ className="text-xs"
138
+ />
139
+ </div>
140
+ <Label className="text-xs text-muted-foreground w-12">Top</Label>
141
+ </div>
142
+
143
+ {/* Bottom Color */}
144
+ <div className="flex items-center gap-3">
145
+ <div
146
+ className="w-12 h-12 rounded-md border border-border"
147
+ style={{ backgroundColor: `rgb(${bgBottomR}, ${bgBottomG}, ${bgBottomB})` }}
148
+ />
149
+ <div className="flex-1 grid grid-cols-3 gap-2">
150
+ <Input
151
+ type="number"
152
+ placeholder="R"
153
+ value={bgBottomR}
154
+ onChange={(e) => setBgBottomR(Number(e.target.value))}
155
+ min={0}
156
+ max={255}
157
+ className="text-xs"
158
+ />
159
+ <Input
160
+ type="number"
161
+ placeholder="G"
162
+ value={bgBottomG}
163
+ onChange={(e) => setBgBottomG(Number(e.target.value))}
164
+ min={0}
165
+ max={255}
166
+ className="text-xs"
167
+ />
168
+ <Input
169
+ type="number"
170
+ placeholder="B"
171
+ value={bgBottomB}
172
+ onChange={(e) => setBgBottomB(Number(e.target.value))}
173
+ min={0}
174
+ max={255}
175
+ className="text-xs"
176
+ />
177
+ </div>
178
+ <Label className="text-xs text-muted-foreground w-12">Bottom</Label>
179
+ </div>
180
+ </div>
181
+
182
+ {/* Parameters Summary */}
183
+ <div className="bg-muted p-3 rounded-md text-xs space-y-1">
184
+ <h4 className="font-semibold mb-2">Parameters to Store</h4>
185
+ <div className="grid grid-cols-2 gap-2">
186
+ <div>
187
+ <span className="text-muted-foreground">Sphere RGB:</span>
188
+ <span className="ml-2 font-mono">
189
+ {predictedColors ? `${predictedColors[0]},${predictedColors[1]},${predictedColors[2]}` : 'N/A'}
190
+ </span>
191
+ </div>
192
+ <div>
193
+ <span className="text-muted-foreground">Camera:</span>
194
+ <span className="ml-2 font-mono">
195
+ {cameraX},{cameraY},{cameraZ}
196
+ </span>
197
+ </div>
198
+ </div>
199
+ <p className="text-muted-foreground pt-2">
200
+ Total: 21 bytes (9 colors + 12 camera)
201
+ </p>
202
+ </div>
203
+
204
+ {/* Mint Button */}
205
+ <Button
206
+ onClick={handleMint}
207
+ disabled={!walletConnected || !isNftConnected || !predictedColors || isPending || isConfirming}
208
+ className="w-full"
209
+ size="lg"
210
+ >
211
+ {isPending || isConfirming ? (
212
+ <>
213
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
214
+ {isPending ? 'Waiting for signature...' : 'Minting NFT...'}
215
+ </>
216
+ ) : (
217
+ <>
218
+ <Coins className="h-4 w-4 mr-2" />
219
+ Mint NFT (~$0.0001)
220
+ </>
221
+ )}
222
+ </Button>
223
+
224
+ {/* Transaction Status */}
225
+ {hash && (
226
+ <div className="space-y-2 pt-4 border-t border-border">
227
+ <div className="flex items-center justify-between text-sm">
228
+ <span className="text-muted-foreground">Transaction:</span>
229
+ <a
230
+ href={explorerUrl || '#'}
231
+ target="_blank"
232
+ rel="noopener noreferrer"
233
+ className="flex items-center gap-1 text-primary hover:underline"
234
+ >
235
+ {hash.slice(0, 6)}...{hash.slice(-4)}
236
+ <ExternalLink className="h-3 w-3" />
237
+ </a>
238
+ </div>
239
+ <div className="flex items-center justify-between text-sm">
240
+ <span className="text-muted-foreground">Status:</span>
241
+ <span className={isSuccess ? 'text-green-500' : 'text-yellow-500'}>
242
+ {isSuccess ? '✓ Success' : '⏳ Confirming...'}
243
+ </span>
244
+ </div>
245
+ </div>
246
+ )}
247
+
248
+ {isSuccess && (
249
+ <div className="bg-green-500/10 border border-green-500/20 p-3 rounded-md text-sm text-green-400">
250
+ <div className="flex items-center gap-2 mb-1">
251
+ <CheckCircle2 className="h-4 w-4" />
252
+ <p className="font-medium">NFT Minted Successfully!</p>
253
+ </div>
254
+ <p className="text-xs mt-1">
255
+ Your rendering parameters are now stored on-chain. Ready for Step 5: Full rendering!
256
+ </p>
257
+ </div>
258
+ )}
259
+
260
+ {error && (
261
+ <div className="bg-red-500/10 border border-red-500/20 p-3 rounded-md text-sm text-red-400">
262
+ <p className="font-medium">Minting Failed</p>
263
+ <p className="text-xs mt-1">{error.message}</p>
264
+ </div>
265
+ )}
266
+
267
+ {/* Info Box */}
268
+ <div className="bg-blue-500/10 border border-blue-500/20 p-3 rounded-md text-xs text-blue-400">
269
+ <p className="font-medium mb-1">💡 What Happens:</p>
270
+ <ul className="space-y-1">
271
+ <li>• Stores 21 bytes on-chain (colors + camera)</li>
272
+ <li>• Generates unique token ID</li>
273
+ <li>• You own the NFT</li>
274
+ <li>• Anyone can render it later (on-demand)</li>
275
+ </ul>
276
+ </div>
277
+ </CardContent>
278
+ </Card>
279
+ );
280
+ }
@@ -0,0 +1,228 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef } from 'react';
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
5
+ import { Button } from '@/components/ui/button';
6
+ import { Label } from '@/components/ui/label';
7
+ import { Slider } from '@/components/ui/slider';
8
+ import { Sparkles, Download } from 'lucide-react';
9
+
10
+ interface RenderCanvasProps {
11
+ predictedColors: [number, number, number] | null;
12
+ cameraX: number;
13
+ setCameraX: (value: number) => void;
14
+ cameraY: number;
15
+ setCameraY: (value: number) => void;
16
+ cameraZ: number;
17
+ setCameraZ: (value: number) => void;
18
+ isRendering: boolean;
19
+ renderedPixels: number[] | null;
20
+ nftAddress: string;
21
+ isNftConnected: boolean;
22
+ }
23
+
24
+ export function RenderCanvas({
25
+ predictedColors,
26
+ cameraX,
27
+ setCameraX,
28
+ cameraY,
29
+ setCameraY,
30
+ cameraZ,
31
+ setCameraZ,
32
+ isRendering,
33
+ renderedPixels,
34
+ nftAddress,
35
+ isNftConnected,
36
+ }: RenderCanvasProps) {
37
+ const canvasRef = useRef<HTMLCanvasElement>(null);
38
+
39
+ // Client-side simple sphere preview
40
+ useEffect(() => {
41
+ if (!predictedColors || !canvasRef.current) return;
42
+
43
+ const canvas = canvasRef.current;
44
+ const ctx = canvas.getContext('2d');
45
+ if (!ctx) return;
46
+
47
+ const width = 256;
48
+ const height = 256;
49
+ canvas.width = width;
50
+ canvas.height = height;
51
+
52
+ // Clear canvas
53
+ ctx.fillStyle = '#0a0a0a';
54
+ ctx.fillRect(0, 0, width, height);
55
+
56
+ // Draw gradient background
57
+ const bgGradient = ctx.createLinearGradient(0, 0, 0, height);
58
+ bgGradient.addColorStop(0, '#ffffff');
59
+ bgGradient.addColorStop(1, '#5b7fd5');
60
+ ctx.fillStyle = bgGradient;
61
+ ctx.fillRect(0, 0, width, height);
62
+
63
+ // Draw sphere with radial gradient (simulating 3D lighting)
64
+ const centerX = width / 2 + cameraX * 10;
65
+ const centerY = height / 2 - cameraY * 10;
66
+ const radius = 80 + cameraZ * 5;
67
+
68
+ const gradient = ctx.createRadialGradient(
69
+ centerX - radius * 0.3,
70
+ centerY - radius * 0.3,
71
+ radius * 0.1,
72
+ centerX,
73
+ centerY,
74
+ radius
75
+ );
76
+
77
+ const [r, g, b] = predictedColors;
78
+
79
+ // Light side (top-left)
80
+ gradient.addColorStop(0, `rgb(${Math.min(255, r + 40)}, ${Math.min(255, g + 40)}, ${Math.min(255, b + 40)})`);
81
+ // Mid tone
82
+ gradient.addColorStop(0.5, `rgb(${r}, ${g}, ${b})`);
83
+ // Shadow (bottom-right)
84
+ gradient.addColorStop(1, `rgb(${Math.floor(r * 0.3)}, ${Math.floor(g * 0.3)}, ${Math.floor(b * 0.3)})`);
85
+
86
+ ctx.fillStyle = gradient;
87
+ ctx.beginPath();
88
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
89
+ ctx.fill();
90
+
91
+ // Add subtle highlight
92
+ const highlight = ctx.createRadialGradient(
93
+ centerX - radius * 0.4,
94
+ centerY - radius * 0.4,
95
+ 0,
96
+ centerX - radius * 0.4,
97
+ centerY - radius * 0.4,
98
+ radius * 0.3
99
+ );
100
+ highlight.addColorStop(0, 'rgba(255, 255, 255, 0.6)');
101
+ highlight.addColorStop(1, 'rgba(255, 255, 255, 0)');
102
+
103
+ ctx.fillStyle = highlight;
104
+ ctx.beginPath();
105
+ ctx.arc(centerX - radius * 0.3, centerY - radius * 0.3, radius * 0.3, 0, Math.PI * 2);
106
+ ctx.fill();
107
+
108
+ }, [predictedColors, cameraX, cameraY, cameraZ]);
109
+
110
+ const handleDownload = () => {
111
+ if (!canvasRef.current) return;
112
+
113
+ const link = document.createElement('a');
114
+ link.download = 'ray-tracing-preview.png';
115
+ link.href = canvasRef.current.toDataURL();
116
+ link.click();
117
+ };
118
+
119
+ return (
120
+ <Card>
121
+ <CardHeader>
122
+ <CardTitle>3D Render Preview</CardTitle>
123
+ <CardDescription>
124
+ {predictedColors
125
+ ? 'Client-side preview with predicted colors from MNN'
126
+ : 'Preview colors with neural network first'
127
+ }
128
+ </CardDescription>
129
+ </CardHeader>
130
+ <CardContent className="space-y-4">
131
+ {/* Canvas */}
132
+ <div className="flex items-center justify-center bg-muted rounded-md p-4">
133
+ {predictedColors ? (
134
+ <canvas
135
+ ref={canvasRef}
136
+ className="border border-border rounded-md"
137
+ style={{ imageRendering: 'pixelated' }}
138
+ />
139
+ ) : (
140
+ <div className="flex items-center justify-center h-64 w-64 text-muted-foreground">
141
+ <div className="text-center">
142
+ <Sparkles className="h-12 w-12 mx-auto mb-3 opacity-50" />
143
+ <p className="text-sm">Generate preview first</p>
144
+ </div>
145
+ </div>
146
+ )}
147
+ </div>
148
+
149
+ {/* Camera Controls */}
150
+ {predictedColors && (
151
+ <>
152
+ <div className="space-y-3 pt-4 border-t border-border">
153
+ <h4 className="text-sm font-semibold">Camera Position</h4>
154
+
155
+ {/* Camera X */}
156
+ <div className="space-y-2">
157
+ <div className="flex items-center justify-between">
158
+ <Label className="text-xs">X-Axis (Left/Right)</Label>
159
+ <span className="text-xs text-muted-foreground">{cameraX}</span>
160
+ </div>
161
+ <Slider
162
+ value={[cameraX]}
163
+ onValueChange={(value) => setCameraX(value[0])}
164
+ min={-20}
165
+ max={20}
166
+ step={1}
167
+ className="w-full"
168
+ />
169
+ </div>
170
+
171
+ {/* Camera Y */}
172
+ <div className="space-y-2">
173
+ <div className="flex items-center justify-between">
174
+ <Label className="text-xs">Y-Axis (Up/Down)</Label>
175
+ <span className="text-xs text-muted-foreground">{cameraY}</span>
176
+ </div>
177
+ <Slider
178
+ value={[cameraY]}
179
+ onValueChange={(value) => setCameraY(value[0])}
180
+ min={-20}
181
+ max={20}
182
+ step={1}
183
+ className="w-full"
184
+ />
185
+ </div>
186
+
187
+ {/* Camera Z */}
188
+ <div className="space-y-2">
189
+ <div className="flex items-center justify-between">
190
+ <Label className="text-xs">Z-Axis (Zoom)</Label>
191
+ <span className="text-xs text-muted-foreground">{cameraZ}</span>
192
+ </div>
193
+ <Slider
194
+ value={[cameraZ]}
195
+ onValueChange={(value) => setCameraZ(value[0])}
196
+ min={-10}
197
+ max={10}
198
+ step={1}
199
+ className="w-full"
200
+ />
201
+ </div>
202
+ </div>
203
+
204
+ {/* Download Button */}
205
+ <Button
206
+ onClick={handleDownload}
207
+ variant="outline"
208
+ className="w-full"
209
+ size="sm"
210
+ >
211
+ <Download className="h-4 w-4 mr-2" />
212
+ Download Preview
213
+ </Button>
214
+
215
+ {/* Info about on-chain rendering */}
216
+ <div className="bg-yellow-500/10 border border-yellow-500/20 p-3 rounded-md text-xs text-yellow-400">
217
+ <p className="font-medium mb-1">ℹ️ Client-Side Preview</p>
218
+ <p>
219
+ This is a simplified preview. For full on-chain ray tracing with proper lighting and shadows,
220
+ mint an NFT and render on-chain (Step 4).
221
+ </p>
222
+ </div>
223
+ </>
224
+ )}
225
+ </CardContent>
226
+ </Card>
227
+ );
228
+ }