feat: Add Analysis page, run comparison, notifications, and config editor
Dashboard enhancements:
- Add Analysis page with tabs: Overview, Parameters, Pareto, Correlations, Constraints, Surrogate, Runs
- Add PlotlyCorrelationHeatmap for parameter-objective correlation analysis
- Add PlotlyFeasibilityChart for constraint satisfaction visualization
- Add PlotlySurrogateQuality for FEA vs NN prediction comparison
- Add PlotlyRunComparison for comparing optimization runs within a study
Real-time improvements:
- Replace watchdog file-watching with SQLite database polling for better Windows reliability
- Add DatabasePoller class with 2-second polling interval
- Enhanced WebSocket messages: trial_completed, new_best, pareto_update, progress
Desktop notifications:
- Add useNotifications hook using Web Notifications API
- Add NotificationSettings toggle component
- Notify users when new best solutions are found
Config editor:
- Add PUT /studies/{study_id}/config endpoint with auto-backup
- Add ConfigEditor modal with tabs: General, Variables, Objectives, Settings, JSON
- Prevents editing while optimization is running
Enhanced Pareto visualization:
- Add dark mode styling with transparent backgrounds
- Add stats bar showing Pareto, FEA, NN, and infeasible counts
- Add Pareto front connecting line for 2D view
- Add table showing top 10 Pareto-optimal solutions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,82 @@ router = APIRouter()
|
||||
# Store active terminal sessions
|
||||
_terminal_sessions: dict = {}
|
||||
|
||||
# Path to Atomizer root (for loading prompts)
|
||||
ATOMIZER_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
)))
|
||||
|
||||
|
||||
def get_session_prompt(study_name: str = None) -> str:
|
||||
"""
|
||||
Generate the initial prompt for a Claude session.
|
||||
|
||||
This injects the Protocol Operating System context and study-specific info.
|
||||
"""
|
||||
prompt_lines = [
|
||||
"# Atomizer Session Context",
|
||||
"",
|
||||
"You are assisting with **Atomizer** - an LLM-first FEA optimization framework.",
|
||||
"",
|
||||
"## Bootstrap (READ FIRST)",
|
||||
"",
|
||||
"Read these files to understand how to help:",
|
||||
"- `.claude/skills/00_BOOTSTRAP.md` - Task classification and routing",
|
||||
"- `.claude/skills/01_CHEATSHEET.md` - Quick reference (I want X → Use Y)",
|
||||
"- `.claude/skills/02_CONTEXT_LOADER.md` - What to load per task",
|
||||
"",
|
||||
"## Protocol System",
|
||||
"",
|
||||
"| Layer | Location | Purpose |",
|
||||
"|-------|----------|---------|",
|
||||
"| Operations | `docs/protocols/operations/OP_*.md` | How-to guides |",
|
||||
"| System | `docs/protocols/system/SYS_*.md` | Core specs |",
|
||||
"| Extensions | `docs/protocols/extensions/EXT_*.md` | Adding features |",
|
||||
"",
|
||||
]
|
||||
|
||||
if study_name:
|
||||
prompt_lines.extend([
|
||||
f"## Current Study: `{study_name}`",
|
||||
"",
|
||||
f"**Directory**: `studies/{study_name}/`",
|
||||
"",
|
||||
"Key files:",
|
||||
f"- `studies/{study_name}/1_setup/optimization_config.json` - Configuration",
|
||||
f"- `studies/{study_name}/2_results/study.db` - Optuna database",
|
||||
f"- `studies/{study_name}/README.md` - Study documentation",
|
||||
"",
|
||||
"Quick status check:",
|
||||
"```bash",
|
||||
f"python -c \"import optuna; s=optuna.load_study('{study_name}', 'sqlite:///studies/{study_name}/2_results/study.db'); print(f'Trials: {{len(s.trials)}}, Best: {{s.best_value}}')\"",
|
||||
"```",
|
||||
"",
|
||||
])
|
||||
else:
|
||||
prompt_lines.extend([
|
||||
"## No Study Selected",
|
||||
"",
|
||||
"No specific study context. You can:",
|
||||
"- List studies: `ls studies/`",
|
||||
"- Create new study: Ask user what they want to optimize",
|
||||
"- Load context: Read `.claude/skills/core/study-creation-core.md`",
|
||||
"",
|
||||
])
|
||||
|
||||
prompt_lines.extend([
|
||||
"## Key Principles",
|
||||
"",
|
||||
"1. **Read bootstrap first** - Follow task routing from 00_BOOTSTRAP.md",
|
||||
"2. **Use centralized extractors** - Check `optimization_engine/extractors/`",
|
||||
"3. **Never modify master models** - Work on copies",
|
||||
"4. **Python env**: Always use `conda activate atomizer`",
|
||||
"",
|
||||
"---",
|
||||
"*Session launched from Atomizer Dashboard*",
|
||||
])
|
||||
|
||||
return "\n".join(prompt_lines)
|
||||
|
||||
# Check if winpty is available (for Windows)
|
||||
try:
|
||||
from winpty import PtyProcess
|
||||
@@ -44,9 +120,6 @@ class TerminalSession:
|
||||
self.websocket = websocket
|
||||
self._running = True
|
||||
|
||||
# Determine the claude command
|
||||
claude_cmd = "claude"
|
||||
|
||||
try:
|
||||
if self._use_winpty:
|
||||
# Use winpty for proper PTY on Windows
|
||||
@@ -306,14 +379,13 @@ async def claude_terminal(websocket: WebSocket, working_dir: str = None, study_i
|
||||
{"type": "output", "data": "terminal output"}
|
||||
{"type": "exit", "code": 0}
|
||||
{"type": "error", "message": "..."}
|
||||
{"type": "context", "prompt": "..."} # Initial context prompt
|
||||
"""
|
||||
await websocket.accept()
|
||||
|
||||
# Default to Atomizer root directory
|
||||
if not working_dir:
|
||||
working_dir = str(os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
))))
|
||||
working_dir = ATOMIZER_ROOT
|
||||
|
||||
# Create session
|
||||
session_id = f"claude-{id(websocket)}"
|
||||
@@ -321,13 +393,24 @@ async def claude_terminal(websocket: WebSocket, working_dir: str = None, study_i
|
||||
_terminal_sessions[session_id] = session
|
||||
|
||||
try:
|
||||
# Send context prompt to frontend (for display/reference)
|
||||
context_prompt = get_session_prompt(study_id)
|
||||
await websocket.send_json({
|
||||
"type": "context",
|
||||
"prompt": context_prompt,
|
||||
"study_id": study_id
|
||||
})
|
||||
|
||||
# Start Claude Code
|
||||
await session.start(websocket)
|
||||
|
||||
# Note: Claude is started in Atomizer root directory so it has access to:
|
||||
# - CLAUDE.md (system instructions)
|
||||
# - .claude/skills/ (skill definitions)
|
||||
# The study_id is available for the user to reference in their prompts
|
||||
# If study_id provided, send initial context to Claude after startup
|
||||
if study_id:
|
||||
# Wait a moment for Claude to initialize
|
||||
await asyncio.sleep(1.0)
|
||||
# Send the context as the first message
|
||||
initial_message = f"I'm working with the Atomizer study '{study_id}'. Please read .claude/skills/00_BOOTSTRAP.md first to understand the Protocol Operating System, then help me with this study.\n"
|
||||
await session.write(initial_message)
|
||||
|
||||
# Handle incoming messages
|
||||
while session._running:
|
||||
@@ -370,3 +453,31 @@ async def terminal_status():
|
||||
"winpty_available": HAS_WINPTY,
|
||||
"message": "Claude Code CLI is available" if claude_path else "Claude Code CLI not found. Install with: npm install -g @anthropic-ai/claude-code"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/context")
|
||||
async def get_context(study_id: str = None):
|
||||
"""
|
||||
Get the context prompt for a Claude session without starting a terminal.
|
||||
|
||||
Useful for displaying context in the UI or preparing prompts.
|
||||
|
||||
Query params:
|
||||
study_id: Optional study ID to include study-specific context
|
||||
"""
|
||||
prompt = get_session_prompt(study_id)
|
||||
|
||||
return {
|
||||
"study_id": study_id,
|
||||
"prompt": prompt,
|
||||
"bootstrap_files": [
|
||||
".claude/skills/00_BOOTSTRAP.md",
|
||||
".claude/skills/01_CHEATSHEET.md",
|
||||
".claude/skills/02_CONTEXT_LOADER.md",
|
||||
],
|
||||
"study_files": [
|
||||
f"studies/{study_id}/1_setup/optimization_config.json",
|
||||
f"studies/{study_id}/2_results/study.db",
|
||||
f"studies/{study_id}/README.md",
|
||||
] if study_id else []
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user