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,259 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { FileNode as FileNodeType } from '@/types/project';
|
|
5
|
+
import { FileNode } from './FileNode';
|
|
6
|
+
import { FolderNode } from './FolderNode';
|
|
7
|
+
import { FileTreeContextMenu } from './ContextMenu';
|
|
8
|
+
import { FileInputDialog } from './FileInputDialog';
|
|
9
|
+
import { DeleteConfirmDialog } from './DeleteConfirmDialog';
|
|
10
|
+
import { Button } from '@/components/ui/button';
|
|
11
|
+
import { Plus, FolderPlus } from 'lucide-react';
|
|
12
|
+
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
13
|
+
|
|
14
|
+
interface FileTreeProps {
|
|
15
|
+
structure: FileNodeType[];
|
|
16
|
+
activeFilePath: string | null;
|
|
17
|
+
onFileClick: (path: string) => void;
|
|
18
|
+
onFolderToggle: (path: string, expanded: boolean) => void;
|
|
19
|
+
onNewFile?: (parentPath?: string) => void;
|
|
20
|
+
onNewFolder?: (parentPath?: string) => void;
|
|
21
|
+
onRename?: (oldPath: string, newName: string) => void;
|
|
22
|
+
onDuplicate?: (path: string) => void;
|
|
23
|
+
onDelete?: (path: string, isFolder: boolean) => void;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function FileTree({
|
|
28
|
+
structure,
|
|
29
|
+
activeFilePath,
|
|
30
|
+
onFileClick,
|
|
31
|
+
onFolderToggle,
|
|
32
|
+
onNewFile,
|
|
33
|
+
onNewFolder,
|
|
34
|
+
onRename,
|
|
35
|
+
onDuplicate,
|
|
36
|
+
onDelete,
|
|
37
|
+
className = '',
|
|
38
|
+
}: FileTreeProps) {
|
|
39
|
+
const [contextNode, setContextNode] = useState<FileNodeType | null>(null);
|
|
40
|
+
|
|
41
|
+
// Dialog states
|
|
42
|
+
const [newFileDialog, setNewFileDialog] = useState<{
|
|
43
|
+
open: boolean;
|
|
44
|
+
parentPath?: string;
|
|
45
|
+
}>({ open: false });
|
|
46
|
+
|
|
47
|
+
const [newFolderDialog, setNewFolderDialog] = useState<{
|
|
48
|
+
open: boolean;
|
|
49
|
+
parentPath?: string;
|
|
50
|
+
}>({ open: false });
|
|
51
|
+
|
|
52
|
+
const [renameDialog, setRenameDialog] = useState<{
|
|
53
|
+
open: boolean;
|
|
54
|
+
path?: string;
|
|
55
|
+
currentName?: string;
|
|
56
|
+
}>({ open: false });
|
|
57
|
+
|
|
58
|
+
const [deleteDialog, setDeleteDialog] = useState<{
|
|
59
|
+
open: boolean;
|
|
60
|
+
path?: string;
|
|
61
|
+
name?: string;
|
|
62
|
+
isFolder?: boolean;
|
|
63
|
+
}>({ open: false });
|
|
64
|
+
|
|
65
|
+
// Context menu handlers
|
|
66
|
+
const handleNewFile = (parentPath?: string) => {
|
|
67
|
+
setNewFileDialog({ open: true, parentPath });
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleNewFolder = (parentPath?: string) => {
|
|
71
|
+
setNewFolderDialog({ open: true, parentPath });
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const handleRename = (path: string) => {
|
|
75
|
+
const pathParts = path.split('/');
|
|
76
|
+
const currentName = pathParts[pathParts.length - 1];
|
|
77
|
+
setRenameDialog({ open: true, path, currentName });
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleDuplicate = (path: string) => {
|
|
81
|
+
onDuplicate?.(path);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handleDelete = (path: string, isFolder: boolean) => {
|
|
85
|
+
const pathParts = path.split('/');
|
|
86
|
+
const name = pathParts[pathParts.length - 1];
|
|
87
|
+
setDeleteDialog({ open: true, path, name, isFolder });
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Dialog confirm handlers
|
|
91
|
+
const confirmNewFile = (name: string) => {
|
|
92
|
+
const fullPath = newFileDialog.parentPath
|
|
93
|
+
? `${newFileDialog.parentPath}/${name}`
|
|
94
|
+
: name;
|
|
95
|
+
onNewFile?.(fullPath);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const confirmNewFolder = (name: string) => {
|
|
99
|
+
const fullPath = newFolderDialog.parentPath
|
|
100
|
+
? `${newFolderDialog.parentPath}/${name}`
|
|
101
|
+
: name;
|
|
102
|
+
onNewFolder?.(fullPath);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const confirmRename = (newName: string) => {
|
|
106
|
+
if (renameDialog.path) {
|
|
107
|
+
onRename?.(renameDialog.path, newName);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const confirmDelete = () => {
|
|
112
|
+
if (deleteDialog.path !== undefined && deleteDialog.isFolder !== undefined) {
|
|
113
|
+
onDelete?.(deleteDialog.path, deleteDialog.isFolder);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<>
|
|
119
|
+
<FileTreeContextMenu
|
|
120
|
+
node={null}
|
|
121
|
+
onNewFile={handleNewFile}
|
|
122
|
+
onNewFolder={handleNewFolder}
|
|
123
|
+
onRename={handleRename}
|
|
124
|
+
onDuplicate={handleDuplicate}
|
|
125
|
+
onDelete={handleDelete}
|
|
126
|
+
>
|
|
127
|
+
<div className={`flex flex-col h-full border-r border-border bg-card ${className}`}>
|
|
128
|
+
{/* Header */}
|
|
129
|
+
<div className="h-10 border-b border-border flex items-center justify-between px-3">
|
|
130
|
+
<span className="text-xs font-semibold text-muted-foreground uppercase">
|
|
131
|
+
Files
|
|
132
|
+
</span>
|
|
133
|
+
|
|
134
|
+
<div className="flex items-center gap-1">
|
|
135
|
+
{onNewFile && (
|
|
136
|
+
<Button
|
|
137
|
+
variant="ghost"
|
|
138
|
+
size="icon"
|
|
139
|
+
className="h-6 w-6"
|
|
140
|
+
onClick={() => handleNewFile()}
|
|
141
|
+
title="New File"
|
|
142
|
+
>
|
|
143
|
+
<Plus className="h-3.5 w-3.5" />
|
|
144
|
+
</Button>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
{onNewFolder && (
|
|
148
|
+
<Button
|
|
149
|
+
variant="ghost"
|
|
150
|
+
size="icon"
|
|
151
|
+
className="h-6 w-6"
|
|
152
|
+
onClick={() => handleNewFolder()}
|
|
153
|
+
title="New Folder"
|
|
154
|
+
>
|
|
155
|
+
<FolderPlus className="h-3.5 w-3.5" />
|
|
156
|
+
</Button>
|
|
157
|
+
)}
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
{/* File Tree */}
|
|
162
|
+
<ScrollArea className="flex-1">
|
|
163
|
+
<div className="p-2">
|
|
164
|
+
{structure.length === 0 ? (
|
|
165
|
+
<div className="text-xs text-muted-foreground text-center py-8">
|
|
166
|
+
No files yet. Create a new file to get started.
|
|
167
|
+
</div>
|
|
168
|
+
) : (
|
|
169
|
+
<div className="space-y-0.5">
|
|
170
|
+
{structure.map((node) => (
|
|
171
|
+
node.type === 'folder' ? (
|
|
172
|
+
<FileTreeContextMenu
|
|
173
|
+
key={node.id}
|
|
174
|
+
node={node}
|
|
175
|
+
onNewFile={handleNewFile}
|
|
176
|
+
onNewFolder={handleNewFolder}
|
|
177
|
+
onRename={handleRename}
|
|
178
|
+
onDuplicate={handleDuplicate}
|
|
179
|
+
onDelete={handleDelete}
|
|
180
|
+
>
|
|
181
|
+
<div>
|
|
182
|
+
<FolderNode
|
|
183
|
+
node={node}
|
|
184
|
+
activeFilePath={activeFilePath}
|
|
185
|
+
onFileClick={onFileClick}
|
|
186
|
+
onFolderToggle={onFolderToggle}
|
|
187
|
+
onContextMenu={() => setContextNode(node)}
|
|
188
|
+
depth={0}
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
</FileTreeContextMenu>
|
|
192
|
+
) : (
|
|
193
|
+
<FileTreeContextMenu
|
|
194
|
+
key={node.id}
|
|
195
|
+
node={node}
|
|
196
|
+
onNewFile={handleNewFile}
|
|
197
|
+
onNewFolder={handleNewFolder}
|
|
198
|
+
onRename={handleRename}
|
|
199
|
+
onDuplicate={handleDuplicate}
|
|
200
|
+
onDelete={handleDelete}
|
|
201
|
+
>
|
|
202
|
+
<div>
|
|
203
|
+
<FileNode
|
|
204
|
+
node={node}
|
|
205
|
+
isActive={node.path === activeFilePath}
|
|
206
|
+
onClick={() => onFileClick(node.path)}
|
|
207
|
+
onContextMenu={() => setContextNode(node)}
|
|
208
|
+
depth={0}
|
|
209
|
+
/>
|
|
210
|
+
</div>
|
|
211
|
+
</FileTreeContextMenu>
|
|
212
|
+
)
|
|
213
|
+
))}
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
216
|
+
</div>
|
|
217
|
+
</ScrollArea>
|
|
218
|
+
</div>
|
|
219
|
+
</FileTreeContextMenu>
|
|
220
|
+
|
|
221
|
+
{/* Dialogs */}
|
|
222
|
+
<FileInputDialog
|
|
223
|
+
open={newFileDialog.open}
|
|
224
|
+
onOpenChange={(open) => setNewFileDialog({ open })}
|
|
225
|
+
title="New File"
|
|
226
|
+
description="Enter the name for the new file (e.g., utils.rs)"
|
|
227
|
+
placeholder="filename.rs"
|
|
228
|
+
onConfirm={confirmNewFile}
|
|
229
|
+
/>
|
|
230
|
+
|
|
231
|
+
<FileInputDialog
|
|
232
|
+
open={newFolderDialog.open}
|
|
233
|
+
onOpenChange={(open) => setNewFolderDialog({ open })}
|
|
234
|
+
title="New Folder"
|
|
235
|
+
description="Enter the name for the new folder"
|
|
236
|
+
placeholder="folder-name"
|
|
237
|
+
onConfirm={confirmNewFolder}
|
|
238
|
+
/>
|
|
239
|
+
|
|
240
|
+
<FileInputDialog
|
|
241
|
+
open={renameDialog.open}
|
|
242
|
+
onOpenChange={(open) => setRenameDialog({ open })}
|
|
243
|
+
title="Rename"
|
|
244
|
+
description="Enter the new name"
|
|
245
|
+
defaultValue={renameDialog.currentName}
|
|
246
|
+
placeholder="new-name"
|
|
247
|
+
onConfirm={confirmRename}
|
|
248
|
+
/>
|
|
249
|
+
|
|
250
|
+
<DeleteConfirmDialog
|
|
251
|
+
open={deleteDialog.open}
|
|
252
|
+
onOpenChange={(open) => setDeleteDialog({ open })}
|
|
253
|
+
itemName={deleteDialog.name || ''}
|
|
254
|
+
isFolder={deleteDialog.isFolder || false}
|
|
255
|
+
onConfirm={confirmDelete}
|
|
256
|
+
/>
|
|
257
|
+
</>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Skeleton } from '@/components/ui/skeleton';
|
|
4
|
+
|
|
5
|
+
export function FileTreeSkeleton() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="h-full p-2 space-y-1">
|
|
8
|
+
{/* Header skeleton */}
|
|
9
|
+
<div className="flex items-center justify-between px-2 py-2 mb-2">
|
|
10
|
+
<Skeleton className="h-4 w-24" />
|
|
11
|
+
<div className="flex gap-1">
|
|
12
|
+
<Skeleton className="h-6 w-6" />
|
|
13
|
+
<Skeleton className="h-6 w-6" />
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
{/* Folder skeletons */}
|
|
18
|
+
{[0, 1, 2, 3, 4].map((i) => (
|
|
19
|
+
<div key={i} className="flex items-center gap-2 px-2 py-1">
|
|
20
|
+
<Skeleton className="h-4 w-4" />
|
|
21
|
+
<Skeleton className="h-4 w-32" style={{ width: `${Math.random() * 100 + 80}px` }} />
|
|
22
|
+
</div>
|
|
23
|
+
))}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { FileNode as FileNodeType } from '@/types/project';
|
|
5
|
+
import { ChevronRight, ChevronDown, Folder, FolderOpen } from 'lucide-react';
|
|
6
|
+
import { FileNode } from './FileNode';
|
|
7
|
+
import { FileTreeContextMenu } from './ContextMenu';
|
|
8
|
+
import { cn } from '@/lib/utils';
|
|
9
|
+
|
|
10
|
+
interface FolderNodeProps {
|
|
11
|
+
node: FileNodeType;
|
|
12
|
+
activeFilePath: string | null;
|
|
13
|
+
onFileClick: (path: string) => void;
|
|
14
|
+
onFolderToggle: (path: string, expanded: boolean) => void;
|
|
15
|
+
onContextMenu: () => void;
|
|
16
|
+
depth: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function FolderNode({
|
|
20
|
+
node,
|
|
21
|
+
activeFilePath,
|
|
22
|
+
onFileClick,
|
|
23
|
+
onFolderToggle,
|
|
24
|
+
onContextMenu,
|
|
25
|
+
depth,
|
|
26
|
+
}: FolderNodeProps) {
|
|
27
|
+
const [isExpanded, setIsExpanded] = useState(node.expanded || false);
|
|
28
|
+
const paddingLeft = depth * 12 + 8;
|
|
29
|
+
|
|
30
|
+
const handleToggle = (e: React.MouseEvent) => {
|
|
31
|
+
e.stopPropagation();
|
|
32
|
+
const newExpanded = !isExpanded;
|
|
33
|
+
setIsExpanded(newExpanded);
|
|
34
|
+
onFolderToggle(node.path, newExpanded);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="select-none">
|
|
41
|
+
{/* Folder Header */}
|
|
42
|
+
<div
|
|
43
|
+
className={cn(
|
|
44
|
+
'flex items-center gap-1 px-2 py-1.5 rounded-md cursor-pointer text-sm',
|
|
45
|
+
'hover:bg-accent transition-colors'
|
|
46
|
+
)}
|
|
47
|
+
style={{ paddingLeft: `${paddingLeft}px` }}
|
|
48
|
+
onClick={handleToggle}
|
|
49
|
+
onContextMenu={(e) => {
|
|
50
|
+
e.stopPropagation();
|
|
51
|
+
onContextMenu();
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{/* Chevron */}
|
|
55
|
+
<div className="flex items-center justify-center w-4 h-4">
|
|
56
|
+
{hasChildren ? (
|
|
57
|
+
isExpanded ? (
|
|
58
|
+
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
|
|
59
|
+
) : (
|
|
60
|
+
<ChevronRight className="h-3.5 w-3.5 text-muted-foreground" />
|
|
61
|
+
)
|
|
62
|
+
) : null}
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{/* Folder Icon */}
|
|
66
|
+
{isExpanded ? (
|
|
67
|
+
<FolderOpen className="h-4 w-4 text-blue-500" />
|
|
68
|
+
) : (
|
|
69
|
+
<Folder className="h-4 w-4 text-blue-500" />
|
|
70
|
+
)}
|
|
71
|
+
|
|
72
|
+
{/* Folder Name */}
|
|
73
|
+
<span className="truncate">{node.name}</span>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
{/* Children (when expanded) */}
|
|
77
|
+
{isExpanded && hasChildren && (
|
|
78
|
+
<div className="space-y-0.5">
|
|
79
|
+
{node.children!.map((childNode) => (
|
|
80
|
+
childNode.type === 'folder' ? (
|
|
81
|
+
<FolderNode
|
|
82
|
+
key={childNode.id}
|
|
83
|
+
node={childNode}
|
|
84
|
+
activeFilePath={activeFilePath}
|
|
85
|
+
onFileClick={onFileClick}
|
|
86
|
+
onFolderToggle={onFolderToggle}
|
|
87
|
+
onContextMenu={onContextMenu}
|
|
88
|
+
depth={depth + 1}
|
|
89
|
+
/>
|
|
90
|
+
) : (
|
|
91
|
+
<FileNode
|
|
92
|
+
key={childNode.id}
|
|
93
|
+
node={childNode}
|
|
94
|
+
isActive={childNode.path === activeFilePath}
|
|
95
|
+
onClick={() => onFileClick(childNode.path)}
|
|
96
|
+
onContextMenu={onContextMenu}
|
|
97
|
+
depth={depth + 1}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogDescription,
|
|
8
|
+
DialogHeader,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
} from '@/components/ui/dialog';
|
|
11
|
+
import { Progress } from '@/components/ui/progress';
|
|
12
|
+
import { Alert, AlertDescription } from '@/components/ui/alert';
|
|
13
|
+
import { CheckCircle2, Loader2, XCircle, Github } from 'lucide-react';
|
|
14
|
+
import { Button } from '@/components/ui/button';
|
|
15
|
+
import { GitHubLoadProgress } from '@/lib/github-loader';
|
|
16
|
+
|
|
17
|
+
interface GitHubLoadingDialogProps {
|
|
18
|
+
open: boolean;
|
|
19
|
+
onOpenChange: (open: boolean) => void;
|
|
20
|
+
progress: GitHubLoadProgress | null;
|
|
21
|
+
onRetry?: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function GitHubLoadingDialog({
|
|
25
|
+
open,
|
|
26
|
+
onOpenChange,
|
|
27
|
+
progress,
|
|
28
|
+
onRetry,
|
|
29
|
+
}: GitHubLoadingDialogProps) {
|
|
30
|
+
const [dots, setDots] = useState('');
|
|
31
|
+
|
|
32
|
+
// Animated dots for loading
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (progress?.stage === 'downloading-files' || progress?.stage === 'fetching-tree') {
|
|
35
|
+
const interval = setInterval(() => {
|
|
36
|
+
setDots((prev) => (prev.length >= 3 ? '' : prev + '.'));
|
|
37
|
+
}, 500);
|
|
38
|
+
return () => clearInterval(interval);
|
|
39
|
+
}
|
|
40
|
+
}, [progress?.stage]);
|
|
41
|
+
|
|
42
|
+
const getStageIcon = () => {
|
|
43
|
+
if (!progress) return <Loader2 className="h-5 w-5 animate-spin text-blue-500" />;
|
|
44
|
+
|
|
45
|
+
switch (progress.stage) {
|
|
46
|
+
case 'complete':
|
|
47
|
+
return <CheckCircle2 className="h-5 w-5 text-green-500" />;
|
|
48
|
+
case 'error':
|
|
49
|
+
return <XCircle className="h-5 w-5 text-red-500" />;
|
|
50
|
+
default:
|
|
51
|
+
return <Loader2 className="h-5 w-5 animate-spin text-blue-500" />;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const getStageTitle = () => {
|
|
56
|
+
if (!progress) return 'Loading...';
|
|
57
|
+
|
|
58
|
+
switch (progress.stage) {
|
|
59
|
+
case 'validating':
|
|
60
|
+
return 'Validating Repository';
|
|
61
|
+
case 'fetching-tree':
|
|
62
|
+
return 'Fetching Repository Structure';
|
|
63
|
+
case 'downloading-files':
|
|
64
|
+
return 'Downloading Files';
|
|
65
|
+
case 'complete':
|
|
66
|
+
return 'Success!';
|
|
67
|
+
case 'error':
|
|
68
|
+
return 'Error';
|
|
69
|
+
default:
|
|
70
|
+
return 'Loading...';
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
76
|
+
<DialogContent className="sm:max-w-md">
|
|
77
|
+
<DialogHeader>
|
|
78
|
+
<div className="flex items-center gap-3">
|
|
79
|
+
{progress?.stage === 'error' ? (
|
|
80
|
+
getStageIcon()
|
|
81
|
+
) : progress?.stage === 'complete' ? (
|
|
82
|
+
getStageIcon()
|
|
83
|
+
) : (
|
|
84
|
+
<Github className="h-5 w-5 text-muted-foreground" />
|
|
85
|
+
)}
|
|
86
|
+
<DialogTitle>{getStageTitle()}</DialogTitle>
|
|
87
|
+
</div>
|
|
88
|
+
<DialogDescription>
|
|
89
|
+
{progress?.stage === 'complete'
|
|
90
|
+
? 'Repository loaded successfully'
|
|
91
|
+
: progress?.stage === 'error'
|
|
92
|
+
? 'Failed to load repository'
|
|
93
|
+
: 'Loading from GitHub...'}
|
|
94
|
+
</DialogDescription>
|
|
95
|
+
</DialogHeader>
|
|
96
|
+
|
|
97
|
+
<div className="space-y-4">
|
|
98
|
+
{/* Progress Bar */}
|
|
99
|
+
{progress && progress.stage !== 'error' && progress.stage !== 'complete' && (
|
|
100
|
+
<Progress value={progress.progress} className="w-full" />
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{/* Status Message */}
|
|
104
|
+
{progress && (
|
|
105
|
+
<div className="space-y-2">
|
|
106
|
+
<p className="text-sm text-muted-foreground">
|
|
107
|
+
{progress.message}
|
|
108
|
+
{(progress.stage === 'downloading-files' || progress.stage === 'fetching-tree') &&
|
|
109
|
+
dots}
|
|
110
|
+
</p>
|
|
111
|
+
|
|
112
|
+
{/* File Progress */}
|
|
113
|
+
{progress.filesTotal && progress.filesDownloaded !== undefined && (
|
|
114
|
+
<div className="text-xs text-muted-foreground">
|
|
115
|
+
{progress.filesDownloaded} / {progress.filesTotal} files downloaded
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
118
|
+
|
|
119
|
+
{/* Current File */}
|
|
120
|
+
{progress.currentFile && (
|
|
121
|
+
<div className="text-xs text-muted-foreground font-mono bg-muted px-2 py-1 rounded">
|
|
122
|
+
{progress.currentFile}
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
{/* Error Message */}
|
|
129
|
+
{progress?.stage === 'error' && (
|
|
130
|
+
<div className="space-y-3">
|
|
131
|
+
<Alert variant="destructive">
|
|
132
|
+
<XCircle className="h-4 w-4" />
|
|
133
|
+
<AlertDescription className="whitespace-pre-wrap">
|
|
134
|
+
{progress.message}
|
|
135
|
+
</AlertDescription>
|
|
136
|
+
</Alert>
|
|
137
|
+
|
|
138
|
+
{/* Help text for common errors */}
|
|
139
|
+
{progress.message.includes('rate limit') && (
|
|
140
|
+
<div className="text-xs text-muted-foreground space-y-1">
|
|
141
|
+
<p className="font-semibold">To increase rate limits:</p>
|
|
142
|
+
<ol className="list-decimal list-inside space-y-1">
|
|
143
|
+
<li>Create a GitHub Personal Access Token</li>
|
|
144
|
+
<li>Add NEXT_PUBLIC_GITHUB_TOKEN to .env.local</li>
|
|
145
|
+
<li>Restart your dev server</li>
|
|
146
|
+
</ol>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{progress.message.includes('not found') && (
|
|
151
|
+
<div className="text-xs text-muted-foreground">
|
|
152
|
+
<p>Make sure the repository URL is correct and the repository is public.</p>
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
|
|
156
|
+
{progress.message.includes('private') && (
|
|
157
|
+
<div className="text-xs text-muted-foreground">
|
|
158
|
+
<p>This repository is private. Public repositories only are supported.</p>
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{progress.message.includes('Network error') && (
|
|
163
|
+
<div className="text-xs text-muted-foreground">
|
|
164
|
+
<p>Check your internet connection and try again.</p>
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{/* Success Message */}
|
|
171
|
+
{progress?.stage === 'complete' && (
|
|
172
|
+
<Alert>
|
|
173
|
+
<CheckCircle2 className="h-4 w-4" />
|
|
174
|
+
<AlertDescription>
|
|
175
|
+
Loaded {progress.filesDownloaded} files successfully
|
|
176
|
+
</AlertDescription>
|
|
177
|
+
</Alert>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
{/* Actions */}
|
|
181
|
+
{progress?.stage === 'error' && onRetry && (
|
|
182
|
+
<div className="flex gap-2">
|
|
183
|
+
<Button onClick={onRetry} className="flex-1">
|
|
184
|
+
Retry
|
|
185
|
+
</Button>
|
|
186
|
+
<Button onClick={() => onOpenChange(false)} variant="outline" className="flex-1">
|
|
187
|
+
Cancel
|
|
188
|
+
</Button>
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
{progress?.stage === 'complete' && (
|
|
193
|
+
<Button onClick={() => onOpenChange(false)} className="w-full">
|
|
194
|
+
Continue to IDE
|
|
195
|
+
</Button>
|
|
196
|
+
)}
|
|
197
|
+
</div>
|
|
198
|
+
</DialogContent>
|
|
199
|
+
</Dialog>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ExternalLink, Github, GitBranch, Folder } from 'lucide-react';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { URLCopyButton } from './URLCopyButton';
|
|
6
|
+
|
|
7
|
+
interface GitHubMetadataBannerProps {
|
|
8
|
+
owner: string;
|
|
9
|
+
repo: string;
|
|
10
|
+
branch: string;
|
|
11
|
+
url: string;
|
|
12
|
+
folderPath?: string; // ✅ ADD THIS
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function GitHubMetadataBanner({
|
|
16
|
+
owner,
|
|
17
|
+
repo,
|
|
18
|
+
branch,
|
|
19
|
+
url,
|
|
20
|
+
folderPath // ✅ ADD THIS
|
|
21
|
+
}: GitHubMetadataBannerProps) {
|
|
22
|
+
return (
|
|
23
|
+
<div className="h-8 border-b border-border bg-blue-500/10 flex items-center justify-between px-4 text-xs">
|
|
24
|
+
<div className="flex items-center gap-3 text-blue-600 dark:text-blue-400">
|
|
25
|
+
<div className="flex items-center gap-1.5">
|
|
26
|
+
<Github className="h-3.5 w-3.5" />
|
|
27
|
+
<span className="font-medium">{owner}/{repo}</span>
|
|
28
|
+
</div>
|
|
29
|
+
<div className="flex items-center gap-1.5 text-muted-foreground">
|
|
30
|
+
<GitBranch className="h-3 w-3" />
|
|
31
|
+
<span>{branch}</span>
|
|
32
|
+
</div>
|
|
33
|
+
{/* ✅ NEW: Show folder path if loading specific folder */}
|
|
34
|
+
{folderPath && (
|
|
35
|
+
<div className="flex items-center gap-1.5 text-muted-foreground">
|
|
36
|
+
<Folder className="h-3 w-3" />
|
|
37
|
+
<span>/{folderPath}</span>
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
<div className="flex items-center gap-2">
|
|
42
|
+
{/* ✅ NEW: Share button */}
|
|
43
|
+
<URLCopyButton
|
|
44
|
+
githubUrl={url}
|
|
45
|
+
branch={branch}
|
|
46
|
+
folderPath={folderPath}
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
<Button
|
|
50
|
+
variant="ghost"
|
|
51
|
+
size="sm"
|
|
52
|
+
className="h-6 text-xs gap-1"
|
|
53
|
+
onClick={() => window.open(url, '_blank')}
|
|
54
|
+
>
|
|
55
|
+
View on GitHub
|
|
56
|
+
<ExternalLink className="h-3 w-3" />
|
|
57
|
+
</Button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|