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:
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user