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:
@@ -1,15 +1,10 @@
|
||||
import { useState, useEffect, lazy, Suspense } from 'react';
|
||||
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 { useOptimizationWebSocket } from '../hooks/useWebSocket';
|
||||
import { apiClient } from '../api/client';
|
||||
import { useStudy } from '../context/StudyContext';
|
||||
import { Card } from '../components/common/Card';
|
||||
import { MetricCard } from '../components/dashboard/MetricCard';
|
||||
import { ControlPanel } from '../components/dashboard/ControlPanel';
|
||||
import { ClaudeTerminal } from '../components/ClaudeTerminal';
|
||||
import { ParetoPlot } from '../components/ParetoPlot';
|
||||
@@ -19,7 +14,7 @@ import { ConvergencePlot } from '../components/ConvergencePlot';
|
||||
import { StudyReportViewer } from '../components/StudyReportViewer';
|
||||
import { ConsoleOutput } from '../components/ConsoleOutput';
|
||||
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
|
||||
const PlotlyParallelCoordinates = lazy(() => import('../components/plotly/PlotlyParallelCoordinates').then(m => ({ default: m.PlotlyParallelCoordinates })));
|
||||
@@ -417,68 +412,62 @@ export default function Dashboard() {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
{/* Control Panel - Left Sidebar (smaller) */}
|
||||
<aside className="col-span-2">
|
||||
<ControlPanel onStatusChange={refreshStudies} />
|
||||
</aside>
|
||||
{/* Control Panel - Full Width on Top */}
|
||||
<div className="mb-6">
|
||||
<ControlPanel onStatusChange={refreshStudies} horizontal />
|
||||
</div>
|
||||
|
||||
{/* Main Content - takes most of the space */}
|
||||
<main className={chatOpen ? 'col-span-6' : 'col-span-10'}>
|
||||
{/* Study Name Header */}
|
||||
{selectedStudyId && (
|
||||
<div className="mb-4 pb-3 border-b border-dark-600">
|
||||
<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>
|
||||
{/* Main Layout: Charts + Claude Terminal */}
|
||||
<div className={`grid gap-4 ${chatOpen ? 'grid-cols-12' : 'grid-cols-1'}`}>
|
||||
{/* Main Content - Charts stacked vertically */}
|
||||
<main className={chatOpen ? 'col-span-7' : 'col-span-1'}>
|
||||
{/* Study Name Header + Metrics in one row */}
|
||||
<div className="mb-4 pb-3 border-b border-dark-600 flex items-center justify-between">
|
||||
<div>
|
||||
{selectedStudyId && (
|
||||
<>
|
||||
<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>
|
||||
)}
|
||||
|
||||
{/* Metrics Grid */}
|
||||
<div className="grid grid-cols-4 gap-4 mb-6">
|
||||
<MetricCard label="Total Trials" value={allTrials.length} />
|
||||
<MetricCard
|
||||
label="Best Value"
|
||||
value={bestValue === Infinity ? '-' : bestValue.toFixed(4)}
|
||||
valueColor="text-green-400"
|
||||
/>
|
||||
<MetricCard
|
||||
label="Avg Objective"
|
||||
value={avgObjective > 0 ? avgObjective.toFixed(4) : '-'}
|
||||
valueColor="text-blue-400"
|
||||
/>
|
||||
<MetricCard
|
||||
label="Pruned"
|
||||
value={prunedCount}
|
||||
valueColor={prunedCount > 0 ? 'text-red-400' : 'text-green-400'}
|
||||
/>
|
||||
{/* Compact Metrics */}
|
||||
<div className="flex gap-3">
|
||||
<div className="text-center px-3">
|
||||
<div className="text-2xl font-bold text-white">{allTrials.length}</div>
|
||||
<div className="text-xs text-dark-400">Trials</div>
|
||||
</div>
|
||||
<div className="text-center px-3 border-l border-dark-600">
|
||||
<div className="text-2xl font-bold text-green-400">
|
||||
{bestValue === Infinity ? '-' : bestValue.toFixed(4)}
|
||||
</div>
|
||||
<div className="text-xs text-dark-400">Best</div>
|
||||
</div>
|
||||
<div className="text-center px-3 border-l border-dark-600">
|
||||
<div className="text-2xl font-bold text-blue-400">
|
||||
{avgObjective > 0 ? avgObjective.toFixed(4) : '-'}
|
||||
</div>
|
||||
<div className="text-xs text-dark-400">Avg</div>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
{/* Protocol 13: Intelligent Optimizer & Pareto Front */}
|
||||
{/* Pareto Front - Full Width */}
|
||||
{selectedStudyId && paretoFront.length > 0 && studyMetadata && studyMetadata.objectives && (
|
||||
<div className="grid grid-cols-2 gap-6 mb-6">
|
||||
<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>
|
||||
<div className="mb-4">
|
||||
<ExpandableChart
|
||||
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' ? (
|
||||
<Suspense fallback={<ChartLoading />}>
|
||||
@@ -486,7 +475,7 @@ export default function Dashboard() {
|
||||
trials={allTrialsRaw}
|
||||
paretoFront={paretoFront}
|
||||
objectives={studyMetadata.objectives}
|
||||
height={350}
|
||||
height={300}
|
||||
/>
|
||||
</Suspense>
|
||||
) : (
|
||||
@@ -500,11 +489,11 @@ export default function Dashboard() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Parallel Coordinates (full width for multi-objective) */}
|
||||
{/* Parallel Coordinates - Full Width */}
|
||||
{allTrialsRaw.length > 0 && studyMetadata && studyMetadata.objectives && studyMetadata.design_variables && (
|
||||
<div className="mb-6">
|
||||
<div className="mb-4">
|
||||
<ExpandableChart
|
||||
title="Parallel Coordinates Plot"
|
||||
title="Parallel Coordinates"
|
||||
subtitle={`${allTrialsRaw.length} trials - Design Variables → Objectives`}
|
||||
>
|
||||
{chartLibrary === 'plotly' ? (
|
||||
@@ -514,7 +503,7 @@ export default function Dashboard() {
|
||||
objectives={studyMetadata.objectives}
|
||||
designVariables={studyMetadata.design_variables}
|
||||
paretoFront={paretoFront}
|
||||
height={450}
|
||||
height={350}
|
||||
/>
|
||||
</Suspense>
|
||||
) : (
|
||||
@@ -531,9 +520,9 @@ export default function Dashboard() {
|
||||
|
||||
{/* Convergence Plot - Full Width */}
|
||||
{allTrialsRaw.length > 0 && (
|
||||
<div className="mb-6">
|
||||
<div className="mb-4">
|
||||
<ExpandableChart
|
||||
title="Convergence Plot"
|
||||
title="Convergence"
|
||||
subtitle={`Best ${studyMetadata?.objectives?.[0]?.name || 'Objective'} over ${allTrialsRaw.length} trials`}
|
||||
>
|
||||
{chartLibrary === 'plotly' ? (
|
||||
@@ -543,7 +532,7 @@ export default function Dashboard() {
|
||||
objectiveIndex={0}
|
||||
objectiveName={studyMetadata?.objectives?.[0]?.name || 'Objective'}
|
||||
direction="minimize"
|
||||
height={350}
|
||||
height={280}
|
||||
/>
|
||||
</Suspense>
|
||||
) : (
|
||||
@@ -560,7 +549,7 @@ export default function Dashboard() {
|
||||
|
||||
{/* Parameter Importance - Full Width */}
|
||||
{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
|
||||
title="Parameter Importance"
|
||||
subtitle={`Correlation with ${studyMetadata?.objectives?.[0]?.name || 'Objective'}`}
|
||||
@@ -576,7 +565,7 @@ export default function Dashboard() {
|
||||
}
|
||||
objectiveIndex={0}
|
||||
objectiveName={studyMetadata?.objectives?.[0]?.name || 'Objective'}
|
||||
height={350}
|
||||
height={280}
|
||||
/>
|
||||
</Suspense>
|
||||
) : (
|
||||
@@ -595,138 +584,6 @@ export default function Dashboard() {
|
||||
</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 */}
|
||||
<Card
|
||||
title={
|
||||
@@ -925,18 +782,18 @@ export default function Dashboard() {
|
||||
</Card>
|
||||
|
||||
{/* Console Output - at the bottom */}
|
||||
<div className="mt-6">
|
||||
<div className="mt-4">
|
||||
<ConsoleOutput
|
||||
studyId={selectedStudyId}
|
||||
refreshInterval={2000}
|
||||
maxLines={200}
|
||||
maxLines={150}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Claude Code Terminal - Right Sidebar (taller for better visibility) */}
|
||||
{/* Claude Code Terminal - Right Sidebar (wider for better visibility) */}
|
||||
{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
|
||||
isExpanded={chatExpanded}
|
||||
onToggleExpand={() => setChatExpanded(!chatExpanded)}
|
||||
|
||||
Reference in New Issue
Block a user