/** * CodeEditorPanel - Monaco editor for custom extractor Python code * * Features: * - Python syntax highlighting * - Auto-completion for common patterns * - Error display * - Claude AI code generation button * - Preview of extracted outputs */ import { useState, useCallback, useRef } from 'react'; import Editor, { OnMount, OnChange } from '@monaco-editor/react'; import { Play, Wand2, Copy, Check, AlertCircle, RefreshCw, X, ChevronDown, ChevronRight, FileCode, Sparkles, } from 'lucide-react'; // Monaco editor types type Monaco = Parameters[1]; type EditorInstance = Parameters[0]; interface CodeEditorPanelProps { /** Initial code content */ initialCode?: string; /** Callback when code changes */ onChange?: (code: string) => void; /** Callback when user requests Claude generation */ onRequestGeneration?: (prompt: string) => Promise; /** Whether the panel is read-only */ readOnly?: boolean; /** Extractor name for context */ extractorName?: string; /** Output variable names */ outputs?: string[]; /** Optional height (default: 300px) */ height?: number | string; /** Show/hide header */ showHeader?: boolean; /** Callback when running code (validation) */ onRun?: (code: string) => Promise<{ success: boolean; error?: string; outputs?: Record }>; /** Close button callback */ onClose?: () => void; } // Default Python template for custom extractors const DEFAULT_EXTRACTOR_TEMPLATE = `""" Custom Extractor Function This function is called after FEA simulation completes. It receives the results and should return extracted values. Available inputs: - op2_path: Path to the .op2 results file - fem_path: Path to the .fem file - params: Dict of current design variable values - subcase_id: Current subcase being analyzed (optional) Return a dict with your extracted values. """ from pyNastran.op2.op2 import OP2 import numpy as np def extract(op2_path: str, fem_path: str, params: dict, subcase_id: int = 1) -> dict: """ Extract physics from FEA results. Args: op2_path: Path to OP2 results file fem_path: Path to FEM file params: Current design variable values subcase_id: Subcase ID to analyze Returns: Dict with extracted values, e.g. {'max_stress': 150.5, 'mass': 2.3} """ # Load OP2 results op2 = OP2() op2.read_op2(op2_path) # Example: Extract max displacement if subcase_id in op2.displacements: disp = op2.displacements[subcase_id] # Get magnitude of displacement vectors magnitudes = np.sqrt(np.sum(disp.data[0, :, 1:4]**2, axis=1)) max_disp = float(np.max(magnitudes)) else: max_disp = 0.0 return { 'max_displacement': max_disp, # Add more outputs as needed } `; export function CodeEditorPanel({ initialCode = DEFAULT_EXTRACTOR_TEMPLATE, onChange, onRequestGeneration, readOnly = false, extractorName = 'custom_extractor', outputs = [], height = 400, showHeader = true, onRun, onClose, }: CodeEditorPanelProps) { const [code, setCode] = useState(initialCode); const [isGenerating, setIsGenerating] = useState(false); const [isRunning, setIsRunning] = useState(false); const [error, setError] = useState(null); const [runResult, setRunResult] = useState<{ success: boolean; outputs?: Record } | null>(null); const [copied, setCopied] = useState(false); const [showPromptInput, setShowPromptInput] = useState(false); const [generationPrompt, setGenerationPrompt] = useState(''); const [showOutputs, setShowOutputs] = useState(true); const editorRef = useRef(null); const monacoRef = useRef(null); // Handle editor mount const handleEditorMount: OnMount = (editor, monaco) => { editorRef.current = editor; monacoRef.current = monaco; // Configure Python language monaco.languages.registerCompletionItemProvider('python', { provideCompletionItems: (model: Parameters[0], position: { lineNumber: number; column: number }) => { const word = model.getWordUntilPosition(position); const range = { startLineNumber: position.lineNumber, endLineNumber: position.lineNumber, startColumn: word.startColumn, endColumn: word.endColumn, }; const suggestions = [ { label: 'op2.read_op2', kind: monaco.languages.CompletionItemKind.Method, insertText: 'op2.read_op2(op2_path)', documentation: 'Read OP2 results file', range, }, { label: 'op2.displacements', kind: monaco.languages.CompletionItemKind.Property, insertText: 'op2.displacements[subcase_id]', documentation: 'Access displacement results for a subcase', range, }, { label: 'op2.eigenvectors', kind: monaco.languages.CompletionItemKind.Property, insertText: 'op2.eigenvectors[subcase_id]', documentation: 'Access eigenvector results for modal analysis', range, }, { label: 'np.max', kind: monaco.languages.CompletionItemKind.Function, insertText: 'np.max(${1:array})', insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, documentation: 'Get maximum value from array', range, }, { label: 'np.sqrt', kind: monaco.languages.CompletionItemKind.Function, insertText: 'np.sqrt(${1:array})', insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, documentation: 'Square root of array elements', range, }, { label: 'extract_function', kind: monaco.languages.CompletionItemKind.Snippet, insertText: `def extract(op2_path: str, fem_path: str, params: dict, subcase_id: int = 1) -> dict: """Extract physics from FEA results.""" op2 = OP2() op2.read_op2(op2_path) # Your extraction logic here return { '\${1:output_name}': \${2:value}, }`, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, documentation: 'Insert a complete extract function template', range, }, ]; return { suggestions }; }, }); }; // Handle code change const handleCodeChange: OnChange = (value) => { const newCode = value || ''; setCode(newCode); setError(null); setRunResult(null); onChange?.(newCode); }; // Copy code to clipboard const handleCopy = useCallback(() => { navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 2000); }, [code]); // Request Claude generation const handleGenerate = useCallback(async () => { if (!onRequestGeneration || !generationPrompt.trim()) return; setIsGenerating(true); setError(null); try { const generatedCode = await onRequestGeneration(generationPrompt); setCode(generatedCode); onChange?.(generatedCode); setShowPromptInput(false); setGenerationPrompt(''); } catch (err) { setError(err instanceof Error ? err.message : 'Generation failed'); } finally { setIsGenerating(false); } }, [onRequestGeneration, generationPrompt, onChange]); // Run/validate code const handleRun = useCallback(async () => { if (!onRun) return; setIsRunning(true); setError(null); setRunResult(null); try { const result = await onRun(code); setRunResult(result); if (!result.success && result.error) { setError(result.error); } } catch (err) { setError(err instanceof Error ? err.message : 'Validation failed'); } finally { setIsRunning(false); } }, [code, onRun]); return (
{/* Header */} {showHeader && (
{extractorName} .py
{/* Claude Generate Button */} {onRequestGeneration && ( )} {/* Copy Button */} {/* Run Button */} {onRun && ( )} {/* Close Button */} {onClose && ( )}
)} {/* Claude Prompt Input */} {showPromptInput && (
Generate with Claude