import { useMemo, useRef, useState, useEffect } from "react";
import { toast } from "react-toastify";
import SendOutlinedIcon from "@mui/icons-material/SendOutlined";
import StopCircleOutlinedIcon from "@mui/icons-material/StopCircleOutlined";
import { CircularProgress, IconButton, TextField } from "@mui/material";
import WarningIcon from "@mui/icons-material/Warning";
import { useAppContext } from "../../contexts/AppContext";
import { useChatContext } from "../../contexts/ChatContext";
type ChatInputProps = {};
const detectTextDirection = (text: string) => {
const firstStrongChar = text.match(/[A-Za-z\u0590-\u05FF]/);
if (firstStrongChar) {
return /[\u0590-\u05FF]/.test(firstStrongChar[0]) ? "rtl" : "ltr";
}
return "ltr";
};
const WarningMessage = ({ show, inputMaxLength }: { show: boolean, inputMaxLength: number }) => {
const [isVisible, setIsVisible] = useState(false);
const [position, setPosition] = useState('translate-y-0');
useEffect(() => {
if (show) {
setIsVisible(true);
setPosition('-top-7');
const timer = setTimeout(() => {
setPosition('top-0');
setTimeout(() => {
setIsVisible(false);
}, 300);
}, 2000);
return () => clearTimeout(timer);
}
}, [show]);
if (!isVisible) return null;
return (
The message should be max {inputMaxLength} characters
);
};
export default function ChatInput({ }: ChatInputProps) {
const { configs } = useAppContext();
const { selectedChat, canReplyToBot, sendMessage, abortMessage } = useChatContext();
const inputMaxLength = configs?.input_max_length || 1000;
const inputRef = useRef(null);
const warningTimeoutRef = useRef(null);
const [message, setMessage] = useState("");
const [loading, setLoading] = useState(false);
const [showWarning, setShowWarning] = useState(false);
const textDirection = useMemo(() => detectTextDirection(message), [message]);
const handleSubmit = async (e?: React.FormEvent) => {
e?.preventDefault();
if (!selectedChat) {
toast.error("No chat selected");
return;
}
setLoading(true);
sendMessage(message, selectedChat.id, {
rtl: textDirection === "rtl",
onSuccess: () => {
setLoading(false);
if (inputRef.current) {
setTimeout(() => inputRef.current?.focus(), 0);
}
},
onError: (err) => {
console.error(err);
toast.error("Failed to send message");
setLoading(false);
if (inputRef.current) {
setTimeout(() => inputRef.current?.focus(), 0);
}
},
});
// immediately clear
setMessage("");
};
const handleAbort = () => {
abortMessage();
setLoading(false);
};
const [currentIndex, setCurrentIndex] = useState(0);
const messageCount = configs?.loading_messages?.length || 1;
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
useEffect(() => {
const loopMessages = async () => {
for (let i = 0; i < messageCount; i++) {
setCurrentIndex(i);
await delay(3000);
}
};
if (loading) {
loopMessages();
}
}, [loading]);
useEffect(() => {
return () => {
if (warningTimeoutRef.current) {
clearTimeout(warningTimeoutRef.current);
}
};
}, []);
const handleMessageChange = (e: React.ChangeEvent) => {
const newValue = e.target.value;
if (newValue.length > inputMaxLength) {
if (warningTimeoutRef.current) {
clearTimeout(warningTimeoutRef.current);
warningTimeoutRef.current = null;
}
setShowWarning(false);
setTimeout(() => setShowWarning(true), 0);
setMessage(newValue.slice(0, inputMaxLength));
} else {
setMessage(newValue);
}
};
if (!selectedChat) return null;
return (
{loading && (
{configs?.loading_messages?.[currentIndex] || "Compiling analysis and sources"}
)}
{selectedChat && !canReplyToBot && (
Follow up questions are not supported
)}
);
}