diff --git a/.gitignore b/.gitignore
index 0839842..6173fda 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,10 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
-/node_modules
+node_modules/
/.pnp
.pnp.js
+.yarn/install-state.gz
# testing
/coverage
@@ -41,4 +42,36 @@ yarn-error.log*
.vercel
#used files
-_*
\ No newline at end of file
+_*
+*.pem
+*.tsbuildinfo
+next-env.d.ts
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+.env
+.venv
+env/
+venv/
+ENV/
+.python-version
+.ruff_cache/
+.mypy_cache/
+
+# IDE
+.idea/
+.vscode/
+*.swp
+*.swo
+
+# Logs
+logs/
+*.log
+
+# Lock files
+package-lock.json
+yarn.lock
+bun.lockb
+uv.lock
\ No newline at end of file
diff --git a/backend/package.json b/backend/package.json
new file mode 100644
index 0000000..8c36515
--- /dev/null
+++ b/backend/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "cotomata-backend",
+ "version": "1.0.0",
+ "description": "Backend server for Cotomata",
+ "main": "src/server.js",
+ "type": "module",
+ "scripts": {
+ "start": "bun src/server.js",
+ "dev": "bun --watch src/server.js"
+ },
+ "dependencies": {
+ "cors": "^2.8.5",
+ "express": "^4.18.2",
+ "redis": "^4.6.12",
+ "socket.io": "^4.8.1"
+ }
+}
\ No newline at end of file
diff --git a/frontend/server.js b/backend/src/server.js
similarity index 65%
rename from frontend/server.js
rename to backend/src/server.js
index e0cef55..3295304 100644
--- a/frontend/server.js
+++ b/backend/src/server.js
@@ -1,11 +1,6 @@
-import { createServer } from 'http';
import { Server } from 'socket.io';
import { createClient } from 'redis';
-import next from 'next';
-
-const dev = process.env.NODE_ENV !== 'production';
-const app = next({ dev });
-const handle = app.getRequestHandler();
+import { createServer } from 'http';
// Redis client configuration
const redisClient = createClient({
@@ -15,19 +10,28 @@ const redisClient = createClient({
// Allowed channels for Redis pub/sub
const allowedChannels = ['Scene:Jack', 'Scene:Jane', 'Human:Jack', 'Jack:Human', 'Agent:Runtime', 'Runtime:Agent'];
-app.prepare().then(async () => {
- // Connect Redis client
- redisClient.on('error', (err) => {
- console.error('Redis error:', err);
- });
+// Connect Redis client
+redisClient.on('error', (err) => {
+ console.error('Redis error:', err);
+});
+
+const init = async () => {
await redisClient.connect();
// Create HTTP server
- const server = createServer((req, res) => {
- handle(req, res);
+ const httpServer = createServer((req, res) => {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ res.writeHead(200);
+ res.end('Socket.IO server running');
+ });
+
+ // Initialize Socket.IO server with CORS
+ const io = new Server(httpServer, {
+ cors: {
+ origin: "http://localhost:3000",
+ methods: ["GET", "POST"]
+ }
});
- // Initialize Socket.IO server
- const io = new Server(server);
// Redis subscriber setup
const subscriber = redisClient.duplicate();
@@ -107,21 +111,40 @@ app.prepare().then(async () => {
// Handle process initialization
socket.on('init_process', async () => {
+ console.log('Received init_process request');
try {
- const response = await fetch('http://localhost:5000/run-dataflow', {
+ const initParams = {
+ node_name: "openhands_node",
+ input_channels: ["Agent:Runtime"],
+ output_channels: ["Runtime:Agent"],
+ modal_session_id: "arpan"
+ };
+
+ const response = await fetch('http://localhost:5000/initialize', {
method: 'POST',
- headers: { 'Content-Type': 'application/json' }
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(initParams)
});
if (!response.ok) {
- throw new Error(`Failed to initialize process: ${response.statusText}`);
+ const errorData = await response.json();
+ throw new Error(`Failed to initialize process: ${errorData.error || response.statusText}`);
}
const result = await response.json();
- socket.emit('init_process_result', result);
- console.log('openhands connected')
+
+ if (result.status === 'initialized') {
+ socket.emit('init_process_result', { success: true });
+ console.log('OpenHands initialized successfully');
+ } else {
+ throw new Error(`Unexpected initialization status: ${result.status}`);
+ }
} catch (err) {
console.error('Error initializing process:', err);
+ socket.emit('init_process_result', {
+ success: false,
+ error: err.message
+ });
}
});
@@ -131,11 +154,10 @@ app.prepare().then(async () => {
});
// Start the server
- const port = process.env.PORT || 3000;
- server.listen(port, (err) => {
- if (err) throw err;
- console.log(`> Ready on http://localhost:3000`);
+ const port = process.env.PORT || 8000;
+ httpServer.listen(port, () => {
+ console.log(`> Backend server ready on http://localhost:${port}`);
});
-});
+};
-export default app;
+init().catch(console.error);
\ No newline at end of file
diff --git a/frontend/app/error.tsx b/frontend/app/error.tsx
new file mode 100644
index 0000000..683665d
--- /dev/null
+++ b/frontend/app/error.tsx
@@ -0,0 +1,37 @@
+"use client"
+
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
+import { Button } from "@/components/ui/button";
+import { useEffect } from "react";
+
+export default function Error({
+ error,
+ reset,
+}: {
+ error: string;
+ reset: () => void;
+}) {
+ useEffect(() => {
+ console.error(error);
+ }, [error]);
+
+ return (
+
+
+
+ Initialization Failed
+
+ {error}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/app/globals.css b/frontend/app/globals.css
index 3abd9be..a3bc090 100644
--- a/frontend/app/globals.css
+++ b/frontend/app/globals.css
@@ -2,85 +2,67 @@
@tailwind components;
@tailwind utilities;
-body {
- font-family: Arial, Helvetica, sans-serif;
-}
-
-@layer utilities {
- .text-balance {
- text-wrap: balance;
- }
-}
-
@layer base {
:root {
--background: 0 0% 100%;
- --foreground: 224 71.4% 4.1%;
+ --foreground: 240 10% 3.9%;
+
--card: 0 0% 100%;
- --card-foreground: 224 71.4% 4.1%;
+ --card-foreground: 240 10% 3.9%;
+
--popover: 0 0% 100%;
- --popover-foreground: 224 71.4% 4.1%;
- --primary: 220.9 39.3% 11%;
- --primary-foreground: 210 20% 98%;
- --secondary: 220 14.3% 95.9%;
- --secondary-foreground: 220.9 39.3% 11%;
- --muted: 220 14.3% 95.9%;
- --muted-foreground: 220 8.9% 46.1%;
- --accent: 220 14.3% 95.9%;
- --accent-foreground: 220.9 39.3% 11%;
+ --popover-foreground: 240 10% 3.9%;
+
+ --primary: 240 5.9% 10%;
+ --primary-foreground: 0 0% 98%;
+
+ --secondary: 240 4.8% 95.9%;
+ --secondary-foreground: 240 5.9% 10%;
+
+ --muted: 240 4.8% 95.9%;
+ --muted-foreground: 240 3.8% 46.1%;
+
+ --accent: 240 4.8% 95.9%;
+ --accent-foreground: 240 5.9% 10%;
+
--destructive: 0 84.2% 60.2%;
- --destructive-foreground: 210 20% 98%;
- --border: 220 13% 91%;
- --input: 220 13% 91%;
- --ring: 224 71.4% 4.1%;
- --chart-1: 12 76% 61%;
- --chart-2: 173 58% 39%;
- --chart-3: 197 37% 24%;
- --chart-4: 43 74% 66%;
- --chart-5: 27 87% 67%;
+ --destructive-foreground: 0 0% 98%;
+
+ --border: 240 5.9% 90%;
+ --input: 240 5.9% 90%;
+ --ring: 240 10% 3.9%;
+
--radius: 0.5rem;
- --sidebar-background: 0 0% 98%;
- --sidebar-foreground: 240 5.3% 26.1%;
- --sidebar-primary: 240 5.9% 10%;
- --sidebar-primary-foreground: 0 0% 98%;
- --sidebar-accent: 240 4.8% 95.9%;
- --sidebar-accent-foreground: 240 5.9% 10%;
- --sidebar-border: 220 13% 91%;
- --sidebar-ring: 217.2 91.2% 59.8%;
}
+
.dark {
- --background: 224 71.4% 4.1%;
- --foreground: 210 20% 98%;
- --card: 224 71.4% 4.1%;
- --card-foreground: 210 20% 98%;
- --popover: 224 71.4% 4.1%;
- --popover-foreground: 210 20% 98%;
- --primary: 210 20% 98%;
- --primary-foreground: 220.9 39.3% 11%;
- --secondary: 215 27.9% 16.9%;
- --secondary-foreground: 210 20% 98%;
- --muted: 215 27.9% 16.9%;
- --muted-foreground: 217.9 10.6% 64.9%;
- --accent: 215 27.9% 16.9%;
- --accent-foreground: 210 20% 98%;
+ --background: 240 10% 3.9%;
+ --foreground: 0 0% 98%;
+
+ --card: 240 10% 3.9%;
+ --card-foreground: 0 0% 98%;
+
+ --popover: 240 10% 3.9%;
+ --popover-foreground: 0 0% 98%;
+
+ --primary: 0 0% 98%;
+ --primary-foreground: 240 5.9% 10%;
+
+ --secondary: 240 3.7% 15.9%;
+ --secondary-foreground: 0 0% 98%;
+
+ --muted: 240 3.7% 15.9%;
+ --muted-foreground: 240 5% 64.9%;
+
+ --accent: 240 3.7% 15.9%;
+ --accent-foreground: 0 0% 98%;
+
--destructive: 0 62.8% 30.6%;
- --destructive-foreground: 210 20% 98%;
- --border: 215 27.9% 16.9%;
- --input: 215 27.9% 16.9%;
- --ring: 216 12.2% 83.9%;
- --chart-1: 220 70% 50%;
- --chart-2: 160 60% 45%;
- --chart-3: 30 80% 55%;
- --chart-4: 280 65% 60%;
- --chart-5: 340 75% 55%;
- --sidebar-background: 240 5.9% 10%;
- --sidebar-foreground: 240 4.8% 95.9%;
- --sidebar-primary: 224.3 76.3% 48%;
- --sidebar-primary-foreground: 0 0% 100%;
- --sidebar-accent: 240 3.7% 15.9%;
- --sidebar-accent-foreground: 240 4.8% 95.9%;
- --sidebar-border: 240 3.7% 15.9%;
- --sidebar-ring: 217.2 91.2% 59.8%;
+ --destructive-foreground: 0 0% 98%;
+
+ --border: 240 3.7% 15.9%;
+ --input: 240 3.7% 15.9%;
+ --ring: 240 4.9% 83.9%;
}
}
diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx
index efae28d..46950bf 100644
--- a/frontend/app/layout.tsx
+++ b/frontend/app/layout.tsx
@@ -1,20 +1,6 @@
import type { Metadata } from "next";
-import localFont from "next/font/local";
import "./globals.css";
-import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
-import { AppSidebar } from "@/components/sidebar";
-import { CSSProperties } from 'react';
-
-const geistSans = localFont({
- src: "./fonts/GeistVF.woff",
- variable: "--font-geist-sans",
- weight: "100 900",
-});
-const geistMono = localFont({
- src: "./fonts/GeistMonoVF.woff",
- variable: "--font-geist-mono",
- weight: "100 900",
-});
+import { LoadingProvider } from '../context/loading-provider';
export const metadata: Metadata = {
title: "Cotomata",
@@ -23,26 +9,16 @@ export const metadata: Metadata = {
export default function RootLayout({
children,
-}: Readonly<{
+}: {
children: React.ReactNode;
-}>) {
+}) {
return (
-
-
-
-
-
- {children}
-
-
+
+
+
+ {children}
+
);
-}
+}
\ No newline at end of file
diff --git a/frontend/app/loading.tsx b/frontend/app/loading.tsx
new file mode 100644
index 0000000..8ccff6a
--- /dev/null
+++ b/frontend/app/loading.tsx
@@ -0,0 +1,15 @@
+import { Loader2 } from "lucide-react";
+
+export default function Loading() {
+ return (
+
+
+
+
Initializing Cotomata...
+
+ This may take up to 5 minutes. Please wait while we set up your environment.
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx
index 6672fa9..77708be 100644
--- a/frontend/app/page.tsx
+++ b/frontend/app/page.tsx
@@ -1,395 +1,184 @@
-/**
- * app/page.tsx
- *
- * This file serves as the main entry point for the React application. It manages the overall
- * application state, handles socket connections for real-time communication, and coordinates
- * interactions between various components such as the code editor, terminal, chat interface,
- * and file system.
- *
- * Key Features:
- * - Establishes a WebSocket connection to the server for real-time updates.
- * - Manages application state using React hooks, including messages, terminal output,
- * and active panels.
- * - Handles incoming messages from the server, including agent actions and chat messages.
- * - Provides a user interface for code editing, browsing, and terminal commands.
- *
- * Components Used:
- * - CodeEditor: For editing code files.
- * - FileSystem: For displaying and managing files.
- * - Terminal: For executing commands and displaying output.
- * - ChatInterface: For user interaction and messaging.
- * - Browser: For displaying web content.
- * - Sidebar: For navigation between different application panels.
- * - SceneContext: For displaying context messages from agents.
- *
- */
"use client"
-import React, { useEffect, useState } from 'react';
-// import io from 'socket.io-client';
-// import { CodeEditor } from '@/components/CodeEditor/CodeEditor';
-// import { FileSystem } from '@/components/CodeEditor/FileSystem';
-// import { Terminal } from '@/components/Terminal/Terminal';
-// import { ChatInterface } from '@/components/ChatInterface/ChatInterface';
-// import { Browser } from '@/components/Browser/Browser';
-// import { Sidebar } from '@/components/Sidebar/Sidebar';
-// import { SceneContext } from '@/components/Sidebar/SceneContext';
-import { useFileSystem } from "@/hooks/useFileSystem";
-import { FileSystem } from "@/components/file-system";
-import { io, Socket } from 'socket.io-client';
-import { CodeEditor } from '@/components/code-editor';
-import { Browser } from '@/components/browser';
-import { SceneContext } from '@/components/scene-context';
-import { ChatInterface } from '@/components/chat-interface';
-import { Terminal } from '@/components/terminal';
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
-import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
-import { SidebarTrigger } from '@/components/ui/sidebar';
-
-type PanelOption = 'fileSystem' | 'sceneContext';
-
-// need for run-dataflow post req
-
-// async function runPythonCommand() {
-// const response = await fetch('/api/run-agent', {
-// method: 'POST',
-// headers: { 'Content-Type': 'application/json' },
-// body: JSON.stringify({ command: 'print("Hello, Python!")' }),
-// });
-// const result = await response.json();
-// console.log(result);
-// }
-
-export default function App() {
- const {
- fileSystem,
- openFiles,
- activeFile,
- handleFileSelect,
- handleFileClose,
- handleFileChange,
- addFile,
- setActiveFile,
- updateFileSystemFromList
- } = useFileSystem();
-
- const [socket, setSocket] = useState(null);
- const [activePanel, setActivePanel] = useState('fileSystem');
- const [activeTab, setActiveTab] = useState<'editor' | 'browser'>('editor');
- const [browserUrl, setBrowserUrl] = useState('https://example.com');
- const [messages, setMessages] = useState>([]);
- const [terminalMessages, setTerminalMessages] = useState([]);
- const [sceneMessages, setSceneMessages] = useState<{ text: string, agentName: string }[]>([]);
-
- useEffect(() => {
-
- // Initialize socket connection to the server
- const socketInstance = io('http://localhost:3000', {
- transports: ['websocket'],
- reconnection: true
- });
-
- // Log connection status
- socketInstance.on('connect', () => {
- console.log('Connected to server with ID:', socketInstance.id);
- // runPythonCommand();
- });
- console.log('socket connect', socketInstance)
-
- // Log connection errors
- socketInstance.on('connect_error', (error) => {
- console.error('Connection error:', error);
- });
-
- socketInstance.emit('init_process');
- socketInstance.on('init_process_result', (result) => {
- if (result.status === 'error') {
- console.error('Process initialization failed:', result.error);
- } else {
- console.log('Process started successfully');
- }
- });
-
-
- setSocket(socketInstance);
-
- // Cleanup on component unmount
- return () => {
- if (socketInstance) {
- socketInstance.disconnect();
- }
- };
- }, []);
-
- useEffect(() => {
- // Function to handle new messages received from the socket
- const handleNewMessage = (data: any) => {
- try {
- // Parse the incoming message data
- const messageData = JSON.parse(data.message);
-
- // Log the entire messageData for debugging
- console.log('Received message data:', messageData);
-
- // Check if messageData.data is defined
- if (!messageData.data) {
- console.error('messageData.data is undefined:', messageData);
- return; // Exit if data is not present
- }
-
- // Handle Scene context messages
- if (data.channel.startsWith('Scene:')) {
- if (messageData.data.data_type === "text") {
- // Update scene messages and set active panel to scene context
- setSceneMessages(prev => [...prev, { text: messageData.data.text, agentName: data.channel }]);
- setActivePanel('sceneContext');
- }
- return;
- }
-
- // Check if it's an agent action
- if (messageData.data.data_type === "agent_action") {
- handleAgentAction(messageData);
- }
- // Check if it's a command output
- else if (messageData.data.data_type === "text" &&
- messageData.data.text.includes("CmdOutputObservation") &&
- !messageData.data.text.includes("**FILE_SYSTEM_REFRESH**")) {
- // Try to extract output from success case (exit code=0)
- let parts = messageData.data.text.split("**CmdOutputObservation (source=None, exit code=0)**");
-
- // If not found, try to extract from error case (exit code=1)
- if (parts.length === 1) {
- parts = messageData.data.text.split("**CmdOutputObservation (source=None, exit code=1)**");
- }
-
- // If we found output in either case, add it to terminal messages
- if (parts.length > 1) {
- const outputText = parts[1].trim();
- // Update terminal messages with the command output
- setTerminalMessages(prev => [...prev, outputText]);
- }
- }
-
- // Handle file structure refresh response
- if (messageData.data.data_type === "text" &&
- messageData.data.text.includes("CmdOutputObservation") &&
- messageData.data.text.includes("**FILE_SYSTEM_REFRESH**")) {
- const parts = messageData.data.text.split("**CmdOutputObservation (source=None, exit code=0)**");
- if (parts.length > 1) {
- const fileList = parts[1].trim().split('\n').filter(Boolean).slice(1);
- updateFileSystemFromList(fileList);
- }
- }
-
- // Handle file content response
- if (messageData.data.data_type === "text" &&
- messageData.data.text.includes("**FILE_CONTENT**")) {
- // Split the response by new lines
- const lines = messageData.data.text.split('\n').slice(1);
- console.log('lines', lines);
-
- // Check if the response has at least 3 parts
- if (lines.length >= 3) {
- const filePath = lines[1].trim(); // The second line contains the file path
- console.log('filePath', filePath);
- const fileContent = lines.slice(2).join('\n').trim(); // The rest is the file content
- console.log('fileContent', fileContent);
- // Update the file content using the handleFileChange function
- handleFileChange(filePath, fileContent); // Update the file content
- }
- }
-
- } catch (error) {
- // Log any errors that occur during message parsing
- console.error('Error parsing message:', error);
- }
- };
-
- // Listen for new messages from the socket
- socket?.on('new_message', handleNewMessage);
- return () => {
- // Clean up the listener on component unmount
- socket?.off('new_message', handleNewMessage);
- };
- }, [updateFileSystemFromList]);
-
- // Function to handle actions from agents
- const handleAgentAction = (messageData: any) => {
- // Check if messageData.data is defined
- if (!messageData.data) {
- console.error('messageData.data is undefined:', messageData);
- return; // Exit if data is not present
- }
-
- const actionType = messageData.data.action_type; // Get the action type from the message
- const agentName = messageData.data.agent_name; // Get the agent's name
-
- console.log('Processing agent action:', actionType, 'from', agentName);
-
- switch (actionType) {
- case "speak":
- // Handle agent speaking
- const newMessage = {
- text: `${agentName}: ${messageData.data.argument}`,
- type: 'message' as const
- };
- // Update messages state with the new message
- setMessages(prev => [...prev, newMessage]);
- break;
-
- case "thought":
- // Handle agent's thoughts
- setMessages(prev => [...prev, {
- text: `💠${agentName} is thinking: ${messageData.data.argument}`,
- type: 'status' as const
- }]);
- break;
-
- case "write":
- // Handle file writing
- const filePath = messageData.data.path; // Get the file path
- const fileContent = messageData.data.argument; // Get the file content
-
- // Check if file already exists in openFiles
- const existingFileIndex = openFiles.findIndex(f => f.path === filePath);
-
- if (existingFileIndex !== -1) {
- // Update existing file content
- handleFileChange(filePath, fileContent);
- } else {
- // Add new file
- addFile(filePath, fileContent);
- }
-
- // Set the active file and update the UI
- setActiveFile(filePath);
- setActiveTab('editor');
- setActivePanel('fileSystem');
- setMessages(prev => [...prev, {
- text: `${agentName} is writing code...`,
- type: 'status' as const
- }]);
- break;
-
- case "read":
- // Check if messageData.data.text is defined
- setMessages(prev => [...prev, {
- text: `${agentName} is reading file ${messageData.data.path}`,
- type: 'status' as const
- }]);
- break;
-
- case "run":
- // Check if messageData.data.text is defined
- // Handle command execution
- setTerminalMessages(prev => [...prev, `$ ${messageData.data.argument}`]);
- setMessages(prev => [...prev, {
- text: `${agentName} is executing a command...`,
- type: 'status' as const
- }]);
- break;
-
- case "browse":
- // Handle browsing action
- const url = messageData.data.argument; // Get the URL to browse
- setBrowserUrl(url);
- setActiveTab('browser');
- setMessages(prev => [...prev, {
- text: `${agentName} is browsing ${url}`,
- type: 'status' as const
- }]);
- break;
-
- default:
- // Log unknown action types for debugging
- console.log('Unknown action type:', actionType);
+import { Button } from "@/components/ui/button";
+import { useRouter } from "next/navigation";
+import { useLoading } from "../context/loading-provider";
+import Loading from "./loading";
+import Error from "./error";
+import { useState } from "react";
+import { Sparkles, Code2 } from 'lucide-react';
+import { motion } from 'framer-motion';
+
+export default function LandingPage() {
+ const router = useRouter();
+ const { isReady, error, socket } = useLoading();
+ const [isInitializing, setIsInitializing] = useState(false);
+
+ const handleStartSession = () => {
+ console.log('Start Session button clicked');
+ setIsInitializing(true);
+ if (socket) {
+ console.log('Emitting init_process event');
+ socket.emit('init_process');
+ } else {
+ console.error('Socket not available');
+ setIsInitializing(false);
}
};
- // Listen for chat messages from the socket
- socket?.on('chat_message', (message: string) => {
- // Update messages state with the new chat message
- setMessages(prev => [...prev, {
- text: message,
- type: 'message' as const
- }]);
- });
+ if (error) {
+ return router.refresh()} />;
+ }
+
+ if (isReady) {
+ router.push('/workspace');
+ return null;
+ }
return (
-
-
- {/* setActivePanel('fileSystem')}>
- File System
-
- setActivePanel('sceneContext')}>
- Scene Context
- */}
- {activePanel === 'fileSystem' ? (
-
- ) : (
-
- )}
-
-
-
+ {/* Header */}
+
+
-
-
-
- Editor
- Browser
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- // Update messages state with the user's message
- setMessages(prev => [...prev, {
- text: `User: ${text}`,
- type: 'message' as const
- }]);
- }}
+
+
+ Cotomata
+
+
+
+ {/* Main Content */}
+
+ {/* Background Effects */}
+
-
+
+
+ {/* Content */}
+
+
+
+
+
+
+ {/* Split text into individual letters for animation */}
+ {"Cotomata".split("").map((letter, index) => (
+
+ {letter}
+
+ ))}
+
+
+
+ Research Prototype for Studying Cooperative Agents
+
+
+
+
+ {isInitializing ? (
+
+
+ Initializing your development environment...
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+
);
}
\ No newline at end of file
diff --git a/frontend/app/workspace/layout.tsx b/frontend/app/workspace/layout.tsx
new file mode 100644
index 0000000..8745503
--- /dev/null
+++ b/frontend/app/workspace/layout.tsx
@@ -0,0 +1,49 @@
+import type { Metadata } from "next";
+import localFont from "next/font/local";
+import "../globals.css";
+import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
+import { AppSidebar } from "@/components/sidebar";
+import { CSSProperties } from 'react';
+import { LoadingProvider } from "../../context/loading-provider";
+
+const geistSans = localFont({
+ src: "../fonts/GeistVF.woff",
+ variable: "--font-geist-sans",
+ weight: "100 900",
+});
+const geistMono = localFont({
+ src: "../fonts/GeistMonoVF.woff",
+ variable: "--font-geist-mono",
+ weight: "100 900",
+});
+
+export const metadata: Metadata = {
+ title: "Cotomata",
+ description: "Research Prototype for Studying Cooperative Agents",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/frontend/app/workspace/page.tsx b/frontend/app/workspace/page.tsx
new file mode 100644
index 0000000..3b015e8
--- /dev/null
+++ b/frontend/app/workspace/page.tsx
@@ -0,0 +1,402 @@
+/**
+ * app/page.tsx
+ *
+ * This file serves as the main entry point for the React application. It manages the overall
+ * application state, handles socket connections for real-time communication, and coordinates
+ * interactions between various components such as the code editor, terminal, chat interface,
+ * and file system.
+ *
+ * Key Features:
+ * - Establishes a WebSocket connection to the server for real-time updates.
+ * - Manages application state using React hooks, including messages, terminal output,
+ * and active panels.
+ * - Handles incoming messages from the server, including agent actions and chat messages.
+ * - Provides a user interface for code editing, browsing, and terminal commands.
+ *
+ * Components Used:
+ * - CodeEditor: For editing code files.
+ * - FileSystem: For displaying and managing files.
+ * - Terminal: For executing commands and displaying output.
+ * - ChatInterface: For user interaction and messaging.
+ * - Browser: For displaying web content.
+ * - Sidebar: For navigation between different application panels.
+ * - SceneContext: For displaying context messages from agents.
+ *
+ */
+"use client"
+
+import React, { useEffect, useState } from 'react';
+// import io from 'socket.io-client';
+// import { CodeEditor } from '@/components/CodeEditor/CodeEditor';
+// import { FileSystem } from '@/components/CodeEditor/FileSystem';
+// import { Terminal } from '@/components/Terminal/Terminal';
+// import { ChatInterface } from '@/components/ChatInterface/ChatInterface';
+// import { Browser } from '@/components/Browser/Browser';
+// import { Sidebar } from '@/components/Sidebar/Sidebar';
+// import { SceneContext } from '@/components/Sidebar/SceneContext';
+import { useFileSystem } from "@/hooks/useFileSystem";
+import { FileSystem } from "@/components/file-system";
+import { io, Socket } from 'socket.io-client';
+import { CodeEditor } from '@/components/code-editor';
+import { Browser } from '@/components/browser';
+import { SceneContext } from '@/components/scene-context';
+import { ChatInterface } from '@/components/chat-interface';
+import { Terminal } from '@/components/terminal';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
+import { SidebarTrigger } from '@/components/ui/sidebar';
+import { useLoading } from "../../context/loading-provider";
+import { redirect } from "next/navigation";
+
+type PanelOption = 'fileSystem' | 'sceneContext';
+
+// need for run-dataflow post req
+
+// async function runPythonCommand() {
+// const response = await fetch('/api/run-agent', {
+// method: 'POST',
+// headers: { 'Content-Type': 'application/json' },
+// body: JSON.stringify({ command: 'print("Hello, Python!")' }),
+// });
+// const result = await response.json();
+// console.log(result);
+// }
+
+export default function App() {
+ const {
+ fileSystem,
+ openFiles,
+ activeFile,
+ handleFileSelect,
+ handleFileClose,
+ handleFileChange,
+ addFile,
+ setActiveFile,
+ updateFileSystemFromList
+ } = useFileSystem();
+
+// const [socket, setSocket] = useState(null);
+ const [activePanel, setActivePanel] = useState('fileSystem');
+ const [activeTab, setActiveTab] = useState<'editor' | 'browser'>('editor');
+ const [browserUrl, setBrowserUrl] = useState('https://example.com');
+ const [messages, setMessages] = useState>([]);
+ const [terminalMessages, setTerminalMessages] = useState([]);
+ const [sceneMessages, setSceneMessages] = useState<{ text: string, agentName: string }[]>([]);
+ const { isReady, socket } = useLoading();
+
+ if (!isReady) {
+ redirect('/');
+ }
+
+// useEffect(() => {
+
+// // Initialize socket connection to the server
+// const socketInstance = io('http://localhost:3000', {
+// transports: ['websocket'],
+// reconnection: true
+// });
+
+// // Log connection status
+// socketInstance.on('connect', () => {
+// console.log('Connected to server with ID:', socketInstance.id);
+// // runPythonCommand();
+// });
+// console.log('socket connect', socketInstance)
+
+// // Log connection errors
+// socketInstance.on('connect_error', (error) => {
+// console.error('Connection error:', error);
+// });
+
+// socketInstance.emit('init_process');
+// socketInstance.on('init_process_result', (result) => {
+// if (result.status === 'error') {
+// console.error('Process initialization failed:', result.error);
+// } else {
+// console.log('Process started successfully');
+// }
+// });
+
+
+// setSocket(socketInstance);
+
+// // Cleanup on component unmount
+// return () => {
+// if (socketInstance) {
+// socketInstance.disconnect();
+// }
+// };
+// }, []);
+
+ useEffect(() => {
+ // Function to handle new messages received from the socket
+ const handleNewMessage = (data: any) => {
+ try {
+ // Parse the incoming message data
+ const messageData = JSON.parse(data.message);
+
+ // Log the entire messageData for debugging
+ console.log('Received message data:', messageData);
+
+ // Check if messageData.data is defined
+ if (!messageData.data) {
+ console.error('messageData.data is undefined:', messageData);
+ return; // Exit if data is not present
+ }
+
+ // Handle Scene context messages
+ if (data.channel.startsWith('Scene:')) {
+ if (messageData.data.data_type === "text") {
+ // Update scene messages and set active panel to scene context
+ setSceneMessages(prev => [...prev, { text: messageData.data.text, agentName: data.channel }]);
+ setActivePanel('sceneContext');
+ }
+ return;
+ }
+
+ // Check if it's an agent action
+ if (messageData.data.data_type === "agent_action") {
+ handleAgentAction(messageData);
+ }
+ // Check if it's a command output
+ else if (messageData.data.data_type === "text" &&
+ messageData.data.text.includes("CmdOutputObservation") &&
+ !messageData.data.text.includes("**FILE_SYSTEM_REFRESH**")) {
+ // Try to extract output from success case (exit code=0)
+ let parts = messageData.data.text.split("**CmdOutputObservation (source=None, exit code=0)**");
+
+ // If not found, try to extract from error case (exit code=1)
+ if (parts.length === 1) {
+ parts = messageData.data.text.split("**CmdOutputObservation (source=None, exit code=1)**");
+ }
+
+ // If we found output in either case, add it to terminal messages
+ if (parts.length > 1) {
+ const outputText = parts[1].trim();
+ // Update terminal messages with the command output
+ setTerminalMessages(prev => [...prev, outputText]);
+ }
+ }
+
+ // Handle file structure refresh response
+ if (messageData.data.data_type === "text" &&
+ messageData.data.text.includes("CmdOutputObservation") &&
+ messageData.data.text.includes("**FILE_SYSTEM_REFRESH**")) {
+ const parts = messageData.data.text.split("**CmdOutputObservation (source=None, exit code=0)**");
+ if (parts.length > 1) {
+ const fileList = parts[1].trim().split('\n').filter(Boolean).slice(1);
+ updateFileSystemFromList(fileList);
+ }
+ }
+
+ // Handle file content response
+ if (messageData.data.data_type === "text" &&
+ messageData.data.text.includes("**FILE_CONTENT**")) {
+ // Split the response by new lines
+ const lines = messageData.data.text.split('\n').slice(1);
+ console.log('lines', lines);
+
+ // Check if the response has at least 3 parts
+ if (lines.length >= 3) {
+ const filePath = lines[1].trim(); // The second line contains the file path
+ console.log('filePath', filePath);
+ const fileContent = lines.slice(2).join('\n').trim(); // The rest is the file content
+ console.log('fileContent', fileContent);
+ // Update the file content using the handleFileChange function
+ handleFileChange(filePath, fileContent); // Update the file content
+ }
+ }
+
+ } catch (error) {
+ // Log any errors that occur during message parsing
+ console.error('Error parsing message:', error);
+ }
+ };
+
+ // Listen for new messages from the socket
+ socket?.on('new_message', handleNewMessage);
+ return () => {
+ // Clean up the listener on component unmount
+ socket?.off('new_message', handleNewMessage);
+ };
+ }, [updateFileSystemFromList]);
+
+ // Function to handle actions from agents
+ const handleAgentAction = (messageData: any) => {
+ // Check if messageData.data is defined
+ if (!messageData.data) {
+ console.error('messageData.data is undefined:', messageData);
+ return; // Exit if data is not present
+ }
+
+ const actionType = messageData.data.action_type; // Get the action type from the message
+ const agentName = messageData.data.agent_name; // Get the agent's name
+
+ console.log('Processing agent action:', actionType, 'from', agentName);
+
+ switch (actionType) {
+ case "speak":
+ // Handle agent speaking
+ const newMessage = {
+ text: `${agentName}: ${messageData.data.argument}`,
+ type: 'message' as const
+ };
+ // Update messages state with the new message
+ setMessages(prev => [...prev, newMessage]);
+ break;
+
+ case "thought":
+ // Handle agent's thoughts
+ setMessages(prev => [...prev, {
+ text: `💠${agentName} is thinking: ${messageData.data.argument}`,
+ type: 'status' as const
+ }]);
+ break;
+
+ case "write":
+ // Handle file writing
+ const filePath = messageData.data.path; // Get the file path
+ const fileContent = messageData.data.argument; // Get the file content
+
+ // Check if file already exists in openFiles
+ const existingFileIndex = openFiles.findIndex(f => f.path === filePath);
+
+ if (existingFileIndex !== -1) {
+ // Update existing file content
+ handleFileChange(filePath, fileContent);
+ } else {
+ // Add new file
+ addFile(filePath, fileContent);
+ }
+
+ // Set the active file and update the UI
+ setActiveFile(filePath);
+ setActiveTab('editor');
+ setActivePanel('fileSystem');
+ setMessages(prev => [...prev, {
+ text: `${agentName} is writing code...`,
+ type: 'status' as const
+ }]);
+ break;
+
+ case "read":
+ // Check if messageData.data.text is defined
+ setMessages(prev => [...prev, {
+ text: `${agentName} is reading file ${messageData.data.path}`,
+ type: 'status' as const
+ }]);
+ break;
+
+ case "run":
+ // Check if messageData.data.text is defined
+ // Handle command execution
+ setTerminalMessages(prev => [...prev, `$ ${messageData.data.argument}`]);
+ setMessages(prev => [...prev, {
+ text: `${agentName} is executing a command...`,
+ type: 'status' as const
+ }]);
+ break;
+
+ case "browse":
+ // Handle browsing action
+ const url = messageData.data.argument; // Get the URL to browse
+ setBrowserUrl(url);
+ setActiveTab('browser');
+ setMessages(prev => [...prev, {
+ text: `${agentName} is browsing ${url}`,
+ type: 'status' as const
+ }]);
+ break;
+
+ default:
+ // Log unknown action types for debugging
+ console.log('Unknown action type:', actionType);
+ }
+ };
+
+ // Listen for chat messages from the socket
+ socket?.on('chat_message', (message: string) => {
+ // Update messages state with the new chat message
+ setMessages(prev => [...prev, {
+ text: message,
+ type: 'message' as const
+ }]);
+ });
+
+ return (
+
+
+ {/* setActivePanel('fileSystem')}>
+ File System
+
+ setActivePanel('sceneContext')}>
+ Scene Context
+ */}
+ {activePanel === 'fileSystem' ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+ Editor
+ Browser
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ // Update messages state with the user's message
+ setMessages(prev => [...prev, {
+ text: `User: ${text}`,
+ type: 'message' as const
+ }]);
+ }}
+ />
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/bun.lockb b/frontend/bun.lockb
index 5ebbab9..1c26f9c 100755
Binary files a/frontend/bun.lockb and b/frontend/bun.lockb differ
diff --git a/frontend/components/code-editor.tsx b/frontend/components/code-editor.tsx
index 694ea79..a6ea79b 100644
--- a/frontend/components/code-editor.tsx
+++ b/frontend/components/code-editor.tsx
@@ -75,54 +75,57 @@ export const CodeEditor = ({
return (
-
-
- {openFiles.map((file) => (
-
-