Files
openhuman/app/src/hooks/useIntelligenceSocket.ts
T
Steven EnamakelandGitHub 7ad3c73ac3 Refactor UI auth/bootstrap and remove legacy UI runtime layers (#63)
* Enhance E2E build process and backend URL handling

- Updated `e2e-build.sh` to set CI environment variable correctly for compatibility with various CI runners.
- Refactored `backendUrl.ts` to support dynamic backend URL resolution from Vite environment variables, improving flexibility in different deployment contexts.

* Implement web channel functionality and enhance chat features

- Introduced new web channel commands for sending and canceling chat messages, improving user interaction with the chat system.
- Added support for server-sent events (SSE) to handle real-time updates for chat interactions, enhancing responsiveness.
- Refactored existing code to improve readability and maintainability, including updates to the core HTTP router and JSON-RPC handling.
- Implemented comprehensive end-to-end tests for the web channel flow, ensuring robust functionality and user experience.
- Enhanced the overall structure of the web channel module, including event publishing and subscription mechanisms for better scalability.

* Refactor socket handling to unify communication across environments

- Removed Tauri-specific socket handling from various components, streamlining the codebase to use a single Socket.IO client for both web and desktop environments.
- Updated the `useIntelligenceSocket` hook to eliminate Tauri checks and directly utilize the socket service for message sending and chat initialization.
- Refactored metadata synchronization in Gmail and Notion services to use the unified socket service, enhancing consistency in socket communication.
- Improved error handling and logging across socket interactions to provide clearer feedback during connection issues.
- Enhanced the Conversations component to manage socket connection status, ensuring a more robust user experience during chat interactions.

* Refactor socket service and enhance core URL resolution

- Replaced the deprecated `getBackendUrl` function with `resolveCoreSocketBaseUrl` to improve backend URL resolution for socket connections, accommodating both Tauri and non-Tauri environments.
- Introduced `coreSocketBaseFromRpcUrl` to streamline the processing of RPC URLs.
- Removed the `tauriSocket.ts` file, consolidating socket handling into a unified service for better maintainability and clarity.
- Updated the socket connection logging to provide more informative messages during connection attempts.

* Add engineioxide and socketioxide packages to Cargo.lock and update Cargo.toml

- Introduced new packages: `engineioxide` and `socketioxide`, both at version 0.15.2, enhancing the project's capabilities.
- Updated `Cargo.toml` to include `socketioxide` with the "extensions" feature, ensuring proper integration with the existing codebase.
- Added dependencies for both packages, improving modularity and functionality across the application.

* Refactor Conversations component and streamline chat handling

- Removed unused imports and functions related to Notion context and socket handling, simplifying the Conversations component.
- Enhanced error handling during chat interactions, ensuring clearer feedback for users.
- Updated chatSend function parameters to eliminate unnecessary context, improving the clarity of the API call.
- Consolidated logic for managing chat message history and context, enhancing maintainability and readability of the code.

* Enhance core RPC client with improved logging and error handling

- Introduced debug logging for socket RPC requests and responses, providing better visibility into the communication process.
- Enhanced error handling in the core RPC client to log detailed error messages, improving debugging capabilities.
- Updated socket service to log inbound events, ensuring comprehensive tracking of socket interactions.
- Removed deprecated MCP handlers from the socket service, streamlining the codebase and improving maintainability.

* Refactor apiClient and socketService for improved maintainability

- Changed the declaration of `_getToken` from `let` to `var` in `apiClient.ts` to enhance variable scoping.
- Removed unused imports in `socketService.ts`, streamlining the code and improving readability.

* Refactor deep link handling and enhance E2E testing capabilities

- Updated the deep link configuration in `lib.rs` to use a more generalized `#[cfg(desktop)]` directive, improving compatibility across desktop platforms.
- Enhanced the `triggerDeepLink` function in `deep-link-helpers.ts` to include macOS-specific app activation and launching logic, ensuring better integration with the macOS environment.
- Introduced a new `buildBypassJwt` function to generate bypass JWT tokens for E2E testing, improving the flexibility of authentication flows in tests.
- Updated E2E tests in `conversations-web-channel-flow.spec.ts` to utilize the new `triggerAuthDeepLinkBypass` function, enhancing the testing of authentication scenarios.

* Enhance deep link handling and E2E test configuration

- Updated `triggerAuthDeepLink` in `deep-link-helpers.ts` to support environment-based bypass tokens, improving flexibility in authentication flows during E2E tests.
- Modified the `serve_on_ephemeral` function call in `json_rpc_e2e.rs` to disable the core HTTP router's default behavior, enhancing test isolation and control over the testing environment.

* Refactor deep link handling and enhance E2E test utilities

- Updated `authFlow.e2e.test.tsx` to utilize `window.__simulateDeepLink` for simulating deep links, improving test accuracy and alignment with real-world scenarios.
- Modified `lib.rs` to restrict deep link registration to Windows and Linux platforms, clarifying platform-specific behavior.
- Enhanced `deep-link-helpers.ts` with a new function to simulate deep links in the WebView, providing a fallback mechanism for E2E tests.
- Updated `conversations-web-channel-flow.spec.ts` to reflect changes in deep link triggering, improving logging and test clarity.

* Update dependencies in Cargo.lock and Cargo.toml for improved project stability

- Removed several unused dependencies from `Cargo.lock` and `Cargo.toml`, including `aes-gcm`, `argon2`, and `async-imap`, streamlining the project and reducing potential security vulnerabilities.
- Updated existing dependencies to their latest versions, ensuring compatibility and leveraging improvements in performance and security.
- Simplified the dependency structure by consolidating features and removing unnecessary comments, enhancing clarity and maintainability of the configuration files.

* Refactor App component and remove unused providers

- Removed `AIProvider` and `SkillProvider` from the `App` component, streamlining the component structure and improving readability.
- Updated the `UserProvider` to focus solely on bootstrapping the authentication token, enhancing its clarity and purpose.
- Adjusted the layout within the `App` component to maintain functionality without the removed providers, ensuring a smooth user experience.

* Remove deprecated AI components and types

- Deleted the `index.ts`, `loader.ts`, `types.ts`, and related test files from the AI module, streamlining the codebase by removing unused and outdated components.
- Eliminated the `default-constitution.md` and associated constitution files, which were no longer relevant to the current architecture.
- This cleanup enhances maintainability and reduces complexity within the AI system.

* Refactor user data fetching logic and remove legacy tools cache watcher

- Enhanced the `useUser` hook to prevent infinite retry loops by implementing a token check with a reference to track the last auto-fetch token.
- Removed the legacy file watcher for `TOOLS.md`, transitioning to a core RPC and socket event-based refresh mechanism for tool/config updates.
- Updated test state structure to include `isAuthBootstrapComplete` and `channelConnections`, improving test coverage and state management.

* Refactor channel module structure and add new providers

- Consolidated channel modules under a new `providers` directory, improving organization and clarity.
- Introduced new channel providers for DingTalk, Discord, Email, iMessage, IRC, and Lark, expanding the messaging capabilities of the application.
- Updated the `mod.rs` file to reflect the new structure and ensure proper module exports, enhancing maintainability and ease of use.

* refactor(ui): remove legacy providers/lib logic and stabilize auth bootstrap

* chore: apply pre-push auto-fixes
2026-03-29 20:22:38 -07:00

362 lines
9.7 KiB
TypeScript

import { useCallback, useEffect, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { socketService } from '../services/socketService';
import type { RootState } from '../store';
import {
addMessage,
setExecutionResult,
setTyping,
updateExecutionProgress,
} from '../store/intelligenceSlice';
import { createChatMessage } from '../utils/intelligenceTransforms';
/**
* WebSocket event payloads for Intelligence system
*/
interface ProcessMessagePayload {
message: string;
threadId: string;
sessionId?: string;
context?: Record<string, unknown>;
}
interface AgentResponsePayload {
message: string;
threadId: string;
sessionId?: string;
shouldExecute?: boolean;
executionPlan?: unknown;
metadata?: Record<string, unknown>;
}
interface ExecutionProgressPayload {
executionId: string;
sessionId: string;
step: {
id: string;
label: string;
status: 'pending' | 'in_progress' | 'completed' | 'failed';
timestamp: string;
};
progress: Array<{
id: string;
label: string;
status: 'pending' | 'in_progress' | 'completed' | 'failed';
timestamp?: Date;
}>;
}
interface ExecutionCompletePayload {
executionId: string;
sessionId: string;
status: 'completed' | 'failed';
result?: unknown;
error?: string;
artifacts?: Array<{ type: string; url: string; title: string; description?: string }>;
}
interface ChatInitPayload {
tools: Record<string, unknown>[];
threadId: string;
sessionId?: string;
context?: Record<string, unknown>;
}
/**
* Hook for Intelligence WebSocket integration
* Handles real-time communication for chat and task execution
*/
export const useIntelligenceSocket = () => {
const dispatch = useDispatch();
const socket = socketService.getSocket();
const eventHandlersRegistered = useRef(false);
// Get current socket connection status
const isSocketConnected = useSelector((state: RootState) => {
const userId = state.user.user?._id || '__pending__';
return state.socket.byUser[userId]?.status === 'connected';
});
/**
* Send message to AI agent via WebSocket
*/
const sendMessage = useCallback(
async (payload: ProcessMessagePayload) => {
if (socket?.connected) {
socket.emit('processMessageForUser', payload);
} else {
console.warn('Cannot send message - socket not connected');
throw new Error('Socket not connected');
}
},
[socket]
);
/**
* Initialize chat session with tools
*/
const sendChatInit = useCallback(
async (payload: ChatInitPayload) => {
if (socket?.connected) {
socket.emit('chat:init', payload);
} else {
console.warn('Cannot initialize chat - socket not connected');
throw new Error('Socket not connected');
}
},
[socket]
);
/**
* Send typing indicator
*/
const sendTyping = useCallback(
(threadId: string, isTyping: boolean) => {
const payload = { threadId, isTyping };
if (socket?.connected) {
socket.emit('chat:typing', payload);
}
},
[socket]
);
/**
* Register WebSocket event handlers
*/
const registerEventHandlers = useCallback(() => {
if (!socket || eventHandlersRegistered.current) return;
// Agent response handler
const handleAgentResponse = (data: AgentResponsePayload) => {
console.log('Intelligence: Received agent response', {
threadId: data.threadId,
hasMessage: !!data.message,
shouldExecute: data.shouldExecute,
});
if (data.message && data.threadId) {
const aiMessage = createChatMessage(data.message, 'ai');
dispatch(addMessage({ threadId: data.threadId, message: aiMessage }));
}
// Stop typing indicator
dispatch(setTyping({ threadId: data.threadId, isTyping: false }));
// Handle execution trigger
if (data.shouldExecute && data.executionPlan) {
console.log('Intelligence: Execution requested', {
threadId: data.threadId,
plan: data.executionPlan,
});
// Execution will be handled by the component
}
};
// Execution progress handler
const handleExecutionProgress = (data: ExecutionProgressPayload) => {
console.log('Intelligence: Execution progress', {
executionId: data.executionId,
step: data.step?.label,
status: data.step?.status,
});
if (data.progress) {
dispatch(
updateExecutionProgress({ executionId: data.executionId, progress: data.progress })
);
}
};
// Execution complete handler
const handleExecutionComplete = (data: ExecutionCompletePayload) => {
console.log('Intelligence: Execution complete', {
executionId: data.executionId,
status: data.status,
hasResult: !!data.result,
hasArtifacts: !!data.artifacts?.length,
});
dispatch(
setExecutionResult({
executionId: data.executionId,
result: data.result,
status: data.status,
error: data.error,
})
);
// Send completion message if we have artifacts
if (data.artifacts?.length && data.sessionId) {
// This would need the threadId - we'd need to track execution to thread mapping
// For now, we'll let the component handle the completion message
console.log('Intelligence: Task completed with artifacts', {
artifactCount: data.artifacts.length,
sessionId: data.sessionId,
});
}
};
// Typing indicator handler
const handleTyping = (data: { threadId: string; isTyping: boolean }) => {
dispatch(setTyping({ threadId: data.threadId, isTyping: data.isTyping }));
};
// Register all handlers
socket.on('agentResponse', handleAgentResponse);
socket.on('execution:step_progress', handleExecutionProgress);
socket.on('execution:complete', handleExecutionComplete);
socket.on('chat:typing', handleTyping);
eventHandlersRegistered.current = true;
// Return cleanup function
return () => {
socket.off('agentResponse', handleAgentResponse);
socket.off('execution:step_progress', handleExecutionProgress);
socket.off('execution:complete', handleExecutionComplete);
socket.off('chat:typing', handleTyping);
eventHandlersRegistered.current = false;
};
}, [socket, dispatch]);
/**
* Register event handlers when socket is available
*/
useEffect(() => {
if (socket && isSocketConnected) {
const cleanup = registerEventHandlers();
return cleanup;
}
}, [socket, isSocketConnected, registerEventHandlers]);
/**
* Cleanup on unmount
*/
useEffect(() => {
return () => {
eventHandlersRegistered.current = false;
};
}, []);
return {
// Connection status
isConnected: isSocketConnected,
// Message sending
sendMessage,
sendChatInit,
sendTyping,
// Utility functions
isReady: isSocketConnected && !!socket,
};
};
/**
* Hook for managing Intelligence WebSocket connection lifecycle
*/
export const useIntelligenceSocketManager = () => {
const token = useSelector((state: RootState) => state.auth.token);
const isConnected = useSelector((state: RootState) => {
const userId = state.user.user?._id || '__pending__';
return state.socket.byUser[userId]?.status === 'connected';
});
/**
* Initialize Intelligence socket connection
*/
const connect = useCallback(() => {
if (token && !isConnected) {
console.log('Intelligence: Initializing socket connection');
socketService.connect(token);
}
}, [token, isConnected]);
/**
* Disconnect Intelligence socket
*/
const disconnect = useCallback(() => {
console.log('Intelligence: Disconnecting socket');
socketService.disconnect();
}, []);
/**
* Auto-connect when token is available
*/
useEffect(() => {
if (token && !isConnected) {
connect();
}
}, [token, isConnected, connect]);
return { connect, disconnect, isConnected, isReady: isConnected && !!token };
};
/**
* Hook for Intelligence-specific event subscriptions
*/
export const useIntelligenceEvents = () => {
const socket = socketService.getSocket();
/**
* Subscribe to agent responses for a specific thread
*/
const onAgentResponse = useCallback(
(threadId: string, callback: (data: AgentResponsePayload) => void) => {
if (!socket) return () => {};
const handler = (data: AgentResponsePayload) => {
if (data.threadId === threadId) {
callback(data);
}
};
socket.on('agentResponse', handler);
return () => socket.off('agentResponse', handler);
},
[socket]
);
/**
* Subscribe to execution progress for a specific execution
*/
const onExecutionProgress = useCallback(
(executionId: string, callback: (data: ExecutionProgressPayload) => void) => {
if (!socket) return () => {};
const handler = (data: ExecutionProgressPayload) => {
if (data.executionId === executionId) {
callback(data);
}
};
socket.on('execution:step_progress', handler);
return () => socket.off('execution:step_progress', handler);
},
[socket]
);
/**
* Subscribe to execution completion for a specific execution
*/
const onExecutionComplete = useCallback(
(executionId: string, callback: (data: ExecutionCompletePayload) => void) => {
if (!socket) return () => {};
const handler = (data: ExecutionCompletePayload) => {
if (data.executionId === executionId) {
callback(data);
}
};
socket.on('execution:complete', handler);
return () => socket.off('execution:complete', handler);
},
[socket]
);
return { onAgentResponse, onExecutionProgress, onExecutionComplete };
};