import React, { useState, useEffect } from 'react'; import { Play, Pause, CheckCircle, Settings, AlertTriangle, Loader2, ExternalLink, Sliders, Skull, Info } from 'lucide-react'; import { apiClient, ProcessStatus } from '../../api/client'; import { useStudy } from '../../context/StudyContext'; interface ControlPanelProps { onStatusChange?: () => void; horizontal?: boolean; } export const ControlPanel: React.FC = ({ onStatusChange, horizontal = false }) => { const { selectedStudy, refreshStudies } = useStudy(); const [processStatus, setProcessStatus] = useState(null); const [actionInProgress, setActionInProgress] = useState(null); const [error, setError] = useState(null); const [showSettings, setShowSettings] = useState(false); const [optunaAvailable, setOptunaAvailable] = useState(null); const [optunaInstallHint, setOptunaInstallHint] = useState(null); // Settings for starting optimization const [settings, setSettings] = useState({ freshStart: false, maxIterations: 100, trials: 300, // For SAT scripts feaBatchSize: 5, tuneTrials: 30, ensembleSize: 3, patience: 5, }); // Validate top N const [validateTopN, setValidateTopN] = useState(5); useEffect(() => { if (selectedStudy) { fetchProcessStatus(); const interval = setInterval(fetchProcessStatus, 5000); return () => clearInterval(interval); } }, [selectedStudy]); // Check optuna-dashboard availability on mount useEffect(() => { const checkOptuna = async () => { try { const result = await apiClient.checkOptunaAvailable(); setOptunaAvailable(result.available); if (!result.available && result.install_instructions) { setOptunaInstallHint(result.install_instructions); } } catch { setOptunaAvailable(false); setOptunaInstallHint('pip install optuna-dashboard'); } }; checkOptuna(); }, []); const fetchProcessStatus = async () => { if (!selectedStudy) return; try { const status = await apiClient.getProcessStatus(selectedStudy.id); setProcessStatus(status); } catch (err) { // Process status endpoint might not exist yet setProcessStatus(null); } }; const handleStart = async () => { if (!selectedStudy) return; setActionInProgress('start'); setError(null); try { await apiClient.startOptimization(selectedStudy.id, { freshStart: settings.freshStart, maxIterations: settings.maxIterations, trials: settings.trials, feaBatchSize: settings.feaBatchSize, tuneTrials: settings.tuneTrials, ensembleSize: settings.ensembleSize, patience: settings.patience, }); await fetchProcessStatus(); await refreshStudies(); onStatusChange?.(); } catch (err: any) { setError(err.message || 'Failed to start optimization'); } finally { setActionInProgress(null); } }; const handleStop = async () => { if (!selectedStudy) return; setActionInProgress('stop'); setError(null); try { await apiClient.stopOptimization(selectedStudy.id); await fetchProcessStatus(); await refreshStudies(); onStatusChange?.(); } catch (err: any) { setError(err.message || 'Failed to stop optimization'); } finally { setActionInProgress(null); } }; const handlePause = async () => { if (!selectedStudy) return; setActionInProgress('pause'); setError(null); try { await apiClient.pauseOptimization(selectedStudy.id); await fetchProcessStatus(); onStatusChange?.(); } catch (err: any) { setError(err.message || 'Failed to pause optimization'); } finally { setActionInProgress(null); } }; const handleResume = async () => { if (!selectedStudy) return; setActionInProgress('resume'); setError(null); try { await apiClient.resumeOptimization(selectedStudy.id); await fetchProcessStatus(); onStatusChange?.(); } catch (err: any) { setError(err.message || 'Failed to resume optimization'); } finally { setActionInProgress(null); } }; const handleValidate = async () => { if (!selectedStudy) return; setActionInProgress('validate'); setError(null); try { await apiClient.validateOptimization(selectedStudy.id, { topN: validateTopN }); await fetchProcessStatus(); await refreshStudies(); onStatusChange?.(); } catch (err: any) { setError(err.message || 'Failed to start validation'); } finally { setActionInProgress(null); } }; const handleLaunchOptuna = async () => { if (!selectedStudy) return; setActionInProgress('optuna'); setError(null); try { const result = await apiClient.launchOptunaDashboard(selectedStudy.id); window.open(result.url, '_blank'); } catch (err: any) { setError(err.message || 'Failed to launch Optuna dashboard'); } finally { setActionInProgress(null); } }; const isRunning = processStatus?.is_running || selectedStudy?.status === 'running'; const isPaused = processStatus?.is_paused || false; // Horizontal layout for top of page if (horizontal) { return (
{/* Status */}
{isRunning && isPaused ? ( <>
Paused ) : isRunning ? ( <>
Running ) : ( <>
Stopped )}
{/* Trial counts */} {processStatus?.completed_trials !== undefined && ( Trials: {processStatus.completed_trials} {processStatus.total_trials && ( /{processStatus.total_trials} )} )} {/* ETA and Rate */} {isRunning && processStatus?.eta_formatted && ( ETA: {processStatus.eta_formatted} )} {processStatus?.rate_per_hour && ( Rate: {processStatus.rate_per_hour.toFixed(1)}/hr )}
{/* Main Actions */}
{isRunning ? ( <> {/* Pause/Resume Button */} {isPaused ? ( ) : ( )} {/* Kill Button */} ) : ( )}
{optunaAvailable === false && optunaInstallHint && (
Install with: {optunaInstallHint}
)}
{/* Settings Toggle */} {/* Error */} {error && (
{error}
)}
{/* Collapsible Settings */} {showSettings && (
setSettings({ ...settings, trials: parseInt(e.target.value) || 300 })} className="w-16 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm" />
setSettings({ ...settings, feaBatchSize: parseInt(e.target.value) || 5 })} className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm" />
setSettings({ ...settings, patience: parseInt(e.target.value) || 5 })} className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm" />
setSettings({ ...settings, tuneTrials: parseInt(e.target.value) || 30 })} className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm" />
setSettings({ ...settings, ensembleSize: parseInt(e.target.value) || 3 })} className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm" />
setValidateTopN(parseInt(e.target.value) || 5)} className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm" />
)}
); } // Vertical layout (original sidebar layout) return (
{/* Header */}

Optimization Control

{/* Status */}
Status
{isRunning && isPaused ? ( <>
Paused ) : isRunning ? ( <>
Running ) : ( <>
Stopped )}
{processStatus && (
{processStatus.completed_trials !== undefined && (
Trials: {processStatus.completed_trials} {processStatus.total_trials && ( /{processStatus.total_trials} )}
)} {isRunning && !isPaused && processStatus.eta_formatted && (
ETA: {processStatus.eta_formatted}
)} {processStatus.rate_per_hour && (
Rate: {processStatus.rate_per_hour.toFixed(1)}/hr
)}
)}
{/* Settings Panel */} {showSettings && (
setSettings({ ...settings, trials: parseInt(e.target.value) || 300 })} className="w-full px-3 py-2 bg-dark-700 border border-dark-600 rounded-lg text-white text-sm" />
setSettings({ ...settings, feaBatchSize: parseInt(e.target.value) || 5 })} className="w-full px-3 py-2 bg-dark-700 border border-dark-600 rounded-lg text-white text-sm" />
setSettings({ ...settings, patience: parseInt(e.target.value) || 5 })} className="w-full px-3 py-2 bg-dark-700 border border-dark-600 rounded-lg text-white text-sm" />
setSettings({ ...settings, tuneTrials: parseInt(e.target.value) || 30 })} className="w-full px-3 py-2 bg-dark-700 border border-dark-600 rounded-lg text-white text-sm" />
setSettings({ ...settings, ensembleSize: parseInt(e.target.value) || 3 })} className="w-full px-3 py-2 bg-dark-700 border border-dark-600 rounded-lg text-white text-sm" />
)} {/* Error Message */} {error && (
{error}
)} {/* Actions */}
{/* Start / Pause / Resume / Kill Buttons */} {isRunning ? ( <> {/* Pause/Resume Button */} {isPaused ? ( ) : ( )} {/* Kill Button */} ) : ( <> {/* Validate Button */} )}
{/* Validation Settings */}
Validate top setValidateTopN(parseInt(e.target.value) || 5)} className="w-16 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm text-center" /> NN predictions with FEA
{/* Optuna Dashboard Button */}
{optunaAvailable === false && optunaInstallHint && (
Install with: {optunaInstallHint}
)}
); }; export default ControlPanel;