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-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/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 = { diff --git a/api/app/titleConvo.js b/api/app/titleConvo.js index 9575377b44..68ac22b4cc 100644 --- a/api/app/titleConvo.js +++ b/api/app/titleConvo.js @@ -18,22 +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 first letters of every word 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('Title Prompt', 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 in title case, using the same language.' - }, - { - 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: ` - } - ], + messages, temperature: 0, presence_penalty: 0, frequency_penalty: 0 diff --git a/api/models/Conversation.js b/api/models/Conversation.js index b5716add01..c7ff0f18f8 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -43,15 +43,17 @@ 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, - { - new: true - } - ).exec(); + let convoId = conversationId; + if (oldConvoId) { + convoId = oldConvoId; + update.conversationId = conversationId; + } + + return await Conversation.findOneAndUpdate({ conversationId: convoId, user }, update, { + new: true + }).exec(); } catch (error) { console.log(error); return { message: 'Error updating conversation' }; @@ -89,7 +91,7 @@ module.exports = { promises.push( Conversation.findOne({ user, - conversationId: convo.conversationId, + conversationId: convo.conversationId }).exec() ) ); @@ -143,13 +145,14 @@ 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); + 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; } }; 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/api/server/index.js b/api/server/index.js index d929bd5d8a..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 }) ); @@ -67,26 +68,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/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, diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index 656caa0e1b..ed3f6db15c 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -13,30 +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()); -}); -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); + if (convo) res.status(200).send(convo.toObject()); + else res.status(404).end(); }); router.post('/clear', async (req, res) => { 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/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/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; + } } diff --git a/client/package-lock.json b/client/package-lock.json index 6c0c23665a..c459631186 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,7 +14,10 @@ "@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", + "@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", @@ -24,11 +27,12 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-lazy-load": "^4.0.1", - "react-markdown": "^8.0.5", - "react-redux": "^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", "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", @@ -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", @@ -3042,29 +3145,19 @@ "@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://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", + "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==", + "engines": { + "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", @@ -3168,15 +3261,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", @@ -3186,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", @@ -3205,9 +3319,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": "*" } @@ -3224,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", @@ -3258,15 +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==", + "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", @@ -3312,16 +3433,16 @@ "@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", "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", @@ -3336,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", @@ -3880,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" }, @@ -4596,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", @@ -4650,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", @@ -4701,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" } @@ -4709,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", @@ -5288,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", @@ -5554,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" } @@ -6148,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", @@ -6707,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", @@ -6716,6 +6881,11 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "node_modules/hamt_plus": { + "version": "1.0.2", + "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", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -6746,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" } @@ -6964,14 +7133,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", @@ -7137,15 +7298,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", @@ -7605,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", @@ -10350,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", @@ -10592,9 +11108,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", @@ -10626,49 +11142,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", @@ -10714,6 +11187,36 @@ } } }, + "node_modules/react-router": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz", + "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==", + "dependencies": { + "@remix-run/router": "1.4.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz", + "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==", + "dependencies": { + "@remix-run/router": "1.4.0", + "react-router": "6.9.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-string-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/react-string-replace/-/react-string-replace-1.1.0.tgz", @@ -10827,20 +11330,23 @@ "node": ">= 10.13.0" } }, - "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==", + "node_modules/recoil": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", "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==", + "hamt_plus": "1.0.2" + }, "peerDependencies": { - "redux": "^4" + "react": ">=16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } } }, "node_modules/regenerate": { @@ -11088,11 +11594,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", @@ -11686,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", @@ -11821,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" }, @@ -13197,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" } @@ -13484,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", @@ -13520,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", @@ -14702,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", @@ -15176,16 +15766,15 @@ "@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://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", @@ -15290,15 +15879,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", @@ -15308,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", @@ -15327,9 +15937,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": "*" } @@ -15346,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", @@ -15380,15 +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==", + "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", @@ -15434,16 +16051,16 @@ "@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", "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", @@ -15458,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", @@ -15876,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" } @@ -16460,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", @@ -16493,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", @@ -16528,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" } @@ -16536,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", @@ -16971,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", @@ -17198,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", @@ -17638,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", @@ -18066,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", @@ -18075,6 +18721,11 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -18098,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", @@ -18255,14 +18905,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", @@ -18395,11 +19037,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", @@ -18701,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", @@ -20433,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", @@ -20614,9 +21514,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", @@ -20642,26 +21542,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", @@ -20683,6 +21563,23 @@ "tslib": "^2.0.0" } }, + "react-router": { + "version": "6.9.0", + "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" + } + }, + "react-router-dom": { + "version": "6.9.0", + "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", + "react-router": "6.9.0" + } + }, "react-string-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/react-string-replace/-/react-string-replace-1.1.0.tgz", @@ -20761,20 +21658,14 @@ "resolve": "^1.20.0" } }, - "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==", + "recoil": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", "requires": { - "@babel/runtime": "^7.9.2" + "hamt_plus": "1.0.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", @@ -20963,11 +21854,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", @@ -21425,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", @@ -21524,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" } diff --git a/client/package.json b/client/package.json index 1357b72066..8fd7aad3d1 100644 --- a/client/package.json +++ b/client/package.json @@ -24,7 +24,10 @@ "@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", + "@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", @@ -34,11 +37,12 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-lazy-load": "^4.0.1", - "react-markdown": "^8.0.5", - "react-redux": "^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", "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/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 @@ diff --git a/client/src/App.jsx b/client/src/App.jsx index 6ee3be2a6b..d8673b69ed 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,53 +1,88 @@ 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 Search from './routes/Search'; +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: + }, + { + path: 'search/:query?', + element: + } + ] + } +]); - 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); + const setModelsFilter = useSetRecoilState(store.modelsFilter); useEffect(() => { - axios.get('/api/search/enable').then((res) => { console.log(res.data); dispatch(setSearchState(res.data))}); + // fetch if seatch enabled + axios + .get('/api/search/enable', { + timeout: 1000, + withCredentials: true + }) + .then(res => { + setIsSearchEnabled(res.data); + }); + + // fetch user userAuth() - .then((user) => dispatch(setUser(user))) - .catch((err) => console.log(err)); + .then(user => setUser(user)) + .catch(err => console.log(err)); + + // fetch models + axios + .get('/api/models', { + timeout: 1000, + withCredentials: true + }) + .then(({ data }) => { + const filter = { + chatgpt: data?.hasOpenAI, + chatgptCustom: data?.hasOpenAI, + bingai: data?.hasBing, + sydney: data?.hasBing, + chatgptBrowser: data?.hasChatGpt + }; + setModelsFilter(filter); + }) + .catch(error => { + console.error(error); + console.log('Not login!'); + window.location.href = '/auth/login'; + }); }, []); if (user) 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, source: 'button' }); 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 : (
diff --git a/client/src/components/Main/BingStyles.jsx b/client/src/components/Input/BingStyles.jsx similarity index 59% rename from client/src/components/Main/BingStyles.jsx rename to client/src/components/Input/BingStyles.jsx index 440107ce11..50e649db80 100644 --- a/client/src/components/Main/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 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 show = isBing && (!conversationId || messages?.length === 0 || props.show); + 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; + 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/Main/Footer.jsx b/client/src/components/Input/Footer.jsx similarity index 100% rename from client/src/components/Main/Footer.jsx rename to client/src/components/Input/Footer.jsx diff --git a/client/src/components/Models/MenuItems.jsx b/client/src/components/Input/Models/MenuItems.jsx similarity index 53% rename from client/src/components/Models/MenuItems.jsx rename to client/src/components/Input/Models/MenuItems.jsx index d0a277ea9e..a8554ba598 100644 --- a/client/src/components/Models/MenuItems.jsx +++ b/client/src/components/Input/Models/MenuItems.jsx @@ -4,16 +4,12 @@ import ModelItem from './ModelItem'; export default function MenuItems({ models, onSelect }) { return ( <> - {models.map((modelItem) => ( + {models.map(modelItem => ( ))} diff --git a/client/src/components/Models/ModelDialog.jsx b/client/src/components/Input/Models/ModelDialog.jsx similarity index 83% rename from client/src/components/Models/ModelDialog.jsx rename to client/src/components/Input/Models/ModelDialog.jsx index 984cb7ba4d..86d6b30abe 100644 --- a/client/src/components/Models/ModelDialog.jsx +++ b/client/src/components/Input/Models/ModelDialog.jsx @@ -1,12 +1,9 @@ import React, { useState, useRef } from 'react'; import TextareaAutosize from 'react-textarea-autosize'; -import { useSelector, useDispatch } from 'react-redux'; -import { setSubmission, setModel, setCustomGpt } from '~/store/submitSlice'; -import { setNewConvo } from '~/store/convoSlice'; import manualSWR from '~/utils/fetchers'; -import { Button } from '../ui/Button.tsx'; -import { Input } from '../ui/Input.tsx'; -import { Label } from '../ui/Label.tsx'; +import { Button } from '../../ui/Button.tsx'; +import { Input } from '../../ui/Input.tsx'; +import { Label } from '../../ui/Label.tsx'; import { DialogClose, @@ -15,11 +12,13 @@ import { DialogFooter, DialogHeader, DialogTitle -} from '../ui/Dialog.tsx'; +} from '../../ui/Dialog.tsx'; + +import store from '~/store'; export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { - const dispatch = useDispatch(); - const { modelMap, initial } = useSelector(state => state.models); + const { newConversation } = store.useConversation(); + const [chatGptLabel, setChatGptLabel] = useState(''); const [promptPrefix, setPromptPrefix] = useState(''); const [saveText, setSaveText] = useState('Save'); @@ -34,12 +33,15 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { inputRef.current.focus(); return; } - dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); - dispatch(setModel('chatgptCustom')); + handleSaveState(chatGptLabel.toLowerCase()); + // Set new conversation - dispatch(setNewConvo()); - dispatch(setSubmission({})); + newConversation({ + model: 'chatgptCustom', + chatGptLabel, + promptPrefix + }); }; const saveHandler = e => { @@ -61,21 +63,25 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { setSaveText('Save'); }, 2500); - dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); - dispatch(setModel('chatgptCustom')); - // dispatch(setDisabled(false)); + // dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); + newConversation({ + model: 'chatgptCustom', + chatGptLabel, + promptPrefix + }); }; - if ( - chatGptLabel !== 'chatgptCustom' && - modelMap[chatGptLabel.toLowerCase()] && - !initial[chatGptLabel.toLowerCase()] && - saveText === 'Save' - ) { - setSaveText('Update'); - } else if (!modelMap[chatGptLabel.toLowerCase()] && saveText === 'Update') { - setSaveText('Save'); - } + // Commented by wtlyu + // if ( + // chatGptLabel !== 'chatgptCustom' && + // modelMap[chatGptLabel.toLowerCase()] && + // !initial[chatGptLabel.toLowerCase()] && + // saveText === 'Save' + // ) { + // setSaveText('Update'); + // } else if (!modelMap[chatGptLabel.toLowerCase()] && saveText === 'Update') { + // setSaveText('Save'); + // } const requiredProp = required ? { required: true } : {}; diff --git a/client/src/components/Models/ModelItem.jsx b/client/src/components/Input/Models/ModelItem.jsx similarity index 63% rename from client/src/components/Models/ModelItem.jsx rename to client/src/components/Input/Models/ModelItem.jsx index 2747e1ccca..b35cc3ece8 100644 --- a/client/src/components/Models/ModelItem.jsx +++ b/client/src/components/Input/Models/ModelItem.jsx @@ -1,36 +1,61 @@ import React, { useState, useRef } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { DropdownMenuRadioItem } from '../ui/DropdownMenu.tsx'; -import { setModels } from '~/store/modelSlice'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { DropdownMenuRadioItem } from '../../ui/DropdownMenu.tsx'; import { Circle } from 'lucide-react'; -import { DialogTrigger } from '../ui/Dialog.tsx'; -import RenameButton from '../Conversations/RenameButton'; -import TrashIcon from '../svg/TrashIcon'; +import { DialogTrigger } from '../../ui/Dialog.tsx'; +import RenameButton from '../../Conversations/RenameButton'; +import TrashIcon from '../../svg/TrashIcon'; import manualSWR from '~/utils/fetchers'; -import { getIconOfModel } from '../../utils'; +import { getIconOfModel } from '~/utils'; + +import store from '~/store'; + +export default function ModelItem({ model: _model, value, onSelect }) { + const { name, model, _id: id, chatGptLabel = null, promptPrefix = null } = _model; + const setCustomGPTModels = useSetRecoilState(store.customGPTModels); + const currentConversation = useRecoilValue(store.conversation) || {}; -export default function ModelItem({ modelName, value, model, onSelect, id, chatGptLabel, promptPrefix }) { - const dispatch = useDispatch(); - const { customModel } = useSelector((state) => state.submit); - const { initial } = useSelector((state) => state.models); const [isHovering, setIsHovering] = useState(false); const [renaming, setRenaming] = useState(false); - const [currentName, setCurrentName] = useState(modelName); - const [modelInput, setModelInput] = useState(modelName); + const [currentName, setCurrentName] = useState(name); + const [modelInput, setModelInput] = useState(name); const inputRef = useRef(null); - const rename = manualSWR(`/api/customGpts`, 'post'); - const deleteCustom = manualSWR(`/api/customGpts/delete`, 'post', (res) => { - const fetchedModels = res.data.map((modelItem) => ({ + const rename = manualSWR(`/api/customGpts`, 'post', res => {}); + const deleteCustom = manualSWR(`/api/customGpts/delete`, 'post', res => { + const fetchedModels = res.data.map(modelItem => ({ ...modelItem, - name: modelItem.chatGptLabel + name: modelItem.chatGptLabel, + model: 'chatgptCustom' })); - dispatch(setModels(fetchedModels)); + setCustomGPTModels(fetchedModels); }); - const icon = getIconOfModel({ size: 20, sender: modelName, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, className: "mr-2" }); + const icon = getIconOfModel({ + size: 20, + sender: chatGptLabel || model, + isCreatedByUser: false, + model, + chatGptLabel, + promptPrefix, + error: false, + className: 'mr-2' + }); - if (value === 'chatgptCustom') { + if (model !== 'chatgptCustom') + // regular model + return ( + + {icon} + {name} + {model === 'chatgpt' && $} + + ); + else if (model === 'chatgptCustom' && chatGptLabel === null && promptPrefix === null) + // base chatgptCustom model, click to add new chatgptCustom. return ( {icon} - {modelName} + {name} $ ); - } - if (initial[value]) - return ( - - {icon} - {modelName} - {value === 'chatgpt' && $} - - ); - - + // else: a chatgptCustom model const handleMouseOver = () => { setIsHovering(true); }; @@ -66,7 +78,7 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG setIsHovering(false); }; - const renameHandler = (e) => { + const renameHandler = e => { e.preventDefault(); e.stopPropagation(); setRenaming(true); @@ -75,10 +87,10 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG }, 25); }; - const onRename = (e) => { + const onRename = e => { e.preventDefault(); setRenaming(false); - if (modelInput === modelName) { + if (modelInput === name) { return; } rename.trigger({ @@ -89,13 +101,13 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG setCurrentName(modelInput); }; - const onDelete = async (e) => { + const onDelete = async e => { e.preventDefault(); await deleteCustom.trigger({ _id: id }); - onSelect('chatgpt', true); + onSelect('chatgpt'); }; - const handleKeyDown = (e) => { + const handleKeyDown = e => { if (e.key === 'Enter') { onRename(e); } @@ -115,14 +127,14 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG { + onClick={e => { if (isHovering) { return; } - onSelect(value, true); + onSelect('chatgptCustom', value); }} > - {customModel === value && ( + {currentConversation?.chatGptLabel === value && ( @@ -137,8 +149,8 @@ export default function ModelItem({ modelName, value, model, onSelect, id, chatG type="text" className="pointer-events-auto z-50 m-0 mr-2 w-3/4 border border-blue-500 bg-transparent p-0 text-sm leading-tight outline-none" value={modelInput} - onClick={(e) => e.stopPropagation()} - onChange={(e) => setModelInput(e.target.value)} + onClick={e => e.stopPropagation()} + onChange={e => setModelInput(e.target.value)} // onBlur={onRename} onKeyDown={handleKeyDown} /> diff --git a/client/src/components/Input/Models/ModelMenu.jsx b/client/src/components/Input/Models/ModelMenu.jsx new file mode 100644 index 0000000000..65dd7f777e --- /dev/null +++ b/client/src/components/Input/Models/ModelMenu.jsx @@ -0,0 +1,205 @@ +import React, { useState, useEffect } from 'react'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import axios from 'axios'; +import ModelDialog from './ModelDialog'; +import MenuItems from './MenuItems'; +import { swr } from '~/utils/fetchers'; +import { getIconOfModel } from '~/utils'; + +import { Button } from '../../ui/Button.tsx'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuSeparator, + DropdownMenuTrigger +} from '../../ui/DropdownMenu.tsx'; +import { Dialog } from '../../ui/Dialog.tsx'; + +import store from '~/store'; + +export default function ModelMenu() { + const [modelSave, setModelSave] = useState(false); + const [menuOpen, setMenuOpen] = useState(false); + + const models = useRecoilValue(store.models); + const availableModels = useRecoilValue(store.availableModels); + const setCustomGPTModels = useSetRecoilState(store.customGPTModels); + + const conversation = useRecoilValue(store.conversation) || {}; + const { model, promptPrefix, chatGptLabel, conversationId } = conversation; + const { newConversation } = store.useConversation(); + + // fetch the list of saved chatgptCustom + const { data, isLoading, mutate } = swr(`/api/customGpts`, res => { + const fetchedModels = res.map(modelItem => ({ + ...modelItem, + name: modelItem.chatGptLabel, + model: 'chatgptCustom' + })); + + setCustomGPTModels(fetchedModels); + }); + + // useEffect(() => { + // mutate(); + // try { + // const lastSelected = JSON.parse(localStorage.getItem('model')); + + // if (lastSelected === 'chatgptCustom') { + // return; + // } else if (initial[lastSelected]) { + // dispatch(setModel(lastSelected)); + // } + // } catch (err) { + // console.log(err); + // } + + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, []); + + // update the default model when availableModels changes + // typically, availableModels changes => modelsFilter or customGPTModels changes + useEffect(() => { + if (conversationId == 'new') { + newConversation(); + } + }, [availableModels]); + + // save selected model to localstoreage + useEffect(() => { + if (model) localStorage.setItem('model', JSON.stringify({ model, chatGptLabel, promptPrefix })); + }, [model]); + + // set the current model + const onChange = (newModel, value = null) => { + setMenuOpen(false); + + if (!newModel) { + return; + } else if (newModel === model && value === chatGptLabel) { + // bypass if not changed + return; + } else if (newModel === 'chatgptCustom' && value === null) { + // return; + } else if (newModel !== 'chatgptCustom') { + newConversation({ + model: newModel, + chatGptLabel: null, + promptPrefix: null + }); + } else if (newModel === 'chatgptCustom') { + const targetModel = models.find(element => element.value == value); + if (targetModel) { + const chatGptLabel = targetModel?.chatGptLabel; + const promptPrefix = targetModel?.promptPrefix; + newConversation({ + model: newModel, + chatGptLabel, + promptPrefix + }); + } + } + }; + + const onOpenChange = open => { + mutate(); + if (!open) { + setModelSave(false); + } + }; + + const handleSaveState = value => { + if (!modelSave) { + return; + } + + setCustomGPTModels(value); + setModelSave(false); + }; + + const defaultColorProps = [ + 'text-gray-500', + 'hover:bg-gray-100', + 'hover:bg-opacity-20', + 'disabled:hover:bg-transparent', + 'dark:data-[state=open]:bg-gray-800', + 'dark:hover:bg-opacity-20', + 'dark:hover:bg-gray-900', + 'dark:hover:text-gray-400', + 'dark:disabled:hover:bg-transparent' + ]; + + const chatgptColorProps = [ + 'text-green-700', + 'data-[state=open]:bg-green-100', + 'dark:text-emerald-300', + 'hover:bg-green-100', + 'disabled:hover:bg-transparent', + 'dark:data-[state=open]:bg-green-900', + 'dark:hover:bg-opacity-50', + 'dark:hover:bg-green-900', + 'dark:hover:text-gray-100', + 'dark:disabled:hover:bg-transparent' + ]; + + const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps; + const icon = getIconOfModel({ + size: 32, + sender: chatGptLabel || model, + isCreatedByUser: false, + model, + chatGptLabel, + promptPrefix, + error: false, + button: true + }); + + return ( + + + + + + event.preventDefault()} + > + Select a Model + + + {availableModels.length ? ( + + ) : ( + No model available. + )} + + + + + + ); +} diff --git a/client/src/components/Main/RowButton.jsx b/client/src/components/Input/RowButton.jsx similarity index 100% rename from client/src/components/Main/RowButton.jsx rename to client/src/components/Input/RowButton.jsx diff --git a/client/src/components/Main/SubmitButton.jsx b/client/src/components/Input/SubmitButton.jsx similarity index 72% rename from client/src/components/Main/SubmitButton.jsx rename to client/src/components/Input/SubmitButton.jsx index 20553a933f..a2d65574ca 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(); }; @@ -13,13 +9,23 @@ export default function SubmitButton({ submitMessage, disabled }) { if (isSubmitting) { return ( ); diff --git a/client/src/components/Input/index.jsx b/client/src/components/Input/index.jsx new file mode 100644 index 0000000000..9eac938c4c --- /dev/null +++ b/client/src/components/Input/index.jsx @@ -0,0 +1,201 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useRecoilValue, useRecoilState } from 'recoil'; +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 { useMessageHandler } from '../../utils/handleSubmit'; + +import store from '~/store'; + +export default function TextChat({ isSearchView = false }) { + 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(() => { + if (conversation?.conversationId !== 'search') 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 submitMessage = () => { + ask({ text }); + setText(''); + }; + + 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 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 && conversation?.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 new file mode 100644 index 0000000000..2a7d329be1 --- /dev/null +++ b/client/src/components/MessageHandler/index.jsx @@ -0,0 +1,274 @@ +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'; + +export default function MessageHandler({ messages }) { + const [submission, setSubmission] = useRecoilState(store.submission); + const [isSubmitting, setIsSubmitting] = useRecoilState(store.isSubmitting); + const setMessages = useSetRecoilState(store.messages); + const setConversation = useSetRecoilState(store.conversation); + const resetLatestMessage = useResetRecoilState(store.latestMessage); + + const { refreshConversations } = store.useConversations(); + + const messageHandler = (data, submission) => { + const { messages, message, initialResponse, isRegenerate = false } = submission; + + if (isRegenerate) + setMessages([ + ...messages, + { + ...initialResponse, + text: data, + parentMessageId: message?.overrideParentMessageId, + messageId: message?.overrideParentMessageId + '_', + submitting: true + } + ]); + else + setMessages([ + ...messages, + message, + { + ...initialResponse, + text: data, + parentMessageId: message?.messageId, + messageId: message?.messageId + '_', + submitting: true + } + ]); + }; + + const cancelHandler = (data, submission) => { + const { messages, message, initialResponse, isRegenerate = false } = submission; + + if (isRegenerate) + setMessages([ + ...messages, + { + ...initialResponse, + text: data, + parentMessageId: message?.overrideParentMessageId, + messageId: message?.overrideParentMessageId + '_', + cancelled: true + } + ]); + else + setMessages([ + ...messages, + message, + { + ...initialResponse, + text: data, + parentMessageId: message?.messageId, + messageId: message?.messageId + '_', + cancelled: true + } + ]); + }; + + const createdHandler = (data, submission) => { + const { messages, message, initialResponse, isRegenerate = false } = submission; + + if (isRegenerate) + setMessages([ + ...messages, + { + ...initialResponse, + parentMessageId: message?.overrideParentMessageId, + messageId: message?.overrideParentMessageId + '_', + submitting: true + } + ]); + else + setMessages([ + ...messages, + message, + { + ...initialResponse, + parentMessageId: message?.messageId, + messageId: message?.messageId + '_', + submitting: true + } + ]); + + const { conversationId } = message; + setConversation(prevState => ({ + ...prevState, + conversationId + })); + resetLatestMessage(); + }; + + const finalHandler = (data, submission) => { + const { conversation, messages, message, initialResponse, isRegenerate = false } = submission; + + const { requestMessage, responseMessage } = data; + const { conversationId } = requestMessage; + + // update the messages + if (isRegenerate) setMessages([...messages, responseMessage]); + else setMessages([...messages, requestMessage, responseMessage]); + setIsSubmitting(false); + + // refresh title + if (requestMessage.parentMessageId == '00000000-0000-0000-0000-000000000000') { + setTimeout(() => { + refreshConversations(); + }, 2000); + + // in case it takes too long. + setTimeout(() => { + refreshConversations(); + }, 5000); + } + + const { model, chatGptLabel, promptPrefix } = conversation; + const isBing = model === 'bingai' || model === 'sydney'; + + if (!isBing) { + const { title } = data; + const { conversationId } = responseMessage; + setConversation(prevState => ({ + ...prevState, + title, + conversationId, + jailbreakConversationId: null, + conversationSignature: null, + clientId: null, + invocationId: null, + chatGptLabel, + promptPrefix, + latestMessage: null + })); + } else if (model === 'bingai') { + const { title } = data; + const { conversationSignature, clientId, conversationId, invocationId } = responseMessage; + setConversation(prevState => ({ + ...prevState, + title, + conversationId, + jailbreakConversationId: null, + conversationSignature, + clientId, + invocationId, + chatGptLabel, + promptPrefix, + latestMessage: null + })); + } else if (model === 'sydney') { + const { title } = data; + const { + jailbreakConversationId, + parentMessageId, + conversationSignature, + clientId, + conversationId, + invocationId + } = responseMessage; + setConversation(prevState => ({ + ...prevState, + title, + conversationId, + jailbreakConversationId, + conversationSignature, + clientId, + invocationId, + chatGptLabel, + promptPrefix, + latestMessage: null + })); + } + }; + + const errorHandler = (data, submission) => { + const { conversation, messages, message, initialResponse, isRegenerate = false } = submission; + + console.log('Error:', data); + const errorResponse = { + ...data, + error: true, + parentMessageId: message?.messageId + }; + setIsSubmitting(false); + setMessages([...messages, message, errorResponse]); + return; + }; + + useEffect(() => { + if (submission === null) return; + if (Object.keys(submission).length === 0) return; + + const { messages, initialResponse, isRegenerate = false } = submission; + let { message } = submission; + + const { server, payload } = createPayload(submission); + + const events = new SSE(server, { + payload: JSON.stringify(payload), + headers: { 'Content-Type': 'application/json' } + }); + + let latestResponseText = ''; + events.onmessage = e => { + const data = JSON.parse(e.data); + + if (data.final) { + finalHandler(data, { ...submission, message }); + console.log('final', data); + } + if (data.created) { + message = { + ...data.message, + model: message?.model, + chatGptLabel: message?.chatGptLabel, + promptPrefix: message?.promptPrefix, + overrideParentMessageId: message?.overrideParentMessageId + }; + createdHandler(data, { ...submission, message }); + console.log('created', message); + } else { + let text = data.text || data.response; + if (data.initial) console.log(data); + + if (data.message) { + latestResponseText = text; + messageHandler(text, { ...submission, message }); + } + // console.log('dataStream', data); + } + }; + + events.onopen = () => console.log('connection is opened'); + + events.oncancel = e => cancelHandler(latestResponseText, { ...submission, message }); + + events.onerror = function (e) { + console.log('error in opening conn.'); + events.close(); + + const data = JSON.parse(e.data); + + errorHandler(data, { ...submission, message }); + }; + + setIsSubmitting(true); + events.stream(); + + return () => { + const isCancelled = events.readyState <= 1; + events.close(); + if (isCancelled) { + const e = new Event('cancel'); + events.dispatchEvent(e); + } + setIsSubmitting(false); + }; + }, [submission]); + + return null; +} diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index 7f4f32803f..fd4b7ef02d 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,34 @@ 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 { + 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; 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 +58,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]); @@ -79,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 }); @@ -92,7 +93,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 = () => { @@ -110,22 +111,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)); - dispatch(setConversation(convo)); - const { data } = await fetchById('messages', message.conversationId); - dispatch(setMessages(data)); - dispatch(setDisabled(false)); + switchToConversation(convo); }; return ( @@ -133,7 +122,6 @@ export default function Message({
@@ -153,7 +141,7 @@ export default function Message({
{searchResult && ( @@ -199,17 +187,13 @@ export default function Message({
{/*
*/}
- {!isCreatedByUser ? - <> - - {generateCursor()} - : - <> - {text} - - } + {!isCreatedByUser ? ( + <> + + + ) : ( + <>{text} + )}
)} @@ -230,8 +214,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]; + if (isSearchView) + return ( + <> + {messagesTree + ? messagesTree.map(message => ( + + )) + : null} + + ); return ( ); diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index 9027e851e3..ae11d090ad 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -1,27 +1,37 @@ import React, { useEffect, useState, useRef, useCallback } from 'react'; +import { useRecoilValue } 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({ isSearchView = false }) { 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 [showScrollButton, setShowScrollButton] = useState(false); const scrollableRef = useRef(null); const messagesEndRef = useRef(null); - const modelName = models.find((element) => element.model == model)?.name; + 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; 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 +43,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 +74,7 @@ export default function Messages({ messages, messageTree }) { timeoutId = setTimeout(handleScroll, 100); }; - const scrollHandler = (e) => { + const scrollHandler = e => { e.preventDefault(); scrollToBottom(); }; @@ -68,23 +85,29 @@ export default function Messages({ messages, messageTree }) { ref={scrollableRef} onScroll={debouncedHandleScroll} > - {/*
*/}
- Model: {modelName} {customModel ? `(${customModel})` : null} + {isSearchView + ? `Search: ${searchQuery}` + : `Model: ${modelName} ${chatGptLabel ? `(${chatGptLabel})` : ''}`}
- {(messageTree.length === 0 || !messages) ? ( + {_messagesTree === null ? ( + ) : _messagesTree?.length == 0 && isSearchView ? ( +
+ Nothing found +
) : ( <>
- {/*
*/}
); } 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/Nav/ClearConvos.jsx b/client/src/components/Nav/ClearConvos.jsx index 9f019765dc..3cf6291c48 100644 --- a/client/src/components/Nav/ClearConvos.jsx +++ b/client/src/components/Nav/ClearConvos.jsx @@ -1,51 +1,47 @@ import React from 'react'; +import store from '~/store'; import TrashIcon from '../svg/TrashIcon'; import { useSWRConfig } from 'swr'; import manualSWR from '~/utils/fetchers'; -import { useDispatch } from 'react-redux'; -import { setNewConvo, removeAll } from '~/store/convoSlice'; -import { setMessages } from '~/store/messageSlice'; -import { setSubmission } from '~/store/submitSlice'; import { Dialog, DialogTrigger } from '../ui/Dialog.tsx'; import DialogTemplate from '../ui/DialogTemplate'; export default function ClearConvos() { - const dispatch = useDispatch(); + const { newConversation } = store.useConversation(); + const { refreshConversations } = store.useConversations(); const { mutate } = useSWRConfig(); const { trigger } = manualSWR(`/api/convos/clear`, 'post', () => { - dispatch(setMessages([])); - dispatch(setNewConvo()); - dispatch(setSubmission({})); + newConversation(); + refreshConversations(); mutate(`/api/convos`); }); const clickHandler = () => { console.log('Clearing conversations...'); - dispatch(removeAll()); trigger({}); }; return ( - - - Clear conversations - - - + + + Clear conversations + + + ); } diff --git a/client/src/components/Nav/Logout.jsx b/client/src/components/Nav/Logout.jsx index 637c6c57fe..515d6fd1ab 100644 --- a/client/src/components/Nav/Logout.jsx +++ b/client/src/components/Nav/Logout.jsx @@ -1,13 +1,13 @@ -import React, { useState, useContext } from 'react'; -import { useSelector } from 'react-redux'; +import React from 'react'; import LogOutIcon from '../svg/LogOutIcon'; - +import { useRecoilValue } from 'recoil'; +import store from '~/store'; export default function Logout() { - const { user } = useSelector((state) => state.user); - + const user = useRecoilValue(store.user); + const clickHandler = () => { - window.location.href = "/auth/logout"; + window.location.href = '/auth/logout'; }; return ( diff --git a/client/src/components/Nav/MobileNav.jsx b/client/src/components/Nav/MobileNav.jsx index 558c88f1a4..25c5afbdec 100644 --- a/client/src/components/Nav/MobileNav.jsx +++ b/client/src/components/Nav/MobileNav.jsx @@ -1,33 +1,19 @@ import React from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { setNewConvo } from '~/store/convoSlice'; -import { setMessages } from '~/store/messageSlice'; -import { setSubmission } from '~/store/submitSlice'; -import { setText } from '~/store/textSlice'; +import { useRecoilValue } from 'recoil'; + +import store from '~/store'; export default function MobileNav({ setNavVisible }) { - const dispatch = useDispatch(); - const { conversationId, convos, title } = useSelector((state) => state.convo); - - const toggleNavVisible = () => { - setNavVisible((prev) => { - return !prev - }) - } - - const newConvo = () => { - dispatch(setText('')); - dispatch(setMessages([])); - dispatch(setNewConvo()); - dispatch(setSubmission({})); - } + const conversation = useRecoilValue(store.conversation); + const { newConversation } = store.useConversation(); + const { title = 'New Chat' } = conversation || {}; return (