wuhp commited on
Commit
3d06096
·
verified ·
1 Parent(s): fadef80

Upload 7 files

Browse files
components/AIBuilderModal.tsx ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { Sparkles, X, MessageSquare, Play, RefreshCw, AlertCircle, HardHat, Search, Wand2, CheckCircle2 } from 'lucide-react';
3
+ import { generateGraphWithAgents, AgentStatus } from '../services/geminiService';
4
+ import { Node } from 'reactflow';
5
+ import { NodeData } from '../types';
6
+
7
+ interface AIBuilderModalProps {
8
+ isOpen: boolean;
9
+ onClose: () => void;
10
+ onApply: (nodes: any[], edges: any[]) => void;
11
+ currentNodes: Node<NodeData>[];
12
+ }
13
+
14
+ const AIBuilderModal: React.FC<AIBuilderModalProps> = ({ isOpen, onClose, onApply, currentNodes }) => {
15
+ const [prompt, setPrompt] = useState('');
16
+ const [isBuilding, setIsBuilding] = useState(false);
17
+ const [error, setError] = useState<string | null>(null);
18
+
19
+ const [agentStatus, setAgentStatus] = useState<AgentStatus>('idle');
20
+ const [agentMessage, setAgentMessage] = useState('');
21
+
22
+ // Reset state when opened
23
+ useEffect(() => {
24
+ if (isOpen) {
25
+ setAgentStatus('idle');
26
+ setAgentMessage('');
27
+ setError(null);
28
+ }
29
+ }, [isOpen]);
30
+
31
+ if (!isOpen) return null;
32
+
33
+ const handleBuild = async () => {
34
+ if (!prompt.trim()) return;
35
+ setIsBuilding(true);
36
+ setError(null);
37
+
38
+ try {
39
+ const result = await generateGraphWithAgents(prompt, currentNodes, (status, msg) => {
40
+ setAgentStatus(status);
41
+ setAgentMessage(msg);
42
+ });
43
+
44
+ if (result && result.nodes && result.edges) {
45
+ // Ensure nodes have correct style defaults if missing
46
+ const processedNodes = result.nodes.map((n: any) => ({
47
+ ...n,
48
+ type: 'custom', // enforce custom type
49
+ data: {
50
+ ...n.data,
51
+ // ensure label exists if AI forgot it
52
+ label: n.data.label || n.data.type
53
+ }
54
+ }));
55
+
56
+ // Ensure edges have styling
57
+ const processedEdges = result.edges.map((e: any) => ({
58
+ ...e,
59
+ animated: true,
60
+ style: { stroke: '#94a3b8' }
61
+ }));
62
+
63
+ // Small delay to let user see "Complete" state
64
+ setTimeout(() => {
65
+ onApply(processedNodes, processedEdges);
66
+ onClose();
67
+ setIsBuilding(false);
68
+ }, 1000);
69
+ } else {
70
+ throw new Error("Invalid response format from AI");
71
+ }
72
+ } catch (err) {
73
+ setError(err instanceof Error ? err.message : "Failed to generate architecture");
74
+ setAgentStatus('error');
75
+ setIsBuilding(false);
76
+ }
77
+ };
78
+
79
+ const getStepStatus = (step: AgentStatus, current: AgentStatus) => {
80
+ const order = ['idle', 'architect', 'critic', 'refiner', 'complete'];
81
+ const stepIdx = order.indexOf(step);
82
+ const currentIdx = order.indexOf(current);
83
+
84
+ if (current === 'error') return 'text-slate-500';
85
+ if (currentIdx > stepIdx) return 'text-emerald-400';
86
+ if (currentIdx === stepIdx) return 'text-blue-400 animate-pulse';
87
+ return 'text-slate-600';
88
+ };
89
+
90
+ const renderAgentStep = (step: AgentStatus, icon: React.ReactNode, label: string) => {
91
+ const statusColor = getStepStatus(step, agentStatus);
92
+ const isCurrent = agentStatus === step;
93
+ const isDone = ['architect', 'critic', 'refiner', 'complete'].indexOf(agentStatus) > ['architect', 'critic', 'refiner'].indexOf(step);
94
+
95
+ return (
96
+ <div className={`flex items-center gap-3 p-3 rounded-lg border border-slate-800 ${isCurrent ? 'bg-slate-800/50 border-blue-500/30' : 'bg-slate-900'}`}>
97
+ <div className={`${statusColor} transition-colors duration-300`}>
98
+ {isDone ? <CheckCircle2 size={24} /> : icon}
99
+ </div>
100
+ <div className="flex-1">
101
+ <div className={`font-semibold text-sm ${isCurrent ? 'text-blue-200' : 'text-slate-400'}`}>{label}</div>
102
+ {isCurrent && (
103
+ <div className="text-xs text-slate-500 mt-1">{agentMessage}</div>
104
+ )}
105
+ </div>
106
+ {isCurrent && <div className="w-2 h-2 rounded-full bg-blue-400 animate-ping" />}
107
+ </div>
108
+ );
109
+ }
110
+
111
+ return (
112
+ <div className="absolute inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm p-4">
113
+ <div className="bg-slate-950 w-full max-w-lg rounded-xl border border-slate-700 shadow-2xl flex flex-col overflow-hidden animate-in fade-in zoom-in duration-200">
114
+
115
+ <div className="flex items-center justify-between px-6 py-4 border-b border-slate-800 bg-gradient-to-r from-slate-900 to-slate-800">
116
+ <h2 className="text-lg font-bold text-white flex items-center gap-2">
117
+ <Sparkles className="text-purple-400" size={20} />
118
+ AI Architect Team
119
+ </h2>
120
+ <button onClick={onClose} disabled={isBuilding} className="text-slate-400 hover:text-white transition-colors disabled:opacity-50">
121
+ <X size={20} />
122
+ </button>
123
+ </div>
124
+
125
+ <div className="p-6 space-y-4">
126
+ {!isBuilding && agentStatus !== 'complete' ? (
127
+ <>
128
+ <div className="bg-purple-500/10 border border-purple-500/20 rounded-lg p-3 text-sm text-purple-200 flex gap-3">
129
+ <MessageSquare size={18} className="shrink-0 mt-0.5" />
130
+ <p>
131
+ Describe your model. Our AI Agents (Architect, Critic, and Refiner) will collaborate to build the best architecture for you.
132
+ </p>
133
+ </div>
134
+
135
+ <textarea
136
+ className="w-full h-32 bg-slate-900 border border-slate-700 rounded-lg p-3 text-slate-200 focus:ring-2 focus:ring-purple-500 outline-none resize-none placeholder-slate-600"
137
+ placeholder="e.g. Build a robust CNN for medical image segmentation with attention gates..."
138
+ value={prompt}
139
+ onChange={(e) => setPrompt(e.target.value)}
140
+ />
141
+
142
+ {error && (
143
+ <div className="flex items-center gap-2 text-red-400 text-sm bg-red-500/10 p-2 rounded border border-red-500/20">
144
+ <AlertCircle size={14} />
145
+ {error}
146
+ </div>
147
+ )}
148
+
149
+ <div className="flex justify-end pt-2">
150
+ <button
151
+ onClick={handleBuild}
152
+ disabled={!prompt.trim()}
153
+ className={`
154
+ flex items-center gap-2 px-6 py-2 rounded-lg font-medium text-white transition-all
155
+ ${!prompt.trim()
156
+ ? 'bg-slate-800 cursor-not-allowed opacity-50'
157
+ : 'bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-500 hover:to-blue-500 shadow-lg shadow-purple-500/20'}
158
+ `}
159
+ >
160
+ <Play size={18} fill="currentColor" />
161
+ Start Agents
162
+ </button>
163
+ </div>
164
+ </>
165
+ ) : (
166
+ <div className="space-y-3 py-2">
167
+ {renderAgentStep('architect', <HardHat size={24} />, "Architect Agent: Drafting Layout")}
168
+ {renderAgentStep('critic', <Search size={24} />, "Critic Agent: Reviewing Design")}
169
+ {renderAgentStep('refiner', <Wand2 size={24} />, "Refiner Agent: Finalizing Architecture")}
170
+
171
+ {agentStatus === 'complete' && (
172
+ <div className="text-center text-emerald-400 font-bold mt-4 animate-pulse">
173
+ Architecture Generation Complete!
174
+ </div>
175
+ )}
176
+ </div>
177
+ )}
178
+ </div>
179
+ </div>
180
+ </div>
181
+ );
182
+ };
183
+
184
+ export default AIBuilderModal;
components/ApiKeyModal.tsx ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { Key, X, Check, Lock } from 'lucide-react';
3
+ import { setUserApiKey } from '../services/geminiService';
4
+
5
+ interface ApiKeyModalProps {
6
+ isOpen: boolean;
7
+ onClose: () => void;
8
+ onSuccess: () => void;
9
+ }
10
+
11
+ const ApiKeyModal: React.FC<ApiKeyModalProps> = ({ isOpen, onClose, onSuccess }) => {
12
+ const [key, setKey] = useState('');
13
+ const [saved, setSaved] = useState(false);
14
+
15
+ if (!isOpen) return null;
16
+
17
+ const handleSave = () => {
18
+ if (key.trim()) {
19
+ setUserApiKey(key.trim());
20
+ setSaved(true);
21
+ setTimeout(() => {
22
+ onSuccess();
23
+ onClose();
24
+ setSaved(false);
25
+ }, 1000);
26
+ }
27
+ };
28
+
29
+ return (
30
+ <div className="absolute inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm p-4">
31
+ <div className="bg-slate-900 w-full max-w-md rounded-xl border border-slate-700 shadow-2xl overflow-hidden animate-in fade-in zoom-in duration-200">
32
+ <div className="flex items-center justify-between px-6 py-4 border-b border-slate-800 bg-slate-800/50">
33
+ <h2 className="text-lg font-bold text-white flex items-center gap-2">
34
+ <Key className="text-emerald-400" size={20} />
35
+ API Access
36
+ </h2>
37
+ <button onClick={onClose} className="text-slate-400 hover:text-white transition-colors">
38
+ <X size={20} />
39
+ </button>
40
+ </div>
41
+
42
+ <div className="p-6 space-y-4">
43
+ <p className="text-sm text-slate-400">
44
+ Enter your Google Gemini API Key to enable AI features like Agents, Suggestions, and Code Generation.
45
+ </p>
46
+
47
+ <div className="relative">
48
+ <Lock className="absolute left-3 top-2.5 text-slate-500" size={16} />
49
+ <input
50
+ type="password"
51
+ className="w-full bg-slate-950 border border-slate-700 rounded-lg pl-10 pr-4 py-2 text-slate-200 focus:ring-2 focus:ring-emerald-500 outline-none placeholder-slate-600 font-mono text-sm"
52
+ placeholder="Paste your API key here..."
53
+ value={key}
54
+ onChange={(e) => setKey(e.target.value)}
55
+ />
56
+ </div>
57
+
58
+ <div className="bg-emerald-500/10 border border-emerald-500/20 rounded-lg p-3">
59
+ <p className="text-xs text-emerald-200">
60
+ Your key is stored locally in your browser and used directly with Google's API.
61
+ </p>
62
+ </div>
63
+
64
+ <div className="flex justify-end pt-2">
65
+ <button
66
+ onClick={handleSave}
67
+ disabled={!key.trim()}
68
+ className={`
69
+ flex items-center gap-2 px-6 py-2 rounded-lg font-medium text-white transition-all
70
+ ${saved ? 'bg-emerald-600' : 'bg-emerald-600 hover:bg-emerald-500'}
71
+ ${!key.trim() && !saved ? 'opacity-50 cursor-not-allowed bg-slate-700' : ''}
72
+ `}
73
+ >
74
+ {saved ? <Check size={18} /> : <Key size={18} />}
75
+ {saved ? 'Saved!' : 'Save Key'}
76
+ </button>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ );
82
+ };
83
+
84
+ export default ApiKeyModal;
components/CodeViewer.tsx ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { X, Copy, Check, Terminal, HardHat, Search, Wand2, CheckCircle2, ArrowLeft, Play } from 'lucide-react';
3
+ import { generateCodeWithAgents, AgentStatus } from '../services/geminiService';
4
+
5
+ interface CodeViewerProps {
6
+ code: string; // This represents the PROMPT text initially
7
+ isOpen: boolean;
8
+ onClose: () => void;
9
+ isLoading: boolean;
10
+ }
11
+
12
+ const CodeViewer: React.FC<CodeViewerProps> = ({ code, isOpen, onClose, isLoading }) => {
13
+ const [copied, setCopied] = useState(false);
14
+ const [viewMode, setViewMode] = useState<'prompt' | 'building' | 'code'>('prompt');
15
+
16
+ const [finalCode, setFinalCode] = useState('');
17
+ const [agentStatus, setAgentStatus] = useState<AgentStatus>('idle');
18
+ const [agentMessage, setAgentMessage] = useState('');
19
+
20
+ // Reset state when opening/closing or changing input
21
+ useEffect(() => {
22
+ if (isOpen) {
23
+ setViewMode('prompt');
24
+ setFinalCode('');
25
+ setAgentStatus('idle');
26
+ }
27
+ }, [isOpen, code]);
28
+
29
+ const handleCopy = (text: string) => {
30
+ navigator.clipboard.writeText(text);
31
+ setCopied(true);
32
+ setTimeout(() => setCopied(false), 2000);
33
+ };
34
+
35
+ const handleBuildWithAgents = async () => {
36
+ setViewMode('building');
37
+ setAgentStatus('architect');
38
+ setAgentMessage('Initializing agents...');
39
+
40
+ try {
41
+ const result = await generateCodeWithAgents(code, (status, msg) => {
42
+ setAgentStatus(status);
43
+ setAgentMessage(msg);
44
+ });
45
+ setFinalCode(result);
46
+ // Small delay to show completion state
47
+ setTimeout(() => {
48
+ setViewMode('code');
49
+ }, 1000);
50
+ } catch (error) {
51
+ setAgentStatus('error');
52
+ setAgentMessage('Generation failed. Please try again.');
53
+ setTimeout(() => setViewMode('prompt'), 2000);
54
+ }
55
+ };
56
+
57
+ const getStepStatus = (step: AgentStatus, current: AgentStatus) => {
58
+ const order = ['idle', 'architect', 'critic', 'refiner', 'complete'];
59
+ const stepIdx = order.indexOf(step);
60
+ const currentIdx = order.indexOf(current);
61
+
62
+ if (current === 'error') return 'text-slate-500';
63
+ if (currentIdx > stepIdx) return 'text-emerald-400';
64
+ if (currentIdx === stepIdx) return 'text-blue-400 animate-pulse';
65
+ return 'text-slate-600';
66
+ };
67
+
68
+ const renderAgentStep = (step: AgentStatus, icon: React.ReactNode, label: string) => {
69
+ const statusColor = getStepStatus(step, agentStatus);
70
+ const isCurrent = agentStatus === step;
71
+ const isDone = ['architect', 'critic', 'refiner', 'complete'].indexOf(agentStatus) > ['architect', 'critic', 'refiner'].indexOf(step);
72
+
73
+ return (
74
+ <div className={`flex items-center gap-3 p-4 rounded-lg border border-slate-800 ${isCurrent ? 'bg-slate-800/50 border-blue-500/30' : 'bg-slate-900'}`}>
75
+ <div className={`${statusColor} transition-colors duration-300`}>
76
+ {isDone ? <CheckCircle2 size={24} /> : icon}
77
+ </div>
78
+ <div className="flex-1">
79
+ <div className={`font-semibold text-sm ${isCurrent ? 'text-blue-200' : 'text-slate-400'}`}>{label}</div>
80
+ {isCurrent && (
81
+ <div className="text-xs text-slate-500 mt-1">{agentMessage}</div>
82
+ )}
83
+ </div>
84
+ {isCurrent && <div className="w-2 h-2 rounded-full bg-blue-400 animate-ping" />}
85
+ </div>
86
+ );
87
+ };
88
+
89
+ if (!isOpen) return null;
90
+
91
+ return (
92
+ <div className="absolute inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm p-4 md:p-8">
93
+ <div className="bg-slate-900 w-full max-w-5xl h-[85vh] rounded-xl border border-slate-700 shadow-2xl flex flex-col overflow-hidden animate-in fade-in zoom-in duration-200">
94
+
95
+ {/* Header */}
96
+ <div className="flex items-center justify-between px-6 py-4 border-b border-slate-800 bg-slate-800/50">
97
+ <h2 className="text-lg font-bold text-white flex items-center gap-2">
98
+ {viewMode === 'prompt' && <span className="text-blue-400">Architecture Prompt</span>}
99
+ {viewMode === 'building' && <span className="text-purple-400 flex items-center gap-2"><HardHat size={18}/> Agents Building...</span>}
100
+ {viewMode === 'code' && <span className="text-emerald-400 flex items-center gap-2"><Terminal size={18}/> Final Code</span>}
101
+
102
+ {isLoading && <span className="text-xs text-blue-400 animate-pulse ml-2">Building...</span>}
103
+ </h2>
104
+
105
+ <div className="flex items-center gap-3">
106
+ {viewMode === 'code' && (
107
+ <button
108
+ onClick={() => setViewMode('prompt')}
109
+ className="flex items-center gap-2 px-3 py-1.5 bg-slate-800 hover:bg-slate-700 rounded text-xs font-medium text-slate-300 transition-colors border border-slate-700"
110
+ >
111
+ <ArrowLeft size={14} /> Back to Prompt
112
+ </button>
113
+ )}
114
+
115
+ {viewMode !== 'building' && (
116
+ <button
117
+ onClick={() => handleCopy(viewMode === 'code' ? finalCode : code)}
118
+ className="flex items-center gap-2 px-3 py-1.5 bg-slate-800 hover:bg-slate-700 rounded text-xs font-medium text-slate-300 transition-colors border border-slate-700"
119
+ >
120
+ {copied ? <Check size={14} className="text-emerald-400"/> : <Copy size={14} />}
121
+ {copied ? 'Copied' : 'Copy'}
122
+ </button>
123
+ )}
124
+
125
+ {viewMode === 'prompt' && !isLoading && (
126
+ <button
127
+ onClick={handleBuildWithAgents}
128
+ className="flex items-center gap-2 px-3 py-1.5 bg-purple-600 hover:bg-purple-500 rounded text-xs font-medium text-white transition-colors shadow-lg shadow-purple-500/20"
129
+ >
130
+ <Play size={14} fill="currentColor" />
131
+ Build with Agents (Beta)
132
+ </button>
133
+ )}
134
+
135
+ <button onClick={onClose} className="text-slate-400 hover:text-white transition-colors ml-2">
136
+ <X size={20} />
137
+ </button>
138
+ </div>
139
+ </div>
140
+
141
+ {/* Content */}
142
+ <div className="flex-1 overflow-hidden bg-[#0d1117] relative">
143
+
144
+ {/* View: Loading Prompt */}
145
+ {isLoading && viewMode === 'prompt' && (
146
+ <div className="h-full flex flex-col items-center justify-center space-y-4">
147
+ <div className="w-12 h-12 border-4 border-blue-500/30 border-t-blue-500 rounded-full animate-spin"></div>
148
+ <p className="text-slate-400 text-sm animate-pulse">Refining architecture prompt...</p>
149
+ </div>
150
+ )}
151
+
152
+ {/* View: Prompt Text */}
153
+ {!isLoading && viewMode === 'prompt' && (
154
+ <div className="h-full overflow-auto p-6 font-mono text-sm text-slate-300 leading-relaxed whitespace-pre-wrap">
155
+ {code}
156
+ </div>
157
+ )}
158
+
159
+ {/* View: Agents Building */}
160
+ {viewMode === 'building' && (
161
+ <div className="h-full flex flex-col items-center justify-center p-8">
162
+ <div className="w-full max-w-md space-y-3">
163
+ {renderAgentStep('architect', <HardHat size={24} />, "Coder Agent: Writing Implementation")}
164
+ {renderAgentStep('critic', <Search size={24} />, "Reviewer Agent: Analyzing Logic")}
165
+ {renderAgentStep('refiner', <Wand2 size={24} />, "Polisher Agent: Finalizing Code")}
166
+ </div>
167
+ {agentStatus === 'complete' && (
168
+ <div className="mt-8 text-emerald-400 font-bold animate-bounce">
169
+ Done! Loading code...
170
+ </div>
171
+ )}
172
+ </div>
173
+ )}
174
+
175
+ {/* View: Final Code */}
176
+ {viewMode === 'code' && (
177
+ <div className="h-full overflow-auto p-6 font-mono text-sm leading-relaxed whitespace-pre-wrap text-blue-100 selection:bg-blue-500/30">
178
+ {finalCode}
179
+ </div>
180
+ )}
181
+
182
+ </div>
183
+ </div>
184
+ </div>
185
+ );
186
+ };
187
+
188
+ export default CodeViewer;
components/CustomNode.tsx ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { memo } from 'react';
2
+ import { Handle, Position, NodeProps } from 'reactflow';
3
+ import {
4
+ Layers, Box, Activity, Zap, ArrowRight, Grid, Minimize,
5
+ Database, GitBranch, AlignJustify, Type, Combine, Maximize,
6
+ ArrowUpCircle, Sliders, RefreshCcw, Brain, Crosshair, Network, Clock, Eye, Workflow,
7
+ Terminal, MinusCircle
8
+ } from 'lucide-react';
9
+ import { NodeData, LayerType } from '../types';
10
+
11
+ const getIcon = (type: LayerType) => {
12
+ switch (type) {
13
+ case LayerType.INPUT: return <ArrowRight className="w-4 h-4" />;
14
+ case LayerType.CONV1D: return <Activity className="w-4 h-4" />;
15
+ case LayerType.CONV2D: return <Layers className="w-4 h-4" />;
16
+ case LayerType.CONV_TRANSPOSE2D: return <ArrowUpCircle className="w-4 h-4" />;
17
+ case LayerType.LINEAR: return <Grid className="w-4 h-4" />;
18
+ case LayerType.RELU:
19
+ case LayerType.LEAKYRELU:
20
+ case LayerType.GELU:
21
+ case LayerType.SILU:
22
+ case LayerType.SWIGLU:
23
+ case LayerType.SIGMOID:
24
+ case LayerType.TANH: return <Zap className="w-4 h-4" />;
25
+ case LayerType.MAXPOOL:
26
+ case LayerType.AVGPOOL:
27
+ case LayerType.ADAPTIVEAVGPOOL: return <Minimize className="w-4 h-4" />;
28
+ case LayerType.UPSAMPLE: return <Maximize className="w-4 h-4" />;
29
+
30
+ // Transformer / GenAI
31
+ case LayerType.ATTENTION:
32
+ case LayerType.CROSS_ATTENTION: return <RefreshCcw className="w-4 h-4" />;
33
+ case LayerType.TRANSFORMER_BLOCK:
34
+ case LayerType.TRANSFORMER_ENCODER:
35
+ case LayerType.TRANSFORMER_DECODER: return <Brain className="w-4 h-4" />;
36
+ case LayerType.MOE_BLOCK: return <Network className="w-4 h-4" />;
37
+ case LayerType.ACTION_HEAD: return <Crosshair className="w-4 h-4" />;
38
+ case LayerType.PATCH_EMBED:
39
+ case LayerType.SAM_PROMPT_ENCODER: return <Eye className="w-4 h-4" />;
40
+ case LayerType.SAM_MASK_DECODER: return <Workflow className="w-4 h-4" />;
41
+ case LayerType.TIME_EMBEDDING:
42
+ case LayerType.ROPE: return <Clock className="w-4 h-4" />;
43
+
44
+ case LayerType.LSTM:
45
+ case LayerType.GRU: return <GitBranch className="w-4 h-4" />;
46
+ case LayerType.EMBEDDING: return <Database className="w-4 h-4" />;
47
+ case LayerType.LAYERNORM:
48
+ case LayerType.BATCHNORM:
49
+ case LayerType.INSTANCENORM:
50
+ case LayerType.RMSNORM: return <Sliders className="w-4 h-4" />;
51
+ case LayerType.FLATTEN: return <Type className="w-4 h-4" />;
52
+ case LayerType.CONCAT:
53
+ case LayerType.ADD: return <Combine className="w-4 h-4" />;
54
+
55
+ case LayerType.CUSTOM: return <Terminal className="w-4 h-4" />;
56
+ case LayerType.IDENTITY: return <MinusCircle className="w-4 h-4" />;
57
+
58
+ default: return <Box className="w-4 h-4" />;
59
+ }
60
+ };
61
+
62
+ const getColor = (type: LayerType) => {
63
+ switch (type) {
64
+ case LayerType.INPUT: return 'border-emerald-500 shadow-emerald-500/20';
65
+ case LayerType.OUTPUT: return 'border-red-500 shadow-red-500/20';
66
+
67
+ case LayerType.CONV2D:
68
+ case LayerType.CONV1D:
69
+ case LayerType.CONV_TRANSPOSE2D:
70
+ case LayerType.MAXPOOL:
71
+ case LayerType.AVGPOOL:
72
+ case LayerType.ADAPTIVEAVGPOOL:
73
+ case LayerType.UPSAMPLE: return 'border-blue-500 shadow-blue-500/20';
74
+
75
+ case LayerType.LINEAR:
76
+ case LayerType.ACTION_HEAD: return 'border-violet-500 shadow-violet-500/20';
77
+
78
+ // GenAI / Transformer - Gold/Amber theme
79
+ case LayerType.ATTENTION:
80
+ case LayerType.CROSS_ATTENTION:
81
+ case LayerType.TRANSFORMER_BLOCK:
82
+ case LayerType.TRANSFORMER_ENCODER:
83
+ case LayerType.TRANSFORMER_DECODER:
84
+ case LayerType.MOE_BLOCK:
85
+ case LayerType.PATCH_EMBED:
86
+ case LayerType.SAM_PROMPT_ENCODER:
87
+ case LayerType.SAM_MASK_DECODER: return 'border-amber-500 shadow-amber-500/20';
88
+
89
+ case LayerType.LSTM:
90
+ case LayerType.GRU:
91
+ case LayerType.EMBEDDING:
92
+ case LayerType.TIME_EMBEDDING:
93
+ case LayerType.ROPE: return 'border-orange-500 shadow-orange-500/20';
94
+
95
+ case LayerType.BATCHNORM:
96
+ case LayerType.LAYERNORM:
97
+ case LayerType.INSTANCENORM:
98
+ case LayerType.RMSNORM: return 'border-cyan-500 shadow-cyan-500/20';
99
+
100
+ case LayerType.CONCAT:
101
+ case LayerType.ADD: return 'border-pink-500 shadow-pink-500/20';
102
+
103
+ case LayerType.CUSTOM: return 'border-lime-500 shadow-lime-500/20';
104
+ case LayerType.IDENTITY: return 'border-slate-600 shadow-slate-600/20';
105
+
106
+ default: return 'border-slate-500 shadow-slate-500/20';
107
+ }
108
+ };
109
+
110
+ const CustomNode = ({ data, selected }: NodeProps<NodeData>) => {
111
+ const isInput = data.type === LayerType.INPUT;
112
+ const isOutput = data.type === LayerType.OUTPUT;
113
+
114
+ // Render input modality badge
115
+ const inputBadge = isInput && data.params.modality ? (
116
+ <span className="ml-auto text-[9px] px-1.5 py-0.5 rounded-full bg-slate-700 text-emerald-400 font-mono tracking-tighter uppercase">
117
+ {data.params.modality}
118
+ </span>
119
+ ) : null;
120
+
121
+ // Custom layer name badge
122
+ const customBadge = data.type === LayerType.CUSTOM && data.params.class_name ? (
123
+ <span className="ml-auto text-[8px] px-1.5 py-0.5 rounded-full bg-slate-700 text-lime-400 font-mono truncate max-w-[80px]">
124
+ {data.params.class_name.split('.').pop()}
125
+ </span>
126
+ ) : null;
127
+
128
+ return (
129
+ <div className={`
130
+ relative min-w-[150px] bg-slate-900 rounded-lg border-2
131
+ transition-all duration-200 shadow-lg
132
+ ${getColor(data.type)}
133
+ ${selected ? 'ring-2 ring-white ring-offset-2 ring-offset-slate-900 scale-105' : ''}
134
+ `}>
135
+ {/* Input Handle */}
136
+ {!isInput && (
137
+ <Handle
138
+ type="target"
139
+ position={Position.Top}
140
+ className="!bg-slate-400 !w-3 !h-3 !-top-2 !border-slate-900"
141
+ />
142
+ )}
143
+
144
+ {/* Header */}
145
+ <div className="flex items-center gap-2 px-3 py-2 border-b border-slate-800 bg-slate-800/50 rounded-t-md">
146
+ <span className="text-slate-300">
147
+ {getIcon(data.type)}
148
+ </span>
149
+ <span className="text-xs font-bold uppercase tracking-wider text-slate-200 truncate max-w-[100px]">
150
+ {data.label}
151
+ </span>
152
+ {inputBadge}
153
+ {customBadge}
154
+ </div>
155
+
156
+ {/* Body - Parameters Summary */}
157
+ <div className="p-3">
158
+ <div className="text-[10px] text-slate-400 font-mono space-y-1">
159
+ {Object.entries(data.params).slice(0, 3).map(([key, value]) => (
160
+ <div key={key} className="flex justify-between gap-4">
161
+ <span className="opacity-70 truncate max-w-[80px]">{key}:</span>
162
+ <span className="text-slate-200 truncate max-w-[80px]">{String(value)}</span>
163
+ </div>
164
+ ))}
165
+ {Object.keys(data.params).length === 0 && (
166
+ <span className="opacity-50 italic">No parameters</span>
167
+ )}
168
+ </div>
169
+ </div>
170
+
171
+ {/* Output Handle */}
172
+ {!isOutput && (
173
+ <Handle
174
+ type="source"
175
+ position={Position.Bottom}
176
+ className="!bg-slate-400 !w-3 !h-3 !-bottom-2 !border-slate-900"
177
+ />
178
+ )}
179
+ </div>
180
+ );
181
+ };
182
+
183
+ export default memo(CustomNode);
components/PropertiesPanel.tsx ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Node } from 'reactflow';
3
+ import { NodeData, LayerDefinition } from '../types';
4
+ import { LAYER_DEFINITIONS } from '../constants';
5
+ import { X, Trash2 } from 'lucide-react';
6
+
7
+ interface PropertiesPanelProps {
8
+ selectedNode: Node<NodeData> | null;
9
+ onChange: (id: string, newParams: Record<string, any>) => void;
10
+ onDelete: (id: string) => void;
11
+ onClose: () => void;
12
+ }
13
+
14
+ const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ selectedNode, onChange, onDelete, onClose }) => {
15
+ if (!selectedNode) {
16
+ return (
17
+ <div className="w-80 bg-slate-900 border-l border-slate-800 p-6 flex flex-col items-center justify-center text-slate-500 h-full">
18
+ <div className="w-16 h-16 rounded-full bg-slate-800 mb-4 flex items-center justify-center">
19
+ <span className="text-2xl opacity-50">⚡</span>
20
+ </div>
21
+ <p className="text-sm text-center">Select a node on the canvas to configure parameters.</p>
22
+ </div>
23
+ );
24
+ }
25
+
26
+ const definition: LayerDefinition = LAYER_DEFINITIONS[selectedNode.data.type];
27
+
28
+ const handleParamChange = (name: string, value: any, type: string) => {
29
+ let parsedValue = value;
30
+ if (type === 'number') parsedValue = Number(value);
31
+ if (type === 'boolean') parsedValue = value === 'true';
32
+
33
+ onChange(selectedNode.id, {
34
+ ...selectedNode.data.params,
35
+ [name]: parsedValue
36
+ });
37
+ };
38
+
39
+ return (
40
+ <div className="w-80 bg-slate-900 border-l border-slate-800 flex flex-col h-full">
41
+ <div className="p-4 border-b border-slate-800 flex justify-between items-center">
42
+ <div>
43
+ <h2 className="text-lg font-bold text-slate-100">{definition.label}</h2>
44
+ <p className="text-xs text-slate-500 font-mono">{selectedNode.id}</p>
45
+ </div>
46
+ <button onClick={onClose} className="text-slate-500 hover:text-slate-300">
47
+ <X size={18} />
48
+ </button>
49
+ </div>
50
+
51
+ <div className="flex-1 overflow-y-auto p-4 space-y-5">
52
+ <div className="text-sm text-slate-400 italic bg-slate-800/50 p-3 rounded border border-slate-800">
53
+ {definition.description}
54
+ </div>
55
+
56
+ <div className="space-y-4">
57
+ {definition.parameters.map((param) => (
58
+ <div key={param.name} className="space-y-1.5">
59
+ <label className="block text-xs font-semibold text-slate-300 uppercase tracking-wide">
60
+ {param.label}
61
+ </label>
62
+
63
+ {param.type === 'select' ? (
64
+ <select
65
+ className="w-full bg-slate-950 border border-slate-700 rounded px-3 py-2 text-sm text-slate-200 focus:ring-1 focus:ring-blue-500 outline-none"
66
+ value={selectedNode.data.params[param.name] || param.default}
67
+ onChange={(e) => handleParamChange(param.name, e.target.value, param.type)}
68
+ >
69
+ {param.options?.map(opt => (
70
+ <option key={opt} value={opt}>{opt}</option>
71
+ ))}
72
+ </select>
73
+ ) : param.type === 'boolean' ? (
74
+ <select
75
+ className="w-full bg-slate-950 border border-slate-700 rounded px-3 py-2 text-sm text-slate-200 focus:ring-1 focus:ring-blue-500 outline-none"
76
+ value={String(selectedNode.data.params[param.name] ?? param.default)}
77
+ onChange={(e) => handleParamChange(param.name, e.target.value === 'true', param.type)}
78
+ >
79
+ <option value="true">True</option>
80
+ <option value="false">False</option>
81
+ </select>
82
+ ) : (
83
+ <input
84
+ type={param.type === 'number' ? 'number' : 'text'}
85
+ className="w-full bg-slate-950 border border-slate-700 rounded px-3 py-2 text-sm text-slate-200 focus:ring-1 focus:ring-blue-500 outline-none placeholder-slate-700"
86
+ value={selectedNode.data.params[param.name] ?? param.default}
87
+ onChange={(e) => handleParamChange(param.name, e.target.value, param.type)}
88
+ />
89
+ )}
90
+ </div>
91
+ ))}
92
+
93
+ {definition.parameters.length === 0 && (
94
+ <p className="text-sm text-slate-500 text-center py-4">This layer has no configurable parameters.</p>
95
+ )}
96
+ </div>
97
+ </div>
98
+
99
+ <div className="p-4 border-t border-slate-800">
100
+ <button
101
+ onClick={() => onDelete(selectedNode.id)}
102
+ className="w-full flex items-center justify-center gap-2 bg-red-500/10 hover:bg-red-500/20 text-red-500 py-2 rounded transition-colors text-sm font-medium border border-red-500/20"
103
+ >
104
+ <Trash2 size={16} />
105
+ Delete Node
106
+ </button>
107
+ </div>
108
+ </div>
109
+ );
110
+ };
111
+
112
+ export default PropertiesPanel;
components/Sidebar.tsx ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { LAYER_DEFINITIONS } from '../constants';
3
+ import { LayerType } from '../types';
4
+ import { Box, Sparkles, LayoutTemplate, Circle } from 'lucide-react';
5
+
6
+ interface SidebarProps {
7
+ onOpenAIBuilder: () => void;
8
+ onSelectTemplate: (templateId: string) => void;
9
+ isConnected: boolean;
10
+ }
11
+
12
+ const Sidebar: React.FC<SidebarProps> = ({ onOpenAIBuilder, onSelectTemplate, isConnected }) => {
13
+ const onDragStart = (event: React.DragEvent, layerType: LayerType) => {
14
+ event.dataTransfer.setData('application/reactflow', layerType);
15
+ event.dataTransfer.effectAllowed = 'move';
16
+ };
17
+
18
+ const categories = Array.from(new Set(Object.values(LAYER_DEFINITIONS).map(l => l.category)));
19
+ const categoryOrder = ['Core', 'Convolution', 'Recurrent', 'Transformer', 'Normalization', 'Utility'];
20
+ categories.sort((a, b) => categoryOrder.indexOf(a) - categoryOrder.indexOf(b));
21
+
22
+ return (
23
+ <aside className="w-64 bg-slate-900 border-r border-slate-800 flex flex-col h-full z-10">
24
+ <div className="p-4 border-b border-slate-800 bg-slate-900">
25
+ <h1 className="text-xl font-bold bg-gradient-to-r from-blue-400 to-violet-400 bg-clip-text text-transparent">
26
+ Architecture Agents
27
+ </h1>
28
+ <p className="text-xs text-slate-500 mt-1">AI-Powered Model Builder</p>
29
+
30
+ <div className="mt-4 grid grid-cols-2 gap-2">
31
+ <button
32
+ onClick={onOpenAIBuilder}
33
+ className="flex flex-col items-center justify-center p-2 bg-purple-500/10 hover:bg-purple-500/20 border border-purple-500/30 rounded-lg text-purple-300 transition-colors group"
34
+ >
35
+ <Sparkles size={18} className="mb-1 group-hover:scale-110 transition-transform" />
36
+ <span className="text-[10px] font-bold">AI Builder</span>
37
+ </button>
38
+ <button
39
+ onClick={() => onSelectTemplate('menu')}
40
+ className="flex flex-col items-center justify-center p-2 bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 rounded-lg text-blue-300 transition-colors group"
41
+ >
42
+ <LayoutTemplate size={18} className="mb-1 group-hover:scale-110 transition-transform" />
43
+ <span className="text-[10px] font-bold">Templates</span>
44
+ </button>
45
+ </div>
46
+ </div>
47
+
48
+ <div className="flex-1 overflow-y-auto p-4 space-y-6 scrollbar-thin scrollbar-thumb-slate-700">
49
+ {categories.map(category => (
50
+ <div key={category}>
51
+ <h3 className="text-xs font-semibold text-slate-500 uppercase tracking-widest mb-3 flex items-center gap-2">
52
+ {category}
53
+ <div className="h-px flex-1 bg-slate-800"></div>
54
+ </h3>
55
+ <div className="grid grid-cols-1 gap-2">
56
+ {Object.values(LAYER_DEFINITIONS)
57
+ .filter(l => l.category === category)
58
+ .map(layer => (
59
+ <div
60
+ key={layer.type}
61
+ className="bg-slate-800 hover:bg-slate-750 p-3 rounded border border-slate-700 cursor-grab active:cursor-grabbing transition-colors group relative overflow-hidden"
62
+ onDragStart={(event) => onDragStart(event, layer.type)}
63
+ draggable
64
+ >
65
+ <div className="flex items-center gap-3 relative z-10">
66
+ <div className={`p-1.5 rounded transition-colors group-hover:bg-slate-900 bg-slate-900/50`}>
67
+ <Box size={14} className="text-slate-400 group-hover:text-blue-400" />
68
+ </div>
69
+ <div>
70
+ <div className="text-sm font-medium text-slate-200 group-hover:text-white">{layer.label}</div>
71
+ <div className="text-xs text-slate-500 leading-tight group-hover:text-slate-400">{layer.description}</div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ ))}
76
+ </div>
77
+ </div>
78
+ ))}
79
+ </div>
80
+
81
+ <div className="p-4 border-t border-slate-800 text-[10px] text-slate-500 text-center flex items-center justify-center gap-2">
82
+ <span>v1.2.0 • Powered by Gemini 2.5</span>
83
+ <Circle size={8} className={isConnected ? "fill-emerald-500 text-emerald-500" : "fill-red-500 text-red-500"} />
84
+ </div>
85
+ </aside>
86
+ );
87
+ };
88
+
89
+ export default Sidebar;
components/SuggestionsModal.tsx ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Lightbulb, X, Sparkles, Wand2 } from 'lucide-react';
3
+
4
+ interface SuggestionsModalProps {
5
+ isOpen: boolean;
6
+ onClose: () => void;
7
+ suggestions: string;
8
+ isLoading: boolean;
9
+ onImplement: () => void;
10
+ isImplementing: boolean;
11
+ }
12
+
13
+ const SuggestionsModal: React.FC<SuggestionsModalProps> = ({
14
+ isOpen,
15
+ onClose,
16
+ suggestions,
17
+ isLoading,
18
+ onImplement,
19
+ isImplementing
20
+ }) => {
21
+ if (!isOpen) return null;
22
+
23
+ return (
24
+ <div className="absolute inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm p-4">
25
+ <div className="bg-slate-900 w-full max-w-lg rounded-xl border border-slate-700 shadow-2xl flex flex-col overflow-hidden animate-in fade-in zoom-in duration-200">
26
+ <div className="flex items-center justify-between px-6 py-4 border-b border-slate-800 bg-gradient-to-r from-slate-900 to-slate-800">
27
+ <h2 className="text-lg font-bold text-white flex items-center gap-2">
28
+ <Lightbulb className="text-amber-400" size={20} />
29
+ AI Suggestions
30
+ </h2>
31
+ <button onClick={onClose} className="text-slate-400 hover:text-white transition-colors">
32
+ <X size={20} />
33
+ </button>
34
+ </div>
35
+ <div className="p-6 overflow-y-auto max-h-[60vh]">
36
+ {isLoading ? (
37
+ <div className="flex flex-col items-center justify-center space-y-4 py-8">
38
+ <Sparkles className="text-amber-400 animate-spin" size={32} />
39
+ <p className="text-slate-400 text-sm animate-pulse">Analyzing architecture...</p>
40
+ </div>
41
+ ) : isImplementing ? (
42
+ <div className="flex flex-col items-center justify-center space-y-4 py-8">
43
+ <Wand2 className="text-purple-400 animate-pulse" size={32} />
44
+ <p className="text-slate-400 text-sm animate-pulse">Agents are applying fixes...</p>
45
+ </div>
46
+ ) : (
47
+ <div className="text-slate-200 text-sm leading-relaxed whitespace-pre-wrap font-medium">
48
+ {suggestions || "No suggestions available."}
49
+ </div>
50
+ )}
51
+ </div>
52
+ <div className="p-4 border-t border-slate-800 bg-slate-900/50 flex justify-between items-center">
53
+ <div className="text-xs text-slate-500">
54
+ Review carefully before applying.
55
+ </div>
56
+ <div className="flex gap-3">
57
+ <button
58
+ onClick={onClose}
59
+ className="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-slate-200 rounded text-sm transition-colors"
60
+ >
61
+ Close
62
+ </button>
63
+ {!isLoading && !isImplementing && suggestions && !suggestions.includes("Error") && (
64
+ <button
65
+ onClick={onImplement}
66
+ className="flex items-center gap-2 px-4 py-2 bg-purple-600 hover:bg-purple-500 text-white rounded text-sm font-medium transition-colors shadow-lg shadow-purple-900/20"
67
+ >
68
+ <Wand2 size={16} />
69
+ Implement Fixes (Auto)
70
+ </button>
71
+ )}
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ );
77
+ };
78
+ export default SuggestionsModal;