import { useState, useEffect, useCallback } from 'react'; import { Settings, Save, X, AlertTriangle, Check, RotateCcw } from 'lucide-react'; import { Card } from './common/Card'; interface DesignVariable { name: string; min: number; max: number; type?: string; description?: string; } interface Objective { name: string; direction: 'minimize' | 'maximize'; description?: string; unit?: string; } interface OptimizationConfig { study_name?: string; description?: string; design_variables?: DesignVariable[]; objectives?: Objective[]; constraints?: any[]; optimization_settings?: { n_trials?: number; sampler?: string; [key: string]: any; }; [key: string]: any; } interface ConfigEditorProps { studyId: string; onClose: () => void; onSaved?: () => void; } export function ConfigEditor({ studyId, onClose, onSaved }: ConfigEditorProps) { const [config, setConfig] = useState(null); const [originalConfig, setOriginalConfig] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const [isRunning, setIsRunning] = useState(false); const [activeTab, setActiveTab] = useState<'general' | 'variables' | 'objectives' | 'settings' | 'json'>('general'); const [jsonText, setJsonText] = useState(''); const [jsonError, setJsonError] = useState(null); // Load config useEffect(() => { const loadConfig = async () => { try { setLoading(true); setError(null); // Check if optimization is running const processRes = await fetch(`/api/optimization/studies/${studyId}/process`); const processData = await processRes.json(); setIsRunning(processData.is_running); // Load config const configRes = await fetch(`/api/optimization/studies/${studyId}/config`); if (!configRes.ok) { throw new Error('Failed to load config'); } const configData = await configRes.json(); setConfig(configData.config); setOriginalConfig(JSON.parse(JSON.stringify(configData.config))); setJsonText(JSON.stringify(configData.config, null, 2)); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load config'); } finally { setLoading(false); } }; loadConfig(); }, [studyId]); // Handle JSON text changes const handleJsonChange = useCallback((text: string) => { setJsonText(text); setJsonError(null); try { const parsed = JSON.parse(text); setConfig(parsed); } catch (err) { setJsonError('Invalid JSON'); } }, []); // Save config const handleSave = async () => { if (!config || isRunning) return; try { setSaving(true); setError(null); setSuccess(null); const res = await fetch(`/api/optimization/studies/${studyId}/config`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ config }) }); if (!res.ok) { const data = await res.json(); throw new Error(data.detail || 'Failed to save config'); } setSuccess('Configuration saved successfully'); setOriginalConfig(JSON.parse(JSON.stringify(config))); onSaved?.(); // Clear success after 3 seconds setTimeout(() => setSuccess(null), 3000); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to save config'); } finally { setSaving(false); } }; // Reset to original const handleReset = () => { if (originalConfig) { setConfig(JSON.parse(JSON.stringify(originalConfig))); setJsonText(JSON.stringify(originalConfig, null, 2)); setJsonError(null); } }; // Check if there are unsaved changes const hasChanges = config && originalConfig ? JSON.stringify(config) !== JSON.stringify(originalConfig) : false; // Update a design variable const updateDesignVariable = (index: number, field: keyof DesignVariable, value: any) => { if (!config?.design_variables) return; const newVars = [...config.design_variables]; newVars[index] = { ...newVars[index], [field]: value }; setConfig({ ...config, design_variables: newVars }); setJsonText(JSON.stringify({ ...config, design_variables: newVars }, null, 2)); }; // Update an objective const updateObjective = (index: number, field: keyof Objective, value: any) => { if (!config?.objectives) return; const newObjs = [...config.objectives]; newObjs[index] = { ...newObjs[index], [field]: value }; setConfig({ ...config, objectives: newObjs }); setJsonText(JSON.stringify({ ...config, objectives: newObjs }, null, 2)); }; // Update optimization settings const updateSettings = (field: string, value: any) => { if (!config) return; const newSettings = { ...config.optimization_settings, [field]: value }; setConfig({ ...config, optimization_settings: newSettings }); setJsonText(JSON.stringify({ ...config, optimization_settings: newSettings }, null, 2)); }; if (loading) { return (
); } return (
{/* Header */}

Edit Configuration

{hasChanges && ( Unsaved changes )}
{/* Warning if running */} {isRunning && (
Optimization is running. Stop it before editing configuration.
)} {/* Tabs */}
{(['general', 'variables', 'objectives', 'settings', 'json'] as const).map(tab => ( ))}
{/* Content */}
{error && (
{error}
)} {success && (
{success}
)} {config && ( <> {/* General Tab */} {activeTab === 'general' && (
{ setConfig({ ...config, study_name: e.target.value }); setJsonText(JSON.stringify({ ...config, study_name: e.target.value }, null, 2)); }} disabled={isRunning} className="w-full px-3 py-2 bg-dark-700 border border-dark-600 rounded-lg text-white disabled:opacity-50" />