From dd1f74da72ff44d58e992e6f67f6c5cd4fa7ebab Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 05:20:35 +0800 Subject: [PATCH 01/47] replace all created to timestamps: true in db --- api/models/Conversation.js | 10 +++------- api/models/CustomGpt.js | 6 +----- api/models/Message.js | 11 ++++++----- api/models/Prompt.js | 6 +----- 4 files changed, 11 insertions(+), 22 deletions(-) diff --git a/api/models/Conversation.js b/api/models/Conversation.js index 1ff778e957..0ae336641c 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -38,11 +38,7 @@ const convoSchema = mongoose.Schema({ }, suggestions: [{ type: String }], messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }], - created: { - type: Date, - default: Date.now - } -}); +}, { timestamps: true }); const Conversation = mongoose.models.Conversation || mongoose.model('Conversation', convoSchema); @@ -85,14 +81,14 @@ module.exports = { return { message: 'Error updating conversation' }; } }, - // getConvos: async () => await Conversation.find({}).sort({ created: -1 }).exec(), + // getConvos: async () => await Conversation.find({}).sort({ createdAt: -1 }).exec(), getConvos: async (pageNumber = 1, pageSize = 12) => { try { const skip = (pageNumber - 1) * pageSize; // const limit = pageNumber * pageSize; const conversations = await Conversation.find({}) - .sort({ created: -1 }) + .sort({ createdAt: -1 }) .skip(skip) // .limit(limit) .limit(pageSize) diff --git a/api/models/CustomGpt.js b/api/models/CustomGpt.js index 33bb75b124..1f02bdc4db 100644 --- a/api/models/CustomGpt.js +++ b/api/models/CustomGpt.js @@ -12,11 +12,7 @@ const customGptSchema = mongoose.Schema({ type: String, required: true }, - created: { - type: Date, - default: Date.now - } -}); +}, { timestamps: true }); const CustomGpt = mongoose.models.CustomGpt || mongoose.model('CustomGpt', customGptSchema); diff --git a/api/models/Message.js b/api/models/Message.js index e6657c060a..b2017c2698 100644 --- a/api/models/Message.js +++ b/api/models/Message.js @@ -32,11 +32,12 @@ const messageSchema = mongoose.Schema({ type: String, required: true }, - created: { - type: Date, - default: Date.now + isCreatedByUser: { + type: Boolean, + required: true, + default: false } -}); +}, { timestamps: true }); const Message = mongoose.models.Message || mongoose.model('Message', messageSchema); @@ -58,7 +59,7 @@ module.exports = { }, getMessages: async (filter) => { try { - return await Message.find(filter).exec() + return await Message.find(filter).sort({createdAt: 1}).exec() } catch (error) { console.error(error); return { message: 'Error getting messages' }; diff --git a/api/models/Prompt.js b/api/models/Prompt.js index 612278d038..f122d5af40 100644 --- a/api/models/Prompt.js +++ b/api/models/Prompt.js @@ -12,11 +12,7 @@ const promptSchema = mongoose.Schema({ category: { type: String, }, - created: { - type: Date, - default: Date.now - } -}); +}, { timestamps: true }); const Prompt = mongoose.models.Prompt || mongoose.model('Prompt', promptSchema); From b9975ac283c1bb82a253118fe53b920d8019b2ed Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 05:21:30 +0800 Subject: [PATCH 02/47] fix: missing setSubmission --- client/src/components/Nav/ClearConvos.jsx | 1 + client/src/components/Nav/MobileNav.jsx | 1 + 2 files changed, 2 insertions(+) diff --git a/client/src/components/Nav/ClearConvos.jsx b/client/src/components/Nav/ClearConvos.jsx index 9df2e9344e..c9a9b379ba 100644 --- a/client/src/components/Nav/ClearConvos.jsx +++ b/client/src/components/Nav/ClearConvos.jsx @@ -5,6 +5,7 @@ import manualSWR from '~/utils/fetchers'; import { useDispatch } from 'react-redux'; import { setNewConvo, removeAll } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; +import { setSubmission } from '~/store/submitSlice'; export default function ClearConvos() { const dispatch = useDispatch(); diff --git a/client/src/components/Nav/MobileNav.jsx b/client/src/components/Nav/MobileNav.jsx index 2a5123560d..e4107e9ac6 100644 --- a/client/src/components/Nav/MobileNav.jsx +++ b/client/src/components/Nav/MobileNav.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { setNewConvo } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; +import { setSubmission } from '~/store/submitSlice'; import { setText } from '~/store/textSlice'; export default function MobileNav({ setNavVisible }) { From bdfc895800b27a9713118861d2892969c3c7b805 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 05:26:17 +0800 Subject: [PATCH 03/47] feat: support resubmit. TODO: basic implementation. should add multi-path record in future. feat: add deleteMessahesSince feat: saveMessage will do createOrSave feat: reorginazed submission --- api/models/Message.js | 23 +++- api/models/index.js | 3 +- api/server/routes/ask.js | 14 +- api/server/routes/askBing.js | 14 +- api/server/routes/askSydney.js | 14 +- client/src/components/Main/TextChat.jsx | 48 ++++--- .../src/components/Messages/HoverButtons.jsx | 4 +- client/src/components/Messages/Message.jsx | 124 +++++++++++++++--- client/src/components/Messages/index.jsx | 10 +- client/src/store/convoSlice.js | 2 +- 10 files changed, 189 insertions(+), 67 deletions(-) diff --git a/api/models/Message.js b/api/models/Message.js index b2017c2698..0e091e416c 100644 --- a/api/models/Message.js +++ b/api/models/Message.js @@ -42,21 +42,32 @@ const messageSchema = mongoose.Schema({ const Message = mongoose.models.Message || mongoose.model('Message', messageSchema); module.exports = { - saveMessage: async ({ id, conversationId, parentMessageId, sender, text }) => { + saveMessage: async ({ id, conversationId, parentMessageId, sender, text, isCreatedByUser=false }) => { try { - await Message.create({ - id, + await Message.findOneAndUpdate({ id }, { conversationId, parentMessageId, sender, - text - }); - return { id, conversationId, parentMessageId, sender, text }; + text, + isCreatedByUser + }, { upsert: true, new: true }); + return { id, conversationId, parentMessageId, sender, text, isCreatedByUser }; } catch (error) { console.error(error); return { message: 'Error saving message' }; } }, + deleteMessagesSince: async ({ id, conversationId }) => { + try { + message = await Message.findOne({ id }).exec() + + if (message) + return await Message.find({ conversationId }).deleteMany({ createdAt: { $gt: message.createdAt } }).exec(); + } catch (error) { + console.error(error); + return { message: 'Error deleting messages' }; + } + }, getMessages: async (filter) => { try { return await Message.find(filter).sort({createdAt: 1}).exec() diff --git a/api/models/index.js b/api/models/index.js index 8af5a1aa9c..4ed1285bbf 100644 --- a/api/models/index.js +++ b/api/models/index.js @@ -1,9 +1,10 @@ -const { saveMessage, deleteMessages } = require('./Message'); +const { saveMessage, deleteMessagesSince, deleteMessages } = require('./Message'); const { getCustomGpts, updateCustomGpt, updateByLabel, deleteCustomGpts } = require('./CustomGpt'); const { getConvoTitle, getConvo, saveConvo } = require('./Conversation'); module.exports = { saveMessage, + deleteMessagesSince, deleteMessages, getConvoTitle, getConvo, diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index 1f18082dd4..9296f895d6 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -10,20 +10,20 @@ const { customClient, detectCode } = require('../../app/'); -const { getConvo, saveMessage, deleteMessages, saveConvo } = require('../../models'); +const { getConvo, saveMessage, deleteMessagesSince, deleteMessages, saveConvo } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); router.use('/bing', askBing); router.use('/sydney', askSydney); router.post('/', async (req, res) => { - let { model, text, parentMessageId, conversationId, chatGptLabel, promptPrefix } = req.body; + let { id, model, text, parentMessageId, conversationId, chatGptLabel, promptPrefix } = req.body; if (text.length === 0) { return handleError(res, 'Prompt empty or too short'); } - const userMessageId = crypto.randomUUID(); - let userMessage = { id: userMessageId, sender: 'User', text }; + const userMessageId = id || crypto.randomUUID(); + let userMessage = { id: userMessageId, sender: 'User', text, parentMessageId, conversationId, isCreatedByUser: true }; console.log('ask log', { model, @@ -53,6 +53,12 @@ router.post('/', async (req, res) => { } } + if (id) { + // existing conversation + await saveMessage(userMessage); + await deleteMessagesSince(userMessage); + } else {} + res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream', diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 76c39f6804..1e670b6d41 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -2,21 +2,27 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); const { titleConvo, getCitations, citeText, askBing } = require('../../app/'); -const { saveMessage, deleteMessages, saveConvo } = require('../../models'); +const { saveMessage, deleteMessages, deleteMessagesSince, saveConvo } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { - const { model, text, ...convo } = req.body; + const { id, model, text, ...convo } = req.body; if (text.length === 0) { return handleError(res, 'Prompt empty or too short'); } - const userMessageId = crypto.randomUUID(); - let userMessage = { id: userMessageId, sender: 'User', text }; + const userMessageId = id || crypto.randomUUID(); + let userMessage = { id: userMessageId, sender: 'User', text, isCreatedByUser: true }; console.log('ask log', { model, ...userMessage, ...convo }); + if (id) { + // existing conversation + await saveMessage(userMessage); + await deleteMessagesSince(userMessage); + } else {} + res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream', diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 219f9b2437..8bc4b8c54f 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -2,21 +2,27 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); const { titleConvo, getCitations, citeText, askSydney } = require('../../app/'); -const { saveMessage, deleteMessages, saveConvo, getConvoTitle } = require('../../models'); +const { saveMessage, deleteMessages, saveConvo, deleteMessagesSince, getConvoTitle } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { - const { model, text, ...convo } = req.body; + const { id, model, text, ...convo } = req.body; if (text.length === 0) { return handleError(res, 'Prompt empty or too short'); } - const userMessageId = crypto.randomUUID(); - let userMessage = { id: userMessageId, sender: 'User', text }; + const userMessageId = id || crypto.randomUUID(); + let userMessage = { id: userMessageId, sender: 'User', text, isCreatedByUser: true }; console.log('ask log', { model, ...userMessage, ...convo }); + if (id) { + // existing conversation + await saveMessage(userMessage); + await deleteMessagesSince(userMessage); + } else {} + res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream', diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 12efc7c123..e73becc743 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -29,13 +29,14 @@ export default function TextChat({ messages }) { }, [convo?.conversationId, ]) const messageHandler = (data, currentState) => { - const { messages, currentMsg, sender } = currentState; + const { messages, currentMsg, message, sender } = currentState; dispatch(setMessages([...messages, currentMsg, { sender, text: data }])); }; const convoHandler = (data, currentState) => { - const { messages, currentMsg, sender, isCustomModel, model, chatGptLabel, promptPrefix } = + const { messages, currentMsg, message, isCustomModel, sender } = currentState; + const { model, chatGptLabel, promptPrefix } = message; dispatch( setMessages([...messages, currentMsg, { sender, text: data.text || data.response }]) ); @@ -111,7 +112,7 @@ export default function TextChat({ messages }) { setErrorMessage(event.data); dispatch(setSubmitState(false)); dispatch(setMessages([...messages.slice(0, -2), currentMsg, errorResponse])); - dispatch(setText(message)); + dispatch(setText(message?.text)); dispatch(setError(true)); return; }; @@ -127,7 +128,7 @@ export default function TextChat({ messages }) { const isCustomModel = model === 'chatgptCustom' || !initial[model]; const message = text.trim(); - const currentMsg = { sender: 'User', text: message, current: true }; + const currentMsg = { sender: 'User', text: message, current: true, isCreatedByUser: true }; const sender = model === 'chatgptCustom' ? chatGptLabel : model; const initialResponse = { sender, text: '' }; @@ -136,36 +137,41 @@ export default function TextChat({ messages }) { dispatch(setText('')); const submission = { - model, - text: message, - convo, - chatGptLabel, - promptPrefix, isCustomModel, - message, + message: { + sender: 'User', + text: message, + isCreatedByUser: true, + model, + chatGptLabel, + promptPrefix, + }, messages, currentMsg, + initialResponse, sender, - initialResponse }; console.log('User Input:', message); // handleSubmit(submission); dispatch(setSubmission(submission)); }; - const createPayload = ({ model, text, convo, chatGptLabel, promptPrefix }) => { + const createPayload = ({ convo, message }) => { const endpoint = `/api/ask`; - let payload = { model, text, chatGptLabel, promptPrefix }; - if (convo.conversationId && convo.parentMessageId) { - payload = { - ...payload, - conversationId: convo.conversationId, - parentMessageId: convo.parentMessageId - }; - } + let payload = { ...message }; + const { model } = message + + if (!payload.conversationId) + if (convo?.conversationId && convo?.parentMessageId) { + payload = { + ...payload, + conversationId: convo.conversationId, + parentMessageId: convo.parentMessageId + }; + } const isBing = model === 'bingai' || model === 'sydney'; - if (isBing && convo.conversationId) { + if (isBing && convo?.conversationId) { payload = { ...payload, jailbreakConversationId: convo.jailbreakConversationId, diff --git a/client/src/components/Messages/HoverButtons.jsx b/client/src/components/Messages/HoverButtons.jsx index 860023ed2f..35c5f14821 100644 --- a/client/src/components/Messages/HoverButtons.jsx +++ b/client/src/components/Messages/HoverButtons.jsx @@ -2,11 +2,11 @@ import React from 'react'; // import Clipboard from '../svg/Clipboard'; import EditIcon from '../svg/EditIcon'; -export default function HoverButtons({ user }) { +export default function HoverButtons({ user, onClick }) { return (
{user && ( - diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index 37f7a5c63b..257ae3e3d1 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -1,22 +1,36 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import TextWrapper from './TextWrapper'; -import { useSelector } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; import GPTIcon from '../svg/GPTIcon'; import BingIcon from '../svg/BingIcon'; import HoverButtons from './HoverButtons'; import Spinner from '../svg/Spinner'; +import { setError } from '~/store/convoSlice'; +import { setMessages } from '~/store/messageSlice'; +import { setSubmitState, setSubmission } from '~/store/submitSlice'; +import { setText } from '~/store/textSlice'; export default function Message({ - sender, - text, + message, + messages, last = false, - error = false, - scrollToBottom + scrollToBottom, + edit, + currentEditIdx, + enterEdit }) { - const { isSubmitting } = useSelector((state) => state.submit); + const { isSubmitting, model, chatGptLabel, promptPrefix } = useSelector((state) => state.submit); const [abortScroll, setAbort] = useState(false); - const notUser = sender.toLowerCase() !== 'user'; - const blinker = isSubmitting && last && notUser; + const { sender, text, isCreatedByUser, error } = message + const textEditor = useRef(null) + const convo = useSelector((state) => state.convo); + const { initial } = useSelector((state) => state.models); + const { error: convoError } = convo; + + const dispatch = useDispatch(); + + // const notUser = !isCreatedByUser; // sender.toLowerCase() !== 'user'; + const blinker = isSubmitting && last && !isCreatedByUser; useEffect(() => { if (blinker && !abortScroll) { @@ -53,12 +67,12 @@ export default function Message({ let icon = `${sender}:`; let backgroundColor = bgColors[sender]; - if (notUser) { + if (!isCreatedByUser) { props.className = '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) || isBing) { + if ((!isCreatedByUser && backgroundColor) || isBing) { icon = (
; + const resubmitMessage = () => { + const text = textEditor.current.innerHTML + + if (convoError) { + dispatch(setError(false)); + } + + if (!!isSubmitting || text.trim() === '') { + return; + } + + const isCustomModel = model === 'chatgptCustom' || !initial[model]; + const currentMsg = { ...message, sender: 'User', text: text.trim(), current: true, isCreatedByUser: true }; + console.log(model) + const sender = model === 'chatgptCustom' ? chatGptLabel : model; + + const initialResponse = { sender, text: '' }; + + dispatch(setSubmitState(true)); + dispatch(setMessages([...messages.slice(0, currentEditIdx), currentMsg, initialResponse])); + dispatch(setText('')); + + const submission = { + isCustomModel, + message: { + ...message, + text: text.trim(), + model, + chatGptLabel, + promptPrefix, + }, + messages: messages.slice(0, currentEditIdx), + currentMsg, + initialResponse, + sender, + }; + console.log('User Input:', message); + // handleSubmit(submission); + dispatch(setSubmission(submission)); + + enterEdit(true); + }; + return (
{error ? ( -
+
{text}
- ) : ( -
- {/*
*/} -
- {notUser ? wrapText(text) : text} - {blinker && } + ) : + edit ? ( +
+ {/*
*/} + +
+ {text} +
+
+ + +
-
- )} + ) : ( +
+ {/*
*/} +
+ {!isCreatedByUser ? wrapText(text) : text} + {blinker && } +
+
+ )}
- + enterEdit()}/>
diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index c6457d6c62..7d9bd69d44 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -4,10 +4,12 @@ import ScrollToBottom from './ScrollToBottom'; import Message from './Message'; const Messages = ({ messages }) => { + const [currentEditIdx, setCurrentEditIdx] = useState(-1) const [showScrollButton, setShowScrollButton] = useState(false); const scrollableRef = useRef(null); const messagesEndRef = useRef(null); + useEffect(() => { const timeoutId = setTimeout(() => { const scrollable = scrollableRef.current; @@ -60,11 +62,13 @@ const Messages = ({ messages }) => { {messages.map((message, i) => ( setCurrentEditIdx(cancel?-1:i)} /> ))} c.conversationId === convo.conversationId); }); state.convos = [...state.convos, ...newConvos].sort( - (a, b) => new Date(b.created) - new Date(a.created) + (a, b) => new Date(b.createdAt) - new Date(a.createdAt) ); }, removeConvo: (state, action) => { From 6d51ec3e3761a065dc7d45c6ea7f982f5d5ed783 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 05:42:49 +0800 Subject: [PATCH 04/47] feat: auto hide edit button when edit is enabled. --- client/src/components/Messages/Message.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index 257ae3e3d1..6b313961f0 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -193,7 +193,7 @@ export default function Message({
)}
- enterEdit()}/> + enterEdit()}/>
From be71140dd49404fb68f5f005e4f999f47de1b328 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 10:20:07 +0800 Subject: [PATCH 05/47] fix: new message should append to the exist one --- client/src/components/Main/TextChat.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index e73becc743..bd127a34d2 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -137,6 +137,7 @@ export default function TextChat({ messages }) { dispatch(setText('')); const submission = { + convo, isCustomModel, message: { sender: 'User', @@ -202,6 +203,7 @@ 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); } 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 06/47] 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: '', From 5a409ccfa62041c154b0e5735d59b117b8d3dfeb Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 12:36:00 +0800 Subject: [PATCH 07/47] fix: don't resubmit html label fix: hide the resubmit editor border --- client/src/components/Messages/Message.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index 6b313961f0..2d6df0360e 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -95,7 +95,7 @@ export default function Message({ const wrapText = (text) => ; const resubmitMessage = () => { - const text = textEditor.current.innerHTML + const text = textEditor.current.innerText if (convoError) { dispatch(setError(false)); @@ -163,7 +163,7 @@ export default function Message({
{/*
*/} -
{text}
From 8773878be2c72c733f763b59c324c4e7480d1565 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 13:11:53 +0800 Subject: [PATCH 08/47] feat: create conversation at the beginning then return the userMessage --- api/server/routes/ask.js | 18 ++++++------- api/server/routes/askBing.js | 18 ++++++------- api/server/routes/askSydney.js | 18 ++++++------- .../components/Conversations/Conversation.jsx | 9 ++++--- client/src/components/Conversations/index.jsx | 1 + client/src/components/Main/TextChat.jsx | 26 ++++++++++++++----- 6 files changed, 50 insertions(+), 40 deletions(-) diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index ea43392551..9be3fe7014 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -17,11 +17,13 @@ router.use('/bing', askBing); router.use('/sydney', askSydney); router.post('/', async (req, res) => { - let { model, text, parentMessageId, conversationId, chatGptLabel, promptPrefix } = req.body; + let { model, text, parentMessageId, conversationId: oldConversationId , chatGptLabel, promptPrefix } = req.body; if (text.length === 0) { return handleError(res, 'Prompt empty or too short'); } + const conversationId = oldConversationId || crypto.randomUUID(); + const userMessageId = crypto.randomUUID(); const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' let userMessage = { @@ -36,8 +38,6 @@ router.post('/', async (req, res) => { console.log('ask log', { model, ...userMessage, - parentMessageId: userParentMessageId, - conversationId, chatGptLabel, promptPrefix }); @@ -61,12 +61,6 @@ router.post('/', async (req, res) => { } } - // if (messageId) { - // // existing conversation - // await saveMessage(userMessage); - // await deleteMessagesSince(userMessage); - // } else {} - res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream', @@ -75,6 +69,10 @@ router.post('/', async (req, res) => { 'X-Accel-Buffering': 'no' }); + await saveMessage(userMessage); + await saveConvo({ ...userMessage, model, chatGptLabel, promptPrefix }); + sendMessage(res, { message: userMessage, created: true }); + try { let i = 0; let tokens = ''; @@ -165,7 +163,7 @@ router.post('/', async (req, res) => { res.end(); } catch (error) { console.log(error); - await deleteMessages({ messageId: userMessageId }); + // await deleteMessages({ messageId: userMessageId }); handleError(res, error.message); } }); diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 08c086597d..cffb669ba8 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -7,10 +7,12 @@ const { handleError, sendMessage } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { - const { model, text, parentMessageId, conversationId, ...convo } = req.body; + const { model, text, parentMessageId, conversationId: oldConversationId, ...convo } = req.body; if (text.length === 0) { return handleError(res, 'Prompt empty or too short'); } + + const conversationId = oldConversationId || crypto.randomUUID(); const userMessageId = messageId; const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' @@ -26,17 +28,9 @@ router.post('/', async (req, res) => { 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', @@ -45,6 +39,10 @@ router.post('/', async (req, res) => { 'X-Accel-Buffering': 'no' }); + await saveMessage(userMessage); + await saveConvo({ ...userMessage, model, chatGptLabel, promptPrefix }); + sendMessage(res, { message: userMessage, created: true }); + try { let tokens = ''; const progressCallback = async (partial) => { @@ -107,7 +105,7 @@ router.post('/', async (req, res) => { res.end(); } catch (error) { console.log(error); - await deleteMessages({ messageId: userMessageId }); + // await deleteMessages({ messageId: userMessageId }); handleError(res, error.message); } }); diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 27284ec526..a4f1b2b866 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -7,10 +7,12 @@ const { handleError, sendMessage } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { - const { model, text, parentMessageId, conversationId, ...convo } = req.body; + const { model, text, parentMessageId, conversationId: oldConversationId, ...convo } = req.body; if (text.length === 0) { return handleError(res, 'Prompt empty or too short'); } + + const conversationId = oldConversationId || crypto.randomUUID(); const userMessageId = messageId; const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' @@ -27,17 +29,9 @@ router.post('/', async (req, res) => { 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', @@ -46,6 +40,10 @@ router.post('/', async (req, res) => { 'X-Accel-Buffering': 'no' }); + await saveMessage(userMessage); + await saveConvo({ ...userMessage, model, chatGptLabel, promptPrefix }); + sendMessage(res, { message: userMessage, created: true }); + try { let tokens = ''; const progressCallback = async (partial) => { @@ -117,7 +115,7 @@ router.post('/', async (req, res) => { res.end(); } catch (error) { console.log(error); - await deleteMessages({ messageId: userMessageId }); + // await deleteMessages({ messageId: userMessageId }); handleError(res, error.message); } }); diff --git a/client/src/components/Conversations/Conversation.jsx b/client/src/components/Conversations/Conversation.jsx index 34e384f624..30df587da9 100644 --- a/client/src/components/Conversations/Conversation.jsx +++ b/client/src/components/Conversations/Conversation.jsx @@ -11,6 +11,7 @@ import ConvoIcon from '../svg/ConvoIcon'; export default function Conversation({ id, + model, parentMessageId, conversationId, title = 'New conversation', @@ -76,12 +77,12 @@ export default function Conversation({ if (chatGptLabel) { dispatch(setModel('chatgptCustom')); } else { - dispatch(setModel(data[1].sender)); + dispatch(setModel(model)); } - if (modelMap[data[1].sender.toLowerCase()]) { - console.log('sender', data[1].sender); - dispatch(setCustomModel(data[1].sender.toLowerCase())); + if (modelMap[model.toLowerCase()]) { + console.log('sender', model); + dispatch(setCustomModel(model.toLowerCase())); } else { dispatch(setCustomModel(null)); } diff --git a/client/src/components/Conversations/index.jsx b/client/src/components/Conversations/index.jsx index 6fcac5e27f..d3ab1ffd0b 100644 --- a/client/src/components/Conversations/index.jsx +++ b/client/src/components/Conversations/index.jsx @@ -26,6 +26,7 @@ export default function Conversations({ conversations, conversationId, showMore { - const { messages, currentMsg, message, sender } = currentState; + const messageHandler = (data, currentState, currentMsg) => { + const { messages, _currentMsg, message, sender } = currentState; + dispatch(setMessages([...messages, currentMsg, { sender, text: data }])); }; - const convoHandler = (data, currentState) => { + const createdHandler = (data, currentState, currentMsg) => { + const { conversationId } = currentMsg; + dispatch( + setConversation({ + conversationId, + }) + ); + }; + + const convoHandler = (data, currentState, currentMsg) => { const { requestMessage, responseMessage } = data; - const { messages, currentMsg, message, isCustomModel, sender } = + const { messages, _currentMsg, message, isCustomModel, sender } = currentState; const { model, chatGptLabel, promptPrefix } = message; dispatch( @@ -210,6 +220,7 @@ export default function TextChat({ messages }) { } const currentState = submission; + let currentMsg = currentState.currentMsg; const { server, payload } = createPayload(submission); const onMessage = (e) => { if (stopStream) { @@ -223,12 +234,15 @@ export default function TextChat({ messages }) { // } if (data.final) { - convoHandler(data, currentState); + convoHandler(data, currentState, currentMsg); console.log('final', data); + } if (data.created) { + currentMsg = data.message; + createdHandler(data, currentState, currentMsg); } else { let text = data.text || data.response; if (data.message) { - messageHandler(text, currentState); + messageHandler(text, currentState, currentMsg); } // console.log('dataStream', data); } From 9f8e9cb091022a1e11bceb86ec0fd93e1fe58f26 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 14:04:47 +0800 Subject: [PATCH 09/47] feat: gen title by sperate api call feat: fix: rename of convo should based on real request. --- api/server/routes/ask.js | 3 ++- api/server/routes/askBing.js | 5 ++-- api/server/routes/askSydney.js | 5 ++-- api/server/routes/convos.js | 26 ++++++++++++++++++ .../components/Conversations/Conversation.jsx | 11 +++++--- client/src/components/Main/TextChat.jsx | 27 ++++++++++--------- client/src/components/Nav/index.jsx | 5 ++-- client/src/store/convoSlice.js | 11 ++++---- 8 files changed, 65 insertions(+), 28 deletions(-) diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index 9be3fe7014..d21f8be263 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -10,7 +10,7 @@ const { customClient, detectCode } = require('../../app/'); -const { getConvo, saveMessage, deleteMessagesSince, deleteMessages, saveConvo } = require('../../models'); +const { getConvo, saveMessage, getConvoTitle, saveConvo } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); router.use('/bing', askBing); @@ -156,6 +156,7 @@ router.post('/', async (req, res) => { await saveMessage(gptResponse); await saveConvo(gptResponse); sendMessage(res, { + title: await getConvoTitle(conversationId), final: true, requestMessage: userMessage, responseMessage: gptResponse diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index cffb669ba8..f2722bb248 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -2,7 +2,7 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); const { titleConvo, getCitations, citeText, askBing } = require('../../app/'); -const { saveMessage, deleteMessages, deleteMessagesSince, saveConvo } = require('../../models'); +const { saveMessage, getConvoTitle, saveConvo } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; @@ -11,7 +11,7 @@ router.post('/', async (req, res) => { if (text.length === 0) { return handleError(res, 'Prompt empty or too short'); } - + const conversationId = oldConversationId || crypto.randomUUID(); const userMessageId = messageId; @@ -98,6 +98,7 @@ router.post('/', async (req, res) => { await saveMessage(response); await saveConvo(response); sendMessage(res, { + title: await getConvoTitle(conversationId), final: true, requestMessage: userMessage, responseMessage: gptResponse diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index a4f1b2b866..a90dd39555 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, getCitations, citeText, askSydney } = require('../../app/'); -const { saveMessage, deleteMessages, saveConvo, deleteMessagesSince, getConvoTitle } = require('../../models'); +const { saveMessage, saveConvo, getConvoTitle } = require('../../models'); const { handleError, sendMessage } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; @@ -11,7 +11,7 @@ router.post('/', async (req, res) => { if (text.length === 0) { return handleError(res, 'Prompt empty or too short'); } - + const conversationId = oldConversationId || crypto.randomUUID(); const userMessageId = messageId; @@ -108,6 +108,7 @@ router.post('/', async (req, res) => { await saveMessage(response); await saveConvo(response); sendMessage(res, { + title: await getConvoTitle(conversationId), final: true, requestMessage: userMessage, responseMessage: gptResponse diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index 4b9320873f..a82242db4a 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -1,12 +1,38 @@ const express = require('express'); const router = express.Router(); +const { titleConvo } = require('../../app/'); +const { getConvo, saveConvo, getConvoTitle } = require('../../models'); const { getConvos, deleteConvos, updateConvo } = require('../../models/Conversation'); +const { getMessages } = require('../../models/Message'); router.get('/', async (req, res) => { const pageNumber = req.query.pageNumber || 1; res.status(200).send(await getConvos(pageNumber)); }); +router.post('/gen_title', async (req, res) => { + const { conversationId } = req.body.arg; + + const convo = await getConvo(conversationId) + const firstMessage = (await getMessages({ conversationId }))[0] + const secondMessage = (await getMessages({ conversationId }))[1] + + const title = convo.jailbreakConversationId + ? await getConvoTitle(conversationId) + : await titleConvo({ + model: convo?.model, + message: firstMessage?.text, + response: JSON.stringify(secondMessage?.text || '') + }); + + await saveConvo({ + conversationId, + title + }) + + res.status(200).send(title); +}); + router.post('/clear', async (req, res) => { let filter = {}; const { conversationId } = req.body.arg; diff --git a/client/src/components/Conversations/Conversation.jsx b/client/src/components/Conversations/Conversation.jsx index 30df587da9..4d90ecb2cc 100644 --- a/client/src/components/Conversations/Conversation.jsx +++ b/client/src/components/Conversations/Conversation.jsx @@ -8,13 +8,14 @@ import { setMessages, setEmptyMessage } from '~/store/messageSlice'; import { setText } from '~/store/textSlice'; import manualSWR from '~/utils/fetchers'; import ConvoIcon from '../svg/ConvoIcon'; +import { refreshConversation } from '../../store/convoSlice'; export default function Conversation({ id, model, parentMessageId, conversationId, - title = 'New conversation', + title, chatGptLabel = null, promptPrefix = null, bingData, @@ -95,6 +96,7 @@ export default function Conversation({ const renameHandler = (e) => { e.preventDefault(); + setTitleInput(title); setRenaming(true); setTimeout(() => { inputRef.current.focus(); @@ -112,7 +114,10 @@ export default function Conversation({ if (titleInput === title) { return; } - rename.trigger({ conversationId, title: titleInput }); + rename.trigger({ conversationId, title: titleInput }) + .then(() => { + dispatch(refreshConversation()) + }); }; const handleKeyDown = (e) => { @@ -149,7 +154,7 @@ export default function Conversation({ onKeyDown={handleKeyDown} /> ) : ( - titleInput + title )}
{conversationId === id ? ( diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 69f4b65898..b541c9a51f 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -1,5 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; import { SSE } from '~/utils/sse'; +import axios from 'axios'; import SubmitButton from './SubmitButton'; import Regenerate from './Regenerate'; import ModelMenu from '../Models/ModelMenu'; @@ -7,10 +8,11 @@ import Footer from './Footer'; import TextareaAutosize from 'react-textarea-autosize'; import handleSubmit from '~/utils/handleSubmit'; import { useSelector, useDispatch } from 'react-redux'; -import { setConversation, setError } from '~/store/convoSlice'; +import { setConversation, setError, refreshConversation } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; import { setSubmitState, setSubmission } from '~/store/submitSlice'; import { setText } from '~/store/textSlice'; +import manualSWR from '~/utils/fetchers'; export default function TextChat({ messages }) { const [errorMessage, setErrorMessage] = useState(''); @@ -22,6 +24,7 @@ export default function TextChat({ messages }) { useSelector((state) => state.submit); const { text } = useSelector((state) => state.text); const { error } = convo; + const genTitle = manualSWR(`/api/convos/gen_title`, 'post'); // auto focus to input, when enter a conversation. useEffect(() => { @@ -45,26 +48,24 @@ export default function TextChat({ messages }) { const convoHandler = (data, currentState, currentMsg) => { const { requestMessage, responseMessage } = data; + const { conversationId } = currentMsg; const { messages, _currentMsg, message, isCustomModel, sender } = currentState; const { model, chatGptLabel, promptPrefix } = message; dispatch( - setMessages([...messages, - { - ...requestMessage, - // messageId: data?.parentMessageId, - }, - { - ...responseMessage, - // sender, - // text: data.text || data.response, - } - ]) + setMessages([...messages, requestMessage, responseMessage,]) ); const isBing = model === 'bingai' || model === 'sydney'; - // if (!message.messageId) + if (requestMessage.parentMessageId == '00000000-0000-0000-0000-000000000000') { + genTitle.trigger({ conversationId }).then((ret) => { + const title = ret?.data + + if (title) + dispatch(refreshConversation()); + }) + } if (!isBing && convo.conversationId === null && convo.parentMessageId === null) { const { title } = data; diff --git a/client/src/components/Nav/index.jsx b/client/src/components/Nav/index.jsx index 7d548429d9..3029686cf8 100644 --- a/client/src/components/Nav/index.jsx +++ b/client/src/components/Nav/index.jsx @@ -11,7 +11,7 @@ import { incrementPage, setConvos } from '~/store/convoSlice'; export default function Nav({ navVisible, setNavVisible }) { const dispatch = useDispatch(); const [isHovering, setIsHovering] = useState(false); - const { conversationId, convos, pageNumber } = useSelector((state) => state.convo); + const { conversationId, convos, pageNumber, refreshConvoHint } = useSelector((state) => state.convo); const onSuccess = (data) => { dispatch(setConvos(data)); }; @@ -20,6 +20,7 @@ export default function Nav({ navVisible, setNavVisible }) { `/api/convos?pageNumber=${pageNumber}`, onSuccess ); + const containerRef = useRef(null); const scrollPositionRef = useRef(null); @@ -35,7 +36,7 @@ export default function Nav({ navVisible, setNavVisible }) { } }; - useDidMountEffect(() => mutate(), [conversationId]); + useDidMountEffect(() => mutate(), [conversationId, refreshConvoHint]); useEffect(() => { const container = containerRef.current; diff --git a/client/src/store/convoSlice.js b/client/src/store/convoSlice.js index 4e55fa2b94..258de84e70 100644 --- a/client/src/store/convoSlice.js +++ b/client/src/store/convoSlice.js @@ -13,6 +13,7 @@ const initialState = { promptPrefix: null, convosLoading: false, pageNumber: 1, + refreshConvoHint: 0, convos: [] }; @@ -20,6 +21,9 @@ const currentSlice = createSlice({ name: 'convo', initialState, reducers: { + refreshConversation: (state, action) => { + state.refreshConvoHint = state.refreshConvoHint + 1; + }, setConversation: (state, action) => { return { ...state, ...action.payload }; }, @@ -44,10 +48,7 @@ const currentSlice = createSlice({ state.pageNumber = 1; }, setConvos: (state, action) => { - const newConvos = action.payload.filter((convo) => { - return !state.convos.some((c) => c.conversationId === convo.conversationId); - }); - state.convos = [...state.convos, ...newConvos].sort( + state.convos = action.payload.sort( (a, b) => new Date(b.createdAt) - new Date(a.createdAt) ); }, @@ -60,7 +61,7 @@ const currentSlice = createSlice({ } }); -export const { setConversation, setConvos, setNewConvo, setError, incrementPage, removeConvo, removeAll } = +export const { refreshConversation, setConversation, setConvos, setNewConvo, setError, incrementPage, removeConvo, removeAll } = currentSlice.actions; export default currentSlice.reducer; From 0e98cb4206d99807e0f2185069a18286b380226e Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 14:05:08 +0800 Subject: [PATCH 10/47] fix: in mobile view, resubmit edit button should always visible --- client/src/components/Messages/HoverButtons.jsx | 2 +- client/src/mobile.css | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/components/Messages/HoverButtons.jsx b/client/src/components/Messages/HoverButtons.jsx index 35c5f14821..f24027118e 100644 --- a/client/src/components/Messages/HoverButtons.jsx +++ b/client/src/components/Messages/HoverButtons.jsx @@ -6,7 +6,7 @@ export default function HoverButtons({ user, onClick }) { return (
{user && ( - diff --git a/client/src/mobile.css b/client/src/mobile.css index 78fff342c5..e0e3988410 100644 --- a/client/src/mobile.css +++ b/client/src/mobile.css @@ -30,6 +30,10 @@ margin-left: 20px; } + .resubmit-edit-button { + display: block; + } + .nav { position: fixed; z-index: 999; From 90dc171b34ed27ac43283c902cd07f5f5b0b2b40 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 14:08:42 +0800 Subject: [PATCH 11/47] test: generate of title shouldn't be mislead by answer --- api/server/routes/convos.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index a82242db4a..5862919f17 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -22,7 +22,7 @@ router.post('/gen_title', async (req, res) => { : await titleConvo({ model: convo?.model, message: firstMessage?.text, - response: JSON.stringify(secondMessage?.text || '') + // response: JSON.stringify(secondMessage?.text || '') }); await saveConvo({ From 4a39965b22d1ab0feab0baf74e274ee60dfcd460 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 15:55:18 +0800 Subject: [PATCH 12/47] fix: add proxy to titleConvo --- api/app/titleConvo.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/api/app/titleConvo.js b/api/app/titleConvo.js index e0461eabaf..52e35b841a 100644 --- a/api/app/titleConvo.js +++ b/api/app/titleConvo.js @@ -1,5 +1,21 @@ const { Configuration, OpenAIApi } = require('openai'); +const proxyEnvToAxiosProxy = (proxyString) => { + if (!proxyString) return null; + + const regex = /^([^:]+):\/\/(?:([^:@]*):?([^:@]*)@)?([^:]+)(?::(\d+))?/; + const [, protocol, username, password, host, port] = proxyString.match(regex); + const proxyConfig = { + protocol, + host, + port: port ? parseInt(port) : undefined, + auth: username && password ? { username, password } : undefined + }; + + return proxyConfig +} +console.log(proxyEnvToAxiosProxy(process.env.PROXY || null)) + const titleConvo = async ({ message, response, model }) => { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY @@ -15,7 +31,7 @@ const titleConvo = async ({ message, response, model }) => { }, { role: 'user', content: `In 5 words or less, summarize the conversation below with a title in title case using the language the user writes in. Don't refer to the participants of the conversation by name. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title. Conversation:\n\nUser: "${message}"\n\n${model}: "${response}"\n\nTitle: ` }, ] - }); + }, { proxy: proxyEnvToAxiosProxy(process.env.PROXY || null) }); //eslint-disable-next-line return completion.data.choices[0].message.content.replace(/["\.]/g, ''); From a4d5f6a3f2dcc78478129f1f9368b1e4ce982e1c Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 21:44:30 +0800 Subject: [PATCH 13/47] feat: fully multipath and resubmit --- api/server/routes/convos.js | 2 +- client/src/components/Main/TextChat.jsx | 14 +- client/src/components/Messages/Message.jsx | 191 ++++++++++++------ .../src/components/Messages/SiblingSwitch.jsx | 26 +++ client/src/components/Messages/index.jsx | 53 +++-- 5 files changed, 197 insertions(+), 89 deletions(-) create mode 100644 client/src/components/Messages/SiblingSwitch.jsx diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index 5862919f17..a82242db4a 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -22,7 +22,7 @@ router.post('/gen_title', async (req, res) => { : await titleConvo({ model: convo?.model, message: firstMessage?.text, - // response: JSON.stringify(secondMessage?.text || '') + response: JSON.stringify(secondMessage?.text || '') }); await saveConvo({ diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index b541c9a51f..d1a4ed511b 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -34,7 +34,7 @@ export default function TextChat({ messages }) { const messageHandler = (data, currentState, currentMsg) => { const { messages, _currentMsg, message, sender } = currentState; - dispatch(setMessages([...messages, currentMsg, { sender, text: data }])); + dispatch(setMessages([...messages, currentMsg, { sender, text: data, parentMessageId: currentMsg?.messageId, messageId: currentMsg?.messageId + '_' }])); }; const createdHandler = (data, currentState, currentMsg) => { @@ -152,11 +152,13 @@ export default function TextChat({ messages }) { return; } + // this is not a real messageId, it is used as placeholder before real messageId returned + const fakeMessageId = crypto.randomUUID(); const isCustomModel = model === 'chatgptCustom' || !initial[model]; const message = text.trim(); - const currentMsg = { sender: 'User', text: message, current: true, isCreatedByUser: true }; + const currentMsg = { sender: 'User', text: message, current: true, isCreatedByUser: true, parentMessageId: convo.parentMessageId || '00000000-0000-0000-0000-000000000000', messageId: fakeMessageId }; const sender = model === 'chatgptCustom' ? chatGptLabel : model; - const initialResponse = { sender, text: '' }; + const initialResponse = { sender, text: '', parentMessageId: fakeMessageId }; dispatch(setSubmitState(true)); dispatch(setMessages([...messages, currentMsg, initialResponse])); @@ -166,9 +168,7 @@ export default function TextChat({ messages }) { convo, isCustomModel, message: { - sender: 'User', - text: message, - isCreatedByUser: true, + ...currentMsg, model, chatGptLabel, promptPrefix, @@ -193,7 +193,7 @@ export default function TextChat({ messages }) { payload = { ...payload, conversationId: convo.conversationId, - parentMessageId: convo.parentMessageId + parentMessageId: convo.parentMessageId || '00000000-0000-0000-0000-000000000000' }; } diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index 2d6df0360e..1eff8c7d98 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -4,20 +4,59 @@ import { useSelector, useDispatch } from 'react-redux'; import GPTIcon from '../svg/GPTIcon'; import BingIcon from '../svg/BingIcon'; import HoverButtons from './HoverButtons'; +import SiblingSwitch from './SiblingSwitch'; import Spinner from '../svg/Spinner'; import { setError } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; import { setSubmitState, setSubmission } from '~/store/submitSlice'; import { setText } from '~/store/textSlice'; +import { setConversation } from '../../store/convoSlice'; + +const MultiMessage = ({ + messageList, + messages, + scrollToBottom, + currentEditId, + setCurrentEditId +}) => { + const [siblingIdx, setSiblingIdx] = useState(0) + + const setSiblingIdxRev = (value) => { + setSiblingIdx(messageList?.length - value - 1) + } + + if (!messageList?.length) return null; + + if (siblingIdx >= messageList?.length) { + setSiblingIdx(0) + return null + } + + return +} + +export { MultiMessage }; export default function Message({ message, messages, - last = false, scrollToBottom, - edit, - currentEditIdx, - enterEdit + currentEditId, + setCurrentEditId, + siblingIdx, + siblingCount, + setSiblingIdx }) { const { isSubmitting, model, chatGptLabel, promptPrefix } = useSelector((state) => state.submit); const [abortScroll, setAbort] = useState(false); @@ -26,6 +65,9 @@ export default function Message({ const convo = useSelector((state) => state.convo); const { initial } = useSelector((state) => state.models); const { error: convoError } = convo; + const last = !message?.children?.length + + const edit = message.messageId == currentEditId; const dispatch = useDispatch(); @@ -37,11 +79,18 @@ export default function Message({ scrollToBottom(); } }, [isSubmitting, text, blinker, scrollToBottom, abortScroll]); - + + useEffect(() => { + if (last) + dispatch(setConversation({parentMessageId: message?.messageId})) + }, [last, ]) + if (sender === '') { return ; } + const enterEdit = (cancel) => setCurrentEditId(cancel?-1:message.messageId) + const handleWheel = () => { if (blinker) { setAbort(true); @@ -105,97 +154,109 @@ export default function Message({ return; } + // this is not a real messageId, it is used as placeholder before real messageId returned + const fakeMessageId = crypto.randomUUID(); const isCustomModel = model === 'chatgptCustom' || !initial[model]; - const currentMsg = { ...message, sender: 'User', text: text.trim(), current: true, isCreatedByUser: true }; - console.log(model) + const currentMsg = { ...message, sender: 'User', text: text.trim(), current: true, isCreatedByUser: true, messageId: fakeMessageId }; const sender = model === 'chatgptCustom' ? chatGptLabel : model; - const initialResponse = { sender, text: '' }; + const initialResponse = { sender, text: '', parentMessageId: fakeMessageId }; dispatch(setSubmitState(true)); - dispatch(setMessages([...messages.slice(0, currentEditIdx), currentMsg, initialResponse])); + dispatch(setMessages([...messages, currentMsg, initialResponse])); dispatch(setText('')); const submission = { isCustomModel, message: { - ...message, - text: text.trim(), + ...currentMsg, model, chatGptLabel, promptPrefix, }, - messages: messages.slice(0, currentEditIdx), + messages: messages, currentMsg, initialResponse, sender, }; - console.log('User Input:', message); + console.log('User Input:', currentMsg?.text); // handleSubmit(submission); dispatch(setSubmission(submission)); + setSiblingIdx(siblingCount - 1) enterEdit(true); }; return ( -
-
- - {typeof icon === 'string' && icon.match(/[^\u0000-\u007F]+/) ? ( - {icon} - ) : ( - icon - )} - -
-
- {error ? ( -
-
- {text} -
-
- ) : - edit ? ( -
- {/*
*/} - -
+ <> +
+
+ +
+ {typeof icon === 'string' && icon.match(/[^\u0000-\u007F]+/) ? ( + {icon} + ) : ( + icon + )} + +
+
+
+ {error ? ( +
+
{text}
-
- - -
- ) : ( -
- {/*
*/} -
- {!isCreatedByUser ? wrapText(text) : text} - {blinker && } + ) : + edit ? ( +
+ {/*
*/} + +
+ {text} +
+
+ + +
-
- )} + ) : ( +
+ {/*
*/} +
+ {!isCreatedByUser ? wrapText(text) : text} + {blinker && } +
+
+ )} +
+ enterEdit()}/>
- enterEdit()}/>
-
+ + ); } diff --git a/client/src/components/Messages/SiblingSwitch.jsx b/client/src/components/Messages/SiblingSwitch.jsx new file mode 100644 index 0000000000..b5586d6a8c --- /dev/null +++ b/client/src/components/Messages/SiblingSwitch.jsx @@ -0,0 +1,26 @@ +import React from 'react'; + +export default function SiblingSwitch({ + siblingIdx, + siblingCount, + setSiblingIdx +}) { + const previous = () => { + setSiblingIdx(siblingIdx - 1); + } + + const next = () => { + setSiblingIdx(siblingIdx + 1); + } + return siblingCount > 1 ? ( +
+ + {siblingIdx + 1}/{siblingCount} + +
+ ):null; +} diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index 7d9bd69d44..5d191e61cf 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -1,15 +1,17 @@ -import React, { useEffect, useState, useRef } from 'react'; +import React, { useEffect, useState, useRef, useMemo } from 'react'; import { CSSTransition } from 'react-transition-group'; import ScrollToBottom from './ScrollToBottom'; -import Message from './Message'; +import { MultiMessage } from './Message'; +import Conversation from '../Conversations/Conversation'; +import { useSelector } from 'react-redux'; const Messages = ({ messages }) => { - const [currentEditIdx, setCurrentEditIdx] = useState(-1) + const [currentEditId, setCurrentEditId] = useState(-1) + const { conversationId } = useSelector((state) => state.convo); const [showScrollButton, setShowScrollButton] = useState(false); const scrollableRef = useRef(null); const messagesEndRef = useRef(null); - useEffect(() => { const timeoutId = setTimeout(() => { const scrollable = scrollableRef.current; @@ -21,6 +23,29 @@ const Messages = ({ messages }) => { clearTimeout(timeoutId); }; }, [messages]); + + const messageTree = useMemo(() => buildTree(messages), [messages, ]); + + function buildTree(messages) { + let messageMap = {}; + let rootMessages = []; + + // Traverse the messages array and store each element in messageMap. + messages.forEach(message => { + messageMap[message.messageId] = {...message, children: []}; + + if (message.parentMessageId === "00000000-0000-0000-0000-000000000000") { + rootMessages.push(messageMap[message.messageId]); + } else { + const parentMessage = messageMap[message.parentMessageId]; + if (parentMessage) { + parentMessage.children.push(messageMap[message.messageId]); + } + } + }); + + return rootMessages; + } const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); @@ -59,18 +84,14 @@ const Messages = ({ messages }) => { {/*
*/}
- {messages.map((message, i) => ( - setCurrentEditIdx(cancel?-1:i)} - /> - ))} + Date: Mon, 13 Mar 2023 22:20:50 +0800 Subject: [PATCH 14/47] fix: loading and send button, mobile style feat: sibling switch, mobile style fix: only the real submitting message will blink feat: drop the text version username, use a similar square. (or it will mass up the sibling switch) --- client/src/components/Main/SubmitButton.jsx | 4 ++-- client/src/components/Main/TextChat.jsx | 4 ++-- client/src/components/Messages/Message.jsx | 17 +++++++++++++---- .../src/components/Messages/SiblingSwitch.jsx | 2 +- client/src/mobile.css | 8 ++++++++ 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/client/src/components/Main/SubmitButton.jsx b/client/src/components/Main/SubmitButton.jsx index 802d1a88c3..ed570f5c76 100644 --- a/client/src/components/Main/SubmitButton.jsx +++ b/client/src/components/Main/SubmitButton.jsx @@ -10,7 +10,7 @@ export default function SubmitButton({ submitMessage }) { if (isSubmitting) { return ( - diff --git a/client/src/mobile.css b/client/src/mobile.css index e0e3988410..471f207f67 100644 --- a/client/src/mobile.css +++ b/client/src/mobile.css @@ -22,6 +22,14 @@ } @media (max-width: 767px) { + .sibling-switch { + left: 114px; + top: unset; + bottom: 6px; + visibility: visible; + z-index: 2; + } + .nav-close-button { display: block; position: absolute; From 2afbc5883f80caea33faf4529e8a1866bf78d637 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 22:53:29 +0800 Subject: [PATCH 15/47] fix: use onCompositionStart and onCompositionEnd to aviod enter submit when using input method. --- client/src/components/Main/TextChat.jsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 1fb3b06d68..50b80b3b74 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -17,6 +17,7 @@ import manualSWR from '~/utils/fetchers'; export default function TextChat({ messages }) { const [errorMessage, setErrorMessage] = useState(''); const inputRef = useRef(null) + const isComposing = useRef(false); const dispatch = useDispatch(); const convo = useSelector((state) => state.convo); const { initial } = useSelector((state) => state.models); @@ -278,6 +279,11 @@ export default function TextChat({ messages }) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); } + + if (e.key === 'Enter' && !e.shiftKey) { + if (!isComposing.current) + submitMessage(); + } }; const handleKeyUp = (e) => { @@ -288,11 +294,15 @@ export default function TextChat({ messages }) { if (isSubmitting) { return; } - - if (e.key === 'Enter' && !e.shiftKey) { - submitMessage(); - } }; + + const handleCompositionStart = (e) => { + isComposing.current = true + } + + const handleCompositionEnd = (e) => { + isComposing.current = false; + } const changeHandler = (e) => { const { value } = e.target; @@ -337,6 +347,8 @@ export default function TextChat({ messages }) { onKeyUp={handleKeyUp} onKeyDown={handleKeyDown} onChange={changeHandler} + onCompositionStart={handleCompositionStart} + onCompositionEnd={handleCompositionEnd} placeholder={disabled ? 'Choose another model or customize GPT again' : ''} disabled={disabled} className="m-0 h-auto max-h-52 resize-none overflow-auto border-0 bg-transparent p-0 pl-9 pr-8 leading-6 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:pl-8" From 953c5fc9703f4f65681430f212e3151cbeba41bd Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Mon, 13 Mar 2023 23:54:57 +0800 Subject: [PATCH 16/47] fix: w<1024px, will overflow. --- client/src/mobile.css | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/client/src/mobile.css b/client/src/mobile.css index 471f207f67..321663129c 100644 --- a/client/src/mobile.css +++ b/client/src/mobile.css @@ -21,15 +21,22 @@ display: none; } -@media (max-width: 767px) { +@media (max-width: 1024px) { .sibling-switch { left: 114px; top: unset; - bottom: 6px; + bottom: 4px; visibility: visible; z-index: 2; } + .resubmit-edit-button { + display: block; + visibility: visible; + } +} + +@media (max-width: 767px) { .nav-close-button { display: block; position: absolute; @@ -38,10 +45,6 @@ margin-left: 20px; } - .resubmit-edit-button { - display: block; - } - .nav { position: fixed; z-index: 999; From d9e5464b3be97309342014cef0a6b3f4a23f2c87 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Tue, 14 Mar 2023 00:11:56 +0800 Subject: [PATCH 17/47] fix: cleanup debug msg --- api/app/titleConvo.js | 1 - 1 file changed, 1 deletion(-) diff --git a/api/app/titleConvo.js b/api/app/titleConvo.js index 52e35b841a..88e8c75074 100644 --- a/api/app/titleConvo.js +++ b/api/app/titleConvo.js @@ -14,7 +14,6 @@ const proxyEnvToAxiosProxy = (proxyString) => { return proxyConfig } -console.log(proxyEnvToAxiosProxy(process.env.PROXY || null)) const titleConvo = async ({ message, response, model }) => { const configuration = new Configuration({ From 0fa19bb6ad4558173ed102d36855747513ef4dcf Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Tue, 14 Mar 2023 03:38:47 +0800 Subject: [PATCH 18/47] feat: save error message into database. --- api/models/Message.js | 11 ++++++++--- api/server/routes/ask.js | 8 ++++++++ api/server/routes/askBing.js | 4 ++++ api/server/routes/askSydney.js | 4 ++++ client/src/components/Main/TextChat.jsx | 12 +++++++----- client/src/components/Messages/Message.jsx | 8 ++++++-- 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/api/models/Message.js b/api/models/Message.js index db1746924b..9e281a9f16 100644 --- a/api/models/Message.js +++ b/api/models/Message.js @@ -36,20 +36,25 @@ const messageSchema = mongoose.Schema({ type: Boolean, required: true, default: false - } + }, + error: { + type: Boolean, + default: false + }, }, { timestamps: true }); const Message = mongoose.models.Message || mongoose.model('Message', messageSchema); module.exports = { - saveMessage: async ({ messageId, conversationId, parentMessageId, sender, text, isCreatedByUser=false }) => { + saveMessage: async ({ messageId, conversationId, parentMessageId, sender, text, isCreatedByUser=false, error }) => { try { await Message.findOneAndUpdate({ messageId }, { conversationId, parentMessageId, sender, text, - isCreatedByUser + isCreatedByUser, + error }, { upsert: true, new: true }); return { messageId, conversationId, parentMessageId, sender, text, isCreatedByUser }; } catch (error) { diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index d21f8be263..d644af1624 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -131,6 +131,10 @@ router.post('/', async (req, res) => { gptResponse.text.toLowerCase().includes('no response') || gptResponse.text.toLowerCase().includes('no answer') ) { + await saveMessage({ + messageId: crypto.randomUUID(), sender: model, + conversationId, parentMessageId: userMessageId, + error: true, text: 'Prompt empty or too short'}); return handleError(res, 'Prompt empty or too short'); } @@ -165,6 +169,10 @@ router.post('/', async (req, res) => { } catch (error) { console.log(error); // await deleteMessages({ messageId: userMessageId }); + await saveMessage({ + messageId: crypto.randomUUID(), sender: model, + conversationId, parentMessageId: userMessageId, + error: true, text: error.message}); handleError(res, error.message); } }); diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index f2722bb248..7b5ca44185 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -107,6 +107,10 @@ router.post('/', async (req, res) => { } catch (error) { console.log(error); // await deleteMessages({ messageId: userMessageId }); + await saveMessage({ + messageId: crypto.randomUUID(), sender: model, + conversationId, parentMessageId: userMessageId, + error: true, text: error.message}); handleError(res, error.message); } }); diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index a90dd39555..cdf571fbfe 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -117,6 +117,10 @@ router.post('/', async (req, res) => { } catch (error) { console.log(error); // await deleteMessages({ messageId: userMessageId }); + await saveMessage({ + messageId: crypto.randomUUID(), sender: model, + conversationId, parentMessageId: userMessageId, + error: true, text: error.message}); handleError(res, error.message); } }); diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 50b80b3b74..fe2c7b08eb 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -128,13 +128,14 @@ export default function TextChat({ messages }) { dispatch(setSubmitState(false)); }; - const errorHandler = (event, currentState) => { - const { initialResponse, messages, currentMsg, message } = currentState; + const errorHandler = (event, currentState, currentMsg) => { + const { initialResponse, messages, _currentMsg, message } = currentState; console.log('Error:', event); const errorResponse = { ...initialResponse, - text: `An error occurred. Please try again in a few moments.\n\nError message: ${event.data}`, - error: true + text: `${event.data}`, + error: true, + parentMessageId: currentMsg?.messageId, }; setErrorMessage(event.data); dispatch(setSubmitState(false)); @@ -264,7 +265,7 @@ export default function TextChat({ messages }) { events.onerror = function (e) { console.log('error in opening conn.'); events.close(); - errorHandler(e, currentState); + errorHandler(e, currentState, currentMsg); }; events.stream(); @@ -306,6 +307,7 @@ export default function TextChat({ messages }) { const changeHandler = (e) => { const { value } = e.target; + console.log(value) if (isSubmitting && (value === '' || value === '\n')) { return; } diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index 4f8a266891..3d5c576059 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -166,7 +166,11 @@ export default function Message({ // this is not a real messageId, it is used as placeholder before real messageId returned const fakeMessageId = crypto.randomUUID(); const isCustomModel = model === 'chatgptCustom' || !initial[model]; - const currentMsg = { ...message, sender: 'User', text: text.trim(), current: true, isCreatedByUser: true, messageId: fakeMessageId }; + const currentMsg = { + sender: 'User', text: text.trim(), current: true, isCreatedByUser: true, + parentMessageId: message?.parentMessageId, + conversationId: message?.conversationId, + messageId: fakeMessageId }; const sender = model === 'chatgptCustom' ? chatGptLabel : model; const initialResponse = { sender, text: '', parentMessageId: fakeMessageId, submitting: true }; @@ -217,7 +221,7 @@ export default function Message({ {error ? (
- {text} + {`An error occurred. Please try again in a few moments.\n\nError message: ${text}`}
) : From 4fd05e15b4b6350382c76f2b84d58b342efa8fdf Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Mon, 13 Mar 2023 21:59:25 -0400 Subject: [PATCH 19/47] fix: migrate old schema to new --- api/models/Conversation.js | 135 ++++++++++++++++++++++++--------- api/models/Message.js | 2 +- api/models/index.js | 3 +- api/server/index.js | 6 +- api/server/routes/askBing.js | 2 +- api/server/routes/askSydney.js | 2 +- 6 files changed, 108 insertions(+), 42 deletions(-) diff --git a/api/models/Conversation.js b/api/models/Conversation.js index c8daf78c4a..1c994fdc38 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -1,44 +1,48 @@ const mongoose = require('mongoose'); +const crypto = require('crypto'); const { getMessages, deleteMessages } = require('./Message'); -const convoSchema = mongoose.Schema({ - conversationId: { - type: String, - unique: true, - required: true +const convoSchema = mongoose.Schema( + { + conversationId: { + type: String, + unique: true, + required: true + }, + parentMessageId: { + type: String, + required: true + }, + title: { + type: String, + default: 'New Chat' + }, + jailbreakConversationId: { + type: String + }, + conversationSignature: { + type: String + }, + clientId: { + type: String + }, + invocationId: { + type: String + }, + chatGptLabel: { + type: String + }, + promptPrefix: { + type: String + }, + model: { + type: String + }, + suggestions: [{ type: String }], + messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }] }, - parentMessageId: { - type: String, - required: true - }, - title: { - type: String, - default: 'New Chat' - }, - jailbreakConversationId: { - type: String - }, - conversationSignature: { - type: String - }, - clientId: { - type: String - }, - invocationId: { - type: String - }, - chatGptLabel: { - type: String - }, - promptPrefix: { - type: String - }, - model: { - type: String - }, - suggestions: [{ type: String }], - messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }], -}, { timestamps: true }); + { timestamps: true } +); const Conversation = mongoose.models.Conversation || mongoose.model('Conversation', convoSchema); @@ -114,5 +118,62 @@ module.exports = { let deleteCount = await Conversation.deleteMany(filter).exec(); deleteCount.messages = await deleteMessages(filter); return deleteCount; + }, + migrateDb: async () => { + try { + const conversations = await Conversation.find({ model: null }).exec(); + + if (!conversations || conversations.length === 0) + return { message: 'No conversations to migrate' }; + + for (let convo of conversations) { + const messages = await getMessages({ + conversationId: convo.conversationId, + messageId: { $exists: false } + }); + + const promises = []; + let model; + let oldId; + messages.forEach((message, i) => { + const msgObj = message.toObject(); + const newId = msgObj.id; + if (i === 0) { + message.parentMessageId = '00000000-0000-0000-0000-000000000000'; + oldId = newId; + } else { + message.parentMessageId = oldId; + oldId = newId; + } + + message.messageId = newId; + message.createdAt = message.created; + if (message.sender.toLowerCase() !== 'user' && !model) { + model = message.sender.toLowerCase(); + } + + if (message.sender.toLowerCase() === 'user') { + message.isCreatedByUser = true; + } + promises.push(message.save()); + }); + await Promise.all(promises); + + await Conversation.findOneAndUpdate( + { conversationId: convo.conversationId }, + { model, createdAt: convo.created }, + { new: true } + ).exec(); + } + + try { + await mongoose.connection.db.collection('messages').dropIndex('id_1'); + } catch (error) { + console.log("Index doesn't exist or already dropped"); + } + } catch (error) { + console.log(error); + return { message: 'Error migrating conversations' }; + } } }; diff --git a/api/models/Message.js b/api/models/Message.js index 9e281a9f16..90165acb86 100644 --- a/api/models/Message.js +++ b/api/models/Message.js @@ -64,7 +64,7 @@ module.exports = { }, deleteMessagesSince: async ({ messageId, conversationId }) => { try { - message = await Message.findOne({ messageId }).exec() + const message = await Message.findOne({ messageId }).exec() if (message) return await Message.find({ conversationId }).deleteMany({ createdAt: { $gt: message.createdAt } }).exec(); diff --git a/api/models/index.js b/api/models/index.js index 4ed1285bbf..cd75b9c6a3 100644 --- a/api/models/index.js +++ b/api/models/index.js @@ -1,6 +1,6 @@ const { saveMessage, deleteMessagesSince, deleteMessages } = require('./Message'); const { getCustomGpts, updateCustomGpt, updateByLabel, deleteCustomGpts } = require('./CustomGpt'); -const { getConvoTitle, getConvo, saveConvo } = require('./Conversation'); +const { getConvoTitle, getConvo, saveConvo, migrateDb } = require('./Conversation'); module.exports = { saveMessage, @@ -9,6 +9,7 @@ module.exports = { getConvoTitle, getConvo, saveConvo, + migrateDb, getCustomGpts, updateCustomGpt, updateByLabel, diff --git a/api/server/index.js b/api/server/index.js index b380932531..bda88b14f4 100644 --- a/api/server/index.js +++ b/api/server/index.js @@ -1,5 +1,6 @@ const express = require('express'); const dbConnect = require('../models/dbConnect'); +const { migrateDb } = require('../models'); const path = require('path'); const cors = require('cors'); const routes = require('./routes'); @@ -7,7 +8,10 @@ const app = express(); const port = process.env.PORT || 3080; const host = process.env.HOST || 'localhost' const projectPath = path.join(__dirname, '..', '..', 'client'); -dbConnect().then(() => console.log('Connected to MongoDB')); +dbConnect().then(() => { + console.log('Connected to MongoDB'); + migrateDb(); +}); app.use(cors()); app.use(express.json()); diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 7b5ca44185..8c8238917a 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -14,7 +14,7 @@ router.post('/', async (req, res) => { const conversationId = oldConversationId || crypto.randomUUID(); - const userMessageId = messageId; + const userMessageId = crypto.randomUUID(); const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' let userMessage = { messageId: userMessageId, diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index cdf571fbfe..8738b625d0 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -14,7 +14,7 @@ router.post('/', async (req, res) => { const conversationId = oldConversationId || crypto.randomUUID(); - const userMessageId = messageId; + const userMessageId = crypto.randomUUID(); const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' let userMessage = { messageId: userMessageId, From 3e7ce67609d64da85f147836b8567e37aced3df3 Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Mon, 13 Mar 2023 22:00:13 -0400 Subject: [PATCH 20/47] fix: migrate old schema to new --- client/src/components/Conversations/Conversation.jsx | 1 + client/src/components/Messages/index.jsx | 2 ++ client/src/components/Models/ModelMenu.jsx | 10 +++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/client/src/components/Conversations/Conversation.jsx b/client/src/components/Conversations/Conversation.jsx index 4d90ecb2cc..cc41d62daf 100644 --- a/client/src/components/Conversations/Conversation.jsx +++ b/client/src/components/Conversations/Conversation.jsx @@ -74,6 +74,7 @@ export default function Conversation({ ); } const data = await trigger(); + console.log('data', data); if (chatGptLabel) { dispatch(setModel('chatgptCustom')); diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index 5d191e61cf..f9452d2d23 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -26,6 +26,8 @@ const Messages = ({ messages }) => { const messageTree = useMemo(() => buildTree(messages), [messages, ]); + console.log('messageTree', messageTree); + function buildTree(messages) { let messageMap = {}; let rootMessages = []; diff --git a/client/src/components/Models/ModelMenu.jsx b/client/src/components/Models/ModelMenu.jsx index 6e796cd567..64d8a427b1 100644 --- a/client/src/components/Models/ModelMenu.jsx +++ b/client/src/components/Models/ModelMenu.jsx @@ -44,9 +44,13 @@ export default function ModelMenu() { useEffect(() => { mutate(); - const lastSelected = JSON.parse(localStorage.getItem('model')); - if (lastSelected && lastSelected !== 'chatgptCustom' && initial[lastSelected]) { - dispatch(setModel(lastSelected)); + try { + const lastSelected = JSON.parse(localStorage.getItem('model')); + if (lastSelected && lastSelected !== 'chatgptCustom' && initial[lastSelected]) { + dispatch(setModel(lastSelected)); + } + } catch (err) { + console.log(err); } // eslint-disable-next-line react-hooks/exhaustive-deps From 27515cb00aeb244682a6bbbc99b89c0f185d54c4 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Tue, 14 Mar 2023 11:42:35 +0800 Subject: [PATCH 21/47] fix: generate title by backend --- api/server/routes/ask.js | 20 +++++++++++------- api/server/routes/askBing.js | 21 ++++++++++++------- api/server/routes/askSydney.js | 20 +++++++++++------- .../components/Conversations/Conversation.jsx | 1 - client/src/components/Main/TextChat.jsx | 2 +- client/src/components/Messages/index.jsx | 2 -- 6 files changed, 40 insertions(+), 26 deletions(-) diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index d644af1624..ea246fb46d 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -138,13 +138,6 @@ 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) - // }); - // } gptResponse.sender = model === 'chatgptCustom' ? chatGptLabel : model; // gptResponse.final = true; gptResponse.text = await detectCode(gptResponse.text); @@ -166,6 +159,19 @@ router.post('/', async (req, res) => { responseMessage: gptResponse }); res.end(); + + if (parentMessageId == '00000000-0000-0000-0000-000000000000') { + const title = await titleConvo({ + model, + message: text, + response: JSON.stringify(gptResponse?.text) + }); + + await saveConvo({ + conversationId, + title + }) + } } catch (error) { console.log(error); // await deleteMessages({ messageId: userMessageId }); diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 8c8238917a..975046986c 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -72,14 +72,6 @@ router.post('/', async (req, res) => { userMessage.invocationId = response.invocationId; await saveMessage(userMessage); - // 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; @@ -104,6 +96,19 @@ router.post('/', async (req, res) => { responseMessage: gptResponse }); res.end(); + + if (parentMessageId == '00000000-0000-0000-0000-000000000000') { + const title = await titleConvo({ + model, + message: text, + response: JSON.stringify(gptResponse?.text) + }); + + await saveConvo({ + conversationId, + title + }) + } } catch (error) { console.log(error); // await deleteMessages({ messageId: userMessageId }); diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 8738b625d0..a31c42e3f0 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -73,13 +73,6 @@ router.post('/', async (req, res) => { // 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(conversationId) - : await titleConvo({ - model, - message: text, - response: JSON.stringify(response.response) - }); response.conversationId = conversationId ? conversationId : crypto.randomUUID(); @@ -114,6 +107,19 @@ router.post('/', async (req, res) => { responseMessage: gptResponse }); res.end(); + + if (parentMessageId == '00000000-0000-0000-0000-000000000000') { + const title = await titleConvo({ + model, + message: text, + response: JSON.stringify(gptResponse?.text) + }); + + await saveConvo({ + conversationId, + title + }) + } } catch (error) { console.log(error); // await deleteMessages({ messageId: userMessageId }); diff --git a/client/src/components/Conversations/Conversation.jsx b/client/src/components/Conversations/Conversation.jsx index cc41d62daf..4d90ecb2cc 100644 --- a/client/src/components/Conversations/Conversation.jsx +++ b/client/src/components/Conversations/Conversation.jsx @@ -74,7 +74,6 @@ export default function Conversation({ ); } const data = await trigger(); - console.log('data', data); if (chatGptLabel) { dispatch(setModel('chatgptCustom')); diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index fe2c7b08eb..710b10f7c2 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -307,7 +307,7 @@ export default function TextChat({ messages }) { const changeHandler = (e) => { const { value } = e.target; - console.log(value) + if (isSubmitting && (value === '' || value === '\n')) { return; } diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index f9452d2d23..5d191e61cf 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -26,8 +26,6 @@ const Messages = ({ messages }) => { const messageTree = useMemo(() => buildTree(messages), [messages, ]); - console.log('messageTree', messageTree); - function buildTree(messages) { let messageMap = {}; let rootMessages = []; From 716849854329da473215f1e41b6c94cf33b17e78 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 15 Mar 2023 00:47:17 +0800 Subject: [PATCH 22/47] fix: jailbreakConversationId=false response will be saved jailbreakConversationId='false' in database. --- api/app/bingai.js | 6 ++++-- api/models/Conversation.js | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/api/app/bingai.js b/api/app/bingai.js index d1175c0bfc..2ccbc2b356 100644 --- a/api/app/bingai.js +++ b/api/app/bingai.js @@ -22,8 +22,10 @@ const askBing = async ({ text, progressCallback, convo }) => { options = { ...options, ...convo }; } - const res = await bingAIClient.sendMessage(text, options - ); + if (options?.jailbreakConversationId == 'false') + options.jailbreakConversationId = false + + const res = await bingAIClient.sendMessage(text, options); return res; diff --git a/api/models/Conversation.js b/api/models/Conversation.js index 1c994fdc38..d2ead788d2 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -64,6 +64,8 @@ module.exports = { if (title) { update.title = title; } + if (!update.jailbreakConversationId) + update.jailbreakConversationId = null return await Conversation.findOneAndUpdate( { conversationId }, From d73375958b559e1b5fbda536a809872580342064 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 15 Mar 2023 00:50:27 +0800 Subject: [PATCH 23/47] feat: return error as a error message, not only text --- api/server/routes/ask.js | 13 +++++++------ api/server/routes/askBing.js | 11 ++++++----- api/server/routes/askSydney.js | 11 ++++++----- api/server/routes/handlers.js | 4 ++-- client/src/components/Main/TextChat.jsx | 16 +++++++++------- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index ea246fb46d..794f6367b1 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -19,7 +19,7 @@ router.use('/sydney', askSydney); router.post('/', async (req, res) => { let { model, text, parentMessageId, conversationId: oldConversationId , chatGptLabel, promptPrefix } = req.body; if (text.length === 0) { - return handleError(res, 'Prompt empty or too short'); + return handleError(res, { text: 'Prompt empty or too short' }); } const conversationId = oldConversationId || crypto.randomUUID(); @@ -135,7 +135,7 @@ router.post('/', async (req, res) => { messageId: crypto.randomUUID(), sender: model, conversationId, parentMessageId: userMessageId, error: true, text: 'Prompt empty or too short'}); - return handleError(res, 'Prompt empty or too short'); + return handleError(res, { text: 'Prompt empty or too short' }); } gptResponse.sender = model === 'chatgptCustom' ? chatGptLabel : model; @@ -175,11 +175,12 @@ router.post('/', async (req, res) => { } catch (error) { console.log(error); // await deleteMessages({ messageId: userMessageId }); - await saveMessage({ + const errorMessage = { messageId: crypto.randomUUID(), sender: model, - conversationId, parentMessageId: userMessageId, - error: true, text: error.message}); - handleError(res, error.message); + conversationId, parentMessageId: overrideParentMessageId || userMessageId, + error: true, text: error.message} + await saveMessage(errorMessage); + handleError(res, errorMessage); } }); diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 975046986c..885741d08c 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -9,7 +9,7 @@ const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { const { model, text, parentMessageId, conversationId: oldConversationId, ...convo } = req.body; if (text.length === 0) { - return handleError(res, 'Prompt empty or too short'); + return handleError(res, { text: 'Prompt empty or too short' }); } const conversationId = oldConversationId || crypto.randomUUID(); @@ -112,11 +112,12 @@ router.post('/', async (req, res) => { } catch (error) { console.log(error); // await deleteMessages({ messageId: userMessageId }); - await saveMessage({ + const errorMessage = { messageId: crypto.randomUUID(), sender: model, - conversationId, parentMessageId: userMessageId, - error: true, text: error.message}); - handleError(res, error.message); + conversationId, parentMessageId: overrideParentMessageId || userMessageId, + error: true, text: error.message} + await saveMessage(errorMessage); + handleError(res, errorMessage); } }); diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index a31c42e3f0..5a63f97004 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -9,7 +9,7 @@ const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { const { model, text, parentMessageId, conversationId: oldConversationId, ...convo } = req.body; if (text.length === 0) { - return handleError(res, 'Prompt empty or too short'); + return handleError(res, { text: 'Prompt empty or too short' }); } const conversationId = oldConversationId || crypto.randomUUID(); @@ -123,11 +123,12 @@ router.post('/', async (req, res) => { } catch (error) { console.log(error); // await deleteMessages({ messageId: userMessageId }); - await saveMessage({ + const errorMessage = { messageId: crypto.randomUUID(), sender: model, - conversationId, parentMessageId: userMessageId, - error: true, text: error.message}); - handleError(res, error.message); + conversationId, parentMessageId: overrideParentMessageId || userMessageId, + error: true, text: error.message} + await saveMessage(errorMessage); + handleError(res, errorMessage); } }); diff --git a/api/server/routes/handlers.js b/api/server/routes/handlers.js index edd64e6184..e727cacfa3 100644 --- a/api/server/routes/handlers.js +++ b/api/server/routes/handlers.js @@ -1,5 +1,5 @@ -const handleError = (res, errorMessage) => { - res.status(500).write(`event: error\ndata: ${errorMessage}`); +const handleError = (res, message) => { + res.write(`event: error\ndata: ${JSON.stringify(message)}\n\n`); res.end(); }; diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 710b10f7c2..0045718617 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -128,18 +128,17 @@ export default function TextChat({ messages }) { dispatch(setSubmitState(false)); }; - const errorHandler = (event, currentState, currentMsg) => { + const errorHandler = (data, currentState, currentMsg) => { const { initialResponse, messages, _currentMsg, message } = currentState; - console.log('Error:', event); + console.log('Error:', data); const errorResponse = { - ...initialResponse, - text: `${event.data}`, + ...data, error: true, parentMessageId: currentMsg?.messageId, }; - setErrorMessage(event.data); + setErrorMessage(data?.text); dispatch(setSubmitState(false)); - dispatch(setMessages([...messages.slice(0, -2), currentMsg, errorResponse])); + dispatch(setMessages([...messages, currentMsg, errorResponse])); dispatch(setText(message?.text)); dispatch(setError(true)); return; @@ -265,7 +264,10 @@ export default function TextChat({ messages }) { events.onerror = function (e) { console.log('error in opening conn.'); events.close(); - errorHandler(e, currentState, currentMsg); + + const data = JSON.parse(e.data); + + errorHandler(data, currentState, currentMsg); }; events.stream(); From 8b00805d24bc1f6ee155b3666dd6be5e4f0b990e Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 15 Mar 2023 00:53:15 +0800 Subject: [PATCH 24/47] fix: dont send gen_title twice --- api/server/routes/ask.js | 2 ++ api/server/routes/askBing.js | 2 ++ api/server/routes/askSydney.js | 2 ++ api/server/routes/convos.js | 31 ++++++++++++++----------- client/src/components/Main/TextChat.jsx | 10 ++++---- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index 794f6367b1..73b85b8012 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -167,6 +167,8 @@ router.post('/', async (req, res) => { response: JSON.stringify(gptResponse?.text) }); + console.log('CONVERSATION TITLE', title); + await saveConvo({ conversationId, title diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 885741d08c..ffe8f2ae6d 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -104,6 +104,8 @@ router.post('/', async (req, res) => { response: JSON.stringify(gptResponse?.text) }); + console.log('CONVERSATION TITLE', title); + await saveConvo({ conversationId, title diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 5a63f97004..20f6c302b3 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -115,6 +115,8 @@ router.post('/', async (req, res) => { response: JSON.stringify(gptResponse?.text) }); + console.log('CONVERSATION TITLE', title); + await saveConvo({ conversationId, title diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index a82242db4a..bfe3bc2d56 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -17,20 +17,23 @@ router.post('/gen_title', async (req, res) => { const firstMessage = (await getMessages({ conversationId }))[0] const secondMessage = (await getMessages({ conversationId }))[1] - const title = convo.jailbreakConversationId - ? await getConvoTitle(conversationId) - : await titleConvo({ - model: convo?.model, - message: firstMessage?.text, - response: JSON.stringify(secondMessage?.text || '') - }); - - await saveConvo({ - conversationId, - title - }) - - res.status(200).send(title); + // if (convo.title == 'New Chat') { + // const title = await titleConvo({ + // model: convo?.model, + // message: firstMessage?.text, + // response: JSON.stringify(secondMessage?.text || '') + // }); + + // console.log('CONVERSATION TITLE', title); + + // await saveConvo({ + // conversationId, + // title + // }) + + // res.status(200).send(title); + // } else + return res.status(200).send(convo.title); }); router.post('/clear', async (req, res) => { diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 0045718617..8ed3576661 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -60,12 +60,14 @@ export default function TextChat({ messages }) { const isBing = model === 'bingai' || model === 'sydney'; if (requestMessage.parentMessageId == '00000000-0000-0000-0000-000000000000') { - genTitle.trigger({ conversationId }).then((ret) => { - const title = ret?.data + setTimeout(() => { + dispatch(refreshConversation()); + }, 2000); - if (title) + // in case it takes too long. + setTimeout(() => { dispatch(refreshConversation()); - }) + }, 5000); } if (!isBing && convo.conversationId === null && convo.parentMessageId === null) { From 644f3f716fe0d2451e814f10afe0b0374862aafc Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 15 Mar 2023 00:54:50 +0800 Subject: [PATCH 25/47] feat: re-orginazed three ask api. To provide ability to reproduce message. feat: bing and sydney come to work again, [need more test] --- api/models/Conversation.js | 5 +- api/server/routes/ask.js | 137 ++++++++++++++++++------ api/server/routes/askBing.js | 60 ++++++++--- api/server/routes/askSydney.js | 69 +++++++++--- client/src/components/Main/TextChat.jsx | 8 +- 5 files changed, 209 insertions(+), 70 deletions(-) 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; From 88824322108fdd0952caaca4a42d1729e2fc6e4e Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 15 Mar 2023 02:33:08 +0800 Subject: [PATCH 26/47] fix: hide the edit button when bingai --- .../src/components/Messages/HoverButtons.jsx | 20 ++++++++++++------- client/src/components/Messages/Message.jsx | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/client/src/components/Messages/HoverButtons.jsx b/client/src/components/Messages/HoverButtons.jsx index f24027118e..34f5fe00b3 100644 --- a/client/src/components/Messages/HoverButtons.jsx +++ b/client/src/components/Messages/HoverButtons.jsx @@ -2,15 +2,21 @@ import React from 'react'; // import Clipboard from '../svg/Clipboard'; import EditIcon from '../svg/EditIcon'; -export default function HoverButtons({ user, onClick }) { +export default function HoverButtons({ visible, onClick, model }) { + const isBing = model === 'bingai' || model === 'sydney'; + const enabled = !isBing; + return (
- {user && ( - - )} + {(visible&&enabled)?( + <> + + + ):null} {/* */} diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index 3d5c576059..ac74285be0 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -259,7 +259,7 @@ export default function Message({
)}
- enterEdit()}/> + enterEdit()}/>
From 71fc86b9a6f5e2250f5ca557a9f793c6c1879716 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 15 Mar 2023 02:43:21 +0800 Subject: [PATCH 27/47] fix: buildTree should store parent-not-exist message as root. rather than dropping them. --- client/src/components/Messages/index.jsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index 5d191e61cf..85c671d691 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -34,14 +34,11 @@ const Messages = ({ messages }) => { messages.forEach(message => { messageMap[message.messageId] = {...message, children: []}; - if (message.parentMessageId === "00000000-0000-0000-0000-000000000000") { + const parentMessage = messageMap[message.parentMessageId]; + if (parentMessage) + parentMessage.children.push(messageMap[message.messageId]); + else rootMessages.push(messageMap[message.messageId]); - } else { - const parentMessage = messageMap[message.parentMessageId]; - if (parentMessage) { - parentMessage.children.push(messageMap[message.messageId]); - } - } }); return rootMessages; From 9a17e94f8fd70c617b33e2ecd5af13b47636ca8c Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 14 Mar 2023 14:51:26 -0400 Subject: [PATCH 28/47] fix: refactor migration and sort old convos correctly --- api/models/Conversation.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/api/models/Conversation.js b/api/models/Conversation.js index a32bc3e7a4..c3b23d85fb 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -97,7 +97,7 @@ module.exports = { // const limit = pageNumber * pageSize; const conversations = await Conversation.find({}) - .sort({ createdAt: -1 }) + .sort({ createdAt: -1, created: -1 }) .skip(skip) // .limit(limit) .limit(pageSize) @@ -129,7 +129,7 @@ module.exports = { const conversations = await Conversation.find({ model: null }).exec(); if (!conversations || conversations.length === 0) - return { message: 'No conversations to migrate' }; + return { message: '[Migrate] No conversations to migrate' }; for (let convo of conversations) { const messages = await getMessages({ @@ -137,22 +137,20 @@ module.exports = { messageId: { $exists: false } }); - const promises = []; let model; let oldId; + const promises = []; messages.forEach((message, i) => { const msgObj = message.toObject(); const newId = msgObj.id; if (i === 0) { message.parentMessageId = '00000000-0000-0000-0000-000000000000'; - oldId = newId; } else { message.parentMessageId = oldId; - oldId = newId; } + oldId = newId; message.messageId = newId; - message.createdAt = message.created; if (message.sender.toLowerCase() !== 'user' && !model) { model = message.sender.toLowerCase(); } @@ -166,7 +164,7 @@ module.exports = { await Conversation.findOneAndUpdate( { conversationId: convo.conversationId }, - { model, createdAt: convo.created }, + { model }, { new: true } ).exec(); } @@ -174,11 +172,11 @@ module.exports = { try { await mongoose.connection.db.collection('messages').dropIndex('id_1'); } catch (error) { - console.log("Index doesn't exist or already dropped"); + console.log("[Migrate] Index doesn't exist or already dropped"); } } catch (error) { console.log(error); - return { message: 'Error migrating conversations' }; + return { message: '[Migrate] Error migrating conversations' }; } } }; From 2e20b28c4dd5ec6a1bcf40cf992944e2e091ae75 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 14 Mar 2023 15:42:59 -0400 Subject: [PATCH 29/47] chore: refactor progressCB to one place, fix sydney, and sanitize html --- api/app/bingai.js | 7 +- api/app/chatgpt-browser.js | 7 +- api/app/chatgpt-client.js | 7 +- api/app/sydney.js | 6 +- api/package-lock.json | 1 + api/package.json | 1 + api/server/routes/ask.js | 33 +--- api/server/routes/askBing.js | 13 +- api/server/routes/askSydney.js | 13 +- api/server/routes/handlers.js | 34 +++- package-lock.json | 293 +++++++++++++++++++++++++++++++++ package.json | 5 + 12 files changed, 351 insertions(+), 69 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/api/app/bingai.js b/api/app/bingai.js index 2ccbc2b356..dcf150d1cf 100644 --- a/api/app/bingai.js +++ b/api/app/bingai.js @@ -1,7 +1,7 @@ require('dotenv').config(); const { KeyvFile } = require('keyv-file'); -const askBing = async ({ text, progressCallback, convo }) => { +const askBing = async ({ text, onProgress, convo }) => { const { BingAIClient } = (await import('@waylaidwanderer/chatgpt-api')); const bingAIClient = new BingAIClient({ @@ -14,10 +14,7 @@ const askBing = async ({ text, progressCallback, convo }) => { proxy: process.env.PROXY || null, }); - let options = { - onProgress: async (partialRes) => await progressCallback(partialRes), - }; - + let options = { onProgress }; if (convo) { options = { ...options, ...convo }; } diff --git a/api/app/chatgpt-browser.js b/api/app/chatgpt-browser.js index 8a3c903641..a5bea22729 100644 --- a/api/app/chatgpt-browser.js +++ b/api/app/chatgpt-browser.js @@ -10,7 +10,7 @@ const clientOptions = { proxy: process.env.PROXY || null, }; -const browserClient = async ({ text, progressCallback, convo }) => { +const browserClient = async ({ text, onProgress, convo }) => { const { ChatGPTBrowserClient } = await import('@waylaidwanderer/chatgpt-api'); const store = { @@ -18,10 +18,7 @@ const browserClient = async ({ text, progressCallback, convo }) => { }; const client = new ChatGPTBrowserClient(clientOptions, store); - - let options = { - onProgress: async (partialRes) => await progressCallback(partialRes) - }; + let options = { onProgress }; if (!!convo.parentMessageId && !!convo.conversationId) { options = { ...options, ...convo }; diff --git a/api/app/chatgpt-client.js b/api/app/chatgpt-client.js index afd31e0a81..350d1210ce 100644 --- a/api/app/chatgpt-client.js +++ b/api/app/chatgpt-client.js @@ -9,17 +9,14 @@ const clientOptions = { debug: false }; -const askClient = async ({ text, progressCallback, convo }) => { +const askClient = async ({ text, onProgress, convo }) => { const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default; const store = { store: new KeyvFile({ filename: './data/cache.json' }) }; const client = new ChatGPTClient(process.env.OPENAI_KEY, clientOptions, store); - - let options = { - onProgress: async (partialRes) => await progressCallback(partialRes) - }; + let options = { onProgress }; if (!!convo.parentMessageId && !!convo.conversationId) { options = { ...options, ...convo }; diff --git a/api/app/sydney.js b/api/app/sydney.js index fe47c74f57..3466f71c17 100644 --- a/api/app/sydney.js +++ b/api/app/sydney.js @@ -1,7 +1,7 @@ require('dotenv').config(); const { KeyvFile } = require('keyv-file'); -const askSydney = async ({ text, progressCallback, convo }) => { +const askSydney = async ({ text, onProgress, convo }) => { const { BingAIClient } = (await import('@waylaidwanderer/chatgpt-api')); const sydneyClient = new BingAIClient({ @@ -15,10 +15,10 @@ const askSydney = async ({ text, progressCallback, convo }) => { let options = { jailbreakConversationId: true, - onProgress: async (partialRes) => await progressCallback(partialRes), + onProgress, }; - if (convo.parentMessageId) { + if (convo.jailbreakConversationId) { options = { ...options, jailbreakConversationId: convo.jailbreakConversationId, parentMessageId: convo.parentMessageId }; } diff --git a/api/package-lock.json b/api/package-lock.json index 911fda953e..daa36e69e4 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -17,6 +17,7 @@ "express": "^4.18.2", "keyv": "^4.5.2", "keyv-file": "^0.2.0", + "lodash": "^4.17.21", "mongoose": "^6.9.0", "openai": "^3.1.0" }, diff --git a/api/package.json b/api/package.json index 28bd6769d3..6dcf344ea0 100644 --- a/api/package.json +++ b/api/package.json @@ -27,6 +27,7 @@ "express": "^4.18.2", "keyv": "^4.5.2", "keyv-file": "^0.2.0", + "lodash": "^4.17.21", "mongoose": "^6.9.0", "openai": "^3.1.0" }, diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index d12ee17b4e..5f94d7d4c3 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -11,7 +11,7 @@ const { detectCode } = require('../../app/'); const { getConvo, saveMessage, getConvoTitle, saveConvo } = require('../../models'); -const { handleError, sendMessage } = require('./handlers'); +const { handleError, sendMessage, createOnProgress } = require('./handlers'); const { getMessages } = require('../../models/Message'); router.use('/bing', askBing); @@ -138,37 +138,10 @@ const ask = async ({ sendMessage(res, { message: userMessage, created: true }); try { - let i = 0; - let tokens = ''; - const progressCallback = async (partial) => { - if (i === 0 && typeof partial === 'object') { - userMessage.conversationId = conversationId ? conversationId : partial.conversationId; - await saveMessage(userMessage); - sendMessage(res, { ...partial, parentMessageId: overrideParentMessageId || userMessageId, initial: true }); - i++; - } - - if (typeof partial === 'object') { - sendMessage(res, { ...partial, parentMessageId: overrideParentMessageId || userMessageId, message: true }); - } else { - tokens += partial === text ? '' : partial; - if (tokens.match(/^\n/)) { - tokens = tokens.replace(/^\n/, ''); - } - - if (tokens.includes('[DONE]')) { - tokens = tokens.replace('[DONE]', ''); - } - - // tokens = await detectCode(tokens); - sendMessage(res, { text: tokens, message: true, initial: i === 0 ? true : false }); - i++; - } - }; - + const progressCallback = createOnProgress(); let gptResponse = await client({ text, - progressCallback, + onProgress: progressCallback.call(null, model, {res, text }), convo: { parentMessageId: userParentMessageId, conversationId, diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 1871e96f40..add804a43e 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -3,7 +3,7 @@ const crypto = require('crypto'); const router = express.Router(); const { titleConvo, getCitations, citeText, askBing } = require('../../app/'); const { saveMessage, getConvoTitle, saveConvo } = require('../../models'); -const { handleError, sendMessage } = require('./handlers'); +const { handleError, sendMessage, createOnProgress } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { @@ -68,17 +68,10 @@ const ask = async ({ sendMessage(res, { message: userMessage, created: true }); try { - let tokens = ''; - const progressCallback = async (partial) => { - tokens += partial === text ? '' : partial; - // tokens = appendCode(tokens); - tokens = citeText(tokens, true); - sendMessage(res, { text: tokens, message: true, parentMessageId: overrideParentMessageId || userMessageId }); - }; - + const progressCallback = createOnProgress(); let response = await askBing({ text, - progressCallback, + onProgress: progressCallback.call(null, model, {res, text, parentMessageId: overrideParentMessageId || userMessageId }), convo: { ...convo, parentMessageId: userParentMessageId, diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 3a663184b6..1f04ad4166 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -3,7 +3,7 @@ const crypto = require('crypto'); const router = express.Router(); const { titleConvo, getCitations, citeText, askSydney } = require('../../app/'); const { saveMessage, saveConvo, getConvoTitle } = require('../../models'); -const { handleError, sendMessage } = require('./handlers'); +const { handleError, sendMessage, createOnProgress } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { @@ -68,17 +68,10 @@ const ask = async ({ sendMessage(res, { message: userMessage, created: true }); try { - let tokens = ''; - const progressCallback = async (partial) => { - tokens += partial === text ? '' : partial; - // tokens = appendCode(tokens); - tokens = citeText(tokens, true); - sendMessage(res, { text: tokens, message: true, parentMessageId: overrideParentMessageId || userMessageId }); - }; - + const progressCallback = createOnProgress(); let response = await askSydney({ text, - progressCallback, + onProgress: progressCallback.call(null, model, {res, text, parentMessageId: overrideParentMessageId || userMessageId }), convo: { parentMessageId: userParentMessageId, conversationId, diff --git a/api/server/routes/handlers.js b/api/server/routes/handlers.js index e727cacfa3..7672ef41a5 100644 --- a/api/server/routes/handlers.js +++ b/api/server/routes/handlers.js @@ -1,3 +1,7 @@ +const { citeText } = require('../../app/'); +const _ = require('lodash'); +const sanitizeHtml = require('sanitize-html'); + const handleError = (res, message) => { res.write(`event: error\ndata: ${JSON.stringify(message)}\n\n`); res.end(); @@ -10,4 +14,32 @@ const sendMessage = (res, message) => { res.write(`event: message\ndata: ${JSON.stringify(message)}\n\n`); }; -module.exports = { handleError, sendMessage }; +const createOnProgress = () => { + let i = 0; + let tokens = ''; + + const progressCallback = async (partial, { res, text, bing = false, ...rest }) => { + tokens += partial === text ? '' : partial; + tokens = tokens.trim(); + tokens = tokens.replaceAll('[DONE]', ''); + if (tokens.includes('```')) { + tokens = sanitizeHtml(tokens); + } + + if (bing) { + tokens = citeText(tokens, true); + } + + sendMessage(res, { text: tokens, message: true, initial: i === 0, ...rest }); + i++; + }; + + const onProgress = (model, opts) => { + const bingModels = new Set(['bingai', 'sydney']); + return _.partialRight(progressCallback, { ...opts, bing: bingModels.has(model) }); + }; + + return onProgress; +}; + +module.exports = { handleError, sendMessage, createOnProgress }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..bd60daac40 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,293 @@ +{ + "name": "chatgpt-clone", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "sanitize-html": "^2.10.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/sanitize-html": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.10.0.tgz", + "integrity": "sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==", + "dependencies": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + } + }, + "dependencies": { + "deepmerge": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==" + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + } + }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" + }, + "parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "sanitize-html": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.10.0.tgz", + "integrity": "sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==", + "requires": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000..4da6b6e8c4 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "sanitize-html": "^2.10.0" + } +} From 8289558d94b613ff58645dc74ca5f85b333979c9 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 15 Mar 2023 04:05:14 +0800 Subject: [PATCH 30/47] feat: pagination in nav --- api/models/Conversation.js | 14 +++---- api/server/routes/convos.js | 4 +- client/src/components/Conversations/index.jsx | 35 ++++++++++------ client/src/components/Nav/MobileNav.jsx | 6 +-- client/src/components/Nav/index.jsx | 41 ++++++++++++++----- client/src/store/convoSlice.js | 19 ++++++--- client/src/style.css | 3 ++ client/src/utils/fetchers.js | 8 ++-- 8 files changed, 84 insertions(+), 46 deletions(-) diff --git a/api/models/Conversation.js b/api/models/Conversation.js index a32bc3e7a4..6e3c646c64 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -91,19 +91,17 @@ module.exports = { } }, // getConvos: async () => await Conversation.find({}).sort({ createdAt: -1 }).exec(), - getConvos: async (pageNumber = 1, pageSize = 12) => { + getConvosByPage: async (pageNumber = 1, pageSize = 12) => { try { - const skip = (pageNumber - 1) * pageSize; - // const limit = pageNumber * pageSize; - - const conversations = await Conversation.find({}) + const totalConvos = await Conversation.countDocuments(); + const totalPages = Math.ceil(totalConvos / pageSize); + const convos = await Conversation.find() .sort({ createdAt: -1 }) - .skip(skip) - // .limit(limit) + .skip((pageNumber - 1) * pageSize) .limit(pageSize) .exec(); - return conversations; + return { conversations: convos, pages: totalPages, pageNumber, pageSize }; } catch (error) { console.log(error); return { message: 'Error getting conversations' }; diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index bfe3bc2d56..9a5f71cee3 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -2,12 +2,12 @@ const express = require('express'); const router = express.Router(); const { titleConvo } = require('../../app/'); const { getConvo, saveConvo, getConvoTitle } = require('../../models'); -const { getConvos, deleteConvos, updateConvo } = require('../../models/Conversation'); +const { getConvosByPage, deleteConvos, updateConvo } = require('../../models/Conversation'); const { getMessages } = require('../../models/Message'); router.get('/', async (req, res) => { const pageNumber = req.query.pageNumber || 1; - res.status(200).send(await getConvos(pageNumber)); + res.status(200).send(await getConvosByPage(pageNumber)); }); router.post('/gen_title', async (req, res) => { diff --git a/client/src/components/Conversations/index.jsx b/client/src/components/Conversations/index.jsx index d3ab1ffd0b..4142cb8ed1 100644 --- a/client/src/components/Conversations/index.jsx +++ b/client/src/components/Conversations/index.jsx @@ -1,10 +1,10 @@ import React from 'react'; import Conversation from './Conversation'; -export default function Conversations({ conversations, conversationId, showMore }) { - const clickHandler = async (e) => { +export default function Conversations({ conversations, conversationId, pageNumber, pages, nextPage, previousPage, moveToTop }) { + const clickHandler = (func) => async (e) => { e.preventDefault(); - await showMore(); + await func(); }; return ( @@ -33,18 +33,29 @@ export default function Conversations({ conversations, conversationId, showMore chatGptLabel={convo.chatGptLabel} promptPrefix={convo.promptPrefix} bingData={bingData} - retainView={showMore.bind(null, false)} + retainView={moveToTop} /> ); })} - {conversations?.length >= 12 && ( - - )} +
+ + + {pageNumber} / {pages} + + +
); } diff --git a/client/src/components/Nav/MobileNav.jsx b/client/src/components/Nav/MobileNav.jsx index e4107e9ac6..0c846524fc 100644 --- a/client/src/components/Nav/MobileNav.jsx +++ b/client/src/components/Nav/MobileNav.jsx @@ -7,7 +7,7 @@ import { setText } from '~/store/textSlice'; export default function MobileNav({ setNavVisible }) { const dispatch = useDispatch(); - const { conversationId, convos } = useSelector((state) => state.convo); + const { conversationId, convos, title } = useSelector((state) => state.convo); const toggleNavVisible = () => { setNavVisible((prev) => { @@ -22,8 +22,6 @@ export default function MobileNav({ setNavVisible }) { dispatch(setSubmission({})); } - const title = convos?.find(element => element?.conversationId == conversationId)?.title || 'New Chat'; - return (
-

{title}

+

{title || 'New Chat'}

diff --git a/client/src/store/convoSlice.js b/client/src/store/convoSlice.js index 258de84e70..c862a8e1e7 100644 --- a/client/src/store/convoSlice.js +++ b/client/src/store/convoSlice.js @@ -13,8 +13,9 @@ const initialState = { promptPrefix: null, convosLoading: false, pageNumber: 1, + pages: 1, refreshConvoHint: 0, - convos: [] + convos: [], }; const currentSlice = createSlice({ @@ -30,12 +31,18 @@ const currentSlice = createSlice({ setError: (state, action) => { state.error = action.payload; }, - incrementPage: (state) => { + increasePage: (state) => { state.pageNumber = state.pageNumber + 1; }, + decreasePage: (state) => { + state.pageNumber = state.pageNumber - 1; + }, + setPage: (state, action) => { + state.pageNumber = action.payload; + }, setNewConvo: (state) => { state.error = false; - state.title = 'New Chat'; + state.title = 'ChatGPT Clone'; state.conversationId = null; state.parentMessageId = null; state.jailbreakConversationId = null; @@ -45,13 +52,15 @@ const currentSlice = createSlice({ state.chatGptLabel = null; state.promptPrefix = null; state.convosLoading = false; - state.pageNumber = 1; }, setConvos: (state, action) => { state.convos = action.payload.sort( (a, b) => new Date(b.createdAt) - new Date(a.createdAt) ); }, + setPages: (state, action) => { + state.pages = action.payload; + }, removeConvo: (state, action) => { state.convos = state.convos.filter((convo) => convo.conversationId !== action.payload); }, @@ -61,7 +70,7 @@ const currentSlice = createSlice({ } }); -export const { refreshConversation, setConversation, setConvos, setNewConvo, setError, incrementPage, removeConvo, removeAll } = +export const { refreshConversation, setConversation, setPages, setConvos, setNewConvo, setError, increasePage, decreasePage, setPage, removeConvo, removeAll } = currentSlice.actions; export default currentSlice.reducer; diff --git a/client/src/style.css b/client/src/style.css index 6b17c388e2..c4a03408b7 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -1876,3 +1876,6 @@ button.scroll-convo { background-color:hsla(0,0%,100%,.4) } } +.hidden-visibility { + visibility: hidden; +} \ No newline at end of file diff --git a/client/src/utils/fetchers.js b/client/src/utils/fetchers.js index 9faa211d4e..d9cfa54116 100644 --- a/client/src/utils/fetchers.js +++ b/client/src/utils/fetchers.js @@ -9,14 +9,14 @@ const postRequest = async (url, { arg }) => { return await axios.post(url, { arg }); }; -export const swr = (path, successCallback) => { - const options = {}; +export const swr = (path, successCallback, options) => { + const _options = {...options}; if (successCallback) { - options.onSuccess = successCallback; + _options.onSuccess = successCallback; } - return useSWR(path, fetcher, options); + return useSWR(path, fetcher, _options); } export default function manualSWR(path, type, successCallback) { From d0ef0f84c85ff16201c45863eaed5edd490c6246 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 14 Mar 2023 16:05:46 -0400 Subject: [PATCH 31/47] chore: delegate text handling to one place, html sanitization in progress --- api/server/routes/ask.js | 7 ++++--- api/server/routes/askBing.js | 3 ++- api/server/routes/askSydney.js | 3 ++- api/server/routes/handlers.js | 24 +++++++++++++++++++----- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index 5f94d7d4c3..8d20b808dc 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -8,10 +8,10 @@ const { askClient, browserClient, customClient, - detectCode + // detectCode } = require('../../app/'); const { getConvo, saveMessage, getConvoTitle, saveConvo } = require('../../models'); -const { handleError, sendMessage, createOnProgress } = require('./handlers'); +const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); const { getMessages } = require('../../models/Message'); router.use('/bing', askBing); @@ -177,7 +177,8 @@ const ask = async ({ gptResponse.sender = model === 'chatgptCustom' ? convo.chatGptLabel : model; // gptResponse.final = true; - gptResponse.text = await detectCode(gptResponse.text); + // gptResponse.text = await detectCode(gptResponse.text); + gptResponse.text = await handleText(gptResponse.text); if (convo.chatGptLabel?.length > 0 && model === 'chatgptCustom') { gptResponse.chatGptLabel = convo.chatGptLabel; diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index add804a43e..5bb8abd4f0 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -3,7 +3,7 @@ const crypto = require('crypto'); const router = express.Router(); const { titleConvo, getCitations, citeText, askBing } = require('../../app/'); const { saveMessage, getConvoTitle, saveConvo } = require('../../models'); -const { handleError, sendMessage, createOnProgress } = require('./handlers'); +const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { @@ -113,6 +113,7 @@ const ask = async ({ response.text = citeText(response) + (links?.length > 0 && hasCitations ? `\n${links}` : ''); + response.text = await handleText(response.text); await saveMessage(response); await saveConvo({...response, model, ...convo}); diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 1f04ad4166..a0a3bd32df 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -3,7 +3,7 @@ const crypto = require('crypto'); const router = express.Router(); const { titleConvo, getCitations, citeText, askSydney } = require('../../app/'); const { saveMessage, saveConvo, getConvoTitle } = require('../../models'); -const { handleError, sendMessage, createOnProgress } = require('./handlers'); +const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { @@ -114,6 +114,7 @@ const ask = async ({ response.text = citeText(response) + (links?.length > 0 && hasCitations ? `\n${links}` : ''); + response.text = await handleText(response.text); // Save user message userMessage.conversationId = response.conversationId || conversationId; diff --git a/api/server/routes/handlers.js b/api/server/routes/handlers.js index 7672ef41a5..d3e613bccc 100644 --- a/api/server/routes/handlers.js +++ b/api/server/routes/handlers.js @@ -1,4 +1,4 @@ -const { citeText } = require('../../app/'); +const { citeText, detectCode } = require('../../app/'); const _ = require('lodash'); const sanitizeHtml = require('sanitize-html'); @@ -20,11 +20,14 @@ const createOnProgress = () => { const progressCallback = async (partial, { res, text, bing = false, ...rest }) => { tokens += partial === text ? '' : partial; - tokens = tokens.trim(); tokens = tokens.replaceAll('[DONE]', ''); - if (tokens.includes('```')) { - tokens = sanitizeHtml(tokens); + + if (tokens.match(/^\n/)) { + tokens = tokens.replace(/^\n/, ''); } + // if (tokens.includes('```')) { + // tokens = sanitizeHtml(tokens); + // } if (bing) { tokens = citeText(tokens, true); @@ -42,4 +45,15 @@ const createOnProgress = () => { return onProgress; }; -module.exports = { handleError, sendMessage, createOnProgress }; \ No newline at end of file +const handleText = async (input) => { + let text = input; + text = await detectCode(text); + // if (text.includes('```')) { + // text = sanitizeHtml(text); + // text = text.replaceAll(') =>', ') =>'); + // } + + return text; +}; + +module.exports = { handleError, sendMessage, createOnProgress, handleText }; \ No newline at end of file From 626a8fbd8e048b16d0bca80bc9ac5f44cf304f63 Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Tue, 14 Mar 2023 18:53:46 -0400 Subject: [PATCH 32/47] fix: if db is empty, will never show new convos until refresh --- api/models/Conversation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/models/Conversation.js b/api/models/Conversation.js index 622744d27a..c4151198cd 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -93,7 +93,7 @@ module.exports = { // getConvos: async () => await Conversation.find({}).sort({ createdAt: -1 }).exec(), getConvosByPage: async (pageNumber = 1, pageSize = 12) => { try { - const totalConvos = await Conversation.countDocuments(); + const totalConvos = (await Conversation.countDocuments()) || 1; const totalPages = Math.ceil(totalConvos / pageSize); const convos = await Conversation.find() .sort({ createdAt: -1, created: -1 }) From 6192c2964ed48adea4e95c8d580692e42b3fd8fd Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Tue, 14 Mar 2023 20:14:38 -0400 Subject: [PATCH 33/47] fix: validation to avoid saving customGpt params to non-custom models --- api/models/Conversation.js | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/api/models/Conversation.js b/api/models/Conversation.js index c4151198cd..45834379e8 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -18,10 +18,12 @@ const convoSchema = mongoose.Schema( default: 'New Chat' }, jailbreakConversationId: { - type: String + type: String, + default: null }, conversationSignature: { - type: String + type: String, + default: null }, clientId: { type: String @@ -30,13 +32,16 @@ const convoSchema = mongoose.Schema( type: String }, chatGptLabel: { - type: String + type: String, + default: null }, promptPrefix: { - type: String + type: String, + default: null }, model: { - type: String + type: String, + required: true }, suggestions: [{ type: String }], messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }] @@ -67,8 +72,14 @@ module.exports = { if (newConversationId) { update.conversationId = newConversationId; } - if (!update.jailbreakConversationId) - update.jailbreakConversationId = null + if (!update.jailbreakConversationId) { + update.jailbreakConversationId = null; + } + if (update.model !== 'chatgptCustom' && update.chatGptLabel && update.promptPrefix) { + console.log('Validation error: resetting chatgptCustom fields', update); + update.chatGptLabel = null; + update.promptPrefix = null; + } return await Conversation.findOneAndUpdate( { conversationId }, @@ -146,7 +157,7 @@ module.exports = { } else { message.parentMessageId = oldId; } - + oldId = newId; message.messageId = newId; if (message.sender.toLowerCase() !== 'user' && !model) { From 6e32f71565ba9449e64593bd684c8d3d60fb108a Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Tue, 14 Mar 2023 20:15:06 -0400 Subject: [PATCH 34/47] fix: adjust custom client for new progress CB --- api/app/chatgpt-custom.js | 7 ++----- api/app/chatgpt.js | 38 -------------------------------------- 2 files changed, 2 insertions(+), 43 deletions(-) delete mode 100644 api/app/chatgpt.js diff --git a/api/app/chatgpt-custom.js b/api/app/chatgpt-custom.js index 9eabb80ccd..e7a0ee0503 100644 --- a/api/app/chatgpt-custom.js +++ b/api/app/chatgpt-custom.js @@ -9,7 +9,7 @@ const clientOptions = { debug: false }; -const customClient = async ({ text, progressCallback, convo, promptPrefix, chatGptLabel }) => { +const customClient = async ({ text, onProgress, convo, promptPrefix, chatGptLabel }) => { const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default; const store = { store: new KeyvFile({ filename: './data/cache.json' }) @@ -23,10 +23,7 @@ const customClient = async ({ text, progressCallback, convo, promptPrefix, chatG const client = new ChatGPTClient(process.env.OPENAI_KEY, clientOptions, store); - let options = { - onProgress: async (partialRes) => await progressCallback(partialRes) - }; - + let options = { onProgress }; if (!!convo.parentMessageId && !!convo.conversationId) { options = { ...options, ...convo }; } diff --git a/api/app/chatgpt.js b/api/app/chatgpt.js deleted file mode 100644 index 18edcfca83..0000000000 --- a/api/app/chatgpt.js +++ /dev/null @@ -1,38 +0,0 @@ -require('dotenv').config(); -const Keyv = require('keyv'); -const { Configuration, OpenAIApi } = require('openai'); -const messageStore = new Keyv(process.env.MONGODB_URI, { namespace: 'chatgpt' }); - -const ask = async (question, progressCallback, convo) => { - const { ChatGPTAPI } = await import('chatgpt'); - const api = new ChatGPTAPI({ apiKey: process.env.OPENAI_KEY, messageStore }); - let options = { - onProgress: async (partialRes) => { - if (partialRes.text.length > 0) { - await progressCallback(partialRes); - } - } - }; - - if (!!convo.parentMessageId && !!convo.conversationId) { - options = { ...options, ...convo }; - } - - const res = await api.sendMessage(question, options); - return res; -}; - -const titleConvo = async (message, response, model) => { - const configuration = new Configuration({ - apiKey: process.env.OPENAI_KEY - }); - const openai = new OpenAIApi(configuration); - const completion = await openai.createCompletion({ - model: 'text-davinci-002', - prompt: `Write a short title in title case, ideally in 5 words or less, and do not refer to the user or ${model}, that summarizes this conversation:\nUser:"${message}"\n${model}:"${response}"\nTitle: ` - }); - - return completion.data.choices[0].text.replace(/\n/g, ''); -}; - -module.exports = { ask, titleConvo }; From 796d8031e8d2c82fa18de6ec8de1cd94c2ed0128 Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Tue, 14 Mar 2023 20:21:41 -0400 Subject: [PATCH 35/47] fix: ensure custom params are not passed to non custom models --- api/package-lock.json | 274 +++++++++++++++++- api/package.json | 3 +- api/server/routes/ask.js | 92 +++--- api/server/routes/askBing.js | 94 +++--- api/server/routes/askSydney.js | 96 +++--- client/package-lock.json | 12 +- .../components/Conversations/Conversation.jsx | 16 +- client/src/components/Models/ModelMenu.jsx | 6 +- client/src/components/Nav/index.jsx | 56 ++-- client/src/store/submitSlice.js | 5 +- 10 files changed, 503 insertions(+), 151 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index daa36e69e4..f934f00b8d 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -19,7 +19,8 @@ "keyv-file": "^0.2.0", "lodash": "^4.17.21", "mongoose": "^6.9.0", - "openai": "^3.1.0" + "openai": "^3.1.0", + "sanitize-html": "^2.10.0" }, "devDependencies": { "nodemon": "^2.0.20", @@ -2211,6 +2212,14 @@ } } }, + "node_modules/deepmerge": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -2247,6 +2256,57 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.0.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", @@ -2278,6 +2338,17 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2751,6 +2822,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2958,6 +3047,14 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -3273,6 +3370,17 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -3469,6 +3577,11 @@ "p-defer": "^3.0.0" } }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3577,6 +3690,29 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.1.0.tgz", "integrity": "sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==" }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -3796,6 +3932,30 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sanitize-html": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.10.0.tgz", + "integrity": "sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==", + "dependencies": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, + "node_modules/sanitize-html/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/saslprep": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", @@ -3985,6 +4145,14 @@ "atomic-sleep": "^1.0.0" } }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -6275,6 +6443,11 @@ "ms": "2.1.2" } }, + "deepmerge": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==" + }, "defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -6298,6 +6471,39 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + } + }, "dotenv": { "version": "16.0.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", @@ -6323,6 +6529,11 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -6687,6 +6898,17 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, + "htmlparser2": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", + "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "entities": "^4.3.0" + } + }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -6826,6 +7048,11 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -7074,6 +7301,11 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -7217,6 +7449,11 @@ "p-defer": "^3.0.0" } }, + "parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -7303,6 +7540,16 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.1.0.tgz", "integrity": "sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==" }, + "postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -7458,6 +7705,26 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sanitize-html": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.10.0.tgz", + "integrity": "sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==", + "requires": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + } + } + }, "saslprep": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", @@ -7615,6 +7882,11 @@ "atomic-sleep": "^1.0.0" } }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", diff --git a/api/package.json b/api/package.json index 6dcf344ea0..561908fad3 100644 --- a/api/package.json +++ b/api/package.json @@ -29,7 +29,8 @@ "keyv-file": "^0.2.0", "lodash": "^4.17.21", "mongoose": "^6.9.0", - "openai": "^3.1.0" + "openai": "^3.1.0", + "sanitize-html": "^2.10.0" }, "devDependencies": { "nodemon": "^2.0.20", diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index 8d20b808dc..265672db8b 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -7,7 +7,7 @@ const { titleConvo, askClient, browserClient, - customClient, + customClient // detectCode } = require('../../app/'); const { getConvo, saveMessage, getConvoTitle, saveConvo } = require('../../models'); @@ -18,7 +18,7 @@ router.use('/bing', askBing); router.use('/sydney', askSydney); router.post('/', async (req, res) => { - let { model, text, parentMessageId, conversationId: oldConversationId , ...convo } = req.body; + let { model, text, parentMessageId, conversationId: oldConversationId, ...convo } = req.body; if (text.length === 0) { return handleError(res, { text: 'Prompt empty or too short' }); } @@ -26,14 +26,14 @@ router.post('/', async (req, res) => { const conversationId = oldConversationId || crypto.randomUUID(); const userMessageId = crypto.randomUUID(); - const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' + const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000'; let userMessage = { - messageId: userMessageId, - sender: 'User', - text, + messageId: userMessageId, + sender: 'User', + text, parentMessageId: userParentMessageId, - conversationId, - isCreatedByUser: true + conversationId, + isCreatedByUser: true }; console.log('ask log', { @@ -55,27 +55,28 @@ router.post('/', async (req, res) => { await saveConvo({ ...userMessage, model, ...convo }); return await ask({ - userMessage, + userMessage, model, convo, preSendRequest: true, - req, res + req, + res }); -}) +}); router.post('/regenerate', async (req, res) => { - const { parentMessageId, model, chatGptLabel, promptPrefix } = req.body; + const { model } = req.body; - const oldUserMessage = await getMessages({ messageId: req.body }) + const oldUserMessage = await getMessages({ messageId: req.body }); if (oldUserMessage) { - const convo = await getConvo(userMessage?.conversationId) + const convo = await getConvo(userMessage?.conversationId); const userMessageId = crypto.randomUUID(); let userMessage = { ...userMessage, - messageId: userMessageId, + messageId: userMessageId }; console.log('ask log for regeneration', { @@ -85,14 +86,14 @@ router.post('/regenerate', async (req, res) => { }); return await ask({ - userMessage, + userMessage, model, convo, preSendRequest: false, - req, res + req, + res }); - } else - return handleError(res, { text: 'Parent message not found' }); + } else return handleError(res, { text: 'Parent message not found' }); // if (model === 'chatgptCustom' && !chatGptLabel && conversationId) { // const convo = await getConvo({ conversationId }); @@ -106,15 +107,21 @@ router.post('/regenerate', async (req, res) => { // await saveConvo({ ...userMessage, model, chatGptLabel, promptPrefix }); }); -const ask = async ({ - userMessage, +const ask = async ({ + userMessage, overrideParentMessageId = null, model, convo, preSendRequest = true, - req, res + req, + res }) => { - let { sender, text, parentMessageId: userParentMessageId, conversationId, messageId: userMessageId } = userMessage; + let { + text, + parentMessageId: userParentMessageId, + conversationId, + messageId: userMessageId + } = userMessage; let client; @@ -134,14 +141,13 @@ const ask = async ({ 'X-Accel-Buffering': 'no' }); - if (preSendRequest) - sendMessage(res, { message: userMessage, created: true }); + if (preSendRequest) sendMessage(res, { message: userMessage, created: true }); try { const progressCallback = createOnProgress(); let gptResponse = await client({ text, - onProgress: progressCallback.call(null, model, {res, text }), + onProgress: progressCallback.call(null, model, { res, text }), convo: { parentMessageId: userParentMessageId, conversationId, @@ -168,16 +174,20 @@ const ask = async ({ gptResponse.text.toLowerCase().includes('no response') || gptResponse.text.toLowerCase().includes('no answer') ) { - await saveMessage({ - messageId: crypto.randomUUID(), sender: model, - conversationId, parentMessageId: overrideParentMessageId || userMessageId, - error: true, text: 'Prompt empty or too short'}); + await saveMessage({ + messageId: crypto.randomUUID(), + sender: model, + conversationId, + parentMessageId: overrideParentMessageId || userMessageId, + error: true, + text: 'Prompt empty or too short' + }); return handleError(res, { text: 'Prompt empty or too short' }); } gptResponse.sender = model === 'chatgptCustom' ? convo.chatGptLabel : model; + gptResponse.model = model; // gptResponse.final = true; - // gptResponse.text = await detectCode(gptResponse.text); gptResponse.text = await handleText(gptResponse.text); if (convo.chatGptLabel?.length > 0 && model === 'chatgptCustom') { @@ -189,14 +199,14 @@ const ask = async ({ } // override the parentMessageId, for the regeneration. - gptResponse.parentMessageId = overrideParentMessageId || userMessageId + gptResponse.parentMessageId = overrideParentMessageId || userMessageId; await saveMessage(gptResponse); await saveConvo(gptResponse); sendMessage(res, { title: await getConvoTitle(conversationId), - final: true, - requestMessage: userMessage, + final: true, + requestMessage: userMessage, responseMessage: gptResponse }); res.end(); @@ -213,15 +223,19 @@ const ask = async ({ await saveConvo({ conversationId, title - }) + }); } } catch (error) { console.log(error); // await deleteMessages({ messageId: userMessageId }); - const errorMessage = { - messageId: crypto.randomUUID(), sender: model, - conversationId, parentMessageId: overrideParentMessageId || userMessageId, - error: true, text: error.message} + const errorMessage = { + messageId: crypto.randomUUID(), + sender: model, + conversationId, + parentMessageId: overrideParentMessageId || userMessageId, + error: true, + text: error.message + }; await saveMessage(errorMessage); handleError(res, errorMessage); } diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 5bb8abd4f0..066b64faec 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -7,26 +7,32 @@ const { handleError, sendMessage, createOnProgress, handleText } = require('./ha const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { - const { model, text, parentMessageId, conversationId: oldConversationId, ...convo } = req.body; + const { + model, + text, + parentMessageId, + conversationId: oldConversationId, + ...convo + } = req.body; if (text.length === 0) { return handleError(res, { text: 'Prompt empty or too short' }); } const conversationId = oldConversationId || crypto.randomUUID(); - const isNewConversation = !oldConversationId + const isNewConversation = !oldConversationId; const userMessageId = crypto.randomUUID(); - const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' + const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000'; let userMessage = { - messageId: userMessageId, - sender: 'User', - text, + messageId: userMessageId, + sender: 'User', + text, parentMessageId: userParentMessageId, - conversationId, - isCreatedByUser: true - }; + conversationId, + isCreatedByUser: true + }; - console.log('ask log', { + console.log('ask log', { model, ...userMessage, ...convo @@ -37,24 +43,31 @@ router.post('/', async (req, res) => { return await ask({ isNewConversation, - userMessage, + userMessage, model, convo, preSendRequest: true, - req, res + req, + res }); -}) +}); -const ask = async ({ +const ask = async ({ isNewConversation, overrideParentMessageId = null, - userMessage, + userMessage, model, convo, preSendRequest = true, - req, res + req, + res }) => { - let { sender, text, parentMessageId: userParentMessageId, conversationId, messageId: userMessageId } = userMessage; + let { + text, + parentMessageId: userParentMessageId, + conversationId, + messageId: userMessageId + } = userMessage; res.writeHead(200, { Connection: 'keep-alive', @@ -64,19 +77,22 @@ const ask = async ({ 'X-Accel-Buffering': 'no' }); - if (preSendRequest) - sendMessage(res, { message: userMessage, created: true }); + if (preSendRequest) sendMessage(res, { message: userMessage, created: true }); try { const progressCallback = createOnProgress(); let response = await askBing({ text, - onProgress: progressCallback.call(null, model, {res, text, parentMessageId: overrideParentMessageId || userMessageId }), + onProgress: progressCallback.call(null, model, { + res, + text, + parentMessageId: overrideParentMessageId || userMessageId + }), convo: { ...convo, parentMessageId: userParentMessageId, - conversationId, - }, + conversationId + } }); console.log('BING RESPONSE', response); @@ -88,13 +104,16 @@ const ask = async ({ 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 }); + if (conversationId != userMessage.conversationId && isNewConversation) + await saveConvo({ + conversationId: conversationId, + newConversationId: userMessage.conversationId + }); conversationId = userMessage.conversationId; response.text = response.response; @@ -107,7 +126,8 @@ const ask = async ({ // response.final = true; // override the parentMessageId, for the regeneration. - response.parentMessageId = overrideParentMessageId || response.parentMessageId || userMessageId; + response.parentMessageId = + overrideParentMessageId || response.parentMessageId || userMessageId; const links = getCitations(response); response.text = @@ -116,11 +136,11 @@ const ask = async ({ response.text = await handleText(response.text); await saveMessage(response); - await saveConvo({...response, model, ...convo}); + await saveConvo({ ...response, model, chatGptLabel: null, promptPrefix: null, ...convo }); sendMessage(res, { title: await getConvoTitle(conversationId), - final: true, - requestMessage: userMessage, + final: true, + requestMessage: userMessage, responseMessage: response }); res.end(); @@ -133,19 +153,23 @@ const ask = async ({ }); console.log('CONVERSATION TITLE', title); - + await saveConvo({ conversationId, title - }) + }); } } catch (error) { console.log(error); // await deleteMessages({ messageId: userMessageId }); - const errorMessage = { - messageId: crypto.randomUUID(), sender: model, - conversationId, parentMessageId: overrideParentMessageId || userMessageId, - error: true, text: error.message} + const errorMessage = { + messageId: crypto.randomUUID(), + sender: model, + conversationId, + parentMessageId: overrideParentMessageId || userMessageId, + error: true, + text: error.message + }; await saveMessage(errorMessage); handleError(res, errorMessage); } diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index a0a3bd32df..03e3479be4 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -7,26 +7,32 @@ const { handleError, sendMessage, createOnProgress, handleText } = require('./ha const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { - const { model, text, parentMessageId, conversationId: oldConversationId, ...convo } = req.body; + const { + model, + text, + parentMessageId, + conversationId: oldConversationId, + ...convo + } = req.body; if (text.length === 0) { return handleError(res, { text: 'Prompt empty or too short' }); } const conversationId = oldConversationId || crypto.randomUUID(); - const isNewConversation = !oldConversationId + const isNewConversation = !oldConversationId; const userMessageId = crypto.randomUUID(); - const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000' + const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000'; let userMessage = { - messageId: userMessageId, - sender: 'User', - text, + messageId: userMessageId, + sender: 'User', + text, parentMessageId: userParentMessageId, - conversationId, - isCreatedByUser: true - }; + conversationId, + isCreatedByUser: true + }; - console.log('ask log', { + console.log('ask log', { model, ...userMessage, ...convo @@ -37,24 +43,31 @@ router.post('/', async (req, res) => { return await ask({ isNewConversation, - userMessage, + userMessage, model, convo, preSendRequest: true, - req, res + req, + res }); -}) +}); -const ask = async ({ +const ask = async ({ isNewConversation, overrideParentMessageId = null, - userMessage, + userMessage, model, convo, preSendRequest = true, - req, res + req, + res }) => { - let { sender, text, parentMessageId: userParentMessageId, conversationId, messageId: userMessageId } = userMessage; + let { + text, + parentMessageId: userParentMessageId, + conversationId, + messageId: userMessageId + } = userMessage; res.writeHead(200, { Connection: 'keep-alive', @@ -64,19 +77,22 @@ const ask = async ({ 'X-Accel-Buffering': 'no' }); - if (preSendRequest) - sendMessage(res, { message: userMessage, created: true }); + if (preSendRequest) sendMessage(res, { message: userMessage, created: true }); try { const progressCallback = createOnProgress(); let response = await askSydney({ text, - onProgress: progressCallback.call(null, model, {res, text, parentMessageId: overrideParentMessageId || userMessageId }), + onProgress: progressCallback.call(null, model, { + res, + text, + parentMessageId: overrideParentMessageId || userMessageId + }), convo: { parentMessageId: userParentMessageId, conversationId, ...convo - }, + } }); console.log('SYDNEY RESPONSE', response); @@ -89,13 +105,11 @@ const ask = async ({ 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.invocationId = convo.invocationId ? convo.invocationId + 1 : 1; - response.conversationId = conversationId - ? conversationId - : crypto.randomUUID(); + response.conversationId = conversationId ? conversationId : crypto.randomUUID(); response.conversationSignature = convo.conversationSignature ? convo.conversationSignature : crypto.randomUUID(); @@ -108,7 +122,8 @@ const ask = async ({ // response.final = true; // override the parentMessageId, for the regeneration. - response.parentMessageId = overrideParentMessageId || response.parentMessageId || userMessageId; + response.parentMessageId = + overrideParentMessageId || response.parentMessageId || userMessageId; const links = getCitations(response); response.text = @@ -124,17 +139,20 @@ const ask = async ({ // 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 }); + 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, model, ...convo}); + await saveConvo({ ...response, model, chatGptLabel: null, promptPrefix: null, ...convo }); sendMessage(res, { title: await getConvoTitle(conversationId), - final: true, - requestMessage: userMessage, + final: true, + requestMessage: userMessage, responseMessage: response }); res.end(); @@ -147,19 +165,23 @@ const ask = async ({ }); console.log('CONVERSATION TITLE', title); - + await saveConvo({ conversationId, title - }) + }); } } catch (error) { console.log(error); // await deleteMessages({ messageId: userMessageId }); - const errorMessage = { - messageId: crypto.randomUUID(), sender: model, - conversationId, parentMessageId: overrideParentMessageId || userMessageId, - error: true, text: error.message} + const errorMessage = { + messageId: crypto.randomUUID(), + sender: model, + conversationId, + parentMessageId: overrideParentMessageId || userMessageId, + error: true, + text: error.message + }; await saveMessage(errorMessage); handleError(res, errorMessage); } diff --git a/client/package-lock.json b/client/package-lock.json index fbfcb2b345..d5e3024751 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -11125,9 +11125,9 @@ } }, "node_modules/webpack": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", - "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "version": "5.76.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz", + "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -19431,9 +19431,9 @@ } }, "webpack": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", - "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "version": "5.76.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz", + "integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", diff --git a/client/src/components/Conversations/Conversation.jsx b/client/src/components/Conversations/Conversation.jsx index 4d90ecb2cc..b89f9c8623 100644 --- a/client/src/components/Conversations/Conversation.jsx +++ b/client/src/components/Conversations/Conversation.jsx @@ -42,6 +42,8 @@ export default function Conversation({ dispatch(setEmptyMessage()); const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix }; + // debugging + console.log(model, chatGptLabel, promptPrefix); if (bingData) { const { @@ -77,17 +79,19 @@ export default function Conversation({ if (chatGptLabel) { dispatch(setModel('chatgptCustom')); + dispatch(setCustomModel(chatGptLabel.toLowerCase())); } else { dispatch(setModel(model)); - } - - if (modelMap[model.toLowerCase()]) { - console.log('sender', model); - dispatch(setCustomModel(model.toLowerCase())); - } else { dispatch(setCustomModel(null)); } + // if (modelMap[chatGptLabel.toLowerCase()]) { + // console.log('custom model', chatGptLabel); + // dispatch(setCustomModel(chatGptLabel.toLowerCase())); + // } else { + // dispatch(setCustomModel(null)); + // } + dispatch(setMessages(data)); dispatch(setCustomGpt(convo)); dispatch(setText('')); diff --git a/client/src/components/Models/ModelMenu.jsx b/client/src/components/Models/ModelMenu.jsx index 64d8a427b1..05bafc676b 100644 --- a/client/src/components/Models/ModelMenu.jsx +++ b/client/src/components/Models/ModelMenu.jsx @@ -46,7 +46,10 @@ export default function ModelMenu() { mutate(); try { const lastSelected = JSON.parse(localStorage.getItem('model')); - if (lastSelected && lastSelected !== 'chatgptCustom' && initial[lastSelected]) { + + if (lastSelected === 'chatgptCustom') { + return; + } else if (initial[lastSelected]) { dispatch(setModel(lastSelected)); } } catch (err) { @@ -72,6 +75,7 @@ export default function ModelMenu() { dispatch(setModel(value)); dispatch(setDisabled(false)); dispatch(setCustomModel(null)); + dispatch(setCustomGpt({ chatGptLabel: null, promptPrefix: null })); } else if (!initial[value]) { const chatGptLabel = modelMap[value]?.chatGptLabel; const promptPrefix = modelMap[value]?.promptPrefix; diff --git a/client/src/components/Nav/index.jsx b/client/src/components/Nav/index.jsx index ea95559cd8..9c02a5e1ea 100644 --- a/client/src/components/Nav/index.jsx +++ b/client/src/components/Nav/index.jsx @@ -11,22 +11,23 @@ import { increasePage, decreasePage, setPage, setConvos, setPages } from '~/stor export default function Nav({ navVisible, setNavVisible }) { const dispatch = useDispatch(); const [isHovering, setIsHovering] = useState(false); - const { conversationId, convos, pages, pageNumber, refreshConvoHint } = useSelector((state) => state.convo); + const { conversationId, convos, pages, pageNumber, refreshConvoHint } = useSelector( + (state) => state.convo + ); const onSuccess = (data) => { const { conversations, pages } = data; - if (pageNumber > pages) + if (pageNumber > pages) { dispatch(setPage(pages)); - else + } else { dispatch(setConvos(conversations)); - dispatch(setPages(pages)); + dispatch(setPages(pages)); + } }; - const { data, isLoading, mutate } = swr( - `/api/convos?pageNumber=${pageNumber}`, - onSuccess, - {revalidateOnMount: false} - ); + const { data, isLoading, mutate } = swr(`/api/convos?pageNumber=${pageNumber}`, onSuccess, { + revalidateOnMount: false + }); const containerRef = useRef(null); const scrollPositionRef = useRef(null); @@ -36,23 +37,25 @@ export default function Nav({ navVisible, setNavVisible }) { if (container) { scrollPositionRef.current = container.scrollTop; } - } + }; const nextPage = async () => { - moveToTop() + moveToTop(); dispatch(increasePage()); await mutate(); }; const previousPage = async () => { - moveToTop() - + moveToTop(); + dispatch(decreasePage()); await mutate(); }; - useEffect(() => {mutate()}, [pageNumber, conversationId, refreshConvoHint]); + useEffect(() => { + mutate(); + }, [pageNumber, conversationId, refreshConvoHint]); useEffect(() => { const container = containerRef.current; @@ -66,14 +69,14 @@ export default function Nav({ navVisible, setNavVisible }) { }, [data]); useEffect(() => { - setNavVisible(false) - }, [conversationId, ]) + setNavVisible(false); + }, [conversationId]); const toggleNavVisible = () => { setNavVisible((prev) => { - return !prev - }) - } + return !prev; + }); + }; const containerClasses = isLoading && pageNumber === 1 @@ -82,7 +85,12 @@ export default function Nav({ navVisible, setNavVisible }) { return ( <> -
+
-
-
+
); } diff --git a/client/src/store/submitSlice.js b/client/src/store/submitSlice.js index a1b4afee34..a965c29335 100644 --- a/client/src/store/submitSlice.js +++ b/client/src/store/submitSlice.js @@ -6,8 +6,8 @@ const initialState = { stopStream: false, disabled: false, model: 'chatgpt', - promptPrefix: '', - chatGptLabel: '', + promptPrefix: null, + chatGptLabel: null, customModel: null, }; @@ -34,6 +34,7 @@ const currentSlice = createSlice({ state.model = action.payload; }, setCustomGpt: (state, action) => { + console.log('setCustomGpt', action.payload); state.promptPrefix = action.payload.promptPrefix; state.chatGptLabel = action.payload.chatGptLabel; }, From 918f2fecb6d469bd54fda73b60adc782ad6612db Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Tue, 14 Mar 2023 21:25:02 -0400 Subject: [PATCH 36/47] fix: convo resets on model change --- client/src/components/Main/TextChat.jsx | 44 ++++------------------ client/src/components/Models/ModelMenu.jsx | 7 ++-- client/src/utils/createPayload.js | 31 +++++++++++++++ client/src/utils/resetConvo.js | 22 +++++++++++ 4 files changed, 64 insertions(+), 40 deletions(-) create mode 100644 client/src/utils/createPayload.js create mode 100644 client/src/utils/resetConvo.js diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 8f1276fce6..c4fd6a8f23 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -1,18 +1,17 @@ import React, { useEffect, useRef, useState } from 'react'; import { SSE } from '~/utils/sse'; -import axios from 'axios'; import SubmitButton from './SubmitButton'; import Regenerate from './Regenerate'; import ModelMenu from '../Models/ModelMenu'; import Footer from './Footer'; import TextareaAutosize from 'react-textarea-autosize'; -import handleSubmit from '~/utils/handleSubmit'; +import createPayload from '~/utils/createPayload'; +import resetConvo from '~/utils/resetConvo'; import { useSelector, useDispatch } from 'react-redux'; import { setConversation, setError, refreshConversation } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; import { setSubmitState, setSubmission } from '~/store/submitSlice'; import { setText } from '~/store/textSlice'; -import manualSWR from '~/utils/fetchers'; export default function TextChat({ messages }) { const [errorMessage, setErrorMessage] = useState(''); @@ -25,7 +24,6 @@ export default function TextChat({ messages }) { useSelector((state) => state.submit); const { text } = useSelector((state) => state.text); const { error } = convo; - const genTitle = manualSWR(`/api/convos/gen_title`, 'post'); // auto focus to input, when enter a conversation. useEffect(() => { @@ -157,8 +155,12 @@ export default function TextChat({ messages }) { const fakeMessageId = crypto.randomUUID(); const isCustomModel = model === 'chatgptCustom' || !initial[model]; const message = text.trim(); - const currentMsg = { sender: 'User', text: message, current: true, isCreatedByUser: true, parentMessageId: convo.parentMessageId || '00000000-0000-0000-0000-000000000000', messageId: fakeMessageId }; const sender = model === 'chatgptCustom' ? chatGptLabel : model; + let parentMessageId = convo.parentMessageId || '00000000-0000-0000-0000-000000000000'; + if (resetConvo(messages, sender)) { + parentMessageId = '00000000-0000-0000-0000-000000000000'; + } + const currentMsg = { sender: 'User', text: message, current: true, isCreatedByUser: true, parentMessageId , messageId: fakeMessageId }; const initialResponse = { sender, text: '', parentMessageId: fakeMessageId, submitting: true }; dispatch(setSubmitState(true)); @@ -184,38 +186,6 @@ export default function TextChat({ messages }) { dispatch(setSubmission(submission)); }; - const createPayload = ({ convo, message }) => { - const endpoint = `/api/ask`; - let payload = { ...message }; - const { model } = message - - if (!payload.conversationId) - if (convo?.conversationId && convo?.parentMessageId) { - payload = { - ...payload, - conversationId: convo.conversationId, - parentMessageId: convo.parentMessageId || '00000000-0000-0000-0000-000000000000' - }; - } - - const isBing = model === 'bingai' || model === 'sydney'; - if (isBing && convo?.conversationId) { - payload = { - ...payload, - jailbreakConversationId: convo.jailbreakConversationId, - conversationId: convo.conversationId, - conversationSignature: convo.conversationSignature, - clientId: convo.clientId, - invocationId: convo.invocationId - }; - } - - let server = endpoint; - server = model === 'bingai' ? server + '/bing' : server; - server = model === 'sydney' ? server + '/sydney' : server; - return { server, payload }; - }; - useEffect(() => { if (Object.keys(submission).length === 0) { return; diff --git a/client/src/components/Models/ModelMenu.jsx b/client/src/components/Models/ModelMenu.jsx index 05bafc676b..7fc819209d 100644 --- a/client/src/components/Models/ModelMenu.jsx +++ b/client/src/components/Models/ModelMenu.jsx @@ -64,6 +64,9 @@ export default function ModelMenu() { }, [model]); const onChange = (value, custom = false) => { + // Set new conversation + dispatch(setNewConvo()); + dispatch(setSubmission({})); // if (custom) { // mutate(); // } @@ -89,9 +92,7 @@ export default function ModelMenu() { dispatch(setCustomModel(null)); } - // Set new conversation - dispatch(setNewConvo()); - dispatch(setSubmission({})); + }; const onOpenChange = (open) => { diff --git a/client/src/utils/createPayload.js b/client/src/utils/createPayload.js new file mode 100644 index 0000000000..fd195e43e1 --- /dev/null +++ b/client/src/utils/createPayload.js @@ -0,0 +1,31 @@ +export default function createPayload({ convo, message }) { + const endpoint = `/api/ask`; + let payload = { ...message }; + const { model } = message; + + if (!payload.conversationId) + if (convo?.conversationId && convo?.parentMessageId) { + payload = { + ...payload, + conversationId: convo.conversationId, + parentMessageId: convo.parentMessageId || '00000000-0000-0000-0000-000000000000' + }; + } + + const isBing = model === 'bingai' || model === 'sydney'; + if (isBing && convo?.conversationId) { + payload = { + ...payload, + jailbreakConversationId: convo.jailbreakConversationId, + conversationId: convo.conversationId, + conversationSignature: convo.conversationSignature, + clientId: convo.clientId, + invocationId: convo.invocationId + }; + } + + let server = endpoint; + server = model === 'bingai' ? server + '/bing' : server; + server = model === 'sydney' ? server + '/sydney' : server; + return { server, payload }; +}; diff --git a/client/src/utils/resetConvo.js b/client/src/utils/resetConvo.js new file mode 100644 index 0000000000..8ce01fda57 --- /dev/null +++ b/client/src/utils/resetConvo.js @@ -0,0 +1,22 @@ +export default function resetConvo(messages, sender) { + if (messages.length === 0) { + return false; + } + let modelMessages = messages.filter((message) => !message.isCreatedByUser); + let lastModel = modelMessages[modelMessages.length - 1].sender; + if (lastModel !== sender) { + console.log( + 'Model change! Reseting convo. Original messages: ', + messages, + 'filtered messages: ', + modelMessages, + 'last model: ', + lastModel, + 'sender: ', + sender + ); + return true; + } + + return false; +} From 4e91437049797fe6cde60732741391f1a050f6c9 Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Tue, 14 Mar 2023 21:32:25 -0400 Subject: [PATCH 37/47] fix: convo resets, sets new Convo --- client/src/components/Main/TextChat.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index c4fd6a8f23..3671833ef0 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -8,7 +8,7 @@ import TextareaAutosize from 'react-textarea-autosize'; import createPayload from '~/utils/createPayload'; import resetConvo from '~/utils/resetConvo'; import { useSelector, useDispatch } from 'react-redux'; -import { setConversation, setError, refreshConversation } from '~/store/convoSlice'; +import { setConversation, setNewConvo, setError, refreshConversation } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; import { setSubmitState, setSubmission } from '~/store/submitSlice'; import { setText } from '~/store/textSlice'; @@ -159,6 +159,7 @@ export default function TextChat({ messages }) { let parentMessageId = convo.parentMessageId || '00000000-0000-0000-0000-000000000000'; if (resetConvo(messages, sender)) { parentMessageId = '00000000-0000-0000-0000-000000000000'; + dispatch(setNewConvo()); } const currentMsg = { sender: 'User', text: message, current: true, isCreatedByUser: true, parentMessageId , messageId: fakeMessageId }; const initialResponse = { sender, text: '', parentMessageId: fakeMessageId, submitting: true }; From 54aa9debb4bad5484b4d53600e4a4a31b5084758 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 15 Mar 2023 13:38:01 +0800 Subject: [PATCH 38/47] fix: don't reset new convo if model not change fix: change model will clear all messages. --- client/src/components/Models/ModelMenu.jsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/client/src/components/Models/ModelMenu.jsx b/client/src/components/Models/ModelMenu.jsx index 7fc819209d..4e47ccbb1b 100644 --- a/client/src/components/Models/ModelMenu.jsx +++ b/client/src/components/Models/ModelMenu.jsx @@ -12,6 +12,8 @@ import ModelDialog from './ModelDialog'; import MenuItems from './MenuItems'; import { swr } from '~/utils/fetchers'; import { setModels } from '~/store/modelSlice'; +import { setMessages } from '~/store/messageSlice'; +import { setText } from '~/store/textSlice'; import GPTIcon from '../svg/GPTIcon'; import BingIcon from '../svg/BingIcon'; import { Button } from '../ui/Button.tsx'; @@ -64,16 +66,13 @@ export default function ModelMenu() { }, [model]); const onChange = (value, custom = false) => { - // Set new conversation - dispatch(setNewConvo()); - dispatch(setSubmission({})); - // if (custom) { - // mutate(); - // } if (!value) { return; + } else if (value === model) { + return; } else if (value === 'chatgptCustom') { // dispatch(setMessages([])); + return; } else if (initial[value]) { dispatch(setModel(value)); dispatch(setDisabled(false)); @@ -92,7 +91,11 @@ export default function ModelMenu() { dispatch(setCustomModel(null)); } - + // Set new conversation + dispatch(setText('')); + dispatch(setMessages([])); + dispatch(setNewConvo()); + dispatch(setSubmission({})); }; const onOpenChange = (open) => { From 5d0b849930921a8cad297c3ac435672d71a0490a Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 15 Mar 2023 14:21:08 +0800 Subject: [PATCH 39/47] feat: show icon within model select menu fix: use icon for gptCustom --- .../components/Conversations/Conversation.jsx | 2 - client/src/components/Messages/Message.jsx | 51 ++--------------- client/src/components/Models/MenuItems.jsx | 3 + client/src/components/Models/ModelItem.jsx | 10 +++- client/src/components/svg/BingIcon.jsx | 6 +- client/src/components/svg/GPTIcon.jsx | 7 +-- client/src/utils/index.js | 57 +++++++++++++++++++ 7 files changed, 80 insertions(+), 56 deletions(-) diff --git a/client/src/components/Conversations/Conversation.jsx b/client/src/components/Conversations/Conversation.jsx index b89f9c8623..9e5c3dce6d 100644 --- a/client/src/components/Conversations/Conversation.jsx +++ b/client/src/components/Conversations/Conversation.jsx @@ -42,8 +42,6 @@ export default function Conversation({ dispatch(setEmptyMessage()); const convo = { title, error: false, conversationId: id, chatGptLabel, promptPrefix }; - // debugging - console.log(model, chatGptLabel, promptPrefix); if (bingData) { const { diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index ac74285be0..cfda8e524f 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -1,8 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import TextWrapper from './TextWrapper'; import { useSelector, useDispatch } from 'react-redux'; -import GPTIcon from '../svg/GPTIcon'; -import BingIcon from '../svg/BingIcon'; import HoverButtons from './HoverButtons'; import SiblingSwitch from './SiblingSwitch'; import Spinner from '../svg/Spinner'; @@ -11,6 +9,7 @@ import { setMessages } from '~/store/messageSlice'; import { setSubmitState, setSubmission } from '~/store/submitSlice'; import { setText } from '~/store/textSlice'; import { setConversation } from '../../store/convoSlice'; +import { getIconOfModel } from '../../utils'; const MultiMessage = ({ messageList, @@ -103,52 +102,12 @@ export default function Message({ className: 'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 bg-white dark:text-gray-100 group dark:bg-gray-800' }; - - const bgColors = { - chatgpt: 'rgb(16, 163, 127)', - chatgptBrowser: 'rgb(25, 207, 207)', - bingai: '', - sydney: '' - }; - - const isBing = sender === 'bingai' || sender === 'sydney'; - - let icon = ( -
- User -
- ); - //`${sender}:`; - - let backgroundColor = bgColors[sender]; - - if (!isCreatedByUser) { + + const icon = getIconOfModel({ sender, isCreatedByUser, model, chatGptLabel, promptPrefix, error }); + + if (!isCreatedByUser) props.className = '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 ((!isCreatedByUser && backgroundColor) || isBing) { - icon = ( -
- {isBing ? : } - {error && ( - - ! - - )} -
- ); - } const wrapText = (text) => ; diff --git a/client/src/components/Models/MenuItems.jsx b/client/src/components/Models/MenuItems.jsx index a119bee381..23e54633cd 100644 --- a/client/src/components/Models/MenuItems.jsx +++ b/client/src/components/Models/MenuItems.jsx @@ -2,6 +2,7 @@ import React from 'react'; import ModelItem from './ModelItem'; export default function MenuItems({ models, onSelect }) { + console.log(models) return ( <> {models.map((modelItem) => ( @@ -11,6 +12,8 @@ export default function MenuItems({ models, onSelect }) { modelName={modelItem.name} value={modelItem.value} onSelect={onSelect} + chatGptLabel={modelItem.chatGptLabel} + promptPrefix={modelItem.promptPrefix} /> ))} diff --git a/client/src/components/Models/ModelItem.jsx b/client/src/components/Models/ModelItem.jsx index c3df204351..e6153e8002 100644 --- a/client/src/components/Models/ModelItem.jsx +++ b/client/src/components/Models/ModelItem.jsx @@ -7,8 +7,9 @@ import { DialogTrigger } from '../ui/Dialog.tsx'; import RenameButton from '../Conversations/RenameButton'; import TrashIcon from '../svg/TrashIcon'; import manualSWR from '~/utils/fetchers'; +import { getIconOfModel } from '../../utils'; -export default function ModelItem({ modelName, value, onSelect, id }) { +export default function ModelItem({ modelName, value, onSelect, id, chatGptLabel, promptPrefix }) { const dispatch = useDispatch(); const { customModel } = useSelector((state) => state.submit); const { initial } = useSelector((state) => state.models); @@ -27,6 +28,8 @@ export default function ModelItem({ modelName, value, onSelect, id }) { dispatch(setModels(fetchedModels)); }); + const icon = getIconOfModel({ size: 16, sender: modelName, isCreatedByUser: false, model: value, chatGptLabel, promptPrefix, error: false, className: "mr-2" }); + if (value === 'chatgptCustom') { return ( @@ -34,6 +37,7 @@ export default function ModelItem({ modelName, value, onSelect, id }) { value={value} className="dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800" > + {icon} {modelName} $ @@ -47,6 +51,7 @@ export default function ModelItem({ modelName, value, onSelect, id }) { value={value} className="dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800" > + {icon} {modelName} {value === 'chatgpt' && $} @@ -122,6 +127,9 @@ export default function ModelItem({ modelName, value, onSelect, id }) { )} + + {icon} + {renaming === true ? ( { + const bgColors = { + chatgpt: 'rgb(16, 163, 127)', + chatgptBrowser: 'rgb(25, 207, 207)', + bingai: 'transparent', + sydney: 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)', + chatgptCustom: 'rgb(0, 163, 255)', + }; + + if (isCreatedByUser) + return ( +
+ User +
+ ) + else if (!isCreatedByUser) { + // TODO: use model from convo, rather than submit + // const { model, chatGptLabel, promptPrefix } = convo; + console.log(model, chatGptLabel) + let background = bgColors[model]; + const isBing = model === 'bingai' || model === 'sydney'; + + return ( +
+ {isBing ? : } + {error && ( + + ! + + )} +
+ ); + } else + return ( +
+ {chatGptLabel} +
+ ) +} From 45ca0a8713904694cd5378d9e07347c3bcab6a48 Mon Sep 17 00:00:00 2001 From: Wentao Lyu <35-wentao.lyu@users.noreply.git.stereye.tech> Date: Wed, 15 Mar 2023 14:42:39 +0800 Subject: [PATCH 40/47] fix: gptCustom icon should show as same in model and message --- client/src/components/Models/MenuItems.jsx | 2 +- client/src/components/Models/ModelItem.jsx | 4 ++-- client/src/store/modelSlice.js | 18 ++++++++++++------ client/src/utils/index.js | 1 - 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/client/src/components/Models/MenuItems.jsx b/client/src/components/Models/MenuItems.jsx index 23e54633cd..d0a277ea9e 100644 --- a/client/src/components/Models/MenuItems.jsx +++ b/client/src/components/Models/MenuItems.jsx @@ -2,7 +2,6 @@ import React from 'react'; import ModelItem from './ModelItem'; export default function MenuItems({ models, onSelect }) { - console.log(models) return ( <> {models.map((modelItem) => ( @@ -11,6 +10,7 @@ export default function MenuItems({ models, onSelect }) { id={modelItem._id} modelName={modelItem.name} value={modelItem.value} + model={modelItem.model || 'chatgptCustom'} onSelect={onSelect} chatGptLabel={modelItem.chatGptLabel} promptPrefix={modelItem.promptPrefix} diff --git a/client/src/components/Models/ModelItem.jsx b/client/src/components/Models/ModelItem.jsx index e6153e8002..163c4206fd 100644 --- a/client/src/components/Models/ModelItem.jsx +++ b/client/src/components/Models/ModelItem.jsx @@ -9,7 +9,7 @@ import TrashIcon from '../svg/TrashIcon'; import manualSWR from '~/utils/fetchers'; import { getIconOfModel } from '../../utils'; -export default function ModelItem({ modelName, value, onSelect, id, chatGptLabel, promptPrefix }) { +export default function ModelItem({ modelName, value, model, onSelect, id, chatGptLabel, promptPrefix }) { const dispatch = useDispatch(); const { customModel } = useSelector((state) => state.submit); const { initial } = useSelector((state) => state.models); @@ -28,7 +28,7 @@ export default function ModelItem({ modelName, value, onSelect, id, chatGptLabel dispatch(setModels(fetchedModels)); }); - const icon = getIconOfModel({ size: 16, sender: modelName, isCreatedByUser: false, model: value, chatGptLabel, promptPrefix, error: false, className: "mr-2" }); + const icon = getIconOfModel({ size: 16, sender: modelName, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, className: "mr-2" }); if (value === 'chatgptCustom') { return ( diff --git a/client/src/store/modelSlice.js b/client/src/store/modelSlice.js index bc34673bfc..7caefdc548 100644 --- a/client/src/store/modelSlice.js +++ b/client/src/store/modelSlice.js @@ -5,27 +5,32 @@ const initialState = { { _id: '0', name: 'ChatGPT', - value: 'chatgpt' + value: 'chatgpt', + model: 'chatgpt' }, { _id: '1', name: 'CustomGPT', - value: 'chatgptCustom' + value: 'chatgptCustom', + model: 'chatgptCustom' }, { _id: '2', name: 'BingAI', - value: 'bingai' + value: 'bingai', + model: 'bingai' }, { _id: '3', name: 'Sydney', - value: 'sydney' + value: 'sydney', + model: 'sydney' }, { _id: '4', name: 'ChatGPT', - value: 'chatgptBrowser' + value: 'chatgptBrowser', + model: 'chatgptBrowser' }, ], modelMap: {}, @@ -45,7 +50,8 @@ const currentSlice = createSlice({ models.slice(initialState.models.length).forEach((modelItem) => { modelMap[modelItem.value] = { chatGptLabel: modelItem.chatGptLabel, - promptPrefix: modelItem.promptPrefix + promptPrefix: modelItem.promptPrefix, + model: 'chatgptCustom' }; }); diff --git a/client/src/utils/index.js b/client/src/utils/index.js index 4231b0c441..a48f2ff3c8 100644 --- a/client/src/utils/index.js +++ b/client/src/utils/index.js @@ -68,7 +68,6 @@ export const getIconOfModel = ({ size=30, sender, isCreatedByUser, model, chatGp else if (!isCreatedByUser) { // TODO: use model from convo, rather than submit // const { model, chatGptLabel, promptPrefix } = convo; - console.log(model, chatGptLabel) let background = bgColors[model]; const isBing = model === 'bingai' || model === 'sydney'; From 96ca7835172c6c0d38ef81bc62b10f5f3bb0a3cf Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 15 Mar 2023 10:42:45 -0400 Subject: [PATCH 41/47] chore: re-organize message modules, fix icon size, convo reset properly rebuilds Tree --- .gitignore | 1 - client/src/App.jsx | 3 +- client/src/components/Main/TextChat.jsx | 8 +- client/src/components/Messages/Message.jsx | 45 ++--------- .../src/components/Messages/MultiMessage.jsx | 40 ++++++++++ client/src/components/Messages/index.jsx | 76 ++++++++----------- client/src/store/messageSlice.js | 3 + client/src/utils/buildTree.js | 17 +++++ 8 files changed, 103 insertions(+), 90 deletions(-) create mode 100644 client/src/components/Messages/MultiMessage.jsx create mode 100644 client/src/utils/buildTree.js diff --git a/.gitignore b/.gitignore index fa70c3c4c4..e201974879 100644 --- a/.gitignore +++ b/.gitignore @@ -47,7 +47,6 @@ bower_components/ .env cache.json api/data/ -.eslintrc.js owner.yml archive .vscode/settings.json diff --git a/client/src/App.jsx b/client/src/App.jsx index 9bab1f5939..5f1446fd0b 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -8,7 +8,7 @@ import useDocumentTitle from '~/hooks/useDocumentTitle'; import { useSelector } from 'react-redux'; const App = () => { - const { messages } = useSelector((state) => state.messages); + const { messages, messageTree } = useSelector((state) => state.messages); const { title } = useSelector((state) => state.convo); const { conversationId } = useSelector((state) => state.convo); const [ navVisible, setNavVisible ]= useState(false) @@ -25,6 +25,7 @@ const App = () => { ) : ( )} diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index 3671833ef0..e0363cd8b1 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -157,15 +157,17 @@ export default function TextChat({ messages }) { const message = text.trim(); const sender = model === 'chatgptCustom' ? chatGptLabel : model; let parentMessageId = convo.parentMessageId || '00000000-0000-0000-0000-000000000000'; - if (resetConvo(messages, sender)) { + let currentMessages = messages; + if (resetConvo(currentMessages, sender)) { parentMessageId = '00000000-0000-0000-0000-000000000000'; dispatch(setNewConvo()); + currentMessages = []; } const currentMsg = { sender: 'User', text: message, current: true, isCreatedByUser: true, parentMessageId , messageId: fakeMessageId }; const initialResponse = { sender, text: '', parentMessageId: fakeMessageId, submitting: true }; dispatch(setSubmitState(true)); - dispatch(setMessages([...messages, currentMsg, initialResponse])); + dispatch(setMessages([...currentMessages, currentMsg, initialResponse])); dispatch(setText('')); const submission = { @@ -177,7 +179,7 @@ export default function TextChat({ messages }) { chatGptLabel, promptPrefix, }, - messages, + messages: currentMessages, currentMsg, initialResponse, sender, diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index cfda8e524f..af60fc1a9a 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import TextWrapper from './TextWrapper'; +import MultiMessage from './MultiMessage'; import { useSelector, useDispatch } from 'react-redux'; import HoverButtons from './HoverButtons'; import SiblingSwitch from './SiblingSwitch'; @@ -11,42 +12,6 @@ import { setText } from '~/store/textSlice'; import { setConversation } from '../../store/convoSlice'; import { getIconOfModel } from '../../utils'; -const MultiMessage = ({ - messageList, - messages, - scrollToBottom, - currentEditId, - setCurrentEditId -}) => { - const [siblingIdx, setSiblingIdx] = useState(0) - - const setSiblingIdxRev = (value) => { - setSiblingIdx(messageList?.length - value - 1) - } - - if (!messageList?.length) return null; - - if (siblingIdx >= messageList?.length) { - setSiblingIdx(0) - return null - } - - return -} - -export { MultiMessage }; - export default function Message({ message, messages, @@ -84,9 +49,9 @@ export default function Message({ dispatch(setConversation({parentMessageId: message?.messageId})) }, [last, ]) - if (sender === '') { - return ; - } + // if (sender === '') { + // return ; + // } const enterEdit = (cancel) => setCurrentEditId(cancel?-1:message.messageId) @@ -167,7 +132,7 @@ export default function Message({ >
-
+
{typeof icon === 'string' && icon.match(/[^\u0000-\u007F]+/) ? ( {icon} ) : ( diff --git a/client/src/components/Messages/MultiMessage.jsx b/client/src/components/Messages/MultiMessage.jsx new file mode 100644 index 0000000000..24ab761eb6 --- /dev/null +++ b/client/src/components/Messages/MultiMessage.jsx @@ -0,0 +1,40 @@ +import React, { useState } from 'react'; +import Message from './Message'; + +export default function MultiMessage({ + messageList, + messages, + scrollToBottom, + currentEditId, + setCurrentEditId +}) { + const [siblingIdx, setSiblingIdx] = useState(0); + + const setSiblingIdxRev = (value) => { + setSiblingIdx(messageList?.length - value - 1); + }; + + // if (!messageList?.length) return null; + if (!(messageList && messageList.length)) { + return null; + } + + if (siblingIdx >= messageList?.length) { + setSiblingIdx(0); + return null; + } + + return ( + + ); +} diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index 85c671d691..eaf0a0cb6d 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -1,12 +1,13 @@ import React, { useEffect, useState, useRef, useMemo } from 'react'; +import Spinner from '../svg/Spinner'; import { CSSTransition } from 'react-transition-group'; import ScrollToBottom from './ScrollToBottom'; -import { MultiMessage } from './Message'; -import Conversation from '../Conversations/Conversation'; +import MultiMessage from './MultiMessage'; +import buildTree from '~/utils/buildTree'; import { useSelector } from 'react-redux'; -const Messages = ({ messages }) => { - const [currentEditId, setCurrentEditId] = useState(-1) +const Messages = ({ messages, messageTree }) => { + const [currentEditId, setCurrentEditId] = useState(-1); const { conversationId } = useSelector((state) => state.convo); const [showScrollButton, setShowScrollButton] = useState(false); const scrollableRef = useRef(null); @@ -23,26 +24,6 @@ const Messages = ({ messages }) => { clearTimeout(timeoutId); }; }, [messages]); - - const messageTree = useMemo(() => buildTree(messages), [messages, ]); - - function buildTree(messages) { - let messageMap = {}; - let rootMessages = []; - - // Traverse the messages array and store each element in messageMap. - messages.forEach(message => { - messageMap[message.messageId] = {...message, children: []}; - - const parentMessage = messageMap[message.parentMessageId]; - if (parentMessage) - parentMessage.children.push(messageMap[message.messageId]); - else - rootMessages.push(messageMap[message.messageId]); - }); - - return rootMessages; - } const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); @@ -79,28 +60,33 @@ const Messages = ({ messages }) => { onScroll={debouncedHandleScroll} > {/*
*/} -
-
- - - {() => showScrollButton && } - - +
+
+ {messageTree.length === 0 ? ( + + ) : ( + <> + + + {() => showScrollButton && } + + + )}
diff --git a/client/src/store/messageSlice.js b/client/src/store/messageSlice.js index 137c329822..955d0c98f8 100644 --- a/client/src/store/messageSlice.js +++ b/client/src/store/messageSlice.js @@ -1,7 +1,9 @@ import { createSlice } from '@reduxjs/toolkit'; +import buildTree from '~/utils/buildTree'; const initialState = { messages: [], + messageTree: [] }; const currentSlice = createSlice({ @@ -10,6 +12,7 @@ const currentSlice = createSlice({ reducers: { setMessages: (state, action) => { state.messages = action.payload; + state.messageTree = buildTree(action.payload); }, setEmptyMessage: (state) => { state.messages = [ diff --git a/client/src/utils/buildTree.js b/client/src/utils/buildTree.js new file mode 100644 index 0000000000..a030509bca --- /dev/null +++ b/client/src/utils/buildTree.js @@ -0,0 +1,17 @@ +export default function buildTree(messages) { + let messageMap = {}; + let rootMessages = []; + + // Traverse the messages array and store each element in messageMap. + messages.forEach(message => { + messageMap[message.messageId] = {...message, children: []}; + + const parentMessage = messageMap[message.parentMessageId]; + if (parentMessage) + parentMessage.children.push(messageMap[message.messageId]); + else + rootMessages.push(messageMap[message.messageId]); + }); + + return rootMessages; +} \ No newline at end of file From 2fd50c99b83978ce6420aadc01cdbfe84bd0ba2c Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 15 Mar 2023 12:47:30 -0400 Subject: [PATCH 42/47] fix: debounce title request and handle error with default title --- api/app/titleConvo.js | 39 +++++++++++++-------- api/server/routes/ask.js | 44 +++++------------------- api/server/routes/askBing.js | 18 +++++----- api/server/routes/askSydney.js | 18 +++++----- api/server/routes/convos.js | 29 ---------------- api/server/routes/handlers.js | 21 +++++++++-- client/src/components/Messages/index.jsx | 1 - 7 files changed, 70 insertions(+), 100 deletions(-) diff --git a/api/app/titleConvo.js b/api/app/titleConvo.js index 88e8c75074..344af6f893 100644 --- a/api/app/titleConvo.js +++ b/api/app/titleConvo.js @@ -1,4 +1,5 @@ const { Configuration, OpenAIApi } = require('openai'); +const _ = require('lodash'); const proxyEnvToAxiosProxy = (proxyString) => { if (!proxyString) return null; @@ -11,29 +12,37 @@ const proxyEnvToAxiosProxy = (proxyString) => { port: port ? parseInt(port) : undefined, auth: username && password ? { username, password } : undefined }; - - return proxyConfig -} + + return proxyConfig; +}; const titleConvo = async ({ message, response, model }) => { const configuration = new Configuration({ apiKey: process.env.OPENAI_KEY }); const openai = new OpenAIApi(configuration); - const completion = await openai.createChatCompletion({ - model: 'gpt-3.5-turbo', - messages: [ - { - role: 'system', - content: - 'You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user in title case, using the same language.' - }, - { role: 'user', content: `In 5 words or less, summarize the conversation below with a title in title case using the language the user writes in. Don't refer to the participants of the conversation by name. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title. Conversation:\n\nUser: "${message}"\n\n${model}: "${response}"\n\nTitle: ` }, - ] - }, { proxy: proxyEnvToAxiosProxy(process.env.PROXY || null) }); + const completion = await openai.createChatCompletion( + { + model: 'gpt-3.5-turbo', + messages: [ + { + role: 'system', + content: + 'You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user in title case, using the same language.' + }, + { + role: 'user', + content: `In 5 words or less, summarize the conversation below with a title in title case using the language the user writes in. Don't refer to the participants of the conversation by name. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title. Conversation:\n\nUser: "${message}"\n\n${model}: "${response}"\n\nTitle: ` + } + ] + }, + { proxy: proxyEnvToAxiosProxy(process.env.PROXY || null) } + ); //eslint-disable-next-line return completion.data.choices[0].message.content.replace(/["\.]/g, ''); }; -module.exports = titleConvo; +const debouncedTitleConvo = _.debounce(titleConvo, 500); + +module.exports = debouncedTitleConvo; diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index 265672db8b..1d497c86b8 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -3,15 +3,15 @@ const crypto = require('crypto'); const router = express.Router(); const askBing = require('./askBing'); const askSydney = require('./askSydney'); -const { - titleConvo, - askClient, - browserClient, - customClient - // detectCode -} = require('../../app/'); +const { askClient, browserClient, customClient } = require('../../app/'); const { getConvo, saveMessage, getConvoTitle, saveConvo } = require('../../models'); -const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); +const { + handleError, + sendMessage, + createOnProgress, + genTitle, + handleText +} = require('./handlers'); const { getMessages } = require('../../models/Message'); router.use('/bing', askBing); @@ -42,15 +42,6 @@ router.post('/', async (req, res) => { ...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 }); @@ -94,17 +85,6 @@ router.post('/regenerate', async (req, res) => { 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 ({ @@ -212,13 +192,7 @@ const ask = async ({ res.end(); if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { - const title = await titleConvo({ - model, - message: text, - response: JSON.stringify(gptResponse?.text) - }); - - console.log('CONVERSATION TITLE', title); + const title = await genTitle({ model, text, response: gptResponse }); await saveConvo({ conversationId, diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 066b64faec..681332ec37 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -1,9 +1,15 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); -const { titleConvo, getCitations, citeText, askBing } = require('../../app/'); +const { getCitations, citeText, askBing } = require('../../app/'); const { saveMessage, getConvoTitle, saveConvo } = require('../../models'); -const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); +const { + handleError, + sendMessage, + createOnProgress, + genTitle, + handleText +} = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { @@ -146,13 +152,7 @@ const ask = async ({ res.end(); if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { - const title = await titleConvo({ - model, - message: text, - response: JSON.stringify(response?.text) - }); - - console.log('CONVERSATION TITLE', title); + const title = await genTitle({ model, text, response }); await saveConvo({ conversationId, diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 03e3479be4..40aa967bcc 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -1,9 +1,15 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); -const { titleConvo, getCitations, citeText, askSydney } = require('../../app/'); +const { getCitations, citeText, askSydney } = require('../../app/'); const { saveMessage, saveConvo, getConvoTitle } = require('../../models'); -const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); +const { + handleError, + sendMessage, + createOnProgress, + genTitle, + handleText +} = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { @@ -158,13 +164,7 @@ const ask = async ({ res.end(); if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { - const title = await titleConvo({ - model, - message: text, - response: JSON.stringify(response?.text) - }); - - console.log('CONVERSATION TITLE', title); + const title = await genTitle({ model, text, response }); await saveConvo({ conversationId, diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index 9a5f71cee3..99fb824708 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -1,41 +1,12 @@ const express = require('express'); const router = express.Router(); -const { titleConvo } = require('../../app/'); -const { getConvo, saveConvo, getConvoTitle } = require('../../models'); const { getConvosByPage, deleteConvos, updateConvo } = require('../../models/Conversation'); -const { getMessages } = require('../../models/Message'); router.get('/', async (req, res) => { const pageNumber = req.query.pageNumber || 1; res.status(200).send(await getConvosByPage(pageNumber)); }); -router.post('/gen_title', async (req, res) => { - const { conversationId } = req.body.arg; - - const convo = await getConvo(conversationId) - const firstMessage = (await getMessages({ conversationId }))[0] - const secondMessage = (await getMessages({ conversationId }))[1] - - // if (convo.title == 'New Chat') { - // const title = await titleConvo({ - // model: convo?.model, - // message: firstMessage?.text, - // response: JSON.stringify(secondMessage?.text || '') - // }); - - // console.log('CONVERSATION TITLE', title); - - // await saveConvo({ - // conversationId, - // title - // }) - - // res.status(200).send(title); - // } else - return res.status(200).send(convo.title); -}); - router.post('/clear', async (req, res) => { let filter = {}; const { conversationId } = req.body.arg; diff --git a/api/server/routes/handlers.js b/api/server/routes/handlers.js index d3e613bccc..7c6d6ce315 100644 --- a/api/server/routes/handlers.js +++ b/api/server/routes/handlers.js @@ -1,4 +1,4 @@ -const { citeText, detectCode } = require('../../app/'); +const { titleConvo, citeText, detectCode } = require('../../app/'); const _ = require('lodash'); const sanitizeHtml = require('sanitize-html'); @@ -14,6 +14,23 @@ const sendMessage = (res, message) => { res.write(`event: message\ndata: ${JSON.stringify(message)}\n\n`); }; +const genTitle = async ({ model, text, response }) => { + let title = 'New Chat'; + try { + title = await titleConvo({ + model, + message: text, + response: JSON.stringify(response?.text) + }); + } catch (e) { + console.error(e); + console.log('There was an issue generating title, see error above'); + } + + console.log('CONVERSATION TITLE', title); + return title; +}; + const createOnProgress = () => { let i = 0; let tokens = ''; @@ -56,4 +73,4 @@ const handleText = async (input) => { return text; }; -module.exports = { handleError, sendMessage, createOnProgress, handleText }; \ No newline at end of file +module.exports = { handleError, sendMessage, createOnProgress, genTitle, handleText }; diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index eaf0a0cb6d..d9b5b2a80b 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -3,7 +3,6 @@ import Spinner from '../svg/Spinner'; import { CSSTransition } from 'react-transition-group'; import ScrollToBottom from './ScrollToBottom'; import MultiMessage from './MultiMessage'; -import buildTree from '~/utils/buildTree'; import { useSelector } from 'react-redux'; const Messages = ({ messages, messageTree }) => { From a8aad30fc8f4d6bc75347cf7898f91946d1a9aab Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 15 Mar 2023 14:36:17 -0400 Subject: [PATCH 43/47] chore: memoized Messages component, will require custom equality check --- api/server/routes/handlers.js | 21 +++++++++++++++------ client/src/components/Messages/index.jsx | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/api/server/routes/handlers.js b/api/server/routes/handlers.js index 7c6d6ce315..e3bd72583f 100644 --- a/api/server/routes/handlers.js +++ b/api/server/routes/handlers.js @@ -1,6 +1,7 @@ -const { titleConvo, citeText, detectCode } = require('../../app/'); const _ = require('lodash'); const sanitizeHtml = require('sanitize-html'); +const { titleConvo, citeText, detectCode } = require('../../app/'); +const htmlTagRegex = /(<\/?\s*[a-zA-Z]*\s*(?:\s+[a-zA-Z]+\s*=\s*(?:"[^"]*"|'[^']*'))*\s*(?:\/?)>|<\s*[a-zA-Z]+\s*(?:\s+[a-zA-Z]+\s*=\s*(?:"[^"]*"|'[^']*'))*\s*(?:\/?>|<\/?>))/g; const handleError = (res, message) => { res.write(`event: error\ndata: ${JSON.stringify(message)}\n\n`); @@ -42,8 +43,13 @@ const createOnProgress = () => { if (tokens.match(/^\n/)) { tokens = tokens.replace(/^\n/, ''); } - // if (tokens.includes('```')) { - // tokens = sanitizeHtml(tokens); + + // const htmlTags = tokens.match(htmlTagRegex); + // if (tokens.includes('```') && htmlTags && htmlTags.length > 0) { + // htmlTags.forEach((tag) => { + // const sanitizedTag = sanitizeHtml(tag); + // tokens = tokens.replaceAll(tag, sanitizedTag); + // }); // } if (bing) { @@ -65,9 +71,12 @@ const createOnProgress = () => { const handleText = async (input) => { let text = input; text = await detectCode(text); - // if (text.includes('```')) { - // text = sanitizeHtml(text); - // text = text.replaceAll(') =>', ') =>'); + // const htmlTags = text.match(htmlTagRegex); + // if (text.includes('```') && htmlTags && htmlTags.length > 0) { + // htmlTags.forEach((tag) => { + // const sanitizedTag = sanitizeHtml(tag); + // text = text.replaceAll(tag, sanitizedTag); + // }); // } return text; diff --git a/client/src/components/Messages/index.jsx b/client/src/components/Messages/index.jsx index d9b5b2a80b..611a0231d3 100644 --- a/client/src/components/Messages/index.jsx +++ b/client/src/components/Messages/index.jsx @@ -95,4 +95,4 @@ const Messages = ({ messages, messageTree }) => { ); }; -export default Messages; +export default React.memo(Messages); From a0c94715ce99321441d0276e8733ca372d03ea42 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 15 Mar 2023 15:21:04 -0400 Subject: [PATCH 44/47] chore: refactor titleConvo --- api/app/titleConvo.js | 63 ++++++++++++++++++++-------------- api/server/routes/ask.js | 12 ++----- api/server/routes/askBing.js | 12 ++----- api/server/routes/askSydney.js | 12 ++----- api/server/routes/handlers.js | 19 +--------- 5 files changed, 47 insertions(+), 71 deletions(-) diff --git a/api/app/titleConvo.js b/api/app/titleConvo.js index 344af6f893..e37fcb3b0c 100644 --- a/api/app/titleConvo.js +++ b/api/app/titleConvo.js @@ -16,33 +16,44 @@ const proxyEnvToAxiosProxy = (proxyString) => { return proxyConfig; }; -const titleConvo = async ({ message, response, model }) => { - const configuration = new Configuration({ - apiKey: process.env.OPENAI_KEY - }); - const openai = new OpenAIApi(configuration); - const completion = await openai.createChatCompletion( - { - model: 'gpt-3.5-turbo', - messages: [ - { - role: 'system', - content: - 'You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user in title case, using the same language.' - }, - { - role: 'user', - content: `In 5 words or less, summarize the conversation below with a title in title case using the language the user writes in. Don't refer to the participants of the conversation by name. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title. Conversation:\n\nUser: "${message}"\n\n${model}: "${response}"\n\nTitle: ` - } - ] - }, - { proxy: proxyEnvToAxiosProxy(process.env.PROXY || null) } - ); +const titleConvo = async ({ model, text, response }) => { + let title = 'New Chat'; + try { + const configuration = new Configuration({ + apiKey: process.env.OPENAI_KEY + }); + const openai = new OpenAIApi(configuration); + const completion = await openai.createChatCompletion( + { + model: 'gpt-3.5-turbo', + messages: [ + { + role: 'system', + content: + 'You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user in title case, using the same language.' + }, + { + role: 'user', + content: `In 5 words or less, summarize the conversation below with a title in title case using the language the user writes in. Don't refer to the participants of the conversation by name. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title. Conversation:\n\nUser: "${text}"\n\n${model}: "${JSON.stringify( + response?.text + )}"\n\nTitle: ` + } + ] + }, + { proxy: proxyEnvToAxiosProxy(process.env.PROXY || null) } + ); - //eslint-disable-next-line - return completion.data.choices[0].message.content.replace(/["\.]/g, ''); + //eslint-disable-next-line + title = completion.data.choices[0].message.content.replace(/["\.]/g, ''); + } catch (e) { + console.error(e); + console.log('There was an issue generating title, see error above'); + } + + console.log('CONVERSATION TITLE', title); + return title; }; -const debouncedTitleConvo = _.debounce(titleConvo, 500); +const throttledTitleConvo = _.throttle(titleConvo, 1000); -module.exports = debouncedTitleConvo; +module.exports = throttledTitleConvo; diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index 1d497c86b8..69c180c8a7 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -3,15 +3,9 @@ const crypto = require('crypto'); const router = express.Router(); const askBing = require('./askBing'); const askSydney = require('./askSydney'); -const { askClient, browserClient, customClient } = require('../../app/'); +const { titleConvo, askClient, browserClient, customClient } = require('../../app/'); const { getConvo, saveMessage, getConvoTitle, saveConvo } = require('../../models'); -const { - handleError, - sendMessage, - createOnProgress, - genTitle, - handleText -} = require('./handlers'); +const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); const { getMessages } = require('../../models/Message'); router.use('/bing', askBing); @@ -192,7 +186,7 @@ const ask = async ({ res.end(); if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { - const title = await genTitle({ model, text, response: gptResponse }); + const title = await titleConvo({ model, text, response: gptResponse }); await saveConvo({ conversationId, diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 681332ec37..aa052c31d7 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -1,15 +1,9 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); -const { getCitations, citeText, askBing } = require('../../app/'); +const { titleConvo, getCitations, citeText, askBing } = require('../../app/'); const { saveMessage, getConvoTitle, saveConvo } = require('../../models'); -const { - handleError, - sendMessage, - createOnProgress, - genTitle, - handleText -} = require('./handlers'); +const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { @@ -152,7 +146,7 @@ const ask = async ({ res.end(); if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { - const title = await genTitle({ model, text, response }); + const title = await titleConvo({ model, text, response }); await saveConvo({ conversationId, diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 40aa967bcc..7fecc2a450 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -1,15 +1,9 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); -const { getCitations, citeText, askSydney } = require('../../app/'); +const { titleConvo, getCitations, citeText, askSydney } = require('../../app/'); const { saveMessage, saveConvo, getConvoTitle } = require('../../models'); -const { - handleError, - sendMessage, - createOnProgress, - genTitle, - handleText -} = require('./handlers'); +const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { @@ -164,7 +158,7 @@ const ask = async ({ res.end(); if (userParentMessageId == '00000000-0000-0000-0000-000000000000') { - const title = await genTitle({ model, text, response }); + const title = await titleConvo({ model, text, response }); await saveConvo({ conversationId, diff --git a/api/server/routes/handlers.js b/api/server/routes/handlers.js index e3bd72583f..31213f3080 100644 --- a/api/server/routes/handlers.js +++ b/api/server/routes/handlers.js @@ -15,23 +15,6 @@ const sendMessage = (res, message) => { res.write(`event: message\ndata: ${JSON.stringify(message)}\n\n`); }; -const genTitle = async ({ model, text, response }) => { - let title = 'New Chat'; - try { - title = await titleConvo({ - model, - message: text, - response: JSON.stringify(response?.text) - }); - } catch (e) { - console.error(e); - console.log('There was an issue generating title, see error above'); - } - - console.log('CONVERSATION TITLE', title); - return title; -}; - const createOnProgress = () => { let i = 0; let tokens = ''; @@ -82,4 +65,4 @@ const handleText = async (input) => { return text; }; -module.exports = { handleError, sendMessage, createOnProgress, genTitle, handleText }; +module.exports = { handleError, sendMessage, createOnProgress, handleText }; From 84b104e65fad1dfa328787ff03eeba554e680022 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 15 Mar 2023 15:44:48 -0400 Subject: [PATCH 45/47] chore: delegate response text parsing to one location --- api/server/routes/ask.js | 2 +- api/server/routes/askBing.js | 10 ++-------- api/server/routes/askSydney.js | 11 ++-------- api/server/routes/handlers.js | 20 +++++++++++++++---- .../src/components/Messages/TextWrapper.jsx | 2 +- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js index 69c180c8a7..ad2a7178c2 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -162,7 +162,7 @@ const ask = async ({ gptResponse.sender = model === 'chatgptCustom' ? convo.chatGptLabel : model; gptResponse.model = model; // gptResponse.final = true; - gptResponse.text = await handleText(gptResponse.text); + gptResponse.text = await handleText(gptResponse); if (convo.chatGptLabel?.length > 0 && model === 'chatgptCustom') { gptResponse.chatGptLabel = convo.chatGptLabel; diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index aa052c31d7..cfbac77185 100644 --- a/api/server/routes/askBing.js +++ b/api/server/routes/askBing.js @@ -1,10 +1,9 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); -const { titleConvo, getCitations, citeText, askBing } = require('../../app/'); +const { titleConvo, askBing } = require('../../app/'); const { saveMessage, getConvoTitle, saveConvo } = require('../../models'); const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); -const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { const { @@ -129,12 +128,7 @@ const ask = async ({ response.parentMessageId = overrideParentMessageId || response.parentMessageId || userMessageId; - const links = getCitations(response); - response.text = - citeText(response) + - (links?.length > 0 && hasCitations ? `\n${links}` : ''); - response.text = await handleText(response.text); - + response.text = await handleText(response, true); await saveMessage(response); await saveConvo({ ...response, model, chatGptLabel: null, promptPrefix: null, ...convo }); sendMessage(res, { diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 7fecc2a450..00f287fa7b 100644 --- a/api/server/routes/askSydney.js +++ b/api/server/routes/askSydney.js @@ -1,10 +1,9 @@ const express = require('express'); const crypto = require('crypto'); const router = express.Router(); -const { titleConvo, getCitations, citeText, askSydney } = require('../../app/'); +const { titleConvo, askSydney } = require('../../app/'); const { saveMessage, saveConvo, getConvoTitle } = require('../../models'); const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers'); -const citationRegex = /\[\^\d+?\^]/g; router.post('/', async (req, res) => { const { @@ -97,7 +96,6 @@ const ask = async ({ console.log('SYDNEY RESPONSE', response); // console.dir(response, { depth: null }); - const hasCitations = response.response.match(citationRegex)?.length > 0; userMessage.conversationSignature = convo.conversationSignature || response.conversationSignature; @@ -125,12 +123,6 @@ const ask = async ({ response.parentMessageId = overrideParentMessageId || response.parentMessageId || userMessageId; - const links = getCitations(response); - response.text = - citeText(response) + - (links?.length > 0 && hasCitations ? `\n${links}` : ''); - response.text = await handleText(response.text); - // Save user message userMessage.conversationId = response.conversationId || conversationId; await saveMessage(userMessage); @@ -146,6 +138,7 @@ const ask = async ({ }); conversationId = userMessage.conversationId; + response.text = await handleText(response, true); // Save sydney response & convo, then send await saveMessage(response); await saveConvo({ ...response, model, chatGptLabel: null, promptPrefix: null, ...convo }); diff --git a/api/server/routes/handlers.js b/api/server/routes/handlers.js index 31213f3080..3ba4e81243 100644 --- a/api/server/routes/handlers.js +++ b/api/server/routes/handlers.js @@ -1,7 +1,8 @@ const _ = require('lodash'); const sanitizeHtml = require('sanitize-html'); -const { titleConvo, citeText, detectCode } = require('../../app/'); -const htmlTagRegex = /(<\/?\s*[a-zA-Z]*\s*(?:\s+[a-zA-Z]+\s*=\s*(?:"[^"]*"|'[^']*'))*\s*(?:\/?)>|<\s*[a-zA-Z]+\s*(?:\s+[a-zA-Z]+\s*=\s*(?:"[^"]*"|'[^']*'))*\s*(?:\/?>|<\/?>))/g; +const citationRegex = /\[\^\d+?\^]/g; +const { getCitations, citeText, detectCode } = require('../../app/'); +// const htmlTagRegex = /(<\/?\s*[a-zA-Z]*\s*(?:\s+[a-zA-Z]+\s*=\s*(?:"[^"]*"|'[^']*'))*\s*(?:\/?)>|<\s*[a-zA-Z]+\s*(?:\s+[a-zA-Z]+\s*=\s*(?:"[^"]*"|'[^']*'))*\s*(?:\/?>|<\/?>))/g; const handleError = (res, message) => { res.write(`event: error\ndata: ${JSON.stringify(message)}\n\n`); @@ -51,9 +52,20 @@ const createOnProgress = () => { return onProgress; }; -const handleText = async (input) => { - let text = input; +const handleText = async (response, bing = false) => { + let { text } = response; text = await detectCode(text); + response.text = text; + + if (bing) { + // const hasCitations = response.response.match(citationRegex)?.length > 0; + const links = getCitations(response); + if (response.text.match(citationRegex)?.length > 0) { + text = citeText(response); + } + text += links?.length > 0 ? `\n${links}` : ''; + } + // const htmlTags = text.match(htmlTagRegex); // if (text.includes('```') && htmlTags && htmlTags.length > 0) { // htmlTags.forEach((tag) => { diff --git a/client/src/components/Messages/TextWrapper.jsx b/client/src/components/Messages/TextWrapper.jsx index af38b56a5c..5719ee876b 100644 --- a/client/src/components/Messages/TextWrapper.jsx +++ b/client/src/components/Messages/TextWrapper.jsx @@ -142,7 +142,7 @@ export default function TextWrapper({ text }) { // map over the parts and wrap any text between tildes with tags const parts = text.split(markupRegex); const codeParts = inLineWrap(parts); - return <>{codeParts}; // return the wrapped text + return {codeParts}; // return the wrapped text } else { return {text}; } From 8c6340aed05b87784e43f9ae13c8b55efcf9dbba Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 15 Mar 2023 16:38:01 -0400 Subject: [PATCH 46/47] chore: refactor cursor blink, debugging --- client/src/components/Messages/Message.jsx | 179 +++++++++++------- .../src/components/Messages/TextWrapper.jsx | 19 +- client/src/style.css | 1 - 3 files changed, 121 insertions(+), 78 deletions(-) diff --git a/client/src/components/Messages/Message.jsx b/client/src/components/Messages/Message.jsx index af60fc1a9a..622b907454 100644 --- a/client/src/components/Messages/Message.jsx +++ b/client/src/components/Messages/Message.jsx @@ -1,10 +1,9 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; import TextWrapper from './TextWrapper'; import MultiMessage from './MultiMessage'; import { useSelector, useDispatch } from 'react-redux'; import HoverButtons from './HoverButtons'; import SiblingSwitch from './SiblingSwitch'; -import Spinner from '../svg/Spinner'; import { setError } from '~/store/convoSlice'; import { setMessages } from '~/store/messageSlice'; import { setSubmitState, setSubmission } from '~/store/submitSlice'; @@ -22,21 +21,28 @@ export default function Message({ siblingCount, setSiblingIdx }) { - const { isSubmitting, model, chatGptLabel, promptPrefix } = useSelector((state) => state.submit); + const { isSubmitting, model, chatGptLabel, promptPrefix } = useSelector( + (state) => state.submit + ); const [abortScroll, setAbort] = useState(false); - const { sender, text, isCreatedByUser, error, submitting } = message - const textEditor = useRef(null) + const { sender, text, isCreatedByUser, error, submitting } = message; + const textEditor = useRef(null); const convo = useSelector((state) => state.convo); const { initial } = useSelector((state) => state.models); const { error: convoError } = convo; - const last = !message?.children?.length - + const last = !message?.children?.length; const edit = message.messageId == currentEditId; - const dispatch = useDispatch(); // const notUser = !isCreatedByUser; // sender.toLowerCase() !== 'user'; const blinker = submitting && isSubmitting && last && !isCreatedByUser; + const generateCursor = useCallback(() => { + if (!blinker) { + return ''; + } + + return ; + }, [blinker]); useEffect(() => { if (blinker && !abortScroll) { @@ -45,15 +51,10 @@ export default function Message({ }, [isSubmitting, text, blinker, scrollToBottom, abortScroll]); useEffect(() => { - if (last) - dispatch(setConversation({parentMessageId: message?.messageId})) - }, [last, ]) + if (last) dispatch(setConversation({ parentMessageId: message?.messageId })); + }, [last]); - // if (sender === '') { - // return ; - // } - - const enterEdit = (cancel) => setCurrentEditId(cancel?-1:message.messageId) + const enterEdit = (cancel) => setCurrentEditId(cancel ? -1 : message.messageId); const handleWheel = () => { if (blinker) { @@ -67,17 +68,24 @@ export default function Message({ className: 'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 bg-white dark:text-gray-100 group dark:bg-gray-800' }; - - const icon = getIconOfModel({ sender, isCreatedByUser, model, chatGptLabel, promptPrefix, error }); - + + const icon = getIconOfModel({ + sender, + isCreatedByUser, + model, + chatGptLabel, + promptPrefix, + error + }); + if (!isCreatedByUser) props.className = '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]'; - const wrapText = (text) => ; + // const wrapText = (text) => ; const resubmitMessage = () => { - const text = textEditor.current.innerText + const text = textEditor.current.innerText; if (convoError) { dispatch(setError(false)); @@ -90,14 +98,23 @@ export default function Message({ // this is not a real messageId, it is used as placeholder before real messageId returned const fakeMessageId = crypto.randomUUID(); const isCustomModel = model === 'chatgptCustom' || !initial[model]; - const currentMsg = { - sender: 'User', text: text.trim(), current: true, isCreatedByUser: true, - parentMessageId: message?.parentMessageId, - conversationId: message?.conversationId, - messageId: fakeMessageId }; + const currentMsg = { + sender: 'User', + text: text.trim(), + current: true, + isCreatedByUser: true, + parentMessageId: message?.parentMessageId, + conversationId: message?.conversationId, + messageId: fakeMessageId + }; const sender = model === 'chatgptCustom' ? chatGptLabel : model; - const initialResponse = { sender, text: '', parentMessageId: fakeMessageId, submitting: true }; + const initialResponse = { + sender, + text: '', + parentMessageId: fakeMessageId, + submitting: true + }; dispatch(setSubmitState(true)); dispatch(setMessages([...messages, currentMsg, initialResponse])); @@ -105,22 +122,22 @@ export default function Message({ const submission = { isCustomModel, - message: { - ...currentMsg, + message: { + ...currentMsg, model, chatGptLabel, - promptPrefix, + promptPrefix }, messages: messages, currentMsg, initialResponse, - sender, + sender }; console.log('User Input:', currentMsg?.text); // handleSubmit(submission); dispatch(setSubmission(submission)); - setSiblingIdx(siblingCount - 1) + setSiblingIdx(siblingCount - 1); enterEdit(true); }; @@ -131,68 +148,84 @@ export default function Message({ onWheel={handleWheel} >
-
{typeof icon === 'string' && icon.match(/[^\u0000-\u007F]+/) ? ( {icon} ) : ( icon )} - +
{error ? ( -
+
{`An error occurred. Please try again in a few moments.\n\nError message: ${text}`}
- ) : - edit ? ( -
- {/*
*/} - -
- {text} -
-
- - -
+ ) : edit ? ( +
+ {/*
*/} + +
+ {text}
- ) : ( -
- {/*
*/} -
- {!isCreatedByUser ? wrapText(text) : text} - {blinker && } -
+
+ +
- )} +
+ ) : ( +
+ {/*
*/} +
+ {!isCreatedByUser ? ( + + ) : ( + text + )} +
+
+ )}
- enterEdit()}/> + enterEdit()} + />
); diff --git a/client/src/components/Messages/TextWrapper.jsx b/client/src/components/Messages/TextWrapper.jsx index 5719ee876b..75766bca0c 100644 --- a/client/src/components/Messages/TextWrapper.jsx +++ b/client/src/components/Messages/TextWrapper.jsx @@ -46,8 +46,9 @@ const inLineWrap = (parts) => { }); }; -export default function TextWrapper({ text }) { +export default function TextWrapper({ text, generateCursor }) { let embedTest = false; + let result = null; // to match unenclosed code blocks if (text.match(/```/g)?.length === 1) { @@ -137,13 +138,23 @@ export default function TextWrapper({ text }) { } }); - return <>{codeParts}; // return the wrapped text + // return <>{codeParts}; // return the wrapped text + result = <>{codeParts}; } else if (text.match(markupRegex)) { // map over the parts and wrap any text between tildes with tags const parts = text.split(markupRegex); const codeParts = inLineWrap(parts); - return {codeParts}; // return the wrapped text + // return <>{codeParts}; // return the wrapped text + result = <>{codeParts}; } else { - return {text}; + // return {text}; + result = {text}; } + + return ( + <> + {result} + {(<>{generateCursor()})} + + ); } diff --git a/client/src/style.css b/client/src/style.css index c4a03408b7..30fae19b40 100644 --- a/client/src/style.css +++ b/client/src/style.css @@ -1251,7 +1251,6 @@ html { vertical-align: baseline; } - /* .result-streaming>:not(ol):not(ul):not(pre):last-child:after, .result-streaming>ol:last-child li:last-child:after, .result-streaming>pre:last-child code:after, From d052d221dc3c102979f9df017d7cc78e193c0998 Mon Sep 17 00:00:00 2001 From: Daniel Avila Date: Wed, 15 Mar 2023 18:05:34 -0400 Subject: [PATCH 47/47] chore: switch focus to textarea when custom model change (still need to figure out reg model change) --- client/src/components/Main/TextChat.jsx | 4 ++-- client/src/components/Messages/TextWrapper.jsx | 2 +- client/src/components/Models/ModelMenu.jsx | 11 ++++------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/client/src/components/Main/TextChat.jsx b/client/src/components/Main/TextChat.jsx index e0363cd8b1..0de24e7f52 100644 --- a/client/src/components/Main/TextChat.jsx +++ b/client/src/components/Main/TextChat.jsx @@ -28,7 +28,7 @@ export default function TextChat({ messages }) { // auto focus to input, when enter a conversation. useEffect(() => { inputRef.current?.focus(); - }, [convo?.conversationId, ]) + }, [convo?.conversationId,]) const messageHandler = (data, currentState, currentMsg) => { const { messages, _currentMsg, message, sender } = currentState; @@ -185,11 +185,11 @@ export default function TextChat({ messages }) { sender, }; console.log('User Input:', message); - // handleSubmit(submission); dispatch(setSubmission(submission)); }; useEffect(() => { + inputRef.current?.focus(); if (Object.keys(submission).length === 0) { return; } diff --git a/client/src/components/Messages/TextWrapper.jsx b/client/src/components/Messages/TextWrapper.jsx index 75766bca0c..3cfd0a1b37 100644 --- a/client/src/components/Messages/TextWrapper.jsx +++ b/client/src/components/Messages/TextWrapper.jsx @@ -154,7 +154,7 @@ export default function TextWrapper({ text, generateCursor }) { return ( <> {result} - {(<>{generateCursor()})} + {generateCursor()} ); } diff --git a/client/src/components/Models/ModelMenu.jsx b/client/src/components/Models/ModelMenu.jsx index 4e47ccbb1b..3e3333a7cc 100644 --- a/client/src/components/Models/ModelMenu.jsx +++ b/client/src/components/Models/ModelMenu.jsx @@ -65,14 +65,13 @@ export default function ModelMenu() { localStorage.setItem('model', JSON.stringify(model)); }, [model]); - const onChange = (value, custom = false) => { + const onChange = (value) => { if (!value) { return; } else if (value === model) { return; } else if (value === 'chatgptCustom') { - // dispatch(setMessages([])); - return; + // return; } else if (initial[value]) { dispatch(setModel(value)); dispatch(setDisabled(false)); @@ -84,9 +83,7 @@ export default function ModelMenu() { dispatch(setCustomGpt({ chatGptLabel, promptPrefix })); dispatch(setModel('chatgptCustom')); dispatch(setCustomModel(value)); - // if (custom) { - // setMenuOpen((prevOpen) => !prevOpen); - // } + setMenuOpen(false); } else if (!modelMap[value]) { dispatch(setCustomModel(null)); } @@ -160,7 +157,7 @@ export default function ModelMenu() { {icon} - + event.preventDefault()}> Select a Model