diff --git a/api/models/Conversation.js b/api/models/Conversation.js index d2ead788d2..a32bc3e7a4 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -57,13 +57,16 @@ const getConvo = async (conversationId) => { }; module.exports = { - saveConvo: async ({ conversationId, title, ...convo }) => { + saveConvo: async ({ conversationId, newConversationId, title, ...convo }) => { try { const messages = await getMessages({ conversationId }); const update = { ...convo, messages }; if (title) { update.title = title; } + if (newConversationId) { + update.conversationId = newConversationId; + } if (!update.jailbreakConversationId) update.jailbreakConversationId = null diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index 73b85b8012..d12ee17b4e 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -12,12 +12,13 @@ const { } = require('../../app/'); const { getConvo, saveMessage, getConvoTitle, saveConvo } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); +const { getMessages } = require('../../models/Message'); router.use('/bing', askBing); router.use('/sydney', askSydney); router.post('/', async (req, res) => { - let { model, text, parentMessageId, conversationId: oldConversationId , chatGptLabel, promptPrefix } = req.body; + let { model, text, parentMessageId, conversationId: oldConversationId , ...convo } = req.body; if (text.length === 0) { return handleError(res, { text: 'Prompt empty or too short' }); } @@ -27,21 +28,94 @@ router.post('/', async (req, res) => { const userMessageId = crypto.randomUUID(); const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' let userMessage = { - messageId: userMessageId, - sender: 'User', - text, - parentMessageId: userParentMessageId, - conversationId, - isCreatedByUser: true + messageId: userMessageId, + sender: 'User', + text, + parentMessageId: userParentMessageId, + conversationId, + isCreatedByUser: true }; console.log('ask log', { model, ...userMessage, - chatGptLabel, - promptPrefix + ...convo }); + // if (model === 'chatgptCustom' && !chatGptLabel && conversationId) { + // const convo = await getConvo({ conversationId }); + // if (convo) { + // console.log('found convo for custom gpt', { convo }) + // chatGptLabel = convo.chatGptLabel; + // promptPrefix = convo.promptPrefix; + // } + // } + + await saveMessage(userMessage); + await saveConvo({ ...userMessage, model, ...convo }); + + return await ask({ + userMessage, + model, + convo, + preSendRequest: true, + req, res + }); +}) + +router.post('/regenerate', async (req, res) => { + const { parentMessageId, model, chatGptLabel, promptPrefix } = req.body; + + const oldUserMessage = await getMessages({ messageId: req.body }) + + if (oldUserMessage) { + const convo = await getConvo(userMessage?.conversationId) + + const userMessageId = crypto.randomUUID(); + + let userMessage = { + ...userMessage, + messageId: userMessageId, + }; + + console.log('ask log for regeneration', { + model, + ...userMessage, + ...convo + }); + + return await ask({ + userMessage, + model, + convo, + preSendRequest: false, + req, res + }); + } else + return handleError(res, { text: 'Parent message not found' }); + + // if (model === 'chatgptCustom' && !chatGptLabel && conversationId) { + // const convo = await getConvo({ conversationId }); + // if (convo) { + // console.log('found convo for custom gpt', { convo }) + // chatGptLabel = convo.chatGptLabel; + // promptPrefix = convo.promptPrefix; + // } + // } + + // await saveConvo({ ...userMessage, model, chatGptLabel, promptPrefix }); +}); + +const ask = async ({ + userMessage, + overrideParentMessageId = null, + model, + convo, + preSendRequest = true, + req, res +}) => { + let { sender, text, parentMessageId: userParentMessageId, conversationId, messageId: userMessageId } = userMessage; + let client; if (model === 'chatgpt') { @@ -52,15 +126,6 @@ router.post('/', async (req, res) => { client = browserClient; } - if (model === 'chatgptCustom' && !chatGptLabel && conversationId) { - const convo = await getConvo({ conversationId }); - if (convo) { - console.log('found convo for custom gpt', { convo }) - chatGptLabel = convo.chatGptLabel; - promptPrefix = convo.promptPrefix; - } - } - res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream', @@ -69,9 +134,8 @@ router.post('/', async (req, res) => { 'X-Accel-Buffering': 'no' }); - await saveMessage(userMessage); - await saveConvo({ ...userMessage, model, chatGptLabel, promptPrefix }); - sendMessage(res, { message: userMessage, created: true }); + if (preSendRequest) + sendMessage(res, { message: userMessage, created: true }); try { let i = 0; @@ -80,12 +144,12 @@ router.post('/', async (req, res) => { if (i === 0 && typeof partial === 'object') { userMessage.conversationId = conversationId ? conversationId : partial.conversationId; await saveMessage(userMessage); - sendMessage(res, { ...partial, initial: true }); + sendMessage(res, { ...partial, parentMessageId: overrideParentMessageId || userMessageId, initial: true }); i++; } if (typeof partial === 'object') { - sendMessage(res, { ...partial, message: true }); + sendMessage(res, { ...partial, parentMessageId: overrideParentMessageId || userMessageId, message: true }); } else { tokens += partial === text ? '' : partial; if (tokens.match(/^\n/)) { @@ -107,10 +171,10 @@ router.post('/', async (req, res) => { progressCallback, convo: { parentMessageId: userParentMessageId, - conversationId + conversationId, + ...convo }, - chatGptLabel, - promptPrefix + ...convo }); console.log('CLIENT RESPONSE', gptResponse); @@ -118,7 +182,7 @@ router.post('/', async (req, res) => { if (!gptResponse.parentMessageId) { gptResponse.text = gptResponse.response; // gptResponse.id = gptResponse.messageId; - gptResponse.parentMessageId = userMessage.messageId; + gptResponse.parentMessageId = overrideParentMessageId || userMessageId; userMessage.conversationId = conversationId ? conversationId : gptResponse.conversationId; @@ -133,23 +197,26 @@ router.post('/', async (req, res) => { ) { await saveMessage({ messageId: crypto.randomUUID(), sender: model, - conversationId, parentMessageId: userMessageId, + conversationId, parentMessageId: overrideParentMessageId || userMessageId, error: true, text: 'Prompt empty or too short'}); return handleError(res, { text: 'Prompt empty or too short' }); } - gptResponse.sender = model === 'chatgptCustom' ? chatGptLabel : model; + gptResponse.sender = model === 'chatgptCustom' ? convo.chatGptLabel : model; // gptResponse.final = true; gptResponse.text = await detectCode(gptResponse.text); - if (chatGptLabel?.length > 0 && model === 'chatgptCustom') { - gptResponse.chatGptLabel = chatGptLabel; + if (convo.chatGptLabel?.length > 0 && model === 'chatgptCustom') { + gptResponse.chatGptLabel = convo.chatGptLabel; } - if (promptPrefix?.length > 0 && model === 'chatgptCustom') { - gptResponse.promptPrefix = promptPrefix; + if (convo.promptPrefix?.length > 0 && model === 'chatgptCustom') { + gptResponse.promptPrefix = convo.promptPrefix; } + // override the parentMessageId, for the regeneration. + gptResponse.parentMessageId = overrideParentMessageId || userMessageId + await saveMessage(gptResponse); await saveConvo(gptResponse); sendMessage(res, { @@ -160,7 +227,7 @@ router.post('/', async (req, res) => { }); res.end(); - if (parentMessageId == '00000000-0000-0000-0000-000000000000') { + if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { const title = await titleConvo({ model, message: text, @@ -184,6 +251,6 @@ router.post('/', async (req, res) => { await saveMessage(errorMessage); handleError(res, errorMessage); } -}); +}; module.exports = router; diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index ffe8f2ae6d..1871e96f40 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -13,6 +13,7 @@ router.post('/', async (req, res) => { } const conversationId = oldConversationId || crypto.randomUUID(); + const isNewConversation = !oldConversationId const userMessageId = crypto.randomUUID(); const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' @@ -31,6 +32,30 @@ router.post('/', async (req, res) => { ...convo }); + await saveMessage(userMessage); + await saveConvo({ ...userMessage, model, ...convo }); + + return await ask({ + isNewConversation, + userMessage, + model, + convo, + preSendRequest: true, + req, res + }); +}) + +const ask = async ({ + isNewConversation, + overrideParentMessageId = null, + userMessage, + model, + convo, + preSendRequest = true, + req, res +}) => { + let { sender, text, parentMessageId: userParentMessageId, conversationId, messageId: userMessageId } = userMessage; + res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream', @@ -39,9 +64,8 @@ router.post('/', async (req, res) => { 'X-Accel-Buffering': 'no' }); - await saveMessage(userMessage); - await saveConvo({ ...userMessage, model, chatGptLabel, promptPrefix }); - sendMessage(res, { message: userMessage, created: true }); + if (preSendRequest) + sendMessage(res, { message: userMessage, created: true }); try { let tokens = ''; @@ -49,28 +73,36 @@ router.post('/', async (req, res) => { tokens += partial === text ? '' : partial; // tokens = appendCode(tokens); tokens = citeText(tokens, true); - sendMessage(res, { text: tokens, message: true }); + sendMessage(res, { text: tokens, message: true, parentMessageId: overrideParentMessageId || userMessageId }); }; let response = await askBing({ text, progressCallback, convo: { + ...convo, parentMessageId: userParentMessageId, conversationId, - ...convo }, }); - console.log('BING RESPONSE'); + console.log('BING RESPONSE', response); // console.dir(response, { depth: null }); const hasCitations = response.response.match(citationRegex)?.length > 0; userMessage.conversationSignature = convo.conversationSignature || response.conversationSignature; - userMessage.conversationId = conversationId || response.conversationId; + userMessage.conversationId = response.conversationId || conversationId; userMessage.invocationId = response.invocationId; await saveMessage(userMessage); + + // Bing API will not use our conversationId at the first time, + // so change the placeholder conversationId to the real one. + // Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId, + // but in this situation, don't change the conversationId, but create new convo. + if (conversationId != userMessage.conversationId && isNewConversation) + await saveConvo({ conversationId: conversationId, newConversationId: userMessage.conversationId }); + conversationId = userMessage.conversationId; response.text = response.response; delete response.response; @@ -79,29 +111,31 @@ router.post('/', async (req, res) => { response.details.suggestedResponses && response.details.suggestedResponses.map((s) => s.text); response.sender = model; - response.parentMessageId = gptResponse.parentMessageId || userMessage.messageId // response.final = true; + // override the parentMessageId, for the regeneration. + response.parentMessageId = overrideParentMessageId || response.parentMessageId || userMessageId; + const links = getCitations(response); response.text = citeText(response) + (links?.length > 0 && hasCitations ? `\n${links}` : ''); await saveMessage(response); - await saveConvo(response); + await saveConvo({...response, model, ...convo}); sendMessage(res, { title: await getConvoTitle(conversationId), final: true, requestMessage: userMessage, - responseMessage: gptResponse + responseMessage: response }); res.end(); - if (parentMessageId == '00000000-0000-0000-0000-000000000000') { + if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { const title = await titleConvo({ model, message: text, - response: JSON.stringify(gptResponse?.text) + response: JSON.stringify(response?.text) }); console.log('CONVERSATION TITLE', title); @@ -121,6 +155,6 @@ router.post('/', async (req, res) => { await saveMessage(errorMessage); handleError(res, errorMessage); } -}); +}; module.exports = router; diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 20f6c302b3..3a663184b6 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -13,6 +13,7 @@ router.post('/', async (req, res) => { } const conversationId = oldConversationId || crypto.randomUUID(); + const isNewConversation = !oldConversationId const userMessageId = crypto.randomUUID(); const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' @@ -25,13 +26,36 @@ router.post('/', async (req, res) => { isCreatedByUser: true }; - console.log('ask log', { model, ...userMessage, ...convo }); + await saveMessage(userMessage); + await saveConvo({ ...userMessage, model, ...convo }); + + return await ask({ + isNewConversation, + userMessage, + model, + convo, + preSendRequest: true, + req, res + }); +}) + +const ask = async ({ + isNewConversation, + overrideParentMessageId = null, + userMessage, + model, + convo, + preSendRequest = true, + req, res +}) => { + let { sender, text, parentMessageId: userParentMessageId, conversationId, messageId: userMessageId } = userMessage; + res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream', @@ -40,9 +64,8 @@ router.post('/', async (req, res) => { 'X-Accel-Buffering': 'no' }); - await saveMessage(userMessage); - await saveConvo({ ...userMessage, model, chatGptLabel, promptPrefix }); - sendMessage(res, { message: userMessage, created: true }); + if (preSendRequest) + sendMessage(res, { message: userMessage, created: true }); try { let tokens = ''; @@ -50,7 +73,7 @@ router.post('/', async (req, res) => { tokens += partial === text ? '' : partial; // tokens = appendCode(tokens); tokens = citeText(tokens, true); - sendMessage(res, { text: tokens, message: true }); + sendMessage(res, { text: tokens, message: true, parentMessageId: overrideParentMessageId || userMessageId }); }; let response = await askSydney({ @@ -63,15 +86,19 @@ router.post('/', async (req, res) => { }, }); - console.log('SYDNEY RESPONSE'); - console.log(response.response); + console.log('SYDNEY RESPONSE', response); // console.dir(response, { depth: null }); const hasCitations = response.response.match(citationRegex)?.length > 0; + userMessage.conversationSignature = + convo.conversationSignature || response.conversationSignature; + userMessage.conversationId = response.conversationId || conversationId; + userMessage.invocationId = response.invocationId; + // Unlike gpt and bing, Sydney will never accept our given userMessage.messageId, it will generate its own one. + await saveMessage(userMessage); + // 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.conversationId = conversationId ? conversationId @@ -85,34 +112,44 @@ router.post('/', async (req, res) => { response.details.suggestedResponses && response.details.suggestedResponses.map((s) => s.text); response.sender = model; - response.parentMessageId = gptResponse.parentMessageId || userMessage.messageId // response.final = true; + // override the parentMessageId, for the regeneration. + response.parentMessageId = overrideParentMessageId || response.parentMessageId || userMessageId; + const links = getCitations(response); response.text = citeText(response) + (links?.length > 0 && hasCitations ? `\n${links}` : ''); // Save user message - userMessage.conversationId = response.conversationId; + userMessage.conversationId = response.conversationId || conversationId; await saveMessage(userMessage); + // Bing API will not use our conversationId at the first time, + // so change the placeholder conversationId to the real one. + // Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId, + // but in this situation, don't change the conversationId, but create new convo. + if (conversationId != userMessage.conversationId && isNewConversation) + await saveConvo({ conversationId: conversationId, newConversationId: userMessage.conversationId }); + conversationId = userMessage.conversationId; + // Save sydney response & convo, then send await saveMessage(response); - await saveConvo(response); + await saveConvo({...response, model, ...convo}); sendMessage(res, { title: await getConvoTitle(conversationId), final: true, requestMessage: userMessage, - responseMessage: gptResponse + responseMessage: response }); res.end(); - if (parentMessageId == '00000000-0000-0000-0000-000000000000') { + if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { const title = await titleConvo({ model, message: text, - response: JSON.stringify(gptResponse?.text) + response: JSON.stringify(response?.text) }); console.log('CONVERSATION TITLE', title); @@ -132,6 +169,6 @@ router.post('/', async (req, res) => { await saveMessage(errorMessage); handleError(res, errorMessage); } -}); +}; module.exports = router; diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 8ed3576661..8f1276fce6 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -49,7 +49,7 @@ export default function TextChat({ messages }) { const convoHandler = (data, currentState, currentMsg) => { const { requestMessage, responseMessage } = data; - const { conversationId } = currentMsg; + const { conversationId } = requestMessage; const { messages, _currentMsg, message, isCustomModel, sender } = currentState; const { model, chatGptLabel, promptPrefix } = message; @@ -66,7 +66,7 @@ export default function TextChat({ messages }) { // in case it takes too long. setTimeout(() => { - dispatch(refreshConversation()); + dispatch(refreshConversation()); }, 5000); } @@ -87,9 +87,7 @@ export default function TextChat({ messages }) { }) ); } else if ( - model === 'bingai' && - convo.conversationId === null && - convo.invocationId === null + model === 'bingai' ) { console.log('Bing data:', data); const { title } = data;