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

@@ -1,15 +1,88 @@
import { useState, Component, ErrorInfo, ReactNode } from 'react';
import { Outlet } from 'react-router-dom';
import { Sidebar } from './Sidebar';
import { ChatPane } from '../chat';
// Error boundary to catch errors without crashing the whole app
class ErrorBoundary extends Component<{ children: ReactNode; name: string }, { hasError: boolean; error: Error | null }> {
constructor(props: { children: ReactNode; name: string }) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error(`[${this.props.name}] Error:`, error, errorInfo);
}
render() {
if (this.state.hasError) {
if (this.props.name === 'ChatPane') {
return null; // Don't render chat if it errors
}
// For main content, show error message
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center p-8 bg-red-900/20 rounded-lg border border-red-500/30">
<h2 className="text-red-400 text-lg font-semibold mb-2">Something went wrong</h2>
<p className="text-dark-400 text-sm mb-4">{this.state.error?.message}</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-primary-500 text-white rounded-lg hover:bg-primary-600"
>
Reload Page
</button>
</div>
</div>
);
}
return this.props.children;
}
}
// Shorthand for ChatPane error boundary (silent fail)
const ChatErrorBoundary = ({ children }: { children: ReactNode }) => (
<ErrorBoundary name="ChatPane">{children}</ErrorBoundary>
);
export const MainLayout = () => {
const [isChatOpen, setIsChatOpen] = useState(false);
// TEMP: Set to false to disable chat for debugging
const ENABLE_CHAT = true;
return (
<div className="min-h-screen bg-dark-900 text-dark-50 font-sans">
<Sidebar />
<main className="ml-64 min-h-screen p-6">
<div className="min-h-screen bg-dark-850 text-dark-50 font-sans relative">
{/* Grid Background */}
<div className="fixed inset-0 grid-bg pointer-events-none z-0 opacity-50" />
{/* Ambient Glow Orbs */}
<div className="glow-orb w-[400px] h-[400px] bg-primary-400/5 -top-48 -left-48 fixed" />
<div className="glow-orb w-[300px] h-[300px] bg-primary-500/5 bottom-0 right-0 fixed" style={{ animationDelay: '-3s' }} />
<Sidebar onChatToggle={ENABLE_CHAT ? () => setIsChatOpen(true) : undefined} />
{/* Main content - adjust width when chat is open */}
<main className={`ml-64 min-h-screen p-6 relative z-10 transition-all duration-200 ${isChatOpen && ENABLE_CHAT ? 'mr-[380px]' : ''}`}>
<div className="max-w-6xl">
<Outlet />
<ErrorBoundary name="MainContent">
<Outlet />
</ErrorBoundary>
</div>
</main>
{/* Chat Panel - wrapped in error boundary */}
{ENABLE_CHAT && (
<ChatErrorBoundary>
<ChatPane
isOpen={isChatOpen}
onToggle={() => setIsChatOpen(!isChatOpen)}
/>
</ChatErrorBoundary>
)}
</div>
);
};

View File

@@ -10,17 +10,18 @@ import {
Pause,
CheckCircle,
Clock,
Zap,
Terminal,
MessageSquare,
Eye
} from 'lucide-react';
import clsx from 'clsx';
import { useStudy } from '../../context/StudyContext';
import { useClaudeTerminal } from '../../context/ClaudeTerminalContext';
export const Sidebar = () => {
interface SidebarProps {
onChatToggle?: () => void;
}
export const Sidebar = ({ onChatToggle }: SidebarProps) => {
const { selectedStudy, clearStudy } = useStudy();
const { isConnected: claudeConnected, setIsOpen: setClaudeTerminalOpen } = useClaudeTerminal();
const navigate = useNavigate();
const handleBackToHome = () => {
@@ -35,7 +36,7 @@ export const Sidebar = () => {
case 'paused':
return <Pause className="w-3 h-3 text-orange-400" />;
case 'completed':
return <CheckCircle className="w-3 h-3 text-blue-400" />;
return <CheckCircle className="w-3 h-3 text-primary-400" />;
case 'not_started':
return <Clock className="w-3 h-3 text-dark-400" />;
default:
@@ -50,7 +51,7 @@ export const Sidebar = () => {
case 'paused':
return 'text-orange-400';
case 'completed':
return 'text-blue-400';
return 'text-primary-400';
case 'not_started':
return 'text-dark-400';
default:
@@ -72,32 +73,33 @@ export const Sidebar = () => {
];
return (
<aside className="w-64 bg-dark-800 border-r border-dark-600 flex flex-col h-screen fixed left-0 top-0">
{/* Header */}
<div className="p-6 border-b border-dark-600">
<aside className="w-64 glass-strong border-r border-primary-400/10 flex flex-col h-screen fixed left-0 top-0">
{/* Header with Atomaste Logo */}
<div className="p-6 border-b border-primary-400/10">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-primary-600 rounded-lg flex items-center justify-center">
<Zap className="w-5 h-5 text-white" />
</div>
<h1 className="text-xl font-bold text-white tracking-tight">Atomizer</h1>
<img
src="/logo_atomaste.png"
alt="Atomaste"
className="h-8 w-auto"
/>
</div>
<p className="text-xs text-dark-300 mt-1 ml-11">Optimization Platform</p>
<p className="text-xs text-primary-400 mt-2 font-mono tracking-wide">ATOMIZER</p>
</div>
{/* Selected Study Info */}
{selectedStudy && (
<div className="p-4 border-b border-dark-600">
<div className="p-4 border-b border-primary-400/10">
<button
onClick={handleBackToHome}
className="flex items-center gap-2 text-sm text-dark-400 hover:text-white
className="flex items-center gap-2 text-sm text-dark-400 hover:text-primary-400
transition-colors mb-3 group"
>
<ChevronLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
Change Study
</button>
<div className="bg-dark-700 rounded-lg p-3">
<div className="text-xs font-medium text-dark-400 uppercase mb-1">Active Study</div>
<div className="glass rounded-lg p-3">
<div className="text-xs font-medium text-primary-400 uppercase mb-1 font-mono">Active Study</div>
<div className="text-white font-medium truncate">{selectedStudy.name}</div>
<div className="flex items-center gap-2 mt-2">
<span className={`flex items-center gap-1 text-xs ${getStatusColor(selectedStudy.status)}`}>
@@ -122,10 +124,10 @@ export const Sidebar = () => {
end={item.to === '/'}
className={({ isActive }) =>
clsx(
'flex items-center gap-3 px-4 py-3 rounded-lg transition-colors duration-200',
'flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-300',
isActive
? 'bg-primary-900/50 text-primary-100 border border-primary-700/50'
: 'text-dark-300 hover:bg-dark-700 hover:text-white'
? 'glass text-primary-400 border border-primary-400/30 shadow-lg shadow-primary-500/10'
: 'text-dark-300 hover:bg-white/5 hover:text-white'
)
}
>
@@ -136,9 +138,24 @@ export const Sidebar = () => {
</nav>
{/* Footer Status */}
<div className="p-4 border-t border-dark-600">
<div className="bg-dark-700 rounded-lg p-4">
<div className="text-xs font-medium text-dark-400 uppercase mb-2">System Status</div>
<div className="p-4 border-t border-primary-400/10 space-y-3">
{/* Assistant Button */}
{onChatToggle && (
<button
onClick={onChatToggle}
className="w-full flex items-center gap-3 px-4 py-3 rounded-lg bg-primary-500/10 border border-primary-500/20 text-primary-400 hover:bg-primary-500/20 transition-colors"
>
<MessageSquare className="w-5 h-5" />
<div className="text-left">
<div className="text-sm font-medium">Ask Assistant</div>
<div className="text-xs text-dark-400">Get help with your study</div>
</div>
</button>
)}
{/* System Status */}
<div className="glass rounded-lg p-4">
<div className="text-xs font-medium text-primary-400 uppercase mb-2 font-mono">System Status</div>
<div className="flex items-center gap-2 text-sm text-green-400">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
Backend Online
@@ -155,17 +172,6 @@ export const Sidebar = () => {
Optimization Paused
</div>
)}
{/* Claude Terminal Status */}
<button
onClick={() => setClaudeTerminalOpen(true)}
className={clsx(
'flex items-center gap-2 text-sm mt-1 w-full text-left hover:opacity-80 transition-opacity',
claudeConnected ? 'text-green-400' : 'text-dark-400'
)}
>
<Terminal className="w-3 h-3" />
{claudeConnected ? 'Claude Connected' : 'Claude Disconnected'}
</button>
</div>
</div>
</aside>