Spaces:
No application file
No application file
| // 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; | |