import { useState } from 'react'; import { FolderSearch, Microscope } from 'lucide-react'; import { useCanvasStore } from '../../../hooks/useCanvasStore'; import { ExpressionSelector } from './ExpressionSelector'; import { FileBrowser } from './FileBrowser'; import { IntrospectionPanel } from './IntrospectionPanel'; import { ModelNodeData, SolverNodeData, DesignVarNodeData, AlgorithmNodeData, ObjectiveNodeData, ExtractorNodeData, ConstraintNodeData, SurrogateNodeData } from '../../../lib/canvas/schema'; interface NodeConfigPanelProps { nodeId: string; } // Common input class for dark theme const inputClass = "w-full px-3 py-2 bg-dark-800 border border-dark-600 text-white placeholder-dark-400 rounded-lg focus:border-primary-500 focus:outline-none transition-colors"; const selectClass = "w-full px-3 py-2 bg-dark-800 border border-dark-600 text-white rounded-lg focus:border-primary-500 focus:outline-none transition-colors"; const labelClass = "block text-sm font-medium text-dark-300 mb-1"; export function NodeConfigPanel({ nodeId }: NodeConfigPanelProps) { const { nodes, updateNodeData, deleteSelected } = useCanvasStore(); const node = nodes.find((n) => n.id === nodeId); const [showFileBrowser, setShowFileBrowser] = useState(false); const [showIntrospection, setShowIntrospection] = useState(false); if (!node) return null; const { data } = node; const handleChange = (field: string, value: unknown) => { updateNodeData(nodeId, { [field]: value, configured: true }); }; return (

Configure {data.label}

{/* Common: Label */}
handleChange('label', e.target.value)} className={inputClass} />
{/* Type-specific fields */} {data.type === 'model' && ( <>
handleChange('filePath', e.target.value)} placeholder="path/to/model.sim" className={`${inputClass} font-mono text-sm flex-1`} />
{/* Introspect Button */} {(data as ModelNodeData).filePath && ( )} )} {data.type === 'solver' && (
)} {data.type === 'designVar' && ( <>
{ handleChange('expressionName', name); handleChange('label', name || 'Design Variable'); if (units) handleChange('unit', units); // Set default min/max around current value if (value !== undefined) { const dvData = data as DesignVarNodeData; if (dvData.minValue === undefined) { handleChange('minValue', value * 0.5); } if (dvData.maxValue === undefined) { handleChange('maxValue', value * 1.5); } } }} placeholder="Select NX expression..." />
handleChange('minValue', parseFloat(e.target.value))} className={inputClass} />
handleChange('maxValue', parseFloat(e.target.value))} className={inputClass} />
handleChange('unit', e.target.value)} placeholder="mm" className={inputClass} />
)} {data.type === 'extractor' && ( <>
)} {data.type === 'algorithm' && ( <>
handleChange('maxTrials', parseInt(e.target.value))} placeholder="100" className={inputClass} />
)} {data.type === 'objective' && ( <>
handleChange('name', e.target.value)} placeholder="mass" className={inputClass} />
handleChange('weight', parseFloat(e.target.value))} className={inputClass} />
)} {data.type === 'constraint' && ( <>
handleChange('name', e.target.value)} placeholder="max_stress" className={inputClass} />
handleChange('value', parseFloat(e.target.value))} className={inputClass} />
)} {data.type === 'surrogate' && ( <>
handleChange('enabled', e.target.checked)} className="w-4 h-4 rounded bg-dark-800 border-dark-600 text-primary-500 focus:ring-primary-500" />
{(data as SurrogateNodeData).enabled && ( <>
handleChange('minTrials', parseInt(e.target.value))} className={inputClass} />
)} )}
{/* File Browser Modal */} setShowFileBrowser(false)} onSelect={(path, fileType) => { handleChange('filePath', path); handleChange('fileType', fileType.replace('.', '')); }} fileTypes={['.sim', '.prt', '.fem', '.afem']} /> {/* Introspection Panel */} {showIntrospection && (data as ModelNodeData).filePath && (
setShowIntrospection(false)} />
)}
); }