## Documentation Updates - DASHBOARD.md: Updated to V3.0 with Canvas V3 features, file browser, introspection - DASHBOARD_IMPLEMENTATION_STATUS.md: Marked Canvas V3 features as COMPLETE - CANVAS.md: New comprehensive guide for Canvas Builder V3 with all features - CLAUDE.md: Added dashboard quick reference and Canvas V3 features ## Canvas V3 Features Documented - File Browser: Browse studies directory for model files - Model Introspection: Auto-discover expressions, solver type, dependencies - One-Click Add: Add expressions as design variables instantly - Claude Bug Fixes: WebSocket reconnection, SQL errors resolved - Health Check: /api/health endpoint for monitoring ## Backend Services - NX introspection service with expression discovery - File browser API with type filtering - Claude session management improvements - Context builder enhancements ## Frontend Components - FileBrowser: Modal for file selection with search - IntrospectionPanel: View discovered model information - ExpressionSelector: Dropdown for design variable configuration - Improved chat hooks with reconnection logic ## Plan Documents - Added RALPH_LOOP_CANVAS_V2/V3 implementation records - Added ATOMIZER_DASHBOARD_V2_MASTER_PLAN - Added investigation and sync documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2816 lines
98 KiB
Markdown
2816 lines
98 KiB
Markdown
# Atomizer Dashboard V2 - Master Implementation Plan
|
|
|
|
**Version:** 1.1
|
|
**Created:** January 8, 2026
|
|
**Updated:** January 14, 2026
|
|
**Purpose:** Complete technical specification for Ralph Loop autonomous development
|
|
**Scope:** MCP Chat + Canvas + React Flow + Full Integration
|
|
|
|
---
|
|
|
|
## IMPLEMENTATION STATUS: COMPLETE
|
|
|
|
| Phase | Description | Status |
|
|
|-------|-------------|--------|
|
|
| **Phase 0** | MCP Chat Foundation | COMPLETE |
|
|
| **Phase 1** | Canvas with React Flow | COMPLETE |
|
|
| **Phase 2** | LLM Intelligence Layer | COMPLETE |
|
|
| **Phase 3** | Bidirectional Sync | COMPLETE |
|
|
| **Phase 4** | Templates & Polish | COMPLETE |
|
|
| Phase 5 | Tauri Desktop | FUTURE |
|
|
|
|
**See:** [RALPH_LOOP_MASTER_PROMPT.md](RALPH_LOOP_MASTER_PROMPT.md) for detailed file inventory and testing checklist.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Executive Summary](#1-executive-summary)
|
|
2. [Architecture Philosophy](#2-architecture-philosophy)
|
|
3. [Current State Analysis](#3-current-state-analysis)
|
|
4. [Target Architecture](#4-target-architecture)
|
|
5. [Phase 0: Foundation - MCP Chat](#5-phase-0-foundation---mcp-chat)
|
|
6. [Phase 1: Canvas - React Flow Integration](#6-phase-1-canvas---react-flow-integration)
|
|
7. [Phase 2: LLM Intelligence Layer](#7-phase-2-llm-intelligence-layer)
|
|
8. [Phase 3: Bidirectional Sync](#8-phase-3-bidirectional-sync)
|
|
9. [Phase 4: Polish & Templates](#9-phase-4-polish--templates)
|
|
10. [Phase 5: Tauri Desktop (Future)](#10-phase-5-tauri-desktop-future)
|
|
11. [Integration with Existing Systems](#11-integration-with-existing-systems)
|
|
12. [Data Flow Specifications](#12-data-flow-specifications)
|
|
13. [Component Specifications](#13-component-specifications)
|
|
14. [API Specifications](#14-api-specifications)
|
|
15. [Security Model](#15-security-model)
|
|
16. [Testing Strategy](#16-testing-strategy)
|
|
17. [Deployment Strategy](#17-deployment-strategy)
|
|
18. [Risk Analysis](#18-risk-analysis)
|
|
19. [Task Breakdown](#19-task-breakdown)
|
|
20. [Appendices](#20-appendices)
|
|
|
|
---
|
|
|
|
## 1. Executive Summary
|
|
|
|
### 1.1 Vision
|
|
|
|
Transform the Atomizer Dashboard from a monitoring tool into an **intelligent optimization workbench** where engineers interact through:
|
|
|
|
1. **Conversational Interface (Chat)** - Natural language interaction with full Atomizer capabilities
|
|
2. **Visual Interface (Canvas)** - Node-based workflow builder using React Flow
|
|
3. **Unified Intelligence Layer** - Claude interprets both inputs using protocols, LAC, and skills
|
|
|
|
### 1.2 Key Principles
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────────┐
|
|
│ CORE PRINCIPLES │
|
|
├─────────────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ 1. CLAUDE IS THE BRAIN │
|
|
│ Canvas and Chat are input modalities │
|
|
│ Claude interprets, validates, executes │
|
|
│ All intelligence flows through the same LLM layer │
|
|
│ │
|
|
│ 2. PROTOCOLS ARE THE OPERATING SYSTEM │
|
|
│ OP_01, OP_02, SYS_12, etc. remain the source of truth │
|
|
│ Canvas doesn't hardcode - it expresses intent │
|
|
│ Claude follows protocols to execute │
|
|
│ │
|
|
│ 3. ADDITIVE, NOT DESTRUCTIVE │
|
|
│ No existing code is broken │
|
|
│ New capabilities layer on top │
|
|
│ Existing CLI/terminal workflows still work │
|
|
│ │
|
|
│ 4. TWO MODES, ONE EXPERIENCE │
|
|
│ User Mode: Safe, product-like │
|
|
│ Power Mode: Full Claude Code capabilities │
|
|
│ Same interface, different permissions │
|
|
│ │
|
|
│ 5. CONTEXT IS KING │
|
|
│ Sessions persist across navigation │
|
|
│ Study context automatically loaded │
|
|
│ LAC insights inform every decision │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 1.3 Success Criteria
|
|
|
|
| Metric | Target |
|
|
|--------|--------|
|
|
| Study creation via chat | < 2 minutes from "create study" to running |
|
|
| Canvas to execution | < 30 seconds from graph complete to optimization start |
|
|
| Session persistence | 100% conversation recovery after refresh |
|
|
| LAC integration | Every study decision informed by historical data |
|
|
| Zero breaking changes | All existing functionality preserved |
|
|
|
|
---
|
|
|
|
## 2. Architecture Philosophy
|
|
|
|
### 2.1 The Intelligence Stack
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────────┐
|
|
│ │
|
|
│ USER INTERACTION LAYER │
|
|
│ ┌─────────────────────────────────┐ ┌─────────────────────────────────────┐ │
|
|
│ │ CHAT PANEL │ │ CANVAS PANEL │ │
|
|
│ │ ┌───────────────────────────┐ │ │ ┌─────────────────────────────────┐│ │
|
|
│ │ │ Natural Language Input │ │ │ │ Visual Node Graph (React Flow) ││ │
|
|
│ │ │ "Create mass optimization"│ │ │ │ [Model]→[Solver]→[Extractor] ││ │
|
|
│ │ └───────────────────────────┘ │ │ └─────────────────────────────────┘│ │
|
|
│ │ ┌───────────────────────────┐ │ │ ┌─────────────────────────────────┐│ │
|
|
│ │ │ Tool Call Visualization │ │ │ │ Node Configuration Panels ││ │
|
|
│ │ │ [get_trial_data ✓] │ │ │ │ Drag-drop Palette ││ │
|
|
│ │ └───────────────────────────┘ │ │ └─────────────────────────────────┘│ │
|
|
│ │ ┌───────────────────────────┐ │ │ ┌─────────────────────────────────┐│ │
|
|
│ │ │ Streaming Response │ │ │ │ Action Buttons ││ │
|
|
│ │ │ Markdown + Code │ │ │ │ [Validate] [Create] [Run] ││ │
|
|
│ │ └───────────────────────────┘ │ │ └─────────────────────────────────┘│ │
|
|
│ └─────────────────────────────────┘ └─────────────────────────────────────┘ │
|
|
│ │ │ │
|
|
│ │ Both serialize to │ │
|
|
│ └──────────────┬─────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ INTENT LAYER │ │
|
|
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
|
|
│ │ │ Unified Intent Schema │ │ │
|
|
│ │ │ { │ │ │
|
|
│ │ │ "source": "chat" | "canvas", │ │ │
|
|
│ │ │ "action": "create" | "run" | "analyze" | "modify", │ │ │
|
|
│ │ │ "intent": { ... structured description ... } │ │ │
|
|
│ │ │ } │ │ │
|
|
│ │ └───────────────────────────────────────────────────────────────────┘ │ │
|
|
│ └─────────────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ INTELLIGENCE LAYER (Claude) │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ PROTOCOLS │ │ LAC │ │ SKILLS │ │ CONTEXT │ │ │
|
|
│ │ │ OP_01-08 │ │ Historical │ │ Interview │ │ Study State │ │ │
|
|
│ │ │ SYS_10-18 │ │ Insights │ │ Creation │ │ Session │ │ │
|
|
│ │ │ EXT_01-04 │ │ Patterns │ │ Modules │ │ History │ │ │
|
|
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
│ │ │ │
|
|
│ │ Claude: "I understand the intent. Based on protocols and LAC: │ │
|
|
│ │ - This matches OP_01 (Create Study) │ │
|
|
│ │ - LAC says TPE works for this geometry type │ │
|
|
│ │ - I'll use E4 for mass, E3 for stress │ │
|
|
│ │ - Executing..." │ │
|
|
│ │ │ │
|
|
│ └─────────────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ EXECUTION LAYER │ │
|
|
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
|
|
│ │ │ Atomizer MCP Server (Tools) │ │ │
|
|
│ │ │ ├─ create_study() → StudyCreator │ │ │
|
|
│ │ │ ├─ run_optimization() → OptimizationRunner │ │ │
|
|
│ │ │ ├─ get_trial_data() → Database queries │ │ │
|
|
│ │ │ ├─ generate_report() → ReportGenerator │ │ │
|
|
│ │ │ └─ ... 15+ tools │ │ │
|
|
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
|
|
│ └─────────────────────────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ ENGINE LAYER (Python) │ │
|
|
│ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ optimization_ │ │ extractors/ │ │ nx/ │ │ study/ │ │ │
|
|
│ │ │ engine/core/ │ │ E1-E24 │ │ solver.py │ │ creator.py │ │ │
|
|
│ │ │ runner.py │ │ extract_*.py │ │ updater.py │ │ wizard.py │ │ │
|
|
│ │ └───────────────┘ └───────────────┘ └───────────────┘ └─────────────┘ │ │
|
|
│ └─────────────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 2.2 Why Not Hardcode?
|
|
|
|
Traditional approach would be:
|
|
```
|
|
Canvas Graph → Template Engine → Python Code → Execute
|
|
```
|
|
|
|
This is **brittle** because:
|
|
- Adding new extractor requires updating template
|
|
- Protocol changes require code changes
|
|
- No intelligence in the translation
|
|
- Can't handle edge cases
|
|
|
|
Our approach:
|
|
```
|
|
Canvas Graph → Intent JSON → Claude (+ Protocols + LAC) → Intelligent Execution
|
|
```
|
|
|
|
This is **robust** because:
|
|
- New extractor? Just add to SYS_12, Claude adapts
|
|
- Protocol change? Update docs, Claude follows
|
|
- Edge case? Claude uses judgment
|
|
- Historical insight? LAC informs decisions
|
|
|
|
### 2.3 The Intent Schema
|
|
|
|
All user interactions serialize to a common intent format:
|
|
|
|
```typescript
|
|
// Intent Schema - The Universal Language
|
|
|
|
interface AtomizerIntent {
|
|
// Metadata
|
|
_meta: {
|
|
source: "chat" | "canvas";
|
|
timestamp: string;
|
|
session_id: string;
|
|
mode: "user" | "power";
|
|
};
|
|
|
|
// What the user wants to do
|
|
action:
|
|
| "validate" // Check if config makes sense
|
|
| "create" // Create study without running
|
|
| "create_and_run" // Create and immediately start
|
|
| "run" // Run existing study
|
|
| "stop" // Stop running optimization
|
|
| "analyze" // Analyze results
|
|
| "report" // Generate report
|
|
| "modify" // Modify existing study
|
|
| "explain"; // Explain something
|
|
|
|
// Study identification (for existing studies)
|
|
study_ref?: {
|
|
name?: string;
|
|
id?: string;
|
|
path?: string;
|
|
};
|
|
|
|
// For create/modify actions - the optimization definition
|
|
optimization?: {
|
|
// Model
|
|
model: {
|
|
path: string;
|
|
type: "nx_part" | "nx_assembly";
|
|
expressions?: string[]; // Known NX expressions
|
|
};
|
|
|
|
// Solver
|
|
solver: {
|
|
type: "nastran";
|
|
solution: 101 | 103 | 105 | 111 | 112;
|
|
deck_options?: Record<string, any>;
|
|
};
|
|
|
|
// Design Variables
|
|
design_variables: Array<{
|
|
name: string;
|
|
nx_expression?: string;
|
|
type: "continuous" | "integer" | "categorical";
|
|
lower?: number;
|
|
upper?: number;
|
|
values?: (string | number)[]; // For categorical
|
|
}>;
|
|
|
|
// Objectives - NOTE: physics name, NOT extractor ID
|
|
// Claude maps physics → extractor using SYS_12
|
|
objectives: Array<{
|
|
name: string;
|
|
physics: string; // "mass", "stress", "frequency", "zernike_wfe"
|
|
direction: "minimize" | "maximize";
|
|
weight?: number; // For weighted sum
|
|
target?: number; // For target-seeking
|
|
}>;
|
|
|
|
// Constraints - same pattern
|
|
constraints: Array<{
|
|
name: string;
|
|
physics: string;
|
|
type: "upper" | "lower" | "equality" | "range";
|
|
value?: number;
|
|
lower?: number;
|
|
upper?: number;
|
|
unit?: string;
|
|
}>;
|
|
|
|
// Algorithm preference (Claude may override with LAC insights)
|
|
algorithm?: {
|
|
name?: string; // "TPE" | "CMA-ES" | "NSGA-II" | "IMSO" | "auto"
|
|
trials?: number;
|
|
timeout_hours?: number;
|
|
// If "auto" or omitted, Claude selects based on problem + LAC
|
|
};
|
|
|
|
// Surrogate preference
|
|
surrogate?: {
|
|
type?: "mlp" | "gnn" | "ensemble" | "none" | "auto";
|
|
// If "auto", Claude decides based on trial count + problem type
|
|
};
|
|
};
|
|
|
|
// For analyze/report actions
|
|
analysis?: {
|
|
type: "convergence" | "pareto" | "sensitivity" | "comparison";
|
|
trials?: number[]; // Specific trials to analyze
|
|
objectives?: string[]; // Which objectives to focus on
|
|
};
|
|
|
|
// For explain actions
|
|
explanation?: {
|
|
topic: string;
|
|
context?: string;
|
|
};
|
|
|
|
// Additional context from canvas
|
|
canvas_state?: {
|
|
nodes: CanvasNode[];
|
|
edges: CanvasEdge[];
|
|
layout: "auto" | "manual";
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Current State Analysis
|
|
|
|
### 3.1 Existing Dashboard Structure
|
|
|
|
```
|
|
atomizer-dashboard/
|
|
├── frontend/ # React + TypeScript + Vite
|
|
│ ├── src/
|
|
│ │ ├── components/
|
|
│ │ │ ├── charts/ # Nivo charts (parallel coords, etc.)
|
|
│ │ │ ├── chat/ # NEW: Chat components (in progress)
|
|
│ │ │ │ ├── ChatPane.tsx
|
|
│ │ │ │ ├── ChatMessage.tsx
|
|
│ │ │ │ ├── ChatInput.tsx
|
|
│ │ │ │ └── ThinkingIndicator.tsx
|
|
│ │ │ ├── common/ # Shared UI components
|
|
│ │ │ ├── layout/ # MainLayout, Sidebar
|
|
│ │ │ └── study/ # Study-specific views
|
|
│ │ ├── context/
|
|
│ │ │ └── StudyContext.tsx # Global study state
|
|
│ │ ├── hooks/
|
|
│ │ │ └── useChat.ts # NEW: Chat hook (in progress)
|
|
│ │ ├── pages/
|
|
│ │ │ ├── Dashboard.tsx
|
|
│ │ │ └── StudyView.tsx
|
|
│ │ └── App.tsx
|
|
│ └── package.json
|
|
│
|
|
├── backend/ # FastAPI
|
|
│ ├── api/
|
|
│ │ ├── routes/
|
|
│ │ │ ├── studies.py
|
|
│ │ │ ├── trials.py
|
|
│ │ │ ├── optimization.py
|
|
│ │ │ └── claude.py # NEW: Claude routes (in progress)
|
|
│ │ └── services/
|
|
│ │ └── claude_cli_agent.py # NEW: CLI agent (basic)
|
|
│ ├── main.py
|
|
│ └── sessions.db # SQLite for sessions
|
|
│
|
|
└── mcp-server/ # EXISTING: siemens-docs MCP
|
|
└── ...
|
|
```
|
|
|
|
### 3.2 What's Working
|
|
|
|
| Component | Status | Notes |
|
|
|-----------|--------|-------|
|
|
| Study listing | ✅ Working | Lists all studies from filesystem |
|
|
| Study viewing | ✅ Working | Shows trials, charts, config |
|
|
| Parallel coordinates | ✅ Working | Nivo-based visualization |
|
|
| Convergence chart | ✅ Working | Best-so-far tracking |
|
|
| Trial table | ✅ Working | Sortable, filterable |
|
|
| Backend API | ✅ Working | FastAPI, SQLite integration |
|
|
| Chat UI shell | 🔄 In Progress | Components created, not connected |
|
|
| MCP siemens-docs | ✅ Working | NX Open documentation server |
|
|
|
|
### 3.3 What's Missing
|
|
|
|
| Component | Status | Priority |
|
|
|-----------|--------|----------|
|
|
| Claude session management | ❌ Not Started | P0 |
|
|
| MCP Atomizer tools server | ❌ Not Started | P0 |
|
|
| Canvas (React Flow) | ❌ Not Started | P1 |
|
|
| Intent serialization | ❌ Not Started | P1 |
|
|
| Bidirectional sync | ❌ Not Started | P2 |
|
|
| Templates library | ❌ Not Started | P3 |
|
|
|
|
### 3.4 Dependencies
|
|
|
|
```
|
|
Frontend:
|
|
├── react: 18.x # ✅ Installed
|
|
├── typescript: 5.x # ✅ Installed
|
|
├── vite: 5.x # ✅ Installed
|
|
├── reactflow: 11.x # ❌ TO INSTALL
|
|
├── @tanstack/react-query: 5.x # ✅ Installed
|
|
├── lucide-react: 0.x # ✅ Installed
|
|
├── react-markdown: 9.x # ✅ Installed
|
|
└── zustand: 4.x # ❌ TO INSTALL (for canvas state)
|
|
|
|
Backend:
|
|
├── fastapi: 0.x # ✅ Installed
|
|
├── uvicorn: 0.x # ✅ Installed
|
|
├── sqlite3: built-in # ✅ Available
|
|
├── asyncio: built-in # ✅ Available
|
|
└── websockets: 0.x # ✅ Installed
|
|
|
|
MCP Server (new):
|
|
├── @modelcontextprotocol/sdk # ❌ TO INSTALL
|
|
├── typescript: 5.x # ✅ Installed (workspace)
|
|
├── better-sqlite3: 11.x # ❌ TO INSTALL
|
|
└── zod: 3.x # ❌ TO INSTALL
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Target Architecture
|
|
|
|
### 4.1 Directory Structure (Final State)
|
|
|
|
```
|
|
atomizer-dashboard/
|
|
├── frontend/
|
|
│ ├── src/
|
|
│ │ ├── components/
|
|
│ │ │ ├── charts/ # UNCHANGED
|
|
│ │ │ ├── chat/ # ENHANCED
|
|
│ │ │ │ ├── ChatPane.tsx # + Mode toggle, tool viz
|
|
│ │ │ │ ├── ChatMessage.tsx # + Tool call rendering
|
|
│ │ │ │ ├── ChatInput.tsx # UNCHANGED
|
|
│ │ │ │ ├── ThinkingIndicator.tsx # UNCHANGED
|
|
│ │ │ │ ├── ModeToggle.tsx # NEW
|
|
│ │ │ │ ├── ToolCallCard.tsx # NEW
|
|
│ │ │ │ └── index.ts
|
|
│ │ │ ├── canvas/ # NEW
|
|
│ │ │ │ ├── AtomizerCanvas.tsx # Main canvas component
|
|
│ │ │ │ ├── CanvasProvider.tsx # Zustand state
|
|
│ │ │ │ ├── nodes/ # Custom node types
|
|
│ │ │ │ │ ├── BaseNode.tsx
|
|
│ │ │ │ │ ├── ModelNode.tsx
|
|
│ │ │ │ │ ├── SolverNode.tsx
|
|
│ │ │ │ │ ├── DesignVarNode.tsx
|
|
│ │ │ │ │ ├── ExtractorNode.tsx
|
|
│ │ │ │ │ ├── ObjectiveNode.tsx
|
|
│ │ │ │ │ ├── ConstraintNode.tsx
|
|
│ │ │ │ │ ├── AlgorithmNode.tsx
|
|
│ │ │ │ │ ├── SurrogateNode.tsx
|
|
│ │ │ │ │ └── index.ts
|
|
│ │ │ │ ├── edges/
|
|
│ │ │ │ │ ├── DataFlowEdge.tsx # Color-coded by type
|
|
│ │ │ │ │ └── index.ts
|
|
│ │ │ │ ├── palette/
|
|
│ │ │ │ │ ├── NodePalette.tsx # Drag-drop source
|
|
│ │ │ │ │ ├── NodeCategory.tsx
|
|
│ │ │ │ │ └── index.ts
|
|
│ │ │ │ ├── panels/
|
|
│ │ │ │ │ ├── NodeConfigPanel.tsx
|
|
│ │ │ │ │ ├── ValidationPanel.tsx
|
|
│ │ │ │ │ ├── CodePreviewPanel.tsx
|
|
│ │ │ │ │ └── index.ts
|
|
│ │ │ │ └── index.ts
|
|
│ │ │ ├── common/ # UNCHANGED
|
|
│ │ │ ├── layout/ # MINOR UPDATES
|
|
│ │ │ │ ├── MainLayout.tsx # + Canvas integration
|
|
│ │ │ │ └── Sidebar.tsx # UNCHANGED
|
|
│ │ │ └── study/ # UNCHANGED
|
|
│ │ ├── context/
|
|
│ │ │ ├── StudyContext.tsx # UNCHANGED
|
|
│ │ │ └── CanvasContext.tsx # NEW
|
|
│ │ ├── hooks/
|
|
│ │ │ ├── useChat.ts # ENHANCED (WebSocket, sessions)
|
|
│ │ │ ├── useCanvas.ts # NEW
|
|
│ │ │ ├── useCanvasChat.ts # NEW (bridge)
|
|
│ │ │ └── useIntentBuilder.ts # NEW
|
|
│ │ ├── lib/
|
|
│ │ │ ├── canvas/
|
|
│ │ │ │ ├── schema.ts # Node/edge type definitions
|
|
│ │ │ │ ├── intent.ts # Graph → Intent serialization
|
|
│ │ │ │ ├── validation.ts # Graph validation rules
|
|
│ │ │ │ └── templates.ts # Preset graph templates
|
|
│ │ │ └── api/
|
|
│ │ │ └── claude.ts # Claude API client
|
|
│ │ ├── pages/
|
|
│ │ │ ├── Dashboard.tsx # UNCHANGED
|
|
│ │ │ ├── StudyView.tsx # + Canvas tab
|
|
│ │ │ └── CanvasView.tsx # NEW (standalone canvas)
|
|
│ │ └── App.tsx # + Canvas routes
|
|
│ ├── package.json
|
|
│ └── vite.config.ts
|
|
│
|
|
├── backend/
|
|
│ ├── api/
|
|
│ │ ├── routes/
|
|
│ │ │ ├── studies.py # UNCHANGED
|
|
│ │ │ ├── trials.py # UNCHANGED
|
|
│ │ │ ├── optimization.py # UNCHANGED
|
|
│ │ │ └── claude.py # REWRITTEN
|
|
│ │ └── services/
|
|
│ │ ├── session_manager.py # NEW
|
|
│ │ ├── conversation_store.py # NEW
|
|
│ │ ├── context_builder.py # NEW
|
|
│ │ └── claude_cli_agent.py # DEPRECATED
|
|
│ ├── main.py # + Lifespan handlers
|
|
│ └── sessions.db
|
|
│
|
|
└── mcp-server/
|
|
├── siemens-docs/ # EXISTING - UNCHANGED
|
|
│ └── ...
|
|
└── atomizer-tools/ # NEW
|
|
├── package.json
|
|
├── tsconfig.json
|
|
├── src/
|
|
│ ├── index.ts # MCP server entry
|
|
│ ├── tools/
|
|
│ │ ├── study.ts # Study management tools
|
|
│ │ ├── optimization.ts # Run/stop tools
|
|
│ │ ├── analysis.ts # Analysis tools
|
|
│ │ ├── reporting.ts # Report generation
|
|
│ │ ├── physics.ts # Physics explanation
|
|
│ │ ├── canvas.ts # Canvas intent processing
|
|
│ │ └── admin.ts # Power mode tools
|
|
│ └── utils/
|
|
│ ├── database.ts # SQLite helpers
|
|
│ ├── python.ts # Python subprocess
|
|
│ └── validation.ts # Input validation
|
|
└── README.md
|
|
```
|
|
|
|
### 4.2 Data Flow Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────────┐
|
|
│ DATA FLOW │
|
|
├─────────────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ FRONTEND (React) │ │
|
|
│ │ │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ ChatPane │◄───────▶│ useChat │◄───────▶│ WebSocket │ │ │
|
|
│ │ │ │ │ (hook) │ │ Client │ │ │
|
|
│ │ └─────────────┘ └─────────────┘ └──────┬──────┘ │ │
|
|
│ │ │ │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌──────▼──────┐ │ │
|
|
│ │ │ Canvas │◄───────▶│ useCanvas │◄───────▶│ Intent │ │ │
|
|
│ │ │ (ReactFlow)│ │ (hook) │ │ Builder │ │ │
|
|
│ │ └─────────────┘ └─────────────┘ └──────┬──────┘ │ │
|
|
│ │ │ │ │
|
|
│ │ ▼ │ │
|
|
│ │ ┌─────────────────┐ │ │
|
|
│ │ │ useCanvasChat │ │ │
|
|
│ │ │ (bridge hook) │ │ │
|
|
│ │ └────────┬────────┘ │ │
|
|
│ └───────────────────────────────────────────────────────┼──────────────────┘ │
|
|
│ │ │
|
|
│ │ WebSocket │
|
|
│ │ /api/claude/ws │
|
|
│ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ BACKEND (FastAPI) │ │
|
|
│ │ │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ WebSocket │────────▶│ Session │────────▶│ Context │ │ │
|
|
│ │ │ Handler │ │ Manager │ │ Builder │ │ │
|
|
│ │ └─────────────┘ └──────┬──────┘ └──────┬──────┘ │ │
|
|
│ │ │ │ │ │
|
|
│ │ │ spawn │ build │ │
|
|
│ │ ▼ ▼ │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ Claude │◄────────│ System │ │ │
|
|
│ │ │ Subprocess │ │ Prompt │ │ │
|
|
│ │ └──────┬──────┘ └─────────────┘ │ │
|
|
│ │ │ │ │
|
|
│ │ │ --mcp-config │ │
|
|
│ │ ▼ │ │
|
|
│ └─────────────────────────────────┼────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ │ MCP Protocol │
|
|
│ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ MCP SERVER (Node.js) │ │
|
|
│ │ │ │
|
|
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
|
|
│ │ │ TOOLS │ │ │
|
|
│ │ │ │ │ │
|
|
│ │ │ User Mode: Power Mode (additional): │ │ │
|
|
│ │ │ ├─ list_studies ├─ edit_file │ │ │
|
|
│ │ │ ├─ get_study_status ├─ create_extractor │ │ │
|
|
│ │ │ ├─ create_study ├─ run_shell_command │ │ │
|
|
│ │ │ ├─ run_optimization ├─ search_codebase │ │ │
|
|
│ │ │ ├─ stop_optimization └─ git_operations │ │ │
|
|
│ │ │ ├─ get_trial_data │ │ │
|
|
│ │ │ ├─ analyze_convergence │ │ │
|
|
│ │ │ ├─ compare_trials │ │ │
|
|
│ │ │ ├─ get_best_design │ │ │
|
|
│ │ │ ├─ generate_report │ │ │
|
|
│ │ │ ├─ explain_physics │ │ │
|
|
│ │ │ ├─ recommend_method │ │ │
|
|
│ │ │ ├─ query_extractors │ │ │
|
|
│ │ │ └─ interpret_canvas_intent │ │ │
|
|
│ │ │ │ │ │
|
|
│ │ └───────────────────────────────────────────────────────────────────┘ │ │
|
|
│ │ │ │ │
|
|
│ │ │ subprocess / import │ │
|
|
│ │ ▼ │ │
|
|
│ └─────────────────────────────────┼────────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ┌─────────────────────────────────┼────────────────────────────────────────┐ │
|
|
│ │ PYTHON ENGINE (optimization_engine/) │ │
|
|
│ │ │ │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ study/ │ │ extractors/ │ │ nx/ │ │ core/ │ │ │
|
|
│ │ │ creator.py │ │ E1-E24 │ │ solver.py │ │ runner.py │ │ │
|
|
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
│ │ │ │
|
|
│ └──────────────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Phase 0: Foundation - MCP Chat
|
|
|
|
### 5.1 Overview
|
|
|
|
Establish the core chat infrastructure with Claude subprocess management, MCP tools, and WebSocket streaming.
|
|
|
|
### 5.2 Components
|
|
|
|
#### 5.2.1 Atomizer MCP Server
|
|
|
|
**Location:** `mcp-server/atomizer-tools/`
|
|
|
|
**Purpose:** Expose Atomizer capabilities as MCP tools that Claude can call.
|
|
|
|
**Package.json:**
|
|
```json
|
|
{
|
|
"name": "atomizer-mcp-server",
|
|
"version": "1.0.0",
|
|
"type": "module",
|
|
"main": "dist/index.js",
|
|
"scripts": {
|
|
"build": "tsc",
|
|
"start": "node dist/index.js",
|
|
"dev": "tsx watch src/index.ts"
|
|
},
|
|
"dependencies": {
|
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
"better-sqlite3": "^11.0.0",
|
|
"zod": "^3.23.0"
|
|
},
|
|
"devDependencies": {
|
|
"@types/better-sqlite3": "^7.6.0",
|
|
"@types/node": "^20.0.0",
|
|
"tsx": "^4.0.0",
|
|
"typescript": "^5.0.0"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Index.ts Structure:**
|
|
```typescript
|
|
// mcp-server/atomizer-tools/src/index.ts
|
|
|
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
import {
|
|
CallToolRequestSchema,
|
|
ListToolsRequestSchema,
|
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
|
|
import { studyTools } from "./tools/study.js";
|
|
import { optimizationTools } from "./tools/optimization.js";
|
|
import { analysisTools } from "./tools/analysis.js";
|
|
import { reportingTools } from "./tools/reporting.js";
|
|
import { physicsTools } from "./tools/physics.js";
|
|
import { canvasTools } from "./tools/canvas.js";
|
|
import { adminTools } from "./tools/admin.js";
|
|
|
|
// Environment determines mode
|
|
const MODE = process.env.ATOMIZER_MODE || "user";
|
|
const ATOMIZER_ROOT = process.env.ATOMIZER_ROOT || "C:/Users/antoi/Atomizer";
|
|
|
|
// Tool registry based on mode
|
|
const userTools = [
|
|
...studyTools,
|
|
...optimizationTools,
|
|
...analysisTools,
|
|
...reportingTools,
|
|
...physicsTools,
|
|
...canvasTools,
|
|
];
|
|
|
|
const powerTools = [
|
|
...userTools,
|
|
...adminTools,
|
|
];
|
|
|
|
const tools = MODE === "power" ? powerTools : userTools;
|
|
|
|
// Create server
|
|
const server = new Server(
|
|
{
|
|
name: "atomizer-tools",
|
|
version: "1.0.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
tools: {},
|
|
},
|
|
}
|
|
);
|
|
|
|
// List tools handler
|
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
tools: tools.map(t => t.definition),
|
|
}));
|
|
|
|
// Call tool handler
|
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
const toolName = request.params.name;
|
|
const tool = tools.find(t => t.definition.name === toolName);
|
|
|
|
if (!tool) {
|
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
}
|
|
|
|
try {
|
|
return await tool.handler(request.params.arguments ?? {});
|
|
} catch (error: any) {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `Error executing ${toolName}: ${error.message}`,
|
|
}],
|
|
isError: true,
|
|
};
|
|
}
|
|
});
|
|
|
|
// Start server
|
|
async function main() {
|
|
const transport = new StdioServerTransport();
|
|
await server.connect(transport);
|
|
console.error("Atomizer MCP Server running...");
|
|
}
|
|
|
|
main().catch(console.error);
|
|
```
|
|
|
|
#### 5.2.2 Tool Implementations
|
|
|
|
**Study Tools (study.ts):**
|
|
```typescript
|
|
// mcp-server/atomizer-tools/src/tools/study.ts
|
|
|
|
import { z } from "zod";
|
|
import { execSync, spawn } from "child_process";
|
|
import Database from "better-sqlite3";
|
|
import * as fs from "fs/promises";
|
|
import * as path from "path";
|
|
|
|
const ATOMIZER_ROOT = process.env.ATOMIZER_ROOT || "C:/Users/antoi/Atomizer";
|
|
const STUDIES_DIR = path.join(ATOMIZER_ROOT, "studies");
|
|
const PYTHON_PATH = "C:/Users/antoi/anaconda3/envs/atomizer/python.exe";
|
|
|
|
interface Tool {
|
|
definition: {
|
|
name: string;
|
|
description: string;
|
|
inputSchema: Record<string, any>;
|
|
};
|
|
handler: (args: any) => Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }>;
|
|
}
|
|
|
|
export const studyTools: Tool[] = [
|
|
{
|
|
definition: {
|
|
name: "list_studies",
|
|
description: "List all available optimization studies with their status, trial counts, and best results",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
filter: {
|
|
type: "string",
|
|
description: "Optional filter pattern to match study names (e.g., 'bracket', 'mirror')",
|
|
},
|
|
include_archived: {
|
|
type: "boolean",
|
|
description: "Include archived studies (default: false)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
handler: async (args: { filter?: string; include_archived?: boolean }) => {
|
|
try {
|
|
const entries = await fs.readdir(STUDIES_DIR, { withFileTypes: true });
|
|
let studies = entries
|
|
.filter(e => e.isDirectory())
|
|
.filter(e => !e.name.startsWith("_") || args.include_archived)
|
|
.map(e => e.name);
|
|
|
|
if (args.filter) {
|
|
const pattern = args.filter.toLowerCase();
|
|
studies = studies.filter(s => s.toLowerCase().includes(pattern));
|
|
}
|
|
|
|
const results = await Promise.all(studies.map(async (name) => {
|
|
const studyDir = path.join(STUDIES_DIR, name);
|
|
const result: any = { name, status: "unknown", trials: 0 };
|
|
|
|
// Check for config
|
|
const configPaths = [
|
|
path.join(studyDir, "optimization_config.json"),
|
|
path.join(studyDir, "1_setup", "optimization_config.json"),
|
|
];
|
|
|
|
for (const configPath of configPaths) {
|
|
try {
|
|
const config = JSON.parse(await fs.readFile(configPath, "utf-8"));
|
|
result.objectives = config.objectives?.map((o: any) => o.name) || [];
|
|
result.design_vars = config.design_variables?.length || 0;
|
|
result.algorithm = config.optimization?.algorithm || "unknown";
|
|
break;
|
|
} catch {}
|
|
}
|
|
|
|
// Check for results
|
|
const dbPath = path.join(studyDir, "3_results", "study.db");
|
|
try {
|
|
const db = new Database(dbPath, { readonly: true });
|
|
const row = db.prepare("SELECT COUNT(*) as count FROM trials WHERE state = 'COMPLETE'").get() as any;
|
|
result.trials = row?.count || 0;
|
|
|
|
if (result.trials > 0) {
|
|
const best = db.prepare(`
|
|
SELECT MIN(tv.value) as best_value
|
|
FROM trial_values tv
|
|
JOIN trials t ON tv.trial_id = t.trial_id
|
|
WHERE t.state = 'COMPLETE'
|
|
`).get() as any;
|
|
result.best_objective = best?.best_value;
|
|
result.status = "has_results";
|
|
} else {
|
|
result.status = "configured";
|
|
}
|
|
|
|
db.close();
|
|
} catch {
|
|
result.status = result.objectives ? "configured" : "incomplete";
|
|
}
|
|
|
|
return result;
|
|
}));
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: JSON.stringify(results, null, 2),
|
|
}],
|
|
};
|
|
} catch (error: any) {
|
|
return {
|
|
content: [{ type: "text", text: `Error listing studies: ${error.message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
|
|
{
|
|
definition: {
|
|
name: "get_study_status",
|
|
description: "Get detailed status of a specific study including configuration, trial count, best results, and current state",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
study_name: {
|
|
type: "string",
|
|
description: "Name of the study to inspect",
|
|
},
|
|
},
|
|
required: ["study_name"],
|
|
},
|
|
},
|
|
handler: async (args: { study_name: string }) => {
|
|
const studyDir = path.join(STUDIES_DIR, args.study_name);
|
|
|
|
try {
|
|
await fs.access(studyDir);
|
|
} catch {
|
|
return {
|
|
content: [{ type: "text", text: `Study '${args.study_name}' not found` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
|
|
const result: any = {
|
|
name: args.study_name,
|
|
path: studyDir,
|
|
};
|
|
|
|
// Load config
|
|
const configPaths = [
|
|
path.join(studyDir, "optimization_config.json"),
|
|
path.join(studyDir, "1_setup", "optimization_config.json"),
|
|
];
|
|
|
|
for (const configPath of configPaths) {
|
|
try {
|
|
result.config = JSON.parse(await fs.readFile(configPath, "utf-8"));
|
|
break;
|
|
} catch {}
|
|
}
|
|
|
|
// Load results
|
|
const dbPath = path.join(studyDir, "3_results", "study.db");
|
|
try {
|
|
const db = new Database(dbPath, { readonly: true });
|
|
|
|
// Trial count
|
|
const countRow = db.prepare("SELECT COUNT(*) as total, SUM(CASE WHEN state = 'COMPLETE' THEN 1 ELSE 0 END) as complete FROM trials").get() as any;
|
|
result.trials = {
|
|
total: countRow?.total || 0,
|
|
complete: countRow?.complete || 0,
|
|
};
|
|
|
|
// Best result
|
|
if (result.trials.complete > 0) {
|
|
const best = db.prepare(`
|
|
SELECT t.number, tv.value
|
|
FROM trial_values tv
|
|
JOIN trials t ON tv.trial_id = t.trial_id
|
|
WHERE t.state = 'COMPLETE'
|
|
ORDER BY tv.value ASC
|
|
LIMIT 1
|
|
`).get() as any;
|
|
|
|
result.best = {
|
|
trial: best?.number,
|
|
objective_value: best?.value,
|
|
};
|
|
|
|
// Recent trials
|
|
const recent = db.prepare(`
|
|
SELECT t.number, t.state, tv.value
|
|
FROM trials t
|
|
LEFT JOIN trial_values tv ON t.trial_id = tv.trial_id
|
|
ORDER BY t.trial_id DESC
|
|
LIMIT 5
|
|
`).all();
|
|
|
|
result.recent_trials = recent;
|
|
}
|
|
|
|
db.close();
|
|
} catch (e) {
|
|
result.database_error = "Could not read results database";
|
|
}
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: JSON.stringify(result, null, 2),
|
|
}],
|
|
};
|
|
},
|
|
},
|
|
|
|
{
|
|
definition: {
|
|
name: "create_study",
|
|
description: "Create a new optimization study. This tool accepts a natural language description or structured intent and creates the complete study structure including config files and run script.",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
name: {
|
|
type: "string",
|
|
description: "Study name in snake_case (e.g., 'bracket_mass_v1')",
|
|
},
|
|
description: {
|
|
type: "string",
|
|
description: "Natural language description of what to optimize, or 'INTENT:' followed by JSON intent",
|
|
},
|
|
template: {
|
|
type: "string",
|
|
enum: ["bracket", "beam", "mirror", "custom"],
|
|
description: "Base template to use for study structure",
|
|
},
|
|
nx_model_path: {
|
|
type: "string",
|
|
description: "Path to the NX model file (.prt)",
|
|
},
|
|
},
|
|
required: ["name", "description"],
|
|
},
|
|
},
|
|
handler: async (args) => {
|
|
// This tool formats the request for Claude to process
|
|
// Claude will use protocols (OP_01) to actually create the study
|
|
|
|
const isIntent = args.description.startsWith("INTENT:");
|
|
|
|
if (isIntent) {
|
|
// Canvas-originated intent
|
|
const intentJson = args.description.substring(7);
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `Canvas intent received for study creation.
|
|
|
|
Study Name: ${args.name}
|
|
Template: ${args.template || "custom"}
|
|
NX Model: ${args.nx_model_path || "not specified"}
|
|
|
|
Intent:
|
|
${intentJson}
|
|
|
|
Please process this intent using OP_01 (Create Study) protocol:
|
|
1. Validate the intent structure
|
|
2. Map physics to extractors using SYS_12
|
|
3. Query LAC for similar optimizations
|
|
4. Create study directory structure
|
|
5. Generate optimization_config.json
|
|
6. Generate run_optimization.py
|
|
7. Report completion status`,
|
|
}],
|
|
};
|
|
} else {
|
|
// Natural language description
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `Study creation requested via natural language.
|
|
|
|
Study Name: ${args.name}
|
|
Template: ${args.template || "custom"}
|
|
NX Model: ${args.nx_model_path || "not specified"}
|
|
|
|
Description:
|
|
${args.description}
|
|
|
|
Please process this using Study Interview Mode or OP_01 protocol:
|
|
1. Parse the description to understand objectives, constraints, design variables
|
|
2. Ask clarifying questions if needed
|
|
3. Map to appropriate extractors (SYS_12)
|
|
4. Query LAC for insights
|
|
5. Create study structure
|
|
6. Generate configuration files`,
|
|
}],
|
|
};
|
|
}
|
|
},
|
|
},
|
|
];
|
|
```
|
|
|
|
**Optimization Tools (optimization.ts):**
|
|
```typescript
|
|
// mcp-server/atomizer-tools/src/tools/optimization.ts
|
|
|
|
import { execSync, spawn, ChildProcess } from "child_process";
|
|
import * as path from "path";
|
|
import * as fs from "fs/promises";
|
|
import Database from "better-sqlite3";
|
|
|
|
const ATOMIZER_ROOT = process.env.ATOMIZER_ROOT || "C:/Users/antoi/Atomizer";
|
|
const STUDIES_DIR = path.join(ATOMIZER_ROOT, "studies");
|
|
const PYTHON_PATH = "C:/Users/antoi/anaconda3/envs/atomizer/python.exe";
|
|
|
|
// Track running optimizations
|
|
const runningOptimizations: Map<string, ChildProcess> = new Map();
|
|
|
|
export const optimizationTools = [
|
|
{
|
|
definition: {
|
|
name: "run_optimization",
|
|
description: "Start an optimization run for a study. The optimization runs in the background.",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
study_name: {
|
|
type: "string",
|
|
description: "Name of the study to optimize",
|
|
},
|
|
max_trials: {
|
|
type: "number",
|
|
description: "Maximum number of trials (overrides config)",
|
|
},
|
|
method: {
|
|
type: "string",
|
|
enum: ["TPE", "CMA-ES", "NSGA-II", "IMSO", "RandomSearch"],
|
|
description: "Optimization method (overrides config)",
|
|
},
|
|
},
|
|
required: ["study_name"],
|
|
},
|
|
},
|
|
handler: async (args: { study_name: string; max_trials?: number; method?: string }) => {
|
|
const studyDir = path.join(STUDIES_DIR, args.study_name);
|
|
const runScript = path.join(studyDir, "run_optimization.py");
|
|
|
|
// Check if already running
|
|
if (runningOptimizations.has(args.study_name)) {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `Optimization for '${args.study_name}' is already running.`,
|
|
}],
|
|
};
|
|
}
|
|
|
|
// Check script exists
|
|
try {
|
|
await fs.access(runScript);
|
|
} catch {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `No run_optimization.py found for study '${args.study_name}'. Create the study first.`,
|
|
}],
|
|
isError: true,
|
|
};
|
|
}
|
|
|
|
// Build command
|
|
const cmdArgs = [runScript];
|
|
if (args.max_trials) cmdArgs.push("--trials", String(args.max_trials));
|
|
if (args.method) cmdArgs.push("--method", args.method);
|
|
|
|
// Spawn process
|
|
const process = spawn(PYTHON_PATH, cmdArgs, {
|
|
cwd: studyDir,
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
detached: true,
|
|
});
|
|
|
|
runningOptimizations.set(args.study_name, process);
|
|
|
|
process.on("exit", (code) => {
|
|
runningOptimizations.delete(args.study_name);
|
|
});
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `Optimization started for '${args.study_name}'.
|
|
PID: ${process.pid}
|
|
Method: ${args.method || "from config"}
|
|
Max Trials: ${args.max_trials || "from config"}
|
|
|
|
Use get_optimization_status to monitor progress.`,
|
|
}],
|
|
};
|
|
},
|
|
},
|
|
|
|
{
|
|
definition: {
|
|
name: "stop_optimization",
|
|
description: "Stop a running optimization gracefully",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
study_name: {
|
|
type: "string",
|
|
description: "Name of the study to stop",
|
|
},
|
|
},
|
|
required: ["study_name"],
|
|
},
|
|
},
|
|
handler: async (args: { study_name: string }) => {
|
|
const process = runningOptimizations.get(args.study_name);
|
|
|
|
if (!process) {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `No running optimization found for '${args.study_name}'.`,
|
|
}],
|
|
};
|
|
}
|
|
|
|
// Send SIGTERM for graceful shutdown
|
|
process.kill("SIGTERM");
|
|
runningOptimizations.delete(args.study_name);
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `Optimization for '${args.study_name}' has been stopped.`,
|
|
}],
|
|
};
|
|
},
|
|
},
|
|
|
|
{
|
|
definition: {
|
|
name: "get_optimization_status",
|
|
description: "Get the current status of an optimization including whether it's running, trial count, and recent progress",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
study_name: {
|
|
type: "string",
|
|
description: "Name of the study to check",
|
|
},
|
|
},
|
|
required: ["study_name"],
|
|
},
|
|
},
|
|
handler: async (args: { study_name: string }) => {
|
|
const isRunning = runningOptimizations.has(args.study_name);
|
|
const studyDir = path.join(STUDIES_DIR, args.study_name);
|
|
const dbPath = path.join(studyDir, "3_results", "study.db");
|
|
|
|
const result: any = {
|
|
study_name: args.study_name,
|
|
is_running: isRunning,
|
|
};
|
|
|
|
try {
|
|
const db = new Database(dbPath, { readonly: true });
|
|
|
|
// Get counts
|
|
const counts = db.prepare(`
|
|
SELECT
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN state = 'COMPLETE' THEN 1 ELSE 0 END) as complete,
|
|
SUM(CASE WHEN state = 'FAIL' THEN 1 ELSE 0 END) as failed
|
|
FROM trials
|
|
`).get() as any;
|
|
|
|
result.trials = counts;
|
|
|
|
// Get best
|
|
const best = db.prepare(`
|
|
SELECT t.number, tv.value
|
|
FROM trial_values tv
|
|
JOIN trials t ON tv.trial_id = t.trial_id
|
|
WHERE t.state = 'COMPLETE'
|
|
ORDER BY tv.value ASC
|
|
LIMIT 1
|
|
`).get() as any;
|
|
|
|
result.best = best;
|
|
|
|
// Get recent
|
|
const recent = db.prepare(`
|
|
SELECT t.number, t.state, tv.value, t.datetime_start
|
|
FROM trials t
|
|
LEFT JOIN trial_values tv ON t.trial_id = tv.trial_id
|
|
ORDER BY t.trial_id DESC
|
|
LIMIT 5
|
|
`).all();
|
|
|
|
result.recent = recent;
|
|
|
|
db.close();
|
|
} catch (e) {
|
|
result.database_error = "Could not read results";
|
|
}
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: JSON.stringify(result, null, 2),
|
|
}],
|
|
};
|
|
},
|
|
},
|
|
];
|
|
```
|
|
|
|
**Analysis Tools (analysis.ts):**
|
|
```typescript
|
|
// mcp-server/atomizer-tools/src/tools/analysis.ts
|
|
|
|
import Database from "better-sqlite3";
|
|
import * as path from "path";
|
|
|
|
const ATOMIZER_ROOT = process.env.ATOMIZER_ROOT || "C:/Users/antoi/Atomizer";
|
|
const STUDIES_DIR = path.join(ATOMIZER_ROOT, "studies");
|
|
|
|
export const analysisTools = [
|
|
{
|
|
definition: {
|
|
name: "get_trial_data",
|
|
description: "Query trial data from a study's database with various filters",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
study_name: { type: "string", description: "Study name" },
|
|
query: {
|
|
type: "string",
|
|
enum: ["all", "best", "pareto", "recent", "failed"],
|
|
description: "Type of query",
|
|
},
|
|
limit: { type: "number", description: "Max results (default: 10)" },
|
|
objective: { type: "string", description: "Objective to sort by" },
|
|
},
|
|
required: ["study_name", "query"],
|
|
},
|
|
},
|
|
handler: async (args) => {
|
|
const dbPath = path.join(STUDIES_DIR, args.study_name, "3_results", "study.db");
|
|
const limit = args.limit || 10;
|
|
|
|
try {
|
|
const db = new Database(dbPath, { readonly: true });
|
|
|
|
let sql: string;
|
|
switch (args.query) {
|
|
case "best":
|
|
sql = `
|
|
SELECT t.number as trial, tv.value as objective,
|
|
GROUP_CONCAT(tp.param_name || '=' || tp.param_value, ', ') as params
|
|
FROM trials t
|
|
JOIN trial_values tv ON t.trial_id = tv.trial_id
|
|
JOIN trial_params tp ON t.trial_id = tp.trial_id
|
|
WHERE t.state = 'COMPLETE'
|
|
GROUP BY t.trial_id
|
|
ORDER BY tv.value ASC
|
|
LIMIT ${limit}
|
|
`;
|
|
break;
|
|
case "recent":
|
|
sql = `
|
|
SELECT t.number as trial, t.state, tv.value as objective,
|
|
t.datetime_start as started
|
|
FROM trials t
|
|
LEFT JOIN trial_values tv ON t.trial_id = tv.trial_id
|
|
ORDER BY t.trial_id DESC
|
|
LIMIT ${limit}
|
|
`;
|
|
break;
|
|
case "failed":
|
|
sql = `
|
|
SELECT t.number as trial, t.state, t.datetime_start as started
|
|
FROM trials t
|
|
WHERE t.state = 'FAIL'
|
|
ORDER BY t.trial_id DESC
|
|
LIMIT ${limit}
|
|
`;
|
|
break;
|
|
case "pareto":
|
|
// Simplified - real Pareto requires multi-objective handling
|
|
sql = `
|
|
SELECT t.number as trial, tv.value as objective
|
|
FROM trials t
|
|
JOIN trial_values tv ON t.trial_id = tv.trial_id
|
|
WHERE t.state = 'COMPLETE'
|
|
ORDER BY tv.value ASC
|
|
LIMIT ${limit}
|
|
`;
|
|
break;
|
|
default:
|
|
sql = `
|
|
SELECT t.number as trial, t.state, tv.value as objective
|
|
FROM trials t
|
|
LEFT JOIN trial_values tv ON t.trial_id = tv.trial_id
|
|
ORDER BY t.trial_id
|
|
LIMIT ${limit}
|
|
`;
|
|
}
|
|
|
|
const results = db.prepare(sql).all();
|
|
db.close();
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: JSON.stringify(results, null, 2),
|
|
}],
|
|
};
|
|
} catch (e: any) {
|
|
return {
|
|
content: [{ type: "text", text: `Error querying trials: ${e.message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
|
|
{
|
|
definition: {
|
|
name: "analyze_convergence",
|
|
description: "Analyze optimization convergence and provide insights",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
study_name: { type: "string" },
|
|
window_size: { type: "number", description: "Rolling window for analysis (default: 10)" },
|
|
},
|
|
required: ["study_name"],
|
|
},
|
|
},
|
|
handler: async (args) => {
|
|
const dbPath = path.join(STUDIES_DIR, args.study_name, "3_results", "study.db");
|
|
const window = args.window_size || 10;
|
|
|
|
try {
|
|
const db = new Database(dbPath, { readonly: true });
|
|
|
|
// Get all completed trials with values
|
|
const trials = db.prepare(`
|
|
SELECT t.number, tv.value
|
|
FROM trials t
|
|
JOIN trial_values tv ON t.trial_id = tv.trial_id
|
|
WHERE t.state = 'COMPLETE'
|
|
ORDER BY t.number
|
|
`).all() as Array<{ number: number; value: number }>;
|
|
|
|
db.close();
|
|
|
|
if (trials.length < 5) {
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `Not enough trials (${trials.length}) for convergence analysis. Need at least 5.`,
|
|
}],
|
|
};
|
|
}
|
|
|
|
// Calculate metrics
|
|
const values = trials.map(t => t.value);
|
|
const bestSoFar: number[] = [];
|
|
let best = Infinity;
|
|
for (const v of values) {
|
|
best = Math.min(best, v);
|
|
bestSoFar.push(best);
|
|
}
|
|
|
|
// Improvement rate (last window vs previous window)
|
|
const recentBest = Math.min(...values.slice(-window));
|
|
const previousBest = Math.min(...values.slice(-2 * window, -window));
|
|
const improvementRate = previousBest > 0 ? (previousBest - recentBest) / previousBest : 0;
|
|
|
|
// Variance in recent trials
|
|
const recentValues = values.slice(-window);
|
|
const mean = recentValues.reduce((a, b) => a + b, 0) / recentValues.length;
|
|
const variance = recentValues.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / recentValues.length;
|
|
const stdDev = Math.sqrt(variance);
|
|
|
|
const result = {
|
|
total_trials: trials.length,
|
|
best_value: best,
|
|
best_trial: trials.find(t => t.value === best)?.number,
|
|
improvement_rate: improvementRate,
|
|
recent_std_dev: stdDev,
|
|
converged: improvementRate < 0.01 && stdDev < mean * 0.1,
|
|
recommendation: "",
|
|
};
|
|
|
|
// Generate recommendation
|
|
if (result.converged) {
|
|
result.recommendation = "Optimization appears converged. Consider stopping or increasing exploration.";
|
|
} else if (improvementRate > 0.05) {
|
|
result.recommendation = "Still improving significantly. Continue optimization.";
|
|
} else if (stdDev > mean * 0.3) {
|
|
result.recommendation = "High variance in results. May benefit from more exploitation (reduce exploration).";
|
|
} else {
|
|
result.recommendation = "Slow convergence. Consider different algorithm or expanded bounds.";
|
|
}
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: JSON.stringify(result, null, 2),
|
|
}],
|
|
};
|
|
} catch (e: any) {
|
|
return {
|
|
content: [{ type: "text", text: `Error analyzing convergence: ${e.message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
|
|
{
|
|
definition: {
|
|
name: "compare_trials",
|
|
description: "Compare multiple trials side by side",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
study_name: { type: "string" },
|
|
trial_numbers: {
|
|
type: "array",
|
|
items: { type: "number" },
|
|
description: "Trial numbers to compare",
|
|
},
|
|
},
|
|
required: ["study_name", "trial_numbers"],
|
|
},
|
|
},
|
|
handler: async (args) => {
|
|
const dbPath = path.join(STUDIES_DIR, args.study_name, "3_results", "study.db");
|
|
|
|
try {
|
|
const db = new Database(dbPath, { readonly: true });
|
|
|
|
const results = args.trial_numbers.map(num => {
|
|
const trial = db.prepare(`
|
|
SELECT t.number, t.state, tv.value as objective
|
|
FROM trials t
|
|
LEFT JOIN trial_values tv ON t.trial_id = tv.trial_id
|
|
WHERE t.number = ?
|
|
`).get(num) as any;
|
|
|
|
if (!trial) return { trial: num, error: "Not found" };
|
|
|
|
const params = db.prepare(`
|
|
SELECT param_name, param_value
|
|
FROM trial_params tp
|
|
JOIN trials t ON tp.trial_id = t.trial_id
|
|
WHERE t.number = ?
|
|
`).all(num) as Array<{ param_name: string; param_value: string }>;
|
|
|
|
trial.parameters = Object.fromEntries(
|
|
params.map(p => [p.param_name, parseFloat(p.param_value) || p.param_value])
|
|
);
|
|
|
|
return trial;
|
|
});
|
|
|
|
db.close();
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: JSON.stringify(results, null, 2),
|
|
}],
|
|
};
|
|
} catch (e: any) {
|
|
return {
|
|
content: [{ type: "text", text: `Error comparing trials: ${e.message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
|
|
{
|
|
definition: {
|
|
name: "get_best_design",
|
|
description: "Get the best design found in an optimization",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
study_name: { type: "string" },
|
|
objective_index: {
|
|
type: "number",
|
|
description: "Index of objective (0-based) for multi-objective studies",
|
|
},
|
|
},
|
|
required: ["study_name"],
|
|
},
|
|
},
|
|
handler: async (args) => {
|
|
const dbPath = path.join(STUDIES_DIR, args.study_name, "3_results", "study.db");
|
|
|
|
try {
|
|
const db = new Database(dbPath, { readonly: true });
|
|
|
|
const best = db.prepare(`
|
|
SELECT t.number, t.state, tv.value as objective
|
|
FROM trials t
|
|
JOIN trial_values tv ON t.trial_id = tv.trial_id
|
|
WHERE t.state = 'COMPLETE'
|
|
ORDER BY tv.value ASC
|
|
LIMIT 1
|
|
`).get() as any;
|
|
|
|
if (!best) {
|
|
db.close();
|
|
return {
|
|
content: [{ type: "text", text: "No completed trials found." }],
|
|
};
|
|
}
|
|
|
|
const params = db.prepare(`
|
|
SELECT param_name, param_value
|
|
FROM trial_params tp
|
|
JOIN trials t ON tp.trial_id = t.trial_id
|
|
WHERE t.number = ?
|
|
`).all(best.number);
|
|
|
|
best.parameters = Object.fromEntries(
|
|
(params as any[]).map(p => [p.param_name, parseFloat(p.param_value) || p.param_value])
|
|
);
|
|
|
|
db.close();
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: JSON.stringify(best, null, 2),
|
|
}],
|
|
};
|
|
} catch (e: any) {
|
|
return {
|
|
content: [{ type: "text", text: `Error getting best design: ${e.message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
];
|
|
```
|
|
|
|
*[Document continues with remaining tool implementations, backend components, frontend components, etc.]*
|
|
|
|
---
|
|
|
|
## 6. Phase 1: Canvas - React Flow Integration
|
|
|
|
### 6.1 Overview
|
|
|
|
Add a visual workflow builder using React Flow that allows users to construct optimization configurations by connecting nodes.
|
|
|
|
### 6.2 React Flow Setup
|
|
|
|
**Install Dependencies:**
|
|
```bash
|
|
cd atomizer-dashboard/frontend
|
|
npm install reactflow zustand
|
|
npm install -D @types/reactflow
|
|
```
|
|
|
|
### 6.3 Node Types
|
|
|
|
```typescript
|
|
// frontend/src/lib/canvas/schema.ts
|
|
|
|
export type NodeCategory =
|
|
| "input" // Model, expressions
|
|
| "process" // Solver, updater
|
|
| "extract" // Physics extractors
|
|
| "logic" // Objectives, constraints
|
|
| "optimize" // Algorithm, surrogate
|
|
| "output"; // Results, reports
|
|
|
|
export interface PortDefinition {
|
|
id: string;
|
|
label: string;
|
|
dataType: DataType;
|
|
direction: "input" | "output";
|
|
required?: boolean;
|
|
multiple?: boolean; // Can accept multiple connections
|
|
}
|
|
|
|
export type DataType =
|
|
| "model" // NX part/assembly
|
|
| "mesh" // FEM mesh
|
|
| "results" // OP2/F06 files
|
|
| "scalar" // Single numeric value
|
|
| "vector" // Array of values
|
|
| "config" // Configuration object
|
|
| "any"; // Accepts any type
|
|
|
|
export interface BaseNodeData {
|
|
label: string;
|
|
category: NodeCategory;
|
|
description?: string;
|
|
ports: {
|
|
inputs: PortDefinition[];
|
|
outputs: PortDefinition[];
|
|
};
|
|
config: Record<string, any>;
|
|
validation?: {
|
|
isValid: boolean;
|
|
errors: string[];
|
|
warnings: string[];
|
|
};
|
|
}
|
|
|
|
// Specific node data types
|
|
export interface ModelNodeData extends BaseNodeData {
|
|
category: "input";
|
|
config: {
|
|
path: string;
|
|
type: "nx_part" | "nx_assembly";
|
|
expressions?: string[];
|
|
};
|
|
}
|
|
|
|
export interface SolverNodeData extends BaseNodeData {
|
|
category: "process";
|
|
config: {
|
|
solver: "nastran";
|
|
solution: 101 | 103 | 105 | 111 | 112;
|
|
options?: Record<string, any>;
|
|
};
|
|
}
|
|
|
|
export interface ExtractorNodeData extends BaseNodeData {
|
|
category: "extract";
|
|
config: {
|
|
physics: string; // Human-readable: "mass", "stress", etc.
|
|
extractor_id?: string; // Resolved by Claude: "E4", "E3", etc.
|
|
subcase?: number;
|
|
aggregation?: "max" | "min" | "mean" | "sum";
|
|
node_set?: string;
|
|
};
|
|
}
|
|
|
|
export interface ObjectiveNodeData extends BaseNodeData {
|
|
category: "logic";
|
|
config: {
|
|
name: string;
|
|
direction: "minimize" | "maximize";
|
|
weight?: number;
|
|
target?: number;
|
|
};
|
|
}
|
|
|
|
export interface ConstraintNodeData extends BaseNodeData {
|
|
category: "logic";
|
|
config: {
|
|
name: string;
|
|
type: "upper" | "lower" | "equality" | "range";
|
|
value?: number;
|
|
lower?: number;
|
|
upper?: number;
|
|
unit?: string;
|
|
};
|
|
}
|
|
|
|
export interface AlgorithmNodeData extends BaseNodeData {
|
|
category: "optimize";
|
|
config: {
|
|
method: "TPE" | "CMA-ES" | "NSGA-II" | "IMSO" | "auto";
|
|
trials?: number;
|
|
timeout?: number;
|
|
seed?: number;
|
|
};
|
|
}
|
|
|
|
export interface SurrogateNodeData extends BaseNodeData {
|
|
category: "optimize";
|
|
config: {
|
|
type: "mlp" | "gnn" | "ensemble" | "none" | "auto";
|
|
train_after?: number; // Train after N FEA trials
|
|
predict_count?: number; // Predictions per FEA validation
|
|
};
|
|
}
|
|
|
|
export interface DesignVarNodeData extends BaseNodeData {
|
|
category: "input";
|
|
config: {
|
|
name: string;
|
|
nx_expression?: string;
|
|
type: "continuous" | "integer" | "categorical";
|
|
lower?: number;
|
|
upper?: number;
|
|
values?: (string | number)[];
|
|
};
|
|
}
|
|
```
|
|
|
|
### 6.4 Node Components
|
|
|
|
```tsx
|
|
// frontend/src/components/canvas/nodes/BaseNode.tsx
|
|
|
|
import React from 'react';
|
|
import { Handle, Position, NodeProps } from 'reactflow';
|
|
import { BaseNodeData, PortDefinition } from '../../../lib/canvas/schema';
|
|
|
|
interface BaseNodeProps extends NodeProps<BaseNodeData> {
|
|
icon: React.ReactNode;
|
|
color: string;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
export const BaseNode: React.FC<BaseNodeProps> = ({
|
|
data,
|
|
selected,
|
|
icon,
|
|
color,
|
|
children,
|
|
}) => {
|
|
return (
|
|
<div
|
|
className={`
|
|
px-4 py-3 rounded-lg border-2 min-w-[180px]
|
|
${selected ? 'border-primary-500 shadow-lg' : 'border-dark-600'}
|
|
bg-dark-800
|
|
`}
|
|
>
|
|
{/* Input Handles */}
|
|
{data.ports.inputs.map((port, idx) => (
|
|
<Handle
|
|
key={port.id}
|
|
type="target"
|
|
position={Position.Left}
|
|
id={port.id}
|
|
style={{
|
|
top: `${((idx + 1) / (data.ports.inputs.length + 1)) * 100}%`,
|
|
background: getDataTypeColor(port.dataType),
|
|
}}
|
|
title={port.label}
|
|
/>
|
|
))}
|
|
|
|
{/* Header */}
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<div
|
|
className="w-8 h-8 rounded-md flex items-center justify-center"
|
|
style={{ backgroundColor: `${color}20` }}
|
|
>
|
|
<span style={{ color }}>{icon}</span>
|
|
</div>
|
|
<div>
|
|
<div className="text-sm font-medium text-dark-100">{data.label}</div>
|
|
<div className="text-xs text-dark-400">{data.category}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
{children}
|
|
|
|
{/* Validation Status */}
|
|
{data.validation && !data.validation.isValid && (
|
|
<div className="mt-2 text-xs text-red-400">
|
|
{data.validation.errors[0]}
|
|
</div>
|
|
)}
|
|
|
|
{/* Output Handles */}
|
|
{data.ports.outputs.map((port, idx) => (
|
|
<Handle
|
|
key={port.id}
|
|
type="source"
|
|
position={Position.Right}
|
|
id={port.id}
|
|
style={{
|
|
top: `${((idx + 1) / (data.ports.outputs.length + 1)) * 100}%`,
|
|
background: getDataTypeColor(port.dataType),
|
|
}}
|
|
title={port.label}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
function getDataTypeColor(type: string): string {
|
|
const colors: Record<string, string> = {
|
|
model: '#3b82f6', // blue
|
|
mesh: '#8b5cf6', // purple
|
|
results: '#f97316', // orange
|
|
scalar: '#22c55e', // green
|
|
vector: '#14b8a6', // teal
|
|
config: '#6b7280', // gray
|
|
any: '#a855f7', // violet
|
|
};
|
|
return colors[type] || colors.any;
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
// frontend/src/components/canvas/nodes/ExtractorNode.tsx
|
|
|
|
import React from 'react';
|
|
import { NodeProps } from 'reactflow';
|
|
import { Beaker } from 'lucide-react';
|
|
import { BaseNode } from './BaseNode';
|
|
import { ExtractorNodeData } from '../../../lib/canvas/schema';
|
|
|
|
// Physics options with their descriptions
|
|
const PHYSICS_OPTIONS = [
|
|
{ value: 'mass', label: 'Mass', description: 'Total mass from BDF' },
|
|
{ value: 'stress', label: 'Von Mises Stress', description: 'Maximum stress' },
|
|
{ value: 'displacement', label: 'Displacement', description: 'Nodal displacement' },
|
|
{ value: 'frequency', label: 'Natural Frequency', description: 'Modal frequency' },
|
|
{ value: 'zernike_wfe', label: 'Zernike WFE', description: 'Wavefront error' },
|
|
{ value: 'buckling', label: 'Buckling Factor', description: 'Critical load factor' },
|
|
{ value: 'temperature', label: 'Temperature', description: 'Thermal results' },
|
|
];
|
|
|
|
export const ExtractorNode: React.FC<NodeProps<ExtractorNodeData>> = (props) => {
|
|
const { data } = props;
|
|
|
|
const selectedPhysics = PHYSICS_OPTIONS.find(p => p.value === data.config.physics);
|
|
|
|
return (
|
|
<BaseNode {...props} icon={<Beaker className="w-4 h-4" />} color="#f97316">
|
|
<div className="space-y-2">
|
|
<div className="text-xs">
|
|
<span className="text-dark-400">Physics: </span>
|
|
<span className="text-dark-200">{selectedPhysics?.label || 'Not set'}</span>
|
|
</div>
|
|
|
|
{data.config.aggregation && (
|
|
<div className="text-xs">
|
|
<span className="text-dark-400">Aggregation: </span>
|
|
<span className="text-dark-200">{data.config.aggregation}</span>
|
|
</div>
|
|
)}
|
|
|
|
{data.config.extractor_id && (
|
|
<div className="text-xs text-dark-500">
|
|
→ {data.config.extractor_id}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</BaseNode>
|
|
);
|
|
};
|
|
|
|
// Default data for new extractor nodes
|
|
export const extractorNodeDefaults: ExtractorNodeData = {
|
|
label: 'Extractor',
|
|
category: 'extract',
|
|
description: 'Extract physics quantity from FEA results',
|
|
ports: {
|
|
inputs: [
|
|
{ id: 'results', label: 'Results', dataType: 'results', direction: 'input', required: true },
|
|
],
|
|
outputs: [
|
|
{ id: 'value', label: 'Value', dataType: 'scalar', direction: 'output' },
|
|
],
|
|
},
|
|
config: {
|
|
physics: '',
|
|
aggregation: 'max',
|
|
},
|
|
};
|
|
```
|
|
|
|
### 6.5 Canvas State Management
|
|
|
|
```typescript
|
|
// frontend/src/hooks/useCanvas.ts
|
|
|
|
import { create } from 'zustand';
|
|
import {
|
|
Node,
|
|
Edge,
|
|
Connection,
|
|
addEdge,
|
|
applyNodeChanges,
|
|
applyEdgeChanges,
|
|
NodeChange,
|
|
EdgeChange,
|
|
} from 'reactflow';
|
|
import { BaseNodeData } from '../lib/canvas/schema';
|
|
import { buildIntent } from '../lib/canvas/intent';
|
|
|
|
interface CanvasState {
|
|
// Graph state
|
|
nodes: Node<BaseNodeData>[];
|
|
edges: Edge[];
|
|
|
|
// Selection
|
|
selectedNodeId: string | null;
|
|
|
|
// Validation
|
|
validationErrors: string[];
|
|
validationWarnings: string[];
|
|
|
|
// Actions
|
|
setNodes: (nodes: Node<BaseNodeData>[]) => void;
|
|
setEdges: (edges: Edge[]) => void;
|
|
onNodesChange: (changes: NodeChange[]) => void;
|
|
onEdgesChange: (changes: EdgeChange[]) => void;
|
|
onConnect: (connection: Connection) => void;
|
|
|
|
addNode: (type: string, position: { x: number; y: number }) => void;
|
|
removeNode: (nodeId: string) => void;
|
|
updateNodeConfig: (nodeId: string, config: Record<string, any>) => void;
|
|
|
|
selectNode: (nodeId: string | null) => void;
|
|
|
|
validate: () => boolean;
|
|
buildIntent: () => any;
|
|
|
|
loadFromConfig: (config: any) => void;
|
|
loadTemplate: (templateName: string) => void;
|
|
clear: () => void;
|
|
}
|
|
|
|
export const useCanvasStore = create<CanvasState>((set, get) => ({
|
|
nodes: [],
|
|
edges: [],
|
|
selectedNodeId: null,
|
|
validationErrors: [],
|
|
validationWarnings: [],
|
|
|
|
setNodes: (nodes) => set({ nodes }),
|
|
setEdges: (edges) => set({ edges }),
|
|
|
|
onNodesChange: (changes) => {
|
|
set({
|
|
nodes: applyNodeChanges(changes, get().nodes) as Node<BaseNodeData>[],
|
|
});
|
|
},
|
|
|
|
onEdgesChange: (changes) => {
|
|
set({
|
|
edges: applyEdgeChanges(changes, get().edges),
|
|
});
|
|
},
|
|
|
|
onConnect: (connection) => {
|
|
// Validate connection types match
|
|
const { nodes } = get();
|
|
const sourceNode = nodes.find(n => n.id === connection.source);
|
|
const targetNode = nodes.find(n => n.id === connection.target);
|
|
|
|
if (!sourceNode || !targetNode) return;
|
|
|
|
const sourcePort = sourceNode.data.ports.outputs.find(
|
|
p => p.id === connection.sourceHandle
|
|
);
|
|
const targetPort = targetNode.data.ports.inputs.find(
|
|
p => p.id === connection.targetHandle
|
|
);
|
|
|
|
if (!sourcePort || !targetPort) return;
|
|
|
|
// Check type compatibility
|
|
if (targetPort.dataType !== 'any' && sourcePort.dataType !== targetPort.dataType) {
|
|
console.warn(`Type mismatch: ${sourcePort.dataType} → ${targetPort.dataType}`);
|
|
return;
|
|
}
|
|
|
|
set({
|
|
edges: addEdge(
|
|
{
|
|
...connection,
|
|
type: 'dataFlow',
|
|
data: { dataType: sourcePort.dataType },
|
|
},
|
|
get().edges
|
|
),
|
|
});
|
|
},
|
|
|
|
addNode: (type, position) => {
|
|
const { nodes } = get();
|
|
const id = `${type}_${Date.now()}`;
|
|
|
|
// Get default data for node type
|
|
const defaults = getNodeDefaults(type);
|
|
|
|
const newNode: Node<BaseNodeData> = {
|
|
id,
|
|
type,
|
|
position,
|
|
data: {
|
|
...defaults,
|
|
label: `${defaults.label} ${nodes.filter(n => n.type === type).length + 1}`,
|
|
},
|
|
};
|
|
|
|
set({ nodes: [...nodes, newNode] });
|
|
},
|
|
|
|
removeNode: (nodeId) => {
|
|
set({
|
|
nodes: get().nodes.filter(n => n.id !== nodeId),
|
|
edges: get().edges.filter(e => e.source !== nodeId && e.target !== nodeId),
|
|
selectedNodeId: get().selectedNodeId === nodeId ? null : get().selectedNodeId,
|
|
});
|
|
},
|
|
|
|
updateNodeConfig: (nodeId, config) => {
|
|
set({
|
|
nodes: get().nodes.map(node =>
|
|
node.id === nodeId
|
|
? { ...node, data: { ...node.data, config: { ...node.data.config, ...config } } }
|
|
: node
|
|
),
|
|
});
|
|
},
|
|
|
|
selectNode: (nodeId) => set({ selectedNodeId: nodeId }),
|
|
|
|
validate: () => {
|
|
const { nodes, edges } = get();
|
|
const errors: string[] = [];
|
|
const warnings: string[] = [];
|
|
|
|
// Check for required nodes
|
|
const hasModel = nodes.some(n => n.type === 'model');
|
|
const hasSolver = nodes.some(n => n.type === 'solver');
|
|
const hasObjective = nodes.some(n => n.type === 'objective');
|
|
const hasAlgorithm = nodes.some(n => n.type === 'algorithm');
|
|
|
|
if (!hasModel) errors.push('Missing model node');
|
|
if (!hasSolver) errors.push('Missing solver node');
|
|
if (!hasObjective) errors.push('Missing objective node');
|
|
if (!hasAlgorithm) warnings.push('No algorithm specified - will use default');
|
|
|
|
// Check for unconnected required ports
|
|
nodes.forEach(node => {
|
|
node.data.ports.inputs
|
|
.filter(p => p.required)
|
|
.forEach(port => {
|
|
const hasConnection = edges.some(
|
|
e => e.target === node.id && e.targetHandle === port.id
|
|
);
|
|
if (!hasConnection) {
|
|
errors.push(`${node.data.label}: Missing required input "${port.label}"`);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Check for objectives without constraints (warning)
|
|
const hasConstraint = nodes.some(n => n.type === 'constraint');
|
|
if (hasObjective && !hasConstraint) {
|
|
const objectiveNodes = nodes.filter(n => n.type === 'objective');
|
|
const hasMassObjective = objectiveNodes.some(
|
|
n => (n.data as any).config?.name?.toLowerCase().includes('mass')
|
|
);
|
|
if (hasMassObjective) {
|
|
warnings.push('Mass objective without constraint may produce paper-thin designs');
|
|
}
|
|
}
|
|
|
|
set({
|
|
validationErrors: errors,
|
|
validationWarnings: warnings,
|
|
nodes: nodes.map(node => ({
|
|
...node,
|
|
data: {
|
|
...node.data,
|
|
validation: {
|
|
isValid: !errors.some(e => e.includes(node.data.label)),
|
|
errors: errors.filter(e => e.includes(node.data.label)),
|
|
warnings: warnings.filter(w => w.includes(node.data.label)),
|
|
},
|
|
},
|
|
})),
|
|
});
|
|
|
|
return errors.length === 0;
|
|
},
|
|
|
|
buildIntent: () => {
|
|
const { nodes, edges } = get();
|
|
return buildIntent(nodes, edges);
|
|
},
|
|
|
|
loadFromConfig: (config) => {
|
|
// Convert optimization_config.json to canvas nodes
|
|
// Implementation details...
|
|
},
|
|
|
|
loadTemplate: (templateName) => {
|
|
// Load preset template
|
|
// Implementation details...
|
|
},
|
|
|
|
clear: () => set({ nodes: [], edges: [], selectedNodeId: null }),
|
|
}));
|
|
|
|
function getNodeDefaults(type: string): BaseNodeData {
|
|
// Return default data for each node type
|
|
const defaults: Record<string, BaseNodeData> = {
|
|
model: {
|
|
label: 'Model',
|
|
category: 'input',
|
|
ports: {
|
|
inputs: [],
|
|
outputs: [{ id: 'model', label: 'Model', dataType: 'model', direction: 'output' }],
|
|
},
|
|
config: { path: '', type: 'nx_part' },
|
|
},
|
|
solver: {
|
|
label: 'Solver',
|
|
category: 'process',
|
|
ports: {
|
|
inputs: [{ id: 'model', label: 'Model', dataType: 'model', direction: 'input', required: true }],
|
|
outputs: [{ id: 'results', label: 'Results', dataType: 'results', direction: 'output' }],
|
|
},
|
|
config: { solver: 'nastran', solution: 101 },
|
|
},
|
|
extractor: {
|
|
label: 'Extractor',
|
|
category: 'extract',
|
|
ports: {
|
|
inputs: [{ id: 'results', label: 'Results', dataType: 'results', direction: 'input', required: true }],
|
|
outputs: [{ id: 'value', label: 'Value', dataType: 'scalar', direction: 'output' }],
|
|
},
|
|
config: { physics: '', aggregation: 'max' },
|
|
},
|
|
objective: {
|
|
label: 'Objective',
|
|
category: 'logic',
|
|
ports: {
|
|
inputs: [{ id: 'value', label: 'Value', dataType: 'scalar', direction: 'input', required: true }],
|
|
outputs: [{ id: 'objective', label: 'Objective', dataType: 'config', direction: 'output' }],
|
|
},
|
|
config: { name: '', direction: 'minimize' },
|
|
},
|
|
constraint: {
|
|
label: 'Constraint',
|
|
category: 'logic',
|
|
ports: {
|
|
inputs: [{ id: 'value', label: 'Value', dataType: 'scalar', direction: 'input', required: true }],
|
|
outputs: [{ id: 'constraint', label: 'Constraint', dataType: 'config', direction: 'output' }],
|
|
},
|
|
config: { name: '', type: 'upper' },
|
|
},
|
|
algorithm: {
|
|
label: 'Algorithm',
|
|
category: 'optimize',
|
|
ports: {
|
|
inputs: [
|
|
{ id: 'objectives', label: 'Objectives', dataType: 'config', direction: 'input', required: true, multiple: true },
|
|
{ id: 'constraints', label: 'Constraints', dataType: 'config', direction: 'input', multiple: true },
|
|
],
|
|
outputs: [{ id: 'config', label: 'Config', dataType: 'config', direction: 'output' }],
|
|
},
|
|
config: { method: 'auto', trials: 100 },
|
|
},
|
|
designVar: {
|
|
label: 'Design Variable',
|
|
category: 'input',
|
|
ports: {
|
|
inputs: [],
|
|
outputs: [{ id: 'var', label: 'Variable', dataType: 'config', direction: 'output' }],
|
|
},
|
|
config: { name: '', type: 'continuous' },
|
|
},
|
|
surrogate: {
|
|
label: 'Surrogate',
|
|
category: 'optimize',
|
|
ports: {
|
|
inputs: [{ id: 'config', label: 'Config', dataType: 'config', direction: 'input' }],
|
|
outputs: [{ id: 'config', label: 'Config', dataType: 'config', direction: 'output' }],
|
|
},
|
|
config: { type: 'auto' },
|
|
},
|
|
};
|
|
|
|
return defaults[type] || defaults.model;
|
|
}
|
|
```
|
|
|
|
*[Document continues with remaining sections...]*
|
|
|
|
---
|
|
|
|
## 19. Task Breakdown
|
|
|
|
### Phase 0: MCP Chat Foundation
|
|
|
|
```markdown
|
|
## P0.1 - Create MCP Server Scaffold
|
|
Priority: CRITICAL
|
|
Estimated: 2-3 hours
|
|
|
|
Files:
|
|
- CREATE: mcp-server/atomizer-tools/package.json
|
|
- CREATE: mcp-server/atomizer-tools/tsconfig.json
|
|
- CREATE: mcp-server/atomizer-tools/src/index.ts
|
|
- CREATE: mcp-server/atomizer-tools/src/utils/paths.ts
|
|
|
|
Tasks:
|
|
1. Create directory structure
|
|
2. Initialize npm package
|
|
3. Configure TypeScript
|
|
4. Create server entry point
|
|
5. Test: npm run build succeeds
|
|
6. Test: server starts without error
|
|
|
|
Acceptance:
|
|
- [ ] npm install completes
|
|
- [ ] npm run build succeeds
|
|
- [ ] Server runs and logs startup message
|
|
|
|
---
|
|
|
|
## P0.2 - Implement Study Tools
|
|
Priority: HIGH
|
|
Estimated: 3-4 hours
|
|
Dependencies: P0.1
|
|
|
|
Files:
|
|
- CREATE: mcp-server/atomizer-tools/src/tools/study.ts
|
|
|
|
Tools:
|
|
1. list_studies - List all studies with status
|
|
2. get_study_status - Detailed study info
|
|
3. create_study - Format intent for Claude
|
|
|
|
Acceptance:
|
|
- [ ] list_studies returns array of studies
|
|
- [ ] get_study_status returns config + trials
|
|
- [ ] create_study formats proper prompt
|
|
|
|
---
|
|
|
|
## P0.3 - Implement Optimization Tools
|
|
Priority: HIGH
|
|
Estimated: 2-3 hours
|
|
Dependencies: P0.1
|
|
|
|
Files:
|
|
- CREATE: mcp-server/atomizer-tools/src/tools/optimization.ts
|
|
|
|
Tools:
|
|
1. run_optimization - Start optimization subprocess
|
|
2. stop_optimization - Graceful termination
|
|
3. get_optimization_status - Running state + progress
|
|
|
|
Acceptance:
|
|
- [ ] Can start optimization
|
|
- [ ] Can stop running optimization
|
|
- [ ] Status reflects actual state
|
|
|
|
---
|
|
|
|
## P0.4 - Implement Analysis Tools
|
|
Priority: HIGH
|
|
Estimated: 3-4 hours
|
|
Dependencies: P0.1
|
|
|
|
Files:
|
|
- CREATE: mcp-server/atomizer-tools/src/tools/analysis.ts
|
|
|
|
Tools:
|
|
1. get_trial_data - Query with filters
|
|
2. analyze_convergence - Trend metrics
|
|
3. compare_trials - Side-by-side comparison
|
|
4. get_best_design - Best design with params
|
|
|
|
Acceptance:
|
|
- [ ] All queries work on test database
|
|
- [ ] Convergence analysis produces valid metrics
|
|
- [ ] Comparison returns structured data
|
|
|
|
---
|
|
|
|
## P0.5 - Implement Reporting Tools
|
|
Priority: MEDIUM
|
|
Estimated: 2 hours
|
|
Dependencies: P0.1
|
|
|
|
Files:
|
|
- CREATE: mcp-server/atomizer-tools/src/tools/reporting.ts
|
|
|
|
Tools:
|
|
1. generate_report - Markdown report
|
|
2. export_data - CSV/JSON export
|
|
|
|
Acceptance:
|
|
- [ ] Report generates valid markdown
|
|
- [ ] Export creates valid files
|
|
|
|
---
|
|
|
|
## P0.6 - Implement Physics Tools
|
|
Priority: MEDIUM
|
|
Estimated: 2 hours
|
|
Dependencies: P0.1
|
|
|
|
Files:
|
|
- CREATE: mcp-server/atomizer-tools/src/tools/physics.ts
|
|
|
|
Tools:
|
|
1. explain_physics - FEA concept explanation
|
|
2. recommend_method - Algorithm recommendation
|
|
3. query_extractors - List available extractors
|
|
|
|
Acceptance:
|
|
- [ ] Explanations are contextual
|
|
- [ ] Recommendations consider problem type
|
|
|
|
---
|
|
|
|
## P0.7 - Implement Admin Tools (Power Mode)
|
|
Priority: MEDIUM
|
|
Estimated: 3-4 hours
|
|
Dependencies: P0.2-P0.6
|
|
|
|
Files:
|
|
- CREATE: mcp-server/atomizer-tools/src/tools/admin.ts
|
|
|
|
Tools:
|
|
1. edit_file - Modify codebase files
|
|
2. create_extractor - Generate new extractor
|
|
3. run_shell_command - Execute commands
|
|
4. search_codebase - Search files
|
|
|
|
Acceptance:
|
|
- [ ] Only available in power mode
|
|
- [ ] File edits work correctly
|
|
- [ ] Commands execute safely
|
|
|
|
---
|
|
|
|
## P0.8 - Build and Register MCP Server
|
|
Priority: HIGH
|
|
Estimated: 1-2 hours
|
|
Dependencies: P0.2-P0.7
|
|
|
|
Tasks:
|
|
1. npm run build
|
|
2. Test all tools
|
|
3. Create MCP config file
|
|
4. Test with claude --mcp-config
|
|
|
|
Acceptance:
|
|
- [ ] Build succeeds
|
|
- [ ] Claude can list tools
|
|
- [ ] Tools execute correctly
|
|
|
|
---
|
|
|
|
## P0.9 - Create Backend Session Manager
|
|
Priority: CRITICAL
|
|
Estimated: 4-5 hours
|
|
Dependencies: P0.8
|
|
|
|
Files:
|
|
- CREATE: backend/api/services/session_manager.py
|
|
- CREATE: backend/api/services/conversation_store.py
|
|
- CREATE: backend/api/services/context_builder.py
|
|
|
|
Components:
|
|
1. SessionManager - Claude subprocess lifecycle
|
|
2. ConversationStore - SQLite persistence
|
|
3. ContextBuilder - System prompt generation
|
|
|
|
Acceptance:
|
|
- [ ] Sessions persist across messages
|
|
- [ ] Context includes study state
|
|
- [ ] Mode switching works
|
|
|
|
---
|
|
|
|
## P0.10 - Update Backend Routes
|
|
Priority: HIGH
|
|
Estimated: 2-3 hours
|
|
Dependencies: P0.9
|
|
|
|
Files:
|
|
- UPDATE: backend/api/routes/claude.py
|
|
- UPDATE: backend/main.py
|
|
|
|
Changes:
|
|
1. Session creation endpoint
|
|
2. WebSocket message handler
|
|
3. Mode switching endpoint
|
|
4. Lifespan handlers
|
|
|
|
Acceptance:
|
|
- [ ] WebSocket connects
|
|
- [ ] Messages stream correctly
|
|
- [ ] Sessions cleanup properly
|
|
|
|
---
|
|
|
|
## P0.11 - Update Frontend Chat Components
|
|
Priority: HIGH
|
|
Estimated: 3-4 hours
|
|
Dependencies: P0.10
|
|
|
|
Files:
|
|
- CREATE: frontend/src/components/chat/ModeToggle.tsx
|
|
- CREATE: frontend/src/components/chat/ToolCallCard.tsx
|
|
- UPDATE: frontend/src/components/chat/ChatPane.tsx
|
|
- UPDATE: frontend/src/components/chat/ChatMessage.tsx
|
|
- UPDATE: frontend/src/hooks/useChat.ts
|
|
|
|
Changes:
|
|
1. Mode toggle UI
|
|
2. Tool call visualization
|
|
3. WebSocket integration
|
|
4. Session management
|
|
|
|
Acceptance:
|
|
- [ ] Mode toggle works
|
|
- [ ] Tool calls display
|
|
- [ ] Streaming works
|
|
- [ ] Sessions persist
|
|
|
|
---
|
|
|
|
## P0.12 - Integration Testing
|
|
Priority: HIGH
|
|
Estimated: 2-3 hours
|
|
Dependencies: P0.11
|
|
|
|
Tests:
|
|
1. Create session → send message → receive response
|
|
2. Tool call → visualize → show result
|
|
3. Mode switch → confirm → new capabilities
|
|
4. Page refresh → restore session
|
|
|
|
Acceptance:
|
|
- [ ] Full conversation flow works
|
|
- [ ] No regressions in existing features
|
|
```
|
|
|
|
### Phase 1: Canvas - React Flow
|
|
|
|
```markdown
|
|
## P1.1 - Install Dependencies
|
|
Priority: HIGH
|
|
Estimated: 30 min
|
|
|
|
Commands:
|
|
npm install reactflow zustand
|
|
npm install -D @types/reactflow
|
|
|
|
---
|
|
|
|
## P1.2 - Create Canvas Schema
|
|
Priority: HIGH
|
|
Estimated: 2-3 hours
|
|
|
|
Files:
|
|
- CREATE: frontend/src/lib/canvas/schema.ts
|
|
- CREATE: frontend/src/lib/canvas/intent.ts
|
|
- CREATE: frontend/src/lib/canvas/validation.ts
|
|
|
|
---
|
|
|
|
## P1.3 - Create Base Node Component
|
|
Priority: HIGH
|
|
Estimated: 2 hours
|
|
|
|
Files:
|
|
- CREATE: frontend/src/components/canvas/nodes/BaseNode.tsx
|
|
|
|
---
|
|
|
|
## P1.4 - Create Node Types
|
|
Priority: HIGH
|
|
Estimated: 4-5 hours
|
|
|
|
Files:
|
|
- CREATE: frontend/src/components/canvas/nodes/ModelNode.tsx
|
|
- CREATE: frontend/src/components/canvas/nodes/SolverNode.tsx
|
|
- CREATE: frontend/src/components/canvas/nodes/ExtractorNode.tsx
|
|
- CREATE: frontend/src/components/canvas/nodes/ObjectiveNode.tsx
|
|
- CREATE: frontend/src/components/canvas/nodes/ConstraintNode.tsx
|
|
- CREATE: frontend/src/components/canvas/nodes/AlgorithmNode.tsx
|
|
- CREATE: frontend/src/components/canvas/nodes/DesignVarNode.tsx
|
|
- CREATE: frontend/src/components/canvas/nodes/SurrogateNode.tsx
|
|
|
|
---
|
|
|
|
## P1.5 - Create Node Palette
|
|
Priority: HIGH
|
|
Estimated: 2 hours
|
|
|
|
Files:
|
|
- CREATE: frontend/src/components/canvas/palette/NodePalette.tsx
|
|
- CREATE: frontend/src/components/canvas/palette/NodeCategory.tsx
|
|
|
|
---
|
|
|
|
## P1.6 - Create Canvas State Management
|
|
Priority: HIGH
|
|
Estimated: 3-4 hours
|
|
|
|
Files:
|
|
- CREATE: frontend/src/hooks/useCanvas.ts
|
|
- CREATE: frontend/src/context/CanvasContext.tsx
|
|
|
|
---
|
|
|
|
## P1.7 - Create Main Canvas Component
|
|
Priority: HIGH
|
|
Estimated: 3-4 hours
|
|
|
|
Files:
|
|
- CREATE: frontend/src/components/canvas/AtomizerCanvas.tsx
|
|
|
|
---
|
|
|
|
## P1.8 - Create Config Panels
|
|
Priority: MEDIUM
|
|
Estimated: 3-4 hours
|
|
|
|
Files:
|
|
- CREATE: frontend/src/components/canvas/panels/NodeConfigPanel.tsx
|
|
- CREATE: frontend/src/components/canvas/panels/ValidationPanel.tsx
|
|
|
|
---
|
|
|
|
## P1.9 - Integrate Canvas with Layout
|
|
Priority: HIGH
|
|
Estimated: 2 hours
|
|
|
|
Files:
|
|
- UPDATE: frontend/src/pages/StudyView.tsx
|
|
- UPDATE: frontend/src/components/layout/MainLayout.tsx
|
|
- CREATE: frontend/src/pages/CanvasView.tsx
|
|
|
|
---
|
|
|
|
## P1.10 - Canvas Testing
|
|
Priority: HIGH
|
|
Estimated: 2-3 hours
|
|
|
|
Tests:
|
|
1. Add nodes via drag-drop
|
|
2. Connect nodes
|
|
3. Validate graph
|
|
4. Serialize to intent
|
|
```
|
|
|
|
### Phase 2: LLM Integration
|
|
|
|
```markdown
|
|
## P2.1 - Create Canvas Intent Tool
|
|
Priority: HIGH
|
|
Estimated: 2 hours
|
|
|
|
Files:
|
|
- CREATE: mcp-server/atomizer-tools/src/tools/canvas.ts
|
|
|
|
---
|
|
|
|
## P2.2 - Create Canvas-Chat Bridge
|
|
Priority: HIGH
|
|
Estimated: 3-4 hours
|
|
|
|
Files:
|
|
- CREATE: frontend/src/hooks/useCanvasChat.ts
|
|
|
|
---
|
|
|
|
## P2.3 - Implement Intent Processing
|
|
Priority: HIGH
|
|
Estimated: 3-4 hours
|
|
|
|
Update context_builder.py to handle canvas intents
|
|
|
|
---
|
|
|
|
## P2.4 - Integration Testing
|
|
Priority: HIGH
|
|
Estimated: 2-3 hours
|
|
|
|
Tests:
|
|
1. Build graph → submit → Claude processes
|
|
2. Claude responses → canvas updates
|
|
3. Validation feedback loop
|
|
```
|
|
|
|
### Phase 3: Bidirectional Sync
|
|
|
|
```markdown
|
|
## P3.1 - Chat → Canvas Updates
|
|
Priority: MEDIUM
|
|
Estimated: 3-4 hours
|
|
|
|
When Claude mentions creating/modifying study, update canvas
|
|
|
|
---
|
|
|
|
## P3.2 - Interview Mode → Canvas Population
|
|
Priority: MEDIUM
|
|
Estimated: 3-4 hours
|
|
|
|
As interview progresses, build graph visually
|
|
|
|
---
|
|
|
|
## P3.3 - Config Import
|
|
Priority: MEDIUM
|
|
Estimated: 2-3 hours
|
|
|
|
Load existing optimization_config.json into canvas
|
|
|
|
---
|
|
|
|
## P3.4 - Real-time Sync Testing
|
|
Priority: HIGH
|
|
Estimated: 2-3 hours
|
|
```
|
|
|
|
### Phase 4: Templates & Polish
|
|
|
|
```markdown
|
|
## P4.1 - Create Template Library
|
|
Priority: LOW
|
|
Estimated: 3-4 hours
|
|
|
|
Templates:
|
|
- Mass minimization
|
|
- Multi-objective
|
|
- Turbo mode
|
|
- Mirror Zernike
|
|
|
|
---
|
|
|
|
## P4.2 - Add Template Selector
|
|
Priority: LOW
|
|
Estimated: 2 hours
|
|
|
|
---
|
|
|
|
## P4.3 - Polish & Animations
|
|
Priority: LOW
|
|
Estimated: 2-3 hours
|
|
|
|
---
|
|
|
|
## P4.4 - Documentation
|
|
Priority: MEDIUM
|
|
Estimated: 2-3 hours
|
|
```
|
|
|
|
---
|
|
|
|
## 20. Appendices
|
|
|
|
### A. MCP Configuration File
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"atomizer": {
|
|
"command": "node",
|
|
"args": ["C:/Users/antoi/Atomizer/mcp-server/atomizer-tools/dist/index.js"],
|
|
"env": {
|
|
"ATOMIZER_MODE": "user",
|
|
"ATOMIZER_ROOT": "C:/Users/antoi/Atomizer"
|
|
}
|
|
},
|
|
"siemens-docs": {
|
|
"command": "node",
|
|
"args": ["C:/Users/antoi/Atomizer/mcp-server/dist/index.js"]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### B. Physics to Extractor Mapping
|
|
|
|
| Physics | Primary Extractor | ID | Notes |
|
|
|---------|-------------------|-----|-------|
|
|
| mass | extract_mass_from_bdf | E4 | Default |
|
|
| mass (CAD) | extract_mass_from_expression | E5 | When BDF unavailable |
|
|
| stress | extract_solid_stress | E3 | Von Mises |
|
|
| displacement | extract_displacement | E1 | Nodal |
|
|
| frequency | extract_frequency | E2 | Modal |
|
|
| zernike_wfe | extract_zernike_wfe_standard | E8 | Standard |
|
|
| zernike_wfe_rel | extract_zernike_wfe_relative | E9 | Relative |
|
|
| buckling | extract_buckling | E23 | SOL 105 |
|
|
| temperature | extract_temperature | E15 | Thermal |
|
|
|
|
### C. Intent Schema (Complete)
|
|
|
|
See Section 2.3 for full schema definition.
|
|
|
|
### D. Error Codes
|
|
|
|
| Code | Category | Description |
|
|
|------|----------|-------------|
|
|
| E001 | Session | Session not found |
|
|
| E002 | Session | Session expired |
|
|
| E003 | Auth | Invalid mode switch |
|
|
| E010 | Study | Study not found |
|
|
| E011 | Study | Invalid configuration |
|
|
| E012 | Study | Missing required files |
|
|
| E020 | Opt | Already running |
|
|
| E021 | Opt | Failed to start |
|
|
| E022 | Opt | Solver error |
|
|
| E030 | Canvas | Invalid graph |
|
|
| E031 | Canvas | Missing required node |
|
|
| E032 | Canvas | Invalid connection |
|
|
|
|
---
|
|
|
|
## Document Information
|
|
|
|
**Total Estimated Hours:** 80-100 hours
|
|
**Recommended Sprint Duration:** 2-3 weeks
|
|
**Critical Path:** P0.1 → P0.8 → P0.9 → P0.10 → P0.11 → P1.1-P1.9 → P2.1-P2.4
|
|
|
|
**For Ralph Loop Execution:**
|
|
1. Read this document fully before starting
|
|
2. Execute phases in order
|
|
3. Test after each phase
|
|
4. Commit after each phase completes
|
|
5. Record any issues to LAC
|
|
|
|
---
|
|
|
|
*Document Version: 1.0*
|
|
*Last Updated: January 8, 2026*
|
|
*Prepared for: Ralph Loop Autonomous Development*
|