mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 23:14:37 +00:00
Add comprehensive documentation for application architecture, state management, services layer, MCP system, pages and routing, components, and providers
- Introduced detailed architecture overview outlining system components and entry points. - Documented state management using Redux Toolkit and Redux-Persist, including store configuration and slice structures. - Described services layer architecture, detailing API client, socket service, and MTProto service functionalities. - Explained the Model Context Protocol (MCP) system, including tool categories and implementation examples. - Outlined routing structure and page components, emphasizing protected and public routes. - Organized reusable components by feature, detailing their structure and usage. - Provided insights into provider management for service lifecycle and shared state. This documentation enhances understanding of the application's structure and improves onboarding for new developers, ensuring clarity in the system's design and functionality.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Tauri Commands
|
||||
*
|
||||
* Helper functions for invoking Tauri commands from the frontend.
|
||||
*/
|
||||
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
// Check if we're running in Tauri
|
||||
export const isTauri = (): boolean => {
|
||||
return typeof window !== 'undefined' && '__TAURI__' in window;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start Telegram login via the widget in the system browser
|
||||
* The backend will redirect back to the app via deep link
|
||||
*/
|
||||
export async function startTelegramLogin(): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
|
||||
await invoke('start_telegram_login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Telegram login with a custom backend URL
|
||||
*/
|
||||
export async function startTelegramLoginWithUrl(backendUrl: string): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
|
||||
await invoke('start_telegram_login_with_url', { backendUrl });
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange a login token for a session token
|
||||
*/
|
||||
export async function exchangeToken(
|
||||
backendUrl: string,
|
||||
token: string
|
||||
): Promise<{ sessionToken: string; user: object }> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
|
||||
return await invoke('exchange_token', { backendUrl, token });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current authentication state from Rust
|
||||
*/
|
||||
export async function getAuthState(): Promise<{
|
||||
is_authenticated: boolean;
|
||||
user: object | null;
|
||||
}> {
|
||||
if (!isTauri()) {
|
||||
return { is_authenticated: false, user: null };
|
||||
}
|
||||
|
||||
return await invoke('get_auth_state');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the session token from secure storage
|
||||
*/
|
||||
export async function getSessionToken(): Promise<string | null> {
|
||||
if (!isTauri()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await invoke('get_session_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout and clear session
|
||||
*/
|
||||
export async function logout(): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await invoke('logout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store session in secure storage
|
||||
*/
|
||||
export async function storeSession(
|
||||
token: string,
|
||||
user: object
|
||||
): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await invoke('store_session', { token, user });
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the main window
|
||||
*/
|
||||
export async function showWindow(): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await invoke('show_window');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the main window
|
||||
*/
|
||||
export async function hideWindow(): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await invoke('hide_window');
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle window visibility
|
||||
*/
|
||||
export async function toggleWindow(): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await invoke('toggle_window');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if window is visible
|
||||
*/
|
||||
export async function isWindowVisible(): Promise<boolean> {
|
||||
if (!isTauri()) {
|
||||
return true; // In browser, window is always visible
|
||||
}
|
||||
|
||||
return await invoke('is_window_visible');
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimize the window
|
||||
*/
|
||||
export async function minimizeWindow(): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await invoke('minimize_window');
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximize or unmaximize the window
|
||||
*/
|
||||
export async function maximizeWindow(): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await invoke('maximize_window');
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the window (minimizes to tray on macOS)
|
||||
*/
|
||||
export async function closeWindow(): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await invoke('close_window');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the window title
|
||||
*/
|
||||
export async function setWindowTitle(title: string): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
document.title = title;
|
||||
return;
|
||||
}
|
||||
|
||||
await invoke('set_window_title', { title });
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Tauri Socket Integration
|
||||
*
|
||||
* This module provides integration between the Rust backend and the frontend
|
||||
* Socket.io service. In Tauri, the socket stays connected even when the
|
||||
* window is hidden because the WebView is not destroyed.
|
||||
*
|
||||
* The Rust backend emits events to coordinate:
|
||||
* - socket:should_connect - When Rust wants frontend to connect
|
||||
* - socket:should_disconnect - When Rust wants frontend to disconnect
|
||||
*
|
||||
* The frontend reports status back:
|
||||
* - report_socket_connected - When connection established
|
||||
* - report_socket_disconnected - When disconnected
|
||||
* - report_socket_error - When error occurs
|
||||
*/
|
||||
|
||||
import { listen, UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { socketService } from '../services/socketService';
|
||||
|
||||
// Check if we're running in Tauri
|
||||
export const isTauri = (): boolean => {
|
||||
return typeof window !== 'undefined' && '__TAURI__' in window;
|
||||
};
|
||||
|
||||
let unlistenConnect: UnlistenFn | null = null;
|
||||
let unlistenDisconnect: UnlistenFn | null = null;
|
||||
|
||||
/**
|
||||
* Setup Tauri socket event listeners
|
||||
* This should be called once when the app starts
|
||||
*/
|
||||
export async function setupTauriSocketListeners(): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Listen for connect requests from Rust
|
||||
unlistenConnect = await listen<{ backendUrl: string; token: string }>(
|
||||
'socket:should_connect',
|
||||
async (event) => {
|
||||
console.log('[TauriSocket] Received connect request');
|
||||
const { token } = event.payload;
|
||||
|
||||
try {
|
||||
socketService.connect(token);
|
||||
// Note: We'll report connected status when socket actually connects
|
||||
} catch (error) {
|
||||
console.error('[TauriSocket] Failed to connect:', error);
|
||||
await invoke('report_socket_error', {
|
||||
error: error instanceof Error ? error.message : 'Connection failed'
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Listen for disconnect requests from Rust
|
||||
unlistenDisconnect = await listen(
|
||||
'socket:should_disconnect',
|
||||
async () => {
|
||||
console.log('[TauriSocket] Received disconnect request');
|
||||
socketService.disconnect();
|
||||
await invoke('report_socket_disconnected');
|
||||
}
|
||||
);
|
||||
|
||||
console.log('[TauriSocket] Event listeners setup complete');
|
||||
} catch (error) {
|
||||
console.error('[TauriSocket] Failed to setup listeners:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup Tauri socket event listeners
|
||||
*/
|
||||
export function cleanupTauriSocketListeners(): void {
|
||||
if (unlistenConnect) {
|
||||
unlistenConnect();
|
||||
unlistenConnect = null;
|
||||
}
|
||||
if (unlistenDisconnect) {
|
||||
unlistenDisconnect();
|
||||
unlistenDisconnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report socket connected status to Rust
|
||||
*/
|
||||
export async function reportSocketConnected(socketId?: string): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke('report_socket_connected', { socketId: socketId ?? null });
|
||||
console.log('[TauriSocket] Reported connected');
|
||||
} catch (error) {
|
||||
console.error('[TauriSocket] Failed to report connected:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report socket disconnected status to Rust
|
||||
*/
|
||||
export async function reportSocketDisconnected(): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke('report_socket_disconnected');
|
||||
console.log('[TauriSocket] Reported disconnected');
|
||||
} catch (error) {
|
||||
console.error('[TauriSocket] Failed to report disconnected:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report socket error to Rust
|
||||
*/
|
||||
export async function reportSocketError(error: string): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke('report_socket_error', { error });
|
||||
console.log('[TauriSocket] Reported error:', error);
|
||||
} catch (error) {
|
||||
console.error('[TauriSocket] Failed to report error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update socket status in Rust
|
||||
*/
|
||||
export async function updateSocketStatus(
|
||||
status: 'connected' | 'connecting' | 'disconnected' | 'reconnecting' | 'error',
|
||||
socketId?: string
|
||||
): Promise<void> {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke('update_socket_status', { status, socketId: socketId ?? null });
|
||||
} catch (error) {
|
||||
console.error('[TauriSocket] Failed to update status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session token from Rust secure storage
|
||||
*/
|
||||
export async function getSecureToken(): Promise<string | null> {
|
||||
if (!isTauri()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return await invoke<string | null>('get_session_token');
|
||||
} catch (error) {
|
||||
console.error('[TauriSocket] Failed to get secure token:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is authenticated (from Rust)
|
||||
*/
|
||||
export async function isAuthenticatedFromRust(): Promise<boolean> {
|
||||
if (!isTauri()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return await invoke<boolean>('is_authenticated');
|
||||
} catch (error) {
|
||||
console.error('[TauriSocket] Failed to check auth:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user