/** * WebSocket Service Types and Utilities * Provides type definitions and helper functions for WebSocket events */ // Event types that can be received from the WebSocket server export interface WebSocketEvents { message_update: { message: { id: string; senderId: string; text: string; timestamp: string; type: string; highlighted: boolean; attached_assets?: string[]; activates_visual_context?: boolean; visualAsset?: { filename: string; displayReference: string; }; }; }; ai_status_update: { status: { status: string; updated_at: string; }; }; moderator_status_update: { moderator_status: { current_section_id: string; current_item_id: string; current_section: string; current_item: string; progress: number; }; }; theme_update: { theme: { id: string; title: string; description: string; quotes: any[]; source: string; created_at: string; }; action: 'added' | 'updated' | 'deleted'; }; focus_group_update: { llm_model: string; reasoning_effort?: string; verbosity?: string; updated_at: string; }; analytics_update: { analytics: { overview: any; participation: any; sentiment_analysis: any; quality_metrics: any; recommendations: string[]; }; }; conversation_state_update: { state: { status: string; conversation_flow: any; conversation_health: any; }; }; } // Helper function to convert WebSocket message to frontend message format export function convertWebSocketMessage(wsMessage: WebSocketEvents['message_update']['message']) { console.log('🔍 [GPT-5 CONVERTER] Input wsMessage:', JSON.stringify(wsMessage, null, 2)); if (!wsMessage) { console.error('🔍 [GPT-5 CONVERTER] ERROR: wsMessage is null/undefined'); return null; } const converted = { id: wsMessage.id, senderId: wsMessage.senderId, text: wsMessage.text, timestamp: new Date(wsMessage.timestamp), type: wsMessage.type, highlighted: wsMessage.highlighted, attached_assets: wsMessage.attached_assets || [], activates_visual_context: wsMessage.activates_visual_context || false, visualAsset: wsMessage.visualAsset }; console.log('🔍 [GPT-5 CONVERTER] Output converted:', JSON.stringify(converted, null, 2)); return converted; } // Helper function to convert WebSocket theme to frontend theme format export function convertWebSocketTheme(wsTheme: WebSocketEvents['theme_update']['theme']) { return { id: wsTheme.id, title: wsTheme.title, description: wsTheme.description, quotes: wsTheme.quotes, source: wsTheme.source as 'generated' | 'highlight', created_at: wsTheme.created_at }; } // WebSocket event names as constants export const WS_EVENTS = { MESSAGE_UPDATE: 'message_update', AI_STATUS_UPDATE: 'ai_status_update', MODERATOR_STATUS_UPDATE: 'moderator_status_update', THEME_UPDATE: 'theme_update', FOCUS_GROUP_UPDATE: 'focus_group_update', ANALYTICS_UPDATE: 'analytics_update', CONVERSATION_STATE_UPDATE: 'conversation_state_update' } as const; // Connection status indicators export enum WebSocketConnectionStatus { DISCONNECTED = 'disconnected', CONNECTING = 'connecting', CONNECTED = 'connected', RECONNECTING = 'reconnecting', ERROR = 'error' } // Helper to get user-friendly connection status messages export function getConnectionStatusMessage(status: WebSocketConnectionStatus, error?: string): string { switch (status) { case WebSocketConnectionStatus.DISCONNECTED: return 'Disconnected from real-time updates'; case WebSocketConnectionStatus.CONNECTING: return 'Connecting to real-time updates...'; case WebSocketConnectionStatus.CONNECTED: return 'Connected to real-time updates'; case WebSocketConnectionStatus.RECONNECTING: return 'Reconnecting to real-time updates...'; case WebSocketConnectionStatus.ERROR: return error ? `Connection error: ${error}` : 'Connection error occurred'; default: return 'Unknown connection status'; } } // Helper to determine if WebSocket should be used based on environment export function shouldUseWebSocket(): boolean { // WebSocket is enabled by default, can be disabled with VITE_DISABLE_WEBSOCKET return import.meta.env.VITE_DISABLE_WEBSOCKET !== 'true'; } // Helper to get the appropriate WebSocket server URL export function getWebSocketUrl(): string { // In development, connect to localhost backend if (import.meta.env.DEV) { return 'http://localhost:5137'; } // If VITE_WEBSOCKET_URL is explicitly set, use that if (import.meta.env.VITE_WEBSOCKET_URL) { return import.meta.env.VITE_WEBSOCKET_URL; } // TEMP DEBUG: Try direct connection to backend bypassing Apache // Add ?direct=1 to URL to test direct connection const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('direct') === '1') { console.log('🔧 USING DIRECT WEBSOCKET CONNECTION (bypassing Apache)'); return 'https://optical-dev.oliver.solution:5137'; } // For production with Apache proxy, use the current origin // The Apache proxy handles the routing to backend const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:'; const host = window.location.host; // includes port if any // Use root domain since we specify full path in socket options return `${protocol}//${host}`; }