From c7c30d8bb594b015aad9d8601d647d6b75f21c2d Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Tue, 28 Mar 2023 22:39:27 +0800 Subject: [PATCH] refactor: basic message and send message. as well as model THIS IS NOT FINISHED. DONT USE THIS --- client/src/App.jsx | 41 +- .../components/Conversations/Conversation.jsx | 5 +- .../{Main => Input}/AdjustToneButton.jsx | 0 .../components/{Main => Input}/BingStyles.jsx | 0 .../src/components/{Main => Input}/Footer.jsx | 0 .../{ => Input}/Models/MenuItems.jsx | 8 +- .../{ => Input}/Models/ModelDialog.jsx | 71 +-- .../{ => Input}/Models/ModelItem.jsx | 104 ++-- .../src/components/Input/Models/ModelMenu.jsx | 205 ++++++++ .../components/{Main => Input}/RowButton.jsx | 0 .../{Main => Input}/SubmitButton.jsx | 8 +- client/src/components/Input/index.jsx | 454 ++++++++++++++++++ client/src/components/Main/Regenerate.jsx | 35 -- client/src/components/Main/TextChat.jsx | 446 ----------------- .../src/components/MessageHandler/index.jsx | 141 +++--- client/src/components/Messages/Message.jsx | 79 ++- .../src/components/Messages/MultiMessage.jsx | 24 +- client/src/components/Messages/index.jsx | 48 +- client/src/components/Models/ModelMenu.jsx | 223 --------- .../src/components/{Main => ui}/Landing.jsx | 21 +- client/src/routes/Chat.jsx | 32 +- client/src/store/conversation.js | 84 +++- client/src/utils/buildTree.js | 2 + client/src/utils/createPayload.js | 61 ++- 24 files changed, 1057 insertions(+), 1035 deletions(-) rename client/src/components/{Main => Input}/AdjustToneButton.jsx (100%) rename client/src/components/{Main => Input}/BingStyles.jsx (100%) rename client/src/components/{Main => Input}/Footer.jsx (100%) rename client/src/components/{ => Input}/Models/MenuItems.jsx (53%) rename client/src/components/{ => Input}/Models/ModelDialog.jsx (79%) rename client/src/components/{ => Input}/Models/ModelItem.jsx (63%) create mode 100644 client/src/components/Input/Models/ModelMenu.jsx rename client/src/components/{Main => Input}/RowButton.jsx (100%) rename client/src/components/{Main => Input}/SubmitButton.jsx (85%) create mode 100644 client/src/components/Input/index.jsx delete mode 100644 client/src/components/Main/Regenerate.jsx delete mode 100644 client/src/components/Main/TextChat.jsx delete mode 100644 client/src/components/Models/ModelMenu.jsx rename client/src/components/{Main => ui}/Landing.jsx (89%) diff --git a/client/src/App.jsx b/client/src/App.jsx index 07b8df49b8..404dce05a8 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom'; import Root from './routes/Root'; -// import Chat from './routes/Chat'; +import Chat from './routes/Chat'; import store from './store'; import userAuth from './utils/userAuth'; import { useRecoilState, useSetRecoilState } from 'recoil'; @@ -24,7 +24,7 @@ const router = createBrowserRouter([ }, { path: 'chat/:conversationId', - element: null // + element: } ] } @@ -33,14 +33,45 @@ const router = createBrowserRouter([ const App = () => { const [user, setUser] = useRecoilState(store.user); const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled); + const setModelsFilter = useSetRecoilState(store.modelsFilter); useEffect(() => { - axios.get('/api/search/enable').then(res => { - setIsSearchEnabled(res.data); - }); + // fetch if seatch enabled + axios + .get('/api/search/enable', { + timeout: 1000, + withCredentials: true + }) + .then(res => { + setIsSearchEnabled(res.data); + }); + + // fetch user userAuth() .then(user => setUser(user)) .catch(err => console.log(err)); + + // fetch models + axios + .get('/api/models', { + timeout: 1000, + withCredentials: true + }) + .then(({ data }) => { + const filter = { + chatgpt: data?.hasOpenAI, + chatgptCustom: data?.hasOpenAI, + bingai: data?.hasBing, + sydney: data?.hasBing, + chatgptBrowser: data?.hasChatGpt + }; + setModelsFilter(filter); + }) + .catch(error => { + console.error(error); + console.log('Not login!'); + window.location.href = '/auth/login'; + }); }, []); if (user) diff --git a/client/src/components/Conversations/Conversation.jsx b/client/src/components/Conversations/Conversation.jsx index da2d45780a..59128f6244 100644 --- a/client/src/components/Conversations/Conversation.jsx +++ b/client/src/components/Conversations/Conversation.jsx @@ -15,6 +15,7 @@ export default function Conversation({ conversation, retainView }) { const resetLatestMessage = useResetRecoilState(store.latestMessage); const { refreshConversations } = store.useConversations(); + const { switchToConversation } = store.useConversation(); const [renaming, setRenaming] = useState(false); const [titleInput, setTitleInput] = useState(title); @@ -56,9 +57,7 @@ export default function Conversation({ conversation, retainView }) { setSubmission(null); // set conversation to the new conversation - setCurrentConversation(conversation); - setMessages(null); - resetLatestMessage(); + switchToConversation(conversation); // if (!stopStream) { // dispatch(setStopStream(true)); diff --git a/client/src/components/Main/AdjustToneButton.jsx b/client/src/components/Input/AdjustToneButton.jsx similarity index 100% rename from client/src/components/Main/AdjustToneButton.jsx rename to client/src/components/Input/AdjustToneButton.jsx diff --git a/client/src/components/Main/BingStyles.jsx b/client/src/components/Input/BingStyles.jsx similarity index 100% rename from client/src/components/Main/BingStyles.jsx rename to client/src/components/Input/BingStyles.jsx diff --git a/client/src/components/Main/Footer.jsx b/client/src/components/Input/Footer.jsx similarity index 100% rename from client/src/components/Main/Footer.jsx rename to client/src/components/Input/Footer.jsx diff --git a/client/src/components/Models/MenuItems.jsx b/client/src/components/Input/Models/MenuItems.jsx similarity index 53% rename from client/src/components/Models/MenuItems.jsx rename to client/src/components/Input/Models/MenuItems.jsx index d0a277ea9e..a8554ba598 100644 --- a/client/src/components/Models/MenuItems.jsx +++ b/client/src/components/Input/Models/MenuItems.jsx @@ -4,16 +4,12 @@ import ModelItem from './ModelItem'; export default function MenuItems({ models, onSelect }) { return ( <> - {models.map((modelItem) => ( + {models.map(modelItem => ( ))} diff --git a/client/src/components/Models/ModelDialog.jsx b/client/src/components/Input/Models/ModelDialog.jsx similarity index 79% rename from client/src/components/Models/ModelDialog.jsx rename to client/src/components/Input/Models/ModelDialog.jsx index 122bf6688a..f91957d771 100644 --- a/client/src/components/Models/ModelDialog.jsx +++ b/client/src/components/Input/Models/ModelDialog.jsx @@ -1,12 +1,9 @@ import React, { useState, useRef } from 'react'; import TextareaAutosize from 'react-textarea-autosize'; -import { useSelector, useDispatch } from 'react-redux'; -import { setSubmission, setModel, setCustomGpt } from '~/store/submitSlice'; -import { setNewConvo } from '~/store/convoSlice'; import manualSWR from '~/utils/fetchers'; -import { Button } from '../ui/Button.tsx'; -import { Input } from '../ui/Input.tsx'; -import { Label } from '../ui/Label.tsx'; +import { Button } from '../../ui/Button.tsx'; +import { Input } from '../../ui/Input.tsx'; +import { Label } from '../../ui/Label.tsx'; import { DialogClose, @@ -15,11 +12,13 @@ import { DialogFooter, DialogHeader, DialogTitle -} from '../ui/Dialog.tsx'; +} from '../../ui/Dialog.tsx'; + +import store from '~/store'; export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { - const dispatch = useDispatch(); - const { modelMap, initial } = useSelector((state) => state.models); + const { newConversation } = store.useConversation(); + const [chatGptLabel, setChatGptLabel] = useState(''); const [promptPrefix, setPromptPrefix] = useState(''); const [saveText, setSaveText] = useState('Save'); @@ -27,22 +26,25 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { const inputRef = useRef(null); const updateCustomGpt = manualSWR(`/api/customGpts/`, 'post'); - const selectHandler = (e) => { + const selectHandler = e => { if (chatGptLabel.length === 0) { e.preventDefault(); setRequired(true); inputRef.current.focus(); return; } - dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); - dispatch(setModel('chatgptCustom')); + handleSaveState(chatGptLabel.toLowerCase()); + // Set new conversation - dispatch(setNewConvo()); - dispatch(setSubmission({})); + newConversation({ + model: 'chatgptCustom', + chatGptLabel, + promptPrefix + }); }; - const saveHandler = (e) => { + const saveHandler = e => { e.preventDefault(); setModelSave(true); const value = chatGptLabel.toLowerCase(); @@ -56,26 +58,30 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { updateCustomGpt.trigger({ value, chatGptLabel, promptPrefix }); mutate(); - setSaveText((prev) => prev + 'd!'); + setSaveText(prev => prev + 'd!'); setTimeout(() => { setSaveText('Save'); }, 2500); - dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); - dispatch(setModel('chatgptCustom')); - // dispatch(setDisabled(false)); + // dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); + newConversation({ + model: 'chatgptCustom', + chatGptLabel, + promptPrefix + }); }; - if ( - chatGptLabel !== 'chatgptCustom' && - modelMap[chatGptLabel.toLowerCase()] && - !initial[chatGptLabel.toLowerCase()] && - saveText === 'Save' - ) { - setSaveText('Update'); - } else if (!modelMap[chatGptLabel.toLowerCase()] && saveText === 'Update') { - setSaveText('Save'); - } + // Commented by wtlyu + // if ( + // chatGptLabel !== 'chatgptCustom' && + // modelMap[chatGptLabel.toLowerCase()] && + // !initial[chatGptLabel.toLowerCase()] && + // saveText === 'Save' + // ) { + // setSaveText('Update'); + // } else if (!modelMap[chatGptLabel.toLowerCase()] && saveText === 'Update') { + // setSaveText('Save'); + // } const requiredProp = required ? { required: true } : {}; @@ -84,8 +90,7 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { Customize ChatGPT - Note: important instructions are often better placed in your message rather than the - prefix.{' '} + Note: important instructions are often better placed in your message rather than the prefix.{' '} setChatGptLabel(e.target.value)} + onChange={e => setChatGptLabel(e.target.value)} placeholder="Set a custom name for ChatGPT" className=" col-span-3 shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 invalid:border-red-400 invalid:text-red-600 invalid:placeholder-red-600 invalid:placeholder-opacity-70 invalid:ring-opacity-10 focus:ring-0 focus:invalid:border-red-400 focus:invalid:ring-red-300 dark:border-none dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:invalid:border-red-600 dark:invalid:text-red-300 dark:invalid:placeholder-opacity-80 dark:focus:border-none dark:focus:border-transparent dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0 dark:focus:invalid:ring-red-600 dark:focus:invalid:ring-opacity-50" @@ -124,7 +129,7 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { setPromptPrefix(e.target.value)} + onChange={e => setPromptPrefix(e.target.value)} placeholder="Set custom instructions. Defaults to: 'You are ChatGPT, a large language model trained by OpenAI.'" className="col-span-3 flex h-20 w-full resize-none rounded-md border border-gray-300 bg-transparent py-2 px-3 text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-none dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-none dark:focus:border-transparent dark:focus:outline-none dark:focus:ring-0 dark:focus:ring-gray-400 dark:focus:ring-offset-0" /> diff --git a/client/src/components/Models/ModelItem.jsx b/client/src/components/Input/Models/ModelItem.jsx similarity index 63% rename from client/src/components/Models/ModelItem.jsx rename to client/src/components/Input/Models/ModelItem.jsx index 2747e1ccca..b35cc3ece8 100644 --- a/client/src/components/Models/ModelItem.jsx +++ b/client/src/components/Input/Models/ModelItem.jsx @@ -1,36 +1,61 @@ import React, { useState, useRef } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { DropdownMenuRadioItem } from '../ui/DropdownMenu.tsx'; -import { setModels } from '~/store/modelSlice'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { DropdownMenuRadioItem } from '../../ui/DropdownMenu.tsx'; import { Circle } from 'lucide-react'; -import { DialogTrigger } from '../ui/Dialog.tsx'; -import RenameButton from '../Conversations/RenameButton'; -import TrashIcon from '../svg/TrashIcon'; +import { DialogTrigger } from '../../ui/Dialog.tsx'; +import RenameButton from '../../Conversations/RenameButton'; +import TrashIcon from '../../svg/TrashIcon'; import manualSWR from '~/utils/fetchers'; -import { getIconOfModel } from '../../utils'; +import { getIconOfModel } from '~/utils'; + +import store from '~/store'; + +export default function ModelItem({ model: _model, value, onSelect }) { + const { name, model, _id: id, chatGptLabel = null, promptPrefix = null } = _model; + const setCustomGPTModels = useSetRecoilState(store.customGPTModels); + const currentConversation = useRecoilValue(store.conversation) || {}; -export default function ModelItem({ modelName, value, model, onSelect, id, chatGptLabel, promptPrefix }) { - const dispatch = useDispatch(); - const { customModel } = useSelector((state) => state.submit); - const { initial } = useSelector((state) => state.models); const [isHovering, setIsHovering] = useState(false); const [renaming, setRenaming] = useState(false); - const [currentName, setCurrentName] = useState(modelName); - const [modelInput, setModelInput] = useState(modelName); + const [currentName, setCurrentName] = useState(name); + const [modelInput, setModelInput] = useState(name); const inputRef = useRef(null); - const rename = manualSWR(`/api/customGpts`, 'post'); - const deleteCustom = manualSWR(`/api/customGpts/delete`, 'post', (res) => { - const fetchedModels = res.data.map((modelItem) => ({ + const rename = manualSWR(`/api/customGpts`, 'post', res => {}); + const deleteCustom = manualSWR(`/api/customGpts/delete`, 'post', res => { + const fetchedModels = res.data.map(modelItem => ({ ...modelItem, - name: modelItem.chatGptLabel + name: modelItem.chatGptLabel, + model: 'chatgptCustom' })); - dispatch(setModels(fetchedModels)); + setCustomGPTModels(fetchedModels); }); - const icon = getIconOfModel({ size: 20, sender: modelName, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, className: "mr-2" }); + const icon = getIconOfModel({ + size: 20, + sender: chatGptLabel || model, + isCreatedByUser: false, + model, + chatGptLabel, + promptPrefix, + error: false, + className: 'mr-2' + }); - if (value === 'chatgptCustom') { + if (model !== 'chatgptCustom') + // regular model + return ( + + {icon} + {name} + {model === 'chatgpt' && $} + + ); + else if (model === 'chatgptCustom' && chatGptLabel === null && promptPrefix === null) + // base chatgptCustom model, click to add new chatgptCustom. return ( {icon} - {modelName} + {name} $ ); - } - if (initial[value]) - return ( - - {icon} - {modelName} - {value === 'chatgpt' && $} - - ); - - + // else: a chatgptCustom model const handleMouseOver = () => { setIsHovering(true); }; @@ -66,7 +78,7 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG setIsHovering(false); }; - const renameHandler = (e) => { + const renameHandler = e => { e.preventDefault(); e.stopPropagation(); setRenaming(true); @@ -75,10 +87,10 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG }, 25); }; - const onRename = (e) => { + const onRename = e => { e.preventDefault(); setRenaming(false); - if (modelInput === modelName) { + if (modelInput === name) { return; } rename.trigger({ @@ -89,13 +101,13 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG setCurrentName(modelInput); }; - const onDelete = async (e) => { + const onDelete = async e => { e.preventDefault(); await deleteCustom.trigger({ _id: id }); - onSelect('chatgpt', true); + onSelect('chatgpt'); }; - const handleKeyDown = (e) => { + const handleKeyDown = e => { if (e.key === 'Enter') { onRename(e); } @@ -115,14 +127,14 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG { + onClick={e => { if (isHovering) { return; } - onSelect(value, true); + onSelect('chatgptCustom', value); }} > - {customModel === value && ( + {currentConversation?.chatGptLabel === value && ( @@ -137,8 +149,8 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG type="text" className="pointer-events-auto z-50 m-0 mr-2 w-3/4 border border-blue-500 bg-transparent p-0 text-sm leading-tight outline-none" value={modelInput} - onClick={(e) => e.stopPropagation()} - onChange={(e) => setModelInput(e.target.value)} + onClick={e => e.stopPropagation()} + onChange={e => setModelInput(e.target.value)} // onBlur={onRename} onKeyDown={handleKeyDown} /> diff --git a/client/src/components/Input/Models/ModelMenu.jsx b/client/src/components/Input/Models/ModelMenu.jsx new file mode 100644 index 0000000000..65dd7f777e --- /dev/null +++ b/client/src/components/Input/Models/ModelMenu.jsx @@ -0,0 +1,205 @@ +import React, { useState, useEffect } from 'react'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import axios from 'axios'; +import ModelDialog from './ModelDialog'; +import MenuItems from './MenuItems'; +import { swr } from '~/utils/fetchers'; +import { getIconOfModel } from '~/utils'; + +import { Button } from '../../ui/Button.tsx'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuSeparator, + DropdownMenuTrigger +} from '../../ui/DropdownMenu.tsx'; +import { Dialog } from '../../ui/Dialog.tsx'; + +import store from '~/store'; + +export default function ModelMenu() { + const [modelSave, setModelSave] = useState(false); + const [menuOpen, setMenuOpen] = useState(false); + + const models = useRecoilValue(store.models); + const availableModels = useRecoilValue(store.availableModels); + const setCustomGPTModels = useSetRecoilState(store.customGPTModels); + + const conversation = useRecoilValue(store.conversation) || {}; + const { model, promptPrefix, chatGptLabel, conversationId } = conversation; + const { newConversation } = store.useConversation(); + + // fetch the list of saved chatgptCustom + const { data, isLoading, mutate } = swr(`/api/customGpts`, res => { + const fetchedModels = res.map(modelItem => ({ + ...modelItem, + name: modelItem.chatGptLabel, + model: 'chatgptCustom' + })); + + setCustomGPTModels(fetchedModels); + }); + + // useEffect(() => { + // mutate(); + // try { + // const lastSelected = JSON.parse(localStorage.getItem('model')); + + // if (lastSelected === 'chatgptCustom') { + // return; + // } else if (initial[lastSelected]) { + // dispatch(setModel(lastSelected)); + // } + // } catch (err) { + // console.log(err); + // } + + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, []); + + // update the default model when availableModels changes + // typically, availableModels changes => modelsFilter or customGPTModels changes + useEffect(() => { + if (conversationId == 'new') { + newConversation(); + } + }, [availableModels]); + + // save selected model to localstoreage + useEffect(() => { + if (model) localStorage.setItem('model', JSON.stringify({ model, chatGptLabel, promptPrefix })); + }, [model]); + + // set the current model + const onChange = (newModel, value = null) => { + setMenuOpen(false); + + if (!newModel) { + return; + } else if (newModel === model && value === chatGptLabel) { + // bypass if not changed + return; + } else if (newModel === 'chatgptCustom' && value === null) { + // return; + } else if (newModel !== 'chatgptCustom') { + newConversation({ + model: newModel, + chatGptLabel: null, + promptPrefix: null + }); + } else if (newModel === 'chatgptCustom') { + const targetModel = models.find(element => element.value == value); + if (targetModel) { + const chatGptLabel = targetModel?.chatGptLabel; + const promptPrefix = targetModel?.promptPrefix; + newConversation({ + model: newModel, + chatGptLabel, + promptPrefix + }); + } + } + }; + + const onOpenChange = open => { + mutate(); + if (!open) { + setModelSave(false); + } + }; + + const handleSaveState = value => { + if (!modelSave) { + return; + } + + setCustomGPTModels(value); + setModelSave(false); + }; + + const defaultColorProps = [ + 'text-gray-500', + 'hover:bg-gray-100', + 'hover:bg-opacity-20', + 'disabled:hover:bg-transparent', + 'dark:data-[state=open]:bg-gray-800', + 'dark:hover:bg-opacity-20', + 'dark:hover:bg-gray-900', + 'dark:hover:text-gray-400', + 'dark:disabled:hover:bg-transparent' + ]; + + const chatgptColorProps = [ + 'text-green-700', + 'data-[state=open]:bg-green-100', + 'dark:text-emerald-300', + 'hover:bg-green-100', + 'disabled:hover:bg-transparent', + 'dark:data-[state=open]:bg-green-900', + 'dark:hover:bg-opacity-50', + 'dark:hover:bg-green-900', + 'dark:hover:text-gray-100', + 'dark:disabled:hover:bg-transparent' + ]; + + const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps; + const icon = getIconOfModel({ + size: 32, + sender: chatGptLabel || model, + isCreatedByUser: false, + model, + chatGptLabel, + promptPrefix, + error: false, + button: true + }); + + return ( + + + + + + event.preventDefault()} + > + Select a Model + + + {availableModels.length ? ( + + ) : ( + No model available. + )} + + + + + + ); +} diff --git a/client/src/components/Main/RowButton.jsx b/client/src/components/Input/RowButton.jsx similarity index 100% rename from client/src/components/Main/RowButton.jsx rename to client/src/components/Input/RowButton.jsx diff --git a/client/src/components/Main/SubmitButton.jsx b/client/src/components/Input/SubmitButton.jsx similarity index 85% rename from client/src/components/Main/SubmitButton.jsx rename to client/src/components/Input/SubmitButton.jsx index 20553a933f..5d76250d6b 100644 --- a/client/src/components/Main/SubmitButton.jsx +++ b/client/src/components/Input/SubmitButton.jsx @@ -1,11 +1,7 @@ import React from 'react'; -import { useSelector } from 'react-redux'; -export default function SubmitButton({ submitMessage, disabled }) { - const { isSubmitting } = useSelector((state) => state.submit); - const { error, latestMessage } = useSelector((state) => state.convo); - - const clickHandler = (e) => { +export default function SubmitButton({ submitMessage, disabled, isSubmitting }) { + const clickHandler = e => { e.preventDefault(); submitMessage(); }; diff --git a/client/src/components/Input/index.jsx b/client/src/components/Input/index.jsx new file mode 100644 index 0000000000..6332aa7654 --- /dev/null +++ b/client/src/components/Input/index.jsx @@ -0,0 +1,454 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useRecoilValue, useRecoilState } from 'recoil'; +// import { SSE } from '~/utils/sse'; +import SubmitButton from './SubmitButton'; +// import AdjustToneButton from './AdjustToneButton'; +// import BingStyles from './BingStyles'; +import ModelMenu from './Models/ModelMenu'; +import Footer from './Footer'; +import TextareaAutosize from 'react-textarea-autosize'; +import createPayload from '~/utils/createPayload'; +import RegenerateIcon from '../svg/RegenerateIcon'; +import StopGeneratingIcon from '../svg/StopGeneratingIcon'; +// import { setConversation, setError, refreshConversation } from '~/store/convoSlice'; +// import { setMessages } from '~/store/messageSlice'; +// import { setSubmitState, toggleCursor } from '~/store/submitSlice'; +// import { setText } from '~/store/textSlice'; +import { useMessageHandler } from '../../utils/handleSubmit'; + +import store from '~/store'; + +export default function TextChat() { + const inputRef = useRef(null); + const isComposing = useRef(false); + + const conversation = useRecoilValue(store.conversation); + const latestMessage = useRecoilValue(store.latestMessage); + const messages = useRecoilValue(store.messages); + + const isSubmitting = useRecoilValue(store.isSubmitting); + + // TODO: do we need this? + const disabled = false; + + const [text, setText] = useState(''); + const { ask, regenerate, stopGenerating } = useMessageHandler(); + + const bingStylesRef = useRef(null); + const [showBingToneSetting, setShowBingToneSetting] = useState(false); + + const isNotAppendable = latestMessage?.cancelled || latestMessage?.error; + + // auto focus to input, when enter a conversation. + useEffect(() => { + inputRef.current?.focus(); + setText(''); + }, [conversation?.conversationId]); + + // controls the height of Bing tone style tabs + useEffect(() => { + if (!inputRef.current) { + return; // wait for the ref to be available + } + + const resizeObserver = new ResizeObserver(() => { + const newHeight = inputRef.current.clientHeight; + if (newHeight >= 24) { + // 24 is the default height of the input + bingStylesRef.current.style.bottom = 15 + newHeight + 'px'; + } + }); + resizeObserver.observe(inputRef.current); + return () => resizeObserver.disconnect(); + }, [inputRef]); + + // const messageHandler = (data, currentState, currentMsg) => { + // const { messages, message, sender, isRegenerate } = currentState; + + // if (isRegenerate) + // dispatch( + // setMessages([ + // ...messages, + // { + // sender, + // text: data, + // parentMessageId: message?.overrideParentMessageId, + // messageId: message?.overrideParentMessageId + '_', + // submitting: true + // } + // ]) + // ); + // else + // dispatch( + // setMessages([ + // ...messages, + // currentMsg, + // { + // sender, + // text: data, + // parentMessageId: currentMsg?.messageId, + // messageId: currentMsg?.messageId + '_', + // submitting: true + // } + // ]) + // ); + // }; + + // const cancelHandler = (data, currentState, currentMsg) => { + // const { messages, message, sender, isRegenerate } = currentState; + + // if (isRegenerate) + // dispatch( + // setMessages([ + // ...messages, + // { + // sender, + // text: data, + // parentMessageId: message?.overrideParentMessageId, + // messageId: message?.overrideParentMessageId + '_', + // cancelled: true + // } + // ]) + // ); + // else + // dispatch( + // setMessages([ + // ...messages, + // currentMsg, + // { + // sender, + // text: data, + // parentMessageId: currentMsg?.messageId, + // messageId: currentMsg?.messageId + '_', + // cancelled: true + // } + // ]) + // ); + // }; + + // const createdHandler = (data, currentState, currentMsg) => { + // const { conversationId } = currentMsg; + // dispatch( + // setConversation({ + // conversationId, + // latestMessage: null + // }) + // ); + // }; + + // const convoHandler = (data, currentState) => { + // const { requestMessage, responseMessage } = data; + // const { messages, message, isCustomModel, isRegenerate } = currentState; + // const { model, chatGptLabel, promptPrefix } = message; + // if (isRegenerate) dispatch(setMessages([...messages, responseMessage])); + // else dispatch(setMessages([...messages, requestMessage, responseMessage])); + // dispatch(setSubmitState(false)); + + // const isBing = model === 'bingai' || model === 'sydney'; + + // // refresh title + // if (requestMessage.parentMessageId == '00000000-0000-0000-0000-000000000000') { + // setTimeout(() => { + // dispatch(refreshConversation()); + // }, 2000); + + // // in case it takes too long. + // setTimeout(() => { + // dispatch(refreshConversation()); + // }, 5000); + // } + + // if (!isBing && convo.conversationId === null && convo.parentMessageId === null) { + // const { title } = data; + // const { conversationId, messageId } = responseMessage; + // dispatch( + // setConversation({ + // title, + // conversationId, + // parentMessageId: messageId, + // jailbreakConversationId: null, + // conversationSignature: null, + // clientId: null, + // invocationId: null, + // chatGptLabel: model === isCustomModel ? chatGptLabel : null, + // promptPrefix: model === isCustomModel ? promptPrefix : null, + // latestMessage: null + // }) + // ); + // } else if (model === 'bingai') { + // console.log('Bing data:', data); + // const { title } = data; + // const { conversationSignature, clientId, conversationId, invocationId, parentMessageId } = + // responseMessage; + // dispatch( + // setConversation({ + // title, + // parentMessageId, + // conversationSignature, + // clientId, + // conversationId, + // invocationId, + // latestMessage: null + // }) + // ); + // } else if (model === 'sydney') { + // const { title } = data; + // const { + // jailbreakConversationId, + // parentMessageId, + // conversationSignature, + // clientId, + // conversationId, + // invocationId + // } = responseMessage; + // dispatch( + // setConversation({ + // title, + // jailbreakConversationId, + // parentMessageId, + // conversationSignature, + // clientId, + // conversationId, + // invocationId, + // latestMessage: null + // }) + // ); + // } + // }; + + // const errorHandler = (data, currentState, currentMsg) => { + // const { messages, message } = currentState; + // console.log('Error:', data); + // const errorResponse = { + // ...data, + // error: true, + // parentMessageId: currentMsg?.messageId + // }; + // dispatch(setSubmitState(false)); + // dispatch(setMessages([...messages, currentMsg, errorResponse])); + // dispatch(setText(message?.text)); + // dispatch(setError(true)); + // return; + // }; + const submitMessage = () => { + ask({ text }); + setText(''); + }; + + // useEffect(() => { + // inputRef.current?.focus(); + // if (Object.keys(submission).length === 0) { + // return; + // } + + // const currentState = submission; + + // let currentMsg = { ...currentState.message }; + // let latestResponseText = ''; + + // const { server, payload } = createPayload(submission); + // const onMessage = e => { + // if (stopStream) { + // return; + // } + + // const data = JSON.parse(e.data); + + // if (data.final) { + // convoHandler(data, currentState); + // dispatch(toggleCursor()); + // console.log('final', data); + // } + // if (data.created) { + // currentMsg = data.message; + // createdHandler(data, currentState, currentMsg); + // } else { + // let text = data.text || data.response; + // if (data.initial) { + // dispatch(toggleCursor()); + // } + // if (data.message) { + // latestResponseText = text; + // messageHandler(text, currentState, currentMsg); + // } + // // console.log('dataStream', data); + // } + // }; + + // const events = new SSE(server, { + // payload: JSON.stringify(payload), + // headers: { 'Content-Type': 'application/json' } + // }); + + // events.onopen = function () { + // console.log('connection is opened'); + // }; + + // events.onmessage = onMessage; + + // events.oncancel = () => { + // dispatch(toggleCursor(true)); + // cancelHandler(latestResponseText, currentState, currentMsg); + // }; + + // events.onerror = function (e) { + // console.log('error in opening conn.'); + // events.close(); + + // const data = JSON.parse(e.data); + // dispatch(toggleCursor(true)); + // errorHandler(data, currentState, currentMsg); + // }; + + // events.stream(); + + // return () => { + // events.removeEventListener('message', onMessage); + // dispatch(toggleCursor(true)); + // const isCancelled = events.readyState <= 1; + // events.close(); + // if (isCancelled) { + // const e = new Event('cancel'); + // events.dispatchEvent(e); + // } + // }; + // }, [submission]); + + const handleRegenerate = () => { + if (latestMessage && !latestMessage?.isCreatedByUser) regenerate(latestMessage); + }; + + const handleStopGenerating = () => { + stopGenerating(); + }; + + const handleKeyDown = e => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + } + + if (e.key === 'Enter' && !e.shiftKey) { + if (!isComposing?.current) submitMessage(); + } + }; + + const handleKeyUp = e => { + if (e.keyCode === 8 && e.target.value.trim() === '') { + setText(e.target.value); + } + + if (e.key === 'Enter' && e.shiftKey) { + return console.log('Enter + Shift'); + } + + if (isSubmitting) { + return; + } + }; + + const handleCompositionStart = () => { + isComposing.current = true; + }; + + const handleCompositionEnd = () => { + isComposing.current = false; + }; + + const changeHandler = e => { + const { value } = e.target; + + setText(value); + }; + + const isSearchView = messages?.[0]?.searchResult === true; + const getPlaceholderText = () => { + if (isSearchView) { + return 'Click a message title to open its conversation.'; + } + + if (disabled) { + return 'Choose another model or customize GPT again'; + } + + if (isNotAppendable) { + return 'Edit your message or Regenerate.'; + } + + return ''; + }; + + const handleBingToneSetting = () => { + setShowBingToneSetting(show => !show); + }; + + if (isSearchView) return <>; + + return ( + <> +
+
+
+ + {/* */} + {isSubmitting ? ( + + ) : latestMessage && !latestMessage?.isCreatedByUser ? ( + + ) : null} + +
+ + + + {/* {messages?.length && model === 'sydney' ? ( + + ) : null} */} +
+
+
+
+
+ + ); +} diff --git a/client/src/components/Main/Regenerate.jsx b/client/src/components/Main/Regenerate.jsx deleted file mode 100644 index b08b90db40..0000000000 --- a/client/src/components/Main/Regenerate.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import RegenerateIcon from '../svg/RegenerateIcon'; - -export default function Regenerate({ submitMessage, tryAgain, errorMessage }) { - const clickHandler = (e) => { - e.preventDefault(); - submitMessage(); - }; - - return ( - <> - - There was an error generating a response - - - {!errorMessage.includes('short') && ( - - )} - - - - ); -} diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx deleted file mode 100644 index 54e3d44344..0000000000 --- a/client/src/components/Main/TextChat.jsx +++ /dev/null @@ -1,446 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { SSE } from '~/utils/sse'; -import SubmitButton from './SubmitButton'; -// import Regenerate from './Regenerate'; // not used as of Wentao's update -import BingStyles from './BingStyles'; -import ModelMenu from '../Models/ModelMenu'; -import Footer from './Footer'; -import TextareaAutosize from 'react-textarea-autosize'; -import createPayload from '~/utils/createPayload'; -import RegenerateIcon from '../svg/RegenerateIcon'; -import StopGeneratingIcon from '../svg/StopGeneratingIcon'; -import { setConversation, setError, refreshConversation } from '~/store/convoSlice'; -import { setMessages } from '~/store/messageSlice'; -import { setSubmitState, toggleCursor } from '~/store/submitSlice'; -import { setText } from '~/store/textSlice'; -import { useMessageHandler } from '../../utils/handleSubmit'; -import AdjustToneButton from './AdjustToneButton'; - -export default function TextChat({ messages }) { - const inputRef = useRef(null); - const bingStylesRef = useRef(null); - const [showBingToneSetting, setShowBingToneSetting] = useState(false); - const isComposing = useRef(false); - const dispatch = useDispatch(); - const convo = useSelector(state => state.convo); - const { isSubmitting, stopStream, submission, disabled, model } = useSelector(state => state.submit); - const { text } = useSelector(state => state.text); - const { latestMessage } = convo; - const { ask, regenerate, stopGenerating } = useMessageHandler(); - - const isNotAppendable = latestMessage?.cancelled || latestMessage?.error; - - // auto focus to input, when enter a conversation. - useEffect(() => { - inputRef.current?.focus(); - }, [convo?.conversationId]); - - // controls the height of Bing tone style tabs - useEffect(() => { - if (!inputRef.current) { - return; // wait for the ref to be available - } - - const resizeObserver = new ResizeObserver(() => { - const newHeight = inputRef.current.clientHeight; - if (newHeight >= 24) { // 24 is the default height of the input - bingStylesRef.current.style.bottom = 15 + newHeight + 'px'; - } - }); - resizeObserver.observe(inputRef.current); - return () => resizeObserver.disconnect(); - }, [inputRef]); - - const messageHandler = (data, currentState, currentMsg) => { - const { messages, message, sender, isRegenerate } = currentState; - - if (isRegenerate) - dispatch( - setMessages([ - ...messages, - { - sender, - text: data, - parentMessageId: message?.overrideParentMessageId, - messageId: message?.overrideParentMessageId + '_', - submitting: true - } - ]) - ); - else - dispatch( - setMessages([ - ...messages, - currentMsg, - { - sender, - text: data, - parentMessageId: currentMsg?.messageId, - messageId: currentMsg?.messageId + '_', - submitting: true - } - ]) - ); - }; - - const cancelHandler = (data, currentState, currentMsg) => { - const { messages, message, sender, isRegenerate } = currentState; - - if (isRegenerate) - dispatch( - setMessages([ - ...messages, - { - sender, - text: data, - parentMessageId: message?.overrideParentMessageId, - messageId: message?.overrideParentMessageId + '_', - cancelled: true - } - ]) - ); - else - dispatch( - setMessages([ - ...messages, - currentMsg, - { - sender, - text: data, - parentMessageId: currentMsg?.messageId, - messageId: currentMsg?.messageId + '_', - cancelled: true - } - ]) - ); - }; - - const createdHandler = (data, currentState, currentMsg) => { - const { conversationId } = currentMsg; - dispatch( - setConversation({ - conversationId, - latestMessage: null - }) - ); - }; - - const convoHandler = (data, currentState) => { - const { requestMessage, responseMessage } = data; - const { messages, message, isCustomModel, isRegenerate } = currentState; - const { model, chatGptLabel, promptPrefix } = message; - if (isRegenerate) dispatch(setMessages([...messages, responseMessage])); - else dispatch(setMessages([...messages, requestMessage, responseMessage])); - dispatch(setSubmitState(false)); - - const isBing = model === 'bingai' || model === 'sydney'; - - // refresh title - if (requestMessage.parentMessageId == '00000000-0000-0000-0000-000000000000') { - setTimeout(() => { - dispatch(refreshConversation()); - }, 2000); - - // in case it takes too long. - setTimeout(() => { - dispatch(refreshConversation()); - }, 5000); - } - - if (!isBing && convo.conversationId === null && convo.parentMessageId === null) { - const { title } = data; - const { conversationId, messageId } = responseMessage; - dispatch( - setConversation({ - title, - conversationId, - parentMessageId: messageId, - jailbreakConversationId: null, - conversationSignature: null, - clientId: null, - invocationId: null, - chatGptLabel: model === isCustomModel ? chatGptLabel : null, - promptPrefix: model === isCustomModel ? promptPrefix : null, - latestMessage: null - }) - ); - } else if (model === 'bingai') { - console.log('Bing data:', data); - const { title } = data; - const { conversationSignature, clientId, conversationId, invocationId, parentMessageId } = - responseMessage; - dispatch( - setConversation({ - title, - parentMessageId, - conversationSignature, - clientId, - conversationId, - invocationId, - latestMessage: null - }) - ); - } else if (model === 'sydney') { - const { title } = data; - const { - jailbreakConversationId, - parentMessageId, - conversationSignature, - clientId, - conversationId, - invocationId - } = responseMessage; - dispatch( - setConversation({ - title, - jailbreakConversationId, - parentMessageId, - conversationSignature, - clientId, - conversationId, - invocationId, - latestMessage: null - }) - ); - } - }; - - const errorHandler = (data, currentState, currentMsg) => { - const { messages, message } = currentState; - console.log('Error:', data); - const errorResponse = { - ...data, - error: true, - parentMessageId: currentMsg?.messageId - }; - dispatch(setSubmitState(false)); - dispatch(setMessages([...messages, currentMsg, errorResponse])); - dispatch(setText(message?.text)); - dispatch(setError(true)); - return; - }; - const submitMessage = () => { - ask({ text }); - }; - - useEffect(() => { - inputRef.current?.focus(); - if (Object.keys(submission).length === 0) { - return; - } - - const currentState = submission; - - let currentMsg = { ...currentState.message }; - let latestResponseText = ''; - - const { server, payload } = createPayload(submission); - const onMessage = e => { - if (stopStream) { - return; - } - - const data = JSON.parse(e.data); - - if (data.final) { - convoHandler(data, currentState); - dispatch(toggleCursor()); - console.log('final', data); - } - if (data.created) { - currentMsg = data.message; - createdHandler(data, currentState, currentMsg); - } else { - let text = data.text || data.response; - if (data.initial) { - dispatch(toggleCursor()); - } - if (data.message) { - latestResponseText = text; - messageHandler(text, currentState, currentMsg); - } - // console.log('dataStream', data); - } - }; - - const events = new SSE(server, { - payload: JSON.stringify(payload), - headers: { 'Content-Type': 'application/json' } - }); - - events.onopen = function () { - console.log('connection is opened'); - }; - - events.onmessage = onMessage; - - events.oncancel = () => { - dispatch(toggleCursor(true)); - cancelHandler(latestResponseText, currentState, currentMsg); - }; - - events.onerror = function (e) { - console.log('error in opening conn.'); - events.close(); - - const data = JSON.parse(e.data); - dispatch(toggleCursor(true)); - errorHandler(data, currentState, currentMsg); - }; - - events.stream(); - - return () => { - events.removeEventListener('message', onMessage); - dispatch(toggleCursor(true)); - const isCancelled = events.readyState <= 1; - events.close(); - if (isCancelled) { - const e = new Event('cancel'); - events.dispatchEvent(e); - } - }; - }, [submission]); - - const handleRegenerate = () => { - if (latestMessage && !latestMessage?.isCreatedByUser) regenerate(latestMessage); - }; - - const handleStopGenerating = () => { - stopGenerating(); - }; - - const handleKeyDown = e => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - } - - if (e.key === 'Enter' && !e.shiftKey) { - if (!isComposing.current) submitMessage(); - } - }; - - const handleKeyUp = e => { - if (e.keyCode === 8 && e.target.value.trim() === '') { - dispatch(setText(e.target.value)); - } - - if (e.key === 'Enter' && e.shiftKey) { - return console.log('Enter + Shift'); - } - - if (isSubmitting) { - return; - } - }; - - const handleCompositionStart = () => { - isComposing.current = true; - }; - - const handleCompositionEnd = () => { - isComposing.current = false; - }; - - const changeHandler = e => { - const { value } = e.target; - - // if (isSubmitting && (value === '' || value === '\n')) { - // return; - // } - dispatch(setText(value)); - }; - - // const tryAgain = (e) => { - // e.preventDefault(); - // dispatch(setError(false)); - // }; - - const isSearchView = messages?.[0]?.searchResult === true; - const getPlaceholderText = () => { - if (isSearchView) { - return 'Click a message title to open its conversation.'; - } - - if (disabled) { - return 'Choose another model or customize GPT again'; - } - - if (isNotAppendable) { - return 'Edit your message or Regenerate.'; - } - - return ''; - }; - - const handleBingToneSetting = () => { - setShowBingToneSetting((show) => !show) - } - - return ( - <> -
-
-
- - - {isSubmitting && !isSearchView ? ( - - ) : latestMessage && !latestMessage?.isCreatedByUser && !isSearchView ? ( - - ) : null} - -
- - - - {messages?.length && model === 'sydney' ? - : - null} -
-
-
-
-
- - ); -} diff --git a/client/src/components/MessageHandler/index.jsx b/client/src/components/MessageHandler/index.jsx index 4a44dca90c..2a7d329be1 100644 --- a/client/src/components/MessageHandler/index.jsx +++ b/client/src/components/MessageHandler/index.jsx @@ -1,10 +1,10 @@ -import React, { useEffect, useRef, useState } from "react"; -import { useRecoilState, useResetRecoilState, useSetRecoilState } from "recoil"; -import { SSE } from "~/utils/sse"; -import { useMessageHandler } from "../../utils/handleSubmit"; -import createPayload from "~/utils/createPayload"; +import React, { useEffect, useRef, useState } from 'react'; +import { useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil'; +import { SSE } from '~/utils/sse'; +import { useMessageHandler } from '../../utils/handleSubmit'; +import createPayload from '~/utils/createPayload'; -import store from "~/store"; +import store from '~/store'; export default function MessageHandler({ messages }) { const [submission, setSubmission] = useRecoilState(store.submission); @@ -16,12 +16,7 @@ export default function MessageHandler({ messages }) { const { refreshConversations } = store.useConversations(); const messageHandler = (data, submission) => { - const { - messages, - message, - initialResponse, - isRegenerate = false, - } = submission; + const { messages, message, initialResponse, isRegenerate = false } = submission; if (isRegenerate) setMessages([ @@ -30,9 +25,9 @@ export default function MessageHandler({ messages }) { ...initialResponse, text: data, parentMessageId: message?.overrideParentMessageId, - messageId: message?.overrideParentMessageId + "_", - submitting: true, - }, + messageId: message?.overrideParentMessageId + '_', + submitting: true + } ]); else setMessages([ @@ -42,19 +37,14 @@ export default function MessageHandler({ messages }) { ...initialResponse, text: data, parentMessageId: message?.messageId, - messageId: message?.messageId + "_", - submitting: true, - }, + messageId: message?.messageId + '_', + submitting: true + } ]); }; const cancelHandler = (data, submission) => { - const { - messages, - message, - initialResponse, - isRegenerate = false, - } = submission; + const { messages, message, initialResponse, isRegenerate = false } = submission; if (isRegenerate) setMessages([ @@ -63,9 +53,9 @@ export default function MessageHandler({ messages }) { ...initialResponse, text: data, parentMessageId: message?.overrideParentMessageId, - messageId: message?.overrideParentMessageId + "_", - cancelled: true, - }, + messageId: message?.overrideParentMessageId + '_', + cancelled: true + } ]); else setMessages([ @@ -75,19 +65,14 @@ export default function MessageHandler({ messages }) { ...initialResponse, text: data, parentMessageId: message?.messageId, - messageId: message?.messageId + "_", - cancelled: true, - }, + messageId: message?.messageId + '_', + cancelled: true + } ]); }; const createdHandler = (data, submission) => { - const { - messages, - message, - initialResponse, - isRegenerate = false, - } = submission; + const { messages, message, initialResponse, isRegenerate = false } = submission; if (isRegenerate) setMessages([ @@ -95,9 +80,9 @@ export default function MessageHandler({ messages }) { { ...initialResponse, parentMessageId: message?.overrideParentMessageId, - messageId: message?.overrideParentMessageId + "_", - submitting: true, - }, + messageId: message?.overrideParentMessageId + '_', + submitting: true + } ]); else setMessages([ @@ -106,27 +91,21 @@ export default function MessageHandler({ messages }) { { ...initialResponse, parentMessageId: message?.messageId, - messageId: message?.messageId + "_", - submitting: true, - }, + messageId: message?.messageId + '_', + submitting: true + } ]); const { conversationId } = message; - setConversation((prevState) => ({ + setConversation(prevState => ({ ...prevState, - conversationId, + conversationId })); resetLatestMessage(); }; const finalHandler = (data, submission) => { - const { - conversation, - messages, - message, - initialResponse, - isRegenerate = false, - } = submission; + const { conversation, messages, message, initialResponse, isRegenerate = false } = submission; const { requestMessage, responseMessage } = data; const { conversationId } = requestMessage; @@ -137,9 +116,7 @@ export default function MessageHandler({ messages }) { setIsSubmitting(false); // refresh title - if ( - requestMessage.parentMessageId == "00000000-0000-0000-0000-000000000000" - ) { + if (requestMessage.parentMessageId == '00000000-0000-0000-0000-000000000000') { setTimeout(() => { refreshConversations(); }, 2000); @@ -151,12 +128,12 @@ export default function MessageHandler({ messages }) { } const { model, chatGptLabel, promptPrefix } = conversation; - const isBing = model === "bingai" || model === "sydney"; + const isBing = model === 'bingai' || model === 'sydney'; if (!isBing) { const { title } = data; const { conversationId } = responseMessage; - setConversation((prevState) => ({ + setConversation(prevState => ({ ...prevState, title, conversationId, @@ -166,13 +143,12 @@ export default function MessageHandler({ messages }) { invocationId: null, chatGptLabel, promptPrefix, - latestMessage: null, + latestMessage: null })); - } else if (model === "bingai") { + } else if (model === 'bingai') { const { title } = data; - const { conversationSignature, clientId, conversationId, invocationId } = - responseMessage; - setConversation((prevState) => ({ + const { conversationSignature, clientId, conversationId, invocationId } = responseMessage; + setConversation(prevState => ({ ...prevState, title, conversationId, @@ -182,9 +158,9 @@ export default function MessageHandler({ messages }) { invocationId, chatGptLabel, promptPrefix, - latestMessage: null, + latestMessage: null })); - } else if (model === "sydney") { + } else if (model === 'sydney') { const { title } = data; const { jailbreakConversationId, @@ -192,9 +168,9 @@ export default function MessageHandler({ messages }) { conversationSignature, clientId, conversationId, - invocationId, + invocationId } = responseMessage; - setConversation((prevState) => ({ + setConversation(prevState => ({ ...prevState, title, conversationId, @@ -204,25 +180,19 @@ export default function MessageHandler({ messages }) { invocationId, chatGptLabel, promptPrefix, - latestMessage: null, + latestMessage: null })); } }; const errorHandler = (data, submission) => { - const { - conversation, - messages, - message, - initialResponse, - isRegenerate = false, - } = submission; + const { conversation, messages, message, initialResponse, isRegenerate = false } = submission; - console.log("Error:", data); + console.log('Error:', data); const errorResponse = { ...data, error: true, - parentMessageId: message?.messageId, + parentMessageId: message?.messageId }; setIsSubmitting(false); setMessages([...messages, message, errorResponse]); @@ -240,16 +210,16 @@ export default function MessageHandler({ messages }) { const events = new SSE(server, { payload: JSON.stringify(payload), - headers: { "Content-Type": "application/json" }, + headers: { 'Content-Type': 'application/json' } }); - let latestResponseText = ""; - events.onmessage = (e) => { + let latestResponseText = ''; + events.onmessage = e => { const data = JSON.parse(e.data); if (data.final) { finalHandler(data, { ...submission, message }); - console.log("final", data); + console.log('final', data); } if (data.created) { message = { @@ -257,10 +227,10 @@ export default function MessageHandler({ messages }) { model: message?.model, chatGptLabel: message?.chatGptLabel, promptPrefix: message?.promptPrefix, - overrideParentMessageId: message?.overrideParentMessageId, + overrideParentMessageId: message?.overrideParentMessageId }; createdHandler(data, { ...submission, message }); - console.log("created", message); + console.log('created', message); } else { let text = data.text || data.response; if (data.initial) console.log(data); @@ -273,13 +243,12 @@ export default function MessageHandler({ messages }) { } }; - events.onopen = () => console.log("connection is opened"); + events.onopen = () => console.log('connection is opened'); - events.oncancel = (e) => - cancelHandler(latestResponseText, { ...submission, message }); + events.oncancel = e => cancelHandler(latestResponseText, { ...submission, message }); events.onerror = function (e) { - console.log("error in opening conn."); + console.log('error in opening conn.'); events.close(); const data = JSON.parse(e.data); @@ -294,7 +263,7 @@ export default function MessageHandler({ messages }) { const isCancelled = events.readyState <= 1; events.close(); if (isCancelled) { - const e = new Event("cancel"); + const e = new Event('cancel'); events.dispatchEvent(e); } setIsSubmitting(false); diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index 7f4f32803f..e0389179f6 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -1,20 +1,19 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; +import { useRecoilValue, useSetRecoilState, useResetRecoilState } from 'recoil'; import SubRow from './Content/SubRow'; import Content from './Content/Content'; import MultiMessage from './MultiMessage'; import HoverButtons from './HoverButtons'; import SiblingSwitch from './SiblingSwitch'; -import { setConversation, setLatestMessage } from '~/store/convoSlice'; -import { setModel, setCustomModel, setCustomGpt, setDisabled } from '~/store/submitSlice'; -import { setMessages } from '~/store/messageSlice'; import { fetchById } from '~/utils/fetchers'; import { getIconOfModel } from '~/utils'; import { useMessageHandler } from '~/utils/handleSubmit'; +import store from '~/store'; + export default function Message({ + conversation, message, - messages, scrollToBottom, currentEditId, setCurrentEditId, @@ -22,30 +21,24 @@ export default function Message({ siblingCount, setSiblingIdx }) { - const { isSubmitting, model, chatGptLabel, cursor, promptPrefix } = useSelector(state => state.submit); + const isSubmitting = useRecoilValue(store.isSubmitting); + const setLatestMessage = useSetRecoilState(store.latestMessage); + const { model, chatGptLabel, promptPrefix } = conversation; const [abortScroll, setAbort] = useState(false); const { sender, text, searchResult, isCreatedByUser, error, submitting } = message; const textEditor = useRef(null); const last = !message?.children?.length; const edit = message.messageId == currentEditId; const { ask } = useMessageHandler(); - const dispatch = useDispatch(); - // const currentConvo = convoMap[message.conversationId]; - - // const notUser = !isCreatedByUser; // sender.toLowerCase() !== 'user'; - // const blinker = submitting && isSubmitting && last && !isCreatedByUser; + const { switchToConversation } = store.useConversation(); const blinker = submitting && isSubmitting; const generateCursor = useCallback(() => { if (!blinker) { return ''; } - if (!cursor) { - return ''; - } - return ; - }, [blinker, cursor]); + }, [blinker]); useEffect(() => { if (blinker && !abortScroll) { @@ -55,9 +48,7 @@ export default function Message({ useEffect(() => { if (last) { - // TODO: stop using conversation.parentMessageId and remove it. - dispatch(setConversation({ parentMessageId: message?.messageId })); - dispatch(setLatestMessage({ ...message })); + setLatestMessage({ ...message }); } }, [last, message]); @@ -110,22 +101,23 @@ export default function Message({ const clickSearchResult = async () => { if (!searchResult) return; - dispatch(setMessages([])); + // dispatch(setMessages([])); const convoResponse = await fetchById('convos', message.conversationId); const convo = convoResponse.data; - if (convo?.chatGptLabel) { - dispatch(setModel('chatgptCustom')); - dispatch(setCustomModel(convo.chatGptLabel.toLowerCase())); - } else { - dispatch(setModel(convo.model)); - dispatch(setCustomModel(null)); - } + // if (convo?.chatGptLabel) { + // // dispatch(setModel('chatgptCustom')); + // // dispatch(setCustomModel(convo.chatGptLabel.toLowerCase())); + // } else { + // // dispatch(setModel(convo.model)); + // // dispatch(setCustomModel(null)); + // } - dispatch(setCustomGpt(convo)); - dispatch(setConversation(convo)); - const { data } = await fetchById('messages', message.conversationId); - dispatch(setMessages(data)); - dispatch(setDisabled(false)); + // dispatch(setCustomGpt(convo)); + switchToConversation(convo); + // dispatch(setConversation(convo)); + // const { data } = await fetchById('messages', message.conversationId); + // dispatch(setMessages(data)); + // dispatch(setDisabled(false)); }; return ( @@ -133,7 +125,6 @@ export default function Message({
@@ -199,17 +190,13 @@ export default function Message({
{/*
*/}
- {!isCreatedByUser ? - <> - - {generateCursor()} - : - <> - {text} - - } + {!isCreatedByUser ? ( + <> + + + ) : ( + <>{text} + )}
)} @@ -230,8 +217,8 @@ export default function Message({
{ - setSiblingIdx(messageList?.length - value - 1); + const setSiblingIdxRev = value => { + setSiblingIdx(messagesTree?.length - value - 1); }; useEffect(() => { // reset siblingIdx when changes, mostly a new message is submitting. setSiblingIdx(0); - }, [messageList?.length]) + }, [messagesTree?.length]); // if (!messageList?.length) return null; - if (!(messageList && messageList.length)) { + if (!(messagesTree && messagesTree.length)) { return null; } - if (siblingIdx >= messageList?.length) { + if (siblingIdx >= messagesTree?.length) { setSiblingIdx(0); return null; } - const message = messageList[messageList.length - siblingIdx - 1]; + const message = messagesTree[messagesTree.length - siblingIdx - 1]; return ( ); diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index 9027e851e3..262575116d 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -1,27 +1,30 @@ import React, { useEffect, useState, useRef, useCallback } from 'react'; +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import Spinner from '../svg/Spinner'; import { throttle } from 'lodash'; import { CSSTransition } from 'react-transition-group'; import ScrollToBottom from './ScrollToBottom'; import MultiMessage from './MultiMessage'; -import { useSelector } from 'react-redux'; -export default function Messages({ messages, messageTree }) { +import store from '~/store'; + +export default function Messages() { const [currentEditId, setCurrentEditId] = useState(-1); - const { conversationId } = useSelector((state) => state.convo); - const { model, customModel } = useSelector((state) => state.submit); - const { models } = useSelector((state) => state.models); + const messagesTree = useRecoilValue(store.messagesTree); + const conversation = useRecoilValue(store.conversation) || {}; + const { conversationId, model, chatGptLabel } = conversation; + const models = useRecoilValue(store.models) || []; const [showScrollButton, setShowScrollButton] = useState(false); const scrollableRef = useRef(null); const messagesEndRef = useRef(null); - const modelName = models.find((element) => element.model == model)?.name; + const modelName = models.find(element => element.model == model)?.name; useEffect(() => { const timeoutId = setTimeout(() => { const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current; const diff = Math.abs(scrollHeight - scrollTop); - const percent = Math.abs(clientHeight - diff ) / clientHeight; + const percent = Math.abs(clientHeight - diff) / clientHeight; const hasScrollbar = scrollHeight > clientHeight && percent > 0.2; setShowScrollButton(hasScrollbar); }, 650); @@ -33,17 +36,24 @@ export default function Messages({ messages, messageTree }) { clearTimeout(timeoutId); window.removeEventListener('scroll', handleScroll); }; - }, [messages]); + }, [messagesTree]); - const scrollToBottom = useCallback(throttle(() => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - setShowScrollButton(false); - }, 750, { leading: true }), [messagesEndRef]); + const scrollToBottom = useCallback( + throttle( + () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + setShowScrollButton(false); + }, + 750, + { leading: true } + ), + [messagesEndRef] + ); const handleScroll = () => { const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current; const diff = Math.abs(scrollHeight - scrollTop); - const percent = Math.abs(clientHeight - diff ) / clientHeight; + const percent = Math.abs(clientHeight - diff) / clientHeight; if (percent <= 0.2) { setShowScrollButton(false); } else { @@ -57,7 +67,7 @@ export default function Messages({ messages, messageTree }) { timeoutId = setTimeout(handleScroll, 100); }; - const scrollHandler = (e) => { + const scrollHandler = e => { e.preventDefault(); scrollToBottom(); }; @@ -68,20 +78,19 @@ export default function Messages({ messages, messageTree }) { ref={scrollableRef} onScroll={debouncedHandleScroll} > - {/*
*/}
- Model: {modelName} {customModel ? `(${customModel})` : null} + Model: {modelName} {chatGptLabel ? `(${chatGptLabel})` : null}
- {(messageTree.length === 0 || !messages) ? ( + {messagesTree === null ? ( ) : ( <>
- {/*
*/}
); } diff --git a/client/src/components/Models/ModelMenu.jsx b/client/src/components/Models/ModelMenu.jsx deleted file mode 100644 index 9bc8aa5fdd..0000000000 --- a/client/src/components/Models/ModelMenu.jsx +++ /dev/null @@ -1,223 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import axios from 'axios'; -import { useSelector, useDispatch } from 'react-redux'; -import { - setSubmission, - setModel, - setDisabled, - setCustomGpt, - setCustomModel -} from '~/store/submitSlice'; -import { setNewConvo } from '~/store/convoSlice'; -import ModelDialog from './ModelDialog'; -import MenuItems from './MenuItems'; -import { swr } from '~/utils/fetchers'; -import { setModels, setInitial } from '~/store/modelSlice'; -import { setMessages } from '~/store/messageSlice'; -import { setText } from '~/store/textSlice'; -import { Button } from '../ui/Button.tsx'; -import { getIconOfModel } from '../../utils'; - -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuLabel, - DropdownMenuRadioGroup, - DropdownMenuSeparator, - DropdownMenuTrigger -} from '../ui/DropdownMenu.tsx'; - -import { Dialog } from '../ui/Dialog.tsx'; - -export default function ModelMenu() { - const dispatch = useDispatch(); - const [modelSave, setModelSave] = useState(false); - const [menuOpen, setMenuOpen] = useState(false); - const { model, customModel, promptPrefix, chatGptLabel } = useSelector((state) => state.submit); - const { models, modelMap, initial } = useSelector((state) => state.models); - const { data, isLoading, mutate } = swr(`/api/customGpts`, (res) => { - const fetchedModels = res.map((modelItem) => ({ - ...modelItem, - name: modelItem.chatGptLabel, - model: 'chatgptCustom' - })); - - dispatch(setModels(fetchedModels)); - }); - - useEffect(() => { - mutate(); - try { - const lastSelected = JSON.parse(localStorage.getItem('model')); - - if (lastSelected === 'chatgptCustom') { - return; - } else if (initial[lastSelected]) { - dispatch(setModel(lastSelected)); - } - } catch (err) { - console.log(err); - } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - axios.get('/api/models', { - timeout: 1000, - withCredentials: true - }).then((res) => { - return res.data - }).then((data) => { - const initial = {chatgpt: data?.hasOpenAI, chatgptCustom: data?.hasOpenAI, bingai: data?.hasBing, sydney: data?.hasBing, chatgptBrowser: data?.hasChatGpt} - dispatch(setInitial(initial)) - // TODO, auto reset default model - if (data?.hasOpenAI) { - dispatch(setModel('chatgpt')); - dispatch(setDisabled(false)); - dispatch(setCustomModel(null)); - dispatch(setCustomGpt({ chatGptLabel: null, promptPrefix: null })); - } else if (data?.hasBing) { - dispatch(setModel('bingai')); - dispatch(setDisabled(false)); - dispatch(setCustomModel(null)); - dispatch(setCustomGpt({ chatGptLabel: null, promptPrefix: null })); - } else if (data?.hasChatGpt) { - dispatch(setModel('chatgptBrowser')); - dispatch(setDisabled(false)); - dispatch(setCustomModel(null)); - dispatch(setCustomGpt({ chatGptLabel: null, promptPrefix: null })); - } else { - dispatch(setDisabled(true)); - } - }).catch((error) => { - console.error(error) - console.log('Not login!') - window.location.href = "/auth/login"; - }) - }, []) - - useEffect(() => { - localStorage.setItem('model', JSON.stringify(model)); - }, [model]); - - const filteredModels = models.filter(({model, _id }) => initial[model] ); - - const onChange = (value) => { - if (!value) { - return; - } else if (value === model) { - return; - } else if (value === 'chatgptCustom') { - // return; - } else if (initial[value]) { - dispatch(setModel(value)); - dispatch(setDisabled(false)); - dispatch(setCustomModel(null)); - dispatch(setCustomGpt({ chatGptLabel: null, promptPrefix: null })); - } else if (!initial[value]) { - const chatGptLabel = modelMap[value]?.chatGptLabel; - const promptPrefix = modelMap[value]?.promptPrefix; - dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); - dispatch(setModel('chatgptCustom')); - dispatch(setCustomModel(value)); - setMenuOpen(false); - } else if (!modelMap[value]) { - dispatch(setCustomModel(null)); - } - - // Set new conversation - dispatch(setText('')); - dispatch(setMessages([])); - dispatch(setNewConvo()); - dispatch(setSubmission({})); - }; - - const onOpenChange = (open) => { - mutate(); - if (!open) { - setModelSave(false); - } - }; - - const handleSaveState = (value) => { - if (!modelSave) { - return; - } - - dispatch(setCustomModel(value)); - setModelSave(false); - }; - - const defaultColorProps = [ - 'text-gray-500', - 'hover:bg-gray-100', - 'hover:bg-opacity-20', - 'disabled:hover:bg-transparent', - 'dark:data-[state=open]:bg-gray-800', - 'dark:hover:bg-opacity-20', - 'dark:hover:bg-gray-900', - 'dark:hover:text-gray-400', - 'dark:disabled:hover:bg-transparent' - ]; - - const chatgptColorProps = [ - 'text-green-700', - 'data-[state=open]:bg-green-100', - 'dark:text-emerald-300', - 'hover:bg-green-100', - 'disabled:hover:bg-transparent', - 'dark:data-[state=open]:bg-green-900', - 'dark:hover:bg-opacity-50', - 'dark:hover:bg-green-900', - 'dark:hover:text-gray-100', - 'dark:disabled:hover:bg-transparent' - ]; - - const isBing = model === 'bingai' || model === 'sydney'; - const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps; - const icon = getIconOfModel({ size: 32, sender: chatGptLabel || model, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, button: true}); - - return ( - - - - - - event.preventDefault()}> - Select a Model - - - {filteredModels.length? - :No model available. - } - - - - - - ); -} diff --git a/client/src/components/Main/Landing.jsx b/client/src/components/ui/Landing.jsx similarity index 89% rename from client/src/components/Main/Landing.jsx rename to client/src/components/ui/Landing.jsx index d0df6d708f..9235973274 100644 --- a/client/src/components/Main/Landing.jsx +++ b/client/src/components/ui/Landing.jsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { setText } from '~/store/textSlice'; +import { useRecoilValue } from 'recoil'; import useDocumentTitle from '~/hooks/useDocumentTitle'; import Templates from '../Prompts/Templates'; import SunIcon from '../svg/SunIcon'; @@ -8,27 +7,31 @@ import LightningIcon from '../svg/LightningIcon'; import CautionIcon from '../svg/CautionIcon'; import ChatIcon from '../svg/ChatIcon'; -export default function Landing({ title }) { +import store from '~/store'; + +export default function Landing() { const [showingTemplates, setShowingTemplates] = useState(false); - const dispatch = useDispatch(); + const conversation = useRecoilValue(store.conversation); + const { title = 'New Chat' } = conversation || {}; + useDocumentTitle(title); - const clickHandler = (e) => { + const clickHandler = e => { e.preventDefault(); const { innerText } = e.target; const quote = innerText.split('"')[1].trim(); - dispatch(setText(quote)); + // dispatch(setText(quote)); }; - const showTemplates = (e) => { + const showTemplates = e => { e.preventDefault(); setShowingTemplates(!showingTemplates); }; return ( -
+
-

+

ChatGPT Clone

diff --git a/client/src/routes/Chat.jsx b/client/src/routes/Chat.jsx index e9a30b71a8..58aa0500d3 100644 --- a/client/src/routes/Chat.jsx +++ b/client/src/routes/Chat.jsx @@ -1,13 +1,13 @@ -import React, { useEffect } from "react"; -import { useNavigate, useParams } from "react-router-dom"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +import React, { useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; -import Landing from "../components/ui/Landing"; -import Messages from "../components/Messages"; -import TextChat from "../components/Input"; +import Landing from '../components/ui/Landing'; +import Messages from '../components/Messages'; +import TextChat from '../components/Input'; -import store from "~/store"; -import manualSWR from "~/utils/fetchers"; +import store from '~/store'; +import manualSWR from '~/utils/fetchers'; // import TextChat from './components/Main/TextChat'; // {/* */} @@ -20,28 +20,22 @@ export default function Chat() { const { conversationId } = useParams(); const navigate = useNavigate(); - const { trigger: messagesTrigger } = manualSWR( - `/api/messages/${conversation?.conversationId}`, - "get" - ); + const { trigger: messagesTrigger } = manualSWR(`/api/messages/${conversation?.conversationId}`, 'get'); - const { trigger: conversationTrigger } = manualSWR( - `/api/convos/${conversationId}`, - "get" - ); + const { trigger: conversationTrigger } = manualSWR(`/api/convos/${conversationId}`, 'get'); // when conversation changed or conversationId (in url) changed useEffect(() => { if (conversation === null) { // no current conversation, we need to do something - if (conversationId == "new") { + if (conversationId == 'new') { // create new newConversation(); } else { // fetch it from server conversationTrigger().then(setConversation); setMessages(null); - console.log("NEED TO FETCH DATA"); + console.log('NEED TO FETCH DATA'); } } else if (conversation?.conversationId !== conversationId) // conversationId (in url) should always follow conversation?.conversationId, unless conversation is null @@ -60,7 +54,7 @@ export default function Chat() { return ( <> - {conversationId == "new" ? : } + {conversationId == 'new' ? : } ); diff --git a/client/src/store/conversation.js b/client/src/store/conversation.js index 0a68a40cf9..07a16e5f18 100644 --- a/client/src/store/conversation.js +++ b/client/src/store/conversation.js @@ -1,5 +1,13 @@ import models from './models'; -import { atom, selector, useRecoilValue, useSetRecoilState, useResetRecoilState } from 'recoil'; +import { + atom, + selector, + useRecoilValue, + useSetRecoilState, + useResetRecoilState, + useRecoilCallback, + useRecoilState +} from 'recoil'; import buildTree from '~/utils/buildTree'; // current conversation, can be null (need to be fetched from server) @@ -44,19 +52,45 @@ const latestMessage = atom({ }); const useConversation = () => { - const modelsFilter = useRecoilValue(models.modelsFilter); const setConversation = useSetRecoilState(conversation); const setMessages = useSetRecoilState(messages); const resetLatestMessage = useResetRecoilState(latestMessage); - const newConversation = ({ model = null, chatGptLabel = null, promptPrefix = null } = {}) => { + const switchToConversation = useRecoilCallback( + ({ snapshot }) => + async (_conversation, messages = null) => { + const prevConversation = await snapshot.getPromise(conversation); + const prevModelsFilter = await snapshot.getPromise(models.modelsFilter); + _switchToConversation(_conversation, messages, { prevModelsFilter, prevConversation }); + }, + [] + ); + + const _switchToConversation = ( + conversation, + messages = null, + { prevModelsFilter = {}, prev_conversation = {} } + ) => { + let { model = null, chatGptLabel = null, promptPrefix = null } = conversation; const getDefaultModel = () => { + try { + // try to use current model + const { _model = null, _chatGptLabel = null, _promptPrefix = null } = prev_conversation || {}; + console.log(_model, _chatGptLabel, _promptPrefix); + if (prevModelsFilter[_model]) { + model = _model; + chatGptLabel = _chatGptLabel; + promptPrefix = _promptPrefix; + return; + } + } catch (error) {} + try { // try to read latest selected model from local storage const lastSelected = JSON.parse(localStorage.getItem('model')); const { model: _model, chatGptLabel: _chatGptLabel, promptPrefix: _promptPrefix } = lastSelected; - if (modelsFilter[_model]) { + if (prevModelsFilter[_model]) { model = _model; chatGptLabel = _chatGptLabel; promptPrefix = _promptPrefix; @@ -65,9 +99,9 @@ const useConversation = () => { } catch (error) {} // if anything happens, reset to default model - if (modelsFilter?.chatgpt) model = 'chatgpt'; - else if (modelsFilter?.bingai) model = 'bingai'; - else if (modelsFilter?.chatgptBrowser) model = 'chatgptBrowser'; + if (prevModelsFilter?.chatgpt) model = 'chatgpt'; + else if (prevModelsFilter?.bingai) model = 'bingai'; + else if (prevModelsFilter?.chatgptBrowser) model = 'chatgptBrowser'; chatGptLabel = null; promptPrefix = null; }; @@ -77,24 +111,36 @@ const useConversation = () => { getDefaultModel(); setConversation({ - conversationId: 'new', - title: 'New Chat', - jailbreakConversationId: null, - conversationSignature: null, - clientId: null, - invocationId: null, + ...conversation, model: model, chatGptLabel: chatGptLabel, - promptPrefix: promptPrefix, - user: null, - suggestions: [], - toneStyle: null + promptPrefix: promptPrefix }); - setMessages([]); + setMessages(messages); resetLatestMessage(); }; - return { newConversation }; + const newConversation = ({ model = null, chatGptLabel = null, promptPrefix = null } = {}) => { + switchToConversation( + { + conversationId: 'new', + title: 'New Chat', + jailbreakConversationId: null, + conversationSignature: null, + clientId: null, + invocationId: null, + model: model, + chatGptLabel: chatGptLabel, + promptPrefix: promptPrefix, + user: null, + suggestions: [], + toneStyle: null + }, + [] + ); + }; + + return { newConversation, switchToConversation }; }; export default { diff --git a/client/src/utils/buildTree.js b/client/src/utils/buildTree.js index 7f9a4134e5..2929e1e7ce 100644 --- a/client/src/utils/buildTree.js +++ b/client/src/utils/buildTree.js @@ -4,6 +4,8 @@ const odd = 'w-full border-b border-black/10 bg-gray-50 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-[#444654] hover:bg-gray-100/40 hover:text-gray-700 dark:hover:bg-[#3b3d49] dark:hover:text-gray-200'; export default function buildTree(messages, groupAll = false) { + if (messages === null) return null; + let messageMap = {}; let rootMessages = []; diff --git a/client/src/utils/createPayload.js b/client/src/utils/createPayload.js index a1b0ff67bb..fb662b3d48 100644 --- a/client/src/utils/createPayload.js +++ b/client/src/utils/createPayload.js @@ -1,31 +1,50 @@ -export default function createPayload({ convo, message }) { - const endpoint = `/api/ask`; - let payload = { ...message }; - const { model } = message; +export default function createPayload(submission) { + const { conversation, messages, message, initialResponse, isRegenerate = false } = submission; - if (!payload.conversationId) - if (convo?.conversationId && convo?.parentMessageId) { - payload = { - ...payload, - conversationId: convo.conversationId, - parentMessageId: convo.parentMessageId || '00000000-0000-0000-0000-000000000000' - }; + const endpoint = `/api/ask`; + const { + model, + chatGptLabel, + promptPrefix, + jailbreakConversationId, + conversationId, + conversationSignature, + clientId, + invocationId, + toneStyle + } = conversation; + + let payload = { + ...message, + ...{ + model, + chatGptLabel, + promptPrefix, + conversationId } + }; + + // if (!payload.conversationId) + // if (convo?.conversationId && convo?.parentMessageId) { + // payload = { + // ...payload, + // conversationId: convo.conversationId, + // parentMessageId: convo.parentMessageId || '00000000-0000-0000-0000-000000000000' + // }; + // } const isBing = model === 'bingai' || model === 'sydney'; - if (isBing && !convo?.conversationId) { - payload.toneStyle = convo.toneStyle || 'fast'; + if (isBing && !conversationId) { + payload.toneStyle = toneStyle || 'fast'; } - - if (isBing && convo?.conversationId) { + + if (isBing && conversationId) { payload = { ...payload, - jailbreakConversationId: convo.jailbreakConversationId, - conversationId: convo.conversationId, - conversationSignature: convo.conversationSignature, - clientId: convo.clientId, - invocationId: convo.invocationId, - toneStyle: convo.toneStyle, + jailbreakConversationId, + conversationSignature, + clientId, + invocationId }; }