diff --git a/README.md b/README.md index 17b55b0..1cd00b4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Chatbot UI +# ChatUI -Chatbot UI is an open source chat UI for AI models. +Another ChatUI for ChimeraGPT + Poe models. -![Chatbot UI](./public/screenshots/screenshot-0402023.jpg) +![Chatbot UI](./public/screenshots/SUS.jpg) diff --git a/components/Chat/Chat.tsx b/components/Chat/Chat.tsx index fa6b69d..1c50056 100644 --- a/components/Chat/Chat.tsx +++ b/components/Chat/Chat.tsx @@ -355,34 +355,21 @@ export const Chat = memo(({ stopConversationRef }: Props) => { Welcome to Chatbot UI
-
{`Chatbot UI is an open source clone of OpenAI's ChatGPT UI.`}
-
- Important: Chatbot UI is 100% unaffiliated with OpenAI. -
-
-
-
- Chatbot UI allows you to plug in your API key to use this UI with - their API. -
-
- It is only used to communicate - with their API. -
+
{`Another ChatUI for ChimeraGPT with Poe models.`}
{t( - 'Please set your OpenAI API key in the bottom left of the sidebar.', + 'Please set your ChimeraAPI key by placing it in "OpenAI API Key" textbox located in the bottom left of the sidebar.', )}
- {t("If you don't have an OpenAI API key, you can get one here: ")} + {t("If you don't have an ChimeraAPI key, you can get one here: ")} - openai.com + Discord Server
@@ -405,7 +392,7 @@ export const Chat = memo(({ stopConversationRef }: Props) => { ) : ( - 'Chatbot UI' + '' )} diff --git a/components/Chat/ChatInput.tsx b/components/Chat/ChatInput.tsx index 64f8df6..84e6c15 100644 --- a/components/Chat/ChatInput.tsx +++ b/components/Chat/ChatInput.tsx @@ -1,398 +1,398 @@ -import { - IconArrowDown, - IconBolt, - IconBrandGoogle, - IconPlayerStop, - IconRepeat, - IconSend, -} from '@tabler/icons-react'; -import { - KeyboardEvent, - MutableRefObject, - useCallback, - useContext, - useEffect, - useRef, - useState, -} from 'react'; - -import { useTranslation } from 'next-i18next'; - -import { Message } from '@/types/chat'; -import { Plugin } from '@/types/plugin'; -import { Prompt } from '@/types/prompt'; - -import HomeContext from '@/pages/api/home/home.context'; - -import { PluginSelect } from './PluginSelect'; -import { PromptList } from './PromptList'; -import { VariableModal } from './VariableModal'; - -interface Props { - onSend: (message: Message, plugin: Plugin | null) => void; - onRegenerate: () => void; - onScrollDownClick: () => void; - stopConversationRef: MutableRefObject; - textareaRef: MutableRefObject; - showScrollDownButton: boolean; -} - -export const ChatInput = ({ - onSend, - onRegenerate, - onScrollDownClick, - stopConversationRef, - textareaRef, - showScrollDownButton, -}: Props) => { - const { t } = useTranslation('chat'); - - const { - state: { selectedConversation, messageIsStreaming, prompts }, - - dispatch: homeDispatch, - } = 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 [isModalVisible, setIsModalVisible] = useState(false); - const [showPluginSelect, setShowPluginSelect] = useState(false); - const [plugin, setPlugin] = useState(null); - - const promptListRef = useRef(null); - - const filteredPrompts = prompts.filter((prompt) => - 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( - t( - `Message limit is {{maxLength}} characters. You have entered {{valueLength}} characters.`, - { maxLength, valueLength: value.length }, - ), - ); - return; - } - - setContent(value); - updatePromptListVisibility(value); - }; - - const handleSend = () => { - if (messageIsStreaming) { - return; - } - - if (!content) { - alert(t('Please enter a message')); - return; - } - - onSend({ role: 'user', content }, plugin); - setContent(''); - setPlugin(null); - - 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 handleInitModal = () => { - const selectedPrompt = filteredPrompts[activePromptIndex]; - if (selectedPrompt) { - setContent((prevContent) => { - const newContent = prevContent?.replace( - /\/\w*$/, - selectedPrompt.content, - ); - return newContent; - }); - handlePromptSelect(selectedPrompt); - } - setShowPromptList(false); - }; - - const handleKeyDown = (e: KeyboardEvent) => { - if (showPromptList) { - if (e.key === 'ArrowDown') { - e.preventDefault(); - setActivePromptIndex((prevIndex) => - prevIndex < prompts.length - 1 ? prevIndex + 1 : prevIndex, - ); - } else if (e.key === 'ArrowUp') { - e.preventDefault(); - setActivePromptIndex((prevIndex) => - prevIndex > 0 ? prevIndex - 1 : prevIndex, - ); - } else if (e.key === 'Tab') { - e.preventDefault(); - setActivePromptIndex((prevIndex) => - prevIndex < prompts.length - 1 ? prevIndex + 1 : 0, - ); - } else if (e.key === 'Enter') { - e.preventDefault(); - handleInitModal(); - } else if (e.key === 'Escape') { - e.preventDefault(); - setShowPromptList(false); - } else { - setActivePromptIndex(0); - } - } else if (e.key === 'Enter' && !isTyping && !isMobile() && !e.shiftKey) { - e.preventDefault(); - handleSend(); - } else if (e.key === '/' && e.metaKey) { - e.preventDefault(); - setShowPluginSelect(!showPluginSelect); - } - }; - - 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 && ( - - )} - -
- - - {showPluginSelect && ( -
- { - if (e.key === 'Escape') { - e.preventDefault(); - setShowPluginSelect(false); - textareaRef.current?.focus(); - } - }} - onPluginChange={(plugin: Plugin) => { - setPlugin(plugin); - setShowPluginSelect(false); - - if (textareaRef && textareaRef.current) { - textareaRef.current.focus(); - } - }} - /> -
- )} - -