import { useCallback, useRef } from "react"; import { Socket } from "socket.io-client"; import { SOCKET_EVENTS } from "../constants"; import { Message } from "../types"; interface UseSocketHandlersProps { socket: Socket | null; setStatus: (status: string) => void; setIsTyping: (isTyping: boolean) => void; setIsProcessing: (isProcessing: boolean) => void; addMessage: (message: Partial) => Message; updateMessage: (id: string, updates: Partial) => void; addSystemMessage: (text: string, type?: string) => void; setCurrentSessionId: (sessionId: string | null) => void; isProcessing: boolean; } export const useSocketHandlers = ({ socket, setStatus, setIsTyping, setIsProcessing, addMessage, updateMessage, addSystemMessage, setCurrentSessionId, isProcessing, }: UseSocketHandlersProps) => { // Keep track of the current bot message like in the JS version const botMessageRef = useRef<{ id: string; text: string; } | null>(null); const setupSocketHandlers = useCallback(() => { if (!socket) return () => {}; // Connection events const onConnect = () => { console.log("🔌 Socket.IO connected!"); setStatus("متصل شد ✅"); addSystemMessage("اتصال برقرار شد", "success"); }; const onDisconnect = (reason: string) => { console.log("🔌 Socket.IO disconnected:", reason); setStatus("قطع ارتباط"); }; const onConnectionError = (error: any) => { console.error("❌ Socket.IO connection error:", error); addSystemMessage("خطا در اتصال", "error"); setStatus("خطا در اتصال"); }; // Authentication events const onAuthenticated = (data: any) => { console.log("✅ Authenticated:", data); setStatus("احراز هویت شد ✅"); }; const onUnauthorized = (data: any) => { console.error("❌ Unauthorized:", data); addSystemMessage("خطا در احراز هویت", "error"); setStatus("خطا در احراز هویت"); }; // Session events const onSessionCreated = (data: any) => { console.log("✅ Session created:", data); setCurrentSessionId(data.session.id); addSystemMessage("جلسه ایجاد شد 🎉", "success"); setStatus("آماده برای گفتگو"); // Auto-join the created session if (socket && data.session.id) { const userId = localStorage.getItem(import.meta.env.VITE_USER_ID_KEY || "user_id") || localStorage.getItem("userId") || "anonymous_user"; socket.emit(SOCKET_EVENTS.JOIN_CHAT, { sessionId: data.session.id, userId: userId, }); } }; const onChatJoined = (data: any) => { console.log("✅ Joined chat:", data); addSystemMessage("به چت متصل شدید", "success"); setStatus("متصل و آماده ✅"); }; const onSessionError = (data: any) => { console.error("❌ Session error:", data); addSystemMessage("خطا در ایجاد جلسه", "error"); }; // Message events const onMessageReceived = (data: any) => { console.log("📩 Message received:", data); // Message is already shown in UI when sent }; // Bot response streaming - following the JS implementation pattern const onBotResponseStart = (data: any) => { console.log("🤖 Bot response starting:", data); // Turn off typing indicator since we're starting streaming setIsTyping(false); // Clear any existing bot message first if (botMessageRef.current) { console.log("⚠️ Clearing existing bot message before starting new one"); botMessageRef.current = null; } // Create new bot message with streaming flag const newMessage = addMessage({ text: "", isUser: false, streaming: true, }); // Store reference like in JS version botMessageRef.current = { id: newMessage.id, text: "", }; console.log("Created new streaming message with ID:", newMessage.id); }; const onBotResponseChunk = (data: any) => { console.log("🤖 Bot response chunk received:", data); // If no bot message exists, create one (in case bot_response_start was missed) if (!botMessageRef.current && data?.data) { console.log("⚠️ Creating bot message from chunk (missing start event)"); // Turn off typing indicator since we're starting streaming setIsTyping(false); const newMessage = addMessage({ text: "", isUser: false, streaming: true, }); botMessageRef.current = { id: newMessage.id, text: "", }; } if (botMessageRef.current && data?.data) { // Accumulate text like in JS version botMessageRef.current.text += data.data; // Update the message content updateMessage(botMessageRef.current.id, { text: botMessageRef.current.text, }); console.log( `Updated message to length: ${botMessageRef.current.text.length}` ); } else { console.warn("Received chunk but no active bot message found", { hasBotMessage: !!botMessageRef.current, hasData: !!data?.data, data, }); } }; const onBotResponseEnd = (data: any) => { console.log("🤖 Bot response ended:", data); if (botMessageRef.current) { // Finalize the message by removing streaming flag updateMessage(botMessageRef.current.id, { streaming: false, }); console.log(`Finalized message ${botMessageRef.current.id}`); // Clear the reference botMessageRef.current = null; } // Reset all states to allow user to send messages again setIsTyping(false); setIsProcessing(false); console.log("✅ Bot response ended - user can send messages again"); }; const onBotResponse = (data: any) => { console.log("🤖 Bot response (non-streaming):", data); if (data.message && data.message.content) { addMessage({ text: data.message.content, isUser: false, metadata: data.message.metadata, }); } else { console.warn("Received bot_response event with no content", data); } // Reset all states to allow user to send messages again setIsTyping(false); setIsProcessing(false); console.log("✅ Bot response completed - user can send messages again"); }; // Typing events const onTypingStart = (data: any) => { if (data.type === "bot") { setIsTyping(true); setStatus("در حال پاسخ..."); } }; const onTypingStop = (data: any) => { if (data.type === "bot") { setIsTyping(false); setStatus("آماده برای گفتگو"); } }; // User status events const onUserJoined = (data: any) => { console.log("👤 User joined:", data); addSystemMessage(`کاربر به چت پیوست`, "info"); }; const onUserLeft = (data: any) => { console.log("👤 User left:", data); addSystemMessage(`کاربر چت را ترک کرد`, "info"); }; // Error events const onError = (data: any) => { console.error("❌ Socket.IO error:", data); addSystemMessage(data.message || "خطا در پردازش", "error"); // Reset states on error to allow user to try again setIsProcessing(false); setIsTyping(false); console.log("❌ Error occurred - reset processing state"); }; const onMessageError = (data: any) => { console.error("❌ Message error:", data); addSystemMessage(data.message || "خطا در ارسال پیام", "error"); // Reset states on error to allow user to try again setIsProcessing(false); setIsTyping(false); console.log("❌ Message error - reset processing state"); }; // Register event handlers socket.on(SOCKET_EVENTS.CONNECT, onConnect); socket.on(SOCKET_EVENTS.DISCONNECT, onDisconnect); socket.on(SOCKET_EVENTS.CONNECTION_ERROR, onConnectionError); socket.on(SOCKET_EVENTS.AUTHENTICATED, onAuthenticated); socket.on(SOCKET_EVENTS.UNAUTHORIZED, onUnauthorized); socket.on(SOCKET_EVENTS.SESSION_CREATED, onSessionCreated); socket.on(SOCKET_EVENTS.CHAT_JOINED, onChatJoined); socket.on(SOCKET_EVENTS.SESSION_ERROR, onSessionError); socket.on(SOCKET_EVENTS.MESSAGE_RECEIVED, onMessageReceived); socket.on(SOCKET_EVENTS.BOT_RESPONSE_START, onBotResponseStart); socket.on(SOCKET_EVENTS.BOT_RESPONSE_CHUNK, onBotResponseChunk); socket.on(SOCKET_EVENTS.BOT_RESPONSE_END, onBotResponseEnd); socket.on(SOCKET_EVENTS.BOT_RESPONSE, onBotResponse); socket.on(SOCKET_EVENTS.TYPING_START, onTypingStart); socket.on(SOCKET_EVENTS.TYPING_STOP, onTypingStop); socket.on(SOCKET_EVENTS.USER_JOINED, onUserJoined); socket.on(SOCKET_EVENTS.USER_LEFT, onUserLeft); socket.on(SOCKET_EVENTS.ERROR, onError); socket.on(SOCKET_EVENTS.MESSAGE_ERROR, onMessageError); socket.on(SOCKET_EVENTS.WEBSOCKET_ERROR, onError); // Return cleanup function return () => { socket.off(SOCKET_EVENTS.CONNECT, onConnect); socket.off(SOCKET_EVENTS.DISCONNECT, onDisconnect); socket.off(SOCKET_EVENTS.CONNECTION_ERROR, onConnectionError); socket.off(SOCKET_EVENTS.AUTHENTICATED, onAuthenticated); socket.off(SOCKET_EVENTS.UNAUTHORIZED, onUnauthorized); socket.off(SOCKET_EVENTS.SESSION_CREATED, onSessionCreated); socket.off(SOCKET_EVENTS.CHAT_JOINED, onChatJoined); socket.off(SOCKET_EVENTS.SESSION_ERROR, onSessionError); socket.off(SOCKET_EVENTS.MESSAGE_RECEIVED, onMessageReceived); socket.off(SOCKET_EVENTS.BOT_RESPONSE_START, onBotResponseStart); socket.off(SOCKET_EVENTS.BOT_RESPONSE_CHUNK, onBotResponseChunk); socket.off(SOCKET_EVENTS.BOT_RESPONSE_END, onBotResponseEnd); socket.off(SOCKET_EVENTS.BOT_RESPONSE, onBotResponse); socket.off(SOCKET_EVENTS.TYPING_START, onTypingStart); socket.off(SOCKET_EVENTS.TYPING_STOP, onTypingStop); socket.off(SOCKET_EVENTS.USER_JOINED, onUserJoined); socket.off(SOCKET_EVENTS.USER_LEFT, onUserLeft); socket.off(SOCKET_EVENTS.ERROR, onError); socket.off(SOCKET_EVENTS.MESSAGE_ERROR, onMessageError); socket.off(SOCKET_EVENTS.WEBSOCKET_ERROR, onError); // Clear bot message reference on cleanup botMessageRef.current = null; }; }, [ socket, setStatus, setIsTyping, setIsProcessing, addMessage, updateMessage, addSystemMessage, setCurrentSessionId, ]); return { setupSocketHandlers }; }; // Helper function to format text with markdown-like syntax const formatText = (text: string) => { if (!text) return ""; // Clean the text first to avoid HTML injection const cleanedText = text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); // Format with markdown-like syntax and handle newlines properly const formattedText = cleanedText // Bold text .replace(/\*\*(.*?)\*\*/g, "$1") // Italic text .replace(/\*(.*?)\*/g, "$1") // Line breaks - ensure they work properly with proper spacing .replace(/\n/g, "
") // Links (optional) .replace( /\[([^\]]+)\]\(([^)]+)\)/g, '$1' ); return formattedText; };