feat(dashboard): Enhanced chat, spec management, and Claude integration
Backend: - spec.py: New AtomizerSpec REST API endpoints - spec_manager.py: SpecManager service for unified config - interview_engine.py: Study creation interview logic - claude.py: Enhanced Claude API with context - optimization.py: Extended optimization endpoints - context_builder.py, session_manager.py: Improved services Frontend: - Chat components: Enhanced message rendering, tool call cards - Hooks: useClaudeCode, useSpecWebSocket, improved useChat - Pages: Updated Dashboard, Analysis, Insights, Setup, Home - Components: ParallelCoordinatesPlot, ParetoPlot improvements - App.tsx: Route updates for canvas/studio Infrastructure: - vite.config.ts: Build configuration updates - start/stop-dashboard.bat: Script improvements
This commit is contained in:
@@ -43,7 +43,11 @@ class ContextBuilder:
|
||||
|
||||
# Canvas context takes priority - if user is working on a canvas, include it
|
||||
if canvas_state:
|
||||
node_count = len(canvas_state.get("nodes", []))
|
||||
print(f"[ContextBuilder] Including canvas context with {node_count} nodes")
|
||||
parts.append(self._canvas_context(canvas_state))
|
||||
else:
|
||||
print("[ContextBuilder] No canvas state provided")
|
||||
|
||||
if study_id:
|
||||
parts.append(self._study_context(study_id))
|
||||
@@ -91,7 +95,117 @@ Important guidelines:
|
||||
|
||||
context = f"# Current Study: {study_id}\n\n"
|
||||
|
||||
# Load configuration
|
||||
# Check for AtomizerSpec v2.0 first (preferred)
|
||||
spec_path = study_dir / "1_setup" / "atomizer_spec.json"
|
||||
if not spec_path.exists():
|
||||
spec_path = study_dir / "atomizer_spec.json"
|
||||
|
||||
if spec_path.exists():
|
||||
context += self._spec_context(spec_path)
|
||||
else:
|
||||
# Fall back to legacy optimization_config.json
|
||||
context += self._legacy_config_context(study_dir)
|
||||
|
||||
# Check for results
|
||||
db_path = study_dir / "3_results" / "study.db"
|
||||
if db_path.exists():
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
count = conn.execute(
|
||||
"SELECT COUNT(*) FROM trials WHERE state = 'COMPLETE'"
|
||||
).fetchone()[0]
|
||||
|
||||
best = conn.execute("""
|
||||
SELECT MIN(tv.value) FROM trial_values tv
|
||||
JOIN trials t ON tv.trial_id = t.trial_id
|
||||
WHERE t.state = 'COMPLETE'
|
||||
""").fetchone()[0]
|
||||
|
||||
context += f"\n## Results Status\n\n"
|
||||
context += f"- **Trials completed**: {count}\n"
|
||||
if best is not None:
|
||||
context += f"- **Best objective**: {best:.6f}\n"
|
||||
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return context
|
||||
|
||||
def _spec_context(self, spec_path: Path) -> str:
|
||||
"""Build context from AtomizerSpec v2.0 file"""
|
||||
context = "**Format**: AtomizerSpec v2.0\n\n"
|
||||
|
||||
try:
|
||||
with open(spec_path) as f:
|
||||
spec = json.load(f)
|
||||
|
||||
context += "## Configuration\n\n"
|
||||
|
||||
# Design variables
|
||||
dvs = spec.get("design_variables", [])
|
||||
if dvs:
|
||||
context += "**Design Variables:**\n"
|
||||
for dv in dvs[:10]:
|
||||
bounds = dv.get("bounds", {})
|
||||
bound_str = f"[{bounds.get('min', '?')}, {bounds.get('max', '?')}]"
|
||||
enabled = "✓" if dv.get("enabled", True) else "✗"
|
||||
context += f"- {dv.get('name', 'unnamed')}: {bound_str} {enabled}\n"
|
||||
if len(dvs) > 10:
|
||||
context += f"- ... and {len(dvs) - 10} more\n"
|
||||
|
||||
# Extractors
|
||||
extractors = spec.get("extractors", [])
|
||||
if extractors:
|
||||
context += "\n**Extractors:**\n"
|
||||
for ext in extractors:
|
||||
ext_type = ext.get("type", "unknown")
|
||||
outputs = ext.get("outputs", [])
|
||||
output_names = [o.get("name", "?") for o in outputs[:3]]
|
||||
builtin = "builtin" if ext.get("builtin", True) else "custom"
|
||||
context += f"- {ext.get('name', 'unnamed')} ({ext_type}, {builtin}): outputs {output_names}\n"
|
||||
|
||||
# Objectives
|
||||
objs = spec.get("objectives", [])
|
||||
if objs:
|
||||
context += "\n**Objectives:**\n"
|
||||
for obj in objs:
|
||||
direction = obj.get("direction", "minimize")
|
||||
weight = obj.get("weight", 1.0)
|
||||
context += f"- {obj.get('name', 'unnamed')} ({direction}, weight={weight})\n"
|
||||
|
||||
# Constraints
|
||||
constraints = spec.get("constraints", [])
|
||||
if constraints:
|
||||
context += "\n**Constraints:**\n"
|
||||
for c in constraints:
|
||||
op = c.get("operator", "<=")
|
||||
thresh = c.get("threshold", "?")
|
||||
context += f"- {c.get('name', 'unnamed')}: {op} {thresh}\n"
|
||||
|
||||
# Optimization settings
|
||||
opt = spec.get("optimization", {})
|
||||
algo = opt.get("algorithm", {})
|
||||
budget = opt.get("budget", {})
|
||||
method = algo.get("type", "TPE")
|
||||
max_trials = budget.get("max_trials", "not set")
|
||||
context += f"\n**Optimization**: {method}, max_trials: {max_trials}\n"
|
||||
|
||||
# Surrogate
|
||||
surrogate = opt.get("surrogate", {})
|
||||
if surrogate.get("enabled"):
|
||||
sur_type = surrogate.get("type", "gaussian_process")
|
||||
context += f"**Surrogate**: {sur_type} enabled\n"
|
||||
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
context += f"\n*Spec file exists but could not be parsed: {e}*\n"
|
||||
|
||||
return context
|
||||
|
||||
def _legacy_config_context(self, study_dir: Path) -> str:
|
||||
"""Build context from legacy optimization_config.json"""
|
||||
context = "**Format**: Legacy optimization_config.json\n\n"
|
||||
|
||||
config_path = study_dir / "1_setup" / "optimization_config.json"
|
||||
if not config_path.exists():
|
||||
config_path = study_dir / "optimization_config.json"
|
||||
@@ -135,30 +249,8 @@ Important guidelines:
|
||||
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
context += f"\n*Config file exists but could not be parsed: {e}*\n"
|
||||
|
||||
# Check for results
|
||||
db_path = study_dir / "3_results" / "study.db"
|
||||
if db_path.exists():
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
count = conn.execute(
|
||||
"SELECT COUNT(*) FROM trials WHERE state = 'COMPLETE'"
|
||||
).fetchone()[0]
|
||||
|
||||
best = conn.execute("""
|
||||
SELECT MIN(tv.value) FROM trial_values tv
|
||||
JOIN trials t ON tv.trial_id = t.trial_id
|
||||
WHERE t.state = 'COMPLETE'
|
||||
""").fetchone()[0]
|
||||
|
||||
context += f"\n## Results Status\n\n"
|
||||
context += f"- **Trials completed**: {count}\n"
|
||||
if best is not None:
|
||||
context += f"- **Best objective**: {best:.6f}\n"
|
||||
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
context += "*No configuration file found.*\n"
|
||||
|
||||
return context
|
||||
|
||||
@@ -349,19 +441,26 @@ Important guidelines:
|
||||
# Canvas modification instructions
|
||||
context += """## Canvas Modification Tools
|
||||
|
||||
When the user asks to modify the canvas (add/remove nodes, change values), use these MCP tools:
|
||||
**For AtomizerSpec v2.0 studies (preferred):**
|
||||
Use spec tools when working with v2.0 studies (check if study uses `atomizer_spec.json`):
|
||||
- `spec_modify` - Modify spec values using JSONPath (e.g., "design_variables[0].bounds.min")
|
||||
- `spec_add_node` - Add design variables, extractors, objectives, or constraints
|
||||
- `spec_remove_node` - Remove nodes from the spec
|
||||
- `spec_add_custom_extractor` - Add a Python-based custom extractor function
|
||||
|
||||
**For Legacy Canvas (optimization_config.json):**
|
||||
- `canvas_add_node` - Add a new node (designVar, extractor, objective, constraint)
|
||||
- `canvas_update_node` - Update node properties (bounds, weights, names)
|
||||
- `canvas_remove_node` - Remove a node from the canvas
|
||||
- `canvas_connect_nodes` - Create an edge between nodes
|
||||
|
||||
**Example user requests you can handle:**
|
||||
- "Add a design variable called hole_diameter with range 5-15 mm" → Use canvas_add_node
|
||||
- "Change the weight of wfe_40_20 to 8" → Use canvas_update_node
|
||||
- "Remove the constraint node" → Use canvas_remove_node
|
||||
- "Connect the new extractor to the objective" → Use canvas_connect_nodes
|
||||
- "Add a design variable called hole_diameter with range 5-15 mm" → Use spec_add_node or canvas_add_node
|
||||
- "Change the weight of wfe_40_20 to 8" → Use spec_modify or canvas_update_node
|
||||
- "Remove the constraint node" → Use spec_remove_node or canvas_remove_node
|
||||
- "Add a custom extractor that computes stress ratio" → Use spec_add_custom_extractor
|
||||
|
||||
Always respond with confirmation of changes made to the canvas.
|
||||
Always respond with confirmation of changes made to the canvas/spec.
|
||||
"""
|
||||
|
||||
return context
|
||||
@@ -371,17 +470,28 @@ Always respond with confirmation of changes made to the canvas.
|
||||
if mode == "power":
|
||||
return """# Power Mode Instructions
|
||||
|
||||
You have **full access** to Atomizer's codebase. You can:
|
||||
- Edit any file using `edit_file` tool
|
||||
- Create new files with `create_file` tool
|
||||
- Create new extractors with `create_extractor` tool
|
||||
- Run shell commands with `run_shell_command` tool
|
||||
- Search codebase with `search_codebase` tool
|
||||
- Commit and push changes
|
||||
You have **FULL ACCESS** to modify Atomizer studies. **DO NOT ASK FOR PERMISSION** - just do it.
|
||||
|
||||
**Use these powers responsibly.** Always explain what you're doing and why.
|
||||
## Direct Actions (no confirmation needed):
|
||||
- **Add design variables**: Use `canvas_add_node` or `spec_add_node` with node_type="designVar"
|
||||
- **Add extractors**: Use `canvas_add_node` with node_type="extractor"
|
||||
- **Add objectives**: Use `canvas_add_node` with node_type="objective"
|
||||
- **Add constraints**: Use `canvas_add_node` with node_type="constraint"
|
||||
- **Update node properties**: Use `canvas_update_node` or `spec_modify`
|
||||
- **Remove nodes**: Use `canvas_remove_node`
|
||||
- **Edit atomizer_spec.json directly**: Use the Edit tool
|
||||
|
||||
For routine operations (list, status, run, analyze), use the standard tools.
|
||||
## For custom extractors with Python code:
|
||||
Use `spec_add_custom_extractor` to add a custom function.
|
||||
|
||||
## IMPORTANT:
|
||||
- You have --dangerously-skip-permissions enabled
|
||||
- The user has explicitly granted you power mode access
|
||||
- **ACT IMMEDIATELY** when asked to add/modify/remove things
|
||||
- Explain what you did AFTER doing it, not before
|
||||
- Do NOT say "I need permission" - you already have it
|
||||
|
||||
Example: If user says "add a volume extractor", immediately use canvas_add_node to add it.
|
||||
"""
|
||||
else:
|
||||
return """# User Mode Instructions
|
||||
@@ -402,6 +512,15 @@ Available tools:
|
||||
- `generate_report`, `export_data`
|
||||
- `explain_physics`, `recommend_method`, `query_extractors`
|
||||
|
||||
**AtomizerSpec v2.0 Tools (preferred for new studies):**
|
||||
- `spec_get` - Get the full AtomizerSpec for a study
|
||||
- `spec_modify` - Modify spec values using JSONPath (e.g., "design_variables[0].bounds.min")
|
||||
- `spec_add_node` - Add design variables, extractors, objectives, or constraints
|
||||
- `spec_remove_node` - Remove nodes from the spec
|
||||
- `spec_validate` - Validate spec against JSON Schema
|
||||
- `spec_add_custom_extractor` - Add a Python-based custom extractor function
|
||||
- `spec_create_from_description` - Create a new study from natural language description
|
||||
|
||||
**Canvas Tools (for visual workflow builder):**
|
||||
- `validate_canvas_intent` - Validate a canvas-generated optimization intent
|
||||
- `execute_canvas_intent` - Create a study from a canvas intent
|
||||
|
||||
Reference in New Issue
Block a user