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:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user