wuhp commited on
Commit
d9248fa
·
verified ·
1 Parent(s): a93e85b

Create AiBuilderModal.tsx

Browse files
Files changed (1) hide show
  1. components/AiBuilderModal.tsx +192 -0
components/AiBuilderModal.tsx ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import React, { useState, useEffect, useRef } from 'react';
4
+ import { Sparkles, X, MessageSquare, Play, RefreshCw, AlertCircle, HardHat, Search, Wand2, CheckCircle2 } from 'lucide-react';
5
+ import { generateGraphWithAgents, AgentStatus } from '../services/geminiService';
6
+ import { Node } from 'reactflow';
7
+ import { NodeData } from '../types';
8
+
9
+ interface AIBuilderModalProps {
10
+ isOpen: boolean;
11
+ onClose: () => void;
12
+ onApply: (nodes: any[], edges: any[], logMsg?: string) => void;
13
+ currentNodes: Node<NodeData>[];
14
+ selectedModel: string;
15
+ }
16
+
17
+ const AIBuilderModal: React.FC<AIBuilderModalProps> = ({ isOpen, onClose, onApply, currentNodes, selectedModel }) => {
18
+ const [prompt, setPrompt] = useState('');
19
+ const [isBuilding, setIsBuilding] = useState(false);
20
+ const [error, setError] = useState<string | null>(null);
21
+
22
+ const [agentStatus, setAgentStatus] = useState<AgentStatus>('idle');
23
+ const [agentMessage, setAgentMessage] = useState('');
24
+
25
+ // Reset state when opened
26
+ useEffect(() => {
27
+ if (isOpen) {
28
+ setAgentStatus('idle');
29
+ setAgentMessage('');
30
+ setError(null);
31
+ }
32
+ }, [isOpen]);
33
+
34
+ if (!isOpen) return null;
35
+
36
+ const handleBuild = async () => {
37
+ if (!prompt.trim()) return;
38
+ setIsBuilding(true);
39
+ setError(null);
40
+
41
+ try {
42
+ const result = await generateGraphWithAgents(prompt, currentNodes, (status, msg) => {
43
+ setAgentStatus(status);
44
+ setAgentMessage(msg);
45
+ }, selectedModel);
46
+
47
+ if (result && result.nodes && result.edges) {
48
+ // Ensure nodes have correct style defaults if missing
49
+ const processedNodes = result.nodes.map((n: any) => {
50
+ const data = n.data || {};
51
+ const type = data.type || n.type || 'Identity'; // Fallback
52
+ return {
53
+ ...n,
54
+ type: 'custom',
55
+ data: {
56
+ ...data,
57
+ type: type,
58
+ label: data.label || n.label || type, // Safe access
59
+ params: data.params || {}
60
+ }
61
+ };
62
+ });
63
+
64
+ // Ensure edges have styling
65
+ const processedEdges = result.edges.map((e: any) => ({
66
+ ...e,
67
+ animated: true,
68
+ style: { stroke: '#94a3b8' }
69
+ }));
70
+
71
+ // Small delay to let user see "Complete" state
72
+ setTimeout(() => {
73
+ onApply(processedNodes, processedEdges, "AI Architect generated new graph.");
74
+ onClose();
75
+ setIsBuilding(false);
76
+ }, 1000);
77
+ } else {
78
+ throw new Error("Invalid response format from AI");
79
+ }
80
+ } catch (err) {
81
+ setError(err instanceof Error ? err.message : "Failed to generate architecture");
82
+ setAgentStatus('error');
83
+ setIsBuilding(false);
84
+ }
85
+ };
86
+
87
+ const getStepStatus = (step: AgentStatus, current: AgentStatus) => {
88
+ const order = ['idle', 'architect', 'critic', 'refiner', 'complete'];
89
+ const stepIdx = order.indexOf(step);
90
+ const currentIdx = order.indexOf(current);
91
+
92
+ if (current === 'error') return 'text-slate-500';
93
+ if (currentIdx > stepIdx) return 'text-emerald-400';
94
+ if (currentIdx === stepIdx) return 'text-blue-400 animate-pulse';
95
+ return 'text-slate-600';
96
+ };
97
+
98
+ const renderAgentStep = (step: AgentStatus, icon: React.ReactNode, label: string) => {
99
+ const statusColor = getStepStatus(step, agentStatus);
100
+ const isCurrent = agentStatus === step;
101
+ const isDone = ['architect', 'critic', 'refiner', 'complete'].indexOf(agentStatus) > ['architect', 'critic', 'refiner'].indexOf(step);
102
+
103
+ return (
104
+ <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'}`}>
105
+ <div className={`${statusColor} transition-colors duration-300`}>
106
+ {isDone ? <CheckCircle2 size={24} /> : icon}
107
+ </div>
108
+ <div className="flex-1">
109
+ <div className={`font-semibold text-sm ${isCurrent ? 'text-blue-200' : 'text-slate-400'}`}>{label}</div>
110
+ {isCurrent && (
111
+ <div className="text-xs text-slate-500 mt-1">{agentMessage}</div>
112
+ )}
113
+ </div>
114
+ {isCurrent && <div className="w-2 h-2 rounded-full bg-blue-400 animate-ping" />}
115
+ </div>
116
+ );
117
+ }
118
+
119
+ return (
120
+ <div className="absolute inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm p-4">
121
+ <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">
122
+
123
+ <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">
124
+ <h2 className="text-lg font-bold text-white flex items-center gap-2">
125
+ <Sparkles className="text-purple-400" size={20} />
126
+ AI Architect Team
127
+ </h2>
128
+ <button onClick={onClose} disabled={isBuilding} className="text-slate-400 hover:text-white transition-colors disabled:opacity-50">
129
+ <X size={20} />
130
+ </button>
131
+ </div>
132
+
133
+ <div className="p-6 space-y-4">
134
+ {!isBuilding && agentStatus !== 'complete' ? (
135
+ <>
136
+ <div className="bg-purple-500/10 border border-purple-500/20 rounded-lg p-3 text-sm text-purple-200 flex gap-3">
137
+ <MessageSquare size={18} className="shrink-0 mt-0.5" />
138
+ <p>
139
+ Describe your model. Our AI Agents (Architect, Critic, and Refiner) will collaborate to build the best architecture for you.
140
+ </p>
141
+ </div>
142
+
143
+ <textarea
144
+ 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"
145
+ placeholder="e.g. Build a robust CNN for medical image segmentation with attention gates..."
146
+ value={prompt}
147
+ onChange={(e) => setPrompt(e.target.value)}
148
+ />
149
+
150
+ {error && (
151
+ <div className="flex items-center gap-2 text-red-400 text-sm bg-red-500/10 p-2 rounded border border-red-500/20">
152
+ <AlertCircle size={14} />
153
+ {error}
154
+ </div>
155
+ )}
156
+
157
+ <div className="flex justify-end pt-2">
158
+ <button
159
+ onClick={handleBuild}
160
+ disabled={!prompt.trim()}
161
+ className={`
162
+ flex items-center gap-2 px-6 py-2 rounded-lg font-medium text-white transition-all
163
+ ${!prompt.trim()
164
+ ? 'bg-slate-800 cursor-not-allowed opacity-50'
165
+ : 'bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-500 hover:to-blue-500 shadow-lg shadow-purple-500/20'}
166
+ `}
167
+ >
168
+ <Play size={18} fill="currentColor" />
169
+ Start Agents
170
+ </button>
171
+ </div>
172
+ </>
173
+ ) : (
174
+ <div className="space-y-3 py-2">
175
+ {renderAgentStep('architect', <HardHat size={24} />, "Architect Agent: Drafting Layout")}
176
+ {renderAgentStep('critic', <Search size={24} />, "Critic Agent: Reviewing Design")}
177
+ {renderAgentStep('refiner', <Wand2 size={24} />, "Refiner Agent: Finalizing Architecture")}
178
+
179
+ {agentStatus === 'complete' && (
180
+ <div className="text-center text-emerald-400 font-bold mt-4 animate-pulse">
181
+ Architecture Generation Complete!
182
+ </div>
183
+ )}
184
+ </div>
185
+ )}
186
+ </div>
187
+ </div>
188
+ </div>
189
+ );
190
+ };
191
+
192
+ export default AIBuilderModal;