feat(dashboard): Enhanced chat, spec management, and Claude integration

Backend:
- spec.py: New AtomizerSpec REST API endpoints
- spec_manager.py: SpecManager service for unified config
- interview_engine.py: Study creation interview logic
- claude.py: Enhanced Claude API with context
- optimization.py: Extended optimization endpoints
- context_builder.py, session_manager.py: Improved services

Frontend:
- Chat components: Enhanced message rendering, tool call cards
- Hooks: useClaudeCode, useSpecWebSocket, improved useChat
- Pages: Updated Dashboard, Analysis, Insights, Setup, Home
- Components: ParallelCoordinatesPlot, ParetoPlot improvements
- App.tsx: Route updates for canvas/studio

Infrastructure:
- vite.config.ts: Build configuration updates
- start/stop-dashboard.bat: Script improvements
This commit is contained in:
2026-01-20 13:10:47 -05:00
parent b05412f807
commit ba0b9a1fae
31 changed files with 4836 additions and 349 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, lazy, Suspense, useMemo } from 'react';
import { useState, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import {
BarChart3,
@@ -14,25 +14,10 @@ import {
} from 'lucide-react';
import { useStudy } from '../context/StudyContext';
import { Card } from '../components/common/Card';
// Lazy load charts
const PlotlyParetoPlot = lazy(() => import('../components/plotly/PlotlyParetoPlot').then(m => ({ default: m.PlotlyParetoPlot })));
const PlotlyParallelCoordinates = lazy(() => import('../components/plotly/PlotlyParallelCoordinates').then(m => ({ default: m.PlotlyParallelCoordinates })));
const PlotlyParameterImportance = lazy(() => import('../components/plotly/PlotlyParameterImportance').then(m => ({ default: m.PlotlyParameterImportance })));
const PlotlyConvergencePlot = lazy(() => import('../components/plotly/PlotlyConvergencePlot').then(m => ({ default: m.PlotlyConvergencePlot })));
const PlotlyCorrelationHeatmap = lazy(() => import('../components/plotly/PlotlyCorrelationHeatmap').then(m => ({ default: m.PlotlyCorrelationHeatmap })));
const PlotlyFeasibilityChart = lazy(() => import('../components/plotly/PlotlyFeasibilityChart').then(m => ({ default: m.PlotlyFeasibilityChart })));
const PlotlySurrogateQuality = lazy(() => import('../components/plotly/PlotlySurrogateQuality').then(m => ({ default: m.PlotlySurrogateQuality })));
const PlotlyRunComparison = lazy(() => import('../components/plotly/PlotlyRunComparison').then(m => ({ default: m.PlotlyRunComparison })));
const ChartLoading = () => (
<div className="flex items-center justify-center h-64 text-dark-400">
<div className="flex flex-col items-center gap-2">
<div className="animate-spin w-6 h-6 border-2 border-primary-500 border-t-transparent rounded-full"></div>
<span className="text-sm animate-pulse">Loading chart...</span>
</div>
</div>
);
import { ConvergencePlot } from '../components/ConvergencePlot';
import { ParameterImportanceChart } from '../components/ParameterImportanceChart';
import { ParallelCoordinatesPlot } from '../components/ParallelCoordinatesPlot';
import { ParetoPlot } from '../components/ParetoPlot';
const NoData = ({ message = 'No data available' }: { message?: string }) => (
<div className="flex items-center justify-center h-64 text-dark-500">
@@ -383,15 +368,12 @@ export default function Analysis() {
{/* Convergence Plot */}
{trials.length > 0 && (
<Card title="Convergence Plot">
<Suspense fallback={<ChartLoading />}>
<PlotlyConvergencePlot
trials={trials}
objectiveIndex={0}
objectiveName={metadata?.objectives?.[0]?.name || 'Objective'}
direction="minimize"
height={350}
/>
</Suspense>
<ConvergencePlot
trials={trials}
objectiveIndex={0}
objectiveName={metadata?.objectives?.[0]?.name || 'Objective'}
direction="minimize"
/>
</Card>
)}
@@ -455,30 +437,24 @@ export default function Analysis() {
{/* Parameter Importance */}
{trials.length > 0 && metadata?.design_variables && (
<Card title="Parameter Importance">
<Suspense fallback={<ChartLoading />}>
<PlotlyParameterImportance
trials={trials}
designVariables={metadata.design_variables}
objectiveIndex={0}
objectiveName={metadata?.objectives?.[0]?.name || 'Objective'}
height={400}
/>
</Suspense>
<ParameterImportanceChart
trials={trials}
designVariables={metadata.design_variables}
objectiveIndex={0}
objectiveName={metadata?.objectives?.[0]?.name || 'Objective'}
/>
</Card>
)}
{/* Parallel Coordinates */}
{trials.length > 0 && metadata && (
<Card title="Parallel Coordinates">
<Suspense fallback={<ChartLoading />}>
<PlotlyParallelCoordinates
trials={trials}
objectives={metadata.objectives || []}
designVariables={metadata.design_variables || []}
paretoFront={paretoFront}
height={450}
/>
</Suspense>
<ParallelCoordinatesPlot
paretoData={trials}
objectives={metadata.objectives || []}
designVariables={metadata.design_variables || []}
paretoFront={paretoFront}
/>
</Card>
)}
</div>
@@ -508,14 +484,11 @@ export default function Analysis() {
{/* Pareto Front Plot */}
{paretoFront.length > 0 && (
<Card title="Pareto Front">
<Suspense fallback={<ChartLoading />}>
<PlotlyParetoPlot
trials={trials}
paretoFront={paretoFront}
objectives={metadata?.objectives || []}
height={500}
/>
</Suspense>
<ParetoPlot
paretoData={paretoFront}
objectives={metadata?.objectives || []}
allTrials={trials}
/>
</Card>
)}
@@ -550,16 +523,10 @@ export default function Analysis() {
{/* Correlations Tab */}
{activeTab === 'correlations' && (
<div className="space-y-6">
{/* Correlation Heatmap */}
{/* Correlation Analysis */}
{trials.length > 2 && (
<Card title="Parameter-Objective Correlation Matrix">
<Suspense fallback={<ChartLoading />}>
<PlotlyCorrelationHeatmap
trials={trials}
objectiveName={metadata?.objectives?.[0]?.name || 'Objective'}
height={Math.min(500, 100 + Object.keys(trials[0]?.params || {}).length * 40)}
/>
</Suspense>
<Card title="Parameter-Objective Correlation Analysis">
<CorrelationTable trials={trials} objectiveName={metadata?.objectives?.[0]?.name || 'Objective'} />
</Card>
)}
@@ -612,11 +579,22 @@ export default function Analysis() {
</Card>
</div>
{/* Feasibility Over Time Chart */}
<Card title="Feasibility Rate Over Time">
<Suspense fallback={<ChartLoading />}>
<PlotlyFeasibilityChart trials={trials} height={350} />
</Suspense>
{/* Feasibility Summary */}
<Card title="Feasibility Analysis">
<div className="p-4">
<div className="flex items-center gap-4 mb-4">
<div className="flex-1 bg-dark-700 rounded-full h-4 overflow-hidden">
<div
className="h-full bg-green-500 transition-all duration-500"
style={{ width: `${stats.feasibilityRate}%` }}
/>
</div>
<span className="text-lg font-bold text-green-400">{stats.feasibilityRate.toFixed(1)}%</span>
</div>
<p className="text-dark-400 text-sm">
{stats.feasible} of {stats.total} trials satisfy all constraints
</p>
</div>
</Card>
{/* Infeasible Trials List */}
@@ -683,11 +661,38 @@ export default function Analysis() {
</Card>
</div>
{/* Surrogate Quality Charts */}
<Card title="Surrogate Model Analysis">
<Suspense fallback={<ChartLoading />}>
<PlotlySurrogateQuality trials={trials} height={400} />
</Suspense>
{/* Surrogate Performance Summary */}
<Card title="Surrogate Model Performance">
<div className="grid grid-cols-2 gap-6 p-4">
<div>
<h4 className="text-sm font-semibold text-dark-300 mb-3">Trial Distribution</h4>
<div className="space-y-2">
<div className="flex items-center gap-3">
<div className="w-3 h-3 bg-blue-500 rounded-full"></div>
<span className="text-dark-200">FEA: {stats.feaTrials} trials</span>
<span className="text-dark-400 ml-auto">
{((stats.feaTrials / stats.total) * 100).toFixed(0)}%
</span>
</div>
<div className="flex items-center gap-3">
<div className="w-3 h-3 bg-purple-500 rounded-full"></div>
<span className="text-dark-200">NN: {stats.nnTrials} trials</span>
<span className="text-dark-400 ml-auto">
{((stats.nnTrials / stats.total) * 100).toFixed(0)}%
</span>
</div>
</div>
</div>
<div>
<h4 className="text-sm font-semibold text-dark-300 mb-3">Efficiency Gains</h4>
<div className="text-center p-4 bg-dark-750 rounded-lg">
<div className="text-3xl font-bold text-primary-400">
{stats.feaTrials > 0 ? `${(stats.total / stats.feaTrials).toFixed(1)}x` : '1.0x'}
</div>
<div className="text-xs text-dark-400 mt-1">Effective Speedup</div>
</div>
</div>
</div>
</Card>
</div>
)}
@@ -700,9 +705,36 @@ export default function Analysis() {
Compare different optimization runs within this study. Studies with adaptive optimization
may have multiple runs (e.g., initial FEA exploration, NN-accelerated iterations).
</p>
<Suspense fallback={<ChartLoading />}>
<PlotlyRunComparison runs={runs} height={400} />
</Suspense>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-dark-600">
<th className="text-left py-2 px-3 text-dark-400 font-medium">Run</th>
<th className="text-left py-2 px-3 text-dark-400 font-medium">Source</th>
<th className="text-left py-2 px-3 text-dark-400 font-medium">Trials</th>
<th className="text-left py-2 px-3 text-dark-400 font-medium">Best Value</th>
<th className="text-left py-2 px-3 text-dark-400 font-medium">Avg Value</th>
</tr>
</thead>
<tbody>
{runs.map((run) => (
<tr key={run.run_id} className="border-b border-dark-700">
<td className="py-2 px-3 font-mono text-white">{run.name || `Run ${run.run_id}`}</td>
<td className="py-2 px-3">
<span className={`px-2 py-0.5 rounded text-xs ${
run.source === 'NN' ? 'bg-purple-500/20 text-purple-400' : 'bg-blue-500/20 text-blue-400'
}`}>
{run.source}
</span>
</td>
<td className="py-2 px-3 text-dark-200">{run.trial_count}</td>
<td className="py-2 px-3 font-mono text-green-400">{run.best_value?.toExponential(4) || 'N/A'}</td>
<td className="py-2 px-3 font-mono text-dark-300">{run.avg_value?.toExponential(4) || 'N/A'}</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
</div>
)}