diff --git a/README.md b/README.md index c32fc798f4..d0ca06a9ca 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,23 @@ https://user-images.githubusercontent.com/110412045/223754183-8b7f45ce-6517-4bd5 ## Updates
+2023-03-09 +Released v.0.0.2 + +Adds Sydney (jailbroken Bing AI) to the model menu. Thank you [DavesDevFails](https://github.com/DavesDevFails) for bringing it to my attention in this [issue](https://github.com/danny-avila/chatgpt-clone/issues/13). Bing/Sydney now correctly cite links, more styling to come. Fix some overlooked bugs, and model menu doesn't close upon deleting a customGpt. + + +I've re-enabled the ChatGPT browser client (free version) since it might be working for most people, it no longer works for me. Sydney is the best free route anyway. +
+ +
+
2023-03-07 Due to increased interest in the repo, I've dockerized the app as of this update for quick setup! See setup instructions below. I realize this still takes some time with installing docker dependencies, so it's on the roadmap to have a deployed demo. Besides this, I've made major improvements for a lot of the existing features across the board, mainly UI/UX. Also worth noting, the method to access the Free Version is no longer working, so I've removed it from model selection until further notice.
- -
Previous Updates
@@ -80,12 +89,12 @@ Here are my recently completed and planned features: - [x] Customize prompt prefix/label (custom ChatGPT using official API) - [x] Server convo pagination (limit fetch and load more with 'show more' button) - [x] Config file for easy startup (docker compose) +- [ ] Bing AI Styling (for suggested responses, convo end, etc.) - **In progress** - [ ] Add warning before clearing convos - [ ] Build test suite for CI/CD - [ ] Conversation Search (by title) - [ ] Resubmit/edit sent messages - [ ] Semantic Search Option (requires more tokens) -- [ ] Bing AI Styling (for suggested responses, convo end, etc.) - [ ] Prompt Templates/Search - [ ] Refactor/clean up code (tech debt) - [ ] Optional use of local storage for credentials @@ -172,7 +181,7 @@ The Bing Access Token is the "_U" cookie from bing.com. Use dev tools or an exte
### Updating -- As the project is still a work-in-progress, you should pull the latest and run some of the steps above again +- As the project is still a work-in-progress, you should pull the latest and run the steps over. Reset your browser cache/clear site data. ## Use Cases ## diff --git a/api/app/bingai.js b/api/app/bingai.js index e33278b9a2..4dfa8a71d5 100644 --- a/api/app/bingai.js +++ b/api/app/bingai.js @@ -10,7 +10,7 @@ const askBing = async ({ text, progressCallback, convo }) => { // If the above doesn't work, provide all your cookies as a string instead // cookies: '', debug: false, - store: new KeyvFile({ filename: './data/cache.json' }) + cache: { store: new KeyvFile({ filename: './data/cache.json' }) } }); let options = { diff --git a/api/app/chatgpt-browser.js b/api/app/chatgpt-browser.js index c6debc0d91..442d2a731d 100644 --- a/api/app/chatgpt-browser.js +++ b/api/app/chatgpt-browser.js @@ -5,7 +5,8 @@ const clientOptions = { // Warning: This will expose your access token to a third party. Consider the risks before using this. reverseProxyUrl: 'https://chatgpt.duti.tech/api/conversation', // Access token from https://chat.openai.com/api/auth/session - accessToken: process.env.CHATGPT_TOKEN + accessToken: process.env.CHATGPT_TOKEN, + // debug: true }; const browserClient = async ({ text, progressCallback, convo }) => { diff --git a/api/app/citeText.js b/api/app/citeText.js new file mode 100644 index 0000000000..536c981d4e --- /dev/null +++ b/api/app/citeText.js @@ -0,0 +1,29 @@ +const citationRegex = /\[\^\d+?\^]/g; + +const citeText = (res, noLinks = false) => { + let result = res.text || res; + const citations = Array.from(new Set(result.match(citationRegex))); + if (citations?.length === 0) return result; + + if (noLinks) { + citations.forEach((citation) => { + const digit = citation.match(/\d+?/g)[0]; + result = result.replaceAll(citation, `[${digit}](#) `); + }); + + return result; + } + + let sources = res.details.sourceAttributions; + if (sources?.length === 0) return result; + sources = sources.map((source) => source.seeMoreUrl); + + citations.forEach((citation) => { + const digit = citation.match(/\d+?/g)[0]; + result = result.replaceAll(citation, `[${digit}](${sources[digit - 1]}) `); + }); + + return result; +}; + +module.exports = citeText; diff --git a/api/app/getCitations.js b/api/app/getCitations.js new file mode 100644 index 0000000000..f4086a4e9e --- /dev/null +++ b/api/app/getCitations.js @@ -0,0 +1,13 @@ +// const regex = / \[\d+\..*?\]\(.*?\)/g; +const regex = / \[.*?]\(.*?\)/g; + +const getCitations = (res) => { + const textBlocks = res.details.adaptiveCards[0].body; + if (!textBlocks) return ''; + let links = textBlocks[textBlocks.length - 1]?.text.match(regex); + if (links?.length === 0 || !links) return ''; + links = links.map((link) => link.trim()); + return links.join('\n'); +}; + +module.exports = getCitations; \ No newline at end of file diff --git a/api/app/index.js b/api/app/index.js index 53d4d594b9..7b61f8de95 100644 --- a/api/app/index.js +++ b/api/app/index.js @@ -2,7 +2,10 @@ const { askClient } = require('./chatgpt-client'); const { browserClient } = require('./chatgpt-browser'); const customClient = require('./chatgpt-custom'); const { askBing } = require('./bingai'); +const { askSydney } = require('./sydney'); const titleConvo = require('./titleConvo'); +const getCitations = require('./getCitations'); +const citeText = require('./citeText'); const detectCode = require('./detectCode'); module.exports = { @@ -10,6 +13,9 @@ module.exports = { browserClient, customClient, askBing, + askSydney, titleConvo, + getCitations, + citeText, detectCode }; \ No newline at end of file diff --git a/api/app/sydney.js b/api/app/sydney.js new file mode 100644 index 0000000000..fe47c74f57 --- /dev/null +++ b/api/app/sydney.js @@ -0,0 +1,36 @@ +require('dotenv').config(); +const { KeyvFile } = require('keyv-file'); + +const askSydney = async ({ text, progressCallback, convo }) => { + const { BingAIClient } = (await import('@waylaidwanderer/chatgpt-api')); + + const sydneyClient = new BingAIClient({ + // "_U" cookie from bing.com + userToken: process.env.BING_TOKEN, + // If the above doesn't work, provide all your cookies as a string instead + // cookies: '', + debug: false, + cache: { store: new KeyvFile({ filename: './data/cache.json' }) } + }); + + let options = { + jailbreakConversationId: true, + onProgress: async (partialRes) => await progressCallback(partialRes), + }; + + if (convo.parentMessageId) { + options = { ...options, jailbreakConversationId: convo.jailbreakConversationId, parentMessageId: convo.parentMessageId }; + } + + console.log('sydney options', options); + + const res = await sydneyClient.sendMessage(text, options + ); + + return res; + + // for reference: + // https://github.com/waylaidwanderer/node-chatgpt-api/blob/main/demos/use-bing-client.js +}; + +module.exports = { askSydney }; diff --git a/api/models/Conversation.js b/api/models/Conversation.js index dfe4761a32..1ff778e957 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -15,6 +15,9 @@ const convoSchema = mongoose.Schema({ type: String, default: 'New conversation' }, + jailbreakConversationId: { + type: String + }, conversationSignature: { type: String }, @@ -44,6 +47,15 @@ const convoSchema = mongoose.Schema({ const Conversation = mongoose.models.Conversation || mongoose.model('Conversation', convoSchema); +const getConvo = async (conversationId) => { + try { + return await Conversation.findOne({ conversationId }).exec(); + } catch (error) { + console.log(error); + return { message: 'Error getting single conversation' }; + } +}; + module.exports = { saveConvo: async ({ conversationId, title, ...convo }) => { try { @@ -92,12 +104,14 @@ module.exports = { return { message: 'Error getting conversations' }; } }, - getConvo: async (conversationId) => { + getConvo, + getConvoTitle: async (conversationId) => { try { - return await Conversation.findOne({ conversationId }).exec(); + const convo = await getConvo(conversationId); + return convo.title; } catch (error) { console.log(error); - return { message: 'Error getting single conversation' }; + return { message: 'Error getting conversation title' }; } }, deleteConvos: async (filter) => { diff --git a/api/models/index.js b/api/models/index.js index 0b195d7248..8af5a1aa9c 100644 --- a/api/models/index.js +++ b/api/models/index.js @@ -1,10 +1,11 @@ const { saveMessage, deleteMessages } = require('./Message'); const { getCustomGpts, updateCustomGpt, updateByLabel, deleteCustomGpts } = require('./CustomGpt'); -const { getConvo, saveConvo } = require('./Conversation'); +const { getConvoTitle, getConvo, saveConvo } = require('./Conversation'); module.exports = { saveMessage, deleteMessages, + getConvoTitle, getConvo, saveConvo, getCustomGpts, diff --git a/api/package-lock.json b/api/package-lock.json index 9a247820c1..911fda953e 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@keyv/mongo": "^2.1.8", "@vscode/vscode-languagedetection": "^1.0.22", - "@waylaidwanderer/chatgpt-api": "^1.15.1", + "@waylaidwanderer/chatgpt-api": "^1.28.2", "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", @@ -1492,9 +1492,9 @@ } }, "node_modules/@waylaidwanderer/chatgpt-api": { - "version": "1.26.1", - "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.26.1.tgz", - "integrity": "sha512-cv9NqC0owO2EGCkVg4VQO0lcA5pDgv2VJrBE/0P6En27/v0gIC+7MedowX3htIUi4GLDkgyyDDDimst2i8ReMw==", + "version": "1.28.2", + "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.28.2.tgz", + "integrity": "sha512-efNvZr8uosiYD69zFq50OPM36s+tyRMixlHpwDzn2q9UuZrdHC++kmm23OAnDxv3/+vA4UwCsZXn+92c35NHBQ==", "dependencies": { "@dqbd/tiktoken": "^0.4.0", "@fastify/cors": "^8.2.0", @@ -5781,9 +5781,9 @@ "integrity": "sha512-rQ/BgMyLuIXSmbA0MSkIPHtcOw14QkeDbAq19sjvaS9LTRr905yij0S8lsyqN5JgOsbtIx7pAcyOxFMzPmqhZQ==" }, "@waylaidwanderer/chatgpt-api": { - "version": "1.26.1", - "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.26.1.tgz", - "integrity": "sha512-cv9NqC0owO2EGCkVg4VQO0lcA5pDgv2VJrBE/0P6En27/v0gIC+7MedowX3htIUi4GLDkgyyDDDimst2i8ReMw==", + "version": "1.28.2", + "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.28.2.tgz", + "integrity": "sha512-efNvZr8uosiYD69zFq50OPM36s+tyRMixlHpwDzn2q9UuZrdHC++kmm23OAnDxv3/+vA4UwCsZXn+92c35NHBQ==", "requires": { "@dqbd/tiktoken": "^0.4.0", "@fastify/cors": "^8.2.0", diff --git a/api/package.json b/api/package.json index 6e38f1f117..28bd6769d3 100644 --- a/api/package.json +++ b/api/package.json @@ -21,7 +21,7 @@ "dependencies": { "@keyv/mongo": "^2.1.8", "@vscode/vscode-languagedetection": "^1.0.22", - "@waylaidwanderer/chatgpt-api": "^1.15.1", + "@waylaidwanderer/chatgpt-api": "^1.28.2", "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index ea515c6c75..23ed20ba85 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -2,6 +2,7 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); const askBing = require('./askBing'); +const askSydney = require('./askSydney'); const { titleConvo, askClient, @@ -13,6 +14,7 @@ const { getConvo, saveMessage, deleteMessages, saveConvo } = require('../../mode const { handleError, sendMessage } = require('./handlers'); router.use('/bing', askBing); +router.use('/sydney', askSydney); router.post('/', async (req, res) => { let { model, text, parentMessageId, conversationId, chatGptLabel, promptPrefix } = req.body; diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 3787e3fb9e..42718b9d91 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -1,9 +1,10 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); -const { titleConvo, askBing } = require('../../app/'); +const { titleConvo, getCitations, citeText, askBing } = require('../../app/'); const { saveMessage, deleteMessages, saveConvo } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); +const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { const { model, text, ...convo } = req.body; @@ -29,6 +30,7 @@ router.post('/', async (req, res) => { const progressCallback = async (partial) => { tokens += partial === text ? '' : partial; // tokens = appendCode(tokens); + tokens = citeText(tokens, true); sendMessage(res, { text: tokens, message: true }); }; @@ -38,8 +40,9 @@ router.post('/', async (req, res) => { convo }); - console.log('CLIENT RESPONSE'); - console.dir(response, { depth: null }); + console.log('BING RESPONSE'); + // console.dir(response, { depth: null }); + const hasCitations = response.response.match(citationRegex)?.length > 0; userMessage.conversationSignature = convo.conversationSignature || response.conversationSignature; @@ -48,16 +51,27 @@ router.post('/', async (req, res) => { await saveMessage(userMessage); if (!convo.conversationSignature) { - response.title = await titleConvo(text, response.response, model); + response.title = await titleConvo({ + model, + message: text, + response: JSON.stringify(response.response) + }); } response.text = response.response; + delete response.response; response.id = response.details.messageId; response.suggestions = response.details.suggestedResponses && response.details.suggestedResponses.map((s) => s.text); response.sender = model; response.final = true; + + const links = getCitations(response); + response.text = + citeText(response) + + (links?.length > 0 && hasCitations ? `\n${links}` : ''); + await saveMessage(response); await saveConvo(response); sendMessage(res, response); diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js new file mode 100644 index 0000000000..149b8b1c45 --- /dev/null +++ b/api/server/routes/askSydney.js @@ -0,0 +1,96 @@ +const express = require('express'); +const crypto = require('crypto'); +const router = express.Router(); +const { titleConvo, getCitations, citeText, askSydney } = require('../../app/'); +const { saveMessage, deleteMessages, saveConvo, getConvoTitle } = require('../../models'); +const { handleError, sendMessage } = require('./handlers'); +const citationRegex = /\[\^\d+?\^]/g; + +router.post('/', async (req, res) => { + const { model, text, ...convo } = req.body; + if (!text.trim().includes(' ') && text.length < 5) { + return handleError(res, 'Prompt empty or too short'); + } + + const userMessageId = crypto.randomUUID(); + let userMessage = { id: userMessageId, sender: 'User', text }; + + console.log('ask log', { model, ...userMessage, ...convo }); + + res.writeHead(200, { + Connection: 'keep-alive', + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache, no-transform', + 'Access-Control-Allow-Origin': '*', + 'X-Accel-Buffering': 'no' + }); + + try { + let tokens = ''; + const progressCallback = async (partial) => { + tokens += partial === text ? '' : partial; + // tokens = appendCode(tokens); + tokens = citeText(tokens, true); + sendMessage(res, { text: tokens, message: true }); + }; + + let response = await askSydney({ + text, + progressCallback, + convo + }); + + console.log('SYDNEY RESPONSE'); + console.log(response.response); + // console.dir(response, { depth: null }); + const hasCitations = response.response.match(citationRegex)?.length > 0; + + // Save sydney response + response.id = response.messageId; + // response.parentMessageId = convo.parentMessageId ? convo.parentMessageId : response.messageId; + response.parentMessageId = response.messageId; + response.invocationId = convo.invocationId ? convo.invocationId + 1 : 1; + response.title = convo.jailbreakConversationId + ? await getConvoTitle(convo.conversationId) + : await titleConvo({ + model, + message: text, + response: JSON.stringify(response.response) + }); + response.conversationId = convo.conversationId + ? convo.conversationId + : crypto.randomUUID(); + response.conversationSignature = convo.conversationSignature + ? convo.conversationSignature + : crypto.randomUUID(); + response.text = response.response; + delete response.response; + response.suggestions = + response.details.suggestedResponses && + response.details.suggestedResponses.map((s) => s.text); + response.sender = model; + response.final = true; + + const links = getCitations(response); + response.text = + citeText(response) + + (links?.length > 0 && hasCitations ? `\n${links}` : ''); + + // Save user message + userMessage.conversationId = response.conversationId; + userMessage.parentMessageId = response.parentMessageId; + await saveMessage(userMessage); + + // Save sydney response & convo, then send + await saveMessage(response); + await saveConvo(response); + sendMessage(res, response); + res.end(); + } catch (error) { + console.log(error); + await deleteMessages({ id: userMessageId }); + handleError(res, error.message); + } +}); + +module.exports = router; diff --git a/client/src/components/Conversations/Conversation.jsx b/client/src/components/Conversations/Conversation.jsx index a887867c8b..745671e1cc 100644 --- a/client/src/components/Conversations/Conversation.jsx +++ b/client/src/components/Conversations/Conversation.jsx @@ -16,7 +16,7 @@ export default function Conversation({ title = 'New conversation', bingData, chatGptLabel = null, - promptPrefix = null, + promptPrefix = null }) { const [renaming, setRenaming] = useState(false); const [titleInput, setTitleInput] = useState(title); @@ -34,11 +34,18 @@ export default function Conversation({ const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix }; if (bingData) { - const { conversationSignature, clientId, invocationId } = bingData; + const { + parentMessageId, + conversationSignature, + jailbreakConversationId, + clientId, + invocationId + } = bingData; dispatch( setConversation({ ...convo, - parentMessageId: null, + parentMessageId, + jailbreakConversationId, conversationSignature, clientId, invocationId @@ -49,6 +56,7 @@ export default function Conversation({ setConversation({ ...convo, parentMessageId, + jailbreakConversationId: null, conversationSignature: null, clientId: null, invocationId: null diff --git a/client/src/components/Conversations/DeleteButton.jsx b/client/src/components/Conversations/DeleteButton.jsx index 6dff5682fe..03c9f47f81 100644 --- a/client/src/components/Conversations/DeleteButton.jsx +++ b/client/src/components/Conversations/DeleteButton.jsx @@ -3,7 +3,7 @@ import TrashIcon from '../svg/TrashIcon'; import CrossIcon from '../svg/CrossIcon'; import manualSWR from '~/utils/fetchers'; import { useDispatch } from 'react-redux'; -import { setConversation, removeConvo } from '~/store/convoSlice'; +import { setNewConvo, removeConvo } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; export default function DeleteButton({ conversationId, renaming, cancelHandler }) { @@ -14,7 +14,7 @@ export default function DeleteButton({ conversationId, renaming, cancelHandler } () => { dispatch(setMessages([])); dispatch(removeConvo(conversationId)); - dispatch(setConversation({ title: 'New chat', conversationId: null, parentMessageId: null })); + dispatch(setNewConvo()); } ); diff --git a/client/src/components/Conversations/index.jsx b/client/src/components/Conversations/index.jsx index f265a97483..13b1e64b00 100644 --- a/client/src/components/Conversations/index.jsx +++ b/client/src/components/Conversations/index.jsx @@ -14,7 +14,9 @@ export default function Conversations({ conversations, conversationId, showMore conversations.map((convo) => { const bingData = convo.conversationSignature ? { + jailbreakConversationId: convo.jailbreakConversationId, conversationSignature: convo.conversationSignature, + parentMessageId: convo.parentMessageId || null, clientId: convo.clientId, invocationId: convo.invocationId } diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 34c49750b9..9cf474357b 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -46,8 +46,10 @@ export default function TextChat({ messages }) { setMessages([...messages, currentMsg, { sender, text: data.text || data.response }]) ); + const isBing = model === 'bingai' || model === 'sydney'; + if ( - model !== 'bingai' && + !isBing && convo.conversationId === null && convo.parentMessageId === null ) { @@ -57,6 +59,7 @@ export default function TextChat({ messages }) { title, conversationId, parentMessageId: id, + jailbreakConversationId: null, conversationSignature: null, clientId: null, invocationId: null, @@ -69,15 +72,43 @@ export default function TextChat({ messages }) { convo.conversationId === null && convo.invocationId === null ) { - const { title, conversationSignature, clientId, conversationId, invocationId } = data; + console.log('Bing data:', data) + const { + title, + conversationSignature, + clientId, + conversationId, + invocationId + } = data; dispatch( setConversation({ title, + parentMessageId: null, + conversationSignature, + clientId, + conversationId, + invocationId, + }) + ); + } else if (model === 'sydney') { + const { + title, + jailbreakConversationId, + parentMessageId, + conversationSignature, + clientId, + conversationId, + invocationId + } = data; + dispatch( + setConversation({ + title, + jailbreakConversationId, + parentMessageId, conversationSignature, clientId, conversationId, invocationId, - parentMessageId: null }) ); } @@ -85,24 +116,6 @@ export default function TextChat({ messages }) { dispatch(setSubmitState(false)); }; - // const convoHandler = (data) => { - // const { conversationId, id, invocationId } = data; - // const conversationData = { - // title: data.title, - // conversationId, - // parentMessageId: - // model !== 'bingai' && !convo.conversationId && !convo.parentMessageId ? id : null, - // conversationSignature: - // model === 'bingai' && !convo.conversationId ? data.conversationSignature : null, - // clientId: model === 'bingai' && !convo.conversationId ? data.clientId : null, - // // invocationId: model === 'bingai' && !convo.conversationId ? data.invocationId : null - // invocationId: invocationId ? invocationId : null - // }; - // dispatch(setMessages([...messages, currentMsg, { sender: model, text: data.text || data.response }])); - // dispatch(setConversation(conversationData)); - // dispatch(setSubmitState(false)); - // }; - const errorHandler = (event) => { console.log('Error:', event); const errorResponse = { diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index b713df48ec..9413f21856 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -48,9 +48,12 @@ export default function Message({ const bgColors = { chatgpt: 'rgb(16, 163, 127)', chatgptBrowser: 'rgb(25, 207, 207)', - bingai: '' + bingai: '', + sydney: '', }; + const isBing = sender === 'bingai' || sender === 'sydney'; + let icon = `${sender}:`; let backgroundColor = bgColors[sender]; @@ -59,13 +62,13 @@ export default function Message({ 'w-full border-b border-black/10 bg-gray-50 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-[#444654]'; } - if ((notUser && backgroundColor) || sender === 'bingai') { + if ((notUser && backgroundColor) || isBing) { icon = (
- {sender === 'bingai' ? : } + {isBing ? : } {error && ( ! diff --git a/client/src/components/Models/ModelDialog.jsx b/client/src/components/Models/ModelDialog.jsx index 93005976e3..47592d8a93 100644 --- a/client/src/components/Models/ModelDialog.jsx +++ b/client/src/components/Models/ModelDialog.jsx @@ -2,6 +2,7 @@ import React, { useState, useRef } from 'react'; import TextareaAutosize from 'react-textarea-autosize'; import { useSelector, useDispatch } from 'react-redux'; import { 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'; @@ -36,7 +37,8 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); dispatch(setModel('chatgptCustom')); handleSaveState(chatGptLabel.toLowerCase()); - // dispatch(setDisabled(false)); + // Set new conversation + dispatch(setNewConvo()); }; const saveHandler = (e) => { diff --git a/client/src/components/Models/ModelMenu.jsx b/client/src/components/Models/ModelMenu.jsx index 80b5dd9dc3..607b48921f 100644 --- a/client/src/components/Models/ModelMenu.jsx +++ b/client/src/components/Models/ModelMenu.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { setModel, setDisabled, setCustomGpt, setCustomModel } from '~/store/submitSlice'; -import { setConversation } from '~/store/convoSlice'; +import { setNewConvo } from '~/store/convoSlice'; import ModelDialog from './ModelDialog'; import MenuItems from './MenuItems'; import manualSWR from '~/utils/fetchers'; @@ -68,22 +68,15 @@ export default function ModelMenu() { dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); dispatch(setModel('chatgptCustom')); dispatch(setCustomModel(value)); - if (custom) { - setMenuOpen((prevOpen) => !prevOpen); - } + // if (custom) { + // setMenuOpen((prevOpen) => !prevOpen); + // } } else if (!modelMap[value]) { dispatch(setCustomModel(null)); } // Set new conversation - dispatch( - setConversation({ - title: 'New Chat', - error: false, - conversationId: null, - parentMessageId: null - }) - ); + dispatch(setNewConvo()); }; const onOpenChange = (open) => { @@ -126,8 +119,9 @@ export default function ModelMenu() { 'dark:disabled:hover:bg-transparent' ]; + const isBing = model === 'bingai' || model === 'sydney'; const colorProps = model === 'chatgpt' ? chatgptColorProps : defaultColorProps; - const icon = model === 'bingai' ? : ; + const icon = isBing ? : ; return ( diff --git a/client/src/components/Nav/ClearConvos.jsx b/client/src/components/Nav/ClearConvos.jsx index 25762aa2f2..d262226bb9 100644 --- a/client/src/components/Nav/ClearConvos.jsx +++ b/client/src/components/Nav/ClearConvos.jsx @@ -3,7 +3,7 @@ import TrashIcon from '../svg/TrashIcon'; import { useSWRConfig } from 'swr'; import manualSWR from '~/utils/fetchers'; import { useDispatch } from 'react-redux'; -import { setConversation, removeAll } from '~/store/convoSlice'; +import { setNewConvo, removeAll } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; export default function ClearConvos() { @@ -12,14 +12,7 @@ export default function ClearConvos() { const { trigger } = manualSWR(`http://localhost:3080/api/convos/clear`, 'post', () => { dispatch(setMessages([])); - dispatch( - setConversation({ - error: false, - title: 'New chat', - conversationId: null, - parentMessageId: null - }) - ); + dispatch(setNewConvo()); mutate(`http://localhost:3080/api/convos`); }); diff --git a/client/src/components/Nav/NewChat.jsx b/client/src/components/Nav/NewChat.jsx index 8535995b13..540c023647 100644 --- a/client/src/components/Nav/NewChat.jsx +++ b/client/src/components/Nav/NewChat.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { useDispatch } from 'react-redux'; -import { setConversation } from '~/store/convoSlice'; +import { setNewConvo } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; import { setText } from '~/store/textSlice'; @@ -10,7 +10,7 @@ export default function NewChat() { const clickHandler = () => { dispatch(setText('')); dispatch(setMessages([])); - dispatch(setConversation({ title: 'New Chat', error: false, conversationId: null, parentMessageId: null })); + dispatch(setNewConvo()); }; return ( diff --git a/client/src/store/convoSlice.js b/client/src/store/convoSlice.js index d2294e5bba..2560a29298 100644 --- a/client/src/store/convoSlice.js +++ b/client/src/store/convoSlice.js @@ -5,6 +5,7 @@ const initialState = { title: 'ChatGPT Clone', conversationId: null, parentMessageId: null, + jailbreakConversationId: null, conversationSignature: null, clientId: null, invocationId: null, @@ -28,6 +29,20 @@ const currentSlice = createSlice({ incrementPage: (state) => { state.pageNumber = state.pageNumber + 1; }, + setNewConvo: (state) => { + state.error = false; + state.title = 'New Chat'; + state.conversationId = null; + state.parentMessageId = null; + state.jailbreakConversationId = null; + state.conversationSignature = null; + state.clientId = null; + state.invocationId = null; + state.chatGptLabel = null; + state.promptPrefix = null; + state.convosLoading = false; + state.pageNumber = 1; + }, setConvos: (state, action) => { const newConvos = action.payload.filter((convo) => { return !state.convos.some((c) => c.conversationId === convo.conversationId); @@ -45,7 +60,7 @@ const currentSlice = createSlice({ } }); -export const { setConversation, setConvos, setError, incrementPage, removeConvo, removeAll } = +export const { setConversation, setConvos, setNewConvo, setError, incrementPage, removeConvo, removeAll } = currentSlice.actions; export default currentSlice.reducer; diff --git a/client/src/store/modelSlice.js b/client/src/store/modelSlice.js index 801f0955d8..bc34673bfc 100644 --- a/client/src/store/modelSlice.js +++ b/client/src/store/modelSlice.js @@ -16,16 +16,21 @@ const initialState = { _id: '2', name: 'BingAI', value: 'bingai' - } - // { - // _id: '3', - // name: 'ChatGPT', - // value: 'chatgptBrowser' - // } + }, + { + _id: '3', + name: 'Sydney', + value: 'sydney' + }, + { + _id: '4', + name: 'ChatGPT', + value: 'chatgptBrowser' + }, ], modelMap: {}, - // initial: { chatgpt: true, chatgptCustom: true, bingai: true, chatgptBrowser: true } - initial: { chatgpt: true, chatgptCustom: true, bingai: true, } + initial: { chatgpt: true, chatgptCustom: true, bingai: true, sydney: true, chatgptBrowser: true } + // initial: { chatgpt: true, chatgptCustom: true, bingai: true, } }; const currentSlice = createSlice({ diff --git a/client/src/style.css b/client/src/style.css index 7fd5ad79b6..6b17c388e2 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -7,6 +7,32 @@ outline: 1px solid limegreen !important; } */ +/* p small { + opacity: 0; + animation: fadeIn 3s ease forwards; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} */ + +p > small { + opacity: 0; + animation: fadein 3s forwards; +} + +@keyframes fadein { + from { opacity: 0; transform: translateY(-20px); } + to { opacity: 1; transform: translateY(0); } +} + blockquote, dd, dl, fieldset, figure, h1, h2, h3, h4, h5, h6, hr, p, pre { margin: 0; } diff --git a/client/src/utils/handleSubmit.js b/client/src/utils/handleSubmit.js index a36a3bbb1c..09e1aa52b6 100644 --- a/client/src/utils/handleSubmit.js +++ b/client/src/utils/handleSubmit.js @@ -21,9 +21,12 @@ export default function handleSubmit({ }; } - if (model === 'bingai' && convo.conversationId) { + const isBing = model === 'bingai' || model === 'sydney'; + if (isBing && convo.conversationId) { + payload = { ...payload, + jailbreakConversationId: convo.jailbreakConversationId, conversationId: convo.conversationId, conversationSignature: convo.conversationSignature, clientId: convo.clientId, @@ -31,7 +34,10 @@ export default function handleSubmit({ }; } - const server = model === 'bingai' ? endpoint + '/bing' : endpoint; + let server = endpoint; + server = model === 'bingai' ? server + '/bing' : server; + server = model === 'sydney' ? server + '/sydney' : server; + const events = new SSE(server, { payload: JSON.stringify(payload), headers: { 'Content-Type': 'application/json' } diff --git a/client/webpack.config.js b/client/webpack.config.js index 2461658544..46b5cb849a 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -10,6 +10,7 @@ module.exports = { * to use its built-in optimizations accordingly. default is production */ mode: 'development', + // cache: false, /** "entry" * the entry point */