From 69b3edc52c104bef2ad8d1fbecda195bf22ed2ca Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Wed, 8 Mar 2023 19:47:23 -0500 Subject: [PATCH 01/12] feat: sydney is functional --- api/app/bingai.js | 2 +- api/app/chatgpt-browser.js | 3 +- api/app/index.js | 2 + api/app/sydney.js | 34 ++++++++ api/models/Conversation.js | 3 + api/package-lock.json | 14 ++-- api/package.json | 2 +- api/server/routes/ask.js | 3 +- api/server/routes/askBing.js | 6 +- api/server/routes/askSydney.js | 77 +++++++++++++++++++ .../components/Conversations/Conversation.jsx | 4 +- client/src/components/Conversations/index.jsx | 1 + client/src/components/Main/TextChat.jsx | 11 ++- client/src/store/convoSlice.js | 1 + client/src/store/modelSlice.js | 16 ++-- 15 files changed, 157 insertions(+), 22 deletions(-) create mode 100644 api/app/sydney.js create mode 100644 api/server/routes/askSydney.js diff --git a/api/app/bingai.js b/api/app/bingai.js index e33278b9a2..dff3958294 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: 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/index.js b/api/app/index.js index 53d4d594b9..a1d0088d9b 100644 --- a/api/app/index.js +++ b/api/app/index.js @@ -2,6 +2,7 @@ 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 detectCode = require('./detectCode'); @@ -10,6 +11,7 @@ module.exports = { browserClient, customClient, askBing, + askSydney, titleConvo, 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..a599128640 --- /dev/null +++ b/api/app/sydney.js @@ -0,0 +1,34 @@ +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: new KeyvFile({ filename: './data/cache.json' }) + }); + + let options = { + jailbreakConversationId: true, + onProgress: async (partialRes) => await progressCallback(partialRes), + }; + + if (convo) { + options = { ...options, ...convo }; + } + + 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..97cb8eaad1 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 }, diff --git a/api/package-lock.json b/api/package-lock.json index 9a247820c1..ebad132167 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.0", "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.0", + "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.28.0.tgz", + "integrity": "sha512-753dc/Eaf+1XmSXrLu+99LR4N+ACL7fQyA7GlsUXjRVXcH+m/iDqqXrU+o1JKGy84VlNKokAvq3oy9LVunKCIw==", "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.0", + "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.28.0.tgz", + "integrity": "sha512-753dc/Eaf+1XmSXrLu+99LR4N+ACL7fQyA7GlsUXjRVXcH+m/iDqqXrU+o1JKGy84VlNKokAvq3oy9LVunKCIw==", "requires": { "@dqbd/tiktoken": "^0.4.0", "@fastify/cors": "^8.2.0", diff --git a/api/package.json b/api/package.json index 6e38f1f117..74c420c8e5 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.0", "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..883be12737 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, @@ -12,7 +13,7 @@ const { const { getConvo, saveMessage, deleteMessages, saveConvo } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); -router.use('/bing', askBing); +router.use('/bing', 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..7b3ab74a53 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -48,7 +48,11 @@ 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; diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js new file mode 100644 index 0000000000..8dbf287f8f --- /dev/null +++ b/api/server/routes/askSydney.js @@ -0,0 +1,77 @@ +const express = require('express'); +const crypto = require('crypto'); +const router = express.Router(); +const { titleConvo, askSydney } = require('../../app/'); +const { saveMessage, deleteMessages, saveConvo } = require('../../models'); +const { handleError, sendMessage } = require('./handlers'); + +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); + sendMessage(res, { text: tokens, message: true }); + }; + + let response = await askSydney({ + text, + progressCallback, + convo + }); + + console.log('CLIENT RESPONSE'); + console.dir(response, { depth: null }); + + userMessage.conversationSignature = + convo.conversationSignature || response.conversationSignature; + userMessage.conversationId = convo.conversationId || response.conversationId; + userMessage.invocationId = response.invocationId; + userMessage.jailbreakConversationId = convo.jailbreakConversationId || response.jailbreakConversationId; + await saveMessage(userMessage); + + if (!convo.conversationSignature) { + response.title = await titleConvo({ + model, + message: text, + response: JSON.stringify(response.response) + }); + } + + response.text = 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; + 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..cd4b61f916 100644 --- a/client/src/components/Conversations/Conversation.jsx +++ b/client/src/components/Conversations/Conversation.jsx @@ -34,11 +34,12 @@ export default function Conversation({ const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix }; if (bingData) { - const { conversationSignature, clientId, invocationId } = bingData; + const { conversationSignature, jailbreakConversationId, clientId, invocationId } = bingData; dispatch( setConversation({ ...convo, parentMessageId: null, + jailbreakConversationId, conversationSignature, clientId, invocationId @@ -49,6 +50,7 @@ export default function Conversation({ setConversation({ ...convo, parentMessageId, + jailbreakConversationId: null, conversationSignature: null, clientId: null, invocationId: null diff --git a/client/src/components/Conversations/index.jsx b/client/src/components/Conversations/index.jsx index f265a97483..ab49d68b75 100644 --- a/client/src/components/Conversations/index.jsx +++ b/client/src/components/Conversations/index.jsx @@ -14,6 +14,7 @@ export default function Conversations({ conversations, conversationId, showMore conversations.map((convo) => { const bingData = convo.conversationSignature ? { + jailbreakConversationId: convo.jailbreakConversationId, conversationSignature: convo.conversationSignature, clientId: convo.clientId, invocationId: convo.invocationId diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 34c49750b9..7701885b4c 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -57,6 +57,7 @@ export default function TextChat({ messages }) { title, conversationId, parentMessageId: id, + jailbreakConversationId: null, conversationSignature: null, clientId: null, invocationId: null, @@ -69,10 +70,18 @@ export default function TextChat({ messages }) { convo.conversationId === null && convo.invocationId === null ) { - const { title, conversationSignature, clientId, conversationId, invocationId } = data; + const { + title, + jailbreakConversationId, + conversationSignature, + clientId, + conversationId, + invocationId + } = data; dispatch( setConversation({ title, + jailbreakConversationId, conversationSignature, clientId, conversationId, diff --git a/client/src/store/convoSlice.js b/client/src/store/convoSlice.js index d2294e5bba..6d10c58265 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, diff --git a/client/src/store/modelSlice.js b/client/src/store/modelSlice.js index 801f0955d8..bc4a1e9b94 100644 --- a/client/src/store/modelSlice.js +++ b/client/src/store/modelSlice.js @@ -16,16 +16,16 @@ const initialState = { _id: '2', name: 'BingAI', value: 'bingai' - } - // { - // _id: '3', - // name: 'ChatGPT', - // value: 'chatgptBrowser' - // } + }, + { + _id: '3', + 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, chatgptBrowser: true } + // initial: { chatgpt: true, chatgptCustom: true, bingai: true, } }; const currentSlice = createSlice({ From 2c1ae68dc41a4ebe0592f5d321376629528e5dca Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Wed, 8 Mar 2023 21:06:58 -0500 Subject: [PATCH 02/12] adding sydney in progress. only uses jailbreakConvoId and parentMsgId --- api/app/bingai.js | 2 +- api/app/sydney.js | 8 ++++-- api/server/routes/ask.js | 3 +- api/server/routes/askBing.js | 2 +- api/server/routes/askSydney.js | 9 +++--- .../components/Conversations/Conversation.jsx | 12 ++++++-- client/src/components/Main/TextChat.jsx | 28 +++++-------------- client/src/components/Messages/Message.jsx | 9 ++++-- client/src/components/Models/ModelMenu.jsx | 3 +- client/src/store/modelSlice.js | 7 ++++- client/src/utils/handleSubmit.js | 11 ++++++-- 11 files changed, 53 insertions(+), 41 deletions(-) diff --git a/api/app/bingai.js b/api/app/bingai.js index dff3958294..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, - cache: new KeyvFile({ filename: './data/cache.json' }) + cache: { store: new KeyvFile({ filename: './data/cache.json' }) } }); let options = { diff --git a/api/app/sydney.js b/api/app/sydney.js index a599128640..fe47c74f57 100644 --- a/api/app/sydney.js +++ b/api/app/sydney.js @@ -10,7 +10,7 @@ const askSydney = async ({ text, progressCallback, convo }) => { // If the above doesn't work, provide all your cookies as a string instead // cookies: '', debug: false, - cache: new KeyvFile({ filename: './data/cache.json' }) + cache: { store: new KeyvFile({ filename: './data/cache.json' }) } }); let options = { @@ -18,10 +18,12 @@ const askSydney = async ({ text, progressCallback, convo }) => { onProgress: async (partialRes) => await progressCallback(partialRes), }; - if (convo) { - options = { ...options, ...convo }; + if (convo.parentMessageId) { + options = { ...options, jailbreakConversationId: convo.jailbreakConversationId, parentMessageId: convo.parentMessageId }; } + console.log('sydney options', options); + const res = await sydneyClient.sendMessage(text, options ); diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index 883be12737..23ed20ba85 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -13,7 +13,8 @@ const { const { getConvo, saveMessage, deleteMessages, saveConvo } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); -router.use('/bing', askSydney); +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 7b3ab74a53..d77282f888 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -38,7 +38,7 @@ router.post('/', async (req, res) => { convo }); - console.log('CLIENT RESPONSE'); + console.log('BING RESPONSE'); console.dir(response, { depth: null }); userMessage.conversationSignature = diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 8dbf287f8f..0b77526b8b 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -38,8 +38,8 @@ router.post('/', async (req, res) => { convo }); - console.log('CLIENT RESPONSE'); - console.dir(response, { depth: null }); + console.log('SYDNEY RESPONSE'); + // console.dir(response, { depth: null }); userMessage.conversationSignature = convo.conversationSignature || response.conversationSignature; @@ -47,7 +47,7 @@ router.post('/', async (req, res) => { userMessage.invocationId = response.invocationId; userMessage.jailbreakConversationId = convo.jailbreakConversationId || response.jailbreakConversationId; await saveMessage(userMessage); - + if (!convo.conversationSignature) { response.title = await titleConvo({ model, @@ -55,8 +55,9 @@ router.post('/', async (req, res) => { response: JSON.stringify(response.response) }); } - + response.text = response.response; + response.parentMessageId = convo.parentMessageId || response.messageId; response.id = response.details.messageId; response.suggestions = response.details.suggestedResponses && diff --git a/client/src/components/Conversations/Conversation.jsx b/client/src/components/Conversations/Conversation.jsx index cd4b61f916..455d331d07 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,17 @@ export default function Conversation({ const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix }; if (bingData) { - const { conversationSignature, jailbreakConversationId, clientId, invocationId } = bingData; + const { + parentMessageId, + conversationSignature, + jailbreakConversationId, + clientId, + invocationId + } = bingData; dispatch( setConversation({ ...convo, - parentMessageId: null, + parentMessageId: parentMessageId || null, jailbreakConversationId, conversationSignature, clientId, diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 7701885b4c..a162a452d2 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 ) { @@ -66,13 +68,15 @@ export default function TextChat({ messages }) { }) ); } else if ( - model === 'bingai' && + isBing && convo.conversationId === null && convo.invocationId === null ) { + console.log('Bing data:', data) const { title, jailbreakConversationId, + parentMessageId, conversationSignature, clientId, conversationId, @@ -82,11 +86,11 @@ export default function TextChat({ messages }) { setConversation({ title, jailbreakConversationId, + parentMessageId: parentMessageId || null, conversationSignature, clientId, conversationId, invocationId, - parentMessageId: null }) ); } @@ -94,24 +98,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..0accb1afeb 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: 'rgb(214, 255, 255)', }; + 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/ModelMenu.jsx b/client/src/components/Models/ModelMenu.jsx index 80b5dd9dc3..d83ec9a927 100644 --- a/client/src/components/Models/ModelMenu.jsx +++ b/client/src/components/Models/ModelMenu.jsx @@ -126,8 +126,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/store/modelSlice.js b/client/src/store/modelSlice.js index bc4a1e9b94..bc34673bfc 100644 --- a/client/src/store/modelSlice.js +++ b/client/src/store/modelSlice.js @@ -19,12 +19,17 @@ const initialState = { }, { _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, sydney: true, chatgptBrowser: true } // initial: { chatgpt: true, chatgptCustom: true, bingai: true, } }; diff --git a/client/src/utils/handleSubmit.js b/client/src/utils/handleSubmit.js index a36a3bbb1c..02f89f21d6 100644 --- a/client/src/utils/handleSubmit.js +++ b/client/src/utils/handleSubmit.js @@ -21,9 +21,13 @@ export default function handleSubmit({ }; } - if (model === 'bingai' && convo.conversationId) { + const isBing = model === 'bingai' || model === 'sydney'; + if (isBing && convo.conversationId) { + + console.log('bing convo', convo); payload = { ...payload, + jailbreakConversationId: convo.jailbreakConversationId, conversationId: convo.conversationId, conversationSignature: convo.conversationSignature, clientId: convo.clientId, @@ -31,7 +35,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' } From 67054e7504fb2a0e432d4557002ef0f1a895ffbd Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Wed, 8 Mar 2023 22:30:29 -0500 Subject: [PATCH 03/12] working sydney, need to test the other models --- api/models/Conversation.js | 17 +++++- api/models/index.js | 3 +- api/server/routes/askSydney.js | 55 ++++++++++++------- .../components/Conversations/Conversation.jsx | 2 +- client/src/components/Conversations/index.jsx | 1 + client/src/components/Main/TextChat.jsx | 23 +++++++- 6 files changed, 75 insertions(+), 26 deletions(-) diff --git a/api/models/Conversation.js b/api/models/Conversation.js index 97cb8eaad1..1ff778e957 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -47,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 { @@ -95,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/server/routes/askSydney.js b/api/server/routes/askSydney.js index 0b77526b8b..a928bc75fc 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -2,7 +2,7 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); const { titleConvo, askSydney } = require('../../app/'); -const { saveMessage, deleteMessages, saveConvo } = require('../../models'); +const { saveMessage, deleteMessages, saveConvo, getConvoTitle } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); router.post('/', async (req, res) => { @@ -39,31 +39,48 @@ router.post('/', async (req, res) => { }); console.log('SYDNEY RESPONSE'); - // console.dir(response, { depth: null }); + // the usual values expected by the client are generated since only + // jailbreakConversationId and initial messageId is needed by sydney + // to continue the conversation + // if (!convo.jailbreakConversationId) { + // response.title = await titleConvo({ + // model, + // message: text, + // response: JSON.stringify(response.response) + // }); + // } - userMessage.conversationSignature = - convo.conversationSignature || response.conversationSignature; - userMessage.conversationId = convo.conversationId || response.conversationId; - userMessage.invocationId = response.invocationId; - userMessage.jailbreakConversationId = convo.jailbreakConversationId || response.jailbreakConversationId; - await saveMessage(userMessage); - - if (!convo.conversationSignature) { - response.title = await titleConvo({ - model, - message: text, - response: JSON.stringify(response.response) - }); - } - + // 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; - response.parentMessageId = convo.parentMessageId || response.messageId; - response.id = response.details.messageId; response.suggestions = response.details.suggestedResponses && response.details.suggestedResponses.map((s) => s.text); response.sender = model; response.final = true; + + // 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); diff --git a/client/src/components/Conversations/Conversation.jsx b/client/src/components/Conversations/Conversation.jsx index 455d331d07..745671e1cc 100644 --- a/client/src/components/Conversations/Conversation.jsx +++ b/client/src/components/Conversations/Conversation.jsx @@ -44,7 +44,7 @@ export default function Conversation({ dispatch( setConversation({ ...convo, - parentMessageId: parentMessageId || null, + parentMessageId, jailbreakConversationId, conversationSignature, clientId, diff --git a/client/src/components/Conversations/index.jsx b/client/src/components/Conversations/index.jsx index ab49d68b75..13b1e64b00 100644 --- a/client/src/components/Conversations/index.jsx +++ b/client/src/components/Conversations/index.jsx @@ -16,6 +16,7 @@ export default function Conversations({ conversations, conversationId, showMore ? { 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 a162a452d2..2350c68e5d 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -68,11 +68,30 @@ export default function TextChat({ messages }) { }) ); } else if ( - isBing && + model === 'bingai' && convo.conversationId === null && convo.invocationId === null ) { 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') { + console.log('Sydney data:', data) const { title, jailbreakConversationId, @@ -86,7 +105,7 @@ export default function TextChat({ messages }) { setConversation({ title, jailbreakConversationId, - parentMessageId: parentMessageId || null, + parentMessageId, conversationSignature, clientId, conversationId, From 1e0cbbf1cc5e4e81ca52b4846eabfe30712e073d Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Wed, 8 Mar 2023 22:32:51 -0500 Subject: [PATCH 04/12] merge readme update --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c32fc798f4..175c9a3242 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # ChatGPT Clone # https://user-images.githubusercontent.com/110412045/223754183-8b7f45ce-6517-4bd5-9b39-c624745bf399.mp4 + ## All AI Conversations under One Roof. ## Assistant AIs are the future and OpenAI revolutionized this movement with ChatGPT. While numerous methods exist to integrate them, this app commemorates the original styling of ChatGPT, with the ability to integrate any current/future AI models, while improving upon original client features, such as conversation search and prompt templates (currently WIP). From eae82edb8304b48b981106b3c4d62cc29955f6e5 Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Wed, 8 Mar 2023 22:33:25 -0500 Subject: [PATCH 05/12] merge readme update --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 175c9a3242..c32fc798f4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # ChatGPT Clone # https://user-images.githubusercontent.com/110412045/223754183-8b7f45ce-6517-4bd5-9b39-c624745bf399.mp4 - ## All AI Conversations under One Roof. ## Assistant AIs are the future and OpenAI revolutionized this movement with ChatGPT. While numerous methods exist to integrate them, this app commemorates the original styling of ChatGPT, with the ability to integrate any current/future AI models, while improving upon original client features, such as conversation search and prompt templates (currently WIP). From 5e57deab5f82f0313dad5245b0595d03a3ab92c0 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Thu, 9 Mar 2023 16:09:53 -0500 Subject: [PATCH 06/12] bing styling in progress --- api/app/citeText.js | 27 ++++++++++++++++++++++ api/app/getCitations.js | 13 +++++++++++ api/app/index.js | 2 ++ api/package-lock.json | 14 +++++------ api/package.json | 2 +- api/server/routes/askSydney.js | 18 +++++---------- client/src/components/Messages/Message.jsx | 4 ++-- 7 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 api/app/citeText.js create mode 100644 api/app/getCitations.js diff --git a/api/app/citeText.js b/api/app/citeText.js new file mode 100644 index 0000000000..fc85c3285f --- /dev/null +++ b/api/app/citeText.js @@ -0,0 +1,27 @@ +/* +// example +const ex = "Fetch API[^1^], Axios[^3^], or XMLHttpRequest[^2^]. Each of these..."; +const links = [ + 'https://www.freecodecamp.org/news/here-is-the-most-popular-ways-to-make-an-http-request-in-javascript-954ce8c95aaa/', + 'https://stackoverflow.com/questions/247483/http-get-request-in-javascript', + 'https://livecodestream.dev/post/5-ways-to-make-http-requests-in-javascript/' +]; + +const regex = /\[\^\d+?\^]/g; + +const citations = Array.from(new Set(ex.match(regex))); +const linkMap = {}; +citations.forEach(citation => { + const digit = citation.match(/\d+?/g)[0]; + linkMap[citation] = links[digit - 1]; +}); +*/ +const citationRegex = /\[\^\d+?\^]/g; + +const citeText = (res) => { + let sources = res.details.sourceAttributions; + if (!sources) return res.response; + sources = sources.map((source) => source.seeMoreUrl); +}; + +module.exports = citeText; \ No newline at end of file diff --git a/api/app/getCitations.js b/api/app/getCitations.js new file mode 100644 index 0000000000..46d4e72c07 --- /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) return ''; + links = links.map((link) => '- ' + link.trim()); + return 'Learn more:\n' + 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 a1d0088d9b..0a829609fd 100644 --- a/api/app/index.js +++ b/api/app/index.js @@ -4,6 +4,7 @@ const customClient = require('./chatgpt-custom'); const { askBing } = require('./bingai'); const { askSydney } = require('./sydney'); const titleConvo = require('./titleConvo'); +const getCitations = require('./getCitations'); const detectCode = require('./detectCode'); module.exports = { @@ -13,5 +14,6 @@ module.exports = { askBing, askSydney, titleConvo, + getCitations, detectCode }; \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json index ebad132167..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.28.0", + "@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.28.0", - "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.28.0.tgz", - "integrity": "sha512-753dc/Eaf+1XmSXrLu+99LR4N+ACL7fQyA7GlsUXjRVXcH+m/iDqqXrU+o1JKGy84VlNKokAvq3oy9LVunKCIw==", + "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.28.0", - "resolved": "https://registry.npmjs.org/@waylaidwanderer/chatgpt-api/-/chatgpt-api-1.28.0.tgz", - "integrity": "sha512-753dc/Eaf+1XmSXrLu+99LR4N+ACL7fQyA7GlsUXjRVXcH+m/iDqqXrU+o1JKGy84VlNKokAvq3oy9LVunKCIw==", + "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 74c420c8e5..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.28.0", + "@waylaidwanderer/chatgpt-api": "^1.28.2", "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index a928bc75fc..1755a40d16 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -1,7 +1,7 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); -const { titleConvo, askSydney } = require('../../app/'); +const { titleConvo, getCitations, askSydney } = require('../../app/'); const { saveMessage, deleteMessages, saveConvo, getConvoTitle } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); @@ -39,17 +39,8 @@ router.post('/', async (req, res) => { }); console.log('SYDNEY RESPONSE'); - // the usual values expected by the client are generated since only - // jailbreakConversationId and initial messageId is needed by sydney - // to continue the conversation - // if (!convo.jailbreakConversationId) { - // response.title = await titleConvo({ - // model, - // message: text, - // response: JSON.stringify(response.response) - // }); - // } - + // console.dir(response, { depth: null }); + // Save sydney response response.id = response.messageId; // response.parentMessageId = convo.parentMessageId ? convo.parentMessageId : response.messageId; @@ -75,6 +66,9 @@ router.post('/', async (req, res) => { response.sender = model; response.final = true; + const links = getCitations(response); + console.log('sydney links', links); + // Save user message userMessage.conversationId = response.conversationId; userMessage.parentMessageId = response.parentMessageId; diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index 0accb1afeb..9413f21856 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -49,7 +49,7 @@ export default function Message({ chatgpt: 'rgb(16, 163, 127)', chatgptBrowser: 'rgb(25, 207, 207)', bingai: '', - sydney: 'rgb(214, 255, 255)', + sydney: '', }; const isBing = sender === 'bingai' || sender === 'sydney'; @@ -65,7 +65,7 @@ export default function Message({ if ((notUser && backgroundColor) || isBing) { icon = (
{isBing ? : } From a451574760c2329823bb4765c9fb69755439ccb8 Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Thu, 9 Mar 2023 18:07:36 -0500 Subject: [PATCH 07/12] feat: cites text with links --- api/app/citeText.js | 44 +++++++++++++++++--------------- api/app/getCitations.js | 2 +- api/app/index.js | 2 ++ api/server/routes/askSydney.js | 6 +++-- client/src/utils/handleSubmit.js | 1 - client/webpack.config.js | 1 + 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/api/app/citeText.js b/api/app/citeText.js index fc85c3285f..8998535fea 100644 --- a/api/app/citeText.js +++ b/api/app/citeText.js @@ -1,27 +1,29 @@ -/* -// example -const ex = "Fetch API[^1^], Axios[^3^], or XMLHttpRequest[^2^]. Each of these..."; -const links = [ - 'https://www.freecodecamp.org/news/here-is-the-most-popular-ways-to-make-an-http-request-in-javascript-954ce8c95aaa/', - 'https://stackoverflow.com/questions/247483/http-get-request-in-javascript', - 'https://livecodestream.dev/post/5-ways-to-make-http-requests-in-javascript/' -]; - -const regex = /\[\^\d+?\^]/g; - -const citations = Array.from(new Set(ex.match(regex))); -const linkMap = {}; -citations.forEach(citation => { - const digit = citation.match(/\d+?/g)[0]; - linkMap[citation] = links[digit - 1]; -}); -*/ const citationRegex = /\[\^\d+?\^]/g; -const citeText = (res) => { +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) return res.response; + 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; \ No newline at end of file +module.exports = citeText; diff --git a/api/app/getCitations.js b/api/app/getCitations.js index 46d4e72c07..0061daff0d 100644 --- a/api/app/getCitations.js +++ b/api/app/getCitations.js @@ -5,7 +5,7 @@ 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) return ''; + if (links?.length === 0 || !links) return ''; links = links.map((link) => '- ' + link.trim()); return 'Learn more:\n' + links.join('\n'); }; diff --git a/api/app/index.js b/api/app/index.js index 0a829609fd..7b61f8de95 100644 --- a/api/app/index.js +++ b/api/app/index.js @@ -5,6 +5,7 @@ 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 = { @@ -15,5 +16,6 @@ module.exports = { askSydney, titleConvo, getCitations, + citeText, detectCode }; \ No newline at end of file diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 1755a40d16..fc6fe9f5ce 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -1,7 +1,7 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); -const { titleConvo, getCitations, askSydney } = require('../../app/'); +const { titleConvo, getCitations, citeText, askSydney } = require('../../app/'); const { saveMessage, deleteMessages, saveConvo, getConvoTitle } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); @@ -29,6 +29,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 }); }; @@ -60,6 +61,7 @@ router.post('/', async (req, res) => { ? convo.conversationSignature : crypto.randomUUID(); response.text = response.response; + delete response.response; response.suggestions = response.details.suggestedResponses && response.details.suggestedResponses.map((s) => s.text); @@ -67,7 +69,7 @@ router.post('/', async (req, res) => { response.final = true; const links = getCitations(response); - console.log('sydney links', links); + response.text = citeText(response); // Save user message userMessage.conversationId = response.conversationId; diff --git a/client/src/utils/handleSubmit.js b/client/src/utils/handleSubmit.js index 02f89f21d6..09e1aa52b6 100644 --- a/client/src/utils/handleSubmit.js +++ b/client/src/utils/handleSubmit.js @@ -24,7 +24,6 @@ export default function handleSubmit({ const isBing = model === 'bingai' || model === 'sydney'; if (isBing && convo.conversationId) { - console.log('bing convo', convo); payload = { ...payload, jailbreakConversationId: convo.jailbreakConversationId, diff --git a/client/webpack.config.js b/client/webpack.config.js index 2461658544..3c44be5385 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 */ From 30936573ac1a38655e1b78b972e15bcfc81d0a40 Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Thu, 9 Mar 2023 18:42:36 -0500 Subject: [PATCH 08/12] feat: includes sources --- api/app/citeText.js | 4 ++-- api/app/getCitations.js | 4 ++-- api/server/routes/askBing.js | 14 +++++++++++-- api/server/routes/askSydney.js | 9 +++++++-- client/src/components/Main/TextChat.jsx | 1 - client/src/style.css | 26 +++++++++++++++++++++++++ 6 files changed, 49 insertions(+), 9 deletions(-) diff --git a/api/app/citeText.js b/api/app/citeText.js index 8998535fea..536c981d4e 100644 --- a/api/app/citeText.js +++ b/api/app/citeText.js @@ -8,7 +8,7 @@ const citeText = (res, noLinks = false) => { if (noLinks) { citations.forEach((citation) => { const digit = citation.match(/\d+?/g)[0]; - result = result.replaceAll(citation, `[${digit}](#)`); + result = result.replaceAll(citation, `[${digit}](#) `); }); return result; @@ -20,7 +20,7 @@ const citeText = (res, noLinks = false) => { citations.forEach((citation) => { const digit = citation.match(/\d+?/g)[0]; - result = result.replaceAll(citation, `[${digit}](${sources[digit - 1]})`); + result = result.replaceAll(citation, `[${digit}](${sources[digit - 1]}) `); }); return result; diff --git a/api/app/getCitations.js b/api/app/getCitations.js index 0061daff0d..f4086a4e9e 100644 --- a/api/app/getCitations.js +++ b/api/app/getCitations.js @@ -6,8 +6,8 @@ const getCitations = (res) => { 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 'Learn more:\n' + links.join('\n'); + links = links.map((link) => link.trim()); + return links.join('\n'); }; module.exports = getCitations; \ No newline at end of file diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index d77282f888..36218320b6 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 }); }; @@ -39,7 +41,8 @@ router.post('/', async (req, res) => { }); console.log('BING RESPONSE'); - console.dir(response, { depth: null }); + // console.dir(response, { depth: null }); + const hasCitations = citationRegex.test(response.response); userMessage.conversationSignature = convo.conversationSignature || response.conversationSignature; @@ -56,12 +59,19 @@ router.post('/', async (req, res) => { } 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 index fc6fe9f5ce..b328f879fa 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -4,6 +4,7 @@ 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; @@ -40,8 +41,10 @@ router.post('/', async (req, res) => { }); console.log('SYDNEY RESPONSE'); + console.log(response.response); // console.dir(response, { depth: null }); - + const hasCitations = citationRegex.test(response.response); + // Save sydney response response.id = response.messageId; // response.parentMessageId = convo.parentMessageId ? convo.parentMessageId : response.messageId; @@ -69,7 +72,9 @@ router.post('/', async (req, res) => { response.final = true; const links = getCitations(response); - response.text = citeText(response); + response.text = + citeText(response) + + (links?.length > 0 && hasCitations ? `\n${links}` : ''); // Save user message userMessage.conversationId = response.conversationId; diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 2350c68e5d..9cf474357b 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -91,7 +91,6 @@ export default function TextChat({ messages }) { }) ); } else if (model === 'sydney') { - console.log('Sydney data:', data) const { title, jailbreakConversationId, diff --git a/client/src/style.css b/client/src/style.css index 7fd5ad79b6..961c67b597 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 2s 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; } From 0ba92c4c03ab5459d690df53bfeb3f83a9a01534 Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Thu, 9 Mar 2023 18:57:37 -0500 Subject: [PATCH 09/12] add new reducer to ensure conversations are renewed on model change --- .../components/Conversations/DeleteButton.jsx | 5 +++-- client/src/components/Models/ModelDialog.jsx | 11 +++++++++++ client/src/components/Models/ModelMenu.jsx | 19 ++++++++++--------- client/src/components/Nav/ClearConvos.jsx | 19 ++++++++++--------- client/src/components/Nav/NewChat.jsx | 5 +++-- client/src/store/convoSlice.js | 16 +++++++++++++++- client/src/style.css | 2 +- 7 files changed, 53 insertions(+), 24 deletions(-) diff --git a/client/src/components/Conversations/DeleteButton.jsx b/client/src/components/Conversations/DeleteButton.jsx index 6dff5682fe..423a8a2c21 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,8 @@ export default function DeleteButton({ conversationId, renaming, cancelHandler } () => { dispatch(setMessages([])); dispatch(removeConvo(conversationId)); - dispatch(setConversation({ title: 'New chat', conversationId: null, parentMessageId: null })); + // dispatch(setConversation({ title: 'New chat', conversationId: null, parentMessageId: null })); + dispatch(setNewConvo()); } ); diff --git a/client/src/components/Models/ModelDialog.jsx b/client/src/components/Models/ModelDialog.jsx index 93005976e3..40030b28bf 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,6 +37,16 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); dispatch(setModel('chatgptCustom')); handleSaveState(chatGptLabel.toLowerCase()); + // Set new conversation + dispatch(setNewConvo()); + // dispatch( + // setConversation({ + // title: 'New Chat', + // error: false, + // conversationId: null, + // parentMessageId: null + // }) + // ); // dispatch(setDisabled(false)); }; diff --git a/client/src/components/Models/ModelMenu.jsx b/client/src/components/Models/ModelMenu.jsx index d83ec9a927..06fe2aeb28 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'; @@ -76,14 +76,15 @@ export default function ModelMenu() { } // Set new conversation - dispatch( - setConversation({ - title: 'New Chat', - error: false, - conversationId: null, - parentMessageId: null - }) - ); + dispatch(setNewConvo()); + // dispatch( + // setConversation({ + // title: 'New Chat', + // error: false, + // conversationId: null, + // parentMessageId: null + // }) + // ); }; const onOpenChange = (open) => { diff --git a/client/src/components/Nav/ClearConvos.jsx b/client/src/components/Nav/ClearConvos.jsx index 25762aa2f2..324c443c4b 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,15 @@ 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()); + // dispatch( + // setConversation({ + // error: false, + // title: 'New chat', + // conversationId: null, + // parentMessageId: null + // }) + // ); mutate(`http://localhost:3080/api/convos`); }); diff --git a/client/src/components/Nav/NewChat.jsx b/client/src/components/Nav/NewChat.jsx index 8535995b13..70e56c4427 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,8 @@ export default function NewChat() { const clickHandler = () => { dispatch(setText('')); dispatch(setMessages([])); - dispatch(setConversation({ title: 'New Chat', error: false, conversationId: null, parentMessageId: null })); + dispatch(setNewConvo()); + // dispatch(setConversation({ title: 'New Chat', error: false, conversationId: null, parentMessageId: null })); }; return ( diff --git a/client/src/store/convoSlice.js b/client/src/store/convoSlice.js index 6d10c58265..2560a29298 100644 --- a/client/src/store/convoSlice.js +++ b/client/src/store/convoSlice.js @@ -29,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); @@ -46,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/style.css b/client/src/style.css index 961c67b597..6b17c388e2 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -25,7 +25,7 @@ p > small { opacity: 0; - animation: fadein 2s forwards; + animation: fadein 3s forwards; } @keyframes fadein { From a286f027c893084ed596a202a3a97292ca028a7e Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Thu, 9 Mar 2023 19:02:59 -0500 Subject: [PATCH 10/12] model menu won't close on model deletion --- client/src/components/Models/ModelDialog.jsx | 9 --------- client/src/components/Models/ModelMenu.jsx | 14 +++----------- client/src/components/Nav/ClearConvos.jsx | 8 -------- client/webpack.config.js | 2 +- 4 files changed, 4 insertions(+), 29 deletions(-) diff --git a/client/src/components/Models/ModelDialog.jsx b/client/src/components/Models/ModelDialog.jsx index 40030b28bf..47592d8a93 100644 --- a/client/src/components/Models/ModelDialog.jsx +++ b/client/src/components/Models/ModelDialog.jsx @@ -39,15 +39,6 @@ export default function ModelDialog({ mutate, setModelSave, handleSaveState }) { handleSaveState(chatGptLabel.toLowerCase()); // Set new conversation dispatch(setNewConvo()); - // dispatch( - // setConversation({ - // title: 'New Chat', - // error: false, - // conversationId: null, - // parentMessageId: null - // }) - // ); - // dispatch(setDisabled(false)); }; const saveHandler = (e) => { diff --git a/client/src/components/Models/ModelMenu.jsx b/client/src/components/Models/ModelMenu.jsx index 06fe2aeb28..607b48921f 100644 --- a/client/src/components/Models/ModelMenu.jsx +++ b/client/src/components/Models/ModelMenu.jsx @@ -68,23 +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(setNewConvo()); - // dispatch( - // setConversation({ - // title: 'New Chat', - // error: false, - // conversationId: null, - // parentMessageId: null - // }) - // ); }; const onOpenChange = (open) => { diff --git a/client/src/components/Nav/ClearConvos.jsx b/client/src/components/Nav/ClearConvos.jsx index 324c443c4b..d262226bb9 100644 --- a/client/src/components/Nav/ClearConvos.jsx +++ b/client/src/components/Nav/ClearConvos.jsx @@ -13,14 +13,6 @@ export default function ClearConvos() { const { trigger } = manualSWR(`http://localhost:3080/api/convos/clear`, 'post', () => { dispatch(setMessages([])); dispatch(setNewConvo()); - // dispatch( - // setConversation({ - // error: false, - // title: 'New chat', - // conversationId: null, - // parentMessageId: null - // }) - // ); mutate(`http://localhost:3080/api/convos`); }); diff --git a/client/webpack.config.js b/client/webpack.config.js index 3c44be5385..46b5cb849a 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -10,7 +10,7 @@ module.exports = { * to use its built-in optimizations accordingly. default is production */ mode: 'development', - cache: false, + // cache: false, /** "entry" * the entry point */ From d0ecaabfd836b9dedd89eb7522ee8a0d8139c86d Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Thu, 9 Mar 2023 20:29:44 -0500 Subject: [PATCH 11/12] feat: cites text with links --- README.md | 13 +++++++++++-- api/server/routes/askBing.js | 2 +- api/server/routes/askSydney.js | 2 +- .../src/components/Conversations/DeleteButton.jsx | 1 - client/src/components/Nav/NewChat.jsx | 1 - 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c32fc798f4..c0e1ee4afa 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
diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 36218320b6..42718b9d91 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -42,7 +42,7 @@ router.post('/', async (req, res) => { console.log('BING RESPONSE'); // console.dir(response, { depth: null }); - const hasCitations = citationRegex.test(response.response); + const hasCitations = response.response.match(citationRegex)?.length > 0; userMessage.conversationSignature = convo.conversationSignature || response.conversationSignature; diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index b328f879fa..149b8b1c45 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -43,7 +43,7 @@ router.post('/', async (req, res) => { console.log('SYDNEY RESPONSE'); console.log(response.response); // console.dir(response, { depth: null }); - const hasCitations = citationRegex.test(response.response); + const hasCitations = response.response.match(citationRegex)?.length > 0; // Save sydney response response.id = response.messageId; diff --git a/client/src/components/Conversations/DeleteButton.jsx b/client/src/components/Conversations/DeleteButton.jsx index 423a8a2c21..03c9f47f81 100644 --- a/client/src/components/Conversations/DeleteButton.jsx +++ b/client/src/components/Conversations/DeleteButton.jsx @@ -14,7 +14,6 @@ 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/Nav/NewChat.jsx b/client/src/components/Nav/NewChat.jsx index 70e56c4427..540c023647 100644 --- a/client/src/components/Nav/NewChat.jsx +++ b/client/src/components/Nav/NewChat.jsx @@ -11,7 +11,6 @@ export default function NewChat() { dispatch(setText('')); dispatch(setMessages([])); dispatch(setNewConvo()); - // dispatch(setConversation({ title: 'New Chat', error: false, conversationId: null, parentMessageId: null })); }; return ( From 4383894ad33f84eea3eb0ae6aa912f9da51086cc Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Thu, 9 Mar 2023 20:31:56 -0500 Subject: [PATCH 12/12] finish update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c0e1ee4afa..d0ca06a9ca 100644 --- a/README.md +++ b/README.md @@ -89,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 @@ -181,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 ##