feat(canvas): Add file browser, introspection, and improve node flow
Phase 1-7 of Canvas V4 Ralph Loop implementation: Backend: - Add /api/files routes for browsing model files - Add /api/nx routes for NX model introspection - Add NXIntrospector service to discover expressions and extractors - Add health check with database status Frontend: - Add FileBrowser component for selecting .sim/.prt/.fem files - Add IntrospectionPanel to discover expressions and extractors - Update NodeConfigPanel with browse and introspect buttons - Update schema with NODE_HANDLES for proper flow direction - Update validation for correct DesignVar -> Model -> Solver flow - Update useCanvasStore.addNode() to accept custom data Flow correction: Design Variables now connect TO Model (as source), not FROM Model. This matches the actual data flow in optimization. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
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,
|
||||
@@ -24,6 +28,9 @@ 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;
|
||||
@@ -63,15 +70,24 @@ export function NodeConfigPanel({ nodeId }: NodeConfigPanelProps) {
|
||||
<>
|
||||
<div>
|
||||
<label className={labelClass}>
|
||||
File Path
|
||||
Model File
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={(data as ModelNodeData).filePath || ''}
|
||||
onChange={(e) => handleChange('filePath', e.target.value)}
|
||||
placeholder="path/to/model.prt"
|
||||
className={`${inputClass} font-mono text-sm`}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={(data as ModelNodeData).filePath || ''}
|
||||
onChange={(e) => handleChange('filePath', e.target.value)}
|
||||
placeholder="path/to/model.sim"
|
||||
className={`${inputClass} font-mono text-sm flex-1`}
|
||||
/>
|
||||
<button
|
||||
onClick={() => setShowFileBrowser(true)}
|
||||
className="px-3 py-2 bg-dark-700 hover:bg-dark-600 rounded-lg text-dark-300 hover:text-white transition-colors"
|
||||
title="Browse files"
|
||||
>
|
||||
<FolderSearch size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>
|
||||
@@ -86,8 +102,21 @@ export function NodeConfigPanel({ nodeId }: NodeConfigPanelProps) {
|
||||
<option value="prt">Part (.prt)</option>
|
||||
<option value="fem">FEM (.fem)</option>
|
||||
<option value="sim">Simulation (.sim)</option>
|
||||
<option value="afem">Assembled FEM (.afem)</option>
|
||||
</select>
|
||||
</div>
|
||||
{/* Introspect Button */}
|
||||
{(data as ModelNodeData).filePath && (
|
||||
<button
|
||||
onClick={() => setShowIntrospection(true)}
|
||||
className="w-full flex items-center justify-center gap-2 px-3 py-2.5 bg-primary-500/20
|
||||
hover:bg-primary-500/30 border border-primary-500/30 rounded-lg
|
||||
text-primary-400 text-sm font-medium transition-colors"
|
||||
>
|
||||
<Microscope size={16} />
|
||||
Introspect Model
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -385,6 +414,27 @@ export function NodeConfigPanel({ nodeId }: NodeConfigPanelProps) {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* File Browser Modal */}
|
||||
<FileBrowser
|
||||
isOpen={showFileBrowser}
|
||||
onClose={() => setShowFileBrowser(false)}
|
||||
onSelect={(path, fileType) => {
|
||||
handleChange('filePath', path);
|
||||
handleChange('fileType', fileType.replace('.', ''));
|
||||
}}
|
||||
fileTypes={['.sim', '.prt', '.fem', '.afem']}
|
||||
/>
|
||||
|
||||
{/* Introspection Panel */}
|
||||
{showIntrospection && (data as ModelNodeData).filePath && (
|
||||
<div className="fixed top-20 right-96 z-40">
|
||||
<IntrospectionPanel
|
||||
filePath={(data as ModelNodeData).filePath!}
|
||||
onClose={() => setShowIntrospection(false)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user