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,1132 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import { useCallback, useEffect, useMemo, useState } from 'react';
5
+ import {
6
+ AlertTriangle,
7
+ Bot,
8
+ CheckCircle2,
9
+ Clock,
10
+ Download,
11
+ FileCode,
12
+ FileText,
13
+ MoreVertical,
14
+ Play,
15
+ Trash2,
16
+ Upload,
17
+ X,
18
+ XCircle,
19
+ Zap,
20
+ } from 'lucide-react';
21
+ import { useAccount } from 'wagmi';
22
+ import { parseURL } from '@/lib/url-parser';
23
+ import { useGitHubLoader } from '@/hooks/useGitHubLoader';
24
+ import { GitHubLoadingDialog } from '@/components/github/GitHubLoadingDialog';
25
+ import { SetupGuide } from '@/components/setup/SetupGuide';
26
+ import { MonacoEditor } from '@/components/editor/MonacoEditor';
27
+ import { ABIDialog } from '@/components/abi/ABIDialog';
28
+ import { ConnectButton } from '@/components/wallet/ConnectButton';
29
+ import { FaucetButton } from '@/components/wallet/FaucetButton';
30
+ import { Button } from '@/components/ui/button';
31
+ import {
32
+ DropdownMenu,
33
+ DropdownMenuContent,
34
+ DropdownMenuItem,
35
+ DropdownMenuTrigger,
36
+ } from '@/components/ui/dropdown-menu';
37
+ import { DeployDialog } from '@/components/deploy/DeployDialog';
38
+ import { ContractInteraction } from '@/components/interact/ContractInteraction';
39
+ import { ContractPlaceholder } from '@/components/interact/ContractPlaceholder';
40
+ import { FileTabs } from '@/components/editor/FileTabs';
41
+ import { ChatPanel } from '@/components/ai/ChatPanel';
42
+ import { KeyboardShortcutHint } from '@/components/ui/KeyboardShortcutHint';
43
+ import { BenchmarkDialog } from '@/components/orbit/BenchmarkDialog';
44
+ import { OrbitExplorer } from '@/components/orbit/OrbitExplorer';
45
+ import { ProjectActions } from '@/components/project/ProjectActions';
46
+ import { getFileByPath, buildFileTree } from '@/lib/project-manager';
47
+ import { FileTree } from '@/components/file-tree/FileTree';
48
+ import { useProjectState } from '@/hooks/useProjectState';
49
+ import { templates, getTemplate } from '@/lib/templates';
50
+ import { stripAnsiCodes, formatCompilationTime } from '@/lib/output-formatter';
51
+ import { useCompilation } from '@/hooks/useCompilation';
52
+ import { useFileTabs } from '@/hooks/useFileTabs';
53
+ import { usePanelState } from '@/hooks/usePanelState';
54
+ import { useKeyboardShortcuts } from '@/hooks/useKeyboardShortcuts';
55
+ import { useResponsive } from '@/hooks/useResponsive';
56
+ import { ProjectState } from '@/types/project';
57
+ import { toast } from 'sonner';
58
+ import { LoadFromGitHubDialog } from '@/components/github/LoadFromGitHubDialog';
59
+ import { FileTreeSkeleton } from '@/components/file-tree/FileTreeSkeleton';
60
+ import { GitHubMetadataBanner } from '@/components/github/GitHubMetadataBanner';
61
+ import { useBlockchainLoader } from '@/hooks/useBlockchainLoader';
62
+ import { BlockchainLoadingDialog } from '@/components/blockchain/BlockchainLoadingDialog';
63
+ import { ContractInteractionData } from '@/types/blockchain';
64
+ import { BlockchainContractBanner } from '@/components/blockchain/BlockchainContractBanner';
65
+
66
+ const DEFAULT_CODE = `// Welcome to Stylus IDE - Multi-File Project
67
+
68
+ #![cfg_attr(not(feature = "export-abi"), no_main)]
69
+ extern crate alloc;
70
+
71
+ use stylus_sdk::prelude::*;
72
+ use stylus_sdk::alloy_primitives::U256;
73
+
74
+ sol_storage! {
75
+ #[entrypoint]
76
+ pub struct Counter {
77
+ uint256 count;
78
+ }
79
+ }
80
+
81
+ #[public]
82
+ impl Counter {
83
+ pub fn get(&self) -> U256 {
84
+ self.count.get()
85
+ }
86
+
87
+ pub fn increment(&mut self) {
88
+ let count = self.count.get();
89
+ self.count.set(count + U256::from(1));
90
+ }
91
+ }
92
+ `;
93
+
94
+ export default function HomePage() {
95
+ const {
96
+ showAIPanel,
97
+ showContractPanel,
98
+ showOutput,
99
+ isAIPanelCollapsed,
100
+ isContractPanelCollapsed,
101
+ setShowAIPanel,
102
+ setShowContractPanel,
103
+ setShowOutput,
104
+ toggleAIPanel,
105
+ toggleContractPanel,
106
+ toggleOutput,
107
+ toggleAIPanelCollapse,
108
+ toggleContractPanelCollapse,
109
+ } = usePanelState();
110
+
111
+ const { isMobile, isDesktop } = useResponsive();
112
+
113
+ // NEW: Project state management for multi-file support
114
+ const {
115
+ project,
116
+ setProject, // ✅ ADD THIS
117
+ setActive,
118
+ toggleFolder,
119
+ updateContent,
120
+ createNewFile,
121
+ createNewFolder,
122
+ getCurrentFile,
123
+ removeFile,
124
+ removeFolder,
125
+ rename,
126
+ duplicateFile,
127
+ manualSave, // ✅ ADD THIS
128
+ resetProject, // ✅ ADD THIS
129
+ } = useProjectState();
130
+
131
+ const {
132
+ tabs,
133
+ activeTabId,
134
+ activeTab,
135
+ addTab,
136
+ removeTab,
137
+ updateTabContent,
138
+ setActiveTab,
139
+ renameTab,
140
+ openOrActivateTab,
141
+ getTabByPath,
142
+ } = useFileTabs(DEFAULT_CODE);
143
+
144
+ const [showABIDialog, setShowABIDialog] = useState(false);
145
+ const [abiData, setAbiData] = useState<{ abi: string | null; solidity: string | null }>({
146
+ abi: null,
147
+ solidity: null,
148
+ });
149
+ const [isExportingABI, setIsExportingABI] = useState(false);
150
+ const [showDeployDialog, setShowDeployDialog] = useState(false);
151
+ const [deployedContracts, setDeployedContracts] = useState<Array<{ address: string; txHash?: string }>>(
152
+ []
153
+ );
154
+
155
+ const { isConnected } = useAccount();
156
+
157
+ const [showBenchmarkDialog, setShowBenchmarkDialog] = useState(false);
158
+ const { isCompiling, output, errors, compilationTime, sessionId, compile, clearOutput } =
159
+ useCompilation();
160
+
161
+ const [parsedAbi, setParsedAbi] = useState<any>(null);
162
+ const { isLoading: isLoadingGitHub, progress: githubProgress, loadFromGitHub } = useGitHubLoader();
163
+ const [showGitHubDialog, setShowGitHubDialog] = useState(false);
164
+ const { isLoading: isLoadingBlockchain, progress: blockchainProgress, loadFromBlockchain } = useBlockchainLoader();
165
+ const [showBlockchainDialog, setShowBlockchainDialog] = useState(false);
166
+ const [loadedContract, setLoadedContract] = useState<ContractInteractionData | null>(null);
167
+
168
+ const [workspaceTab, setWorkspaceTab] = useState<
169
+ 'editor' | 'orbit' | 'ml' | 'qlearning' | 'raytracing'
170
+ >('editor');
171
+
172
+ const [mounted, setMounted] = useState(false);
173
+ useEffect(() => setMounted(true), []);
174
+ const mobile = mounted ? isMobile : false;
175
+ const desktop = mounted ? isDesktop : true;
176
+
177
+ useEffect(() => {
178
+ if (!mounted) return;
179
+ const anyMobileOverlayOpen = mobile && (showAIPanel || showContractPanel);
180
+ const prev = document.body.style.overflow;
181
+ document.body.style.overflow = anyMobileOverlayOpen ? 'hidden' : '';
182
+ return () => {
183
+ document.body.style.overflow = prev;
184
+ };
185
+ }, [mounted, mobile, showAIPanel, showContractPanel]);
186
+
187
+ // NEW: Check URL on mount
188
+ useEffect(() => {
189
+ if (typeof window === 'undefined') return;
190
+
191
+ const searchParams = new URLSearchParams(window.location.search);
192
+ const url = searchParams.get('url');
193
+
194
+ if (url) {
195
+ // ✅ UPDATED: Build full URL with query params
196
+ const branch = searchParams.get('branch');
197
+ const file = searchParams.get('file');
198
+ const path = searchParams.get('path');
199
+
200
+ let fullUrl = url;
201
+ const params = new URLSearchParams();
202
+
203
+ if (branch) params.set('branch', branch);
204
+ if (file) params.set('file', file);
205
+ if (path) params.set('path', path);
206
+
207
+ if (params.toString()) {
208
+ fullUrl += (url.includes('?') ? '&' : '?') + params.toString();
209
+ }
210
+
211
+ handleURLLoad(fullUrl);
212
+ }
213
+ }, []);
214
+
215
+ useEffect(() => {
216
+ if (abiData.abi) {
217
+ try {
218
+ const parsed = JSON.parse(abiData.abi);
219
+ setParsedAbi(parsed);
220
+ } catch (e) {
221
+ console.error('Failed to parse ABI:', e);
222
+ setParsedAbi(null);
223
+ }
224
+ } else {
225
+ setParsedAbi(null);
226
+ }
227
+ }, [abiData.abi]);
228
+
229
+ // Update the existing handleURLLoad function
230
+ // Update the existing handleURLLoad function
231
+ const handleURLLoad = async (url: string) => {
232
+ const parsed = parseURL(url);
233
+
234
+ if (parsed.type === 'github') {
235
+ setShowGitHubDialog(true);
236
+
237
+ try {
238
+ const project = await loadFromGitHub(parsed);
239
+
240
+ if (project) {
241
+ // Load project into IDE
242
+ setProject(project);
243
+
244
+ // Build file tree structure from files
245
+ const updatedProject = {
246
+ ...project,
247
+ structure: buildFileTree(project.files),
248
+ };
249
+ setProject(updatedProject);
250
+
251
+ // Open first Rust file in tab
252
+ const firstRustFile = project.files.find((f) => f.language === 'rust' && f.isOpen);
253
+ if (firstRustFile) {
254
+ openOrActivateTab(
255
+ firstRustFile.path,
256
+ firstRustFile.name,
257
+ firstRustFile.content,
258
+ firstRustFile.language
259
+ );
260
+ }
261
+
262
+ // Show success toast
263
+ toast.success('Repository loaded!', {
264
+ description: `Loaded ${project.files.length} files from ${parsed.owner}/${parsed.repo}`,
265
+ });
266
+
267
+ // Clean URL after successful load
268
+ if (typeof window !== 'undefined') {
269
+ window.history.replaceState({}, '', window.location.pathname);
270
+ }
271
+
272
+ // Close dialog after 1.5 seconds
273
+ setTimeout(() => {
274
+ setShowGitHubDialog(false);
275
+ }, 1500);
276
+ }
277
+ } catch (error) {
278
+ const errorMessage = error instanceof Error ? error.message : 'Failed to load repository';
279
+ toast.error('Failed to load repository', {
280
+ description: errorMessage,
281
+ });
282
+ }
283
+ } else if (parsed.type === 'blockchain') {
284
+ // ✅ NEW: Load contract for interaction (not editor)
285
+ setShowBlockchainDialog(true);
286
+
287
+ try {
288
+ const contractData = await loadFromBlockchain(parsed);
289
+
290
+ if (contractData) {
291
+ // Store contract data
292
+ setLoadedContract(contractData);
293
+
294
+ // Parse ABI
295
+ let parsedAbi;
296
+ try {
297
+ parsedAbi = JSON.parse(contractData.abi);
298
+ } catch (e) {
299
+ console.error('Failed to parse ABI:', e);
300
+ throw new Error('Invalid ABI format');
301
+ }
302
+
303
+ // Set ABI in state
304
+ setAbiData({ abi: contractData.abi, solidity: '' });
305
+
306
+ // Add to deployed contracts list
307
+ setDeployedContracts([
308
+ {
309
+ address: contractData.address,
310
+ txHash: `loaded-from-${contractData.chain}`, // Optional: helps identify source
311
+ }
312
+ ]);
313
+
314
+ // Open contract interaction panel
315
+ setShowContractPanel(true);
316
+
317
+ // Show success toast
318
+ toast.success('Contract loaded!', {
319
+ description: `${contractData.name} is ready for interaction`,
320
+ });
321
+
322
+ // Clean URL after successful load
323
+ if (typeof window !== 'undefined') {
324
+ window.history.replaceState({}, '', window.location.pathname);
325
+ }
326
+
327
+ // Close dialog after 1.5 seconds
328
+ setTimeout(() => {
329
+ setShowBlockchainDialog(false);
330
+ }, 1500);
331
+ }
332
+ } catch (error) {
333
+ const errorMessage = error instanceof Error ? error.message : 'Failed to load contract';
334
+ toast.error('Failed to load contract', {
335
+ description: errorMessage,
336
+ });
337
+ }
338
+ } else {
339
+ toast.error('Invalid URL', {
340
+ description: 'Please use a GitHub repository or blockchain explorer URL',
341
+ });
342
+ }
343
+ };
344
+
345
+ // NEW: Sync file tree clicks with tabs
346
+ const handleFileClick = (path: string) => {
347
+ const file = getFileByPath(project, path);
348
+ if (!file) return;
349
+
350
+ // Open or activate tab
351
+ openOrActivateTab(path, file.name, file.content, file.language);
352
+
353
+ // Update project active file
354
+ setActive(path);
355
+ };
356
+
357
+ // NEW: Sync tab content changes with project
358
+ useEffect(() => {
359
+ if (activeTab && project.activeFilePath === activeTab.path) {
360
+ const projectFile = getFileByPath(project, activeTab.path);
361
+ if (projectFile && projectFile.content !== activeTab.content) {
362
+ // Update project content when tab content changes
363
+ updateContent(activeTab.path, activeTab.content);
364
+ }
365
+ }
366
+ }, [activeTab?.content, project.activeFilePath, activeTab?.path, updateContent, project]);
367
+
368
+ const compilationStatus = useMemo(() => {
369
+ if (isCompiling) return 'compiling';
370
+ if (output.some((o) => o.type === 'complete' && o.data.includes('successful'))) return 'success';
371
+ if (output.some((o) => o.type === 'error' || o.type === 'complete')) return 'error';
372
+ return 'idle';
373
+ }, [isCompiling, output]);
374
+
375
+ const canExportAbi = !!sessionId && compilationStatus === 'success' && !isExportingABI;
376
+ const canDeploy = !!sessionId && compilationStatus === 'success' && isConnected;
377
+
378
+ const handleCompile = async () => {
379
+ if (!activeTab) return;
380
+ await compile(activeTab.content, true);
381
+
382
+ if (desktop || !showAIPanel) {
383
+ setShowOutput(true);
384
+ }
385
+ };
386
+
387
+ const handleSave = () => {
388
+ handleCompile();
389
+ };
390
+
391
+ useKeyboardShortcuts({
392
+ onToggleAI: () => {
393
+ if (mobile) {
394
+ setShowContractPanel(false);
395
+ toggleAIPanel();
396
+ return;
397
+ }
398
+ toggleAIPanelCollapse();
399
+ },
400
+ onToggleOutput: toggleOutput,
401
+ onCompile: handleCompile,
402
+ });
403
+
404
+ const handleLoadTemplate = (templateId: string) => {
405
+ const template = getTemplate(templateId);
406
+ if (template) {
407
+ if (activeTab) {
408
+ updateTabContent(activeTab.id, template.code);
409
+ }
410
+ clearOutput();
411
+ setWorkspaceTab('editor');
412
+ if (mobile) {
413
+ setShowAIPanel(false);
414
+ setShowContractPanel(false);
415
+ }
416
+ }
417
+ };
418
+
419
+ const handleNewFile = (type: 'rust' | 'toml' | 'markdown') => {
420
+ const extensions = { rust: '.rs', toml: '.toml', markdown: '.md' } as const;
421
+ const languages = { rust: 'rust', toml: 'toml', markdown: 'markdown' } as const;
422
+
423
+ const count = tabs.filter((t) => t.name.includes(extensions[type])).length;
424
+ const name = `new_file_${count + 1}${extensions[type]}`;
425
+
426
+ addTab(name, '', languages[type]);
427
+ setWorkspaceTab('editor');
428
+
429
+ if (mobile) {
430
+ setShowAIPanel(false);
431
+ setShowContractPanel(false);
432
+ }
433
+ };
434
+
435
+ const handleDeploySuccess = (contractAddress: string, txHash?: string) => {
436
+ setDeployedContracts((prev) => [...prev, { address: contractAddress, txHash }]);
437
+ if (mobile) {
438
+ setShowAIPanel(false);
439
+ setShowContractPanel(true);
440
+ }
441
+ };
442
+
443
+ const handleExportABI = async () => {
444
+ if (!sessionId) {
445
+ alert('Please compile your contract first');
446
+ return;
447
+ }
448
+
449
+ setIsExportingABI(true);
450
+ try {
451
+ const response = await fetch('/api/export-abi', {
452
+ method: 'POST',
453
+ headers: { 'Content-Type': 'application/json' },
454
+ body: JSON.stringify({ sessionId }),
455
+ });
456
+
457
+ const result = await response.json();
458
+
459
+ if (result.success) {
460
+ setAbiData({
461
+ abi: result.abi || null,
462
+ solidity: result.solidity || null,
463
+ });
464
+ setShowABIDialog(true);
465
+ } else {
466
+ if (result.details && result.details.includes('solc not found')) {
467
+ alert(
468
+ 'solc (Solidity compiler) is required for ABI export.\n\n' +
469
+ 'Install it:\n' +
470
+ '• macOS: brew install solidity\n' +
471
+ '• Linux: sudo apt-get install solc\n' +
472
+ '• Windows: Download from github.com/ethereum/solidity/releases\n\n' +
473
+ 'Then try exporting again.'
474
+ );
475
+ } else {
476
+ const errorMsg = result.details
477
+ ? `${result.error}\n\nDetails:\n${result.details}`
478
+ : result.error || 'Unknown error';
479
+
480
+ console.error('ABI Export Error:', errorMsg);
481
+ alert(`ABI export failed:\n${errorMsg}`);
482
+ }
483
+ }
484
+ } catch (error) {
485
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
486
+ console.error('ABI Export Error:', errorMsg);
487
+ alert(`ABI export failed: ${errorMsg}`);
488
+ } finally {
489
+ setIsExportingABI(false);
490
+ }
491
+ };
492
+
493
+ const getOutputIcon = (type: string) => {
494
+ switch (type) {
495
+ case 'error':
496
+ return <XCircle className="h-3 w-3 shrink-0" />;
497
+ case 'stderr':
498
+ return <AlertTriangle className="h-3 w-3 shrink-0" />;
499
+ case 'complete':
500
+ return <CheckCircle2 className="h-3 w-3 shrink-0" />;
501
+ default:
502
+ return null;
503
+ }
504
+ };
505
+
506
+ const getOutputColor = (type: string) => {
507
+ switch (type) {
508
+ case 'error':
509
+ return 'text-red-400';
510
+ case 'stderr':
511
+ return 'text-yellow-400';
512
+ case 'complete':
513
+ return 'text-green-400';
514
+ default:
515
+ return 'text-muted-foreground';
516
+ }
517
+ };
518
+
519
+ const handleToggleAI = () => {
520
+ if (mobile) {
521
+ setShowContractPanel(false);
522
+ setShowAIPanel(!showAIPanel);
523
+ return;
524
+ }
525
+ toggleAIPanelCollapse();
526
+ };
527
+
528
+ const handleToggleContract = () => {
529
+ if (mobile) {
530
+ setShowAIPanel(false);
531
+ setShowContractPanel(!showContractPanel);
532
+ return;
533
+ }
534
+ toggleContractPanelCollapse();
535
+ };
536
+
537
+ const closeMobilePanels = () => {
538
+ setShowAIPanel(false);
539
+ setShowContractPanel(false);
540
+ };
541
+
542
+ const handleImportProject = useCallback((importedProject: ProjectState) => {
543
+ setProject(importedProject);
544
+ // Clear tabs and reload
545
+ window.location.reload();
546
+ }, [setProject]);
547
+
548
+ return (
549
+ <>
550
+ <SetupGuide />
551
+
552
+ {/* NEW: GitHub Loading Dialog */}
553
+ <GitHubLoadingDialog
554
+ open={showGitHubDialog}
555
+ onOpenChange={setShowGitHubDialog}
556
+ progress={githubProgress}
557
+ onRetry={() => {
558
+ const searchParams = new URLSearchParams(window.location.search);
559
+ const url = searchParams.get('url');
560
+ if (url) handleURLLoad(url);
561
+ }}
562
+ />
563
+
564
+ {/* ✅ NEW: Blockchain Loading Dialog */}
565
+ <BlockchainLoadingDialog
566
+ open={showBlockchainDialog}
567
+ onOpenChange={setShowBlockchainDialog}
568
+ progress={blockchainProgress}
569
+ onRetry={() => {
570
+ const searchParams = new URLSearchParams(window.location.search);
571
+ const url = searchParams.get('url');
572
+ if (url) handleURLLoad(url);
573
+ }}
574
+ />
575
+
576
+ <ABIDialog
577
+ open={showABIDialog}
578
+ onOpenChange={setShowABIDialog}
579
+ abi={abiData.abi}
580
+ solidity={abiData.solidity}
581
+ />
582
+
583
+ <DeployDialog
584
+ open={showDeployDialog}
585
+ onOpenChange={setShowDeployDialog}
586
+ sessionId={sessionId}
587
+ onDeploySuccess={handleDeploySuccess}
588
+ />
589
+
590
+ <main className="h-screen flex flex-col bg-background">
591
+ {/* Header */}
592
+ <header className="border-b border-border px-3 py-2 md:px-4 md:py-0 md:h-14 flex items-center justify-between gap-2 flex-wrap">
593
+ <div className="min-w-0 flex items-center gap-2">
594
+ <Link
595
+ href="/"
596
+ className="text-lg md:text-xl font-bold text-primary hover:opacity-80 truncate"
597
+ >
598
+ Stylus IDE
599
+ </Link>
600
+ <div className="md:hidden flex items-center gap-2">
601
+ {compilationStatus === 'success' && <CheckCircle2 className="h-4 w-4 text-green-400" />}
602
+ {compilationStatus === 'error' && <XCircle className="h-4 w-4 text-red-400" />}
603
+ {compilationStatus === 'compiling' && (
604
+ <span className="text-xs text-blue-400 animate-pulse">Compiling…</span>
605
+ )}
606
+ </div>
607
+ </div>
608
+
609
+ <div className="flex items-center gap-2 flex-wrap justify-end">
610
+ <ConnectButton />
611
+
612
+ <FaucetButton />
613
+ {/* NEW: Load from GitHub Button */}
614
+ <LoadFromGitHubDialog
615
+ onLoadURL={handleURLLoad}
616
+ isLoading={isLoadingGitHub}
617
+ />
618
+ <ProjectActions
619
+ project={project}
620
+ onImport={handleImportProject}
621
+ onReset={resetProject}
622
+ onSave={manualSave}
623
+ />
624
+
625
+ <Button onClick={handleCompile} disabled={isCompiling} size="sm" className="hidden sm:flex">
626
+ <Play className="h-4 w-4 mr-2" />
627
+ {isCompiling ? 'Compiling...' : 'Compile'}
628
+ </Button>
629
+
630
+ {abiData.abi && (
631
+ <Button
632
+ variant="outline"
633
+ size="sm"
634
+ onClick={() => setShowBenchmarkDialog(true)}
635
+ className="hidden lg:flex"
636
+ >
637
+ <Zap className="h-4 w-4 mr-2" />
638
+ Benchmark Orbit
639
+ </Button>
640
+ )}
641
+
642
+ <Button
643
+ onClick={handleExportABI}
644
+ disabled={!canExportAbi}
645
+ size="sm"
646
+ variant="outline"
647
+ className="hidden sm:flex"
648
+ >
649
+ <Download className="h-4 w-4 mr-2" />
650
+ {isExportingABI ? 'Exporting...' : 'Export ABI'}
651
+ </Button>
652
+
653
+ <Button
654
+ onClick={() => setShowDeployDialog(true)}
655
+ disabled={!canDeploy}
656
+ size="sm"
657
+ className="hidden sm:flex"
658
+ >
659
+ <Upload className="h-4 w-4 mr-2" />
660
+ Deploy
661
+ </Button>
662
+
663
+ <DropdownMenu>
664
+ <DropdownMenuTrigger asChild>
665
+ <Button variant="outline" size="sm" className="hidden md:flex">
666
+ <FileCode className="h-4 w-4 mr-2" />
667
+ Templates
668
+ </Button>
669
+ </DropdownMenuTrigger>
670
+ <DropdownMenuContent align="end">
671
+ {templates.map((template) => (
672
+ <DropdownMenuItem key={template.id} onClick={() => handleLoadTemplate(template.id)}>
673
+ <div>
674
+ <div className="font-medium">{template.name}</div>
675
+ <div className="text-xs text-muted-foreground">{template.description}</div>
676
+ </div>
677
+ </DropdownMenuItem>
678
+ ))}
679
+ </DropdownMenuContent>
680
+ </DropdownMenu>
681
+
682
+ <DropdownMenu>
683
+ <DropdownMenuTrigger asChild>
684
+ <Button variant="outline" size="icon" className="sm:hidden" aria-label="Actions">
685
+ <MoreVertical className="h-4 w-4" />
686
+ </Button>
687
+ </DropdownMenuTrigger>
688
+ <DropdownMenuContent align="end" className="w-72">
689
+ <DropdownMenuItem onClick={handleCompile} disabled={isCompiling || !activeTab}>
690
+ <Play className="h-4 w-4 mr-2" />
691
+ {isCompiling ? 'Compiling...' : 'Compile'}
692
+ </DropdownMenuItem>
693
+
694
+ <DropdownMenuItem onClick={handleExportABI} disabled={!canExportAbi}>
695
+ <Download className="h-4 w-4 mr-2" />
696
+ {isExportingABI ? 'Exporting ABI…' : 'Export ABI'}
697
+ </DropdownMenuItem>
698
+
699
+ <DropdownMenuItem onClick={() => setShowDeployDialog(true)} disabled={!canDeploy}>
700
+ <Upload className="h-4 w-4 mr-2" />
701
+ Deploy
702
+ </DropdownMenuItem>
703
+
704
+ {abiData.abi && (
705
+ <DropdownMenuItem onClick={() => setShowBenchmarkDialog(true)}>
706
+ <Zap className="h-4 w-4 mr-2" />
707
+ Benchmark Orbit
708
+ </DropdownMenuItem>
709
+ )}
710
+
711
+ <div className="px-2 py-1.5 text-xs text-muted-foreground">Templates</div>
712
+ {templates.map((template) => (
713
+ <DropdownMenuItem key={template.id} onClick={() => handleLoadTemplate(template.id)}>
714
+ <div className="min-w-0">
715
+ <div className="font-medium truncate">{template.name}</div>
716
+ <div className="text-xs text-muted-foreground truncate">{template.description}</div>
717
+ </div>
718
+ </DropdownMenuItem>
719
+ ))}
720
+ </DropdownMenuContent>
721
+ </DropdownMenu>
722
+
723
+ <Button
724
+ variant="ghost"
725
+ size="icon"
726
+ className="focus-visible-ring"
727
+ onClick={handleToggleAI}
728
+ aria-label="Toggle AI assistant"
729
+ >
730
+ <Bot className="h-5 w-5" />
731
+ </Button>
732
+
733
+ <Button
734
+ variant="ghost"
735
+ size="icon"
736
+ className="focus-visible-ring"
737
+ onClick={handleToggleContract}
738
+ aria-label="Toggle contract panel"
739
+ >
740
+ <FileText className="h-5 w-5" />
741
+ </Button>
742
+ </div>
743
+ </header>
744
+
745
+ {/* Workspace Tabs Bar */}
746
+ <div className="h-10 border-b border-border bg-card flex items-center px-2 sm:px-4 gap-2 overflow-x-auto whitespace-nowrap">
747
+ <Button
748
+ size="sm"
749
+ variant={workspaceTab === 'editor' ? 'secondary' : 'ghost'}
750
+ onClick={() => setWorkspaceTab('editor')}
751
+ className="h-7 shrink-0"
752
+ >
753
+ Editor
754
+ </Button>
755
+ <Button
756
+ size="sm"
757
+ variant={workspaceTab === 'orbit' ? 'secondary' : 'ghost'}
758
+ onClick={() => setWorkspaceTab('orbit')}
759
+ className="h-7 shrink-0"
760
+ >
761
+ Orbit Explorer
762
+ </Button>
763
+ <Button
764
+ size="sm"
765
+ variant={workspaceTab === 'ml' ? 'secondary' : 'ghost'}
766
+ onClick={() => setWorkspaceTab('ml')}
767
+ className="h-7 shrink-0"
768
+ >
769
+ On-chain ML
770
+ </Button>
771
+ <Button
772
+ size="sm"
773
+ variant={workspaceTab === 'qlearning' ? 'secondary' : 'ghost'}
774
+ onClick={() => setWorkspaceTab('qlearning')}
775
+ className="h-7 shrink-0"
776
+ >
777
+ Q-Learning
778
+ </Button>
779
+ <Button
780
+ size="sm"
781
+ variant={workspaceTab === 'raytracing' ? 'secondary' : 'ghost'}
782
+ onClick={() => setWorkspaceTab('raytracing')}
783
+ className="h-7 shrink-0"
784
+ >
785
+ Raytracing
786
+ </Button>
787
+ </div>
788
+
789
+ {/* NEW: GitHub Metadata Banner */}
790
+ {project.metadata?.source === 'github' &&
791
+ project.metadata.owner &&
792
+ project.metadata.repo && (
793
+ <GitHubMetadataBanner
794
+ owner={project.metadata.owner}
795
+ repo={project.metadata.repo}
796
+ branch={project.metadata.branch || 'main'}
797
+ url={project.metadata.url || `https://github.com/${project.metadata.owner}/${project.metadata.repo}`}
798
+ folderPath={project.metadata.folderPath} // ✅ ADD THIS
799
+ />
800
+ )}
801
+
802
+ {/* ✅ NEW: Blockchain Contract Banner */}
803
+ {loadedContract && (
804
+ <BlockchainContractBanner
805
+ address={loadedContract.address}
806
+ name={loadedContract.name}
807
+ chain={loadedContract.chain}
808
+ verified={loadedContract.verified}
809
+ explorerUrl={loadedContract.explorerUrl}
810
+ compiler={loadedContract.compiler}
811
+ />
812
+ )}
813
+
814
+ {/* Main Content Area */}
815
+ <div className="flex-1 flex flex-col lg:flex-row overflow-hidden">
816
+ <div className="flex-1 flex flex-col min-w-0 relative">
817
+ {workspaceTab === 'orbit' ? (
818
+ <section className="flex-1 min-h-0 overflow-auto bg-muted/30">
819
+ <div className="p-3 sm:p-6">
820
+ <OrbitExplorer />
821
+ </div>
822
+ </section>
823
+ ) : workspaceTab === 'ml' ? (
824
+ <section className="flex-1 min-h-0 bg-background">
825
+ <iframe title="On-chain ML Inference" src="/ml" className="w-full h-full border-0" />
826
+ </section>
827
+ ) : workspaceTab === 'qlearning' ? (
828
+ <section className="flex-1 min-h-0 bg-background">
829
+ <iframe title="Q-Learning" src="/qlearning" className="w-full h-full border-0" />
830
+ </section>
831
+ ) : workspaceTab === 'raytracing' ? (
832
+ <section className="flex-1 min-h-0 bg-background">
833
+ <iframe title="Raytracing" src="/raytracing" className="w-full h-full border-0" />
834
+ </section>
835
+ ) : (
836
+ <>
837
+ {/* NEW: File Tree + Editor Layout */}
838
+ <div className="flex-1 flex min-h-0">
839
+ {/* ✅ UPDATED: File Tree Sidebar with Loading Skeleton */}
840
+ <div className="hidden md:block w-64 border-r border-border">
841
+ {isLoadingGitHub ? (
842
+ <FileTreeSkeleton />
843
+ ) : (
844
+ <FileTree
845
+ structure={project.structure}
846
+ activeFilePath={project.activeFilePath}
847
+ onFileClick={handleFileClick}
848
+ onFolderToggle={toggleFolder}
849
+ onNewFile={(path) => {
850
+ createNewFile(path || 'new_file.rs');
851
+ }}
852
+ onNewFolder={(path) => {
853
+ createNewFolder(path || 'new_folder');
854
+ }}
855
+ onRename={(oldPath, newName) => {
856
+ const pathParts = oldPath.split('/');
857
+ pathParts[pathParts.length - 1] = newName;
858
+ const newPath = pathParts.join('/');
859
+ rename(oldPath, newPath);
860
+ }}
861
+ onDuplicate={(path) => {
862
+ duplicateFile(path);
863
+ }}
864
+ onDelete={(path, isFolder) => {
865
+ if (isFolder) {
866
+ removeFolder(path);
867
+ } else {
868
+ removeFile(path);
869
+ }
870
+ }}
871
+ />
872
+ )}
873
+ </div>
874
+
875
+ {/* Editor Section */}
876
+ <section className="flex-1 flex flex-col min-w-0">
877
+ <FileTabs
878
+ tabs={tabs}
879
+ activeTabId={activeTabId}
880
+ onTabClick={setActiveTab}
881
+ onTabClose={removeTab}
882
+ onNewFile={handleNewFile}
883
+ onRenameTab={renameTab}
884
+ />
885
+
886
+ <div className="h-10 border-b border-border flex items-center justify-between px-2 sm:px-4 gap-2">
887
+ <div className="flex items-center gap-2 overflow-x-auto">
888
+ <span className="text-xs text-muted-foreground shrink-0">
889
+ {tabs.length} file{tabs.length !== 1 ? 's' : ''}
890
+ </span>
891
+
892
+ {errors.length > 0 && (
893
+ <span className="text-xs bg-red-500/20 text-red-400 px-2 py-0.5 rounded-full shrink-0">
894
+ {errors.length} {errors.length === 1 ? 'error' : 'errors'}
895
+ </span>
896
+ )}
897
+
898
+ {compilationTime !== null && (
899
+ <span className="hidden sm:flex text-xs text-muted-foreground items-center gap-1 shrink-0">
900
+ <Clock className="h-3 w-3" />
901
+ {formatCompilationTime(compilationTime)}
902
+ </span>
903
+ )}
904
+ </div>
905
+
906
+ <div className="flex items-center gap-2 shrink-0">
907
+ <Button
908
+ onClick={handleCompile}
909
+ disabled={isCompiling || !activeTab}
910
+ size="sm"
911
+ className="sm:hidden h-7"
912
+ aria-label="Compile"
913
+ title="Compile"
914
+ >
915
+ <Play className="h-3 w-3" />
916
+ </Button>
917
+
918
+ <Button
919
+ variant="ghost"
920
+ size="sm"
921
+ className="sm:hidden h-7"
922
+ onClick={toggleOutput}
923
+ aria-label={showOutput ? 'Hide output panel' : 'Show output panel'}
924
+ >
925
+ Output {showOutput ? '−' : '+'}
926
+ </Button>
927
+ </div>
928
+ </div>
929
+
930
+ <div className="flex-1 bg-card transition-all duration-300 min-h-0">
931
+ {activeTab ? (
932
+ <MonacoEditor
933
+ key={activeTab.id}
934
+ value={activeTab.content}
935
+ onChange={(value) => updateTabContent(activeTab.id, value)}
936
+ onSave={handleSave}
937
+ readOnly={isCompiling}
938
+ errors={errors}
939
+ />
940
+ ) : (
941
+ <div className="h-full flex items-center justify-center text-muted-foreground">
942
+ No file open
943
+ </div>
944
+ )}
945
+ </div>
946
+ </section>
947
+ </div>
948
+
949
+ {/* Bottom Panel - Output */}
950
+ <section
951
+ className={`
952
+ border-t border-border flex flex-col bg-card
953
+ transition-all duration-300 ease-in-out
954
+ ${showOutput ? 'h-40 sm:h-48 md:h-56 xl:h-64' : 'h-12'}
955
+ `}
956
+ >
957
+ <div className="h-12 border-b border-border flex items-center justify-between px-2 sm:px-4">
958
+ <div className="flex items-center gap-2 sm:gap-3 overflow-x-auto min-w-0">
959
+ <button
960
+ className="text-sm font-medium cursor-pointer flex items-center gap-2 hover:text-primary transition-colors shrink-0"
961
+ onClick={toggleOutput}
962
+ aria-label={showOutput ? 'Hide output panel' : 'Show output panel'}
963
+ >
964
+ <span className="hidden sm:inline">Output</span>
965
+ <span className="sm:hidden">Out</span>
966
+ {compilationStatus === 'success' && (
967
+ <CheckCircle2 className="h-4 w-4 text-green-400" />
968
+ )}
969
+ {compilationStatus === 'error' && <XCircle className="h-4 w-4 text-red-400" />}
970
+ </button>
971
+
972
+ {output.length > 0 && (
973
+ <span className="text-xs bg-primary/20 text-primary px-2 py-0.5 rounded-full shrink-0">
974
+ {output.length}
975
+ </span>
976
+ )}
977
+
978
+ {compilationTime !== null && (
979
+ <span className="hidden sm:flex text-xs text-muted-foreground items-center gap-1 shrink-0">
980
+ <Clock className="h-3 w-3" />
981
+ {formatCompilationTime(compilationTime)}
982
+ </span>
983
+ )}
984
+ </div>
985
+
986
+ <div className="flex items-center gap-1 sm:gap-2 shrink-0">
987
+ {output.length > 0 && (
988
+ <Button
989
+ variant="ghost"
990
+ size="sm"
991
+ onClick={clearOutput}
992
+ className="h-8 px-2 sm:px-3"
993
+ aria-label="Clear output"
994
+ >
995
+ <Trash2 className="h-3 w-3 sm:mr-1" />
996
+ <span className="hidden sm:inline">Clear</span>
997
+ </Button>
998
+ )}
999
+
1000
+ <Button
1001
+ variant="ghost"
1002
+ size="sm"
1003
+ onClick={toggleOutput}
1004
+ className="h-8 px-2"
1005
+ aria-label={showOutput ? 'Collapse output' : 'Expand output'}
1006
+ >
1007
+ {showOutput ? '−' : '+'}
1008
+ </Button>
1009
+ </div>
1010
+ </div>
1011
+
1012
+ {showOutput && (
1013
+ <div className="flex-1 overflow-auto p-2 sm:p-4 min-h-0 font-mono text-xs space-y-1 custom-scrollbar">
1014
+ {output.length === 0 && !isCompiling && (
1015
+ <p className="text-muted-foreground">Ready to compile. Press Compile or Cmd/Ctrl+S</p>
1016
+ )}
1017
+ {isCompiling && output.length === 0 && (
1018
+ <p className="text-blue-400 animate-pulse">Starting compilation...</p>
1019
+ )}
1020
+ {output.map((item, index) => (
1021
+ <div key={index} className={`flex items-start gap-2 ${getOutputColor(item.type)}`}>
1022
+ {getOutputIcon(item.type)}
1023
+ <span className="flex-1 whitespace-pre-wrap break-anywhere">
1024
+ {stripAnsiCodes(item.data)}
1025
+ </span>
1026
+ </div>
1027
+ ))}
1028
+ </div>
1029
+ )}
1030
+ </section>
1031
+ </>
1032
+ )}
1033
+ </div>
1034
+
1035
+ {/* Right Sidebar - AI Panel */}
1036
+ {(showAIPanel || (!isAIPanelCollapsed && desktop)) && (
1037
+ <aside
1038
+ className={`
1039
+ transition-all duration-300 ease-in-out
1040
+ ${showAIPanel && mobile ? 'fixed inset-0 z-50 bg-card' : 'hidden lg:block'}
1041
+ ${
1042
+ isAIPanelCollapsed
1043
+ ? 'lg:w-0 lg:min-w-0 lg:max-w-0 lg:overflow-hidden'
1044
+ : 'lg:w-96 lg:max-w-100 lg:min-w-[320px]'
1045
+ }
1046
+ border-l border-border bg-card flex flex-col
1047
+ `}
1048
+ >
1049
+ <div className="lg:hidden h-14 border-b border-border flex items-center justify-between px-4 bg-card/95 backdrop-blur-sm">
1050
+ <span className="font-semibold">AI Assistant</span>
1051
+ <Button variant="ghost" size="icon" onClick={() => setShowAIPanel(false)} aria-label="Close AI panel">
1052
+ <X className="h-5 w-5" />
1053
+ </Button>
1054
+ </div>
1055
+
1056
+ <div className="flex-1 min-h-0 lg:h-full">
1057
+ <ChatPanel
1058
+ currentCode={activeTab?.content}
1059
+ compilationErrors={errors}
1060
+ onInsertCode={(code) => {
1061
+ if (activeTab) updateTabContent(activeTab.id, code);
1062
+ }}
1063
+ />
1064
+ </div>
1065
+ </aside>
1066
+ )}
1067
+
1068
+ {/* Right Sidebar - Contract Panel */}
1069
+ {(showContractPanel || (!isContractPanelCollapsed && desktop)) && (
1070
+ <aside
1071
+ className={`
1072
+ transition-all duration-300 ease-in-out
1073
+ ${showContractPanel && mobile ? 'fixed inset-0 z-50 bg-card' : 'hidden lg:block'}
1074
+ ${
1075
+ isContractPanelCollapsed
1076
+ ? 'lg:w-0 lg:min-w-0 lg:max-w-0 lg:overflow-hidden'
1077
+ : 'lg:w-96 lg:max-w-100 lg:min-w-[320px]'
1078
+ }
1079
+ border-l border-border bg-card flex flex-col
1080
+ `}
1081
+ >
1082
+ <div className="lg:hidden h-14 border-b border-border flex items-center justify-between px-4 bg-card/95 backdrop-blur-sm">
1083
+ <span className="font-semibold">Contract Interaction</span>
1084
+ <Button
1085
+ variant="ghost"
1086
+ size="icon"
1087
+ onClick={() => setShowContractPanel(false)}
1088
+ aria-label="Close contract panel"
1089
+ >
1090
+ <X className="h-5 w-5" />
1091
+ </Button>
1092
+ </div>
1093
+
1094
+ <div className="flex-1 min-h-0 lg:h-full">
1095
+ {deployedContracts.length > 0 && abiData.abi ? (
1096
+ <ContractInteraction
1097
+ contractAddress={deployedContracts[deployedContracts.length - 1].address}
1098
+ abi={abiData.abi}
1099
+ />
1100
+ ) : (
1101
+ <ContractPlaceholder />
1102
+ )}
1103
+ </div>
1104
+ </aside>
1105
+ )}
1106
+
1107
+ {/* Mobile Overlay */}
1108
+ {(showAIPanel || showContractPanel) && (
1109
+ <div
1110
+ className="fixed inset-0 bg-background/80 panel-overlay z-40 lg:hidden"
1111
+ onClick={closeMobilePanels}
1112
+ aria-label="Close panel"
1113
+ />
1114
+ )}
1115
+ </div>
1116
+
1117
+ <KeyboardShortcutHint />
1118
+
1119
+ {parsedAbi && (
1120
+ <BenchmarkDialog
1121
+ open={showBenchmarkDialog}
1122
+ onOpenChange={setShowBenchmarkDialog}
1123
+ abi={parsedAbi}
1124
+ functionName="number"
1125
+ title="Multi-Chain Gas Benchmark"
1126
+ description="Deploy your contract to multiple chains and compare gas costs"
1127
+ />
1128
+ )}
1129
+ </main>
1130
+ </>
1131
+ );
1132
+ }