workofarttattoo's picture
🚀 QuLab MCP Server: Complete Experiment Taxonomy Deployment
91994bf
// App.tsx – Main application entry with dark‑mode toggle and psychedelic accents
import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import ReactFlow, {
MiniMap,
Controls,
Background,
addEdge,
useNodesState,
useEdgesState,
ReactFlowProvider,
type ReactFlowInstance,
} from 'reactflow';
import 'reactflow/dist/style.css';
import Sidebar from './Sidebar';
import axios from 'axios';
import AgentNode from './AgentNode';
import AgentFeed from './AgentFeed';
import VoiceControl from './VoiceControl';
import AgentProposals from './AgentProposals';
// ---------------------------------------------------------------------------
// Node type registration – we expose a custom AgentNode component.
// ---------------------------------------------------------------------------
// Node type registration moved inside component to access callbacks
// ---------------------------------------------------------------------------
// Initial graph data (can be expanded later).
// ---------------------------------------------------------------------------
const initialNodes = [
{ id: '1', type: 'input', position: { x: 250, y: 5 }, data: { label: 'Start' } },
];
const initialEdges: any[] = [];
let idCounter = 2;
const getId = () => `${idCounter++}`;
function App() {
// -----------------------------------------------------------------------
// State hooks
// -----------------------------------------------------------------------
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const [reactFlowInstance, setReactFlowInstance] = useState<ReactFlowInstance | null>(null);
const [latestProposal, setLatestProposal] = useState<any>(null);
const [theme, setTheme] = useState<'light' | 'dark'>('light');
// Apply theme to the document root – enables CSS variables defined in index.css
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
}, [theme]);
// -----------------------------------------------------------------------
// WebSocket for live agent proposals
// -----------------------------------------------------------------------
useEffect(() => {
const ws = new WebSocket('ws://localhost:8000/ws/agent-feed');
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'agent_proposal') {
setLatestProposal(message);
}
};
return () => ws.close();
}, []);
// -----------------------------------------------------------------------
// Helper callbacks
// -----------------------------------------------------------------------
const onNodeDataChange = useCallback((id: string, data: any) => {
setNodes((nds) => nds.map((node) => (node.id === id ? { ...node, data } : node)));
}, []);
const onConnect = useCallback((params: any) => {
setEdges((eds) => addEdge(params, eds));
}, []);
const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, []);
const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault();
const type = event.dataTransfer.getData('application/reactflow');
const agentData = JSON.parse(event.dataTransfer.getData('application/json'));
if (!type) return;
const position = reactFlowInstance?.screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
if (!position) return;
const newNode = {
id: getId(),
type: 'agentNode',
position,
data: {
label: `${agentData.agent_type} Node`,
agent: agentData,
parameters: '',
onDataChange: onNodeDataChange,
},
};
setNodes((nds) => nds.concat(newNode));
},
[reactFlowInstance, onNodeDataChange]
);
const onRunWorkflow = useCallback(() => {
const workflow = {
nodes: nodes.map((n) => ({ id: n.id, type: n.type, position: n.position, data: n.data })),
edges: edges.map((e) => ({ id: e.id, source: e.source, target: e.target })),
};
axios
.post('http://localhost:8000/workflows', workflow)
.then((response) => {
alert('Workflow executed successfully!');
console.log('Result:', response.data);
})
.catch((error) => {
alert('Failed to execute workflow.');
console.error(error);
});
}, [nodes, edges]);
// -----------------------------------------------------------------------
// Memoized node types (required by ReactFlow)
// -----------------------------------------------------------------------
const memoizedNodeTypes = useMemo(() => ({
agentNode: (props: any) => <AgentNode {...props} onDataChange={onNodeDataChange} />,
}), [onNodeDataChange]);
// -----------------------------------------------------------------------
// UI – dark‑mode toggle button and the main React Flow canvas
// -----------------------------------------------------------------------
return (
<div className="app-container" ref={useRef(null)}>
{/* Theme toggle */}
<button className="theme-toggle" onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
{theme === 'light' ? '🌙 Dark Mode' : '☀️ Light Mode'}
</button>
<ReactFlowProvider>
<Sidebar />
<div className="main-content">
<div className="reactflow-wrapper">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onInit={setReactFlowInstance}
onDrop={onDrop}
onDragOver={onDragOver}
fitView
nodeTypes={memoizedNodeTypes}
>
<Controls />
<MiniMap />
<Background variant={"dots" as any} gap={12} size={1} />
</ReactFlow>
</div>
<AgentFeed />
</div>
<div className="sidebar-right">
<VoiceControl />
<AgentProposals newProposal={latestProposal} />
</div>
<div className="run-button-container">
<button onClick={onRunWorkflow}>Run Workflow</button>
</div>
</ReactFlowProvider>
</div>
);
}
export default App;