create-stylus-ide 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/Readme.MD +1515 -0
  2. package/cli.js +28 -0
  3. package/frontend/.vscode/settings.json +9 -0
  4. package/frontend/app/api/chat/route.ts +101 -0
  5. package/frontend/app/api/check-setup/route.ts +93 -0
  6. package/frontend/app/api/cleanup/route.ts +14 -0
  7. package/frontend/app/api/compile/route.ts +95 -0
  8. package/frontend/app/api/compile-stream/route.ts +98 -0
  9. package/frontend/app/api/complete/route.ts +86 -0
  10. package/frontend/app/api/deploy/route.ts +118 -0
  11. package/frontend/app/api/export-abi/route.ts +58 -0
  12. package/frontend/app/favicon.ico +0 -0
  13. package/frontend/app/globals.css +177 -0
  14. package/frontend/app/layout.tsx +29 -0
  15. package/frontend/app/ml/page.tsx +694 -0
  16. package/frontend/app/page.tsx +1132 -0
  17. package/frontend/app/providers.tsx +18 -0
  18. package/frontend/app/qlearning/page.tsx +188 -0
  19. package/frontend/app/raytracing/page.tsx +268 -0
  20. package/frontend/components/abi/ABIDialog.tsx +132 -0
  21. package/frontend/components/ai/AICompletionPopup.tsx +76 -0
  22. package/frontend/components/ai/ChatPanel.tsx +292 -0
  23. package/frontend/components/ai/QuickActions.tsx +128 -0
  24. package/frontend/components/blockchain/BlockchainContractBanner.tsx +64 -0
  25. package/frontend/components/blockchain/BlockchainLoadingDialog.tsx +188 -0
  26. package/frontend/components/deploy/DeployDialog.tsx +334 -0
  27. package/frontend/components/editor/FileTabs.tsx +181 -0
  28. package/frontend/components/editor/MonacoEditor.tsx +306 -0
  29. package/frontend/components/file-tree/ContextMenu.tsx +110 -0
  30. package/frontend/components/file-tree/DeleteConfirmDialog.tsx +61 -0
  31. package/frontend/components/file-tree/FileInputDialog.tsx +97 -0
  32. package/frontend/components/file-tree/FileNode.tsx +60 -0
  33. package/frontend/components/file-tree/FileTree.tsx +259 -0
  34. package/frontend/components/file-tree/FileTreeSkeleton.tsx +26 -0
  35. package/frontend/components/file-tree/FolderNode.tsx +105 -0
  36. package/frontend/components/github/GitHubLoadingDialog.tsx +201 -0
  37. package/frontend/components/github/GitHubMetadataBanner.tsx +61 -0
  38. package/frontend/components/github/LoadFromGitHubDialog.tsx +125 -0
  39. package/frontend/components/github/URLCopyButton.tsx +60 -0
  40. package/frontend/components/interact/ContractInteraction.tsx +323 -0
  41. package/frontend/components/interact/ContractPlaceholder.tsx +41 -0
  42. package/frontend/components/orbit/BenchmarkDialog.tsx +342 -0
  43. package/frontend/components/orbit/OrbitExplorer.tsx +273 -0
  44. package/frontend/components/project/ProjectActions.tsx +176 -0
  45. package/frontend/components/q-learning/ContractConfig.tsx +172 -0
  46. package/frontend/components/q-learning/MazeGrid.tsx +346 -0
  47. package/frontend/components/q-learning/PathAnimation.tsx +384 -0
  48. package/frontend/components/q-learning/QTableHeatmap.tsx +300 -0
  49. package/frontend/components/q-learning/TrainingForm.tsx +349 -0
  50. package/frontend/components/ray-tracing/ContractConfig.tsx +245 -0
  51. package/frontend/components/ray-tracing/MintingForm.tsx +280 -0
  52. package/frontend/components/ray-tracing/RenderCanvas.tsx +228 -0
  53. package/frontend/components/ray-tracing/RenderingPanel.tsx +259 -0
  54. package/frontend/components/ray-tracing/StyleControls.tsx +217 -0
  55. package/frontend/components/setup/SetupGuide.tsx +290 -0
  56. package/frontend/components/ui/KeyboardShortcutHint.tsx +74 -0
  57. package/frontend/components/ui/alert-dialog.tsx +157 -0
  58. package/frontend/components/ui/alert.tsx +66 -0
  59. package/frontend/components/ui/badge.tsx +46 -0
  60. package/frontend/components/ui/button.tsx +62 -0
  61. package/frontend/components/ui/card.tsx +92 -0
  62. package/frontend/components/ui/context-menu.tsx +252 -0
  63. package/frontend/components/ui/dialog.tsx +143 -0
  64. package/frontend/components/ui/dropdown-menu.tsx +257 -0
  65. package/frontend/components/ui/input.tsx +21 -0
  66. package/frontend/components/ui/label.tsx +24 -0
  67. package/frontend/components/ui/progress.tsx +31 -0
  68. package/frontend/components/ui/scroll-area.tsx +58 -0
  69. package/frontend/components/ui/select.tsx +190 -0
  70. package/frontend/components/ui/separator.tsx +28 -0
  71. package/frontend/components/ui/sheet.tsx +139 -0
  72. package/frontend/components/ui/skeleton.tsx +13 -0
  73. package/frontend/components/ui/slider.tsx +63 -0
  74. package/frontend/components/ui/sonner.tsx +40 -0
  75. package/frontend/components/ui/tabs.tsx +66 -0
  76. package/frontend/components/ui/textarea.tsx +18 -0
  77. package/frontend/components/wallet/ConnectButton.tsx +167 -0
  78. package/frontend/components/wallet/FaucetButton.tsx +256 -0
  79. package/frontend/components.json +22 -0
  80. package/frontend/eslint.config.mjs +18 -0
  81. package/frontend/hooks/useAICompletion.ts +75 -0
  82. package/frontend/hooks/useBlockchainLoader.ts +58 -0
  83. package/frontend/hooks/useChats.ts +137 -0
  84. package/frontend/hooks/useCompilation.ts +173 -0
  85. package/frontend/hooks/useFileTabs.ts +178 -0
  86. package/frontend/hooks/useGitHubLoader.ts +50 -0
  87. package/frontend/hooks/useKeyboardShortcuts.ts +47 -0
  88. package/frontend/hooks/usePanelState.ts +115 -0
  89. package/frontend/hooks/useProjectState.ts +276 -0
  90. package/frontend/hooks/useResponsive.ts +29 -0
  91. package/frontend/lib/abi-parser.ts +58 -0
  92. package/frontend/lib/blockchain-api.ts +374 -0
  93. package/frontend/lib/blockchain-explorers.ts +75 -0
  94. package/frontend/lib/blockchain-loader.ts +112 -0
  95. package/frontend/lib/cargo-template.ts +64 -0
  96. package/frontend/lib/compilation.ts +529 -0
  97. package/frontend/lib/constants.ts +31 -0
  98. package/frontend/lib/deployment.ts +176 -0
  99. package/frontend/lib/file-utils.ts +83 -0
  100. package/frontend/lib/github-api.ts +246 -0
  101. package/frontend/lib/github-loader.ts +369 -0
  102. package/frontend/lib/ml-contract-template.txt +900 -0
  103. package/frontend/lib/orbit-chains.ts +181 -0
  104. package/frontend/lib/output-formatter.ts +68 -0
  105. package/frontend/lib/project-manager.ts +632 -0
  106. package/frontend/lib/ray-tracing-abi.ts +206 -0
  107. package/frontend/lib/storage.ts +189 -0
  108. package/frontend/lib/templates.ts +1662 -0
  109. package/frontend/lib/url-parser.ts +188 -0
  110. package/frontend/lib/utils.ts +6 -0
  111. package/frontend/lib/wagmi-config.ts +24 -0
  112. package/frontend/next.config.ts +7 -0
  113. package/frontend/package-lock.json +16259 -0
  114. package/frontend/package.json +60 -0
  115. package/frontend/postcss.config.mjs +7 -0
  116. package/frontend/public/file.svg +1 -0
  117. package/frontend/public/globe.svg +1 -0
  118. package/frontend/public/ml-weights/.gitkeep +0 -0
  119. package/frontend/public/ml-weights/model.pkl +0 -0
  120. package/frontend/public/ml-weights/model_weights.json +27102 -0
  121. package/frontend/public/ml-weights/test_samples.json +7888 -0
  122. package/frontend/public/next.svg +1 -0
  123. package/frontend/public/vercel.svg +1 -0
  124. package/frontend/public/window.svg +1 -0
  125. package/frontend/scripts/check-env.js +52 -0
  126. package/frontend/scripts/setup.js +285 -0
  127. package/frontend/tailwind.config.ts +64 -0
  128. package/frontend/tsconfig.json +34 -0
  129. package/frontend/types/blockchain.ts +63 -0
  130. package/frontend/types/github.ts +54 -0
  131. package/frontend/types/project.ts +106 -0
  132. package/ml-training/README.md +56 -0
  133. package/ml-training/train_tiny_model.py +325 -0
  134. package/ml-training/update_template.py +59 -0
  135. package/package.json +30 -0
@@ -0,0 +1,292 @@
1
+ 'use client';
2
+
3
+ import { useState, useRef, useEffect } from 'react';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Textarea } from '@/components/ui/textarea';
6
+ import { ScrollArea } from '@/components/ui/scroll-area';
7
+ import { Send, Trash2, Loader2, Sparkles, Copy, Check } from 'lucide-react';
8
+ import { useChat, type Message } from '@/hooks/useChats';
9
+ import { QuickActions, type QuickAction } from './QuickActions';
10
+ import ReactMarkdown from 'react-markdown';
11
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
12
+ import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
13
+
14
+ interface ChatPanelProps {
15
+ currentCode?: string;
16
+ compilationErrors?: Array<{ line: number; column: number; message: string }>;
17
+ onInsertCode?: (code: string) => void;
18
+ }
19
+
20
+ export function ChatPanel({
21
+ currentCode,
22
+ compilationErrors,
23
+ onInsertCode
24
+ }: ChatPanelProps) {
25
+ const [input, setInput] = useState('');
26
+ const [copiedCode, setCopiedCode] = useState<string | null>(null);
27
+ const { messages, isLoading, error, sendMessage, clearMessages } = useChat();
28
+ const scrollRef = useRef<HTMLDivElement>(null);
29
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
30
+
31
+ // Auto-scroll to bottom on new messages
32
+ useEffect(() => {
33
+ if (scrollRef.current) {
34
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
35
+ }
36
+ }, [messages]);
37
+
38
+ const handleSend = async () => {
39
+ if (!input.trim() || isLoading) return;
40
+
41
+ // Build context from current state
42
+ let context = '';
43
+ if (currentCode) {
44
+ context += `Current code:\n\`\`\`rust\n${currentCode}\n\`\`\`\n\n`;
45
+ }
46
+ if (compilationErrors && compilationErrors.length > 0) {
47
+ context += `Compilation errors:\n${compilationErrors
48
+ .map((e) => `Line ${e.line}:${e.column} - ${e.message}`)
49
+ .join('\n')}\n\n`;
50
+ }
51
+
52
+ await sendMessage(input, context);
53
+ setInput('');
54
+ };
55
+
56
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
57
+ if (e.key === 'Enter' && !e.shiftKey) {
58
+ e.preventDefault();
59
+ handleSend();
60
+ }
61
+ };
62
+
63
+ const handleQuickAction = (action: QuickAction) => {
64
+ const prompt = action.prompt(currentCode);
65
+ setInput(prompt);
66
+
67
+ // Focus textarea and move cursor to end
68
+ setTimeout(() => {
69
+ if (textareaRef.current) {
70
+ textareaRef.current.focus();
71
+ textareaRef.current.setSelectionRange(prompt.length, prompt.length);
72
+ }
73
+ }, 0);
74
+ };
75
+
76
+ const extractCodeFromMessage = (content: string): string[] => {
77
+ const codeBlocks: string[] = [];
78
+ const regex = /```(?:rust|rs)?\n([\s\S]*?)```/g;
79
+ let match;
80
+
81
+ while ((match = regex.exec(content)) !== null) {
82
+ codeBlocks.push(match[1].trim());
83
+ }
84
+
85
+ return codeBlocks;
86
+ };
87
+
88
+ const handleCopyCode = async (code: string) => {
89
+ try {
90
+ await navigator.clipboard.writeText(code);
91
+ setCopiedCode(code);
92
+ setTimeout(() => setCopiedCode(null), 2000);
93
+ } catch (err) {
94
+ console.error('Failed to copy:', err);
95
+ }
96
+ };
97
+
98
+ const handleInsertCode = (code: string) => {
99
+ if (onInsertCode) {
100
+ onInsertCode(code);
101
+ }
102
+ };
103
+
104
+ return (
105
+ <div className="h-full flex flex-col bg-card">
106
+ {/* Header - Responsive */}
107
+ <div className="p-3 sm:p-4 border-b border-border flex items-center justify-between shrink-0">
108
+ <div className="flex items-center gap-2">
109
+ <Sparkles className="h-4 w-4 sm:h-5 sm:w-5 text-primary" />
110
+ <h2 className="font-semibold text-sm sm:text-base">AI Assistant</h2>
111
+ </div>
112
+ {messages.length > 0 && (
113
+ <Button
114
+ variant="ghost"
115
+ size="sm"
116
+ onClick={clearMessages}
117
+ disabled={isLoading}
118
+ className="h-8 w-8 p-0 sm:h-auto sm:w-auto sm:px-3"
119
+ aria-label="Clear chat history"
120
+ >
121
+ <Trash2 className="h-3 w-3 sm:h-4 sm:w-4" />
122
+ <span className="hidden sm:inline ml-2">Clear</span>
123
+ </Button>
124
+ )}
125
+ </div>
126
+
127
+ {/* Messages - Responsive Scrolling */}
128
+ <div className="flex-1 overflow-hidden">
129
+ <ScrollArea className="h-full custom-scrollbar">
130
+ <div className="p-3 sm:p-4 space-y-3 sm:space-y-4">
131
+ {messages.length === 0 && (
132
+ <div className="space-y-4">
133
+ <div className="text-center text-muted-foreground text-sm py-4 space-y-2">
134
+ <Sparkles className="h-6 w-6 sm:h-8 sm:w-8 mx-auto mb-3 text-primary" />
135
+ <p className="font-medium text-sm sm:text-base">Hi! I'm your Stylus AI assistant.</p>
136
+ <p className="text-xs sm:text-sm">Ask me anything about Rust smart contracts!</p>
137
+ </div>
138
+
139
+ {/* Quick Actions - Show when no messages */}
140
+ <QuickActions
141
+ onActionClick={handleQuickAction}
142
+ hasCode={!!currentCode}
143
+ hasErrors={!!compilationErrors && compilationErrors.length > 0}
144
+ />
145
+ </div>
146
+ )}
147
+
148
+ {messages.map((message) => {
149
+ const codeBlocks = message.role === 'assistant'
150
+ ? extractCodeFromMessage(message.content)
151
+ : [];
152
+
153
+ return (
154
+ <div key={message.id} className="space-y-2">
155
+ <div
156
+ className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'
157
+ }`}
158
+ >
159
+ <div
160
+ className={`max-w-[90%] sm:max-w-[85%] rounded-lg p-3 ${message.role === 'user'
161
+ ? 'bg-primary text-primary-foreground'
162
+ : 'bg-muted'
163
+ }`}
164
+ >
165
+ {message.role === 'user' ? (
166
+ <p className="text-sm whitespace-pre-wrap break-anywhere">{message.content}</p>
167
+ ) : (
168
+ <div className="prose prose-sm dark:prose-invert max-w-none">
169
+ <ReactMarkdown
170
+ components={{
171
+ code({ node, className, children, ...props }: any) {
172
+ const match = /language-(\w+)/.exec(className || '');
173
+
174
+ return match ? (
175
+ <SyntaxHighlighter
176
+ style={vscDarkPlus as any}
177
+ language={match[1]}
178
+ PreTag="div"
179
+ className="rounded-md text-xs overflow-x-auto"
180
+ >
181
+ {String(children).replace(/\n$/, '')}
182
+ </SyntaxHighlighter>
183
+ ) : (
184
+ <code className={`${className} wrap-break-words`} {...props}>
185
+ {children}
186
+ </code>
187
+ );
188
+ },
189
+ p: ({ children }) => <p className="wrap-break-words">{children}</p>,
190
+ }}
191
+ >
192
+ {message.content}
193
+ </ReactMarkdown>
194
+ </div>
195
+ )}
196
+ </div>
197
+ </div>
198
+
199
+ {/* Code Action Buttons - Responsive */}
200
+ {codeBlocks.length > 0 && (
201
+ <div className="flex flex-wrap gap-1 sm:gap-2 ml-2">
202
+ {codeBlocks.map((code, index) => (
203
+ <div key={index} className="flex gap-1">
204
+ <Button
205
+ variant="outline"
206
+ size="sm"
207
+ onClick={() => handleCopyCode(code)}
208
+ className="h-6 sm:h-7 text-xs px-2 sm:px-3"
209
+ >
210
+ {copiedCode === code ? (
211
+ <>
212
+ <Check className="h-3 w-3 sm:mr-1" />
213
+ <span className="hidden sm:inline">Copied</span>
214
+ </>
215
+ ) : (
216
+ <>
217
+ <Copy className="h-3 w-3 sm:mr-1" />
218
+ <span className="hidden sm:inline">Copy</span>
219
+ </>
220
+ )}
221
+ </Button>
222
+ {onInsertCode && (
223
+ <Button
224
+ variant="outline"
225
+ size="sm"
226
+ onClick={() => handleInsertCode(code)}
227
+ className="h-6 sm:h-7 text-xs px-2 sm:px-3"
228
+ >
229
+ <span className="hidden sm:inline">Insert to Editor</span>
230
+ <span className="sm:hidden">Insert</span>
231
+ </Button>
232
+ )}
233
+ </div>
234
+ ))}
235
+ </div>
236
+ )}
237
+ </div>
238
+ );
239
+ })}
240
+
241
+ {isLoading && (
242
+ <div className="flex justify-start">
243
+ <div className="bg-muted rounded-lg p-3 flex items-center gap-2">
244
+ <Loader2 className="h-4 w-4 animate-spin" />
245
+ <span className="text-sm text-muted-foreground">Thinking...</span>
246
+ </div>
247
+ </div>
248
+ )}
249
+
250
+ {error && (
251
+ <div className="bg-destructive/10 border border-destructive/20 rounded-lg p-3 text-sm text-destructive wrap-break-words">
252
+ {error}
253
+ </div>
254
+ )}
255
+ </div>
256
+ </ScrollArea>
257
+ </div>
258
+
259
+ {/* Input - Responsive */}
260
+ <div className="p-3 sm:p-4 border-t border-border space-y-2 shrink-0">
261
+ <Textarea
262
+ ref={textareaRef}
263
+ value={input}
264
+ onChange={(e) => setInput(e.target.value)}
265
+ onKeyDown={handleKeyDown}
266
+ placeholder="Ask about Stylus contracts... (Enter to send, Shift+Enter for new line)"
267
+ className="min-h-16 sm:min-h-20 resize-none text-sm"
268
+ disabled={isLoading}
269
+ />
270
+ <Button
271
+ onClick={handleSend}
272
+ disabled={isLoading || !input.trim()}
273
+ className="w-full h-9 sm:h-10"
274
+ >
275
+ {isLoading ? (
276
+ <>
277
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
278
+ <span className="hidden sm:inline">Sending...</span>
279
+ <span className="sm:hidden">...</span>
280
+ </>
281
+ ) : (
282
+ <>
283
+ <Send className="h-4 w-4 mr-2" />
284
+ <span className="hidden sm:inline">Send Message</span>
285
+ <span className="sm:hidden">Send</span>
286
+ </>
287
+ )}
288
+ </Button>
289
+ </div>
290
+ </div>
291
+ );
292
+ }
@@ -0,0 +1,128 @@
1
+ 'use client';
2
+
3
+ import { Button } from '@/components/ui/button';
4
+ import {
5
+ Wand2,
6
+ Bug,
7
+ Zap,
8
+ Code2,
9
+ FileCode,
10
+ ArrowRightLeft,
11
+ HelpCircle,
12
+ Lightbulb
13
+ } from 'lucide-react';
14
+
15
+ export interface QuickAction {
16
+ id: string;
17
+ label: string;
18
+ icon: React.ReactNode;
19
+ prompt: (context?: string) => string;
20
+ requiresCode?: boolean;
21
+ requiresErrors?: boolean;
22
+ }
23
+
24
+ const quickActions: QuickAction[] = [
25
+ {
26
+ id: 'generate',
27
+ label: 'Generate Contract',
28
+ icon: <Wand2 className="h-4 w-4" />,
29
+ prompt: () => 'Generate a Stylus smart contract that ',
30
+ requiresCode: false,
31
+ },
32
+ {
33
+ id: 'explain',
34
+ label: 'Explain Code',
35
+ icon: <HelpCircle className="h-4 w-4" />,
36
+ prompt: (code) => `Explain what this Stylus contract does:\n\`\`\`rust\n${code}\n\`\`\``,
37
+ requiresCode: true,
38
+ },
39
+ {
40
+ id: 'fix-error',
41
+ label: 'Fix Error',
42
+ icon: <Bug className="h-4 w-4" />,
43
+ prompt: (code) => `Help me fix the compilation errors in this Stylus contract:\n\`\`\`rust\n${code}\n\`\`\``,
44
+ requiresCode: true,
45
+ requiresErrors: true,
46
+ },
47
+ {
48
+ id: 'optimize',
49
+ label: 'Optimize Gas',
50
+ icon: <Zap className="h-4 w-4" />,
51
+ prompt: (code) => `Suggest gas optimizations for this Stylus contract:\n\`\`\`rust\n${code}\n\`\`\``,
52
+ requiresCode: true,
53
+ },
54
+ {
55
+ id: 'add-function',
56
+ label: 'Add Function',
57
+ icon: <Code2 className="h-4 w-4" />,
58
+ prompt: (code) => `Add a new function to this contract that `,
59
+ requiresCode: true,
60
+ },
61
+ {
62
+ id: 'convert',
63
+ label: 'Convert Solidity',
64
+ icon: <ArrowRightLeft className="h-4 w-4" />,
65
+ prompt: () => 'Convert this Solidity contract to Stylus:\n\`\`\`solidity\n\n\`\`\`',
66
+ requiresCode: false,
67
+ },
68
+ {
69
+ id: 'add-test',
70
+ label: 'Add Tests',
71
+ icon: <FileCode className="h-4 w-4" />,
72
+ prompt: (code) => `Write unit tests for this Stylus contract:\n\`\`\`rust\n${code}\n\`\`\``,
73
+ requiresCode: true,
74
+ },
75
+ {
76
+ id: 'best-practices',
77
+ label: 'Best Practices',
78
+ icon: <Lightbulb className="h-4 w-4" />,
79
+ prompt: (code) => `Review this contract and suggest best practices:\n\`\`\`rust\n${code}\n\`\`\``,
80
+ requiresCode: true,
81
+ },
82
+ ];
83
+
84
+ interface QuickActionsProps {
85
+ onActionClick: (action: QuickAction) => void;
86
+ hasCode: boolean;
87
+ hasErrors: boolean;
88
+ }
89
+
90
+ export function QuickActions({ onActionClick, hasCode, hasErrors }: QuickActionsProps) {
91
+ return (
92
+ <div className="space-y-3">
93
+ <div className="px-2 sm:px-4">
94
+ <h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">
95
+ Quick Actions
96
+ </h3>
97
+ </div>
98
+
99
+ <div className="grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-2 gap-1.5 sm:gap-2 px-2 sm:px-4">
100
+ {quickActions.map((action) => {
101
+ const isDisabled =
102
+ (action.requiresCode && !hasCode) ||
103
+ (action.requiresErrors && !hasErrors);
104
+
105
+ return (
106
+ <Button
107
+ key={action.id}
108
+ variant="outline"
109
+ size="sm"
110
+ onClick={() => onActionClick(action)}
111
+ disabled={isDisabled}
112
+ className="h-auto py-2 sm:py-3 flex flex-col items-center gap-1 sm:gap-1.5 hover:bg-primary/10 transition-colors"
113
+ >
114
+ <div className="flex-shrink-0">
115
+ {action.icon}
116
+ </div>
117
+ <span className="text-xs leading-tight text-center break-words hyphens-auto">
118
+ {action.label}
119
+ </span>
120
+ </Button>
121
+ );
122
+ })}
123
+ </div>
124
+ </div>
125
+ );
126
+ }
127
+
128
+ export { quickActions };
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+
3
+ import { ExternalLink, Shield, CheckCircle } from 'lucide-react';
4
+ import { Button } from '@/components/ui/button';
5
+
6
+ interface BlockchainContractBannerProps {
7
+ address: string;
8
+ name: string;
9
+ chain: string;
10
+ verified: boolean;
11
+ explorerUrl: string;
12
+ compiler?: string;
13
+ }
14
+
15
+ export function BlockchainContractBanner({
16
+ address,
17
+ name,
18
+ chain,
19
+ verified,
20
+ explorerUrl,
21
+ compiler,
22
+ }: BlockchainContractBannerProps) {
23
+ return (
24
+ <div className="h-10 border-b border-border bg-purple-500/10 flex items-center justify-between px-4 text-xs">
25
+ <div className="flex items-center gap-3 text-purple-600 dark:text-purple-400">
26
+ <div className="flex items-center gap-1.5">
27
+ <Shield className="h-3.5 w-3.5" />
28
+ <span className="font-medium">{name}</span>
29
+ </div>
30
+
31
+ <div className="flex items-center gap-1.5 text-muted-foreground">
32
+ <span className="font-mono">{address.slice(0, 6)}...{address.slice(-4)}</span>
33
+ </div>
34
+
35
+ <div className="flex items-center gap-1.5 text-muted-foreground">
36
+ <span>on {chain}</span>
37
+ </div>
38
+
39
+ {verified && (
40
+ <div className="flex items-center gap-1 text-green-600 dark:text-green-400">
41
+ <CheckCircle className="h-3 w-3" />
42
+ <span className="text-xs">Verified</span>
43
+ </div>
44
+ )}
45
+
46
+ {compiler && (
47
+ <div className="hidden md:block text-muted-foreground text-xs">
48
+ {compiler}
49
+ </div>
50
+ )}
51
+ </div>
52
+
53
+ <Button
54
+ variant="ghost"
55
+ size="sm"
56
+ className="h-6 text-xs gap-1"
57
+ onClick={() => window.open(explorerUrl, '_blank')}
58
+ >
59
+ View on Explorer
60
+ <ExternalLink className="h-3 w-3" />
61
+ </Button>
62
+ </div>
63
+ );
64
+ }
@@ -0,0 +1,188 @@
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, Shield } from 'lucide-react';
14
+ import { Button } from '@/components/ui/button';
15
+ import { BlockchainLoadProgress } from '@/lib/blockchain-loader';
16
+
17
+ interface BlockchainLoadingDialogProps {
18
+ open: boolean;
19
+ onOpenChange: (open: boolean) => void;
20
+ progress: BlockchainLoadProgress | null;
21
+ onRetry?: () => void;
22
+ }
23
+
24
+ export function BlockchainLoadingDialog({
25
+ open,
26
+ onOpenChange,
27
+ progress,
28
+ onRetry,
29
+ }: BlockchainLoadingDialogProps) {
30
+ const [dots, setDots] = useState('');
31
+
32
+ // Animated dots for loading
33
+ useEffect(() => {
34
+ if (progress?.stage === 'fetching' || progress?.stage === 'parsing') {
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 Contract';
61
+ case 'fetching':
62
+ return 'Fetching Contract Data';
63
+ case 'parsing':
64
+ return 'Extracting ABI';
65
+ case 'complete':
66
+ return 'Ready to Interact!';
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
+ <Shield className="h-5 w-5 text-muted-foreground" />
85
+ )}
86
+ <DialogTitle>{getStageTitle()}</DialogTitle>
87
+ </div>
88
+ <DialogDescription>
89
+ {progress?.stage === 'complete'
90
+ ? 'Contract is ready for interaction'
91
+ : progress?.stage === 'error'
92
+ ? 'Failed to load contract'
93
+ : 'Loading contract from blockchain...'}
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 === 'fetching' || progress.stage === 'parsing') && dots}
109
+ </p>
110
+
111
+ {/* Contract Name Display */}
112
+ {progress.contractName && progress.stage !== 'complete' && (
113
+ <div className="text-xs font-medium text-foreground">
114
+ Contract: {progress.contractName}
115
+ </div>
116
+ )}
117
+ </div>
118
+ )}
119
+
120
+ {/* Error Message */}
121
+ {progress?.stage === 'error' && (
122
+ <div className="space-y-3">
123
+ <Alert variant="destructive">
124
+ <XCircle className="h-4 w-4" />
125
+ <AlertDescription className="whitespace-pre-wrap">
126
+ {progress.message}
127
+ </AlertDescription>
128
+ </Alert>
129
+
130
+ {/* Help text for common errors */}
131
+ {progress.message.includes('API key') && (
132
+ <div className="text-xs text-muted-foreground space-y-1">
133
+ <p className="font-semibold">To add an API key:</p>
134
+ <ol className="list-decimal list-inside space-y-1">
135
+ <li>Get a free API key from the explorer</li>
136
+ <li>Add NEXT_PUBLIC_ETHERSCAN_API_KEY to .env.local</li>
137
+ <li>Restart your dev server</li>
138
+ </ol>
139
+ </div>
140
+ )}
141
+
142
+ {progress.message.includes('not verified') && (
143
+ <div className="text-xs text-muted-foreground">
144
+ <p>This contract has not been verified on the blockchain explorer.</p>
145
+ <p className="mt-1">Only verified contracts can be loaded.</p>
146
+ </div>
147
+ )}
148
+
149
+ {progress.message.includes('Network error') && (
150
+ <div className="text-xs text-muted-foreground">
151
+ <p>Check your internet connection and try again.</p>
152
+ </div>
153
+ )}
154
+ </div>
155
+ )}
156
+
157
+ {/* Success Message */}
158
+ {progress?.stage === 'complete' && (
159
+ <Alert>
160
+ <CheckCircle2 className="h-4 w-4" />
161
+ <AlertDescription>
162
+ {progress.contractName || 'Contract'} is ready for interaction
163
+ </AlertDescription>
164
+ </Alert>
165
+ )}
166
+
167
+ {/* Actions */}
168
+ {progress?.stage === 'error' && onRetry && (
169
+ <div className="flex gap-2">
170
+ <Button onClick={onRetry} className="flex-1">
171
+ Retry
172
+ </Button>
173
+ <Button onClick={() => onOpenChange(false)} variant="outline" className="flex-1">
174
+ Cancel
175
+ </Button>
176
+ </div>
177
+ )}
178
+
179
+ {progress?.stage === 'complete' && (
180
+ <Button onClick={() => onOpenChange(false)} className="w-full">
181
+ Start Interacting
182
+ </Button>
183
+ )}
184
+ </div>
185
+ </DialogContent>
186
+ </Dialog>
187
+ );
188
+ }