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.
- package/Readme.MD +1515 -0
- package/cli.js +28 -0
- package/frontend/.vscode/settings.json +9 -0
- package/frontend/app/api/chat/route.ts +101 -0
- package/frontend/app/api/check-setup/route.ts +93 -0
- package/frontend/app/api/cleanup/route.ts +14 -0
- package/frontend/app/api/compile/route.ts +95 -0
- package/frontend/app/api/compile-stream/route.ts +98 -0
- package/frontend/app/api/complete/route.ts +86 -0
- package/frontend/app/api/deploy/route.ts +118 -0
- package/frontend/app/api/export-abi/route.ts +58 -0
- package/frontend/app/favicon.ico +0 -0
- package/frontend/app/globals.css +177 -0
- package/frontend/app/layout.tsx +29 -0
- package/frontend/app/ml/page.tsx +694 -0
- package/frontend/app/page.tsx +1132 -0
- package/frontend/app/providers.tsx +18 -0
- package/frontend/app/qlearning/page.tsx +188 -0
- package/frontend/app/raytracing/page.tsx +268 -0
- package/frontend/components/abi/ABIDialog.tsx +132 -0
- package/frontend/components/ai/AICompletionPopup.tsx +76 -0
- package/frontend/components/ai/ChatPanel.tsx +292 -0
- package/frontend/components/ai/QuickActions.tsx +128 -0
- package/frontend/components/blockchain/BlockchainContractBanner.tsx +64 -0
- package/frontend/components/blockchain/BlockchainLoadingDialog.tsx +188 -0
- package/frontend/components/deploy/DeployDialog.tsx +334 -0
- package/frontend/components/editor/FileTabs.tsx +181 -0
- package/frontend/components/editor/MonacoEditor.tsx +306 -0
- package/frontend/components/file-tree/ContextMenu.tsx +110 -0
- package/frontend/components/file-tree/DeleteConfirmDialog.tsx +61 -0
- package/frontend/components/file-tree/FileInputDialog.tsx +97 -0
- package/frontend/components/file-tree/FileNode.tsx +60 -0
- package/frontend/components/file-tree/FileTree.tsx +259 -0
- package/frontend/components/file-tree/FileTreeSkeleton.tsx +26 -0
- package/frontend/components/file-tree/FolderNode.tsx +105 -0
- package/frontend/components/github/GitHubLoadingDialog.tsx +201 -0
- package/frontend/components/github/GitHubMetadataBanner.tsx +61 -0
- package/frontend/components/github/LoadFromGitHubDialog.tsx +125 -0
- package/frontend/components/github/URLCopyButton.tsx +60 -0
- package/frontend/components/interact/ContractInteraction.tsx +323 -0
- package/frontend/components/interact/ContractPlaceholder.tsx +41 -0
- package/frontend/components/orbit/BenchmarkDialog.tsx +342 -0
- package/frontend/components/orbit/OrbitExplorer.tsx +273 -0
- package/frontend/components/project/ProjectActions.tsx +176 -0
- package/frontend/components/q-learning/ContractConfig.tsx +172 -0
- package/frontend/components/q-learning/MazeGrid.tsx +346 -0
- package/frontend/components/q-learning/PathAnimation.tsx +384 -0
- package/frontend/components/q-learning/QTableHeatmap.tsx +300 -0
- package/frontend/components/q-learning/TrainingForm.tsx +349 -0
- package/frontend/components/ray-tracing/ContractConfig.tsx +245 -0
- package/frontend/components/ray-tracing/MintingForm.tsx +280 -0
- package/frontend/components/ray-tracing/RenderCanvas.tsx +228 -0
- package/frontend/components/ray-tracing/RenderingPanel.tsx +259 -0
- package/frontend/components/ray-tracing/StyleControls.tsx +217 -0
- package/frontend/components/setup/SetupGuide.tsx +290 -0
- package/frontend/components/ui/KeyboardShortcutHint.tsx +74 -0
- package/frontend/components/ui/alert-dialog.tsx +157 -0
- package/frontend/components/ui/alert.tsx +66 -0
- package/frontend/components/ui/badge.tsx +46 -0
- package/frontend/components/ui/button.tsx +62 -0
- package/frontend/components/ui/card.tsx +92 -0
- package/frontend/components/ui/context-menu.tsx +252 -0
- package/frontend/components/ui/dialog.tsx +143 -0
- package/frontend/components/ui/dropdown-menu.tsx +257 -0
- package/frontend/components/ui/input.tsx +21 -0
- package/frontend/components/ui/label.tsx +24 -0
- package/frontend/components/ui/progress.tsx +31 -0
- package/frontend/components/ui/scroll-area.tsx +58 -0
- package/frontend/components/ui/select.tsx +190 -0
- package/frontend/components/ui/separator.tsx +28 -0
- package/frontend/components/ui/sheet.tsx +139 -0
- package/frontend/components/ui/skeleton.tsx +13 -0
- package/frontend/components/ui/slider.tsx +63 -0
- package/frontend/components/ui/sonner.tsx +40 -0
- package/frontend/components/ui/tabs.tsx +66 -0
- package/frontend/components/ui/textarea.tsx +18 -0
- package/frontend/components/wallet/ConnectButton.tsx +167 -0
- package/frontend/components/wallet/FaucetButton.tsx +256 -0
- package/frontend/components.json +22 -0
- package/frontend/eslint.config.mjs +18 -0
- package/frontend/hooks/useAICompletion.ts +75 -0
- package/frontend/hooks/useBlockchainLoader.ts +58 -0
- package/frontend/hooks/useChats.ts +137 -0
- package/frontend/hooks/useCompilation.ts +173 -0
- package/frontend/hooks/useFileTabs.ts +178 -0
- package/frontend/hooks/useGitHubLoader.ts +50 -0
- package/frontend/hooks/useKeyboardShortcuts.ts +47 -0
- package/frontend/hooks/usePanelState.ts +115 -0
- package/frontend/hooks/useProjectState.ts +276 -0
- package/frontend/hooks/useResponsive.ts +29 -0
- package/frontend/lib/abi-parser.ts +58 -0
- package/frontend/lib/blockchain-api.ts +374 -0
- package/frontend/lib/blockchain-explorers.ts +75 -0
- package/frontend/lib/blockchain-loader.ts +112 -0
- package/frontend/lib/cargo-template.ts +64 -0
- package/frontend/lib/compilation.ts +529 -0
- package/frontend/lib/constants.ts +31 -0
- package/frontend/lib/deployment.ts +176 -0
- package/frontend/lib/file-utils.ts +83 -0
- package/frontend/lib/github-api.ts +246 -0
- package/frontend/lib/github-loader.ts +369 -0
- package/frontend/lib/ml-contract-template.txt +900 -0
- package/frontend/lib/orbit-chains.ts +181 -0
- package/frontend/lib/output-formatter.ts +68 -0
- package/frontend/lib/project-manager.ts +632 -0
- package/frontend/lib/ray-tracing-abi.ts +206 -0
- package/frontend/lib/storage.ts +189 -0
- package/frontend/lib/templates.ts +1662 -0
- package/frontend/lib/url-parser.ts +188 -0
- package/frontend/lib/utils.ts +6 -0
- package/frontend/lib/wagmi-config.ts +24 -0
- package/frontend/next.config.ts +7 -0
- package/frontend/package-lock.json +16259 -0
- package/frontend/package.json +60 -0
- package/frontend/postcss.config.mjs +7 -0
- package/frontend/public/file.svg +1 -0
- package/frontend/public/globe.svg +1 -0
- package/frontend/public/ml-weights/.gitkeep +0 -0
- package/frontend/public/ml-weights/model.pkl +0 -0
- package/frontend/public/ml-weights/model_weights.json +27102 -0
- package/frontend/public/ml-weights/test_samples.json +7888 -0
- package/frontend/public/next.svg +1 -0
- package/frontend/public/vercel.svg +1 -0
- package/frontend/public/window.svg +1 -0
- package/frontend/scripts/check-env.js +52 -0
- package/frontend/scripts/setup.js +285 -0
- package/frontend/tailwind.config.ts +64 -0
- package/frontend/tsconfig.json +34 -0
- package/frontend/types/blockchain.ts +63 -0
- package/frontend/types/github.ts +54 -0
- package/frontend/types/project.ts +106 -0
- package/ml-training/README.md +56 -0
- package/ml-training/train_tiny_model.py +325 -0
- package/ml-training/update_template.py +59 -0
- 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
|
+
}
|