import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { Settings, Target, Sliders, AlertTriangle, Cpu, Box, Layers, Play, Download, RefreshCw, ChevronDown, ChevronUp, ArrowUp, ArrowDown, CheckCircle, Info, FileBox, FolderOpen, File, Layout, Grid3X3 } from 'lucide-react'; import { useStudy } from '../context/StudyContext'; import { Card } from '../components/common/Card'; import { Button } from '../components/common/Button'; import { apiClient, ModelFile } from '../api/client'; import { AtomizerCanvas } from '../components/canvas/AtomizerCanvas'; import { ConfigImporter } from '../components/canvas/panels/ConfigImporter'; import { useCanvasStore } from '../hooks/useCanvasStore'; interface StudyConfig { study_name: string; description?: string; objectives: { name: string; direction: 'minimize' | 'maximize'; unit?: string; target?: number; weight?: number; }[]; design_variables: { name: string; type: 'float' | 'int' | 'categorical'; low?: number; high?: number; step?: number; choices?: string[]; unit?: string; }[]; constraints: { name: string; type: 'le' | 'ge' | 'eq'; bound: number; unit?: string; }[]; algorithm: { name: string; sampler: string; pruner?: string; n_trials: number; timeout?: number; }; fea_model?: { software: string; solver: string; sim_file?: string; mesh_elements?: number; }; extractors?: { name: string; type: string; source?: string; }[]; } type TabType = 'config' | 'canvas'; export default function Setup() { const navigate = useNavigate(); const { selectedStudy, isInitialized } = useStudy(); const [activeTab, setActiveTab] = useState('config'); const [config, setConfig] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [expandedSections, setExpandedSections] = useState>( new Set(['objectives', 'variables', 'constraints', 'algorithm', 'modelFiles']) ); const [modelFiles, setModelFiles] = useState([]); const [modelDir, setModelDir] = useState(''); const [showImporter, setShowImporter] = useState(false); const [canvasNotification, setCanvasNotification] = useState(null); const { nodes } = useCanvasStore(); // Redirect if no study selected useEffect(() => { if (isInitialized && !selectedStudy) { navigate('/'); } }, [selectedStudy, navigate, isInitialized]); // Load study configuration useEffect(() => { if (selectedStudy) { loadConfig(); loadModelFiles(); } }, [selectedStudy]); const loadModelFiles = async () => { if (!selectedStudy) return; try { const data = await apiClient.getModelFiles(selectedStudy.id); setModelFiles(data.files); setModelDir(data.model_dir); } catch (err) { console.error('Failed to load model files:', err); } }; const handleOpenFolder = async () => { if (!selectedStudy) return; try { await apiClient.openFolder(selectedStudy.id, 'model'); } catch (err: any) { setError(err.message || 'Failed to open folder'); } }; const loadConfig = async () => { if (!selectedStudy) return; setLoading(true); setError(null); try { const response = await apiClient.getStudyConfig(selectedStudy.id); const rawConfig = response.config; // Transform backend config format to our StudyConfig format const transformedConfig: StudyConfig = { study_name: rawConfig.study_name || selectedStudy.name || selectedStudy.id, description: rawConfig.description, objectives: (rawConfig.objectives || []).map((obj: any) => ({ name: obj.name, direction: obj.direction || 'minimize', unit: obj.unit || obj.units, target: obj.target, weight: obj.weight })), design_variables: (rawConfig.design_variables || []).map((dv: any) => ({ name: dv.name, type: dv.type || 'float', low: dv.min ?? dv.low, high: dv.max ?? dv.high, step: dv.step, choices: dv.choices, unit: dv.unit || dv.units })), constraints: (rawConfig.constraints || []).map((c: any) => ({ name: c.name, type: c.type || 'le', bound: c.max_value ?? c.min_value ?? c.bound ?? 0, unit: c.unit || c.units })), algorithm: { name: rawConfig.optimizer?.name || rawConfig.algorithm?.name || 'Optuna', sampler: rawConfig.optimization?.algorithm || rawConfig.optimization_settings?.sampler || rawConfig.algorithm?.sampler || 'TPESampler', pruner: rawConfig.optimization_settings?.pruner || rawConfig.algorithm?.pruner, n_trials: rawConfig.optimization?.n_trials || rawConfig.optimization_settings?.n_trials || rawConfig.trials?.n_trials || selectedStudy.progress.total, timeout: rawConfig.optimization_settings?.timeout }, fea_model: rawConfig.fea_model || rawConfig.solver ? { software: rawConfig.fea_model?.software || rawConfig.solver?.type || 'NX Nastran', solver: rawConfig.fea_model?.solver || rawConfig.solver?.name || 'SOL 103', sim_file: rawConfig.sim_file || rawConfig.fea_model?.sim_file, mesh_elements: rawConfig.fea_model?.mesh_elements } : undefined, extractors: rawConfig.extractors }; setConfig(transformedConfig); } catch (err: any) { // If no config endpoint, create mock from available data setConfig({ study_name: selectedStudy.name || selectedStudy.id, objectives: [{ name: 'objective', direction: 'minimize' }], design_variables: [], constraints: [], algorithm: { name: 'Optuna', sampler: 'TPESampler', n_trials: selectedStudy.progress.total } }); setError('Configuration loaded with limited data'); } finally { setLoading(false); } }; const toggleSection = (section: string) => { setExpandedSections(prev => { const next = new Set(prev); if (next.has(section)) { next.delete(section); } else { next.add(section); } return next; }); }; const handleStartOptimization = async () => { if (!selectedStudy) return; try { await apiClient.startOptimization(selectedStudy.id); navigate('/dashboard'); } catch (err: any) { setError(err.message || 'Failed to start optimization'); } }; const handleExportConfig = () => { if (!config) return; const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${selectedStudy?.id || 'study'}_config.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; // Loading state if (!isInitialized) { return (

Loading study...

); } if (!selectedStudy) return null; // Calculate design space size const designSpaceSize = config?.design_variables.reduce((acc, v) => { if (v.type === 'categorical' && v.choices) { return acc * v.choices.length; } else if (v.type === 'int' && v.low !== undefined && v.high !== undefined) { return acc * (v.high - v.low + 1); } return acc * 1000; // Approximate for continuous }, 1) || 0; // Canvas tab - full height and full width if (activeTab === 'canvas') { const handleImport = (source: string) => { setCanvasNotification(`Loaded: ${source}`); setTimeout(() => setCanvasNotification(null), 3000); }; return (
{/* Tab Bar */}
{nodes.length === 0 && ( )} {selectedStudy?.name || 'Study'}
{/* Canvas - takes remaining height */}
{/* Config Importer Modal */} setShowImporter(false)} onImport={handleImport} currentStudyId={selectedStudy?.id} /> {/* Notification Toast */} {canvasNotification && (
{canvasNotification}
)}
); } return (
{/* Tab Bar */}
{/* Header */}

{config?.study_name || selectedStudy.name}

Study Configuration

{config?.description && (

{config.description}

)}
{selectedStudy.status === 'not_started' && ( )}
{/* Error Message */} {error && (
{error}
)} {loading ? (
) : (
{/* Left Column */}
{/* Objectives Panel */} {expandedSections.has('objectives') && (
{config?.objectives.map((obj, idx) => (
{obj.name} {obj.direction === 'minimize' ? ( ) : ( )} {obj.direction}
{obj.unit && Unit: {obj.unit}} {obj.target !== undefined && ( Target: {obj.target} )} {obj.weight !== undefined && ( Weight: {obj.weight} )}
))} {config?.objectives.length === 0 && (

No objectives configured

)}
Type: {(config?.objectives.length || 0) > 1 ? 'Multi-Objective' : 'Single-Objective'}
)}
{/* Design Variables Panel */} {expandedSections.has('variables') && (
{config?.design_variables.map((v, idx) => (
{v.name} {v.type}
{v.low !== undefined && v.high !== undefined && ( Range: [{v.low}, {v.high}] )} {v.step && Step: {v.step}} {v.unit && {v.unit}} {v.choices && ( Choices: {v.choices.join(', ')} )}
))} {config?.design_variables.length === 0 && (

No design variables configured

)} {designSpaceSize > 0 && (
Design Space: ~{designSpaceSize.toExponential(2)} combinations
)}
)}
{/* Constraints Panel */} {expandedSections.has('constraints') && (
{(config?.constraints.length || 0) > 0 ? ( {config?.constraints.map((c, idx) => ( ))}
Name Type Bound Unit
{c.name} {c.type === 'le' ? '≤' : c.type === 'ge' ? '≥' : '='} {c.bound} {c.unit || '-'}
) : (

No constraints configured

)}
)}
{/* Right Column */}
{/* Algorithm Configuration */} {expandedSections.has('algorithm') && (
Optimizer
{config?.algorithm.name || 'Optuna'}
Sampler
{config?.algorithm.sampler || 'TPE'}
Total Trials
{config?.algorithm.n_trials || selectedStudy.progress.total}
{config?.algorithm.pruner && (
Pruner
{config.algorithm.pruner}
)} {config?.algorithm.timeout && (
Timeout
{config.algorithm.timeout}s
)}
)}
{/* FEA Model Info */} {config?.fea_model && ( {expandedSections.has('model') && (
Software
{config.fea_model.software}
Solver
{config.fea_model.solver}
{config.fea_model.mesh_elements && (
Mesh Elements
{config.fea_model.mesh_elements.toLocaleString()}
)} {config.fea_model.sim_file && (
Simulation File
{config.fea_model.sim_file}
)}
)}
)} {/* NX Model Files */} {expandedSections.has('modelFiles') && (
{/* Open Folder Button */} {/* Model Directory Path */} {modelDir && (
{modelDir}
)} {/* File List */} {modelFiles.length > 0 ? (
{modelFiles.map((file, idx) => (
{file.name}
{file.extension.slice(1)}
{file.size_display} {new Date(file.modified).toLocaleDateString()}
))}
) : (

No model files found

)} {/* File Type Legend */} {modelFiles.length > 0 && (
.prt = Part .sim = Simulation .fem = FEM .bdf = Nastran .op2 = Results
)}
)}
{/* Extractors */} {config?.extractors && config.extractors.length > 0 && ( {expandedSections.has('extractors') && (
{config.extractors.map((ext, idx) => (
{ext.name} {ext.type}
{ext.source && (
{ext.source}
)}
))}
)}
)} {/* Study Stats */}
{selectedStudy.progress.current}
Trials Completed
{selectedStudy.best_value?.toExponential(3) || 'N/A'}
Best Value
{/* Progress Bar */}
Progress {Math.round((selectedStudy.progress.current / selectedStudy.progress.total) * 100)}%
{/* Status Badge */}
{selectedStudy.status === 'completed' && } {selectedStudy.status === 'running' && } {selectedStudy.status.replace('_', ' ')}
)}
); }