- {/* Header */}
-
-
-
-
چت بات داناک
- {/* Always show a compact connection status */}
-
- {connectionInfo.icon}
-
-
-
-
-
- {/* Control buttons */}
-
-
-
-
- {connectionStatus !== 'connected' && (
-
- )}
-
-
- {/* Messages */}
-
- {
- messages?.map((message: Message) => {
- // Debug message content
- console.log(`Rendering message ${message.id}:`, {
- text: message.text.substring(0, 30) + (message.text.length > 30 ? "..." : ""),
- isUser: message.isUser,
- isSystem: message.isSystem,
- streaming: message.streaming
- });
-
- return (
-
- {message.isUser && (
-
- 👤
-
- )}
-
-
-
- {message.timestamp.toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' })}
-
- {message.metadata?.confidence !== undefined &&
- message.metadata.confidence !== 0 &&
- message.metadata.confidence !== 0.5 &&
- message.metadata.confidence !== 1 && (
-
- اعتماد: {Math.round(message.metadata.confidence * 100)}%
-
- )}
-
- {!message.isUser && !message.isSystem && (
-
- 🤖
-
- )}
-
- );
- })
- }
- {/* Improved typing indicator */}
- {isTyping && (
-
-
-
- در حال پاسخ...
-
-
-
-
-
- 🤖
-
-
- )}
-
-
-
- {/* Input */}
-
+ return (
+
+ {/* Header */}
+
+
+
+
چت بات داناک
+ {/* Always show a compact connection status */}
+
+ {connectionInfo.icon}
+
- );
+
+
+
+ {/* Messages */}
+
+ {messages?.map((message: Message) => {
+ // Debug message content
+ console.log(`Rendering message ${message.id}:`, {
+ text:
+ message.text.substring(0, 30) +
+ (message.text.length > 30 ? "..." : ""),
+ isUser: message.isUser,
+ isSystem: message.isSystem,
+ streaming: message.streaming,
+ });
+
+ return (
+
+ {message.isUser && (
+
+ 👤
+
+ )}
+
+
+
+ {message.timestamp.toLocaleTimeString("fa-IR", {
+ hour: "2-digit",
+ minute: "2-digit",
+ })}
+
+ {message.metadata?.confidence !== undefined &&
+ message.metadata.confidence !== 0 &&
+ message.metadata.confidence !== 0.5 &&
+ message.metadata.confidence !== 1 && (
+
+ اعتماد: {Math.round(message.metadata.confidence * 100)}%
+
+ )}
+
+ {!message.isUser && !message.isSystem && (
+
+ 🤖
+
+ )}
+
+ );
+ })}
+ {/* Typing indicator - only show when no streaming messages exist */}
+ {isTyping && !messages.some((msg) => msg.streaming) && (
+
+
+
+ در حال پاسخ...
+
+
+
+
+
+ 🤖
+
+
+ )}
+
+
+
+ {/* Input */}
+
+
+ );
};
-export default Chatbot;
\ No newline at end of file
+export default Chatbot;
diff --git a/src/chatbot/chatbot.css b/src/chatbot/chatbot.css
index 31cfdcf..5cbf7dd 100644
--- a/src/chatbot/chatbot.css
+++ b/src/chatbot/chatbot.css
@@ -111,3 +111,31 @@
transform: translateY(-4px);
}
}
+
+/* Typing dots for streaming messages */
+.typing-dots {
+ display: flex;
+ align-items: center;
+}
+
+.typing-dots span {
+ width: 3px;
+ height: 3px;
+ margin: 0 1px;
+ background-color: #3b82f6;
+ border-radius: 50%;
+ display: inline-block;
+ animation: typing-dot 1.2s infinite ease-in-out;
+}
+
+.typing-dots span:nth-child(1) {
+ animation-delay: 0s;
+}
+
+.typing-dots span:nth-child(2) {
+ animation-delay: 0.2s;
+}
+
+.typing-dots span:nth-child(3) {
+ animation-delay: 0.4s;
+}
diff --git a/src/chatbot/hooks/useSocketHandlers.ts b/src/chatbot/hooks/useSocketHandlers.ts
index 641d9ea..189501e 100644
--- a/src/chatbot/hooks/useSocketHandlers.ts
+++ b/src/chatbot/hooks/useSocketHandlers.ts
@@ -1,4 +1,4 @@
-import { useCallback, useRef, useState } from "react";
+import { useCallback, useRef } from "react";
import { Socket } from "socket.io-client";
import { SOCKET_EVENTS } from "../constants";
import { Message } from "../types";
@@ -26,34 +26,11 @@ export const useSocketHandlers = ({
setCurrentSessionId,
isProcessing,
}: UseSocketHandlersProps) => {
- // Keep track of the current streaming message
- const currentStreamingMessageRef = useRef<{
+ // Keep track of the current bot message like in the JS version
+ const botMessageRef = useRef<{
id: string;
text: string;
} | null>(null);
- const [streamingText, setStreamingText] = useState
("");
-
- // Simple function to update streaming message content
- const updateStreamingMessage = (id: string, text: string) => {
- console.log(
- `Updating streaming message ${id} with ${text.length} characters`
- );
-
- // Update the state for React
- updateMessage(id, { text });
-
- // Also try direct DOM update as a fallback
- setTimeout(() => {
- try {
- const element = document.getElementById(`message-${id}`);
- if (element) {
- element.innerHTML = formatText(text);
- }
- } catch (e) {
- console.error("Error updating DOM directly:", e);
- }
- }, 0);
- };
const setupSocketHandlers = useCallback(() => {
if (!socket) return () => {};
@@ -112,6 +89,7 @@ export const useSocketHandlers = ({
const onChatJoined = (data: any) => {
console.log("✅ Joined chat:", data);
addSystemMessage("به چت متصل شدید", "success");
+ setStatus("متصل و آماده ✅");
};
const onSessionError = (data: any) => {
@@ -125,22 +103,28 @@ export const useSocketHandlers = ({
// Message is already shown in UI when sent
};
- // Bot response streaming
+ // Bot response streaming - following the JS implementation pattern
const onBotResponseStart = (data: any) => {
console.log("🤖 Bot response starting:", data);
- // Reset streaming text
- setStreamingText("");
+ // Turn off typing indicator since we're starting streaming
+ setIsTyping(false);
- // Create a new message and store its ID
+ // 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 the message ID and initial text in ref
- currentStreamingMessageRef.current = {
+ // Store reference like in JS version
+ botMessageRef.current = {
id: newMessage.id,
text: "",
};
@@ -149,32 +133,43 @@ export const useSocketHandlers = ({
};
const onBotResponseChunk = (data: any) => {
- console.log(
- "🤖 Bot response chunk received, data length:",
- data?.data?.length || 0
- );
+ console.log("🤖 Bot response chunk received:", data);
- if (data.data && currentStreamingMessageRef.current) {
- try {
- // Update the text in our ref and state
- const newText = currentStreamingMessageRef.current.text + data.data;
- currentStreamingMessageRef.current.text = newText;
+ // 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)");
- // Update state to trigger re-render
- setStreamingText(newText);
+ // Turn off typing indicator since we're starting streaming
+ setIsTyping(false);
- // Update the message content
- updateStreamingMessage(
- currentStreamingMessageRef.current.id,
- newText
- );
- } catch (error) {
- console.error("Error processing bot response chunk:", error);
- }
+ 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 streaming message found", {
- hasData: !!data.data,
- hasCurrentMessage: !!currentStreamingMessageRef.current,
+ console.warn("Received chunk but no active bot message found", {
+ hasBotMessage: !!botMessageRef.current,
+ hasData: !!data?.data,
data,
});
}
@@ -183,45 +178,28 @@ export const useSocketHandlers = ({
const onBotResponseEnd = (data: any) => {
console.log("🤖 Bot response ended:", data);
- if (currentStreamingMessageRef.current) {
- // Make sure we have the final text
- const finalText = currentStreamingMessageRef.current.text;
- const messageId = currentStreamingMessageRef.current.id;
-
- console.log(
- `Finalizing message ${messageId} with text length: ${finalText.length}`
- );
-
- // Final update to the message content
- updateStreamingMessage(messageId, finalText);
-
+ if (botMessageRef.current) {
// Finalize the message by removing streaming flag
- setTimeout(() => {
- updateMessage(messageId, {
- streaming: false,
- });
- }, 100);
+ updateMessage(botMessageRef.current.id, {
+ streaming: false,
+ });
- // Clear the ref and state
- setStreamingText("");
- currentStreamingMessageRef.current = null;
+ 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) {
- console.log(
- `Adding non-streaming message: "${data.message.content.substring(
- 0,
- 50
- )}${data.message.content.length > 50 ? "..." : ""}"`
- );
-
addMessage({
text: data.message.content,
isUser: false,
@@ -231,8 +209,10 @@ export const useSocketHandlers = ({
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
@@ -265,15 +245,21 @@ export const useSocketHandlers = ({
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
@@ -321,9 +307,8 @@ export const useSocketHandlers = ({
socket.off(SOCKET_EVENTS.MESSAGE_ERROR, onMessageError);
socket.off(SOCKET_EVENTS.WEBSOCKET_ERROR, onError);
- // Clear streaming message ref on cleanup
- currentStreamingMessageRef.current = null;
- setStreamingText("");
+ // Clear bot message reference on cleanup
+ botMessageRef.current = null;
};
}, [
socket,
@@ -334,10 +319,9 @@ export const useSocketHandlers = ({
updateMessage,
addSystemMessage,
setCurrentSessionId,
- streamingText, // Add dependency to ensure handler is updated when streaming text changes
]);
- return { setupSocketHandlers, streamingText };
+ return { setupSocketHandlers };
};
// Helper function to format text with markdown-like syntax