Add comprehensive frontend UI and distributed infrastructure

Frontend Enhancements:
- Complete React TypeScript frontend with modern UI components
- Distributed workflows management interface with real-time updates
- Socket.IO integration for live agent status monitoring
- Agent management dashboard with cluster visualization
- Project management interface with metrics and task tracking
- Responsive design with proper error handling and loading states

Backend Infrastructure:
- Distributed coordinator for multi-agent workflow orchestration
- Cluster management API with comprehensive agent operations
- Enhanced database models for agents and projects
- Project service for filesystem-based project discovery
- Performance monitoring and metrics collection
- Comprehensive API documentation and error handling

Documentation:
- Complete distributed development guide (README_DISTRIBUTED.md)
- Comprehensive development report with architecture insights
- System configuration templates and deployment guides

The platform now provides a complete web interface for managing the distributed AI cluster
with real-time monitoring, workflow orchestration, and agent coordination capabilities.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-07-10 08:41:59 +10:00
parent fc0eec91ef
commit 85bf1341f3
28348 changed files with 2646896 additions and 69 deletions

View File

@@ -0,0 +1,260 @@
import { useEffect, useState, useRef, useCallback } from 'react';
import { io, Socket } from 'socket.io-client';
export interface SocketIOMessage {
type: string;
data: any;
timestamp: string;
}
export interface SocketIOHookOptions {
url: string;
autoConnect?: boolean;
reconnectionAttempts?: number;
reconnectionDelay?: number;
onMessage?: (message: SocketIOMessage) => void;
onConnect?: () => void;
onDisconnect?: () => void;
onError?: (error: any) => void;
}
export interface SocketIOHookReturn {
socket: Socket | null;
isConnected: boolean;
connectionState: 'connecting' | 'connected' | 'disconnected' | 'error';
sendMessage: (event: string, data: any) => void;
joinRoom: (room: string) => void;
leaveRoom: (room: string) => void;
subscribe: (events: string[], room?: string) => void;
lastMessage: SocketIOMessage | null;
connect: () => void;
disconnect: () => void;
reconnect: () => void;
}
export const useSocketIO = (options: SocketIOHookOptions): SocketIOHookReturn => {
const {
url,
autoConnect = true,
reconnectionAttempts = 5,
reconnectionDelay = 1000,
onMessage,
onConnect,
onDisconnect,
onError
} = options;
const [socket, setSocket] = useState<Socket | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [connectionState, setConnectionState] = useState<'connecting' | 'connected' | 'disconnected' | 'error'>('disconnected');
const [lastMessage, setLastMessage] = useState<SocketIOMessage | null>(null);
const reconnectAttemptsRef = useRef(0);
const shouldReconnectRef = useRef(true);
const connect = useCallback(() => {
if (socket?.connected) {
return;
}
try {
setConnectionState('connecting');
console.log('Socket.IO connecting to:', url);
const socketInstance = io(url, {
transports: ['websocket', 'polling'],
upgrade: true,
rememberUpgrade: true,
autoConnect: true,
reconnection: true,
reconnectionAttempts,
reconnectionDelay,
timeout: 20000,
forceNew: false
});
socketInstance.on('connect', () => {
console.log('Socket.IO connected');
setIsConnected(true);
setConnectionState('connected');
reconnectAttemptsRef.current = 0;
onConnect?.();
});
socketInstance.on('disconnect', (reason) => {
console.log('Socket.IO disconnected:', reason);
setIsConnected(false);
setConnectionState('disconnected');
onDisconnect?.();
});
socketInstance.on('connect_error', (error) => {
console.error('Socket.IO connection error:', error);
setConnectionState('error');
onError?.(error);
});
socketInstance.on('reconnect_error', (error) => {
console.error('Socket.IO reconnection error:', error);
setConnectionState('error');
onError?.(error);
});
socketInstance.on('reconnect', (attemptNumber) => {
console.log(`Socket.IO reconnected after ${attemptNumber} attempts`);
setIsConnected(true);
setConnectionState('connected');
reconnectAttemptsRef.current = 0;
onConnect?.();
});
socketInstance.on('reconnect_failed', () => {
console.error('Socket.IO reconnection failed');
setConnectionState('error');
onError?.(new Error('Reconnection failed'));
});
// Listen for connection confirmation
socketInstance.on('connection_confirmed', (data) => {
console.log('Socket.IO connection confirmed:', data);
setLastMessage({
type: 'connection_confirmed',
data,
timestamp: new Date().toISOString()
});
});
// Listen for room events
socketInstance.on('room_joined', (data) => {
console.log('Socket.IO room joined:', data);
setLastMessage({
type: 'room_joined',
data,
timestamp: new Date().toISOString()
});
});
socketInstance.on('room_left', (data) => {
console.log('Socket.IO room left:', data);
setLastMessage({
type: 'room_left',
data,
timestamp: new Date().toISOString()
});
});
socketInstance.on('subscription_confirmed', (data) => {
console.log('Socket.IO subscription confirmed:', data);
setLastMessage({
type: 'subscription_confirmed',
data,
timestamp: new Date().toISOString()
});
});
// Handle generic messages
socketInstance.onAny((event, data) => {
const message: SocketIOMessage = {
type: event,
data,
timestamp: new Date().toISOString()
};
setLastMessage(message);
onMessage?.(message);
});
setSocket(socketInstance);
} catch (error) {
console.error('Failed to create Socket.IO connection:', error);
setConnectionState('error');
onError?.(error);
}
}, [url, reconnectionAttempts, reconnectionDelay, onMessage, onConnect, onDisconnect, onError]);
const disconnect = useCallback(() => {
shouldReconnectRef.current = false;
if (socket) {
socket.disconnect();
}
setSocket(null);
setIsConnected(false);
setConnectionState('disconnected');
}, [socket]);
const reconnect = useCallback(() => {
disconnect();
shouldReconnectRef.current = true;
reconnectAttemptsRef.current = 0;
setTimeout(() => connect(), 100);
}, [disconnect, connect]);
const sendMessage = useCallback((event: string, data: any) => {
if (socket?.connected) {
socket.emit(event, data);
} else {
console.warn('Socket.IO is not connected. Cannot send message:', { event, data });
}
}, [socket]);
const joinRoom = useCallback((room: string) => {
if (socket?.connected) {
socket.emit('join_room', { room });
} else {
console.warn('Socket.IO is not connected. Cannot join room:', room);
}
}, [socket]);
const leaveRoom = useCallback((room: string) => {
if (socket?.connected) {
socket.emit('leave_room', { room });
} else {
console.warn('Socket.IO is not connected. Cannot leave room:', room);
}
}, [socket]);
const subscribe = useCallback((events: string[], room: string = 'general') => {
if (socket?.connected) {
socket.emit('subscribe', { events, room });
} else {
console.warn('Socket.IO is not connected. Cannot subscribe to events:', { events, room });
}
}, [socket]);
// Cleanup on unmount
useEffect(() => {
return () => {
shouldReconnectRef.current = false;
if (socket) {
socket.disconnect();
}
};
}, [socket]);
// Auto-connect on mount
useEffect(() => {
if (autoConnect) {
shouldReconnectRef.current = true;
connect();
}
return () => {
shouldReconnectRef.current = false;
};
}, [connect, autoConnect]);
return {
socket,
isConnected,
connectionState,
sendMessage,
joinRoom,
leaveRoom,
subscribe,
lastMessage,
connect,
disconnect,
reconnect
};
};

View File

@@ -0,0 +1,217 @@
import { useEffect, useState, useRef, useCallback } from 'react';
export interface WebSocketMessage {
type: string;
data: any;
timestamp: string;
}
export interface WebSocketHookOptions {
url: string;
reconnectAttempts?: number;
reconnectDelay?: number;
onMessage?: (message: WebSocketMessage) => void;
onConnect?: () => void;
onDisconnect?: () => void;
onError?: (error: Event) => void;
}
export interface WebSocketHookReturn {
socket: WebSocket | null;
isConnected: boolean;
connectionState: 'connecting' | 'connected' | 'disconnected' | 'error';
sendMessage: (type: string, data: any) => void;
lastMessage: WebSocketMessage | null;
connect: () => void;
disconnect: () => void;
reconnect: () => void;
}
export const useWebSocket = (options: WebSocketHookOptions): WebSocketHookReturn => {
const {
url,
reconnectAttempts = 5,
reconnectDelay = 3000,
onMessage,
onConnect,
onDisconnect,
onError
} = options;
const [socket, setSocket] = useState<WebSocket | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [connectionState, setConnectionState] = useState<'connecting' | 'connected' | 'disconnected' | 'error'>('disconnected');
const [lastMessage, setLastMessage] = useState<WebSocketMessage | null>(null);
const reconnectAttemptsRef = useRef(0);
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const shouldReconnectRef = useRef(true);
const connect = useCallback(() => {
if (socket?.readyState === WebSocket.OPEN) {
return;
}
try {
setConnectionState('connecting');
// Ensure we use the correct URL in production
const wsUrl = url.includes('localhost') ? 'wss://hive.home.deepblack.cloud/socket.io/general' : url;
console.log('WebSocket connecting to:', wsUrl);
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('WebSocket connected');
setIsConnected(true);
setConnectionState('connected');
reconnectAttemptsRef.current = 0;
onConnect?.();
};
ws.onmessage = (event) => {
try {
const message: WebSocketMessage = JSON.parse(event.data);
setLastMessage(message);
onMessage?.(message);
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
}
};
ws.onclose = (event) => {
console.log('WebSocket disconnected:', event.code, event.reason);
setIsConnected(false);
setSocket(null);
if (event.code !== 1000 && shouldReconnectRef.current) {
setConnectionState('disconnected');
// Attempt to reconnect
if (reconnectAttemptsRef.current < reconnectAttempts) {
reconnectAttemptsRef.current++;
console.log(`Attempting to reconnect (${reconnectAttemptsRef.current}/${reconnectAttempts})`);
reconnectTimeoutRef.current = setTimeout(() => {
connect();
}, reconnectDelay);
} else {
setConnectionState('error');
console.error('Max reconnection attempts reached');
}
} else {
setConnectionState('disconnected');
}
onDisconnect?.();
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
setConnectionState('error');
onError?.(error);
};
setSocket(ws);
} catch (error) {
console.error('Failed to create WebSocket connection:', error);
setConnectionState('error');
}
}, [url, reconnectAttempts, reconnectDelay, onMessage, onConnect, onDisconnect, onError, socket]);
const disconnect = useCallback(() => {
shouldReconnectRef.current = false;
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
if (socket) {
socket.close(1000, 'User disconnected');
}
setSocket(null);
setIsConnected(false);
setConnectionState('disconnected');
}, [socket]);
const reconnect = useCallback(() => {
disconnect();
shouldReconnectRef.current = true;
reconnectAttemptsRef.current = 0;
setTimeout(() => connect(), 100);
}, [disconnect, connect]);
const sendMessage = useCallback((type: string, data: any) => {
if (socket?.readyState === WebSocket.OPEN) {
const message = {
type,
data,
timestamp: new Date().toISOString()
};
socket.send(JSON.stringify(message));
} else {
console.warn('WebSocket is not connected. Cannot send message:', { type, data });
}
}, [socket]);
// Cleanup on unmount
useEffect(() => {
return () => {
shouldReconnectRef.current = false;
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
if (socket) {
socket.close(1000, 'Component unmounted');
}
};
}, [socket]);
// Auto-connect on mount
useEffect(() => {
shouldReconnectRef.current = true;
connect();
return () => {
shouldReconnectRef.current = false;
};
}, [connect]);
return {
socket,
isConnected,
connectionState,
sendMessage,
lastMessage,
connect,
disconnect,
reconnect
};
};
// Utility hook for subscribing to specific message types
export const useWebSocketSubscription = (
socket: WebSocket | null,
messageType: string,
handler: (data: any) => void
) => {
useEffect(() => {
if (!socket) return;
const handleMessage = (event: MessageEvent) => {
try {
const message: WebSocketMessage = JSON.parse(event.data);
if (message.type === messageType) {
handler(message.data);
}
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
}
};
socket.addEventListener('message', handleMessage);
return () => {
socket.removeEventListener('message', handleMessage);
};
}, [socket, messageType, handler]);
};