feat: Improve dashboard layout and Claude terminal context
- Reorganize dashboard: control panel on top, charts stacked vertically - Add Set Context button to Claude terminal for study awareness - Add conda environment instructions to CLAUDE.md - Fix STUDY_REPORT.md location in generate-report.md skill - Claude terminal now sends study context with skills reminder 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -394,7 +394,7 @@ Would you like me to:
|
|||||||
|
|
||||||
When a STUDY_REPORT.md file is generated, it can be viewed directly in the Atomizer Dashboard:
|
When a STUDY_REPORT.md file is generated, it can be viewed directly in the Atomizer Dashboard:
|
||||||
|
|
||||||
1. **Save report to**: `studies/{study_name}/2_results/STUDY_REPORT.md`
|
1. **Save report to**: `studies/{study_name}/STUDY_REPORT.md` (study root folder)
|
||||||
2. **View in dashboard**: Click "Study Report" button on the dashboard
|
2. **View in dashboard**: Click "Study Report" button on the dashboard
|
||||||
3. **Features**:
|
3. **Features**:
|
||||||
- Full markdown rendering with proper typography
|
- Full markdown rendering with proper typography
|
||||||
|
|||||||
20
CLAUDE.md
20
CLAUDE.md
@@ -51,6 +51,26 @@ When optimization needs >50 trials:
|
|||||||
- Suggest fixes
|
- Suggest fixes
|
||||||
- Recover from failures
|
- Recover from failures
|
||||||
|
|
||||||
|
## Python Environment
|
||||||
|
|
||||||
|
**CRITICAL: Always use the `atomizer` conda environment.** All dependencies are pre-installed.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Activate before ANY Python command
|
||||||
|
conda activate atomizer
|
||||||
|
|
||||||
|
# Then run scripts
|
||||||
|
python run_optimization.py --start
|
||||||
|
python -m optimization_engine.runner ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**DO NOT:**
|
||||||
|
- Install packages with pip/conda (everything is already installed)
|
||||||
|
- Create new virtual environments
|
||||||
|
- Use system Python
|
||||||
|
|
||||||
|
**Pre-installed packages include:** optuna, numpy, scipy, pandas, matplotlib, pyNastran, torch, plotly, and all Atomizer dependencies.
|
||||||
|
|
||||||
## Key Files & Locations
|
## Key Files & Locations
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import {
|
|||||||
Minimize2,
|
Minimize2,
|
||||||
X,
|
X,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
AlertCircle
|
AlertCircle,
|
||||||
|
FolderOpen
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useStudy } from '../context/StudyContext';
|
import { useStudy } from '../context/StudyContext';
|
||||||
|
|
||||||
@@ -33,6 +34,8 @@ export const ClaudeTerminal: React.FC<ClaudeTerminalProps> = ({
|
|||||||
const [isConnecting, setIsConnecting] = useState(false);
|
const [isConnecting, setIsConnecting] = useState(false);
|
||||||
const [_error, setError] = useState<string | null>(null);
|
const [_error, setError] = useState<string | null>(null);
|
||||||
const [cliAvailable, setCliAvailable] = useState<boolean | null>(null);
|
const [cliAvailable, setCliAvailable] = useState<boolean | null>(null);
|
||||||
|
const [contextSet, setContextSet] = useState(false);
|
||||||
|
const [settingContext, setSettingContext] = useState(false);
|
||||||
|
|
||||||
// Check CLI availability
|
// Check CLI availability
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -165,8 +168,7 @@ export const ClaudeTerminal: React.FC<ClaudeTerminalProps> = ({
|
|||||||
xtermRef.current?.clear();
|
xtermRef.current?.clear();
|
||||||
xtermRef.current?.writeln('\x1b[1;32mConnected to Claude Code\x1b[0m');
|
xtermRef.current?.writeln('\x1b[1;32mConnected to Claude Code\x1b[0m');
|
||||||
if (selectedStudy?.id) {
|
if (selectedStudy?.id) {
|
||||||
xtermRef.current?.writeln(`\x1b[90mStudy context: \x1b[1;33m${selectedStudy.id}\x1b[0m`);
|
xtermRef.current?.writeln(`\x1b[90mStudy: \x1b[1;33m${selectedStudy.id}\x1b[0m \x1b[90m- Click "Set Context" to initialize\x1b[0m`);
|
||||||
xtermRef.current?.writeln('\x1b[90mTip: Tell Claude about your study, e.g. "Help me with study ' + selectedStudy.id + '"\x1b[0m');
|
|
||||||
}
|
}
|
||||||
xtermRef.current?.writeln('');
|
xtermRef.current?.writeln('');
|
||||||
|
|
||||||
@@ -241,8 +243,26 @@ export const ClaudeTerminal: React.FC<ClaudeTerminalProps> = ({
|
|||||||
wsRef.current = null;
|
wsRef.current = null;
|
||||||
}
|
}
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
|
setContextSet(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Set study context - sends context message to Claude silently
|
||||||
|
const setStudyContext = useCallback(() => {
|
||||||
|
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN || !selectedStudy?.id) return;
|
||||||
|
|
||||||
|
setSettingContext(true);
|
||||||
|
// Send context message - Claude should use CLAUDE.md and .claude/skills/ for guidance
|
||||||
|
const contextMessage = `Context: Working on study "${selectedStudy.id}" at studies/${selectedStudy.id}/. ` +
|
||||||
|
`Read .claude/skills/ for task protocols. Use atomizer conda env. Acknowledge briefly.`;
|
||||||
|
wsRef.current.send(JSON.stringify({ type: 'input', data: contextMessage + '\n' }));
|
||||||
|
|
||||||
|
// Mark as done after Claude has had time to process
|
||||||
|
setTimeout(() => {
|
||||||
|
setSettingContext(false);
|
||||||
|
setContextSet(true);
|
||||||
|
}, 500);
|
||||||
|
}, [selectedStudy]);
|
||||||
|
|
||||||
// Cleanup on unmount
|
// Cleanup on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -293,6 +313,35 @@ export const ClaudeTerminal: React.FC<ClaudeTerminalProps> = ({
|
|||||||
{isConnected ? 'Disconnect' : 'Connect'}
|
{isConnected ? 'Disconnect' : 'Connect'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* Set Context button - always show, with different states */}
|
||||||
|
<button
|
||||||
|
onClick={setStudyContext}
|
||||||
|
disabled={!selectedStudy?.id || !isConnected || settingContext || contextSet}
|
||||||
|
className={`px-3 py-1.5 text-sm rounded-lg transition-colors flex items-center gap-2 ${
|
||||||
|
contextSet
|
||||||
|
? 'bg-green-600/20 text-green-400'
|
||||||
|
: !selectedStudy?.id || !isConnected
|
||||||
|
? 'bg-dark-600 text-dark-400'
|
||||||
|
: 'bg-primary-600/20 text-primary-400 hover:bg-primary-600/30'
|
||||||
|
} disabled:opacity-50 disabled:cursor-not-allowed`}
|
||||||
|
title={
|
||||||
|
!selectedStudy?.id
|
||||||
|
? 'No study selected - select a study from Home page'
|
||||||
|
: !isConnected
|
||||||
|
? 'Connect first to set study context'
|
||||||
|
: contextSet
|
||||||
|
? 'Context already set'
|
||||||
|
: `Set context to study: ${selectedStudy.id}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{settingContext ? (
|
||||||
|
<RefreshCw className="w-3 h-3 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<FolderOpen className="w-3 h-3" />
|
||||||
|
)}
|
||||||
|
{contextSet ? 'Context Set' : selectedStudy?.id ? 'Set Context' : 'No Study'}
|
||||||
|
</button>
|
||||||
|
|
||||||
{onToggleExpand && (
|
{onToggleExpand && (
|
||||||
<button
|
<button
|
||||||
onClick={onToggleExpand}
|
onClick={onToggleExpand}
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ import { useStudy } from '../../context/StudyContext';
|
|||||||
|
|
||||||
interface ControlPanelProps {
|
interface ControlPanelProps {
|
||||||
onStatusChange?: () => void;
|
onStatusChange?: () => void;
|
||||||
|
horizontal?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ControlPanel: React.FC<ControlPanelProps> = ({ onStatusChange }) => {
|
export const ControlPanel: React.FC<ControlPanelProps> = ({ onStatusChange, horizontal = false }) => {
|
||||||
const { selectedStudy, refreshStudies } = useStudy();
|
const { selectedStudy, refreshStudies } = useStudy();
|
||||||
const [processStatus, setProcessStatus] = useState<ProcessStatus | null>(null);
|
const [processStatus, setProcessStatus] = useState<ProcessStatus | null>(null);
|
||||||
const [actionInProgress, setActionInProgress] = useState<string | null>(null);
|
const [actionInProgress, setActionInProgress] = useState<string | null>(null);
|
||||||
@@ -131,6 +132,177 @@ export const ControlPanel: React.FC<ControlPanelProps> = ({ onStatusChange }) =>
|
|||||||
|
|
||||||
const isRunning = processStatus?.is_running || selectedStudy?.status === 'running';
|
const isRunning = processStatus?.is_running || selectedStudy?.status === 'running';
|
||||||
|
|
||||||
|
// Horizontal layout for top of page
|
||||||
|
if (horizontal) {
|
||||||
|
return (
|
||||||
|
<div className="bg-dark-800 rounded-xl border border-dark-600 overflow-hidden">
|
||||||
|
<div className="px-4 py-3 flex items-center gap-6">
|
||||||
|
{/* Status */}
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{isRunning ? (
|
||||||
|
<>
|
||||||
|
<div className="w-3 h-3 bg-green-500 rounded-full animate-pulse" />
|
||||||
|
<span className="text-green-400 font-medium text-sm">Running</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="w-3 h-3 bg-dark-500 rounded-full" />
|
||||||
|
<span className="text-dark-400 text-sm">Stopped</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{processStatus?.fea_count && (
|
||||||
|
<span className="text-xs text-dark-400">
|
||||||
|
FEA: <span className="text-primary-400">{processStatus.fea_count}</span>
|
||||||
|
{processStatus.nn_count && (
|
||||||
|
<> | NN: <span className="text-orange-400">{processStatus.nn_count}</span></>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Actions */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{isRunning ? (
|
||||||
|
<button
|
||||||
|
onClick={handleStop}
|
||||||
|
disabled={actionInProgress !== null}
|
||||||
|
className="flex items-center gap-2 px-3 py-1.5 bg-red-600 hover:bg-red-500
|
||||||
|
disabled:opacity-50 text-white rounded-lg text-sm font-medium"
|
||||||
|
>
|
||||||
|
{actionInProgress === 'stop' ? <Loader2 className="w-4 h-4 animate-spin" /> : <Skull className="w-4 h-4" />}
|
||||||
|
Kill
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={handleStart}
|
||||||
|
disabled={actionInProgress !== null}
|
||||||
|
className="flex items-center gap-2 px-3 py-1.5 bg-green-600 hover:bg-green-500
|
||||||
|
disabled:opacity-50 text-white rounded-lg text-sm font-medium"
|
||||||
|
>
|
||||||
|
{actionInProgress === 'start' ? <Loader2 className="w-4 h-4 animate-spin" /> : <Play className="w-4 h-4" />}
|
||||||
|
Start
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleValidate}
|
||||||
|
disabled={actionInProgress !== null || isRunning}
|
||||||
|
className="flex items-center gap-2 px-3 py-1.5 bg-primary-600 hover:bg-primary-500
|
||||||
|
disabled:opacity-50 text-white rounded-lg text-sm font-medium"
|
||||||
|
>
|
||||||
|
{actionInProgress === 'validate' ? <Loader2 className="w-4 h-4 animate-spin" /> : <CheckCircle className="w-4 h-4" />}
|
||||||
|
Validate
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleLaunchOptuna}
|
||||||
|
disabled={actionInProgress !== null}
|
||||||
|
className="flex items-center gap-2 px-3 py-1.5 bg-dark-700 hover:bg-dark-600
|
||||||
|
border border-dark-600 disabled:opacity-50 text-dark-300 hover:text-white rounded-lg text-sm"
|
||||||
|
>
|
||||||
|
<ExternalLink className="w-4 h-4" />
|
||||||
|
Optuna
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Settings Toggle */}
|
||||||
|
<button
|
||||||
|
onClick={() => setShowSettings(!showSettings)}
|
||||||
|
className={`p-1.5 rounded-lg transition-colors ${
|
||||||
|
showSettings ? 'bg-primary-600 text-white' : 'bg-dark-700 text-dark-300 hover:text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Settings className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Error */}
|
||||||
|
{error && (
|
||||||
|
<div className="flex items-center gap-2 text-red-400 text-sm">
|
||||||
|
<AlertTriangle className="w-4 h-4" />
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Collapsible Settings */}
|
||||||
|
{showSettings && (
|
||||||
|
<div className="px-4 py-3 border-t border-dark-700 bg-dark-750">
|
||||||
|
<div className="flex items-center gap-4 flex-wrap">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<label className="text-xs text-dark-400">Iterations:</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={settings.maxIterations}
|
||||||
|
onChange={(e) => setSettings({ ...settings, maxIterations: parseInt(e.target.value) || 100 })}
|
||||||
|
className="w-16 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<label className="text-xs text-dark-400">FEA Batch:</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={settings.feaBatchSize}
|
||||||
|
onChange={(e) => setSettings({ ...settings, feaBatchSize: parseInt(e.target.value) || 5 })}
|
||||||
|
className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<label className="text-xs text-dark-400">Patience:</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={settings.patience}
|
||||||
|
onChange={(e) => setSettings({ ...settings, patience: parseInt(e.target.value) || 5 })}
|
||||||
|
className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<label className="text-xs text-dark-400">Tune:</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={settings.tuneTrials}
|
||||||
|
onChange={(e) => setSettings({ ...settings, tuneTrials: parseInt(e.target.value) || 30 })}
|
||||||
|
className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<label className="text-xs text-dark-400">Ensemble:</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={settings.ensembleSize}
|
||||||
|
onChange={(e) => setSettings({ ...settings, ensembleSize: parseInt(e.target.value) || 3 })}
|
||||||
|
className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<label className="text-xs text-dark-400">Validate Top:</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={20}
|
||||||
|
value={validateTopN}
|
||||||
|
onChange={(e) => setValidateTopN(parseInt(e.target.value) || 5)}
|
||||||
|
className="w-12 px-2 py-1 bg-dark-700 border border-dark-600 rounded text-white text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={settings.freshStart}
|
||||||
|
onChange={(e) => setSettings({ ...settings, freshStart: e.target.checked })}
|
||||||
|
className="w-4 h-4 rounded border-dark-600 bg-dark-700 text-primary-600"
|
||||||
|
/>
|
||||||
|
<span className="text-xs text-dark-300">Fresh Start</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical layout (original sidebar layout)
|
||||||
return (
|
return (
|
||||||
<div className="bg-dark-800 rounded-xl border border-dark-600 overflow-hidden">
|
<div className="bg-dark-800 rounded-xl border border-dark-600 overflow-hidden">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import { useState, useEffect, lazy, Suspense } from 'react';
|
import { useState, useEffect, lazy, Suspense } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import {
|
|
||||||
LineChart, Line, ScatterChart, Scatter,
|
|
||||||
XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, Cell
|
|
||||||
} from 'recharts';
|
|
||||||
import { Terminal } from 'lucide-react';
|
import { Terminal } from 'lucide-react';
|
||||||
import { useOptimizationWebSocket } from '../hooks/useWebSocket';
|
import { useOptimizationWebSocket } from '../hooks/useWebSocket';
|
||||||
import { apiClient } from '../api/client';
|
import { apiClient } from '../api/client';
|
||||||
import { useStudy } from '../context/StudyContext';
|
import { useStudy } from '../context/StudyContext';
|
||||||
import { Card } from '../components/common/Card';
|
import { Card } from '../components/common/Card';
|
||||||
import { MetricCard } from '../components/dashboard/MetricCard';
|
|
||||||
import { ControlPanel } from '../components/dashboard/ControlPanel';
|
import { ControlPanel } from '../components/dashboard/ControlPanel';
|
||||||
import { ClaudeTerminal } from '../components/ClaudeTerminal';
|
import { ClaudeTerminal } from '../components/ClaudeTerminal';
|
||||||
import { ParetoPlot } from '../components/ParetoPlot';
|
import { ParetoPlot } from '../components/ParetoPlot';
|
||||||
@@ -19,7 +14,7 @@ import { ConvergencePlot } from '../components/ConvergencePlot';
|
|||||||
import { StudyReportViewer } from '../components/StudyReportViewer';
|
import { StudyReportViewer } from '../components/StudyReportViewer';
|
||||||
import { ConsoleOutput } from '../components/ConsoleOutput';
|
import { ConsoleOutput } from '../components/ConsoleOutput';
|
||||||
import { ExpandableChart } from '../components/ExpandableChart';
|
import { ExpandableChart } from '../components/ExpandableChart';
|
||||||
import type { Trial, ConvergenceDataPoint, ParameterSpaceDataPoint } from '../types';
|
import type { Trial } from '../types';
|
||||||
|
|
||||||
// Lazy load Plotly components for better initial load performance
|
// Lazy load Plotly components for better initial load performance
|
||||||
const PlotlyParallelCoordinates = lazy(() => import('../components/plotly/PlotlyParallelCoordinates').then(m => ({ default: m.PlotlyParallelCoordinates })));
|
const PlotlyParallelCoordinates = lazy(() => import('../components/plotly/PlotlyParallelCoordinates').then(m => ({ default: m.PlotlyParallelCoordinates })));
|
||||||
@@ -417,68 +412,62 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="grid grid-cols-12 gap-4">
|
{/* Control Panel - Full Width on Top */}
|
||||||
{/* Control Panel - Left Sidebar (smaller) */}
|
<div className="mb-6">
|
||||||
<aside className="col-span-2">
|
<ControlPanel onStatusChange={refreshStudies} horizontal />
|
||||||
<ControlPanel onStatusChange={refreshStudies} />
|
</div>
|
||||||
</aside>
|
|
||||||
|
|
||||||
{/* Main Content - takes most of the space */}
|
{/* Main Layout: Charts + Claude Terminal */}
|
||||||
<main className={chatOpen ? 'col-span-6' : 'col-span-10'}>
|
<div className={`grid gap-4 ${chatOpen ? 'grid-cols-12' : 'grid-cols-1'}`}>
|
||||||
{/* Study Name Header */}
|
{/* Main Content - Charts stacked vertically */}
|
||||||
{selectedStudyId && (
|
<main className={chatOpen ? 'col-span-7' : 'col-span-1'}>
|
||||||
<div className="mb-4 pb-3 border-b border-dark-600">
|
{/* Study Name Header + Metrics in one row */}
|
||||||
<h2 className="text-xl font-semibold text-primary-300">
|
<div className="mb-4 pb-3 border-b border-dark-600 flex items-center justify-between">
|
||||||
{selectedStudyId}
|
<div>
|
||||||
</h2>
|
{selectedStudyId && (
|
||||||
{studyMetadata?.description && (
|
<>
|
||||||
<p className="text-sm text-dark-400 mt-1">{studyMetadata.description}</p>
|
<h2 className="text-xl font-semibold text-primary-300">
|
||||||
|
{selectedStudyId}
|
||||||
|
</h2>
|
||||||
|
{studyMetadata?.description && (
|
||||||
|
<p className="text-sm text-dark-400 mt-1">{studyMetadata.description}</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
{/* Compact Metrics */}
|
||||||
|
<div className="flex gap-3">
|
||||||
{/* Metrics Grid */}
|
<div className="text-center px-3">
|
||||||
<div className="grid grid-cols-4 gap-4 mb-6">
|
<div className="text-2xl font-bold text-white">{allTrials.length}</div>
|
||||||
<MetricCard label="Total Trials" value={allTrials.length} />
|
<div className="text-xs text-dark-400">Trials</div>
|
||||||
<MetricCard
|
</div>
|
||||||
label="Best Value"
|
<div className="text-center px-3 border-l border-dark-600">
|
||||||
value={bestValue === Infinity ? '-' : bestValue.toFixed(4)}
|
<div className="text-2xl font-bold text-green-400">
|
||||||
valueColor="text-green-400"
|
{bestValue === Infinity ? '-' : bestValue.toFixed(4)}
|
||||||
/>
|
</div>
|
||||||
<MetricCard
|
<div className="text-xs text-dark-400">Best</div>
|
||||||
label="Avg Objective"
|
</div>
|
||||||
value={avgObjective > 0 ? avgObjective.toFixed(4) : '-'}
|
<div className="text-center px-3 border-l border-dark-600">
|
||||||
valueColor="text-blue-400"
|
<div className="text-2xl font-bold text-blue-400">
|
||||||
/>
|
{avgObjective > 0 ? avgObjective.toFixed(4) : '-'}
|
||||||
<MetricCard
|
</div>
|
||||||
label="Pruned"
|
<div className="text-xs text-dark-400">Avg</div>
|
||||||
value={prunedCount}
|
</div>
|
||||||
valueColor={prunedCount > 0 ? 'text-red-400' : 'text-green-400'}
|
<div className="text-center px-3 border-l border-dark-600">
|
||||||
/>
|
<div className={`text-2xl font-bold ${prunedCount > 0 ? 'text-red-400' : 'text-green-400'}`}>
|
||||||
|
{prunedCount}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-dark-400">Pruned</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Protocol 13: Intelligent Optimizer & Pareto Front */}
|
{/* Pareto Front - Full Width */}
|
||||||
{selectedStudyId && paretoFront.length > 0 && studyMetadata && studyMetadata.objectives && (
|
{selectedStudyId && paretoFront.length > 0 && studyMetadata && studyMetadata.objectives && (
|
||||||
<div className="grid grid-cols-2 gap-6 mb-6">
|
<div className="mb-4">
|
||||||
<Card title="Optimizer Strategy">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="text-sm text-dark-300">
|
|
||||||
<span className="font-semibold text-dark-100">Algorithm:</span> {studyMetadata.sampler || 'NSGA-II'}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-dark-300">
|
|
||||||
<span className="font-semibold text-dark-100">Type:</span> Multi-objective
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-dark-300">
|
|
||||||
<span className="font-semibold text-dark-100">Objectives:</span> {studyMetadata.objectives?.length || 2}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-dark-300">
|
|
||||||
<span className="font-semibold text-dark-100">Design Variables:</span> {studyMetadata.design_variables?.length || 0}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
<ExpandableChart
|
<ExpandableChart
|
||||||
title="Pareto Front"
|
title="Pareto Front"
|
||||||
subtitle={`${paretoFront.length} Pareto-optimal solutions`}
|
subtitle={`${paretoFront.length} Pareto-optimal solutions | ${studyMetadata.sampler || 'NSGA-II'} | ${studyMetadata.objectives?.length || 2} objectives`}
|
||||||
>
|
>
|
||||||
{chartLibrary === 'plotly' ? (
|
{chartLibrary === 'plotly' ? (
|
||||||
<Suspense fallback={<ChartLoading />}>
|
<Suspense fallback={<ChartLoading />}>
|
||||||
@@ -486,7 +475,7 @@ export default function Dashboard() {
|
|||||||
trials={allTrialsRaw}
|
trials={allTrialsRaw}
|
||||||
paretoFront={paretoFront}
|
paretoFront={paretoFront}
|
||||||
objectives={studyMetadata.objectives}
|
objectives={studyMetadata.objectives}
|
||||||
height={350}
|
height={300}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
) : (
|
) : (
|
||||||
@@ -500,11 +489,11 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Parallel Coordinates (full width for multi-objective) */}
|
{/* Parallel Coordinates - Full Width */}
|
||||||
{allTrialsRaw.length > 0 && studyMetadata && studyMetadata.objectives && studyMetadata.design_variables && (
|
{allTrialsRaw.length > 0 && studyMetadata && studyMetadata.objectives && studyMetadata.design_variables && (
|
||||||
<div className="mb-6">
|
<div className="mb-4">
|
||||||
<ExpandableChart
|
<ExpandableChart
|
||||||
title="Parallel Coordinates Plot"
|
title="Parallel Coordinates"
|
||||||
subtitle={`${allTrialsRaw.length} trials - Design Variables → Objectives`}
|
subtitle={`${allTrialsRaw.length} trials - Design Variables → Objectives`}
|
||||||
>
|
>
|
||||||
{chartLibrary === 'plotly' ? (
|
{chartLibrary === 'plotly' ? (
|
||||||
@@ -514,7 +503,7 @@ export default function Dashboard() {
|
|||||||
objectives={studyMetadata.objectives}
|
objectives={studyMetadata.objectives}
|
||||||
designVariables={studyMetadata.design_variables}
|
designVariables={studyMetadata.design_variables}
|
||||||
paretoFront={paretoFront}
|
paretoFront={paretoFront}
|
||||||
height={450}
|
height={350}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
) : (
|
) : (
|
||||||
@@ -531,9 +520,9 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
{/* Convergence Plot - Full Width */}
|
{/* Convergence Plot - Full Width */}
|
||||||
{allTrialsRaw.length > 0 && (
|
{allTrialsRaw.length > 0 && (
|
||||||
<div className="mb-6">
|
<div className="mb-4">
|
||||||
<ExpandableChart
|
<ExpandableChart
|
||||||
title="Convergence Plot"
|
title="Convergence"
|
||||||
subtitle={`Best ${studyMetadata?.objectives?.[0]?.name || 'Objective'} over ${allTrialsRaw.length} trials`}
|
subtitle={`Best ${studyMetadata?.objectives?.[0]?.name || 'Objective'} over ${allTrialsRaw.length} trials`}
|
||||||
>
|
>
|
||||||
{chartLibrary === 'plotly' ? (
|
{chartLibrary === 'plotly' ? (
|
||||||
@@ -543,7 +532,7 @@ export default function Dashboard() {
|
|||||||
objectiveIndex={0}
|
objectiveIndex={0}
|
||||||
objectiveName={studyMetadata?.objectives?.[0]?.name || 'Objective'}
|
objectiveName={studyMetadata?.objectives?.[0]?.name || 'Objective'}
|
||||||
direction="minimize"
|
direction="minimize"
|
||||||
height={350}
|
height={280}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
) : (
|
) : (
|
||||||
@@ -560,7 +549,7 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
{/* Parameter Importance - Full Width */}
|
{/* Parameter Importance - Full Width */}
|
||||||
{allTrialsRaw.length > 0 && (studyMetadata?.design_variables?.length > 0 || (allTrialsRaw[0]?.params && Object.keys(allTrialsRaw[0].params).length > 0)) && (
|
{allTrialsRaw.length > 0 && (studyMetadata?.design_variables?.length > 0 || (allTrialsRaw[0]?.params && Object.keys(allTrialsRaw[0].params).length > 0)) && (
|
||||||
<div className="mb-6">
|
<div className="mb-4">
|
||||||
<ExpandableChart
|
<ExpandableChart
|
||||||
title="Parameter Importance"
|
title="Parameter Importance"
|
||||||
subtitle={`Correlation with ${studyMetadata?.objectives?.[0]?.name || 'Objective'}`}
|
subtitle={`Correlation with ${studyMetadata?.objectives?.[0]?.name || 'Objective'}`}
|
||||||
@@ -576,7 +565,7 @@ export default function Dashboard() {
|
|||||||
}
|
}
|
||||||
objectiveIndex={0}
|
objectiveIndex={0}
|
||||||
objectiveName={studyMetadata?.objectives?.[0]?.name || 'Objective'}
|
objectiveName={studyMetadata?.objectives?.[0]?.name || 'Objective'}
|
||||||
height={350}
|
height={280}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
) : (
|
) : (
|
||||||
@@ -595,138 +584,6 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Charts */}
|
|
||||||
<div className="grid grid-cols-2 gap-6 mb-6">
|
|
||||||
{/* Convergence Chart */}
|
|
||||||
<ExpandableChart
|
|
||||||
title="Convergence Plot (Single Objective)"
|
|
||||||
subtitle={`${convergenceData.length} trials`}
|
|
||||||
>
|
|
||||||
<Card title="Convergence Plot">
|
|
||||||
{convergenceData.length > 0 ? (
|
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
|
||||||
<LineChart data={convergenceData}>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
|
||||||
<XAxis
|
|
||||||
dataKey="trial_number"
|
|
||||||
stroke="#94a3b8"
|
|
||||||
label={{ value: 'Trial Number', position: 'insideBottom', offset: -5, fill: '#94a3b8' }}
|
|
||||||
/>
|
|
||||||
<YAxis
|
|
||||||
stroke="#94a3b8"
|
|
||||||
label={{ value: 'Objective', angle: -90, position: 'insideLeft', fill: '#94a3b8' }}
|
|
||||||
/>
|
|
||||||
<Tooltip
|
|
||||||
contentStyle={{ backgroundColor: '#1e293b', border: 'none', borderRadius: '8px' }}
|
|
||||||
labelStyle={{ color: '#e2e8f0' }}
|
|
||||||
/>
|
|
||||||
<Legend />
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey="objective"
|
|
||||||
stroke="#60a5fa"
|
|
||||||
name="Objective"
|
|
||||||
dot={{ r: 3 }}
|
|
||||||
/>
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey="best_so_far"
|
|
||||||
stroke="#10b981"
|
|
||||||
name="Best So Far"
|
|
||||||
strokeWidth={2}
|
|
||||||
dot={{ r: 4 }}
|
|
||||||
/>
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
) : (
|
|
||||||
<div className="h-64 flex items-center justify-center text-dark-300">
|
|
||||||
No trial data yet
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
</ExpandableChart>
|
|
||||||
|
|
||||||
{/* Parameter Space Chart with Selectable Axes */}
|
|
||||||
<ExpandableChart
|
|
||||||
title="Parameter Space"
|
|
||||||
subtitle={`${parameterSpaceData.length} trials - ${paramNames[paramXIndex] || 'X'} vs ${paramNames[paramYIndex] || 'Y'}`}
|
|
||||||
>
|
|
||||||
<Card title={
|
|
||||||
<div className="flex items-center justify-between w-full">
|
|
||||||
<span>Parameter Space</span>
|
|
||||||
{paramNames.length > 2 && (
|
|
||||||
<div className="flex items-center gap-2 text-sm">
|
|
||||||
<span className="text-dark-400">X:</span>
|
|
||||||
<select
|
|
||||||
value={paramXIndex}
|
|
||||||
onChange={(e) => setParamXIndex(Number(e.target.value))}
|
|
||||||
className="bg-dark-600 text-dark-100 px-2 py-1 rounded text-xs border border-dark-500"
|
|
||||||
>
|
|
||||||
{paramNames.map((name, idx) => (
|
|
||||||
<option key={idx} value={idx}>{name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<span className="text-dark-400">Y:</span>
|
|
||||||
<select
|
|
||||||
value={paramYIndex}
|
|
||||||
onChange={(e) => setParamYIndex(Number(e.target.value))}
|
|
||||||
className="bg-dark-600 text-dark-100 px-2 py-1 rounded text-xs border border-dark-500"
|
|
||||||
>
|
|
||||||
{paramNames.map((name, idx) => (
|
|
||||||
<option key={idx} value={idx}>{name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}>
|
|
||||||
{parameterSpaceData.length > 0 ? (
|
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
|
||||||
<ScatterChart>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
|
||||||
<XAxis
|
|
||||||
type="number"
|
|
||||||
dataKey="x"
|
|
||||||
stroke="#94a3b8"
|
|
||||||
name={paramNames[paramXIndex] || 'X'}
|
|
||||||
label={{ value: paramNames[paramXIndex] || 'Parameter X', position: 'insideBottom', offset: -5, fill: '#94a3b8' }}
|
|
||||||
/>
|
|
||||||
<YAxis
|
|
||||||
type="number"
|
|
||||||
dataKey="y"
|
|
||||||
stroke="#94a3b8"
|
|
||||||
name={paramNames[paramYIndex] || 'Y'}
|
|
||||||
label={{ value: paramNames[paramYIndex] || 'Parameter Y', angle: -90, position: 'insideLeft', fill: '#94a3b8' }}
|
|
||||||
/>
|
|
||||||
<Tooltip
|
|
||||||
cursor={{ strokeDasharray: '3 3' }}
|
|
||||||
contentStyle={{ backgroundColor: '#1e293b', border: 'none', borderRadius: '8px' }}
|
|
||||||
labelStyle={{ color: '#e2e8f0' }}
|
|
||||||
formatter={(value: any, name: string) => {
|
|
||||||
if (name === 'objective') return [value.toFixed(4), 'Objective'];
|
|
||||||
return [value.toFixed(3), name];
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Scatter name="Trials" data={parameterSpaceData}>
|
|
||||||
{parameterSpaceData.map((entry, index) => (
|
|
||||||
<Cell
|
|
||||||
key={`cell-${index}`}
|
|
||||||
fill={entry.isBest ? '#10b981' : '#60a5fa'}
|
|
||||||
r={entry.isBest ? 8 : 5}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Scatter>
|
|
||||||
</ScatterChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
) : (
|
|
||||||
<div className="h-64 flex items-center justify-center text-dark-300">
|
|
||||||
No trial data yet
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
</ExpandableChart>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Trial History with Sort Controls and Pagination */}
|
{/* Trial History with Sort Controls and Pagination */}
|
||||||
<Card
|
<Card
|
||||||
title={
|
title={
|
||||||
@@ -925,18 +782,18 @@ export default function Dashboard() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Console Output - at the bottom */}
|
{/* Console Output - at the bottom */}
|
||||||
<div className="mt-6">
|
<div className="mt-4">
|
||||||
<ConsoleOutput
|
<ConsoleOutput
|
||||||
studyId={selectedStudyId}
|
studyId={selectedStudyId}
|
||||||
refreshInterval={2000}
|
refreshInterval={2000}
|
||||||
maxLines={200}
|
maxLines={150}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Claude Code Terminal - Right Sidebar (taller for better visibility) */}
|
{/* Claude Code Terminal - Right Sidebar (wider for better visibility) */}
|
||||||
{chatOpen && (
|
{chatOpen && (
|
||||||
<aside className="col-span-4 h-[calc(100vh-8rem)] sticky top-20">
|
<aside className="col-span-5 h-[calc(100vh-12rem)] sticky top-4">
|
||||||
<ClaudeTerminal
|
<ClaudeTerminal
|
||||||
isExpanded={chatExpanded}
|
isExpanded={chatExpanded}
|
||||||
onToggleExpand={() => setChatExpanded(!chatExpanded)}
|
onToggleExpand={() => setChatExpanded(!chatExpanded)}
|
||||||
|
|||||||
Reference in New Issue
Block a user