feat: Add dashboard chat integration and MCP server

Major changes:
- Dashboard: WebSocket-based chat with session management
- Dashboard: New chat components (ChatPane, ChatInput, ModeToggle)
- Dashboard: Enhanced UI with parallel coordinates chart
- MCP Server: New atomizer-tools server for Claude integration
- Extractors: Enhanced Zernike OPD extractor
- Reports: Improved report generator

New studies (configs and scripts only):
- M1 Mirror: Cost reduction campaign studies
- Simple Beam, Simple Bracket, UAV Arm studies

Note: Large iteration data (2_iterations/, best_design_archive/)
excluded via .gitignore - kept on local Gitea only.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-13 15:53:55 -05:00
parent 69c0d76b50
commit 73a7b9d9f1
1680 changed files with 144922 additions and 723 deletions

View File

@@ -27,7 +27,19 @@ const PlotlyRunComparison = lazy(() => import('../components/plotly/PlotlyRunCom
const ChartLoading = () => (
<div className="flex items-center justify-center h-64 text-dark-400">
<div className="animate-pulse">Loading chart...</div>
<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>
);
const NoData = ({ message = 'No data available' }: { message?: string }) => (
<div className="flex items-center justify-center h-64 text-dark-500">
<div className="flex flex-col items-center gap-2">
<BarChart3 className="w-8 h-8" />
<span className="text-sm">{message}</span>
</div>
</div>
);
@@ -70,6 +82,7 @@ export default function Analysis() {
const { selectedStudy, isInitialized } = useStudy();
const [activeTab, setActiveTab] = useState<AnalysisTab>('overview');
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [trials, setTrials] = useState<TrialData[]>([]);
const [metadata, setMetadata] = useState<StudyMetadata | null>(null);
const [paretoFront, setParetoFront] = useState<any[]>([]);
@@ -88,9 +101,13 @@ export default function Analysis() {
const loadData = async () => {
setLoading(true);
setError(null);
try {
// Load trial history
// Load trial history (required)
const historyRes = await fetch(`/api/optimization/studies/${selectedStudy.id}/history?limit=500`);
if (!historyRes.ok) {
throw new Error(`Failed to load trial history: ${historyRes.statusText}`);
}
const historyData = await historyRes.json();
const trialsData = historyData.trials.map((t: any) => {
let values: number[] = [];
@@ -112,26 +129,45 @@ export default function Analysis() {
});
setTrials(trialsData);
// Load metadata
const metadataRes = await fetch(`/api/optimization/studies/${selectedStudy.id}/metadata`);
const metadataData = await metadataRes.json();
setMetadata(metadataData);
// Load Pareto front
const paretoRes = await fetch(`/api/optimization/studies/${selectedStudy.id}/pareto-front`);
const paretoData = await paretoRes.json();
if (paretoData.is_multi_objective && paretoData.pareto_front) {
setParetoFront(paretoData.pareto_front);
// Load metadata (optional, graceful fallback)
try {
const metadataRes = await fetch(`/api/optimization/studies/${selectedStudy.id}/metadata`);
if (metadataRes.ok) {
const metadataData = await metadataRes.json();
setMetadata(metadataData);
}
} catch {
console.warn('Metadata not available');
}
// Load runs data for comparison
const runsRes = await fetch(`/api/optimization/studies/${selectedStudy.id}/runs`);
const runsData = await runsRes.json();
if (runsData.runs) {
setRuns(runsData.runs);
// Load Pareto front (optional)
try {
const paretoRes = await fetch(`/api/optimization/studies/${selectedStudy.id}/pareto-front`);
if (paretoRes.ok) {
const paretoData = await paretoRes.json();
if (paretoData.is_multi_objective && paretoData.pareto_front) {
setParetoFront(paretoData.pareto_front);
}
}
} catch {
console.warn('Pareto data not available');
}
// Load runs data for comparison (optional)
try {
const runsRes = await fetch(`/api/optimization/studies/${selectedStudy.id}/runs`);
if (runsRes.ok) {
const runsData = await runsRes.json();
if (runsData.runs) {
setRuns(runsData.runs);
}
}
} catch {
console.warn('Runs data not available');
}
} catch (err) {
console.error('Failed to load analysis data:', err);
setError(err instanceof Error ? err.message : 'Failed to load analysis data');
} finally {
setLoading(false);
}
@@ -271,6 +307,20 @@ export default function Analysis() {
<div className="flex items-center justify-center py-16">
<RefreshCw className="w-8 h-8 animate-spin text-dark-400" />
</div>
) : error ? (
<div className="flex flex-col items-center justify-center py-16 gap-4">
<div className="text-red-400 text-6xl">!</div>
<div className="text-red-400 text-lg font-medium">Error Loading Data</div>
<div className="text-dark-400 text-sm max-w-md text-center">{error}</div>
<button
onClick={() => window.location.reload()}
className="mt-4 px-4 py-2 bg-primary-600 hover:bg-primary-500 text-white rounded-lg transition-colors"
>
Retry
</button>
</div>
) : trials.length === 0 ? (
<NoData message="No trials found for this study" />
) : (
<>
{/* Overview Tab */}