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,173 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback, useEffect } from "react";
|
|
4
|
+
import { CompilationOutput } from "@/lib/compilation";
|
|
5
|
+
|
|
6
|
+
interface ParsedError {
|
|
7
|
+
line: number;
|
|
8
|
+
column: number;
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// NEW: Project file interface
|
|
13
|
+
interface ProjectFile {
|
|
14
|
+
path: string;
|
|
15
|
+
content: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface UseCompilationReturn {
|
|
19
|
+
isCompiling: boolean;
|
|
20
|
+
output: CompilationOutput[];
|
|
21
|
+
errors: ParsedError[];
|
|
22
|
+
compilationTime: number | null;
|
|
23
|
+
sessionId: string | null;
|
|
24
|
+
compile: (
|
|
25
|
+
code: string,
|
|
26
|
+
streaming?: boolean,
|
|
27
|
+
projectFiles?: ProjectFile[] // NEW: Add projectFiles parameter
|
|
28
|
+
) => Promise<void>;
|
|
29
|
+
clearOutput: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function useCompilation(): UseCompilationReturn {
|
|
33
|
+
const [isCompiling, setIsCompiling] = useState(false);
|
|
34
|
+
const [output, setOutput] = useState<CompilationOutput[]>([]);
|
|
35
|
+
const [errors, setErrors] = useState<ParsedError[]>([]);
|
|
36
|
+
const [compilationTime, setCompilationTime] = useState<number | null>(null);
|
|
37
|
+
const [sessionId, setSessionId] = useState<string | null>(null);
|
|
38
|
+
|
|
39
|
+
// Cleanup old projects periodically
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const cleanupInterval = setInterval(() => {
|
|
42
|
+
fetch("/api/cleanup", { method: "POST" }).catch(() => {
|
|
43
|
+
// Ignore cleanup errors
|
|
44
|
+
});
|
|
45
|
+
}, 5 * 60 * 1000); // Every 5 minutes
|
|
46
|
+
|
|
47
|
+
return () => clearInterval(cleanupInterval);
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
const compile = useCallback(
|
|
51
|
+
async (
|
|
52
|
+
code: string,
|
|
53
|
+
streaming = false,
|
|
54
|
+
projectFiles?: ProjectFile[] // NEW: Accept project files
|
|
55
|
+
) => {
|
|
56
|
+
setIsCompiling(true);
|
|
57
|
+
setOutput([]);
|
|
58
|
+
setErrors([]);
|
|
59
|
+
setCompilationTime(null);
|
|
60
|
+
setSessionId(null);
|
|
61
|
+
const startTime = Date.now();
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
if (streaming) {
|
|
65
|
+
// NEW: Updated streaming endpoint with projectFiles
|
|
66
|
+
const response = await fetch("/api/compile-stream", {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: { "Content-Type": "application/json" },
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
code,
|
|
71
|
+
projectFiles, // NEW: Send all files
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
throw new Error("Compilation request failed");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const reader = response.body?.getReader();
|
|
80
|
+
const decoder = new TextDecoder();
|
|
81
|
+
|
|
82
|
+
if (!reader) {
|
|
83
|
+
throw new Error("No response body");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let buffer = "";
|
|
87
|
+
while (true) {
|
|
88
|
+
const { done, value } = await reader.read();
|
|
89
|
+
if (done) break;
|
|
90
|
+
|
|
91
|
+
buffer += decoder.decode(value, { stream: true });
|
|
92
|
+
const lines = buffer.split("\n\n");
|
|
93
|
+
buffer = lines.pop() || "";
|
|
94
|
+
|
|
95
|
+
for (const line of lines) {
|
|
96
|
+
if (line.startsWith("data: ")) {
|
|
97
|
+
const data = JSON.parse(line.slice(6));
|
|
98
|
+
setOutput((prev) => [...prev, data]);
|
|
99
|
+
|
|
100
|
+
if (data.type === "start" && data.sessionId) {
|
|
101
|
+
setSessionId(data.sessionId);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (data.type === "result" && data.errors) {
|
|
105
|
+
setErrors(data.errors);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
setCompilationTime(Date.now() - startTime);
|
|
112
|
+
} else {
|
|
113
|
+
// NEW: Updated non-streaming endpoint with projectFiles
|
|
114
|
+
const response = await fetch("/api/compile", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: { "Content-Type": "application/json" },
|
|
117
|
+
body: JSON.stringify({
|
|
118
|
+
code,
|
|
119
|
+
projectFiles, // NEW: Send all files
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const result = await response.json();
|
|
124
|
+
|
|
125
|
+
if (result.sessionId) {
|
|
126
|
+
setSessionId(result.sessionId);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (result.output) {
|
|
130
|
+
setOutput(result.output);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (result.errors) {
|
|
134
|
+
setErrors(result.errors);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (result.error) {
|
|
138
|
+
setOutput([{ type: "error", data: result.error }]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setCompilationTime(Date.now() - startTime);
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
setOutput([
|
|
145
|
+
{
|
|
146
|
+
type: "error",
|
|
147
|
+
data: error instanceof Error ? error.message : "Compilation failed",
|
|
148
|
+
},
|
|
149
|
+
]);
|
|
150
|
+
setCompilationTime(Date.now() - startTime);
|
|
151
|
+
} finally {
|
|
152
|
+
setIsCompiling(false);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
[]
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const clearOutput = useCallback(() => {
|
|
159
|
+
setOutput([]);
|
|
160
|
+
setErrors([]);
|
|
161
|
+
setCompilationTime(null);
|
|
162
|
+
}, []);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
isCompiling,
|
|
166
|
+
output,
|
|
167
|
+
errors,
|
|
168
|
+
compilationTime,
|
|
169
|
+
sessionId,
|
|
170
|
+
compile,
|
|
171
|
+
clearOutput,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
|
|
5
|
+
export interface FileTab {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
content: string;
|
|
9
|
+
language: "rust" | "toml" | "markdown" | "text" | "gitignore";
|
|
10
|
+
path: string; // NEW - Full path for multi-file support
|
|
11
|
+
modified: boolean; // NEW - Track if file has unsaved changes
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface UseFileTabsReturn {
|
|
15
|
+
tabs: FileTab[];
|
|
16
|
+
activeTabId: string | null;
|
|
17
|
+
activeTab: FileTab | undefined;
|
|
18
|
+
addTab: (
|
|
19
|
+
name: string,
|
|
20
|
+
content: string,
|
|
21
|
+
language: FileTab["language"],
|
|
22
|
+
path?: string
|
|
23
|
+
) => string;
|
|
24
|
+
removeTab: (id: string) => void;
|
|
25
|
+
updateTabContent: (id: string, content: string) => void;
|
|
26
|
+
setActiveTab: (id: string) => void;
|
|
27
|
+
renameTab: (id: string, newName: string) => void;
|
|
28
|
+
getTabByPath: (path: string) => FileTab | undefined;
|
|
29
|
+
openOrActivateTab: (
|
|
30
|
+
path: string,
|
|
31
|
+
name: string,
|
|
32
|
+
content: string,
|
|
33
|
+
language: FileTab["language"]
|
|
34
|
+
) => void;
|
|
35
|
+
markTabSaved: (id: string) => void;
|
|
36
|
+
closeTabByPath: (path: string) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useFileTabs(defaultContent: string = ""): UseFileTabsReturn {
|
|
40
|
+
const [tabs, setTabs] = useState<FileTab[]>([
|
|
41
|
+
{
|
|
42
|
+
id: "default",
|
|
43
|
+
name: "lib.rs",
|
|
44
|
+
content: defaultContent,
|
|
45
|
+
language: "rust",
|
|
46
|
+
path: "src/lib.rs",
|
|
47
|
+
modified: false,
|
|
48
|
+
},
|
|
49
|
+
]);
|
|
50
|
+
const [activeTabId, setActiveTabId] = useState<string | null>("default");
|
|
51
|
+
|
|
52
|
+
const activeTab = tabs.find((tab) => tab.id === activeTabId);
|
|
53
|
+
|
|
54
|
+
// Add a new tab
|
|
55
|
+
const addTab = useCallback(
|
|
56
|
+
(
|
|
57
|
+
name: string,
|
|
58
|
+
content: string,
|
|
59
|
+
language: FileTab["language"],
|
|
60
|
+
path?: string
|
|
61
|
+
): string => {
|
|
62
|
+
const newTab: FileTab = {
|
|
63
|
+
id: `tab-${Date.now()}-${Math.random()}`,
|
|
64
|
+
name,
|
|
65
|
+
content,
|
|
66
|
+
language,
|
|
67
|
+
path: path || name,
|
|
68
|
+
modified: false,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
setTabs((prev) => [...prev, newTab]);
|
|
72
|
+
setActiveTabId(newTab.id);
|
|
73
|
+
return newTab.id;
|
|
74
|
+
},
|
|
75
|
+
[]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Remove a tab
|
|
79
|
+
const removeTab = useCallback((id: string) => {
|
|
80
|
+
setTabs((prev) => {
|
|
81
|
+
const filtered = prev.filter((tab) => tab.id !== id);
|
|
82
|
+
|
|
83
|
+
// If we removed the active tab, activate another one
|
|
84
|
+
setActiveTabId((currentActive) => {
|
|
85
|
+
if (currentActive === id) {
|
|
86
|
+
return filtered.length > 0 ? filtered[filtered.length - 1].id : null;
|
|
87
|
+
}
|
|
88
|
+
return currentActive;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return filtered;
|
|
92
|
+
});
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
// Update tab content
|
|
96
|
+
const updateTabContent = useCallback((id: string, content: string) => {
|
|
97
|
+
setTabs((prev) =>
|
|
98
|
+
prev.map((tab) =>
|
|
99
|
+
tab.id === id ? { ...tab, content, modified: true } : tab
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
// Set active tab
|
|
105
|
+
const setActiveTab = useCallback((id: string) => {
|
|
106
|
+
setActiveTabId(id);
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
109
|
+
// Rename a tab
|
|
110
|
+
const renameTab = useCallback((id: string, newName: string) => {
|
|
111
|
+
setTabs((prev) =>
|
|
112
|
+
prev.map((tab) => (tab.id === id ? { ...tab, name: newName } : tab))
|
|
113
|
+
);
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
// Get tab by path (for file tree integration)
|
|
117
|
+
const getTabByPath = useCallback(
|
|
118
|
+
(path: string): FileTab | undefined => {
|
|
119
|
+
return tabs.find((tab) => tab.path === path);
|
|
120
|
+
},
|
|
121
|
+
[tabs]
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Open or activate existing tab (key function for file tree)
|
|
125
|
+
const openOrActivateTab = useCallback(
|
|
126
|
+
(
|
|
127
|
+
path: string,
|
|
128
|
+
name: string,
|
|
129
|
+
content: string,
|
|
130
|
+
language: FileTab["language"]
|
|
131
|
+
) => {
|
|
132
|
+
const existingTab = tabs.find((tab) => tab.path === path);
|
|
133
|
+
|
|
134
|
+
if (existingTab) {
|
|
135
|
+
// Tab already open, just activate it
|
|
136
|
+
setActiveTabId(existingTab.id);
|
|
137
|
+
} else {
|
|
138
|
+
// Open new tab
|
|
139
|
+
const newTabId = addTab(name, content, language, path);
|
|
140
|
+
setActiveTabId(newTabId);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
[tabs, addTab]
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Mark tab as saved (remove modified flag)
|
|
147
|
+
const markTabSaved = useCallback((id: string) => {
|
|
148
|
+
setTabs((prev) =>
|
|
149
|
+
prev.map((tab) => (tab.id === id ? { ...tab, modified: false } : tab))
|
|
150
|
+
);
|
|
151
|
+
}, []);
|
|
152
|
+
|
|
153
|
+
// Close tab by path (for file tree integration)
|
|
154
|
+
const closeTabByPath = useCallback(
|
|
155
|
+
(path: string) => {
|
|
156
|
+
const tab = tabs.find((t) => t.path === path);
|
|
157
|
+
if (tab) {
|
|
158
|
+
removeTab(tab.id);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
[tabs, removeTab]
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
tabs,
|
|
166
|
+
activeTabId,
|
|
167
|
+
activeTab,
|
|
168
|
+
addTab,
|
|
169
|
+
removeTab,
|
|
170
|
+
updateTabContent,
|
|
171
|
+
setActiveTab,
|
|
172
|
+
renameTab,
|
|
173
|
+
getTabByPath,
|
|
174
|
+
openOrActivateTab,
|
|
175
|
+
markTabSaved,
|
|
176
|
+
closeTabByPath,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { ProjectState } from "@/types/project";
|
|
5
|
+
import { GitHubURLInfo } from "@/lib/url-parser";
|
|
6
|
+
import { loadGitHubRepo, GitHubLoadProgress } from "@/lib/github-loader";
|
|
7
|
+
|
|
8
|
+
export function useGitHubLoader() {
|
|
9
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
10
|
+
const [progress, setProgress] = useState<GitHubLoadProgress | null>(null);
|
|
11
|
+
const [error, setError] = useState<string | null>(null);
|
|
12
|
+
|
|
13
|
+
const loadFromGitHub = useCallback(
|
|
14
|
+
async (urlInfo: GitHubURLInfo): Promise<ProjectState | null> => {
|
|
15
|
+
setIsLoading(true);
|
|
16
|
+
setError(null);
|
|
17
|
+
setProgress(null);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const project = await loadGitHubRepo(urlInfo, (progressUpdate) => {
|
|
21
|
+
setProgress(progressUpdate);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
setIsLoading(false);
|
|
25
|
+
return project;
|
|
26
|
+
} catch (err) {
|
|
27
|
+
const errorMessage =
|
|
28
|
+
err instanceof Error ? err.message : "Unknown error";
|
|
29
|
+
setError(errorMessage);
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
[]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const reset = useCallback(() => {
|
|
38
|
+
setIsLoading(false);
|
|
39
|
+
setProgress(null);
|
|
40
|
+
setError(null);
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
isLoading,
|
|
45
|
+
progress,
|
|
46
|
+
error,
|
|
47
|
+
loadFromGitHub,
|
|
48
|
+
reset,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
interface KeyboardShortcutsProps {
|
|
6
|
+
onToggleAI: () => void;
|
|
7
|
+
onToggleOutput: () => void;
|
|
8
|
+
onCompile: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useKeyboardShortcuts({ onToggleAI, onToggleOutput, onCompile }: KeyboardShortcutsProps) {
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
14
|
+
// Check if user is typing in an input/textarea
|
|
15
|
+
const target = event.target as HTMLElement;
|
|
16
|
+
const isInputElement = target.tagName === 'INPUT' ||
|
|
17
|
+
target.tagName === 'TEXTAREA' ||
|
|
18
|
+
target.contentEditable === 'true';
|
|
19
|
+
|
|
20
|
+
// Cmd/Ctrl + K: Toggle AI panel
|
|
21
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 'k' && !isInputElement) {
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
onToggleAI();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Cmd/Ctrl + `: Toggle output panel
|
|
27
|
+
if ((event.metaKey || event.ctrlKey) && event.key === '`' && !isInputElement) {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
onToggleOutput();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Cmd/Ctrl + Enter: Compile
|
|
33
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter' && !isInputElement) {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
onCompile();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Escape: Close panels on mobile
|
|
39
|
+
if (event.key === 'Escape' && window.innerWidth < 1024) {
|
|
40
|
+
onToggleAI(); // This will close if open
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
45
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
46
|
+
}, [onToggleAI, onToggleOutput, onCompile]);
|
|
47
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
interface PanelState {
|
|
6
|
+
showAIPanel: boolean;
|
|
7
|
+
showContractPanel: boolean;
|
|
8
|
+
showOutput: boolean;
|
|
9
|
+
isAIPanelCollapsed: boolean;
|
|
10
|
+
isContractPanelCollapsed: boolean;
|
|
11
|
+
setShowAIPanel: (show: boolean) => void;
|
|
12
|
+
setShowContractPanel: (show: boolean) => void;
|
|
13
|
+
setShowOutput: (show: boolean) => void;
|
|
14
|
+
setIsAIPanelCollapsed: (collapsed: boolean) => void;
|
|
15
|
+
setIsContractPanelCollapsed: (collapsed: boolean) => void;
|
|
16
|
+
toggleAIPanel: () => void;
|
|
17
|
+
toggleContractPanel: () => void;
|
|
18
|
+
toggleOutput: () => void;
|
|
19
|
+
toggleAIPanelCollapse: () => void;
|
|
20
|
+
toggleContractPanelCollapse: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function usePanelState(): PanelState {
|
|
24
|
+
const [showAIPanel, setShowAIPanel] = useState(false);
|
|
25
|
+
const [showContractPanel, setShowContractPanel] = useState(false);
|
|
26
|
+
const [showOutput, setShowOutput] = useState(true);
|
|
27
|
+
const [isAIPanelCollapsed, setIsAIPanelCollapsed] = useState(false);
|
|
28
|
+
const [isContractPanelCollapsed, setIsContractPanelCollapsed] = useState(false);
|
|
29
|
+
|
|
30
|
+
// Handle responsive behavior
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const handleResize = () => {
|
|
33
|
+
const isDesktop = window.innerWidth >= 1024;
|
|
34
|
+
|
|
35
|
+
if (!isDesktop) {
|
|
36
|
+
// On smaller screens, ensure panels don't conflict
|
|
37
|
+
const activePanels = [showAIPanel, showContractPanel, showOutput].filter(Boolean).length;
|
|
38
|
+
if (activePanels > 1) {
|
|
39
|
+
// Close output panel if multiple panels are open on mobile
|
|
40
|
+
setShowOutput(false);
|
|
41
|
+
}
|
|
42
|
+
// Reset collapse state on mobile
|
|
43
|
+
setIsAIPanelCollapsed(false);
|
|
44
|
+
setIsContractPanelCollapsed(false);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
window.addEventListener('resize', handleResize);
|
|
49
|
+
handleResize(); // Initial check
|
|
50
|
+
|
|
51
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
52
|
+
}, [showAIPanel, showContractPanel, showOutput]);
|
|
53
|
+
|
|
54
|
+
const toggleAIPanel = () => {
|
|
55
|
+
setShowAIPanel(prev => {
|
|
56
|
+
const newValue = !prev;
|
|
57
|
+
// On mobile, close other panels when opening AI panel
|
|
58
|
+
if (newValue && window.innerWidth < 1024) {
|
|
59
|
+
setShowContractPanel(false);
|
|
60
|
+
setShowOutput(false);
|
|
61
|
+
}
|
|
62
|
+
return newValue;
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const toggleContractPanel = () => {
|
|
67
|
+
setShowContractPanel(prev => {
|
|
68
|
+
const newValue = !prev;
|
|
69
|
+
// On mobile, close other panels when opening contract panel
|
|
70
|
+
if (newValue && window.innerWidth < 1024) {
|
|
71
|
+
setShowAIPanel(false);
|
|
72
|
+
setShowOutput(false);
|
|
73
|
+
}
|
|
74
|
+
return newValue;
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const toggleOutput = () => {
|
|
79
|
+
setShowOutput(prev => {
|
|
80
|
+
const newValue = !prev;
|
|
81
|
+
// On mobile, close side panels when opening output
|
|
82
|
+
if (newValue && window.innerWidth < 1024) {
|
|
83
|
+
setShowAIPanel(false);
|
|
84
|
+
setShowContractPanel(false);
|
|
85
|
+
}
|
|
86
|
+
return newValue;
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const toggleAIPanelCollapse = () => {
|
|
91
|
+
setIsAIPanelCollapsed(prev => !prev);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const toggleContractPanelCollapse = () => {
|
|
95
|
+
setIsContractPanelCollapsed(prev => !prev);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
showAIPanel,
|
|
100
|
+
showContractPanel,
|
|
101
|
+
showOutput,
|
|
102
|
+
isAIPanelCollapsed,
|
|
103
|
+
isContractPanelCollapsed,
|
|
104
|
+
setShowAIPanel,
|
|
105
|
+
setShowContractPanel,
|
|
106
|
+
setShowOutput,
|
|
107
|
+
setIsAIPanelCollapsed,
|
|
108
|
+
setIsContractPanelCollapsed,
|
|
109
|
+
toggleAIPanel,
|
|
110
|
+
toggleContractPanel,
|
|
111
|
+
toggleOutput,
|
|
112
|
+
toggleAIPanelCollapse,
|
|
113
|
+
toggleContractPanelCollapse,
|
|
114
|
+
};
|
|
115
|
+
}
|