feat: Add Claude Code terminal integration to dashboard

- Add embedded Claude Code terminal with xterm.js for full CLI experience
- Create WebSocket PTY backend for real-time terminal communication
- Add terminal status endpoint to check CLI availability
- Update dashboard to use Claude Code terminal instead of API chat
- Add optimization control panel with start/stop/validate actions
- Add study context provider for global state management
- Update frontend with new dependencies (xterm.js addons)
- Comprehensive README documentation for all new features

🤖 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 15:02:13 -05:00
parent 8cbdbcad78
commit 9eed4d81eb
23 changed files with 5060 additions and 339 deletions

View File

@@ -1,14 +1,17 @@
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 { StudyCard } from '../components/dashboard/StudyCard';
// import { OptimizerPanel } from '../components/OptimizerPanel'; // Not used currently
import { ControlPanel } from '../components/dashboard/ControlPanel';
import { ClaudeTerminal } from '../components/ClaudeTerminal';
import { ParetoPlot } from '../components/ParetoPlot';
import { ParallelCoordinatesPlot } from '../components/ParallelCoordinatesPlot';
import { ParameterImportanceChart } from '../components/ParameterImportanceChart';
@@ -16,7 +19,7 @@ import { ConvergencePlot } from '../components/ConvergencePlot';
import { StudyReportViewer } from '../components/StudyReportViewer';
import { ConsoleOutput } from '../components/ConsoleOutput';
import { ExpandableChart } from '../components/ExpandableChart';
import type { Study, Trial, ConvergenceDataPoint, ParameterSpaceDataPoint } from '../types';
import type { Trial, ConvergenceDataPoint, ParameterSpaceDataPoint } from '../types';
// Lazy load Plotly components for better initial load performance
const PlotlyParallelCoordinates = lazy(() => import('../components/plotly/PlotlyParallelCoordinates').then(m => ({ default: m.PlotlyParallelCoordinates })));
@@ -32,8 +35,17 @@ const ChartLoading = () => (
);
export default function Dashboard() {
const [studies, setStudies] = useState<Study[]>([]);
const [selectedStudyId, setSelectedStudyId] = useState<string | null>(null);
const navigate = useNavigate();
const { selectedStudy, refreshStudies } = useStudy();
const selectedStudyId = selectedStudy?.id || null;
// Redirect to home if no study selected
useEffect(() => {
if (!selectedStudy) {
navigate('/');
}
}, [selectedStudy, navigate]);
const [allTrials, setAllTrials] = useState<Trial[]>([]);
const [displayedTrials, setDisplayedTrials] = useState<Trial[]>([]);
const [bestValue, setBestValue] = useState<number>(Infinity);
@@ -55,26 +67,9 @@ export default function Dashboard() {
// Chart library toggle: 'recharts' (faster) or 'plotly' (more interactive but slower)
const [chartLibrary, setChartLibrary] = useState<'plotly' | 'recharts'>('recharts');
// Load studies on mount
useEffect(() => {
apiClient.getStudies()
.then(data => {
setStudies(data.studies);
if (data.studies.length > 0) {
// Check LocalStorage for last selected study
const savedStudyId = localStorage.getItem('lastSelectedStudyId');
const studyExists = data.studies.find(s => s.id === savedStudyId);
if (savedStudyId && studyExists) {
setSelectedStudyId(savedStudyId);
} else {
const running = data.studies.find(s => s.status === 'running');
setSelectedStudyId(running?.id || data.studies[0].id);
}
}
})
.catch(console.error);
}, []);
// Claude chat panel state
const [chatOpen, setChatOpen] = useState(false);
const [chatExpanded, setChatExpanded] = useState(false);
const showAlert = (type: 'success' | 'warning', message: string) => {
const id = alertIdCounter;
@@ -111,9 +106,6 @@ export default function Dashboard() {
setPrunedCount(0);
setExpandedTrials(new Set());
// Save to LocalStorage
localStorage.setItem('lastSelectedStudyId', selectedStudyId);
apiClient.getStudyHistory(selectedStudyId)
.then(data => {
const validTrials = data.trials.filter(t => t.objective !== null && t.objective !== undefined);
@@ -331,6 +323,19 @@ export default function Dashboard() {
<p className="text-dark-300 mt-1">Real-time optimization monitoring</p>
</div>
<div className="flex gap-2">
{/* Claude Code Terminal Toggle Button */}
<button
onClick={() => setChatOpen(!chatOpen)}
className={`flex items-center gap-2 px-4 py-2 rounded-lg transition-colors ${
chatOpen
? 'bg-primary-600 text-white'
: 'bg-dark-700 text-dark-200 hover:bg-dark-600 hover:text-white border border-dark-600'
}`}
title="Open Claude Code terminal"
>
<Terminal className="w-4 h-4" />
<span className="hidden sm:inline">Claude Code</span>
</button>
{selectedStudyId && (
<StudyReportViewer studyId={selectedStudyId} />
)}
@@ -380,24 +385,13 @@ export default function Dashboard() {
</header>
<div className="grid grid-cols-12 gap-6">
{/* Sidebar - Study List */}
{/* Control Panel - Left Sidebar */}
<aside className="col-span-3">
<Card title="Active Studies">
<div className="space-y-3 max-h-[calc(100vh-200px)] overflow-y-auto">
{studies.map(study => (
<StudyCard
key={study.id}
study={study}
isActive={study.id === selectedStudyId}
onClick={() => setSelectedStudyId(study.id)}
/>
))}
</div>
</Card>
<ControlPanel onStatusChange={refreshStudies} />
</aside>
{/* Main Content */}
<main className="col-span-9">
{/* Main Content - shrinks when chat is open */}
<main className={chatOpen ? 'col-span-5' : 'col-span-9'}>
{/* Study Name Header */}
{selectedStudyId && (
<div className="mb-4 pb-3 border-b border-dark-600">
@@ -884,6 +878,17 @@ export default function Dashboard() {
/>
</div>
</main>
{/* Claude Code Terminal - Right Sidebar */}
{chatOpen && (
<aside className="col-span-4 h-[calc(100vh-12rem)] sticky top-24">
<ClaudeTerminal
isExpanded={chatExpanded}
onToggleExpand={() => setChatExpanded(!chatExpanded)}
onClose={() => setChatOpen(false)}
/>
</aside>
)}
</div>
</div>
);