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:
@@ -89,14 +89,63 @@ export interface CanvasEdge {
|
||||
targetHandle?: string;
|
||||
}
|
||||
|
||||
// Valid connections
|
||||
// Valid connections - defines what a node can connect TO (as source)
|
||||
// Flow: DesignVar -> Model -> Solver -> Extractor -> Objective/Constraint -> Algorithm -> Surrogate
|
||||
export const VALID_CONNECTIONS: Record<NodeType, NodeType[]> = {
|
||||
model: ['solver', 'designVar'],
|
||||
solver: ['extractor'],
|
||||
designVar: ['model'],
|
||||
extractor: ['objective', 'constraint'],
|
||||
objective: ['algorithm'],
|
||||
constraint: ['algorithm'],
|
||||
algorithm: ['surrogate'],
|
||||
surrogate: [],
|
||||
model: ['solver'], // Model outputs to Solver
|
||||
solver: ['extractor'], // Solver outputs to Extractor
|
||||
designVar: ['model'], // DesignVar outputs to Model (expressions feed into model)
|
||||
extractor: ['objective', 'constraint'], // Extractor outputs to Objective/Constraint
|
||||
objective: ['algorithm'], // Objective outputs to Algorithm
|
||||
constraint: ['algorithm'], // Constraint outputs to Algorithm
|
||||
algorithm: ['surrogate'], // Algorithm outputs to Surrogate
|
||||
surrogate: [], // Surrogate is terminal
|
||||
};
|
||||
|
||||
// Node handle configuration for proper flow direction
|
||||
export interface HandleConfig {
|
||||
id: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface NodeHandleConfig {
|
||||
inputs: HandleConfig[];
|
||||
outputs: HandleConfig[];
|
||||
}
|
||||
|
||||
// Define handles for each node type
|
||||
// Flow: DesignVar(s) -> Model -> Solver -> Extractor(s) -> Objective(s) -> Algorithm
|
||||
export const NODE_HANDLES: Record<NodeType, NodeHandleConfig> = {
|
||||
model: {
|
||||
inputs: [{ id: 'params', label: 'Parameters' }], // Receives from DesignVars
|
||||
outputs: [{ id: 'sim', label: 'Simulation' }], // Sends to Solver
|
||||
},
|
||||
solver: {
|
||||
inputs: [{ id: 'model', label: 'Model' }], // Receives from Model
|
||||
outputs: [{ id: 'results', label: 'Results' }], // Sends to Extractors
|
||||
},
|
||||
designVar: {
|
||||
inputs: [], // No inputs - this is a source
|
||||
outputs: [{ id: 'value', label: 'Value' }], // Sends to Model
|
||||
},
|
||||
extractor: {
|
||||
inputs: [{ id: 'results', label: 'Results' }], // Receives from Solver
|
||||
outputs: [{ id: 'value', label: 'Value' }], // Sends to Objective/Constraint
|
||||
},
|
||||
objective: {
|
||||
inputs: [{ id: 'value', label: 'Value' }], // Receives from Extractor
|
||||
outputs: [{ id: 'objective', label: 'Objective' }], // Sends to Algorithm
|
||||
},
|
||||
constraint: {
|
||||
inputs: [{ id: 'value', label: 'Value' }], // Receives from Extractor
|
||||
outputs: [{ id: 'constraint', label: 'Constraint' }], // Sends to Algorithm
|
||||
},
|
||||
algorithm: {
|
||||
inputs: [{ id: 'objectives', label: 'Objectives' }], // Receives from Objectives/Constraints
|
||||
outputs: [{ id: 'algo', label: 'Algorithm' }], // Sends to Surrogate
|
||||
},
|
||||
surrogate: {
|
||||
inputs: [{ id: 'algo', label: 'Algorithm' }], // Receives from Algorithm
|
||||
outputs: [], // No outputs - this is a sink
|
||||
},
|
||||
};
|
||||
|
||||
@@ -74,13 +74,68 @@ export function validateGraph(
|
||||
}
|
||||
}
|
||||
|
||||
// Check connectivity
|
||||
// Check connectivity - verify proper flow direction
|
||||
// Design Variables should connect TO Model (as source -> target)
|
||||
const modelNodes = nodes.filter(n => n.data.type === 'model');
|
||||
for (const dvar of designVars) {
|
||||
const connectsToModel = edges.some(e =>
|
||||
e.source === dvar.id && modelNodes.some(m => m.id === e.target)
|
||||
);
|
||||
if (!connectsToModel) {
|
||||
warnings.push(`${dvar.data.label} is not connected to a Model`);
|
||||
}
|
||||
}
|
||||
|
||||
// Model should connect TO Solver
|
||||
const solverNodes = nodes.filter(n => n.data.type === 'solver');
|
||||
for (const model of modelNodes) {
|
||||
const connectsToSolver = edges.some(e =>
|
||||
e.source === model.id && solverNodes.some(s => s.id === e.target)
|
||||
);
|
||||
if (!connectsToSolver) {
|
||||
errors.push(`${model.data.label} is not connected to a Solver`);
|
||||
}
|
||||
}
|
||||
|
||||
// Solver should connect TO Extractors
|
||||
for (const solver of solverNodes) {
|
||||
const connectsToExtractor = edges.some(e =>
|
||||
e.source === solver.id && extractors.some(ex => ex.id === e.target)
|
||||
);
|
||||
if (!connectsToExtractor) {
|
||||
warnings.push(`${solver.data.label} is not connected to any Extractor`);
|
||||
}
|
||||
}
|
||||
|
||||
// Extractors should connect TO Objectives or Constraints
|
||||
const objectives = nodes.filter(n => n.data.type === 'objective');
|
||||
const constraints = nodes.filter(n => n.data.type === 'constraint');
|
||||
for (const extractor of extractors) {
|
||||
const connectsToObjective = edges.some(e =>
|
||||
e.source === extractor.id &&
|
||||
(objectives.some(obj => obj.id === e.target) || constraints.some(c => c.id === e.target))
|
||||
);
|
||||
if (!connectsToObjective) {
|
||||
warnings.push(`${extractor.data.label} is not connected to any Objective or Constraint`);
|
||||
}
|
||||
}
|
||||
|
||||
// Objectives should connect TO Algorithm
|
||||
const algorithmNodes = nodes.filter(n => n.data.type === 'algorithm');
|
||||
for (const obj of objectives) {
|
||||
const hasIncoming = edges.some(e => e.target === obj.id);
|
||||
const hasIncoming = edges.some(e =>
|
||||
extractors.some(ex => ex.id === e.source) && e.target === obj.id
|
||||
);
|
||||
if (!hasIncoming) {
|
||||
errors.push(`${obj.data.label} has no connected extractor`);
|
||||
}
|
||||
|
||||
const connectsToAlgorithm = edges.some(e =>
|
||||
e.source === obj.id && algorithmNodes.some(a => a.id === e.target)
|
||||
);
|
||||
if (!connectsToAlgorithm) {
|
||||
warnings.push(`${obj.data.label} is not connected to an Algorithm`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user