wuhp commited on
Commit
88a044e
·
verified ·
1 Parent(s): 93320b8

Update App.tsx

Browse files
Files changed (1) hide show
  1. App.tsx +19 -561
App.tsx CHANGED
@@ -1,566 +1,22 @@
1
-
2
- import React, { useCallback, useRef, useState, useMemo, useEffect } from 'react';
3
- import ReactFlow, {
4
- ReactFlowProvider,
5
- addEdge,
6
- useNodesState,
7
- useEdgesState,
8
- Controls,
9
- Background,
10
- Connection,
11
- Edge,
12
- Node,
13
- BackgroundVariant,
14
- Panel,
15
- ConnectionMode
16
- } from 'reactflow';
17
- import { FileText, CheckCircle, AlertTriangle, X, Copy, Check, Lightbulb, Key, Wrench, Menu, Activity } from 'lucide-react';
18
- import Sidebar from './components/Sidebar';
19
- import PropertiesPanel from './components/PropertiesPanel';
20
- import CustomNode from './components/CustomNode';
21
- import CodeViewer from './components/CodeViewer';
22
- import AIBuilderModal from './components/AIBuilderModal';
23
- import SuggestionsModal from './components/SuggestionsModal';
24
- import ApiKeyModal from './components/ApiKeyModal';
25
- import FixerModal from './components/FixerModal';
26
  import LandingPage from './components/LandingPage';
27
- import { INITIAL_NODES, INITIAL_EDGES, LAYER_DEFINITIONS, TEMPLATES } from './constants';
28
- import { generateRefinedPrompt, validateArchitecture, getArchitectureSuggestions, getUserApiKey, implementArchitectureSuggestions } from './services/geminiService';
29
- import { NodeData, LayerType, LogEntry } from './types';
30
-
31
- let id = 1000;
32
- const getId = () => `${id++}`;
33
-
34
- // --- MAIN BUILDER CONTENT ---
35
- interface BuilderProps {
36
- onBackToHome: () => void;
37
- }
38
-
39
- const Builder: React.FC<BuilderProps> = ({ onBackToHome }) => {
40
- const reactFlowWrapper = useRef<HTMLDivElement>(null);
41
- const [nodes, setNodes, onNodesChange] = useNodesState(INITIAL_NODES);
42
- const [edges, setEdges, onEdgesChange] = useEdgesState(INITIAL_EDGES);
43
- const [reactFlowInstance, setReactFlowInstance] = useState<any>(null);
44
- const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
45
-
46
- const [generatedPrompt, setGeneratedPrompt] = useState('');
47
- const [isPromptViewerOpen, setIsPromptViewerOpen] = useState(false);
48
- const [isGeneratingPrompt, setIsGeneratingPrompt] = useState(false);
49
-
50
- const [validationMsg, setValidationMsg] = useState<string | null>(null);
51
- const [layoutCopied, setLayoutCopied] = useState(false);
52
-
53
- // Responsive Layout State
54
- const [isSidebarOpen, setIsSidebarOpen] = useState(true);
55
- const [isPropertiesOpen, setIsPropertiesOpen] = useState(false);
56
-
57
- // Suggestions State
58
- const [isSuggestionsOpen, setIsSuggestionsOpen] = useState(false);
59
- const [suggestionsText, setSuggestionsText] = useState('');
60
- const [isSuggestionsLoading, setIsSuggestionsLoading] = useState(false);
61
- const [isImplementingSuggestions, setIsImplementingSuggestions] = useState(false);
62
-
63
- // Fixer State
64
- const [isFixerOpen, setIsFixerOpen] = useState(false);
65
-
66
- // AI Builder and Template State
67
- const [isAIBuilderOpen, setIsAIBuilderOpen] = useState(false);
68
- const [isTemplateMenuOpen, setIsTemplateMenuOpen] = useState(false);
69
-
70
- // API Key State
71
- const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false);
72
- const [isConnected, setIsConnected] = useState(false);
73
-
74
- // Activity Log State
75
- const [logs, setLogs] = useState<LogEntry[]>([]);
76
-
77
- const addLog = useCallback((message: string, type: 'info' | 'success' | 'warning' | 'error' = 'info') => {
78
- setLogs(prev => [{
79
- id: Date.now().toString() + Math.random(),
80
- timestamp: new Date(),
81
- message,
82
- type
83
- }, ...prev]);
84
- }, []);
85
-
86
- // Initialize responsiveness
87
- useEffect(() => {
88
- const handleResize = () => {
89
- if (window.innerWidth < 768) {
90
- setIsSidebarOpen(false);
91
- } else {
92
- setIsSidebarOpen(true);
93
- }
94
- };
95
-
96
- // Set initial state
97
- handleResize();
98
-
99
- window.addEventListener('resize', handleResize);
100
- return () => window.removeEventListener('resize', handleResize);
101
- }, []);
102
-
103
- // Load connection status on mount
104
- useEffect(() => {
105
- setIsConnected(!!getUserApiKey());
106
- addLog('System initialized. Ready to build.', 'info');
107
- }, [addLog]);
108
-
109
- // Define custom node types
110
- const nodeTypes = useMemo(() => ({ custom: CustomNode }), []);
111
-
112
- const onConnect = useCallback(
113
- (params: Connection) => setEdges((eds) => addEdge({ ...params, animated: true, style: { stroke: '#94a3b8' } }, eds)),
114
- [setEdges]
115
- );
116
-
117
- const onDragOver = useCallback((event: React.DragEvent) => {
118
- event.preventDefault();
119
- event.dataTransfer.dropEffect = 'move';
120
- }, []);
121
-
122
- const onDrop = useCallback(
123
- (event: React.DragEvent) => {
124
- event.preventDefault();
125
-
126
- if (!reactFlowWrapper.current || !reactFlowInstance) return;
127
-
128
- const type = event.dataTransfer.getData('application/reactflow') as LayerType;
129
-
130
- if (!type) return;
131
-
132
- const position = reactFlowInstance.screenToFlowPosition({
133
- x: event.clientX,
134
- y: event.clientY,
135
- });
136
-
137
- const label = LAYER_DEFINITIONS[type].label;
138
- const newNode: Node<NodeData> = {
139
- id: getId(),
140
- type: 'custom',
141
- position,
142
- data: {
143
- label: label,
144
- type: type,
145
- params: LAYER_DEFINITIONS[type].parameters.reduce((acc, p) => ({...acc, [p.name]: p.default}), {})
146
- },
147
- };
148
-
149
- setNodes((nds) => nds.concat(newNode));
150
- setSelectedNodeId(newNode.id);
151
- setIsPropertiesOpen(true); // Open properties panel on drop
152
-
153
- // On mobile, close sidebar after dropping
154
- if (window.innerWidth < 768) {
155
- setIsSidebarOpen(false);
156
- }
157
-
158
- addLog(`Added layer: ${label}`, 'info');
159
- },
160
- [reactFlowInstance, setNodes, addLog]
161
- );
162
-
163
- const onNodeClick = useCallback((_: React.MouseEvent, node: Node) => {
164
- setSelectedNodeId(node.id);
165
- setIsPropertiesOpen(true);
166
- }, []);
167
-
168
- const onPaneClick = useCallback(() => {
169
- setSelectedNodeId(null);
170
- setIsPropertiesOpen(false);
171
- }, []);
172
 
173
- // Update node data (params or label) from Properties Panel
174
- const updateNodeData = (id: string, newData: Partial<NodeData>) => {
175
- setNodes((nds) =>
176
- nds.map((node) => {
177
- if (node.id === id) {
178
- return {
179
- ...node,
180
- data: { ...node.data, ...newData }
181
- };
182
- }
183
- return node;
184
- })
185
- );
186
- };
187
 
188
- const deleteNode = (id: string) => {
189
- const node = nodes.find(n => n.id === id);
190
- setNodes((nds) => nds.filter((node) => node.id !== id));
191
- setEdges((eds) => eds.filter((edge) => edge.source !== id && edge.target !== id));
192
- setSelectedNodeId(null);
193
- setIsPropertiesOpen(false);
194
- if (node) addLog(`Deleted layer: ${node.data.label}`, 'info');
195
- };
196
-
197
- const handleAPIError = (error: any) => {
198
- const msg = error.message || String(error);
199
- addLog(`API Error: ${msg}`, 'error');
200
-
201
- // Force disconnect if authentication failed to allow user to re-enter key
202
- if (msg.includes('API Key') || msg.includes('403') || msg.includes('401') || msg.includes('400')) {
203
- setIsConnected(false);
204
- setIsApiKeyModalOpen(true);
205
- addLog("Disconnected due to invalid API key. Please check your key.", 'warning');
206
- }
207
- }
208
-
209
- const handleGeneratePrompt = async () => {
210
- if (!isConnected) { setIsApiKeyModalOpen(true); return; }
211
- setIsGeneratingPrompt(true);
212
- setIsPromptViewerOpen(true);
213
- addLog("Generating PyTorch code prompt...", 'info');
214
- try {
215
- const promptText = await generateRefinedPrompt(nodes, edges);
216
- setGeneratedPrompt(promptText);
217
- addLog("Code prompt generated successfully.", 'success');
218
- } catch (e) {
219
- setGeneratedPrompt("Failed to generate prompt. See logs for details.");
220
- handleAPIError(e);
221
- } finally {
222
- setIsGeneratingPrompt(false);
223
- }
224
- };
225
-
226
- const handleValidate = async () => {
227
- if (!isConnected) { setIsApiKeyModalOpen(true); return; }
228
- setValidationMsg("Validating...");
229
- addLog("Running architecture validation...", 'info');
230
- try {
231
- const result = await validateArchitecture(nodes, edges);
232
- setValidationMsg(result);
233
- if (result.toLowerCase().includes("valid") && !result.toLowerCase().includes("invalid") && result.length < 50) {
234
- setTimeout(() => setValidationMsg(null), 5000);
235
- addLog("Validation passed: Architecture is valid.", 'success');
236
- } else {
237
- addLog("Validation issues found.", 'warning');
238
- }
239
- } catch (e) {
240
- setValidationMsg("Validation failed due to API error.");
241
- handleAPIError(e);
242
- }
243
- };
244
-
245
- const handleGetSuggestions = async () => {
246
- if (!isConnected) { setIsApiKeyModalOpen(true); return; }
247
- setIsSuggestionsOpen(true);
248
- setIsSuggestionsLoading(true);
249
- try {
250
- const suggestions = await getArchitectureSuggestions(nodes, edges);
251
- setSuggestionsText(suggestions);
252
- } catch (error) {
253
- setSuggestionsText("Failed to get suggestions. API Error.");
254
- handleAPIError(error);
255
- } finally {
256
- setIsSuggestionsLoading(false);
257
- }
258
- };
259
-
260
- const handleImplementSuggestions = async () => {
261
- if (!suggestionsText || isImplementingSuggestions) return;
262
- setIsImplementingSuggestions(true);
263
- addLog("AI Agents implementing suggestions...", 'info');
264
- try {
265
- const result = await implementArchitectureSuggestions(nodes, edges, suggestionsText);
266
- if (result && result.nodes) {
267
- // Fix types and defaults safely
268
- const processedNodes = result.nodes.map((n: any) => {
269
- const data = n.data || {};
270
- const type = data.type || n.type || 'Identity'; // Fallback type
271
- return {
272
- ...n,
273
- type: 'custom',
274
- data: {
275
- ...data,
276
- type: type,
277
- label: data.label || n.label || type, // Safe fallback for label
278
- params: data.params || {}
279
- }
280
- };
281
- });
282
-
283
- const processedEdges = result.edges.map((e: any) => ({
284
- ...e, animated: true, style: { stroke: '#94a3b8' }
285
- }));
286
-
287
- setNodes(processedNodes);
288
- setEdges(processedEdges);
289
- setIsSuggestionsOpen(false); // Close modal on success
290
- addLog("Implemented AI improvements.", 'success');
291
- }
292
- } catch (e) {
293
- handleAPIError(e);
294
- addLog("Failed to implement suggestions.", 'error');
295
- } finally {
296
- setIsImplementingSuggestions(false);
297
- }
298
- };
299
-
300
- const handleCopyLayout = () => {
301
- const layout = {
302
- nodes,
303
- edges
304
- };
305
- navigator.clipboard.writeText(JSON.stringify(layout, null, 2));
306
- setLayoutCopied(true);
307
- setTimeout(() => setLayoutCopied(false), 2000);
308
- addLog("Layout JSON copied to clipboard.", 'info');
309
- };
310
-
311
- const handleApplyAIBuild = (newNodes: any[], newEdges: any[], logMsg?: string) => {
312
- setNodes(newNodes);
313
- setEdges(newEdges);
314
- if (logMsg) {
315
- addLog(logMsg, 'success');
316
- }
317
- };
318
-
319
- const loadTemplate = (templateKey: string) => {
320
- const template = TEMPLATES[templateKey];
321
- if (template) {
322
- // Need to deep clone to avoid reference issues if loaded multiple times
323
- const clonedNodes = JSON.parse(JSON.stringify(template.nodes));
324
- const clonedEdges = JSON.parse(JSON.stringify(template.edges));
325
-
326
- // Update styling and functional props that aren't in JSON
327
- const hydratedNodes = clonedNodes.map((n: any) => ({
328
- ...n,
329
- data: {
330
- ...n.data,
331
- label: LAYER_DEFINITIONS[n.data.type as LayerType]?.label || n.data.label
332
- }
333
- }));
334
-
335
- const hydratedEdges = clonedEdges.map((e: any) => ({
336
- ...e,
337
- animated: true,
338
- style: { stroke: '#94a3b8' }
339
- }));
340
-
341
- setNodes(hydratedNodes);
342
- setEdges(hydratedEdges);
343
- setIsTemplateMenuOpen(false);
344
-
345
- // Auto open properties to see details of first node
346
- if (hydratedNodes.length > 0) {
347
- setSelectedNodeId(hydratedNodes[0].id);
348
- setIsPropertiesOpen(true);
349
- }
350
- addLog(`Loaded template: ${template.name}`, 'info');
351
- }
352
- };
353
-
354
- const selectedNode = useMemo(() =>
355
- nodes.find((n) => n.id === selectedNodeId) || null,
356
- [nodes, selectedNodeId]
357
- );
358
-
359
- return (
360
- <div className="flex h-screen w-screen bg-slate-950 overflow-hidden">
361
- <Sidebar
362
- onOpenAIBuilder={() => isConnected ? setIsAIBuilderOpen(true) : setIsApiKeyModalOpen(true)}
363
- onSelectTemplate={(id) => id === 'menu' ? setIsTemplateMenuOpen(true) : loadTemplate(id)}
364
- onBackToHome={onBackToHome}
365
- isConnected={isConnected}
366
- isOpen={isSidebarOpen}
367
- onToggle={() => setIsSidebarOpen(!isSidebarOpen)}
368
- />
369
-
370
- <div className="flex-1 h-full relative" ref={reactFlowWrapper}>
371
- <ReactFlow
372
- nodes={nodes}
373
- edges={edges}
374
- onNodesChange={onNodesChange}
375
- onEdgesChange={onEdgesChange}
376
- onConnect={onConnect}
377
- onInit={setReactFlowInstance}
378
- onDrop={onDrop}
379
- onDragOver={onDragOver}
380
- onNodeClick={onNodeClick}
381
- onPaneClick={onPaneClick}
382
- nodeTypes={nodeTypes}
383
- connectionMode={ConnectionMode.Loose}
384
- fitView
385
- className="bg-slate-950"
386
- >
387
- <Background color="#1e293b" variant={BackgroundVariant.Dots} gap={20} size={1} />
388
- <Controls className="!bg-slate-800 !border-slate-700 [&>button]:!fill-slate-300 [&>button:hover]:!bg-slate-700 md:bottom-4 md:left-4 bottom-24 left-4" />
389
-
390
- {/* Mobile Menu Trigger */}
391
- <Panel position="top-left" className="md:hidden">
392
- <button
393
- onClick={() => setIsSidebarOpen(true)}
394
- className="p-2 bg-slate-800/80 backdrop-blur border border-slate-700 rounded-lg text-slate-300 shadow-lg"
395
- >
396
- <Menu size={20} />
397
- </button>
398
- </Panel>
399
-
400
- {/* Action Toolbar */}
401
- <Panel position="top-right" className="flex flex-wrap justify-end gap-2 max-w-[80vw]">
402
- <button
403
- onClick={handleCopyLayout}
404
- className="flex items-center gap-2 px-3 py-2 bg-slate-800/80 backdrop-blur hover:bg-slate-700 border border-slate-700 rounded-lg text-slate-200 text-xs md:text-sm font-medium transition-colors shadow-lg"
405
- title="Copy Layout JSON"
406
- >
407
- {layoutCopied ? <Check size={16} className="text-emerald-500" /> : <Copy size={16} className="text-slate-400" />}
408
- <span className="hidden md:inline">{layoutCopied ? 'Copied' : 'Copy'}</span>
409
- </button>
410
- <button
411
- onClick={() => setIsApiKeyModalOpen(true)}
412
- className="flex items-center gap-2 px-3 py-2 bg-slate-800/80 backdrop-blur hover:bg-slate-700 border border-slate-700 rounded-lg text-slate-200 text-xs md:text-sm font-medium transition-colors shadow-lg"
413
- title="API Key Settings"
414
- >
415
- <Key size={16} className={isConnected ? "text-emerald-500" : "text-slate-400"} />
416
- <span className="hidden md:inline">{isConnected ? 'Key Active' : 'Add Key'}</span>
417
- </button>
418
- <button
419
- onClick={handleGetSuggestions}
420
- className="flex items-center gap-2 px-3 py-2 bg-slate-800/80 backdrop-blur hover:bg-slate-700 border border-slate-700 rounded-lg text-slate-200 text-xs md:text-sm font-medium transition-colors shadow-lg"
421
- title="Get AI Suggestions"
422
- >
423
- <Lightbulb size={16} className="text-amber-400" />
424
- <span className="hidden md:inline">Tips</span>
425
- </button>
426
- <button
427
- onClick={handleValidate}
428
- className="flex items-center gap-2 px-3 py-2 bg-slate-800/80 backdrop-blur hover:bg-slate-700 border border-slate-700 rounded-lg text-slate-200 text-xs md:text-sm font-medium transition-colors shadow-lg"
429
- title="Validate Architecture"
430
- >
431
- <CheckCircle size={16} className="text-emerald-500" />
432
- <span className="hidden md:inline">Validate</span>
433
- </button>
434
- <button
435
- onClick={handleGeneratePrompt}
436
- className="flex items-center gap-2 px-3 py-2 bg-blue-600/90 hover:bg-blue-500 backdrop-blur rounded-lg text-white text-xs md:text-sm font-medium transition-colors shadow-lg shadow-blue-900/20"
437
- title="Generate Code Prompt"
438
- >
439
- <FileText size={16} />
440
- <span className="hidden md:inline">Code</span>
441
- </button>
442
- {/* Mobile Activity Toggle */}
443
- <button
444
- onClick={() => { setSelectedNodeId(null); setIsPropertiesOpen(true); }}
445
- className="md:hidden flex items-center gap-2 px-3 py-2 bg-slate-800/80 backdrop-blur hover:bg-slate-700 border border-slate-700 rounded-lg text-slate-200 transition-colors shadow-lg"
446
- title="System Logs"
447
- >
448
- <Activity size={16} className="text-blue-400" />
449
- </button>
450
- </Panel>
451
-
452
- {validationMsg && (
453
- <Panel position="bottom-center" className="mb-20 md:mb-8 w-full flex justify-center px-4">
454
- <div className="bg-slate-900/95 backdrop-blur border border-slate-700 text-slate-200 px-6 py-4 rounded-xl shadow-2xl max-w-2xl w-full flex flex-col gap-3 animate-in slide-in-from-bottom-5">
455
- <div className="flex items-start gap-3">
456
- <AlertTriangle className="text-amber-500 shrink-0 mt-0.5" size={20} />
457
- <div className="flex-1 min-w-0">
458
- <h4 className="font-bold text-sm text-slate-300 mb-1">Validation Report</h4>
459
- <p className="text-sm whitespace-pre-wrap leading-relaxed text-slate-400 max-h-[30vh] overflow-y-auto scrollbar-thin scrollbar-thumb-slate-700 pr-2">{validationMsg}</p>
460
- </div>
461
- </div>
462
-
463
- <div className="flex justify-end gap-3 mt-2">
464
- <button
465
- onClick={() => setValidationMsg(null)}
466
- className="px-4 py-1.5 text-xs font-medium text-slate-400 hover:text-slate-200 bg-slate-800 hover:bg-slate-700 rounded transition-colors"
467
- >
468
- Dismiss
469
- </button>
470
- {!validationMsg.toLowerCase().includes("architecture is valid") && (
471
- <button
472
- onClick={() => setIsFixerOpen(true)}
473
- className="flex items-center gap-2 px-4 py-1.5 bg-red-600 hover:bg-red-500 text-white rounded text-xs font-medium transition-colors shadow-lg shadow-red-900/20"
474
- >
475
- <Wrench size={14} />
476
- Fix Errors
477
- </button>
478
- )}
479
- </div>
480
- </div>
481
- </Panel>
482
- )}
483
-
484
- {/* Template Selection Modal Overlay */}
485
- {isTemplateMenuOpen && (
486
- <div className="absolute inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
487
- <div className="bg-slate-900 border border-slate-700 rounded-xl shadow-2xl w-96 max-w-full flex flex-col max-h-[80vh] overflow-hidden animate-in zoom-in-95 duration-200">
488
- <div className="flex justify-between items-center p-6 border-b border-slate-800 shrink-0 bg-slate-900">
489
- <h3 className="text-lg font-bold text-white">Select Template</h3>
490
- <button onClick={() => setIsTemplateMenuOpen(false)}><X size={20} className="text-slate-500 hover:text-white"/></button>
491
- </div>
492
- <div className="overflow-y-auto p-6 scrollbar-thin scrollbar-thumb-slate-700">
493
- <div className="grid gap-3">
494
- {Object.entries(TEMPLATES).map(([key, t]) => (
495
- <button
496
- key={key}
497
- onClick={() => loadTemplate(key)}
498
- className="text-left p-3 rounded bg-slate-800 hover:bg-slate-750 border border-slate-700 hover:border-blue-500/50 transition-all group"
499
- >
500
- <div className="font-semibold text-slate-200 group-hover:text-blue-400">{t.name}</div>
501
- <div className="text-xs text-slate-500">{t.description}</div>
502
- </button>
503
- ))}
504
- </div>
505
- </div>
506
- </div>
507
- </div>
508
- )}
509
-
510
- </ReactFlow>
511
- </div>
512
-
513
- <PropertiesPanel
514
- selectedNode={selectedNode}
515
- onChange={updateNodeData}
516
- onDelete={deleteNode}
517
- onClose={() => { setSelectedNodeId(null); setIsPropertiesOpen(false); }}
518
- logs={logs}
519
- isOpen={isPropertiesOpen}
520
- />
521
-
522
- <CodeViewer
523
- isOpen={isPromptViewerOpen}
524
- onClose={() => setIsPromptViewerOpen(false)}
525
- code={generatedPrompt}
526
- isLoading={isGeneratingPrompt}
527
- />
528
-
529
- <SuggestionsModal
530
- isOpen={isSuggestionsOpen}
531
- onClose={() => setIsSuggestionsOpen(false)}
532
- suggestions={suggestionsText}
533
- isLoading={isSuggestionsLoading}
534
- onImplement={handleImplementSuggestions}
535
- isImplementing={isImplementingSuggestions}
536
- />
537
-
538
- <AIBuilderModal
539
- isOpen={isAIBuilderOpen}
540
- onClose={() => setIsAIBuilderOpen(false)}
541
- onApply={handleApplyAIBuild}
542
- currentNodes={nodes}
543
- />
544
-
545
- <FixerModal
546
- isOpen={isFixerOpen}
547
- onClose={() => setIsFixerOpen(false)}
548
- onApply={handleApplyAIBuild}
549
- errorMsg={validationMsg || ''}
550
- nodes={nodes}
551
- edges={edges}
552
- />
553
-
554
- <ApiKeyModal
555
- isOpen={isApiKeyModalOpen}
556
- onClose={() => setIsApiKeyModalOpen(false)}
557
- onSuccess={() => setIsConnected(true)}
558
- />
559
- </div>
560
- );
561
- };
562
 
563
- const Main = () => {
564
  const [showLanding, setShowLanding] = useState(true);
565
 
566
  if (showLanding) {
@@ -569,9 +25,11 @@ const Main = () => {
569
 
570
  return (
571
  <ReactFlowProvider>
572
- <Builder onBackToHome={() => setShowLanding(true)} />
 
 
573
  </ReactFlowProvider>
574
  );
575
  };
576
 
577
- export default Main;
 
1
+ import React, { useState, Suspense } from 'react';
2
+ import { ReactFlowProvider } from 'reactflow';
3
+ import { Sparkles } from 'lucide-react';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import LandingPage from './components/LandingPage';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
+ // Lazy load the heavy builder component to improve initial load performance
7
+ const Builder = React.lazy(() => import('./components/Builder'));
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ // Loading screen fallback for Suspense
10
+ const LoadingScreen = () => (
11
+ <div className="fixed inset-0 bg-slate-950 flex items-center justify-center text-white z-50">
12
+ <div className="flex flex-col items-center gap-4">
13
+ <Sparkles className="animate-spin text-blue-500" size={32} />
14
+ <p className="text-slate-400 text-sm animate-pulse font-medium">Loading Builder Environment...</p>
15
+ </div>
16
+ </div>
17
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ const App = () => {
20
  const [showLanding, setShowLanding] = useState(true);
21
 
22
  if (showLanding) {
 
25
 
26
  return (
27
  <ReactFlowProvider>
28
+ <Suspense fallback={<LoadingScreen />}>
29
+ <Builder onBackToHome={() => setShowLanding(true)} />
30
+ </Suspense>
31
  </ReactFlowProvider>
32
  );
33
  };
34
 
35
+ export default App;