import { IconPlayerStop, IconRepeat, IconSend, } from '@tabler/icons-react'; import { KeyboardEvent, MutableRefObject, useCallback, useContext, useEffect, useRef, useState, } from 'react'; import { Message } from '@/types/chat'; import { Prompt } from '@/types/prompt'; import HomeContext from '@/pages/api/home.context'; interface Props { onSend: (message: Message) => void; onRegenerate: () => void; onScrollDownClick: () => void; stopConversationRef: MutableRefObject; textareaRef: MutableRefObject; showScrollDownButton: boolean; } export const ChatInput = ({ onSend, onRegenerate, stopConversationRef, textareaRef, }: Props) => { const { state: { selectedConversation, messageIsStreaming, prompts }, dispatch: homeDispatch, }: any = useContext(HomeContext); const [content, setContent] = useState(); const [isTyping, setIsTyping] = useState(false); const [showPromptList, setShowPromptList] = useState(false); const [activePromptIndex, setActivePromptIndex] = useState(0); const [promptInputValue, setPromptInputValue] = useState(''); const [variables, setVariables] = useState([]); const promptListRef = useRef(null); const filteredPrompts = prompts.filter((prompt: { name: string; }) => prompt.name.toLowerCase().includes(promptInputValue.toLowerCase()), ); const handleChange = (e: React.ChangeEvent) => { const value = e.target.value; const maxLength = selectedConversation?.model.maxLength; if (maxLength && value.length > maxLength) { alert( `Message limit is ${maxLength} characters. You have entered ${value.length} characters.`, ); return; } setContent(value); updatePromptListVisibility(value); }; const handleSend = () => { if (messageIsStreaming) { return; } if (!content) { alert('Please enter a message'); return; } onSend({ role: 'user', content }); setContent(''); if (window.innerWidth < 640 && textareaRef && textareaRef.current) { textareaRef.current.blur(); } }; const handleStopConversation = () => { stopConversationRef.current = true; setTimeout(() => { stopConversationRef.current = false; }, 1000); }; const isMobile = () => { const userAgent = typeof window.navigator === 'undefined' ? '' : navigator.userAgent; const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i; return mobileRegex.test(userAgent); }; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Enter' && !isTyping && !isMobile() && !e.shiftKey) { e.preventDefault(); handleSend(); } else if (e.key === '/' && e.metaKey) { e.preventDefault(); } }; const parseVariables = (content: string) => { const regex = /{{(.*?)}}/g; const foundVariables = []; let match; while ((match = regex.exec(content)) !== null) { foundVariables.push(match[1]); } return foundVariables; }; const updatePromptListVisibility = useCallback((text: string) => { const match = text.match(/\/\w*$/); if (match) { setShowPromptList(true); setPromptInputValue(match[0].slice(1)); } else { setShowPromptList(false); setPromptInputValue(''); } }, []); const handlePromptSelect = (prompt: Prompt) => { const parsedVariables = parseVariables(prompt.content); setVariables(parsedVariables); if (parsedVariables.length > 0) { setIsModalVisible(true); } else { setContent((prevContent) => { const updatedContent = prevContent?.replace(/\/\w*$/, prompt.content); return updatedContent; }); updatePromptListVisibility(prompt.content); } }; const handleSubmit = (updatedVariables: string[]) => { const newContent = content?.replace(/{{(.*?)}}/g, (match, variable) => { const index = variables.indexOf(variable); return updatedVariables[index]; }); setContent(newContent); if (textareaRef && textareaRef.current) { textareaRef.current.focus(); } }; useEffect(() => { if (promptListRef.current) { promptListRef.current.scrollTop = activePromptIndex * 30; } }, [activePromptIndex]); useEffect(() => { if (textareaRef && textareaRef.current) { textareaRef.current.style.height = 'inherit'; textareaRef.current.style.height = `${textareaRef.current?.scrollHeight}px`; textareaRef.current.style.overflow = `${ textareaRef?.current?.scrollHeight > 400 ? 'auto' : 'hidden' }`; } }, [content]); useEffect(() => { const handleOutsideClick = (e: MouseEvent) => { if ( promptListRef.current && !promptListRef.current.contains(e.target as Node) ) { setShowPromptList(false); } }; window.addEventListener('click', handleOutsideClick); return () => { window.removeEventListener('click', handleOutsideClick); }; }, []); return (
{messageIsStreaming && ( )} {!messageIsStreaming && selectedConversation && selectedConversation.messages.length > 0 && ( )}