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:
Antoine
2025-12-04 20:59:31 -05:00
parent f8b90156b3
commit fb2d06236a
5 changed files with 309 additions and 211 deletions

View File

@@ -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)}