import React, { useCallback, useRef, useState, useMemo, useEffect } from 'react'; import ReactFlow, { addEdge, useNodesState, useEdgesState, Controls, Background, Connection, Edge, Node, BackgroundVariant, Panel, ConnectionMode } from 'reactflow'; import { FileText, CheckCircle, AlertTriangle, X, Copy, Check, Lightbulb, Key, Wrench, Menu, Activity } from 'lucide-react'; import Sidebar from './Sidebar'; import PropertiesPanel from './PropertiesPanel'; import CustomNode from './CustomNode'; import CodeViewer from './CodeViewer'; import AIBuilderModal from './AIBuilderModal'; import SuggestionsModal from './SuggestionsModal'; import ApiKeyModal from './ApiKeyModal'; import FixerModal from './FixerModal'; import { INITIAL_NODES, INITIAL_EDGES, LAYER_DEFINITIONS, TEMPLATES } from '../constants'; import { generateRefinedPrompt, validateArchitecture, getArchitectureSuggestions, getUserApiKey, implementArchitectureSuggestions } from '../services/geminiService'; import { NodeData, LayerType, LogEntry } from '../types'; let id = 1000; const getId = () => `${id++}`; // --- MAIN BUILDER CONTENT --- interface BuilderProps { onBackToHome: () => void; } const Builder: React.FC = ({ onBackToHome }) => { const reactFlowWrapper = useRef(null); const [nodes, setNodes, onNodesChange] = useNodesState(INITIAL_NODES); const [edges, setEdges, onEdgesChange] = useEdgesState(INITIAL_EDGES); const [reactFlowInstance, setReactFlowInstance] = useState(null); const [selectedNodeId, setSelectedNodeId] = useState(null); const [generatedPrompt, setGeneratedPrompt] = useState(''); const [isPromptViewerOpen, setIsPromptViewerOpen] = useState(false); const [isGeneratingPrompt, setIsGeneratingPrompt] = useState(false); const [validationMsg, setValidationMsg] = useState(null); const [layoutCopied, setLayoutCopied] = useState(false); // Responsive Layout State const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [isPropertiesOpen, setIsPropertiesOpen] = useState(false); // Suggestions State const [isSuggestionsOpen, setIsSuggestionsOpen] = useState(false); const [suggestionsText, setSuggestionsText] = useState(''); const [isSuggestionsLoading, setIsSuggestionsLoading] = useState(false); const [isImplementingSuggestions, setIsImplementingSuggestions] = useState(false); // Fixer State const [isFixerOpen, setIsFixerOpen] = useState(false); // AI Builder and Template State const [isAIBuilderOpen, setIsAIBuilderOpen] = useState(false); const [isTemplateMenuOpen, setIsTemplateMenuOpen] = useState(false); // API Key State const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false); const [isConnected, setIsConnected] = useState(false); // Activity Log State const [logs, setLogs] = useState([]); const addLog = useCallback((message: string, type: 'info' | 'success' | 'warning' | 'error' = 'info') => { setLogs(prev => [{ id: Date.now().toString() + Math.random(), timestamp: new Date(), message, type }, ...prev]); }, []); // Initialize responsiveness useEffect(() => { const handleResize = () => { if (window.innerWidth < 768) { setIsSidebarOpen(false); } else { setIsSidebarOpen(true); } }; // Set initial state handleResize(); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); // Load connection status on mount useEffect(() => { setIsConnected(!!getUserApiKey()); addLog('System initialized. Ready to build.', 'info'); }, [addLog]); // Define custom node types const nodeTypes = useMemo(() => ({ custom: CustomNode }), []); const onConnect = useCallback( (params: Connection) => setEdges((eds) => addEdge({ ...params, animated: true, style: { stroke: '#94a3b8' } }, eds)), [setEdges] ); const onDragOver = useCallback((event: React.DragEvent) => { event.preventDefault(); event.dataTransfer.dropEffect = 'move'; }, []); const onDrop = useCallback( (event: React.DragEvent) => { event.preventDefault(); if (!reactFlowWrapper.current || !reactFlowInstance) return; const type = event.dataTransfer.getData('application/reactflow') as LayerType; if (!type) return; const position = reactFlowInstance.screenToFlowPosition({ x: event.clientX, y: event.clientY, }); const label = LAYER_DEFINITIONS[type].label; const newNode: Node = { id: getId(), type: 'custom', position, data: { label: label, type: type, params: LAYER_DEFINITIONS[type].parameters.reduce((acc, p) => ({...acc, [p.name]: p.default}), {}) }, }; setNodes((nds) => nds.concat(newNode)); setSelectedNodeId(newNode.id); setIsPropertiesOpen(true); // Open properties panel on drop // On mobile, close sidebar after dropping if (window.innerWidth < 768) { setIsSidebarOpen(false); } addLog(`Added layer: ${label}`, 'info'); }, [reactFlowInstance, setNodes, addLog] ); const onNodeClick = useCallback((_: React.MouseEvent, node: Node) => { setSelectedNodeId(node.id); setIsPropertiesOpen(true); }, []); const onPaneClick = useCallback(() => { setSelectedNodeId(null); setIsPropertiesOpen(false); }, []); // Update node data (params or label) from Properties Panel const updateNodeData = (id: string, newData: Partial) => { setNodes((nds) => nds.map((node) => { if (node.id === id) { return { ...node, data: { ...node.data, ...newData } }; } return node; }) ); }; const deleteNode = (id: string) => { const node = nodes.find(n => n.id === id); setNodes((nds) => nds.filter((node) => node.id !== id)); setEdges((eds) => eds.filter((edge) => edge.source !== id && edge.target !== id)); setSelectedNodeId(null); setIsPropertiesOpen(false); if (node) addLog(`Deleted layer: ${node.data.label}`, 'info'); }; const handleAPIError = (error: any) => { const msg = error.message || String(error); addLog(`API Error: ${msg}`, 'error'); // Force disconnect if authentication failed to allow user to re-enter key if (msg.includes('API Key') || msg.includes('403') || msg.includes('401') || msg.includes('400')) { setIsConnected(false); setIsApiKeyModalOpen(true); addLog("Disconnected due to invalid API key. Please check your key.", 'warning'); } } const handleGeneratePrompt = async () => { if (!isConnected) { setIsApiKeyModalOpen(true); return; } setIsGeneratingPrompt(true); setIsPromptViewerOpen(true); addLog("Generating PyTorch code prompt...", 'info'); try { const promptText = await generateRefinedPrompt(nodes, edges); setGeneratedPrompt(promptText); addLog("Code prompt generated successfully.", 'success'); } catch (e) { setGeneratedPrompt("Failed to generate prompt. See logs for details."); handleAPIError(e); } finally { setIsGeneratingPrompt(false); } }; const handleValidate = async () => { if (!isConnected) { setIsApiKeyModalOpen(true); return; } setValidationMsg("Validating..."); addLog("Running architecture validation...", 'info'); try { const result = await validateArchitecture(nodes, edges); setValidationMsg(result); if (result.toLowerCase().includes("valid") && !result.toLowerCase().includes("invalid") && result.length < 50) { setTimeout(() => setValidationMsg(null), 5000); addLog("Validation passed: Architecture is valid.", 'success'); } else { addLog("Validation issues found.", 'warning'); } } catch (e) { setValidationMsg("Validation failed due to API error."); handleAPIError(e); } }; const handleGetSuggestions = async () => { if (!isConnected) { setIsApiKeyModalOpen(true); return; } setIsSuggestionsOpen(true); setIsSuggestionsLoading(true); try { const suggestions = await getArchitectureSuggestions(nodes, edges); setSuggestionsText(suggestions); } catch (error) { setSuggestionsText("Failed to get suggestions. API Error."); handleAPIError(error); } finally { setIsSuggestionsLoading(false); } }; const handleImplementSuggestions = async () => { if (!suggestionsText || isImplementingSuggestions) return; setIsImplementingSuggestions(true); addLog("AI Agents implementing suggestions...", 'info'); try { const result = await implementArchitectureSuggestions(nodes, edges, suggestionsText); if (result && result.nodes) { // Fix types and defaults safely const processedNodes = result.nodes.map((n: any) => { const data = n.data || {}; const type = data.type || n.type || 'Identity'; // Fallback type return { ...n, type: 'custom', data: { ...data, type: type, label: data.label || n.label || type, // Safe fallback for label params: data.params || {} } }; }); const processedEdges = result.edges.map((e: any) => ({ ...e, animated: true, style: { stroke: '#94a3b8' } })); setNodes(processedNodes); setEdges(processedEdges); setIsSuggestionsOpen(false); // Close modal on success addLog("Implemented AI improvements.", 'success'); } } catch (e) { handleAPIError(e); addLog("Failed to implement suggestions.", 'error'); } finally { setIsImplementingSuggestions(false); } }; const handleCopyLayout = () => { const layout = { nodes, edges }; navigator.clipboard.writeText(JSON.stringify(layout, null, 2)); setLayoutCopied(true); setTimeout(() => setLayoutCopied(false), 2000); addLog("Layout JSON copied to clipboard.", 'info'); }; const handleApplyAIBuild = (newNodes: any[], newEdges: any[], logMsg?: string) => { setNodes(newNodes); setEdges(newEdges); if (logMsg) { addLog(logMsg, 'success'); } }; const loadTemplate = (templateKey: string) => { const template = TEMPLATES[templateKey]; if (template) { // Need to deep clone to avoid reference issues if loaded multiple times const clonedNodes = JSON.parse(JSON.stringify(template.nodes)); const clonedEdges = JSON.parse(JSON.stringify(template.edges)); // Update styling and functional props that aren't in JSON const hydratedNodes = clonedNodes.map((n: any) => ({ ...n, data: { ...n.data, label: LAYER_DEFINITIONS[n.data.type as LayerType]?.label || n.data.label } })); const hydratedEdges = clonedEdges.map((e: any) => ({ ...e, animated: true, style: { stroke: '#94a3b8' } })); setNodes(hydratedNodes); setEdges(hydratedEdges); setIsTemplateMenuOpen(false); // Auto open properties to see details of first node if (hydratedNodes.length > 0) { setSelectedNodeId(hydratedNodes[0].id); setIsPropertiesOpen(true); } addLog(`Loaded template: ${template.name}`, 'info'); } }; const selectedNode = useMemo(() => nodes.find((n) => n.id === selectedNodeId) || null, [nodes, selectedNodeId] ); return (
isConnected ? setIsAIBuilderOpen(true) : setIsApiKeyModalOpen(true)} onSelectTemplate={(id) => id === 'menu' ? setIsTemplateMenuOpen(true) : loadTemplate(id)} onBackToHome={onBackToHome} isConnected={isConnected} isOpen={isSidebarOpen} onToggle={() => setIsSidebarOpen(!isSidebarOpen)} />
{/* Mobile Menu Trigger */} {/* Action Toolbar */} {/* Mobile Activity Toggle */} {validationMsg && (

Validation Report

{validationMsg}

{!validationMsg.toLowerCase().includes("architecture is valid") && ( )}
)} {/* Template Selection Modal Overlay */} {isTemplateMenuOpen && (

Select Template

{Object.entries(TEMPLATES).map(([key, t]) => ( ))}
)}
{ setSelectedNodeId(null); setIsPropertiesOpen(false); }} logs={logs} isOpen={isPropertiesOpen} /> setIsPromptViewerOpen(false)} code={generatedPrompt} isLoading={isGeneratingPrompt} /> setIsSuggestionsOpen(false)} suggestions={suggestionsText} isLoading={isSuggestionsLoading} onImplement={handleImplementSuggestions} isImplementing={isImplementingSuggestions} /> setIsAIBuilderOpen(false)} onApply={handleApplyAIBuild} currentNodes={nodes} /> setIsFixerOpen(false)} onApply={handleApplyAIBuild} errorMsg={validationMsg || ''} nodes={nodes} edges={edges} /> setIsApiKeyModalOpen(false)} onSuccess={() => setIsConnected(true)} />
); }; export default Builder;