Files
dzone-front/src/chatbot/hooks/useSocketHandlers.ts
T
mahyargdz 1b6139a80e chat bot
2025-06-08 17:00:17 +03:30

355 lines
12 KiB
TypeScript

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>) => Message;
updateMessage: (id: string, updates: Partial<Message>) => 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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
// Format with markdown-like syntax and handle newlines properly
const formattedText = cleanedText
// Bold text
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
// Italic text
.replace(/\*(.*?)\*/g, "<em>$1</em>")
// Line breaks - ensure they work properly with proper spacing
.replace(/\n/g, "<br class='message-line-break' />")
// Links (optional)
.replace(
/\[([^\]]+)\]\(([^)]+)\)/g,
'<a href="$2" target="_blank" rel="noopener noreferrer" class="text-blue-500 underline">$1</a>'
);
return formattedText;
};