wuhp commited on
Commit
b0c12de
·
verified ·
1 Parent(s): f4fff4e

Update App.tsx

Browse files
Files changed (1) hide show
  1. App.tsx +84 -22
App.tsx CHANGED
@@ -1,5 +1,4 @@
1
 
2
-
3
  import React, { useCallback, useRef, useState, useMemo, useEffect } from 'react';
4
  import ReactFlow, {
5
  ReactFlowProvider,
@@ -15,7 +14,7 @@ import ReactFlow, {
15
  Panel,
16
  ConnectionMode
17
  } from 'reactflow';
18
- import { FileText, CheckCircle, AlertTriangle, X, Copy, Check, Lightbulb, Key, Wrench } from 'lucide-react';
19
  import Sidebar from './components/Sidebar';
20
  import PropertiesPanel from './components/PropertiesPanel';
21
  import CustomNode from './components/CustomNode';
@@ -45,6 +44,10 @@ const AppContent = () => {
45
  const [validationMsg, setValidationMsg] = useState<string | null>(null);
46
  const [layoutCopied, setLayoutCopied] = useState(false);
47
 
 
 
 
 
48
  // Suggestions State
49
  const [isSuggestionsOpen, setIsSuggestionsOpen] = useState(false);
50
  const [suggestionsText, setSuggestionsText] = useState('');
@@ -74,6 +77,23 @@ const AppContent = () => {
74
  }, ...prev]);
75
  }, []);
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  // Load connection status on mount
78
  useEffect(() => {
79
  setIsConnected(!!getUserApiKey());
@@ -122,6 +142,13 @@ const AppContent = () => {
122
 
123
  setNodes((nds) => nds.concat(newNode));
124
  setSelectedNodeId(newNode.id);
 
 
 
 
 
 
 
125
  addLog(`Added layer: ${label}`, 'info');
126
  },
127
  [reactFlowInstance, setNodes, addLog]
@@ -129,10 +156,12 @@ const AppContent = () => {
129
 
130
  const onNodeClick = useCallback((_: React.MouseEvent, node: Node) => {
131
  setSelectedNodeId(node.id);
 
132
  }, []);
133
 
134
  const onPaneClick = useCallback(() => {
135
  setSelectedNodeId(null);
 
136
  }, []);
137
 
138
  // Update node data (params or label) from Properties Panel
@@ -155,6 +184,7 @@ const AppContent = () => {
155
  setNodes((nds) => nds.filter((node) => node.id !== id));
156
  setEdges((eds) => eds.filter((edge) => edge.source !== id && edge.target !== id));
157
  setSelectedNodeId(null);
 
158
  if (node) addLog(`Deleted layer: ${node.data.label}`, 'info');
159
  };
160
 
@@ -239,7 +269,6 @@ const AppContent = () => {
239
  } catch (e) {
240
  console.error(e);
241
  addLog("Failed to implement suggestions.", 'error');
242
- // alert("Failed to implement suggestions automatically.");
243
  } finally {
244
  setIsImplementingSuggestions(false);
245
  }
@@ -289,6 +318,12 @@ const AppContent = () => {
289
  setNodes(hydratedNodes);
290
  setEdges(hydratedEdges);
291
  setIsTemplateMenuOpen(false);
 
 
 
 
 
 
292
  addLog(`Loaded template: ${template.name}`, 'info');
293
  }
294
  };
@@ -299,11 +334,13 @@ const AppContent = () => {
299
  );
300
 
301
  return (
302
- <div className="flex h-screen w-screen bg-slate-950">
303
  <Sidebar
304
  onOpenAIBuilder={() => isConnected ? setIsAIBuilderOpen(true) : setIsApiKeyModalOpen(true)}
305
  onSelectTemplate={(id) => id === 'menu' ? setIsTemplateMenuOpen(true) : loadTemplate(id)}
306
  isConnected={isConnected}
 
 
307
  />
308
 
309
  <div className="flex-1 h-full relative" ref={reactFlowWrapper}>
@@ -324,54 +361,78 @@ const AppContent = () => {
324
  className="bg-slate-950"
325
  >
326
  <Background color="#1e293b" variant={BackgroundVariant.Dots} gap={20} size={1} />
327
- <Controls className="!bg-slate-800 !border-slate-700 [&>button]:!fill-slate-300 [&>button:hover]:!bg-slate-700" />
328
 
329
- <Panel position="top-right" className="flex gap-2">
 
 
 
 
 
 
 
 
 
 
 
330
  <button
331
  onClick={handleCopyLayout}
332
- className="flex items-center gap-2 px-4 py-2 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-lg text-slate-200 text-sm font-medium transition-colors shadow-lg"
 
333
  >
334
  {layoutCopied ? <Check size={16} className="text-emerald-500" /> : <Copy size={16} className="text-slate-400" />}
335
- {layoutCopied ? 'Copied' : 'Copy Layout'}
336
  </button>
337
  <button
338
  onClick={() => setIsApiKeyModalOpen(true)}
339
- className="flex items-center gap-2 px-4 py-2 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-lg text-slate-200 text-sm font-medium transition-colors shadow-lg"
 
340
  >
341
  <Key size={16} className={isConnected ? "text-emerald-500" : "text-slate-400"} />
342
- {isConnected ? 'Key Active' : 'Add Key'}
343
  </button>
344
  <button
345
  onClick={handleGetSuggestions}
346
- className="flex items-center gap-2 px-4 py-2 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-lg text-slate-200 text-sm font-medium transition-colors shadow-lg"
 
347
  >
348
  <Lightbulb size={16} className="text-amber-400" />
349
- Suggestions
350
  </button>
351
  <button
352
  onClick={handleValidate}
353
- className="flex items-center gap-2 px-4 py-2 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-lg text-slate-200 text-sm font-medium transition-colors shadow-lg"
 
354
  >
355
  <CheckCircle size={16} className="text-emerald-500" />
356
- Validate
357
  </button>
358
  <button
359
  onClick={handleGeneratePrompt}
360
- className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-500 rounded-lg text-white text-sm font-medium transition-colors shadow-lg shadow-blue-900/20"
 
361
  >
362
  <FileText size={16} />
363
- Generate Prompt
 
 
 
 
 
 
 
 
364
  </button>
365
  </Panel>
366
 
367
  {validationMsg && (
368
- <Panel position="bottom-center" className="mb-8 w-full flex justify-center">
369
- <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">
370
  <div className="flex items-start gap-3">
371
  <AlertTriangle className="text-amber-500 shrink-0 mt-0.5" size={20} />
372
  <div className="flex-1 min-w-0">
373
  <h4 className="font-bold text-sm text-slate-300 mb-1">Validation Report</h4>
374
- <p className="text-sm whitespace-pre-wrap leading-relaxed text-slate-400 max-h-[50vh] overflow-y-auto scrollbar-thin scrollbar-thumb-slate-700 pr-2">{validationMsg}</p>
375
  </div>
376
  </div>
377
 
@@ -388,7 +449,7 @@ const AppContent = () => {
388
  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"
389
  >
390
  <Wrench size={14} />
391
- Fix Errors (Auto)
392
  </button>
393
  )}
394
  </div>
@@ -398,7 +459,7 @@ const AppContent = () => {
398
 
399
  {/* Template Selection Modal Overlay */}
400
  {isTemplateMenuOpen && (
401
- <div className="absolute inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4">
402
  <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">
403
  <div className="flex justify-between items-center p-6 border-b border-slate-800 shrink-0 bg-slate-900">
404
  <h3 className="text-lg font-bold text-white">Select Template</h3>
@@ -429,8 +490,9 @@ const AppContent = () => {
429
  selectedNode={selectedNode}
430
  onChange={updateNodeData}
431
  onDelete={deleteNode}
432
- onClose={() => setSelectedNodeId(null)}
433
  logs={logs}
 
434
  />
435
 
436
  <CodeViewer
 
1
 
 
2
  import React, { useCallback, useRef, useState, useMemo, useEffect } from 'react';
3
  import ReactFlow, {
4
  ReactFlowProvider,
 
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';
 
44
  const [validationMsg, setValidationMsg] = useState<string | null>(null);
45
  const [layoutCopied, setLayoutCopied] = useState(false);
46
 
47
+ // Responsive Layout State
48
+ const [isSidebarOpen, setIsSidebarOpen] = useState(true);
49
+ const [isPropertiesOpen, setIsPropertiesOpen] = useState(false);
50
+
51
  // Suggestions State
52
  const [isSuggestionsOpen, setIsSuggestionsOpen] = useState(false);
53
  const [suggestionsText, setSuggestionsText] = useState('');
 
77
  }, ...prev]);
78
  }, []);
79
 
80
+ // Initialize responsiveness
81
+ useEffect(() => {
82
+ const handleResize = () => {
83
+ if (window.innerWidth < 768) {
84
+ setIsSidebarOpen(false);
85
+ } else {
86
+ setIsSidebarOpen(true);
87
+ }
88
+ };
89
+
90
+ // Set initial state
91
+ handleResize();
92
+
93
+ window.addEventListener('resize', handleResize);
94
+ return () => window.removeEventListener('resize', handleResize);
95
+ }, []);
96
+
97
  // Load connection status on mount
98
  useEffect(() => {
99
  setIsConnected(!!getUserApiKey());
 
142
 
143
  setNodes((nds) => nds.concat(newNode));
144
  setSelectedNodeId(newNode.id);
145
+ setIsPropertiesOpen(true); // Open properties panel on drop
146
+
147
+ // On mobile, close sidebar after dropping
148
+ if (window.innerWidth < 768) {
149
+ setIsSidebarOpen(false);
150
+ }
151
+
152
  addLog(`Added layer: ${label}`, 'info');
153
  },
154
  [reactFlowInstance, setNodes, addLog]
 
156
 
157
  const onNodeClick = useCallback((_: React.MouseEvent, node: Node) => {
158
  setSelectedNodeId(node.id);
159
+ setIsPropertiesOpen(true);
160
  }, []);
161
 
162
  const onPaneClick = useCallback(() => {
163
  setSelectedNodeId(null);
164
+ setIsPropertiesOpen(false);
165
  }, []);
166
 
167
  // Update node data (params or label) from Properties Panel
 
184
  setNodes((nds) => nds.filter((node) => node.id !== id));
185
  setEdges((eds) => eds.filter((edge) => edge.source !== id && edge.target !== id));
186
  setSelectedNodeId(null);
187
+ setIsPropertiesOpen(false);
188
  if (node) addLog(`Deleted layer: ${node.data.label}`, 'info');
189
  };
190
 
 
269
  } catch (e) {
270
  console.error(e);
271
  addLog("Failed to implement suggestions.", 'error');
 
272
  } finally {
273
  setIsImplementingSuggestions(false);
274
  }
 
318
  setNodes(hydratedNodes);
319
  setEdges(hydratedEdges);
320
  setIsTemplateMenuOpen(false);
321
+
322
+ // Auto open properties to see details of first node
323
+ if (hydratedNodes.length > 0) {
324
+ setSelectedNodeId(hydratedNodes[0].id);
325
+ setIsPropertiesOpen(true);
326
+ }
327
  addLog(`Loaded template: ${template.name}`, 'info');
328
  }
329
  };
 
334
  );
335
 
336
  return (
337
+ <div className="flex h-screen w-screen bg-slate-950 overflow-hidden">
338
  <Sidebar
339
  onOpenAIBuilder={() => isConnected ? setIsAIBuilderOpen(true) : setIsApiKeyModalOpen(true)}
340
  onSelectTemplate={(id) => id === 'menu' ? setIsTemplateMenuOpen(true) : loadTemplate(id)}
341
  isConnected={isConnected}
342
+ isOpen={isSidebarOpen}
343
+ onToggle={() => setIsSidebarOpen(!isSidebarOpen)}
344
  />
345
 
346
  <div className="flex-1 h-full relative" ref={reactFlowWrapper}>
 
361
  className="bg-slate-950"
362
  >
363
  <Background color="#1e293b" variant={BackgroundVariant.Dots} gap={20} size={1} />
364
+ <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" />
365
 
366
+ {/* Mobile Menu Trigger */}
367
+ <Panel position="top-left" className="md:hidden">
368
+ <button
369
+ onClick={() => setIsSidebarOpen(true)}
370
+ className="p-2 bg-slate-800/80 backdrop-blur border border-slate-700 rounded-lg text-slate-300 shadow-lg"
371
+ >
372
+ <Menu size={20} />
373
+ </button>
374
+ </Panel>
375
+
376
+ {/* Action Toolbar */}
377
+ <Panel position="top-right" className="flex flex-wrap justify-end gap-2 max-w-[80vw]">
378
  <button
379
  onClick={handleCopyLayout}
380
+ 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"
381
+ title="Copy Layout JSON"
382
  >
383
  {layoutCopied ? <Check size={16} className="text-emerald-500" /> : <Copy size={16} className="text-slate-400" />}
384
+ <span className="hidden md:inline">{layoutCopied ? 'Copied' : 'Copy'}</span>
385
  </button>
386
  <button
387
  onClick={() => setIsApiKeyModalOpen(true)}
388
+ 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"
389
+ title="API Key Settings"
390
  >
391
  <Key size={16} className={isConnected ? "text-emerald-500" : "text-slate-400"} />
392
+ <span className="hidden md:inline">{isConnected ? 'Key Active' : 'Add Key'}</span>
393
  </button>
394
  <button
395
  onClick={handleGetSuggestions}
396
+ 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"
397
+ title="Get AI Suggestions"
398
  >
399
  <Lightbulb size={16} className="text-amber-400" />
400
+ <span className="hidden md:inline">Tips</span>
401
  </button>
402
  <button
403
  onClick={handleValidate}
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="Validate Architecture"
406
  >
407
  <CheckCircle size={16} className="text-emerald-500" />
408
+ <span className="hidden md:inline">Validate</span>
409
  </button>
410
  <button
411
  onClick={handleGeneratePrompt}
412
+ 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"
413
+ title="Generate Code Prompt"
414
  >
415
  <FileText size={16} />
416
+ <span className="hidden md:inline">Code</span>
417
+ </button>
418
+ {/* Mobile Activity Toggle */}
419
+ <button
420
+ onClick={() => { setSelectedNodeId(null); setIsPropertiesOpen(true); }}
421
+ 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"
422
+ title="System Logs"
423
+ >
424
+ <Activity size={16} className="text-blue-400" />
425
  </button>
426
  </Panel>
427
 
428
  {validationMsg && (
429
+ <Panel position="bottom-center" className="mb-20 md:mb-8 w-full flex justify-center px-4">
430
+ <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">
431
  <div className="flex items-start gap-3">
432
  <AlertTriangle className="text-amber-500 shrink-0 mt-0.5" size={20} />
433
  <div className="flex-1 min-w-0">
434
  <h4 className="font-bold text-sm text-slate-300 mb-1">Validation Report</h4>
435
+ <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>
436
  </div>
437
  </div>
438
 
 
449
  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"
450
  >
451
  <Wrench size={14} />
452
+ Fix Errors
453
  </button>
454
  )}
455
  </div>
 
459
 
460
  {/* Template Selection Modal Overlay */}
461
  {isTemplateMenuOpen && (
462
+ <div className="absolute inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
463
  <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">
464
  <div className="flex justify-between items-center p-6 border-b border-slate-800 shrink-0 bg-slate-900">
465
  <h3 className="text-lg font-bold text-white">Select Template</h3>
 
490
  selectedNode={selectedNode}
491
  onChange={updateNodeData}
492
  onDelete={deleteNode}
493
+ onClose={() => { setSelectedNodeId(null); setIsPropertiesOpen(false); }}
494
  logs={logs}
495
+ isOpen={isPropertiesOpen}
496
  />
497
 
498
  <CodeViewer