From 7d43032a98b20c84215b56c8da359ba9a5661389 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Tue, 28 Mar 2023 00:15:29 +0800 Subject: [PATCH 01/30] feat: return home page on any path fix: clearConvo will remove all messages --- api/server/index.js | 15 ++++++++------- api/server/routes/convos.js | 23 ----------------------- client/public/index.html | 4 ++-- 3 files changed, 10 insertions(+), 32 deletions(-) diff --git a/api/server/index.js b/api/server/index.js index d929bd5d8a..17a3e9f809 100644 --- a/api/server/index.js +++ b/api/server/index.js @@ -67,26 +67,27 @@ const projectPath = path.join(__dirname, '..', '..', 'client'); res.send(JSON.stringify({ hasOpenAI, hasChatGpt, hasBing })); }); + app.get('/*', routes.authenticatedOrRedirect, function (req, res) { + res.sendFile(path.join(projectPath, 'public', 'index.html')); + }); + app.listen(port, host, () => { if (host == '0.0.0.0') console.log( `Server listening on all interface at port ${port}. Use http://localhost:${port} to access it` ); - else - console.log( - `Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}` - ); + else console.log(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`); }); })(); let messageCount = 0; -process.on('uncaughtException', (err) => { +process.on('uncaughtException', err => { if (!err.message.includes('fetch failed')) { console.error('There was an uncaught error:', err.message); } - + if (err.message.includes('fetch failed')) { - if (messageCount === 0) { + if (messageCount === 0) { console.error('Meilisearch error, search will be disabled'); messageCount++; } diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index 7b2f9c72f9..45b23d0bd6 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -16,29 +16,6 @@ router.get('/:conversationId', async (req, res) => { res.status(200).send(convo.toObject()); }); -router.post('/gen_title', async (req, res) => { - const { conversationId } = req.body.arg; - - const convo = await getConvo(req?.session?.user?.username, conversationId); - const firstMessage = (await getMessages({ conversationId }))[0]; - const secondMessage = (await getMessages({ conversationId }))[1]; - - const title = convo.jailbreakConversationId - ? await getConvoTitle(req?.session?.user?.username, conversationId) - : await titleConvo({ - model: convo?.model, - message: firstMessage?.text, - response: JSON.stringify(secondMessage?.text || '') - }); - - await saveConvo(req?.session?.user?.username, { - conversationId, - title - }); - - res.status(200).send(title); -}); - router.post('/clear', async (req, res) => { let filter = {}; const { conversationId } = req.body.arg; diff --git a/client/public/index.html b/client/public/index.html index 8e8c205f6c..cc2670e745 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -26,7 +26,7 @@ /> @@ -34,7 +34,7 @@ From d8ccc5b870d9b19f2044cf8428acfcd122ec24ff Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Tue, 28 Mar 2023 01:19:44 +0800 Subject: [PATCH 02/30] fix: clearConvo will remove all messages --- api/models/Conversation.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/api/models/Conversation.js b/api/models/Conversation.js index b5716add01..512010c6ba 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -45,13 +45,9 @@ module.exports = { }, updateConvo: async (user, { conversationId, ...update }) => { try { - return await Conversation.findOneAndUpdate( - { conversationId: conversationId, user }, - update, - { - new: true - } - ).exec(); + return await Conversation.findOneAndUpdate({ conversationId: conversationId, user }, update, { + new: true + }).exec(); } catch (error) { console.log(error); return { message: 'Error updating conversation' }; @@ -89,7 +85,7 @@ module.exports = { promises.push( Conversation.findOne({ user, - conversationId: convo.conversationId, + conversationId: convo.conversationId }).exec() ) ); @@ -143,13 +139,13 @@ module.exports = { } } catch (error) { console.log(error); - return 'Error getting conversation title'; + return { message: 'Error getting conversation title' }; } }, deleteConvos: async (user, filter) => { let deleteCount = await Conversation.deleteMany({ ...filter, user }).exec(); console.log('deleteCount', deleteCount); - deleteCount.messages = await deleteMessages(filter); + deleteCount.messages = await deleteMessages({ ...filter, user }); return deleteCount; } }; From af3d74b104daf4cf33f0842dfa881a1fa4b38feb Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Tue, 28 Mar 2023 20:36:21 +0800 Subject: [PATCH 03/30] refactor: nav and search. feat: use recoil to replace redux feat: use react-native THIS IS NOT FINISHED. DONT USE THIS --- client/index.js | 14 +- client/package.json | 2 + client/src/App.jsx | 75 +++-- .../components/Conversations/Conversation.jsx | 206 +++++++----- .../components/Conversations/DeleteButton.jsx | 29 +- client/src/components/Conversations/Pages.jsx | 4 +- client/src/components/Conversations/index.jsx | 23 +- .../src/components/MessageHandler/index.jsx | 305 ++++++++++++++++++ client/src/components/Nav/ClearConvos.jsx | 33 +- client/src/components/Nav/Logout.jsx | 12 +- client/src/components/Nav/MobileNav.jsx | 30 +- client/src/components/Nav/NavLinks.jsx | 12 +- client/src/components/Nav/NewChat.jsx | 20 +- client/src/components/Nav/SearchBar.jsx | 33 +- client/src/components/Nav/index.jsx | 161 +++++---- client/src/routes/Chat.jsx | 67 ++++ client/src/routes/Root.jsx | 29 ++ client/src/store/conversation.js | 106 ++++++ client/src/store/conversations.js | 27 ++ client/src/store/index.js | 35 +- client/src/store/models.js | 80 +++++ client/src/store/search.js | 25 ++ client/src/store/submission.js | 37 +++ client/src/store/user.js | 17 + client/src/{store => store2}/convoSlice.js | 0 client/src/store2/index.js | 22 ++ client/src/{store => store2}/messageSlice.js | 0 client/src/{store => store2}/modelSlice.js | 0 client/src/{store => store2}/searchSlice.js | 0 client/src/{store => store2}/submitSlice.js | 0 client/src/{store => store2}/textSlice.js | 0 client/src/{store => store2}/userReducer.js | 0 client/src/utils/handleSubmit.js | 211 +++++------- 33 files changed, 1142 insertions(+), 473 deletions(-) create mode 100644 client/src/components/MessageHandler/index.jsx create mode 100644 client/src/routes/Chat.jsx create mode 100644 client/src/routes/Root.jsx create mode 100644 client/src/store/conversation.js create mode 100644 client/src/store/conversations.js create mode 100644 client/src/store/models.js create mode 100644 client/src/store/search.js create mode 100644 client/src/store/submission.js create mode 100644 client/src/store/user.js rename client/src/{store => store2}/convoSlice.js (100%) create mode 100644 client/src/store2/index.js rename client/src/{store => store2}/messageSlice.js (100%) rename client/src/{store => store2}/modelSlice.js (100%) rename client/src/{store => store2}/searchSlice.js (100%) rename client/src/{store => store2}/submitSlice.js (100%) rename client/src/{store => store2}/textSlice.js (100%) rename client/src/{store => store2}/userReducer.js (100%) diff --git a/client/index.js b/client/index.js index 6e1b2c1620..f4cf5cb234 100644 --- a/client/index.js +++ b/client/index.js @@ -1,19 +1,21 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; -import { Provider } from 'react-redux'; -import { store } from './src/store'; +// import { Provider } from 'react-redux'; +// import { store } from './src/store'; +import { RecoilRoot } from 'recoil'; + import { ThemeProvider } from './src/hooks/ThemeContext'; import App from './src/App'; import './src/style.css'; -import './src/mobile.css' +import './src/mobile.css'; const container = document.getElementById('root'); const root = createRoot(container); root.render( - + - -); \ No newline at end of file + +); diff --git a/client/package.json b/client/package.json index 1357b72066..6bd5b4fdd6 100644 --- a/client/package.json +++ b/client/package.json @@ -36,9 +36,11 @@ "react-lazy-load": "^4.0.1", "react-markdown": "^8.0.5", "react-redux": "^8.0.5", + "react-router-dom": "^6.9.0", "react-string-replace": "^1.1.0", "react-textarea-autosize": "^8.4.0", "react-transition-group": "^4.4.5", + "recoil": "^0.7.7", "rehype-highlight": "^6.0.0", "rehype-katex": "^6.0.2", "rehype-raw": "^6.1.1", diff --git a/client/src/App.jsx b/client/src/App.jsx index 6ee3be2a6b..07b8df49b8 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,53 +1,52 @@ import React, { useEffect, useState } from 'react'; -import Messages from './components/Messages'; -import Landing from './components/Main/Landing'; -import TextChat from './components/Main/TextChat'; -import Nav from './components/Nav'; -import MobileNav from './components/Nav/MobileNav'; -import useDocumentTitle from '~/hooks/useDocumentTitle'; -import { useSelector, useDispatch } from 'react-redux'; +import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom'; +import Root from './routes/Root'; +// import Chat from './routes/Chat'; +import store from './store'; import userAuth from './utils/userAuth'; -import { setUser } from './store/userReducer'; -import { setSearchState } from './store/searchSlice'; +import { useRecoilState, useSetRecoilState } from 'recoil'; + import axios from 'axios'; -const App = () => { - const dispatch = useDispatch(); +const router = createBrowserRouter([ + { + path: '/', + element: , + children: [ + { + index: true, + element: ( + + ) + }, + { + path: 'chat/:conversationId', + element: null // + } + ] + } +]); - const { messages, messageTree } = useSelector((state) => state.messages); - const { user } = useSelector((state) => state.user); - const { title } = useSelector((state) => state.convo); - const [navVisible, setNavVisible] = useState(false); - useDocumentTitle(title); +const App = () => { + const [user, setUser] = useRecoilState(store.user); + const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled); useEffect(() => { - axios.get('/api/search/enable').then((res) => { console.log(res.data); dispatch(setSearchState(res.data))}); + axios.get('/api/search/enable').then(res => { + setIsSearchEnabled(res.data); + }); userAuth() - .then((user) => dispatch(setUser(user))) - .catch((err) => console.log(err)); + .then(user => setUser(user)) + .catch(err => console.log(err)); }, []); if (user) return ( -
-
- {conversationId === id ? ( + {currentConversation?.conversationId === conversationId ? (
{ - dispatch(setMessages([])); - dispatch(removeConvo(conversationId)); - dispatch(setNewConvo()); - dispatch(setSubmission({})); - retainView(); - } - ); + const currentConversation = useRecoilValue(store.conversation) || {}; + const { newConversation } = store.useConversation(); + const { refreshConversations } = store.useConversations(); + const { trigger } = manualSWR(`/api/convos/clear`, 'post', () => { + if (currentConversation?.conversationId == conversationId) newConversation(); + refreshConversations(); + retainView(); + }); const clickHandler = () => trigger({ conversationId }); const handler = renaming ? cancelHandler : clickHandler; @@ -29,7 +24,7 @@ export default function DeleteButton({ conversationId, renaming, cancelHandler, className="p-1 hover:text-white" onClick={handler} > - { renaming ? : } + {renaming ? : } ); } diff --git a/client/src/components/Conversations/Pages.jsx b/client/src/components/Conversations/Pages.jsx index 1001ee8362..91b93388b4 100644 --- a/client/src/components/Conversations/Pages.jsx +++ b/client/src/components/Conversations/Pages.jsx @@ -1,12 +1,12 @@ import React from 'react'; export default function Pages({ pageNumber, pages, nextPage, previousPage }) { - const clickHandler = (func) => async (e) => { + const clickHandler = func => async e => { e.preventDefault(); await func(); }; - return ( + return pageNumber == 1 && pages == 1 ? null : (
+ + 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 }; } From 8ea98cca5dd51f15c881dece95d5308633388733 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Tue, 28 Mar 2023 23:00:29 +0800 Subject: [PATCH 06/30] refactor: bing button THIS IS NOT FINISHED. DONT USE THIS --- .../src/components/Input/AdjustToneButton.jsx | 10 +- client/src/components/Input/BingStyles.jsx | 28 +- client/src/components/Input/index.jsx | 264 +----------------- client/src/components/Messages/Message.jsx | 4 +- client/src/store/conversation.js | 4 +- 5 files changed, 32 insertions(+), 278 deletions(-) diff --git a/client/src/components/Input/AdjustToneButton.jsx b/client/src/components/Input/AdjustToneButton.jsx index e9153a0c72..a89e787451 100644 --- a/client/src/components/Input/AdjustToneButton.jsx +++ b/client/src/components/Input/AdjustToneButton.jsx @@ -1,13 +1,13 @@ import React from 'react'; export default function AdjustButton({ onClick }) { - const clickHandler = (e) => { + const clickHandler = e => { e.preventDefault(); onClick(); }; return ( diff --git a/client/src/components/Input/BingStyles.jsx b/client/src/components/Input/BingStyles.jsx index 440107ce11..369d7237b9 100644 --- a/client/src/components/Input/BingStyles.jsx +++ b/client/src/components/Input/BingStyles.jsx @@ -1,36 +1,38 @@ import React, { useState, useEffect, forwardRef } from 'react'; import { Tabs, TabsList, TabsTrigger } from '../ui/Tabs.tsx'; -import { useDispatch, useSelector } from 'react-redux'; -import { setConversation } from '~/store/convoSlice'; +import { useRecoilValue, useRecoilState } from 'recoil'; +// import { setConversation } from '~/store/convoSlice'; + +import store from '~/store'; function BingStyles(props, ref) { - const dispatch = useDispatch(); const [value, setValue] = useState('fast'); - const { model } = useSelector((state) => state.submit); - const { conversationId } = useSelector((state) => state.convo); - const { messages } = useSelector((state) => state.messages); + + const [conversation, setConversation] = useRecoilState(store.conversation) || {}; + const { model, conversationId } = conversation; + const messages = useRecoilValue(store.messages); const isBing = model === 'bingai' || model === 'sydney'; useEffect(() => { - if (model === 'bingai' && !conversationId || model === 'sydney') { - dispatch(setConversation({ toneStyle: value })); + if ((model === 'bingai' && !conversationId) || model === 'sydney') { + setConversation(prevState => ({ ...prevState, toneStyle: value })); } - }, [conversationId, model, value, dispatch]); + }, [conversationId, model, value]); - const show = isBing && ((!conversationId || messages?.length === 0) || props.show); + const show = isBing && (!conversationId || messages?.length === 0 || props.show); const defaultClasses = 'p-2 rounded-md font-normal bg-white/[.60] dark:bg-gray-700 text-black'; const defaultSelected = defaultClasses + 'font-medium data-[state=active]:text-white'; - const selectedClass = (val) => val + '-tab ' + defaultSelected; + const selectedClass = val => val + '-tab ' + defaultSelected; const changeHandler = value => { setValue(value); - dispatch(setConversation({ toneStyle: value })); + setConversation(prevState => ({ ...prevState, toneStyle: value })); }; return ( diff --git a/client/src/components/Input/index.jsx b/client/src/components/Input/index.jsx index 6332aa7654..359767aad0 100644 --- a/client/src/components/Input/index.jsx +++ b/client/src/components/Input/index.jsx @@ -1,19 +1,14 @@ 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 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'; @@ -62,258 +57,11 @@ export default function TextChat() { 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); }; @@ -389,10 +137,10 @@ export default function TextChat() {
- {/* */} + /> {isSubmitting ? (
diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index e0389179f6..7d61a110dd 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -83,7 +83,7 @@ export default function Message({ if (message.bg && searchResult) { props.className = message.bg.split('hover')[0]; - props.titleClass = message.bg.split(props.className)[1] + ' cursor-pointer'; + props.titleclass = message.bg.split(props.className)[1] + ' cursor-pointer'; } const resubmitMessage = () => { @@ -144,7 +144,7 @@ export default function Message({
{searchResult && ( diff --git a/client/src/store/conversation.js b/client/src/store/conversation.js index 07a16e5f18..9e1c0f080e 100644 --- a/client/src/store/conversation.js +++ b/client/src/store/conversation.js @@ -42,7 +42,9 @@ const messages = atom({ const messagesTree = selector({ key: 'messagesTree', get: ({ get }) => { - return buildTree(get(messages)); + const _messages = get(messages); + const groupAll = _messages?.[0]?.searchResult; + return buildTree(_messages, groupAll); } }); From 370dc2dd8a2edb2a2ff27718c13267b1bee44574 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 00:08:02 +0800 Subject: [PATCH 07/30] feat: support search-style-url fix: url can be null in conversationId and query fix: get conversation api should handle not found. --- api/server/routes/convos.js | 4 +- client/src/App.jsx | 7 +++- client/src/components/Input/index.jsx | 5 +-- client/src/components/Messages/index.jsx | 29 +++++++++----- client/src/components/Nav/SearchBar.jsx | 16 +++++--- client/src/components/Nav/index.jsx | 10 ++--- client/src/routes/Chat.jsx | 22 +++++++---- client/src/routes/Search.jsx | 50 ++++++++++++++++++++++++ client/src/store/conversation.js | 37 +++++++++++------- client/src/store/search.js | 15 +++++++ 10 files changed, 147 insertions(+), 48 deletions(-) create mode 100644 client/src/routes/Search.jsx diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index 45b23d0bd6..0774b92ddf 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -13,7 +13,9 @@ router.get('/', async (req, res) => { router.get('/:conversationId', async (req, res) => { const { conversationId } = req.params; const convo = await getConvo(req?.session?.user?.username, conversationId); - res.status(200).send(convo.toObject()); + + if (convo) res.status(200).send(convo.toObject()); + else res.status(404).end(); }); router.post('/clear', async (req, res) => { diff --git a/client/src/App.jsx b/client/src/App.jsx index 404dce05a8..d8673b69ed 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -2,6 +2,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 Search from './routes/Search'; import store from './store'; import userAuth from './utils/userAuth'; import { useRecoilState, useSetRecoilState } from 'recoil'; @@ -23,8 +24,12 @@ const router = createBrowserRouter([ ) }, { - path: 'chat/:conversationId', + path: 'chat/:conversationId?', element: + }, + { + path: 'search/:query?', + element: } ] } diff --git a/client/src/components/Input/index.jsx b/client/src/components/Input/index.jsx index 359767aad0..9eac938c4c 100644 --- a/client/src/components/Input/index.jsx +++ b/client/src/components/Input/index.jsx @@ -13,7 +13,7 @@ import { useMessageHandler } from '../../utils/handleSubmit'; import store from '~/store'; -export default function TextChat() { +export default function TextChat({ isSearchView = false }) { const inputRef = useRef(null); const isComposing = useRef(false); @@ -36,7 +36,7 @@ export default function TextChat() { // auto focus to input, when enter a conversation. useEffect(() => { - inputRef.current?.focus(); + if (conversation?.conversationId !== 'search') inputRef.current?.focus(); setText(''); }, [conversation?.conversationId]); @@ -108,7 +108,6 @@ export default function TextChat() { setText(value); }; - const isSearchView = messages?.[0]?.searchResult === true; const getPlaceholderText = () => { if (isSearchView) { return 'Click a message title to open its conversation.'; diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index 262575116d..45c56f1616 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useState, useRef, useCallback } from 'react'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilValue } from 'recoil'; import Spinner from '../svg/Spinner'; import { throttle } from 'lodash'; import { CSSTransition } from 'react-transition-group'; @@ -8,18 +8,25 @@ import MultiMessage from './MultiMessage'; import store from '~/store'; -export default function Messages() { +export default function Messages({ isSearchView = false }) { const [currentEditId, setCurrentEditId] = useState(-1); - 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 messagesTree = useRecoilValue(store.messagesTree); + const searchResultMessagesTree = useRecoilValue(store.searchResultMessagesTree); + + const _messagesTree = isSearchView ? searchResultMessagesTree : messagesTree; + + const conversation = useRecoilValue(store.conversation) || {}; + const { conversationId, model, chatGptLabel } = conversation; + + const models = useRecoilValue(store.models) || []; const modelName = models.find(element => element.model == model)?.name; + const searchQuery = useRecoilValue(store.searchQuery); + useEffect(() => { const timeoutId = setTimeout(() => { const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current; @@ -36,7 +43,7 @@ export default function Messages() { clearTimeout(timeoutId); window.removeEventListener('scroll', handleScroll); }; - }, [messagesTree]); + }, [_messagesTree]); const scrollToBottom = useCallback( throttle( @@ -81,16 +88,18 @@ export default function Messages() {
- Model: {modelName} {chatGptLabel ? `(${chatGptLabel})` : null} + {isSearchView + ? `Search: ${searchQuery}` + : `Model: ${modelName} ${chatGptLabel ? `(${chatGptLabel})` : ''}`}
- {messagesTree === null ? ( + {_messagesTree === null ? ( ) : ( <> { setSearchQuery(q); - if (q.length > 0) { - fetch(q, 1); - } }, 750), [setSearchQuery] ); + useEffect(() => { + if (searchQuery.length > 0) { + fetch(searchQuery, 1); + setInputValue(searchQuery); + } + }, [searchQuery]); + const handleKeyUp = e => { const { value } = e.target; if (e.keyCode === 8 && value === '') { diff --git a/client/src/components/Nav/index.jsx b/client/src/components/Nav/index.jsx index 9d1c69d342..85523033ea 100644 --- a/client/src/components/Nav/index.jsx +++ b/client/src/components/Nav/index.jsx @@ -27,12 +27,12 @@ export default function Nav({ navVisible, setNavVisible }) { const searchQuery = useRecoilValue(store.searchQuery); const isSearchEnabled = useRecoilValue(store.isSearchEnabled); const isSearching = useRecoilValue(store.isSearching); - const { newConversation } = store.useConversation(); + const { newConversation, searchPlaceholderConversation } = store.useConversation(); // current conversation const conversation = useRecoilValue(store.conversation); const { conversationId } = conversation || {}; - const setMessages = useSetRecoilState(store.messages); + const setSearchResultMessages = useSetRecoilState(store.searchResultMessages); // refreshConversationsHint is used for other components to ask refresh of Nav const refreshConversationsHint = useRecoilValue(store.refreshConversationsHint); @@ -66,10 +66,8 @@ export default function Nav({ navVisible, setNavVisible }) { setPageNumber(res.pageNumber); setPages(res.pages); setIsFetching(false); - if (res.messages?.length > 0) { - setMessages(res.messages); - // dispatch(setDisabled(true)); - } + searchPlaceholderConversation(); + setSearchResultMessages(res.messages); }; // TODO: dont need this diff --git a/client/src/routes/Chat.jsx b/client/src/routes/Chat.jsx index 58aa0500d3..1468dfa654 100644 --- a/client/src/routes/Chat.jsx +++ b/client/src/routes/Chat.jsx @@ -8,11 +8,9 @@ import TextChat from '../components/Input'; import store from '~/store'; import manualSWR from '~/utils/fetchers'; -// import TextChat from './components/Main/TextChat'; - -// {/* */} export default function Chat() { + const searchQuery = useRecoilValue(store.searchQuery); const [conversation, setConversation] = useRecoilState(store.conversation); const setMessages = useSetRecoilState(store.messages); const messagesTree = useRecoilValue(store.messagesTree); @@ -28,18 +26,23 @@ export default function Chat() { useEffect(() => { if (conversation === null) { // no current conversation, we need to do something - if (conversationId == 'new') { + if (conversationId === 'new') { // create new newConversation(); - } else { + } else if (conversationId) { // fetch it from server conversationTrigger().then(setConversation); setMessages(null); - console.log('NEED TO FETCH DATA'); + } else { + navigate(`/chat/new`); } - } else if (conversation?.conversationId !== conversationId) + } else if (conversation?.conversationId === 'search') { + // jump to search page + navigate(`/search/${searchQuery}`); + } else if (conversation?.conversationId !== conversationId) { // conversationId (in url) should always follow conversation?.conversationId, unless conversation is null navigate(`/chat/${conversation?.conversationId}`); + } }, [conversation, conversationId]); // when messagesTree is null (<=> messages is null) @@ -50,7 +53,12 @@ export default function Chat() { } }, [conversation?.conversationId]); + // if not a conversation + if (conversation?.conversationId === 'search') return null; + // if conversationId not match if (conversation?.conversationId !== conversationId) return null; + // if conversationId is null + if (!conversationId) return null; return ( <> diff --git a/client/src/routes/Search.jsx b/client/src/routes/Search.jsx new file mode 100644 index 0000000000..f91e7e224b --- /dev/null +++ b/client/src/routes/Search.jsx @@ -0,0 +1,50 @@ +import React, { useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useRecoilState, useRecoilValue } from 'recoil'; + +import Messages from '../components/Messages'; +import TextChat from '../components/Input'; + +import store from '~/store'; + +export default function Search() { + const [searchQuery, setSearchQuery] = useRecoilState(store.searchQuery); + const conversation = useRecoilValue(store.conversation); + const { searchPlaceholderConversation } = store.useConversation(); + const { query } = useParams(); + const navigate = useNavigate(); + + // when conversation changed or conversationId (in url) changed + useEffect(() => { + if (conversation === null) { + // no current conversation, we need to do something + if (query) { + // create new + searchPlaceholderConversation(); + setSearchQuery(query); + } else { + navigate(`/chat/new`); + } + } else if (conversation?.conversationId === 'search') { + // jump to search page + if (searchQuery !== query) navigate(`/search/${searchQuery}`); + } else { + // conversationId (in url) should always follow conversation?.conversationId, unless conversation is null + navigate(`/chat/${conversation?.conversationId}`); + } + }, [conversation, query, searchQuery]); + + // if not a search + if (conversation?.conversationId !== 'search') return null; + // if query not match + if (searchQuery !== query) return null; + // if query is null + if (!query) return null; + + return ( + <> + + + + ); +} diff --git a/client/src/store/conversation.js b/client/src/store/conversation.js index 9e1c0f080e..7ae123c254 100644 --- a/client/src/store/conversation.js +++ b/client/src/store/conversation.js @@ -1,13 +1,5 @@ import models from './models'; -import { - atom, - selector, - useRecoilValue, - useSetRecoilState, - useResetRecoilState, - useRecoilCallback, - useRecoilState -} from 'recoil'; +import { atom, selector, useSetRecoilState, useResetRecoilState, useRecoilCallback } from 'recoil'; import buildTree from '~/utils/buildTree'; // current conversation, can be null (need to be fetched from server) @@ -42,9 +34,7 @@ const messages = atom({ const messagesTree = selector({ key: 'messagesTree', get: ({ get }) => { - const _messages = get(messages); - const groupAll = _messages?.[0]?.searchResult; - return buildTree(_messages, groupAll); + return buildTree(get(messages), false); } }); @@ -78,7 +68,6 @@ const useConversation = () => { 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; @@ -142,7 +131,27 @@ const useConversation = () => { ); }; - return { newConversation, switchToConversation }; + const searchPlaceholderConversation = ({ model = null, chatGptLabel = null, promptPrefix = null } = {}) => { + switchToConversation( + { + conversationId: 'search', + title: 'Search', + jailbreakConversationId: null, + conversationSignature: null, + clientId: null, + invocationId: null, + model: model, + chatGptLabel: chatGptLabel, + promptPrefix: promptPrefix, + user: null, + suggestions: [], + toneStyle: null + }, + [] + ); + }; + + return { newConversation, switchToConversation, searchPlaceholderConversation }; }; export default { diff --git a/client/src/store/search.js b/client/src/store/search.js index 554a012b72..ebc956def5 100644 --- a/client/src/store/search.js +++ b/client/src/store/search.js @@ -1,4 +1,5 @@ import { atom, selector } from 'recoil'; +import buildTree from '~/utils/buildTree'; const isSearchEnabled = atom({ key: 'isSearchEnabled', @@ -10,6 +11,18 @@ const searchQuery = atom({ default: '' }); +const searchResultMessages = atom({ + key: 'searchResultMessages', + default: null +}); + +const searchResultMessagesTree = selector({ + key: 'searchResultMessagesTree', + get: ({ get }) => { + return buildTree(get(searchResultMessages), true); + } +}); + const isSearching = selector({ key: 'isSearching', get: ({ get }) => { @@ -21,5 +34,7 @@ const isSearching = selector({ export default { isSearchEnabled, isSearching, + searchResultMessages, + searchResultMessagesTree, searchQuery }; From d0d0a3d23ee5cc7700807edb7dfba014eb1631a7 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 00:18:27 +0800 Subject: [PATCH 08/30] feat: print nothing found when no search result. fix: handle 404 of conversation fetch failed --- client/src/components/Messages/Message.jsx | 13 ------------- client/src/components/Messages/index.jsx | 4 ++++ client/src/routes/Chat.jsx | 8 +++++++- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index 7d61a110dd..c95a163398 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -101,23 +101,10 @@ export default function Message({ const clickSearchResult = async () => { if (!searchResult) return; - // 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)); - // } - // dispatch(setCustomGpt(convo)); switchToConversation(convo); - // dispatch(setConversation(convo)); - // const { data } = await fetchById('messages', message.conversationId); - // dispatch(setMessages(data)); - // dispatch(setDisabled(false)); }; return ( diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index 45c56f1616..75dde9a249 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -94,6 +94,10 @@ export default function Messages({ isSearchView = false }) {
{_messagesTree === null ? ( + ) : _messagesTree?.length == 0 && isSearchView ? ( +
+ Nothing found +
) : ( <> { + console.error('failed to fetch the conversation'); + console.error(error); + newConversation(); + }); setMessages(null); } else { navigate(`/chat/new`); From 894aad9f0bf33301921caf9c29e118b0c6072182 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 00:20:57 +0800 Subject: [PATCH 09/30] sync with main --- client/src/components/Input/Models/ModelDialog.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Input/Models/ModelDialog.jsx b/client/src/components/Input/Models/ModelDialog.jsx index f91957d771..86d6b30abe 100644 --- a/client/src/components/Input/Models/ModelDialog.jsx +++ b/client/src/components/Input/Models/ModelDialog.jsx @@ -131,7 +131,7 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { value={promptPrefix} 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" + className="col-span-3 flex h-20 max-h-52 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" />
From f595cb2aa1e91957c2b1034996dde715f11fe183 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 01:09:03 +0800 Subject: [PATCH 10/30] fix: three dot looks wide on android chrome. --- client/src/components/Input/SubmitButton.jsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/client/src/components/Input/SubmitButton.jsx b/client/src/components/Input/SubmitButton.jsx index 5d76250d6b..a2d65574ca 100644 --- a/client/src/components/Input/SubmitButton.jsx +++ b/client/src/components/Input/SubmitButton.jsx @@ -9,13 +9,23 @@ export default function SubmitButton({ submitMessage, disabled, isSubmitting }) if (isSubmitting) { return ( ); From 319e4f0f9543dda5712658e32ff426063e209cd8 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 01:26:58 +0800 Subject: [PATCH 11/30] fix: set to default model in searchPlaceholderConversation fix: set max auth cookie to 7 days --- api/server/index.js | 3 ++- client/src/store/conversation.js | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/api/server/index.js b/api/server/index.js index 17a3e9f809..9f68518c2a 100644 --- a/api/server/index.js +++ b/api/server/index.js @@ -29,7 +29,8 @@ const projectPath = path.join(__dirname, '..', '..', 'client'); session({ secret: 'chatgpt-clone-random-secrect', resave: false, - saveUninitialized: true + saveUninitialized: true, + cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 } // 7 days }) ); diff --git a/client/src/store/conversation.js b/client/src/store/conversation.js index 7ae123c254..b59aef4af5 100644 --- a/client/src/store/conversation.js +++ b/client/src/store/conversation.js @@ -131,7 +131,7 @@ const useConversation = () => { ); }; - const searchPlaceholderConversation = ({ model = null, chatGptLabel = null, promptPrefix = null } = {}) => { + const searchPlaceholderConversation = () => { switchToConversation( { conversationId: 'search', @@ -140,9 +140,9 @@ const useConversation = () => { conversationSignature: null, clientId: null, invocationId: null, - model: model, - chatGptLabel: chatGptLabel, - promptPrefix: promptPrefix, + model: null, + chatGptLabel: null, + promptPrefix: null, user: null, suggestions: [], toneStyle: null From dc743df2557e35dc8165a6f990eb120b043f565a Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 01:50:57 +0800 Subject: [PATCH 12/30] feat: update title generator prompt, to support better on language. --- api/app/titleConvo.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/api/app/titleConvo.js b/api/app/titleConvo.js index 9575377b44..249e75c8b4 100644 --- a/api/app/titleConvo.js +++ b/api/app/titleConvo.js @@ -25,13 +25,11 @@ const titleConvo = async ({ model, text, response }) => { { role: 'system', content: - 'You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user in title case, using the same language.' + "You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user, using the same language. The requirement are: 1. If possible, generate in 5 words or less, 2. Using title case, 3. must give the title using the language as the user said. 4. Don't refer to the participants of the conversation. 5. Do not include punctuation or quotation marks. 6. Your response should be in title case, exclusively containing the title. 7. don't say anything except the title." }, { role: 'user', - content: `In 5 words or less, summarize the conversation below with a title in title case using the language the user writes in. Don't refer to the participants of the conversation nor the language. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title. Conversation:\n\nUser: "${text}"\n\n${model}: "${JSON.stringify( - response?.text - )}"\n\nTitle: ` + content: `User:\n "${text}"\n\n${model}: \n"${JSON.stringify(response?.text)}"\n\n` } ], temperature: 0, From aa26eea8c5d26fda05c98c19c3bcf05faa2ffc40 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 02:29:15 +0800 Subject: [PATCH 13/30] fix: missing icon of search result feat: use search result message as single list --- api/server/routes/search.js | 18 +++--- client/src/components/Messages/Message.jsx | 16 ++++- .../src/components/Messages/MultiMessage.jsx | 23 ++++++- client/src/components/Messages/index.jsx | 1 + client/src/utils/buildTree.js | 25 ++++---- client/src/utils/index.js | 63 ++++++++++++------- 6 files changed, 102 insertions(+), 44 deletions(-) diff --git a/api/server/routes/search.js b/api/server/routes/search.js index 076e4093c9..1cc3602833 100644 --- a/api/server/routes/search.js +++ b/api/server/routes/search.js @@ -42,7 +42,7 @@ router.get('/', async function (req, res) { }, true ) - ).hits.map((message) => { + ).hits.map(message => { const { _formatted, ...rest } = message; return { ...rest, @@ -64,7 +64,9 @@ router.get('/', async function (req, res) { message.conversationId = cleanUpPrimaryKeyValue(message.conversationId); } if (result.convoMap[message.conversationId] && !message.error) { - message = { ...message, title: result.convoMap[message.conversationId].title }; + const convo = result.convoMap[message.conversationId]; + const { title, chatGptLabel, model } = convo; + message = { ...message, ...{ title, chatGptLabel, model } }; activeMessages.push(message); } } @@ -91,12 +93,12 @@ router.get('/clear', async function (req, res) { router.get('/test', async function (req, res) { const { q } = req.query; - const messages = ( - await Message.meiliSearch(q, { attributesToHighlight: ['text'] }, true) - ).hits.map((message) => { - const { _formatted, ...rest } = message; - return { ...rest, searchResult: true, text: _formatted.text }; - }); + const messages = (await Message.meiliSearch(q, { attributesToHighlight: ['text'] }, true)).hits.map( + message => { + const { _formatted, ...rest } = message; + return { ...rest, searchResult: true, text: _formatted.text }; + } + ); res.send(messages); }); diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index c95a163398..fd4b7ef02d 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -25,7 +25,17 @@ export default function Message({ const setLatestMessage = useSetRecoilState(store.latestMessage); const { model, chatGptLabel, promptPrefix } = conversation; const [abortScroll, setAbort] = useState(false); - const { sender, text, searchResult, isCreatedByUser, error, submitting } = message; + const { + sender, + text, + searchResult, + isCreatedByUser, + error, + submitting, + model: messageModel, + chatGptLabel: messageChatGptLabel, + searchResult: isSearchResult + } = message; const textEditor = useRef(null); const last = !message?.children?.length; const edit = message.messageId == currentEditId; @@ -70,9 +80,9 @@ export default function Message({ const icon = getIconOfModel({ sender, isCreatedByUser, - model, + model: isSearchResult ? messageModel : model, searchResult, - chatGptLabel, + chatGptLabel: isSearchResult ? messageChatGptLabel : chatGptLabel, promptPrefix, error }); diff --git a/client/src/components/Messages/MultiMessage.jsx b/client/src/components/Messages/MultiMessage.jsx index 61e023ef1d..fa86c041ba 100644 --- a/client/src/components/Messages/MultiMessage.jsx +++ b/client/src/components/Messages/MultiMessage.jsx @@ -6,7 +6,8 @@ export default function MultiMessage({ messagesTree, scrollToBottom, currentEditId, - setCurrentEditId + setCurrentEditId, + isSearchView }) { const [siblingIdx, setSiblingIdx] = useState(0); @@ -30,6 +31,26 @@ export default function MultiMessage({ } const message = messagesTree[messagesTree.length - siblingIdx - 1]; + if (isSearchView) + return ( + <> + {messagesTree + ? messagesTree.map(message => ( + + )) + : null} + + ); return ( ({ ...m, bg: idx % 2 === 0 ? even : odd })); + } if (!groupAll) { // Traverse the messages array and store each element in messageMap. messages.forEach(message => { @@ -22,18 +25,18 @@ export default function buildTree(messages, groupAll = false) { return rootMessages; } - // Group all messages into one tree - let parentId = null; - messages.forEach((message, i) => { - messageMap[message.messageId] = { ...message, bg: i % 2 === 0 ? even : odd, children: [] }; - const currentMessage = messageMap[message.messageId]; - const parentMessage = messageMap[parentId]; - if (parentMessage) parentMessage.children.push(currentMessage); - else rootMessages.push(currentMessage); - parentId = message.messageId; - }); + // // Group all messages into one tree + // let parentId = null; + // messages.forEach((message, i) => { + // messageMap[message.messageId] = { ...message, bg: i % 2 === 0 ? even : odd, children: [] }; + // const currentMessage = messageMap[message.messageId]; + // const parentMessage = messageMap[parentId]; + // if (parentMessage) parentMessage.children.push(currentMessage); + // else rootMessages.push(currentMessage); + // parentId = message.messageId; + // }); - return rootMessages; + // return rootMessages; // Group all messages by conversation, doesn't look great // Traverse the messages array and store each element in messageMap. diff --git a/client/src/utils/index.js b/client/src/utils/index.js index 60637d2cda..4ff806ebaa 100644 --- a/client/src/utils/index.js +++ b/client/src/utils/index.js @@ -38,40 +38,55 @@ export const languages = [ 'pascal' ]; -export const getIconOfModel = ({ size=30, sender, isCreatedByUser, searchResult, model, chatGptLabel, error, ...props }) => { - // 'ai' is used as 'model' is not accurate for search results - let ai = searchResult ? sender : model; +export const getIconOfModel = ({ + size = 30, + sender, + isCreatedByUser, + searchResult, + model, + chatGptLabel, + error, + ...props +}) => { const { button } = props; const bgColors = { - chatgpt: `rgb(16, 163, 127${ button ? ', 0.75' : ''})`, - chatgptBrowser: `rgb(25, 207, 207${ button ? ', 0.75' : ''})`, + chatgpt: `rgb(16, 163, 127${button ? ', 0.75' : ''})`, + chatgptBrowser: `rgb(25, 207, 207${button ? ', 0.75' : ''})`, bingai: 'transparent', sydney: 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)', - chatgptCustom: `rgb(0, 163, 255${ button ? ', 0.75' : ''})`, + chatgptCustom: `rgb(0, 163, 255${button ? ', 0.75' : ''})` }; - if (isCreatedByUser) + if (isCreatedByUser) return (
User
- ) + ); else if (!isCreatedByUser) { // TODO: use model from convo, rather than submit // const { model, chatGptLabel, promptPrefix } = convo; - let background = bgColors[ai]; - const isBing = ai === 'bingai' || ai === 'sydney'; - + let background = bgColors[model]; + const isBing = model === 'bingai' || model === 'sydney'; + return (
{isBing ? : } @@ -85,11 +100,17 @@ export const getIconOfModel = ({ size=30, sender, isCreatedByUser, searchResult, } else return (
{chatGptLabel}
- ) -} + ); +}; From b7af3595cf71252ce410ec5f057130dfe23d5bdb Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 02:38:24 +0800 Subject: [PATCH 14/30] cleanup remove redux store --- client/src/store2/convoSlice.js | 113 ------------------------------ client/src/store2/index.js | 22 ------ client/src/store2/messageSlice.js | 35 --------- client/src/store2/modelSlice.js | 68 ------------------ client/src/store2/searchSlice.js | 35 --------- client/src/store2/submitSlice.js | 57 --------------- client/src/store2/textSlice.js | 19 ----- client/src/store2/userReducer.js | 19 ----- 8 files changed, 368 deletions(-) delete mode 100644 client/src/store2/convoSlice.js delete mode 100644 client/src/store2/index.js delete mode 100644 client/src/store2/messageSlice.js delete mode 100644 client/src/store2/modelSlice.js delete mode 100644 client/src/store2/searchSlice.js delete mode 100644 client/src/store2/submitSlice.js delete mode 100644 client/src/store2/textSlice.js delete mode 100644 client/src/store2/userReducer.js diff --git a/client/src/store2/convoSlice.js b/client/src/store2/convoSlice.js deleted file mode 100644 index d800535296..0000000000 --- a/client/src/store2/convoSlice.js +++ /dev/null @@ -1,113 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; - -const initialState = { - error: false, - title: 'ChatGPT Clone', - conversationId: null, - parentMessageId: null, - jailbreakConversationId: null, - conversationSignature: null, - clientId: null, - invocationId: null, - toneStyle: null, - chatGptLabel: null, - promptPrefix: null, - convosLoading: false, - pageNumber: 1, - pages: 1, - refreshConvoHint: 0, - search: false, - latestMessage: null, - convos: [], - convoMap: {}, -}; - -const currentSlice = createSlice({ - name: 'convo', - initialState, - reducers: { - refreshConversation: (state) => { - state.refreshConvoHint = state.refreshConvoHint + 1; - }, - setConversation: (state, action) => { - // return { ...state, ...action.payload }; - - for (const key in action.payload) { - if (Object.hasOwnProperty.call(action.payload, key)) { - state[key] = action.payload[key]; - } - } - }, - setError: (state, action) => { - state.error = action.payload; - }, - increasePage: (state) => { - state.pageNumber = state.pageNumber + 1; - }, - decreasePage: (state) => { - state.pageNumber = state.pageNumber - 1; - }, - setPage: (state, action) => { - state.pageNumber = action.payload; - }, - setNewConvo: (state) => { - state.error = false; - state.title = 'ChatGPT Clone'; - state.conversationId = null; - state.parentMessageId = null; - state.jailbreakConversationId = null; - state.conversationSignature = null; - state.clientId = null; - state.invocationId = null; - state.toneStyle = null; - state.chatGptLabel = null; - state.promptPrefix = null; - state.convosLoading = false; - state.latestMessage = null; - }, - setConvos: (state, action) => { - const { convos, searchFetch } = action.payload; - if (searchFetch) { - state.convos = convos; - } else { - state.convos = convos.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); - } - - // state.convoMap = convos.reduce((acc, curr) => { - // acc[curr.conversationId] = { ...curr }; - // delete acc[curr.conversationId].conversationId; - // return acc; - // }, {}); - - }, - setPages: (state, action) => { - state.pages = action.payload; - }, - removeConvo: (state, action) => { - state.convos = state.convos.filter((convo) => convo.conversationId !== action.payload); - }, - removeAll: (state) => { - state.convos = []; - }, - setLatestMessage: (state, action) => { - state.latestMessage = action.payload; - } - } -}); - -export const { - refreshConversation, - setConversation, - setPages, - setConvos, - setNewConvo, - setError, - increasePage, - decreasePage, - setPage, - removeConvo, - removeAll, - setLatestMessage -} = currentSlice.actions; - -export default currentSlice.reducer; diff --git a/client/src/store2/index.js b/client/src/store2/index.js deleted file mode 100644 index aa45a5c9d3..0000000000 --- a/client/src/store2/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import { configureStore } from '@reduxjs/toolkit'; - -import convoReducer from './convoSlice.js'; -import messageReducer from './messageSlice.js'; -import modelReducer from './modelSlice.js'; -import submitReducer from './submitSlice.js'; -import textReducer from './textSlice.js'; -import userReducer from './userReducer.js'; -import searchReducer from './searchSlice.js'; - -export const store = configureStore({ - reducer: { - convo: convoReducer, - messages: messageReducer, - models: modelReducer, - text: textReducer, - submit: submitReducer, - user: userReducer, - search: searchReducer - }, - devTools: true -}); diff --git a/client/src/store2/messageSlice.js b/client/src/store2/messageSlice.js deleted file mode 100644 index de1fef38e0..0000000000 --- a/client/src/store2/messageSlice.js +++ /dev/null @@ -1,35 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; -import buildTree from '~/utils/buildTree'; - -const initialState = { - messages: [], - messageTree: [] -}; - -const currentSlice = createSlice({ - name: 'messages', - initialState, - reducers: { - setMessages: (state, action) => { - state.messages = action.payload; - const groupAll = action.payload[0]?.searchResult; - if (groupAll) console.log('grouping all messages'); - state.messageTree = buildTree(action.payload, groupAll); - }, - setEmptyMessage: (state) => { - state.messages = [ - { - messageId: '1', - conversationId: '1', - parentMessageId: '1', - sender: '', - text: '' - } - ] - }, - } -}); - -export const { setMessages, setEmptyMessage } = currentSlice.actions; - -export default currentSlice.reducer; diff --git a/client/src/store2/modelSlice.js b/client/src/store2/modelSlice.js deleted file mode 100644 index 1db760de5d..0000000000 --- a/client/src/store2/modelSlice.js +++ /dev/null @@ -1,68 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; - -const initialState = { - models: [ - { - _id: '0', - name: 'ChatGPT', - value: 'chatgpt', - model: 'chatgpt' - }, - { - _id: '1', - name: 'CustomGPT', - value: 'chatgptCustom', - model: 'chatgptCustom' - }, - { - _id: '2', - name: 'BingAI', - value: 'bingai', - model: 'bingai' - }, - { - _id: '3', - name: 'Sydney', - value: 'sydney', - model: 'sydney' - }, - { - _id: '4', - name: 'ChatGPT', - value: 'chatgptBrowser', - model: 'chatgptBrowser' - }, - ], - modelMap: {}, - initial: { chatgpt: false, chatgptCustom: false, bingai: false, sydney: false, chatgptBrowser: false } - // initial: { chatgpt: true, chatgptCustom: true, bingai: true, } -}; - -const currentSlice = createSlice({ - name: 'models', - initialState, - reducers: { - setModels: (state, action) => { - const models = [...initialState.models, ...action.payload]; - state.models = models; - const modelMap = {}; - - models.slice(initialState.models.length).forEach((modelItem) => { - modelMap[modelItem.value] = { - chatGptLabel: modelItem.chatGptLabel, - promptPrefix: modelItem.promptPrefix, - model: 'chatgptCustom' - }; - }); - - state.modelMap = modelMap; - }, - setInitial: (state, action) => { - state.initial = action.payload; - } - } -}); - -export const { setModels, setInitial } = currentSlice.actions; - -export default currentSlice.reducer; diff --git a/client/src/store2/searchSlice.js b/client/src/store2/searchSlice.js deleted file mode 100644 index d53c0eea62..0000000000 --- a/client/src/store2/searchSlice.js +++ /dev/null @@ -1,35 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; - -const initialState = { - searchEnabled: false, - search: false, - query: '', - inputValue: '', -}; - -const currentSlice = createSlice({ - name: 'search', - initialState, - reducers: { - setInputValue: (state, action) => { - state.inputValue = action.payload; - }, - setSearchState: (state, action) => { - state.searchEnabled = action.payload; - }, - setQuery: (state, action) => { - const q = action.payload; - state.query = q; - - if (q === '') { - state.search = false; - } else if (q?.length > 0 && !state.search) { - state.search = true; - } - }, - } -}); - -export const { setInputValue, setSearchState, setQuery } = currentSlice.actions; - -export default currentSlice.reducer; diff --git a/client/src/store2/submitSlice.js b/client/src/store2/submitSlice.js deleted file mode 100644 index 6efdb85148..0000000000 --- a/client/src/store2/submitSlice.js +++ /dev/null @@ -1,57 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; - -const initialState = { - isSubmitting: false, - submission: {}, - stopStream: false, - disabled: true, - model: 'chatgpt', - promptPrefix: null, - chatGptLabel: null, - customModel: null, - cursor: true, -}; - -const currentSlice = createSlice({ - name: 'submit', - initialState, - reducers: { - setSubmitState: (state, action) => { - state.isSubmitting = action.payload; - }, - setSubmission: (state, action) => { - state.submission = action.payload; - if (Object.keys(action.payload).length === 0) { - state.isSubmitting = false; - } - }, - setStopStream: (state, action) => { - state.stopStream = action.payload; - }, - setDisabled: (state, action) => { - state.disabled = action.payload; - }, - setModel: (state, action) => { - state.model = action.payload; - }, - toggleCursor: (state, action) => { - if (action.payload) { - state.cursor = action.payload; - } else { - state.cursor = !state.cursor; - } - }, - setCustomGpt: (state, action) => { - state.promptPrefix = action.payload.promptPrefix; - state.chatGptLabel = action.payload.chatGptLabel; - }, - setCustomModel: (state, action) => { - state.customModel = action.payload; - } - } -}); - -export const { toggleCursor, setSubmitState, setSubmission, setStopStream, setDisabled, setModel, setCustomGpt, setCustomModel } = - currentSlice.actions; - -export default currentSlice.reducer; diff --git a/client/src/store2/textSlice.js b/client/src/store2/textSlice.js deleted file mode 100644 index 7d25b16d26..0000000000 --- a/client/src/store2/textSlice.js +++ /dev/null @@ -1,19 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; - -const initialState = { - text: '', -}; - -const currentSlice = createSlice({ - name: 'text', - initialState, - reducers: { - setText: (state, action) => { - state.text = action.payload; - }, - } -}); - -export const { setText } = currentSlice.actions; - -export default currentSlice.reducer; diff --git a/client/src/store2/userReducer.js b/client/src/store2/userReducer.js deleted file mode 100644 index bf0591333b..0000000000 --- a/client/src/store2/userReducer.js +++ /dev/null @@ -1,19 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; - -const initialState = { - user: null, -}; - -const currentSlice = createSlice({ - name: 'user', - initialState, - reducers: { - setUser: (state, action) => { - state.user = action.payload; - }, - } -}); - -export const { setUser } = currentSlice.actions; - -export default currentSlice.reducer; From ee3f6e1d1d0f95dde8619421614e43a0fb296deb Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 02:42:37 +0800 Subject: [PATCH 15/30] remove react-redux. help needed, please generate a package-lock.json --- client/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/package.json b/client/package.json index 6bd5b4fdd6..11d8301083 100644 --- a/client/package.json +++ b/client/package.json @@ -24,7 +24,6 @@ "@radix-ui/react-dropdown-menu": "^2.0.2", "@radix-ui/react-label": "^2.0.0", "@radix-ui/react-tabs": "^1.0.3", - "@reduxjs/toolkit": "^1.9.2", "axios": "^1.3.4", "class-variance-authority": "^0.4.0", "clsx": "^1.2.1", @@ -35,7 +34,6 @@ "react-dom": "^18.2.0", "react-lazy-load": "^4.0.1", "react-markdown": "^8.0.5", - "react-redux": "^8.0.5", "react-router-dom": "^6.9.0", "react-string-replace": "^1.1.0", "react-textarea-autosize": "^8.4.0", From 95e9f05688a9aecbb5bea77ce770c2be1fe1d1ac Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 28 Mar 2023 15:15:10 -0400 Subject: [PATCH 16/30] chore: reset package lock file --- client/package-lock.json | 256 +++++---------------------------------- 1 file changed, 29 insertions(+), 227 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 8f32d07cb0..9b81272887 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,7 +14,6 @@ "@radix-ui/react-dropdown-menu": "^2.0.2", "@radix-ui/react-label": "^2.0.0", "@radix-ui/react-tabs": "^1.0.3", - "@reduxjs/toolkit": "^1.9.2", "axios": "^1.3.4", "class-variance-authority": "^0.4.0", "clsx": "^1.2.1", @@ -25,7 +24,6 @@ "react-dom": "^18.2.0", "react-lazy-load": "^4.0.1", "react-markdown": "^8.0.5", - "react-redux": "^8.0.5", "react-router-dom": "^6.9.0", "react-string-replace": "^1.1.0", "react-textarea-autosize": "^8.4.0", @@ -3044,34 +3042,10 @@ "@babel/runtime": "^7.13.10" } }, - "node_modules/@reduxjs/toolkit": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz", - "integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==", - "dependencies": { - "immer": "^9.0.16", - "redux": "^4.2.0", - "redux-thunk": "^2.4.2", - "reselect": "^4.1.7" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18", - "react-redux": "^7.2.1 || ^8.0.2" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-redux": { - "optional": true - } - } - }, "node_modules/@remix-run/router": { "version": "1.4.0", - "resolved": "https://npm.stereye.tech/@remix-run%2frouter/-/router-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==", - "license": "MIT", "engines": { "node": ">=14" } @@ -3179,15 +3153,6 @@ "@types/unist": "*" } }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, "node_modules/@types/http-proxy": { "version": "1.17.10", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.10.tgz", @@ -3216,9 +3181,9 @@ "integrity": "sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==" }, "node_modules/@types/mdast": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", - "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.11.tgz", + "integrity": "sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==", "dependencies": { "@types/unist": "*" } @@ -3272,6 +3237,7 @@ "version": "18.0.28", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -3287,7 +3253,8 @@ "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "peer": true }, "node_modules/@types/semver": { "version": "7.3.13", @@ -3328,11 +3295,6 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" - }, "node_modules/@types/uuid": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", @@ -6729,9 +6691,8 @@ }, "node_modules/hamt_plus": { "version": "1.0.2", - "resolved": "https://npm.stereye.tech/hamt_plus/-/hamt_plus-1.0.2.tgz", - "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==", - "license": "MIT" + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" }, "node_modules/handle-thing": { "version": "2.0.1", @@ -6981,14 +6942,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -7154,15 +7107,6 @@ "node": ">= 4" } }, - "node_modules/immer": { - "version": "9.0.19", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz", - "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -10609,9 +10553,9 @@ } }, "node_modules/react-markdown": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.5.tgz", - "integrity": "sha512-jGJolWWmOWAvzf+xMdB9zwStViODyyFQhNB/bwCerbBKmrTmgmA599CGiOlP58OId1IMoIRsA8UdI1Lod4zb5A==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.6.tgz", + "integrity": "sha512-KgPWsYgHuftdx510wwIzpwf+5js/iHqBR+fzxefv8Khk3mFbnioF1bmL2idHN3ler0LMQmICKeDrWnZrX9mtbQ==", "dependencies": { "@types/hast": "^2.0.0", "@types/prop-types": "^15.0.0", @@ -10643,49 +10587,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, - "node_modules/react-redux": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", - "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", - "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - }, - "peerDependencies": { - "@types/react": "^16.8 || ^17.0 || ^18.0", - "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0", - "react-native": ">=0.59", - "redux": "^4" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, - "node_modules/react-redux/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, "node_modules/react-remove-scroll": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", @@ -10733,9 +10634,8 @@ }, "node_modules/react-router": { "version": "6.9.0", - "resolved": "https://npm.stereye.tech/react-router/-/react-router-6.9.0.tgz", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz", "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==", - "license": "MIT", "dependencies": { "@remix-run/router": "1.4.0" }, @@ -10748,9 +10648,8 @@ }, "node_modules/react-router-dom": { "version": "6.9.0", - "resolved": "https://npm.stereye.tech/react-router-dom/-/react-router-dom-6.9.0.tgz", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz", "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==", - "license": "MIT", "dependencies": { "@remix-run/router": "1.4.0", "react-router": "6.9.0" @@ -10878,9 +10777,8 @@ }, "node_modules/recoil": { "version": "0.7.7", - "resolved": "https://npm.stereye.tech/recoil/-/recoil-0.7.7.tgz", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", - "license": "MIT", "dependencies": { "hamt_plus": "1.0.2" }, @@ -10896,22 +10794,6 @@ } } }, - "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", - "peerDependencies": { - "redux": "^4" - } - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -11157,11 +11039,6 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, - "node_modules/reselect": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", - "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" - }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -15245,20 +15122,9 @@ "@babel/runtime": "^7.13.10" } }, - "@reduxjs/toolkit": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz", - "integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==", - "requires": { - "immer": "^9.0.16", - "redux": "^4.2.0", - "redux-thunk": "^2.4.2", - "reselect": "^4.1.7" - } - }, "@remix-run/router": { "version": "1.4.0", - "resolved": "https://npm.stereye.tech/@remix-run%2frouter/-/router-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==" }, "@types/body-parser": { @@ -15364,15 +15230,6 @@ "@types/unist": "*" } }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, "@types/http-proxy": { "version": "1.17.10", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.10.tgz", @@ -15401,9 +15258,9 @@ "integrity": "sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==" }, "@types/mdast": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", - "integrity": "sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==", + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.11.tgz", + "integrity": "sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==", "requires": { "@types/unist": "*" } @@ -15457,6 +15314,7 @@ "version": "18.0.28", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "peer": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -15472,7 +15330,8 @@ "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "peer": true }, "@types/semver": { "version": "7.3.13", @@ -15513,11 +15372,6 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" }, - "@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" - }, "@types/uuid": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", @@ -18151,7 +18005,7 @@ }, "hamt_plus": { "version": "1.0.2", - "resolved": "https://npm.stereye.tech/hamt_plus/-/hamt_plus-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" }, "handle-thing": { @@ -18334,14 +18188,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - } - }, "hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -18474,11 +18320,6 @@ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, - "immer": { - "version": "9.0.19", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz", - "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==" - }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -20693,9 +20534,9 @@ "requires": {} }, "react-markdown": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.5.tgz", - "integrity": "sha512-jGJolWWmOWAvzf+xMdB9zwStViODyyFQhNB/bwCerbBKmrTmgmA599CGiOlP58OId1IMoIRsA8UdI1Lod4zb5A==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.6.tgz", + "integrity": "sha512-KgPWsYgHuftdx510wwIzpwf+5js/iHqBR+fzxefv8Khk3mFbnioF1bmL2idHN3ler0LMQmICKeDrWnZrX9mtbQ==", "requires": { "@types/hast": "^2.0.0", "@types/prop-types": "^15.0.0", @@ -20721,26 +20562,6 @@ } } }, - "react-redux": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", - "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", - "requires": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - }, - "dependencies": { - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - } - } - }, "react-remove-scroll": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", @@ -20764,7 +20585,7 @@ }, "react-router": { "version": "6.9.0", - "resolved": "https://npm.stereye.tech/react-router/-/react-router-6.9.0.tgz", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz", "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==", "requires": { "@remix-run/router": "1.4.0" @@ -20772,7 +20593,7 @@ }, "react-router-dom": { "version": "6.9.0", - "resolved": "https://npm.stereye.tech/react-router-dom/-/react-router-dom-6.9.0.tgz", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz", "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==", "requires": { "@remix-run/router": "1.4.0", @@ -20859,26 +20680,12 @@ }, "recoil": { "version": "0.7.7", - "resolved": "https://npm.stereye.tech/recoil/-/recoil-0.7.7.tgz", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", "requires": { "hamt_plus": "1.0.2" } }, - "redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "requires": { - "@babel/runtime": "^7.9.2" - } - }, - "redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", - "requires": {} - }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -21067,11 +20874,6 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, - "reselect": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", - "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" - }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", From 2a16a646128e5aa4bba945080cad67b35672d639 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 28 Mar 2023 15:32:22 -0400 Subject: [PATCH 17/30] fix: cursor appears on placeholder message --- client/src/utils/handleSubmit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/utils/handleSubmit.js b/client/src/utils/handleSubmit.js index 26a63bea12..a1a2492deb 100644 --- a/client/src/utils/handleSubmit.js +++ b/client/src/utils/handleSubmit.js @@ -69,7 +69,7 @@ const useMessageHandler = () => { // construct the placeholder response message const initialResponse = { sender: chatGptLabel || model, - text: '', + text: '', parentMessageId: isRegenerate ? messageId : fakeMessageId, messageId: (isRegenerate ? messageId : fakeMessageId) + '_', conversationId, From e818ee913d2067d26142fb6fa6dd9e0a07b9f555 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 03:34:13 +0800 Subject: [PATCH 18/30] fix: add typescript package for react --- client/package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/package.json b/client/package.json index 11d8301083..cc3b50272f 100644 --- a/client/package.json +++ b/client/package.json @@ -24,6 +24,10 @@ "@radix-ui/react-dropdown-menu": "^2.0.2", "@radix-ui/react-label": "^2.0.0", "@radix-ui/react-tabs": "^1.0.3", + "@types/jest": "^29.5.0", + "@types/node": "^18.15.10", + "@types/react": "^18.0.30", + "@types/react-dom": "^18.0.11", "axios": "^1.3.4", "class-variance-authority": "^0.4.0", "clsx": "^1.2.1", From 74924d2eea9b7c1c6b5700586e3851589c117562 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 28 Mar 2023 16:29:42 -0400 Subject: [PATCH 19/30] reset package-lock again --- api/package-lock.json | 14 +- api/package.json | 2 +- client/package-lock.json | 1092 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 1051 insertions(+), 57 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index 0504951d22..355c9dcc69 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@keyv/mongo": "^2.1.8", - "@waylaidwanderer/chatgpt-api": "^1.32.8", + "@waylaidwanderer/chatgpt-api": "^1.33.1", "axios": "^1.3.4", "chatgpt-latest": "npm:@waylaidwanderer/chatgpt-api@^1.31.6", "cors": "^2.8.5", @@ -1626,9 +1626,9 @@ } }, "node_modules/@waylaidwanderer/chatgpt-api": { - "version": "1.32.8", - "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.32.8.tgz", - "integrity": "sha512-0PZTP+M8tyJa9fT0avDZjGcNVRy4glSKj1dWUIGosCySS2EdOyNt0BZ18zsQYDQP50p2FADtY3b3b6DTZCcldw==", + "version": "1.33.1", + "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.33.1.tgz", + "integrity": "sha512-RUWrwOcm22mV1j0bQUoY1TB3UzmpuQxV7jQurUMsIy/pfSQwS5n8nsJ0erU76t9H5eSiXoRL6/cmMBWbUd+J9w==", "dependencies": { "@dqbd/tiktoken": "^1.0.2", "@fastify/cors": "^8.2.0", @@ -6969,9 +6969,9 @@ } }, "@waylaidwanderer/chatgpt-api": { - "version": "1.32.8", - "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.32.8.tgz", - "integrity": "sha512-0PZTP+M8tyJa9fT0avDZjGcNVRy4glSKj1dWUIGosCySS2EdOyNt0BZ18zsQYDQP50p2FADtY3b3b6DTZCcldw==", + "version": "1.33.1", + "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.33.1.tgz", + "integrity": "sha512-RUWrwOcm22mV1j0bQUoY1TB3UzmpuQxV7jQurUMsIy/pfSQwS5n8nsJ0erU76t9H5eSiXoRL6/cmMBWbUd+J9w==", "requires": { "@dqbd/tiktoken": "^1.0.2", "@fastify/cors": "^8.2.0", diff --git a/api/package.json b/api/package.json index 48643acc1a..06594b0872 100644 --- a/api/package.json +++ b/api/package.json @@ -20,7 +20,7 @@ "homepage": "https://github.com/danny-avila/chatgpt-clone#readme", "dependencies": { "@keyv/mongo": "^2.1.8", - "@waylaidwanderer/chatgpt-api": "^1.32.8", + "@waylaidwanderer/chatgpt-api": "^1.33.1", "axios": "^1.3.4", "chatgpt-latest": "npm:@waylaidwanderer/chatgpt-api@^1.31.6", "cors": "^2.8.5", diff --git a/client/package-lock.json b/client/package-lock.json index 9b81272887..4e04ad0de9 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,6 +14,10 @@ "@radix-ui/react-dropdown-menu": "^2.0.2", "@radix-ui/react-label": "^2.0.0", "@radix-ui/react-tabs": "^1.0.3", + "@types/jest": "^29.5.0", + "@types/node": "^18.15.10", + "@types/react": "^18.0.30", + "@types/react-dom": "^18.0.11", "axios": "^1.3.4", "class-variance-authority": "^0.4.0", "clsx": "^1.2.1", @@ -123,7 +127,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, "dependencies": { "@babel/highlight": "^7.18.6" }, @@ -510,7 +513,6 @@ "version": "7.19.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -557,7 +559,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", @@ -2443,6 +2444,108 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dependencies": { + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dependencies": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", @@ -3050,6 +3153,11 @@ "node": ">=14" } }, + "node_modules/@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -3162,6 +3270,36 @@ "@types/node": "*" } }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -3200,10 +3338,9 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "node_modules/@types/node": { - "version": "18.14.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", - "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==", - "dev": true + "version": "18.15.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz", + "integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -3234,16 +3371,23 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.0.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", - "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", - "peer": true, + "version": "18.0.30", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.30.tgz", + "integrity": "sha512-AnME2cHDH11Pxt/yYX6r0w448BfTwQOLEhQEjCdwB7QskEI7EKtxhGUsExTQe/MsY3D9D5rMtu62WRocw9A8FA==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, + "node_modules/@types/react-dom": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -3253,8 +3397,7 @@ "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "peer": true + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "node_modules/@types/semver": { "version": "7.3.13", @@ -3290,6 +3433,11 @@ "@types/node": "*" } }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" + }, "node_modules/@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -3309,6 +3457,19 @@ "@types/node": "*" } }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" + }, "node_modules/@typescript-eslint/scope-manager": { "version": "5.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.0.tgz", @@ -3853,7 +4014,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -4569,7 +4729,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -4623,6 +4782,20 @@ "node": ">=6.0" } }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, "node_modules/cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -4674,7 +4847,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -4682,8 +4854,7 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/colorette": { "version": "2.0.19", @@ -5261,6 +5432,14 @@ "node": ">=0.3.1" } }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -5527,7 +5706,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -6121,6 +6299,21 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -6680,8 +6873,7 @@ "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "node_modules/grapheme-splitter": { "version": "1.0.4", @@ -6724,7 +6916,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, "engines": { "node": ">=4" } @@ -7566,6 +7757,341 @@ "node": ">=0.10.0" } }, + "node_modules/jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -10311,6 +10837,35 @@ } } }, + "node_modules/pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dependencies": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -11632,6 +12187,25 @@ "wbuf": "^1.7.3" } }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "engines": { + "node": ">=8" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -11767,7 +12341,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -13143,7 +13716,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, "requires": { "@babel/highlight": "^7.18.6" } @@ -13430,8 +14002,7 @@ "@babel/helper-validator-identifier": { "version": "7.19.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" }, "@babel/helper-validator-option": { "version": "7.21.0", @@ -13466,7 +14037,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", @@ -14648,6 +15218,80 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "requires": { + "jest-get-type": "^29.4.3" + } + }, + "@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "requires": { + "@sinclair/typebox": "^0.25.16" + } + }, + "@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "requires": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@jridgewell/gen-mapping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", @@ -15127,6 +15771,11 @@ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==" }, + "@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==" + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -15239,6 +15888,36 @@ "@types/node": "*" } }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -15277,10 +15956,9 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "@types/node": { - "version": "18.14.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", - "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==", - "dev": true + "version": "18.15.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz", + "integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==" }, "@types/parse-json": { "version": "4.0.0", @@ -15311,16 +15989,23 @@ "dev": true }, "@types/react": { - "version": "18.0.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", - "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", - "peer": true, + "version": "18.0.30", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.30.tgz", + "integrity": "sha512-AnME2cHDH11Pxt/yYX6r0w448BfTwQOLEhQEjCdwB7QskEI7EKtxhGUsExTQe/MsY3D9D5rMtu62WRocw9A8FA==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, + "@types/react-dom": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", + "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "requires": { + "@types/react": "*" + } + }, "@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -15330,8 +16015,7 @@ "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "peer": true + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, "@types/semver": { "version": "7.3.13", @@ -15367,6 +16051,11 @@ "@types/node": "*" } }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" + }, "@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -15386,6 +16075,19 @@ "@types/node": "*" } }, + "@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" + }, "@typescript-eslint/scope-manager": { "version": "5.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.0.tgz", @@ -15804,7 +16506,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -16388,7 +17089,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -16421,6 +17121,11 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==" + }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -16456,7 +17161,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -16464,8 +17168,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "colorette": { "version": "2.0.19", @@ -16899,6 +17602,11 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==" }, + "diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==" + }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -17126,8 +17834,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" }, "eslint": { "version": "8.35.0", @@ -17566,6 +18273,18 @@ "strip-final-newline": "^2.0.0" } }, + "expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "requires": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + } + }, "express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -17994,8 +18713,7 @@ "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "grapheme-splitter": { "version": "1.0.4", @@ -18031,8 +18749,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, "has-property-descriptors": { "version": "1.0.0", @@ -18621,6 +19338,247 @@ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true }, + "jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==" + }, + "jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -20353,6 +21311,28 @@ "dev": true, "requires": {} }, + "pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "requires": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -21331,6 +22311,21 @@ "wbuf": "^1.7.3" } }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + } + } + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -21430,7 +22425,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } From e6632700722dfebe896839a3b836c5543c3ef661 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 05:48:44 +0800 Subject: [PATCH 20/30] fix: show message list. --- client/src/routes/Chat.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/routes/Chat.jsx b/client/src/routes/Chat.jsx index 04cdf93a95..2d1607baa4 100644 --- a/client/src/routes/Chat.jsx +++ b/client/src/routes/Chat.jsx @@ -68,7 +68,7 @@ export default function Chat() { return ( <> - {conversationId == 'new' ? : } + {conversationId === 'new' && !!messagesTree?.length ? : } ); From e706f0ea9e58cab85cc9672efda7e4b1e9d3a4fa Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 06:44:18 +0800 Subject: [PATCH 21/30] typo --- client/src/routes/Chat.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/routes/Chat.jsx b/client/src/routes/Chat.jsx index 2d1607baa4..3feab50a0f 100644 --- a/client/src/routes/Chat.jsx +++ b/client/src/routes/Chat.jsx @@ -68,7 +68,7 @@ export default function Chat() { return ( <> - {conversationId === 'new' && !!messagesTree?.length ? : } + {conversationId === 'new' && !messagesTree?.length ? : } ); From f53b620df56cd30f9429b5f435a97dac4f00e1f3 Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Tue, 28 Mar 2023 19:10:22 -0400 Subject: [PATCH 22/30] chore: add back clear convo dialog --- client/src/components/Nav/ClearConvos.jsx | 33 ++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/client/src/components/Nav/ClearConvos.jsx b/client/src/components/Nav/ClearConvos.jsx index e975c79e5e..3cf6291c48 100644 --- a/client/src/components/Nav/ClearConvos.jsx +++ b/client/src/components/Nav/ClearConvos.jsx @@ -1,9 +1,10 @@ import React from 'react'; +import store from '~/store'; import TrashIcon from '../svg/TrashIcon'; import { useSWRConfig } from 'swr'; import manualSWR from '~/utils/fetchers'; - -import store from '~/store'; +import { Dialog, DialogTrigger } from '../ui/Dialog.tsx'; +import DialogTemplate from '../ui/DialogTemplate'; export default function ClearConvos() { const { newConversation } = store.useConversation(); @@ -13,6 +14,7 @@ export default function ClearConvos() { const { trigger } = manualSWR(`/api/convos/clear`, 'post', () => { newConversation(); refreshConversations(); + mutate(`/api/convos`); }); const clickHandler = () => { @@ -21,12 +23,25 @@ export default function ClearConvos() { }; return ( -
- - Clear conversations - + + + + + Clear conversations + + + + ); } From 005d8fb1787065a37e56b39d5df2db888fc9976b Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Tue, 28 Mar 2023 19:50:37 -0400 Subject: [PATCH 23/30] edit titleConvo for consistent results --- api/app/titleConvo.js | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/api/app/titleConvo.js b/api/app/titleConvo.js index 249e75c8b4..44c2b9f5e8 100644 --- a/api/app/titleConvo.js +++ b/api/app/titleConvo.js @@ -18,20 +18,31 @@ const proxyEnvToAxiosProxy = proxyString => { const titleConvo = async ({ model, text, response }) => { let title = 'New Chat'; + const messages = [ + { + role: 'system', + content: + // `You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user, using the same language. The requirement are: 1. If possible, generate in 5 words or less, 2. Using title case, 3. must give the title using the language as the user said. 4. Don't refer to the participants of the conversation. 5. Do not include punctuation or quotation marks. 6. Your response should be in title case, exclusively containing the title. 7. don't say anything except the title. + `Detect user language and write in the same language an extremely concise title for this conversation, which you must accurately detect. Write in the detected language. Title in 5 Words or Less. No Punctuation/Quotation. All words should be capitalized and complete only the title in User Language only. + +||>User: +"${text}" +||>Response: +"${JSON.stringify(response?.text)}" + +||>Title:` + } + // { + // role: 'user', + // content: `User:\n "${text}"\n\n${model}: \n"${JSON.stringify(response?.text)}"\n\n` + // } + ]; + + console.log('MESSAGES', messages[0]); const request = { model: 'gpt-3.5-turbo', - messages: [ - { - role: 'system', - content: - "You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user, using the same language. The requirement are: 1. If possible, generate in 5 words or less, 2. Using title case, 3. must give the title using the language as the user said. 4. Don't refer to the participants of the conversation. 5. Do not include punctuation or quotation marks. 6. Your response should be in title case, exclusively containing the title. 7. don't say anything except the title." - }, - { - role: 'user', - content: `User:\n "${text}"\n\n${model}: \n"${JSON.stringify(response?.text)}"\n\n` - } - ], + messages, temperature: 0, presence_penalty: 0, frequency_penalty: 0 From 0d7300be9b7d0e49b6ccc16bb82b8441136ffef6 Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Tue, 28 Mar 2023 22:28:43 -0400 Subject: [PATCH 24/30] fix: chatgptBrowser handling and ask.js refactor --- api/app/clients/chatgpt-browser.js | 2 + api/app/titleConvo.js | 4 +- api/models/Conversation.js | 10 ++- api/server/routes/ask.js | 115 +++++++---------------------- 4 files changed, 39 insertions(+), 92 deletions(-) diff --git a/api/app/clients/chatgpt-browser.js b/api/app/clients/chatgpt-browser.js index 01de94cd65..e4d452bfd7 100644 --- a/api/app/clients/chatgpt-browser.js +++ b/api/app/clients/chatgpt-browser.js @@ -31,6 +31,8 @@ const browserClient = async ({ text, onProgress, convo, abortController }) => { options = { ...options, ...convo }; } + console.log('gptBrowser options', options, clientOptions); + /* will error if given a convoId at the start */ if (convo.parentMessageId.startsWith('0000')) { delete options.conversationId; diff --git a/api/app/titleConvo.js b/api/app/titleConvo.js index 44c2b9f5e8..68ac22b4cc 100644 --- a/api/app/titleConvo.js +++ b/api/app/titleConvo.js @@ -23,7 +23,7 @@ const titleConvo = async ({ model, text, response }) => { role: 'system', content: // `You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user, using the same language. The requirement are: 1. If possible, generate in 5 words or less, 2. Using title case, 3. must give the title using the language as the user said. 4. Don't refer to the participants of the conversation. 5. Do not include punctuation or quotation marks. 6. Your response should be in title case, exclusively containing the title. 7. don't say anything except the title. - `Detect user language and write in the same language an extremely concise title for this conversation, which you must accurately detect. Write in the detected language. Title in 5 Words or Less. No Punctuation/Quotation. All words should be capitalized and complete only the title in User Language only. + `Detect user language and write in the same language an extremely concise title for this conversation, which you must accurately detect. Write in the detected language. Title in 5 Words or Less. No Punctuation/Quotation. All first letters of every word should be capitalized and complete only the title in User Language only. ||>User: "${text}" @@ -38,7 +38,7 @@ const titleConvo = async ({ model, text, response }) => { // } ]; - console.log('MESSAGES', messages[0]); + // console.log('Title Prompt', messages[0]); const request = { model: 'gpt-3.5-turbo', diff --git a/api/models/Conversation.js b/api/models/Conversation.js index 512010c6ba..fed22793d2 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -43,9 +43,15 @@ module.exports = { return { message: 'Error saving conversation' }; } }, - updateConvo: async (user, { conversationId, ...update }) => { + updateConvo: async (user, { conversationId, oldConvoId, ...update }) => { try { - return await Conversation.findOneAndUpdate({ conversationId: conversationId, user }, update, { + let convoId = conversationId; + if (oldConvoId) { + convoId = oldConvoId; + update.conversationId = conversationId; + } + + return await Conversation.findOneAndUpdate({ conversationId: convoId, user }, update, { new: true }).exec(); } catch (error) { diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index fff05511c0..00ed502f14 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -4,26 +4,27 @@ const router = express.Router(); const askBing = require('./askBing'); const askSydney = require('./askSydney'); const { titleConvo, askClient, browserClient, customClient } = require('../../app/'); -const { getConvo, saveMessage, getConvoTitle, saveConvo, updateConvo } = require('../../models'); +const { saveMessage, getConvoTitle, saveConvo, updateConvo } = require('../../models'); const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); -const { getMessages } = require('../../models/Message'); router.use('/bing', askBing); router.use('/sydney', askSydney); router.post('/', async (req, res) => { - let { model, text, overrideParentMessageId=null, parentMessageId, conversationId: oldConversationId, ...convo } = req.body; - if (text.length === 0) { - return handleError(res, { text: 'Prompt empty or too short' }); - } + const { + model, + text, + overrideParentMessageId = null, + parentMessageId, + conversationId: oldConversationId, + ...convo + } = req.body; + if (text.length === 0) return handleError(res, { text: 'Prompt empty or too short' }); - console.log('model:', model, 'oldConvoId:', oldConversationId); const conversationId = oldConversationId || crypto.randomUUID(); - console.log('conversationId after old:', conversationId); - const userMessageId = crypto.randomUUID(); const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000'; - let userMessage = { + const userMessage = { messageId: userMessageId, sender: 'User', text, @@ -31,32 +32,18 @@ router.post('/', async (req, res) => { conversationId, isCreatedByUser: true }; - console.log('ask log', { model, ...userMessage, ...convo }); - // Chore: This creates a loose a stranded initial message for chatgptBrowser - if (!overrideParentMessageId) { await saveMessage(userMessage); + await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo }); } - -if (!overrideParentMessageId && model !== 'chatgptBrowser') { - await saveConvo(req?.session?.user?.username, { ...userMessage, model, ...convo }); -} - return await ask({ - userMessage, - model, - convo, - preSendRequest: true, - overrideParentMessageId, - req, - res - }); + return await ask({ userMessage, model, convo, preSendRequest: true, overrideParentMessageId, req, res }); }); const ask = async ({ @@ -68,22 +55,14 @@ const ask = async ({ req, res }) => { - let { + const { text, parentMessageId: userParentMessageId, conversationId, messageId: userMessageId } = userMessage; - let client; - - if (model === 'chatgpt') { - client = askClient; - } else if (model === 'chatgptCustom') { - client = customClient; - } else { - client = browserClient; - } + const client = model === 'chatgpt' ? askClient : model === 'chatgptCustom' ? customClient : browserClient; res.writeHead(200, { Connection: 'keep-alive', @@ -97,79 +76,46 @@ const ask = async ({ try { const progressCallback = createOnProgress(); - const abortController = new AbortController(); - res.on('close', () => { - console.log('The client has disconnected.'); - // 执行其他操作 - abortController.abort(); - }) - + res.on('close', () => abortController.abort()); let gptResponse = await client({ text, onProgress: progressCallback.call(null, model, { res, text }), - convo: { - parentMessageId: userParentMessageId, - conversationId, - ...convo - }, + convo: { parentMessageId: userParentMessageId, conversationId, ...convo }, ...convo, abortController }); - console.log('CLIENT RESPONSE', gptResponse); gptResponse.text = gptResponse.response; + console.log('CLIENT RESPONSE', gptResponse); if (!gptResponse.parentMessageId) { - // gptResponse.id = gptResponse.messageId; gptResponse.parentMessageId = overrideParentMessageId || userMessageId; - // userMessage.conversationId = conversationId - // ? conversationId - // : gptResponse.conversationId; - // await saveMessage(userMessage); delete gptResponse.response; } - if ( - (gptResponse.text.includes('2023') && !gptResponse.text.trim().includes(' ')) || - gptResponse.text.toLowerCase().includes('no response') || - gptResponse.text.toLowerCase().includes('no answer') - ) { - await saveMessage({ - messageId: crypto.randomUUID(), - sender: model, - conversationId, - parentMessageId: overrideParentMessageId || userMessageId, - error: true, - text: 'Prompt empty or too short' - }); - return handleError(res, { text: 'Prompt empty or too short' }); - } - gptResponse.sender = model === 'chatgptCustom' ? convo.chatGptLabel : model; gptResponse.model = model; - // gptResponse.final = true; gptResponse.text = await handleText(gptResponse); - - if (convo.chatGptLabel?.length > 0 && model === 'chatgptCustom') { gptResponse.chatGptLabel = convo.chatGptLabel; } - + if (convo.promptPrefix?.length > 0 && model === 'chatgptCustom') { gptResponse.promptPrefix = convo.promptPrefix; } - // override the parentMessageId, for the regeneration. gptResponse.parentMessageId = overrideParentMessageId || userMessageId; - /* this is a hacky solution to get the browserClient working right, will refactor later */ if (model === 'chatgptBrowser' && userParentMessageId.startsWith('000')) { await saveMessage({ ...userMessage, conversationId: gptResponse.conversationId }); } await saveMessage(gptResponse); - await updateConvo(req?.session?.user?.username, gptResponse); + await updateConvo(req?.session?.user?.username, { + ...gptResponse, + oldConvoId: model === 'chatgptBrowser' && conversationId + }); sendMessage(res, { title: await getConvoTitle(req?.session?.user?.username, conversationId), final: true, @@ -180,19 +126,12 @@ const ask = async ({ if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { const title = await titleConvo({ model, text, response: gptResponse }); - - await updateConvo( - req?.session?.user?.username, - { - /* again, for sake of browser client, will soon refactor */ - conversationId: model === 'chatgptBrowser' ? gptResponse.conversationId : conversationId, - title - } - ); + await updateConvo(req?.session?.user?.username, { + conversationId: model === 'chatgptBrowser' ? gptResponse.conversationId : conversationId, + title + }); } } catch (error) { - console.log(error); - // await deleteMessages({ messageId: userMessageId }); const errorMessage = { messageId: crypto.randomUUID(), sender: model, From e796a19136b69b24c4e585010523a3a31b68f3f7 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 13:32:01 +0800 Subject: [PATCH 25/30] fix: remove related messages when deleting conversations. --- api/models/Conversation.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/models/Conversation.js b/api/models/Conversation.js index fed22793d2..c7ff0f18f8 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -149,9 +149,10 @@ module.exports = { } }, deleteConvos: async (user, filter) => { - let deleteCount = await Conversation.deleteMany({ ...filter, user }).exec(); - console.log('deleteCount', deleteCount); - deleteCount.messages = await deleteMessages({ ...filter, user }); + let toRemove = await Conversation.find({...filter, user}).select('conversationId') + const ids = toRemove.map(instance => instance.conversationId); + let deleteCount = await Conversation.deleteMany({...filter, user}).exec(); + deleteCount.messages = await deleteMessages({conversationId: {$in: ids}}); return deleteCount; } }; From 2c1871d5baf82836a4951f13584890ae8972b8ea Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 29 Mar 2023 16:11:43 +0800 Subject: [PATCH 26/30] fix: update url rule in nginx --- client/nginx.conf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/nginx.conf b/client/nginx.conf index 1924aded1e..96cfe345e7 100644 --- a/client/nginx.conf +++ b/client/nginx.conf @@ -2,14 +2,14 @@ server { listen 80; server_name localhost; - location / { - # Serve your React app - root /usr/share/nginx/html; - index index.html; - } - location /api { # Proxy requests to the API service proxy_pass http://api:3080/api; } + + location / { + # Serve your React app + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + } } From f93df2aea64b2702ede550ada17017521d859d2b Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 29 Mar 2023 08:19:00 -0400 Subject: [PATCH 27/30] lift react markdown --- client/package-lock.json | 2 +- client/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 4e04ad0de9..c459631186 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -27,7 +27,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-lazy-load": "^4.0.1", - "react-markdown": "^8.0.5", + "react-markdown": "^8.0.6", "react-router-dom": "^6.9.0", "react-string-replace": "^1.1.0", "react-textarea-autosize": "^8.4.0", diff --git a/client/package.json b/client/package.json index cc3b50272f..8fd7aad3d1 100644 --- a/client/package.json +++ b/client/package.json @@ -37,7 +37,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-lazy-load": "^4.0.1", - "react-markdown": "^8.0.5", + "react-markdown": "^8.0.6", "react-router-dom": "^6.9.0", "react-string-replace": "^1.1.0", "react-textarea-autosize": "^8.4.0", From b9699feb3b7e9ab89515acf6c7ebad2aba6379ad Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 29 Mar 2023 08:49:39 -0400 Subject: [PATCH 28/30] fix: prevent scroll to top on initial messages --- client/src/components/Nav/index.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/components/Nav/index.jsx b/client/src/components/Nav/index.jsx index 85523033ea..aa6a7895e1 100644 --- a/client/src/components/Nav/index.jsx +++ b/client/src/components/Nav/index.jsx @@ -142,9 +142,9 @@ export default function Nav({ navVisible, setNavVisible }) { setNavVisible(prev => !prev); }; - useEffect(() => { - moveTo(); - }, [data]); + // useEffect(() => { + // moveTo(); + // }, [data]); useEffect(() => { setNavVisible(false); From 39ff9c1bc2884fc8ed7640c6ef7fe6fe19b9bce4 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 29 Mar 2023 09:02:49 -0400 Subject: [PATCH 29/30] fix: resize bing tabs --- client/src/components/Input/BingStyles.jsx | 4 ++-- client/src/style.css | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/components/Input/BingStyles.jsx b/client/src/components/Input/BingStyles.jsx index 369d7237b9..50e649db80 100644 --- a/client/src/components/Input/BingStyles.jsx +++ b/client/src/components/Input/BingStyles.jsx @@ -20,8 +20,8 @@ function BingStyles(props, ref) { }, [conversationId, model, value]); const show = isBing && (!conversationId || messages?.length === 0 || props.show); - const defaultClasses = 'p-2 rounded-md font-normal bg-white/[.60] dark:bg-gray-700 text-black'; - const defaultSelected = defaultClasses + 'font-medium data-[state=active]:text-white'; + const defaultClasses = 'p-2 rounded-md min-w-[75px] font-normal bg-white/[.60] dark:bg-gray-700 text-black text-xs'; + const defaultSelected = defaultClasses + 'font-medium data-[state=active]:text-white text-xs'; const selectedClass = val => val + '-tab ' + defaultSelected; diff --git a/client/src/style.css b/client/src/style.css index 2df50e274c..86e343e63c 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -37,7 +37,7 @@ bottom: 39px; z-index: 995; margin-left: auto; margin-right: auto; -width: 408px; /* Need a specific value to work */ +width: 308px; /* Need a specific value to work */ transition: all 0.5s ease-in-out; pointer-events: none; transform: translateY(-60px); From 4a94ee7af8f368f5c6851d6e314d24b3b41ba3a9 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 29 Mar 2023 10:17:24 -0400 Subject: [PATCH 30/30] feat: allow default gpt api model before frontend customization --- api/.env.example | 6 ++++++ api/app/clients/chatgpt-client.js | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/api/.env.example b/api/.env.example index 7a1c9b15f9..30f097f759 100644 --- a/api/.env.example +++ b/api/.env.example @@ -18,6 +18,12 @@ MONGO_URI="mongodb://127.0.0.1:27017/chatgpt-clone" # API key configuration. # Leave blank if you don't want them. OPENAI_KEY= + +# Default ChatGPT API Model, options: 'gpt-4', 'text-davinci-003', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0301' +# you will have errors if you don't have access to a model like 'gpt-4', defaults to turbo if left empty/excluded. +DEFAULT_API_GPT=gpt-3.5-turbo + +# _U Cookies Value from bing.com BING_TOKEN= # ChatGPT Browser Client (free but use at your own risk) diff --git a/api/app/clients/chatgpt-client.js b/api/app/clients/chatgpt-client.js index 04368e85bd..7b20a18f82 100644 --- a/api/app/clients/chatgpt-client.js +++ b/api/app/clients/chatgpt-client.js @@ -1,5 +1,6 @@ require('dotenv').config(); const { KeyvFile } = require('keyv-file'); +const set = new Set(['gpt-4', 'text-davinci-003', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0301']); const clientOptions = { modelOptions: { @@ -9,6 +10,10 @@ const clientOptions = { debug: false }; +if (set.has(process.env.DEFAULT_API_GPT)) { + clientOptions.modelOptions.model = process.env.DEFAULT_API_GPT; +} + const askClient = async ({ text, onProgress, convo, abortController }) => { const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default; const store = {