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