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:
Antoine
2025-12-05 19:57:20 -05:00
parent 5c660ff270
commit 5fb94fdf01
27 changed files with 5878 additions and 722 deletions

View File

@@ -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 []
}