From 0ed8a40a41ecb31d9a2bcfd4bec4f8df9e875c9a Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 12:35:55 +0800 Subject: [PATCH] feat: merge all message.id into message.messageId feat: the first message will have a parentMessageId as 00000000-0000-0000-0000-000000000000 (in order not to create new convo when resubmit) feat: ask will return the userMessage as well, to send back the messageId TODO: comment out the title generation. TODO: bing version need to be test fix: never use the same messageId fix: never delete exist messages fix: connect response.parentMessageId to the userMessage.messageId fix: set default convo title as new Chat --- api/models/Conversation.js | 2 +- api/models/Message.js | 12 ++--- api/server/routes/ask.js | 58 ++++++++++++--------- api/server/routes/askBing.js | 69 ++++++++++++++++--------- api/server/routes/askSydney.js | 61 +++++++++++++++------- client/src/components/Main/TextChat.jsx | 40 ++++++++++---- client/src/store/messageSlice.js | 2 +- 7 files changed, 159 insertions(+), 85 deletions(-) diff --git a/api/models/Conversation.js b/api/models/Conversation.js index 0ae336641c..c8daf78c4a 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -13,7 +13,7 @@ const convoSchema = mongoose.Schema({ }, title: { type: String, - default: 'New conversation' + default: 'New Chat' }, jailbreakConversationId: { type: String diff --git a/api/models/Message.js b/api/models/Message.js index 0e091e416c..db1746924b 100644 --- a/api/models/Message.js +++ b/api/models/Message.js @@ -1,7 +1,7 @@ const mongoose = require('mongoose'); const messageSchema = mongoose.Schema({ - id: { + messageId: { type: String, unique: true, required: true @@ -42,24 +42,24 @@ const messageSchema = mongoose.Schema({ const Message = mongoose.models.Message || mongoose.model('Message', messageSchema); module.exports = { - saveMessage: async ({ id, conversationId, parentMessageId, sender, text, isCreatedByUser=false }) => { + saveMessage: async ({ messageId, conversationId, parentMessageId, sender, text, isCreatedByUser=false }) => { try { - await Message.findOneAndUpdate({ id }, { + await Message.findOneAndUpdate({ messageId }, { conversationId, parentMessageId, sender, text, isCreatedByUser }, { upsert: true, new: true }); - return { id, conversationId, parentMessageId, sender, text, isCreatedByUser }; + return { messageId, conversationId, parentMessageId, sender, text, isCreatedByUser }; } catch (error) { console.error(error); return { message: 'Error saving message' }; } }, - deleteMessagesSince: async ({ id, conversationId }) => { + deleteMessagesSince: async ({ messageId, conversationId }) => { try { - message = await Message.findOne({ id }).exec() + message = await Message.findOne({ messageId }).exec() if (message) return await Message.find({ conversationId }).deleteMany({ createdAt: { $gt: message.createdAt } }).exec(); diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index 9296f895d6..ea43392551 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -17,18 +17,26 @@ router.use('/bing', askBing); router.use('/sydney', askSydney); router.post('/', async (req, res) => { - let { id, model, text, parentMessageId, conversationId, chatGptLabel, promptPrefix } = req.body; + let { model, text, parentMessageId, conversationId, chatGptLabel, promptPrefix } = req.body; if (text.length === 0) { return handleError(res, 'Prompt empty or too short'); } - const userMessageId = id || crypto.randomUUID(); - let userMessage = { id: userMessageId, sender: 'User', text, parentMessageId, conversationId, isCreatedByUser: true }; + const userMessageId = crypto.randomUUID(); + const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' + let userMessage = { + messageId: userMessageId, + sender: 'User', + text, + parentMessageId: userParentMessageId, + conversationId, + isCreatedByUser: true + }; console.log('ask log', { model, ...userMessage, - parentMessageId, + parentMessageId: userParentMessageId, conversationId, chatGptLabel, promptPrefix @@ -53,11 +61,11 @@ router.post('/', async (req, res) => { } } - if (id) { - // existing conversation - await saveMessage(userMessage); - await deleteMessagesSince(userMessage); - } else {} + // if (messageId) { + // // existing conversation + // await saveMessage(userMessage); + // await deleteMessagesSince(userMessage); + // } else {} res.writeHead(200, { Connection: 'keep-alive', @@ -72,7 +80,6 @@ router.post('/', async (req, res) => { let tokens = ''; const progressCallback = async (partial) => { if (i === 0 && typeof partial === 'object') { - userMessage.parentMessageId = parentMessageId ? parentMessageId : partial.id; userMessage.conversationId = conversationId ? conversationId : partial.conversationId; await saveMessage(userMessage); sendMessage(res, { ...partial, initial: true }); @@ -101,7 +108,7 @@ router.post('/', async (req, res) => { text, progressCallback, convo: { - parentMessageId, + parentMessageId: userParentMessageId, conversationId }, chatGptLabel, @@ -112,9 +119,8 @@ router.post('/', async (req, res) => { if (!gptResponse.parentMessageId) { gptResponse.text = gptResponse.response; - gptResponse.id = gptResponse.messageId; - gptResponse.parentMessageId = gptResponse.messageId; - userMessage.parentMessageId = parentMessageId ? parentMessageId : gptResponse.messageId; + // gptResponse.id = gptResponse.messageId; + gptResponse.parentMessageId = userMessage.messageId; userMessage.conversationId = conversationId ? conversationId : gptResponse.conversationId; @@ -130,15 +136,15 @@ router.post('/', async (req, res) => { return handleError(res, 'Prompt empty or too short'); } - if (!parentMessageId) { - gptResponse.title = await titleConvo({ - model, - message: text, - response: JSON.stringify(gptResponse.text) - }); - } + // if (!parentMessageId) { + // gptResponse.title = await titleConvo({ + // model, + // message: text, + // response: JSON.stringify(gptResponse.text) + // }); + // } gptResponse.sender = model === 'chatgptCustom' ? chatGptLabel : model; - gptResponse.final = true; + // gptResponse.final = true; gptResponse.text = await detectCode(gptResponse.text); if (chatGptLabel?.length > 0 && model === 'chatgptCustom') { @@ -151,11 +157,15 @@ router.post('/', async (req, res) => { await saveMessage(gptResponse); await saveConvo(gptResponse); - sendMessage(res, gptResponse); + sendMessage(res, { + final: true, + requestMessage: userMessage, + responseMessage: gptResponse + }); res.end(); } catch (error) { console.log(error); - await deleteMessages({ id: userMessageId }); + await deleteMessages({ messageId: userMessageId }); handleError(res, error.message); } }); diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 1e670b6d41..08c086597d 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -7,22 +7,36 @@ const { handleError, sendMessage } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { - const { id, model, text, ...convo } = req.body; + const { model, text, parentMessageId, conversationId, ...convo } = req.body; if (text.length === 0) { return handleError(res, 'Prompt empty or too short'); } - const userMessageId = id || crypto.randomUUID(); - let userMessage = { id: userMessageId, sender: 'User', text, isCreatedByUser: true }; + const userMessageId = messageId; + const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' + let userMessage = { + messageId: userMessageId, + sender: 'User', + text, + parentMessageId: userParentMessageId, + conversationId, + isCreatedByUser: true + }; - console.log('ask log', { model, ...userMessage, ...convo }); + console.log('ask log', { + model, + ...userMessage, + parentMessageId: userParentMessageId, + conversationId, + ...convo + }); + + // if (messageId) { + // // existing conversation + // await saveMessage(userMessage); + // await deleteMessagesSince(userMessage); + // } else {} - if (id) { - // existing conversation - await saveMessage(userMessage); - await deleteMessagesSince(userMessage); - } else {} - res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream', @@ -43,7 +57,11 @@ router.post('/', async (req, res) => { let response = await askBing({ text, progressCallback, - convo + convo: { + parentMessageId: userParentMessageId, + conversationId, + ...convo + }, }); console.log('BING RESPONSE'); @@ -52,26 +70,27 @@ router.post('/', async (req, res) => { userMessage.conversationSignature = convo.conversationSignature || response.conversationSignature; - userMessage.conversationId = convo.conversationId || response.conversationId; + userMessage.conversationId = conversationId || response.conversationId; userMessage.invocationId = response.invocationId; await saveMessage(userMessage); - if (!convo.conversationSignature) { - response.title = await titleConvo({ - model, - message: text, - response: JSON.stringify(response.response) - }); - } + // if (!convo.conversationSignature) { + // 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.id = response.details.messageId; response.suggestions = response.details.suggestedResponses && response.details.suggestedResponses.map((s) => s.text); response.sender = model; - response.final = true; + response.parentMessageId = gptResponse.parentMessageId || userMessage.messageId + // response.final = true; const links = getCitations(response); response.text = @@ -80,11 +99,15 @@ router.post('/', async (req, res) => { await saveMessage(response); await saveConvo(response); - sendMessage(res, response); + sendMessage(res, { + final: true, + requestMessage: userMessage, + responseMessage: gptResponse + }); res.end(); } catch (error) { console.log(error); - await deleteMessages({ id: userMessageId }); + await deleteMessages({ messageId: userMessageId }); handleError(res, error.message); } }); diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 8bc4b8c54f..27284ec526 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -7,22 +7,37 @@ const { handleError, sendMessage } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { - const { id, model, text, ...convo } = req.body; + const { model, text, parentMessageId, conversationId, ...convo } = req.body; if (text.length === 0) { return handleError(res, 'Prompt empty or too short'); } - const userMessageId = id || crypto.randomUUID(); - let userMessage = { id: userMessageId, sender: 'User', text, isCreatedByUser: true }; + const userMessageId = messageId; + const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' + let userMessage = { + messageId: userMessageId, + sender: 'User', + text, + parentMessageId: userParentMessageId, + conversationId, + isCreatedByUser: true + }; - console.log('ask log', { model, ...userMessage, ...convo }); - if (id) { - // existing conversation - await saveMessage(userMessage); - await deleteMessagesSince(userMessage); - } else {} - + console.log('ask log', { + model, + ...userMessage, + parentMessageId: userParentMessageId, + conversationId, + ...convo + }); + + // if (messageId) { + // // existing conversation + // await saveMessage(userMessage); + // await deleteMessagesSince(userMessage); + // } else {} + res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream', @@ -43,7 +58,11 @@ router.post('/', async (req, res) => { let response = await askSydney({ text, progressCallback, - convo + convo: { + parentMessageId: userParentMessageId, + conversationId, + ...convo + }, }); console.log('SYDNEY RESPONSE'); @@ -52,19 +71,19 @@ router.post('/', async (req, res) => { const hasCitations = response.response.match(citationRegex)?.length > 0; // Save sydney response - response.id = response.messageId; + // 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 getConvoTitle(conversationId) : await titleConvo({ model, message: text, response: JSON.stringify(response.response) }); - response.conversationId = convo.conversationId - ? convo.conversationId + response.conversationId = conversationId + ? conversationId : crypto.randomUUID(); response.conversationSignature = convo.conversationSignature ? convo.conversationSignature @@ -75,7 +94,8 @@ router.post('/', async (req, res) => { response.details.suggestedResponses && response.details.suggestedResponses.map((s) => s.text); response.sender = model; - response.final = true; + response.parentMessageId = gptResponse.parentMessageId || userMessage.messageId + // response.final = true; const links = getCitations(response); response.text = @@ -84,17 +104,20 @@ router.post('/', async (req, res) => { // 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); + sendMessage(res, { + final: true, + requestMessage: userMessage, + responseMessage: gptResponse + }); res.end(); } catch (error) { console.log(error); - await deleteMessages({ id: userMessageId }); + await deleteMessages({ messageId: userMessageId }); handleError(res, error.message); } }); diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index bd127a34d2..460d6954f3 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -34,22 +34,36 @@ export default function TextChat({ messages }) { }; const convoHandler = (data, currentState) => { + const { requestMessage, responseMessage } = data; const { messages, currentMsg, message, isCustomModel, sender } = currentState; const { model, chatGptLabel, promptPrefix } = message; dispatch( - setMessages([...messages, currentMsg, { sender, text: data.text || data.response }]) + setMessages([...messages, + { + ...requestMessage, + // messageId: data?.parentMessageId, + }, + { + ...responseMessage, + // sender, + // text: data.text || data.response, + } + ]) ); const isBing = model === 'bingai' || model === 'sydney'; + // if (!message.messageId) + if (!isBing && convo.conversationId === null && convo.parentMessageId === null) { - const { title, conversationId, id } = data; + const { title } = data; + const { conversationId, messageId } = responseMessage; dispatch( setConversation({ title, conversationId, - parentMessageId: id, + parentMessageId: messageId, jailbreakConversationId: null, conversationSignature: null, clientId: null, @@ -64,7 +78,8 @@ export default function TextChat({ messages }) { convo.invocationId === null ) { console.log('Bing data:', data); - const { title, conversationSignature, clientId, conversationId, invocationId } = data; + const { title } = data; + const { conversationSignature, clientId, conversationId, invocationId } = responseMessage; dispatch( setConversation({ title, @@ -76,15 +91,15 @@ export default function TextChat({ messages }) { }) ); } else if (model === 'sydney') { + const { title } = data; const { - title, jailbreakConversationId, parentMessageId, conversationSignature, clientId, conversationId, invocationId - } = data; + } = responseMessage; dispatch( setConversation({ title, @@ -202,16 +217,19 @@ export default function TextChat({ messages }) { } const data = JSON.parse(e.data); - let text = data.text || data.response; - console.log(data) - if (data.message) { - messageHandler(text, currentState); - } + + // if (data.message) { + // messageHandler(text, currentState); + // } if (data.final) { convoHandler(data, currentState); console.log('final', data); } else { + let text = data.text || data.response; + if (data.message) { + messageHandler(text, currentState); + } // console.log('dataStream', data); } }; diff --git a/client/src/store/messageSlice.js b/client/src/store/messageSlice.js index c93f70048e..137c329822 100644 --- a/client/src/store/messageSlice.js +++ b/client/src/store/messageSlice.js @@ -14,7 +14,7 @@ const currentSlice = createSlice({ setEmptyMessage: (state) => { state.messages = [ { - id: '1', + messageId: '1', conversationId: '1', parentMessageId: '1', sender: '',