import { useEffect, useState } from 'react'; import { Cpu, Layers, Target, Database, Brain, RefreshCw } from 'lucide-react'; import { apiClient } from '../../api/client'; interface OptimizerState { available: boolean; source?: string; phase?: string; phase_description?: string; phase_progress?: number; current_strategy?: string; sampler?: { name: string; description: string; }; objectives?: Array<{ name: string; direction: string; current_best?: number; unit?: string; }>; plan?: { total_phases: number; current_phase: number; phases: string[]; }; completed_trials?: number; total_trials?: number; } interface OptimizerStatePanelProps { studyId?: string; // Legacy props for backwards compatibility sampler?: string; nTrials?: number; completedTrials?: number; feaTrials?: number; nnTrials?: number; objectives?: Array<{ name: string; direction: string }>; isMultiObjective?: boolean; paretoSize?: number; } export function OptimizerStatePanel({ studyId, sampler: legacySampler = 'TPESampler', nTrials: legacyNTrials = 100, completedTrials: legacyCompletedTrials = 0, feaTrials = 0, nnTrials = 0, objectives: legacyObjectives = [], isMultiObjective = false, paretoSize = 0 }: OptimizerStatePanelProps) { const [state, setState] = useState(null); const [loading, setLoading] = useState(false); // Fetch dynamic state if studyId is provided useEffect(() => { if (!studyId) return; const fetchState = async () => { setLoading(true); try { const result = await apiClient.getOptimizerState(studyId); setState(result); } catch { setState(null); } finally { setLoading(false); } }; fetchState(); const interval = setInterval(fetchState, 5000); // Refresh every 5 seconds return () => clearInterval(interval); }, [studyId]); // Use dynamic state if available, otherwise fall back to legacy props const sampler = state?.sampler?.name || legacySampler; const samplerDescription = state?.sampler?.description; const nTrials = state?.total_trials || legacyNTrials; const completedTrials = state?.completed_trials ?? legacyCompletedTrials; const objectives = state?.objectives?.map(o => ({ name: o.name, direction: o.direction })) || legacyObjectives; const phase = state?.phase || getPhase(completedTrials, nTrials); const phaseDescription = state?.phase_description; const phaseProgress = state?.phase_progress ?? getPhaseProgress(completedTrials, nTrials); const plan = state?.plan; // Determine optimizer phase based on progress (fallback) function getPhase(completed: number, total: number): string { if (completed === 0) return 'initializing'; if (completed < 10) return 'exploration'; if (completed < total * 0.5) return 'exploitation'; if (completed < total * 0.9) return 'refinement'; return 'convergence'; } function getPhaseProgress(completed: number, total: number): number { if (completed === 0) return 0; if (completed < 10) return completed / 10; if (completed < total * 0.5) return (completed - 10) / (total * 0.5 - 10); if (completed < total * 0.9) return (completed - total * 0.5) / (total * 0.4); return (completed - total * 0.9) / (total * 0.1); } // Format sampler name for display const formatSampler = (s: string) => { const samplers: Record = { 'TPESampler': 'TPE (Bayesian)', 'NSGAIISampler': 'NSGA-II', 'NSGAIIISampler': 'NSGA-III', 'CmaEsSampler': 'CMA-ES', 'RandomSampler': 'Random', 'GridSampler': 'Grid', 'QMCSampler': 'Quasi-Monte Carlo' }; return samplers[s] || s; }; const phaseColors: Record = { 'initializing': 'text-dark-400', 'exploration': 'text-primary-400', 'exploitation': 'text-yellow-400', 'refinement': 'text-blue-400', 'convergence': 'text-green-400' }; const phaseLabels: Record = { 'initializing': 'Initializing', 'exploration': 'Exploration', 'exploitation': 'Exploitation', 'refinement': 'Refinement', 'convergence': 'Convergence' }; return (
{/* Header */}
Optimizer State
{loading && }
{/* Sampler with description */}
{formatSampler(sampler)}
{samplerDescription && (

{samplerDescription}

)}
{/* Phase progress */}
Phase {plan ? `${plan.current_phase}/${plan.total_phases}` : ''} {phaseLabels[phase] || phase}
{phaseDescription && (

{phaseDescription}

)}
{/* FEA vs NN Trials (for hybrid optimizations) */} {(feaTrials > 0 || nnTrials > 0) && (
Trial Sources
{feaTrials}
FEA
{nnTrials}
Neural Net
{nnTrials > 0 && (
{((nnTrials / (feaTrials + nnTrials)) * 100).toFixed(0)}% acceleration from surrogate
)}
)} {/* Live objectives with current best */} {objectives.length > 0 && (
{isMultiObjective || objectives.length > 1 ? 'Multi-Objective' : 'Single Objective'}
{objectives.slice(0, 3).map((obj, idx) => { const stateObj = state?.objectives?.[idx]; return (
{obj.name.length > 15 ? obj.name.slice(0, 13) + '...' : obj.name}
{stateObj?.current_best !== undefined && stateObj.current_best !== null && ( {stateObj.current_best.toFixed(2)} {stateObj.unit && {stateObj.unit}} )} {obj.direction === 'minimize' ? 'min' : 'max'}
); })} {objectives.length > 3 && (
+{objectives.length - 3} more
)}
)} {/* Pareto Front Size (for multi-objective) */} {(isMultiObjective || objectives.length > 1) && paretoSize > 0 && (
Pareto Front Size {paretoSize} solutions
)} {/* Source indicator (for debugging) */} {state?.source && (
Source: {state.source}
)}
); }