list old sessions

This commit is contained in:
hamid zarghami
2025-06-09 11:43:51 +03:30
parent 5153c1e1af
commit 67086b91ff
2 changed files with 403 additions and 235 deletions
+402 -235
View File
@@ -1,5 +1,5 @@
import { FC, useState, useRef, useEffect } from "react"; import { FC, useState, useRef, useEffect } from "react";
import { Send, CloseCircle, User } from "iconsax-react"; import { Send, CloseCircle, User, Add, ArrowRight } from "iconsax-react";
import io, { Socket } from "socket.io-client"; import io, { Socket } from "socket.io-client";
import { SOCKET_EVENTS } from "./constants"; import { SOCKET_EVENTS } from "./constants";
import { useSocketHandlers } from "./hooks/useSocketHandlers"; import { useSocketHandlers } from "./hooks/useSocketHandlers";
@@ -7,6 +7,7 @@ import { useMessages } from "./hooks/useMessages";
import { Message } from "./types"; import { Message } from "./types";
import { getToken } from "../config/func"; import { getToken } from "../config/func";
import "./chatbot.css"; // Import CSS for animations import "./chatbot.css"; // Import CSS for animations
import axios from "axios";
// Helper function to format text with markdown-like syntax // Helper function to format text with markdown-like syntax
export const formatText = (text: string) => { export const formatText = (text: string) => {
@@ -45,6 +46,14 @@ interface ChatbotProps {
// Connection status types // Connection status types
type ConnectionStatus = "disconnected" | "connecting" | "connected" | "error"; type ConnectionStatus = "disconnected" | "connecting" | "connected" | "error";
// Session type definition
interface ChatSession {
id: string;
title: string;
createdAt: string;
updatedAt: string;
}
const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => { const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
// State // State
const [inputValue, setInputValue] = useState<string>(""); const [inputValue, setInputValue] = useState<string>("");
@@ -55,11 +64,14 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
const [currentSessionId, setCurrentSessionId] = useState<string | null>(null); const [currentSessionId, setCurrentSessionId] = useState<string | null>(null);
const [socket, setSocket] = useState<Socket | null>(null); const [socket, setSocket] = useState<Socket | null>(null);
const [showChatbot, setShowChatbot] = useState<boolean>(false); const [showChatbot, setShowChatbot] = useState<boolean>(false);
// const [showConnectionMessage, setShowConnectionMessage] =
// useState<boolean>(false);
const [reconnectAttempts, setReconnectAttempts] = useState<number>(0); const [reconnectAttempts, setReconnectAttempts] = useState<number>(0);
const maxReconnectAttempts = 5; const maxReconnectAttempts = 5;
// Sessions related state
const [sessions, setSessions] = useState<ChatSession[]>([]);
const [isLoadingSessions, setIsLoadingSessions] = useState<boolean>(false);
const [showSessionSelect, setShowSessionSelect] = useState<boolean>(true);
// Config // Config
const apiBaseUrl = import.meta.env.VITE_BASE_URL; const apiBaseUrl = import.meta.env.VITE_BASE_URL;
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
@@ -73,6 +85,7 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
addMessage, addMessage,
addSystemMessage, addSystemMessage,
updateMessage, updateMessage,
setMessages,
} = useMessages(); } = useMessages();
const { setupSocketHandlers } = useSocketHandlers({ const { setupSocketHandlers } = useSocketHandlers({
socket, socket,
@@ -147,10 +160,11 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
} }
}, [isOpen]); }, [isOpen]);
// Connect to socket when component mounts // Connect to socket and fetch sessions when component mounts
useEffect(() => { useEffect(() => {
if (isOpen) { if (isOpen) {
connectSocket(); connectSocket();
fetchSessions();
} }
// Cleanup on unmount or when closed // Cleanup on unmount or when closed
@@ -198,17 +212,190 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
}; };
}, [socket]); // Only depend on socket, not setupSocketHandlers }, [socket]); // Only depend on socket, not setupSocketHandlers
// Show connection message only briefly when status changes // Fetch user's previous chat sessions
useEffect(() => { const fetchSessions = async () => {
// Always show connection status, don't hide it try {
// setShowConnectionMessage(true); setIsLoadingSessions(true);
const token = await getToken();
const response = await axios.get(`${apiBaseUrl}/chatbot/sessions`, {
headers: { Authorization: `Bearer ${token}` }
});
return () => { // Extract sessions from response.data.data as the API returns { statusCode, success, data: [...sessions] }
if (connectionTimeoutRef.current) { const sessionsData = response.data?.data || [];
clearTimeout(connectionTimeoutRef.current); setSessions(sessionsData);
console.log("📝 Fetched sessions:", sessionsData);
} catch (error) {
console.error("Error fetching sessions:", error);
addSystemMessage("خطا در دریافت جلسات قبلی", "error");
} finally {
setIsLoadingSessions(false);
}
};
// Join an existing chat session
const joinChat = (sessionId: string) => {
if (!socket || !socket.connected) {
addSystemMessage("اتصال Socket.IO برقرار نیست", "error");
return;
}
// Get user ID from localStorage or use a default
const userId =
localStorage.getItem(import.meta.env.VITE_USER_ID_KEY || "user_id") ||
localStorage.getItem("userId") ||
"anonymous_user";
console.log(`🔄 Joining chat session: ${sessionId}`);
socket.emit(SOCKET_EVENTS.JOIN_CHAT, {
sessionId: sessionId,
userId: userId,
});
};
// Load a specific session with its messages
const loadSession = async (sessionId: string) => {
try {
setIsProcessing(true);
const token = await getToken();
// First ensure we have a socket connection
if (!socket || !socket.connected) {
console.log("Socket not connected, connecting first...");
await connectSocketPromise(token);
} }
};
}, [connectionStatus]); const response = await axios.get(`${apiBaseUrl}/chatbot/sessions/${sessionId}`, {
headers: { Authorization: `Bearer ${token}` }
});
// Extract session data from response.data.data
const sessionData = response.data?.data || {};
// Set current session ID
setCurrentSessionId(sessionId);
// Convert API messages to our message format and load them
if (sessionData && sessionData.messages) {
const formattedMessages = sessionData.messages.map((msg: any) => ({
id: msg.id || Date.now().toString() + Math.random().toString(),
text: msg.content || msg.text || "",
isUser: msg.type === "user",
isSystem: msg.type === "system",
timestamp: new Date(msg.createdAt || msg.timestamp || Date.now()),
metadata: msg.metadata || {}
}));
setMessages(formattedMessages);
}
// Join the chat session
if (socket && socket.connected) {
joinChat(sessionId);
} else {
console.error("Still couldn't connect socket after retrying");
addSystemMessage("خطا در اتصال به سرور چت", "error");
}
// Switch to chat view
setShowSessionSelect(false);
} catch (error) {
console.error("Error loading session:", error);
addSystemMessage("خطا در بارگذاری جلسه", "error");
} finally {
setIsProcessing(false);
}
};
// Promise-based socket connection
const connectSocketPromise = (token: string | undefined): Promise<Socket> => {
return new Promise((resolve, reject) => {
try {
setConnectionStatus("connecting");
if (!apiBaseUrl) {
console.error("API base URL not defined");
setConnectionStatus("error");
reject(new Error("API base URL not defined"));
return;
}
if (!token) {
console.error("No authentication token available");
setConnectionStatus("error");
reject(new Error("No authentication token available"));
return;
}
console.log("🔄 Connecting to Socket.IO...");
const newSocket = io(`${apiBaseUrl}/chat`, {
auth: { token }
});
// Set up connection timeout
const timeout = setTimeout(() => {
if (newSocket.connected) {
clearTimeout(timeout);
return;
}
console.log("❌ Connection timeout");
setConnectionStatus("error");
reject(new Error("Connection timeout"));
}, 10000);
// Handle successful connection
newSocket.on("connect", () => {
console.log("✅ Socket connected successfully");
setConnectionStatus("connected");
setSocket(newSocket);
clearTimeout(timeout);
resolve(newSocket);
});
// Handle connection error
newSocket.on("connect_error", (err) => {
console.error("Socket connection error:", err);
setConnectionStatus("error");
clearTimeout(timeout);
reject(err);
});
} catch (error) {
console.error("Socket connection promise error:", error);
setConnectionStatus("error");
reject(error);
}
});
};
// Create a new chat session
const handleCreateNewSession = async () => {
try {
setIsProcessing(true);
setShowSessionSelect(false);
setCurrentSessionId(null);
setMessages([]);
// Ensure socket is connected
if (!socket || !socket.connected) {
const token = await getToken();
await connectSocketPromise(token);
}
// Create a new session
if (socket && socket.connected) {
createSession(socket);
} else {
console.error("Failed to connect socket for new session");
addSystemMessage("خطا در اتصال به سرور چت", "error");
}
} catch (error) {
console.error("Error creating new session:", error);
addSystemMessage("خطا در ایجاد جلسه جدید", "error");
} finally {
setIsProcessing(false);
}
};
const scrollToBottom = () => { const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
@@ -216,41 +403,9 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
const connectSocket = async () => { const connectSocket = async () => {
try { try {
setConnectionStatus("connecting");
// Get token from environment or localStorage
const token = await getToken(); const token = await getToken();
await connectSocketPromise(token);
if (!apiBaseUrl) {
console.error("API base URL not defined in environment variables");
setConnectionStatus("error");
return;
}
console.log("🔄 Connecting to Socket.IO...");
const newSocket = io(`${apiBaseUrl}/chat`, {
auth: {
token: token,
},
});
setSocket(newSocket);
setReconnectAttempts(0); setReconnectAttempts(0);
// Set a timeout for connection
connectionTimeoutRef.current = setTimeout(() => {
if (connectionStatus === "connecting") {
console.log("❌ Connection timeout");
setConnectionStatus("error");
}
}, 10000);
// Create session after connection - like in JS version
setTimeout(() => {
if (newSocket.connected) {
createSession(newSocket);
}
}, 1000);
} catch (error) { } catch (error) {
console.error("Socket connection error:", error); console.error("Socket connection error:", error);
setConnectionStatus("error"); setConnectionStatus("error");
@@ -297,29 +452,10 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
console.log("🔄 Creating new session via Socket.IO..."); console.log("🔄 Creating new session via Socket.IO...");
socketInstance.emit(SOCKET_EVENTS.CREATE_SESSION, { socketInstance.emit(SOCKET_EVENTS.CREATE_SESSION, {
title: `جلسه ${new Date().toLocaleDateString("fa-IR")}`, title: `چت ${new Date().toLocaleDateString("fa-IR")}`,
}); });
}; };
// const joinChat = (sessionId: string) => {
// if (!socket || !socket.connected) {
// addSystemMessage("اتصال Socket.IO برقرار نیست", "error");
// return;
// }
// // Get user ID from localStorage or use a default
// const userId =
// localStorage.getItem(import.meta.env.VITE_USER_ID_KEY || "user_id") ||
// localStorage.getItem("userId") ||
// "anonymous_user";
// console.log(`🔄 Joining chat session: ${sessionId}`);
// socket.emit(SOCKET_EVENTS.JOIN_CHAT, {
// sessionId: sessionId,
// userId: userId,
// });
// };
const handleSendMessage = () => { const handleSendMessage = () => {
const message = inputValue.trim(); const message = inputValue.trim();
@@ -422,40 +558,23 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
}); });
}; };
// Get connection status icon and text // Format date for session list
// const getConnectionStatusInfo = () => { const formatDate = (dateString: string) => {
// switch (connectionStatus) { const date = new Date(dateString);
// case "connected": return date.toLocaleDateString("fa-IR", {
// return { year: "numeric",
// icon: <Wifi size={16} className="text-green-400" />, month: "long",
// text: "متصل", day: "numeric",
// className: "bg-green-100 text-green-800", });
// }; };
// case "connecting":
// return { // Handle return to sessions list
// icon: <Wifi size={16} className="text-yellow-400 animate-pulse" />, const handleReturnToSessions = () => {
// text: "در حال اتصال...", setShowSessionSelect(true);
// className: "bg-yellow-100 text-yellow-800", };
// };
// case "error":
// return {
// icon: <Wifi size={16} className="text-red-500" />,
// text: "خطا در اتصال",
// className: "bg-red-100 text-red-800",
// };
// default:
// return {
// icon: <Wifi size={16} variant="Bold" className="text-gray-500" />,
// text: "قطع اتصال",
// className: "bg-gray-100 text-gray-800",
// };
// }
// };
if (!isOpen) return null; if (!isOpen) return null;
// const connectionInfo = getConnectionStatusInfo();
return ( return (
<div <div
className={`fixed bottom-20 right-4 w-80 sm:w-96 bg-white rounded-xl shadow-xl flex flex-col z-50 border border-gray-200 transition-all duration-300 ease-in-out ${showChatbot className={`fixed bottom-20 right-4 w-80 sm:w-96 bg-white rounded-xl shadow-xl flex flex-col z-50 border border-gray-200 transition-all duration-300 ease-in-out ${showChatbot
@@ -475,18 +594,16 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
: "bg-red-400" : "bg-red-400"
}`} }`}
></div> ></div>
{!showSessionSelect && (
<button
onClick={handleReturnToSessions}
className="ml-2 p-1 focus:outline-none hover:bg-gray-700 rounded-full transition-colors"
title="برگشت به لیست جلسات"
>
<ArrowRight size={18} />
</button>
)}
<h3 className="font-bold text-base">چت بات داناک</h3> <h3 className="font-bold text-base">چت بات داناک</h3>
{/* Always show a compact connection status */}
{/* <div
className={`flex items-center text-xs mr-2 ml-2 ${connectionStatus === "connected"
? "text-green-200"
: connectionStatus === "connecting"
? "text-yellow-200"
: "text-red-200"
}`}
>
{connectionInfo.icon}
</div> */}
</div> </div>
<button <button
onClick={onClose} onClick={onClose}
@@ -497,147 +614,197 @@ const Chatbot: FC<ChatbotProps> = ({ isOpen, onClose }) => {
</button> </button>
</div> </div>
{/* Messages */} {showSessionSelect ? (
<div /* Sessions Selection Screen */
className="flex-1 p-3.5 overflow-y-auto bg-gray-50" <div className="flex-1 p-3.5 overflow-y-auto bg-gray-50">
style={{ height: "350px" }} <div className="mb-4">
> <h3 className="text-lg font-bold text-gray-800 mb-2">جلسات قبلی شما</h3>
{messages?.map((message: Message) => { <p className="text-sm text-gray-600">یک جلسه را انتخاب کنید یا جلسه جدیدی بسازید.</p>
// Debug message content </div>
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 ( {isLoadingSessions ? (
<div <div className="flex justify-center items-center h-64">
key={message.id} <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
className={`mb-3 flex ${message.isUser ? "justify-start" : "justify-end" </div>
} animate-fade-in`} ) : (
> <>
{message.isUser && ( <button
<div className="w-7 h-7 rounded-full bg-black flex items-center justify-center ml-2 flex-shrink-0"> onClick={handleCreateNewSession}
<span className="text-white text-xs"><User size={14} color="white" /></span> className="w-full bg-black text-white rounded-lg p-3 mb-4 flex items-center justify-center hover:bg-gray-800 transition-colors"
>
<Add size={20} className="ml-2" />
<span>شروع جلسه جدید</span>
</button>
{sessions.length === 0 ? (
<div className="text-center text-gray-500 p-4">
هنوز هیچ جلسهای ندارید
</div>
) : (
<div className="space-y-2">
{sessions.map((session) => (
<div
key={session.id}
onClick={() => loadSession(session.id)}
className="bg-white p-3 rounded-lg shadow-sm cursor-pointer hover:bg-gray-100 transition-colors"
>
<div className="font-medium">{session.title}</div>
<div className="text-xs text-gray-500 mt-1">
{formatDate(session.createdAt)}
</div>
</div>
))}
</div> </div>
)} )}
<div </>
className={`relative px-3.5 py-2.5 pb-4 rounded-lg max-w-[85%] ${message.isUser )}
? "bg-black text-white rounded-tr-none shadow-sm animate-slide-right" </div>
: message.isSystem ) : (
? message.metadata?.type === "error" /* Chat UI */
? "bg-red-100 text-red-800 rounded-tl-none" <>
: message.metadata?.type === "success" {/* Messages */}
? "bg-green-100 text-green-800 rounded-tl-none" <div
: "bg-yellow-100 text-yellow-800 rounded-tl-none" className="flex-1 p-3.5 overflow-y-auto bg-gray-50"
: message.metadata && "confidence" in message.metadata style={{ height: "350px" }}
? message.metadata.confidence === 1 >
? "bg-green-100 text-green-800 rounded-tl-none" {messages?.map((message: Message) => {
: message.metadata.confidence === 0 // Debug message content
? "bg-red-100 text-red-800 rounded-tl-none" console.log(`Rendering message ${message.id}:`, {
: "bg-yellow-100 text-yellow-800 rounded-tl-none" text:
: "bg-white text-gray-800 rounded-tl-none shadow-sm animate-slide-left" message.text.substring(0, 30) +
} ${message.streaming (message.text.length > 30 ? "..." : ""),
? "border-r-4 border-gray-400 streaming-message animate-pulse" isUser: message.isUser,
: "" isSystem: message.isSystem,
}`} streaming: message.streaming,
> });
return (
<div <div
id={`message-${message.id}`} key={message.id}
className={`text-sm leading-6 font-light whitespace-pre-line break-words text-right mb-2 ${message.streaming ? "streaming-content" : "" className={`mb-3 flex ${message.isUser ? "justify-start" : "justify-end"
}`} } animate-fade-in`}
dangerouslySetInnerHTML={{ __html: formatText(message.text) }}
/>
<div
className={`text-[8px] opacity-70 absolute bottom-1 ${message.isUser
? "left-2.5 text-left"
: "right-2.5 text-right"
}`}
> >
{message.timestamp.toLocaleTimeString("fa-IR", { {message.isUser && (
hour: "2-digit", <div className="w-7 h-7 rounded-full bg-black flex items-center justify-center ml-2 flex-shrink-0">
minute: "2-digit", <span className="text-white text-xs"><User size={14} color="white" /></span>
})}
</div>
{message.metadata?.confidence !== undefined &&
message.metadata.confidence !== 0 &&
message.metadata.confidence !== 0.5 &&
message.metadata.confidence !== 1 && (
<div className="text-[9px] mt-0.5 text-right">
اعتماد: {Math.round(message.metadata.confidence * 100)}%
</div> </div>
)} )}
</div> <div
{!message.isUser && !message.isSystem && ( className={`relative px-3.5 py-2.5 pb-4 rounded-lg max-w-[85%] ${message.isUser
? "bg-black text-white rounded-tr-none shadow-sm animate-slide-right"
: message.isSystem
? message.metadata?.type === "error"
? "bg-red-100 text-red-800 rounded-tl-none"
: message.metadata?.type === "success"
? "bg-green-100 text-green-800 rounded-tl-none"
: "bg-yellow-100 text-yellow-800 rounded-tl-none"
: message.metadata && "confidence" in message.metadata
? message.metadata.confidence === 1
? "bg-green-100 text-green-800 rounded-tl-none"
: message.metadata.confidence === 0
? "bg-red-100 text-red-800 rounded-tl-none"
: "bg-yellow-100 text-yellow-800 rounded-tl-none"
: "bg-white text-gray-800 rounded-tl-none shadow-sm animate-slide-left"
} ${message.streaming
? "border-r-4 border-gray-400 streaming-message animate-pulse"
: ""
}`}
>
<div
id={`message-${message.id}`}
className={`text-sm leading-6 font-light whitespace-pre-line break-words text-right mb-2 ${message.streaming ? "streaming-content" : ""
}`}
dangerouslySetInnerHTML={{ __html: formatText(message.text) }}
/>
<div
className={`text-[8px] opacity-70 absolute bottom-1 ${message.isUser
? "left-2.5 text-left"
: "right-2.5 text-right"
}`}
>
{message.timestamp.toLocaleTimeString("fa-IR", {
hour: "2-digit",
minute: "2-digit",
})}
</div>
{message.metadata?.confidence !== undefined &&
message.metadata.confidence !== 0 &&
message.metadata.confidence !== 0.5 &&
message.metadata.confidence !== 1 && (
<div className="text-[9px] mt-0.5 text-right">
اعتماد: {Math.round(message.metadata.confidence * 100)}%
</div>
)}
</div>
{!message.isUser && !message.isSystem && (
<div className="w-7 h-7 rounded-full bg-gray-200 flex items-center justify-center mr-2 flex-shrink-0">
<span className="text-gray-600 text-xs">🤖</span>
</div>
)}
</div>
);
})}
{/* Typing indicator - only show when no streaming messages exist */}
{isTyping && !messages.some((msg) => msg.streaming) && (
<div className="flex justify-end mb-3 animate-fade-in">
<div className="bg-white text-gray-800 px-3.5 py-2 rounded-lg rounded-tl-none shadow-sm">
<div className="typing-indicator">
<span>در حال پاسخ...</span>
<span></span>
<span></span>
</div>
</div>
<div className="w-7 h-7 rounded-full bg-gray-200 flex items-center justify-center mr-2 flex-shrink-0"> <div className="w-7 h-7 rounded-full bg-gray-200 flex items-center justify-center mr-2 flex-shrink-0">
<span className="text-gray-600 text-xs">🤖</span> <span className="text-gray-600 text-xs">🤖</span>
</div> </div>
)}
</div>
);
})}
{/* Typing indicator - only show when no streaming messages exist */}
{isTyping && !messages.some((msg) => msg.streaming) && (
<div className="flex justify-end mb-3 animate-fade-in">
<div className="bg-white text-gray-800 px-3.5 py-2 rounded-lg rounded-tl-none shadow-sm">
<div className="typing-indicator">
<span>در حال پاسخ...</span>
<span></span>
<span></span>
</div> </div>
</div> )}
<div className="w-7 h-7 rounded-full bg-gray-200 flex items-center justify-center mr-2 flex-shrink-0"> <div ref={messagesEndRef} />
<span className="text-gray-600 text-xs">🤖</span> </div>
{/* Input */}
<div className="border-t border-gray-200 p-3 bg-white rounded-b-xl">
<div className="flex items-center bg-gray-50 rounded-full pl-1 pr-3 overflow-hidden shadow-inner">
<textarea
value={inputValue}
onChange={autoResizeTextarea}
onKeyPress={handleKeyPress}
placeholder="پیام خود را بنویسید..."
disabled={isProcessing || connectionStatus !== "connected"}
className="flex-1 bg-transparent border-none px-2 py-2.5 text-sm focus:outline-none resize-none min-h-[40px] font-light"
style={{ maxHeight: "80px" }}
/>
<button
onClick={handleSendMessage}
disabled={
inputValue.trim() === "" ||
isProcessing ||
connectionStatus !== "connected"
}
className={`ml-2 ${inputValue.trim() === "" ||
isProcessing ||
connectionStatus !== "connected"
? "bg-gray-300"
: "bg-gray-800 hover:bg-gray-900"
} text-white rounded-full w-9 h-9 flex items-center justify-center focus:outline-none transition-colors flex-shrink-0`}
>
<Send
size={18}
className={`transform rotate-180 ${inputValue.trim() !== "" &&
!isProcessing &&
connectionStatus === "connected"
? "animate-pulse"
: ""
}`}
/>
</button>
</div> </div>
</div> </div>
)} </>
<div ref={messagesEndRef} /> )}
</div>
{/* Input */}
<div className="border-t border-gray-200 p-3 bg-white rounded-b-xl">
<div className="flex items-center bg-gray-50 rounded-full pl-1 pr-3 overflow-hidden shadow-inner">
<textarea
value={inputValue}
onChange={autoResizeTextarea}
onKeyPress={handleKeyPress}
placeholder="پیام خود را بنویسید..."
disabled={isProcessing || connectionStatus !== "connected"}
className="flex-1 bg-transparent border-none px-2 py-2.5 text-sm focus:outline-none resize-none min-h-[40px] font-light"
style={{ maxHeight: "80px" }}
/>
<button
onClick={handleSendMessage}
disabled={
inputValue.trim() === "" ||
isProcessing ||
connectionStatus !== "connected"
}
className={`ml-2 ${inputValue.trim() === "" ||
isProcessing ||
connectionStatus !== "connected"
? "bg-gray-300"
: "bg-gray-800 hover:bg-gray-900"
} text-white rounded-full w-9 h-9 flex items-center justify-center focus:outline-none transition-colors flex-shrink-0`}
>
<Send
size={18}
className={`transform rotate-180 ${inputValue.trim() !== "" &&
!isProcessing &&
connectionStatus === "connected"
? "animate-pulse"
: ""
}`}
/>
</button>
</div>
</div>
</div> </div>
); );
}; };
export default Chatbot; export default Chatbot;
+1
View File
@@ -81,5 +81,6 @@ export const useMessages = () => {
updateMessage, updateMessage,
removeMessage, removeMessage,
clearMessages, clearMessages,
setMessages,
}; };
}; };